1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.message; |
7 | |
8 | import java.io.ByteArrayInputStream; |
9 | import java.io.IOException; |
10 | import java.io.PrintWriter; |
11 | import java.lang.reflect.InvocationTargetException; |
12 | import java.sql.DriverManager; |
13 | import java.sql.SQLException; |
14 | import java.text.MessageFormat; |
15 | import java.util.Locale; |
16 | import java.util.Map.Entry; |
17 | import java.util.Properties; |
18 | |
19 | import org.h2.api.ErrorCode; |
20 | import org.h2.engine.Constants; |
21 | import org.h2.jdbc.JdbcSQLException; |
22 | import org.h2.util.SortedProperties; |
23 | import org.h2.util.StringUtils; |
24 | import org.h2.util.Utils; |
25 | |
26 | /** |
27 | * This exception wraps a checked exception. |
28 | * It is used in methods where checked exceptions are not supported, |
29 | * for example in a Comparator. |
30 | */ |
31 | public class DbException extends RuntimeException { |
32 | |
33 | private static final long serialVersionUID = 1L; |
34 | |
35 | private static final Properties MESSAGES = new Properties(); |
36 | |
37 | private Object source; |
38 | |
39 | static { |
40 | try { |
41 | byte[] messages = Utils.getResource( |
42 | "/org/h2/res/_messages_en.prop"); |
43 | if (messages != null) { |
44 | MESSAGES.load(new ByteArrayInputStream(messages)); |
45 | } |
46 | String language = Locale.getDefault().getLanguage(); |
47 | if (!"en".equals(language)) { |
48 | byte[] translations = Utils.getResource( |
49 | "/org/h2/res/_messages_" + language + ".prop"); |
50 | // message: translated message + english |
51 | // (otherwise certain applications don't work) |
52 | if (translations != null) { |
53 | Properties p = SortedProperties.fromLines( |
54 | new String(translations, Constants.UTF8)); |
55 | for (Entry<Object, Object> e : p.entrySet()) { |
56 | String key = (String) e.getKey(); |
57 | String translation = (String) e.getValue(); |
58 | if (translation != null && !translation.startsWith("#")) { |
59 | String original = MESSAGES.getProperty(key); |
60 | String message = translation + "\n" + original; |
61 | MESSAGES.put(key, message); |
62 | } |
63 | } |
64 | } |
65 | } |
66 | } catch (OutOfMemoryError e) { |
67 | DbException.traceThrowable(e); |
68 | } catch (IOException e) { |
69 | DbException.traceThrowable(e); |
70 | } |
71 | } |
72 | |
73 | private DbException(SQLException e) { |
74 | super(e.getMessage(), e); |
75 | } |
76 | |
77 | private static String translate(String key, String... params) { |
78 | String message = null; |
79 | if (MESSAGES != null) { |
80 | // Tomcat sets final static fields to null sometimes |
81 | message = MESSAGES.getProperty(key); |
82 | } |
83 | if (message == null) { |
84 | message = "(Message " + key + " not found)"; |
85 | } |
86 | if (params != null) { |
87 | for (int i = 0; i < params.length; i++) { |
88 | String s = params[i]; |
89 | if (s != null && s.length() > 0) { |
90 | params[i] = StringUtils.quoteIdentifier(s); |
91 | } |
92 | } |
93 | message = MessageFormat.format(message, (Object[]) params); |
94 | } |
95 | return message; |
96 | } |
97 | |
98 | /** |
99 | * Get the SQLException object. |
100 | * |
101 | * @return the exception |
102 | */ |
103 | public SQLException getSQLException() { |
104 | return (SQLException) getCause(); |
105 | } |
106 | |
107 | /** |
108 | * Get the error code. |
109 | * |
110 | * @return the error code |
111 | */ |
112 | public int getErrorCode() { |
113 | return getSQLException().getErrorCode(); |
114 | } |
115 | |
116 | /** |
117 | * Set the SQL statement of the given exception. |
118 | * This method may create a new object. |
119 | * |
120 | * @param sql the SQL statement |
121 | * @return the exception |
122 | */ |
123 | public DbException addSQL(String sql) { |
124 | SQLException e = getSQLException(); |
125 | if (e instanceof JdbcSQLException) { |
126 | JdbcSQLException j = (JdbcSQLException) e; |
127 | if (j.getSQL() == null) { |
128 | j.setSQL(sql); |
129 | } |
130 | return this; |
131 | } |
132 | e = new JdbcSQLException(e.getMessage(), sql, e.getSQLState(), |
133 | e.getErrorCode(), e, null); |
134 | return new DbException(e); |
135 | } |
136 | |
137 | /** |
138 | * Create a database exception for a specific error code. |
139 | * |
140 | * @param errorCode the error code |
141 | * @return the exception |
142 | */ |
143 | public static DbException get(int errorCode) { |
144 | return get(errorCode, (String) null); |
145 | } |
146 | |
147 | /** |
148 | * Create a database exception for a specific error code. |
149 | * |
150 | * @param errorCode the error code |
151 | * @param p1 the first parameter of the message |
152 | * @return the exception |
153 | */ |
154 | public static DbException get(int errorCode, String p1) { |
155 | return get(errorCode, new String[] { p1 }); |
156 | } |
157 | |
158 | /** |
159 | * Create a database exception for a specific error code. |
160 | * |
161 | * @param errorCode the error code |
162 | * @param cause the cause of the exception |
163 | * @param params the list of parameters of the message |
164 | * @return the exception |
165 | */ |
166 | public static DbException get(int errorCode, Throwable cause, |
167 | String... params) { |
168 | return new DbException(getJdbcSQLException(errorCode, cause, params)); |
169 | } |
170 | |
171 | /** |
172 | * Create a database exception for a specific error code. |
173 | * |
174 | * @param errorCode the error code |
175 | * @param params the list of parameters of the message |
176 | * @return the exception |
177 | */ |
178 | public static DbException get(int errorCode, String... params) { |
179 | return new DbException(getJdbcSQLException(errorCode, null, params)); |
180 | } |
181 | |
182 | /** |
183 | * Create a syntax error exception. |
184 | * |
185 | * @param sql the SQL statement |
186 | * @param index the position of the error in the SQL statement |
187 | * @return the exception |
188 | */ |
189 | public static DbException getSyntaxError(String sql, int index) { |
190 | sql = StringUtils.addAsterisk(sql, index); |
191 | return get(ErrorCode.SYNTAX_ERROR_1, sql); |
192 | } |
193 | |
194 | /** |
195 | * Create a syntax error exception. |
196 | * |
197 | * @param sql the SQL statement |
198 | * @param index the position of the error in the SQL statement |
199 | * @param message the message |
200 | * @return the exception |
201 | */ |
202 | public static DbException getSyntaxError(String sql, int index, |
203 | String message) { |
204 | sql = StringUtils.addAsterisk(sql, index); |
205 | return new DbException(getJdbcSQLException(ErrorCode.SYNTAX_ERROR_2, |
206 | null, sql, message)); |
207 | } |
208 | |
209 | /** |
210 | * Gets a SQL exception meaning this feature is not supported. |
211 | * |
212 | * @param message what exactly is not supported |
213 | * @return the exception |
214 | */ |
215 | public static DbException getUnsupportedException(String message) { |
216 | return get(ErrorCode.FEATURE_NOT_SUPPORTED_1, message); |
217 | } |
218 | |
219 | /** |
220 | * Gets a SQL exception meaning this value is invalid. |
221 | * |
222 | * @param param the name of the parameter |
223 | * @param value the value passed |
224 | * @return the IllegalArgumentException object |
225 | */ |
226 | public static DbException getInvalidValueException(String param, |
227 | Object value) { |
228 | return get(ErrorCode.INVALID_VALUE_2, |
229 | value == null ? "null" : value.toString(), param); |
230 | } |
231 | |
232 | /** |
233 | * Throw an internal error. This method seems to return an exception object, |
234 | * so that it can be used instead of 'return', but in fact it always throws |
235 | * the exception. |
236 | * |
237 | * @param s the message |
238 | * @return the RuntimeException object |
239 | * @throws RuntimeException the exception |
240 | */ |
241 | public static RuntimeException throwInternalError(String s) { |
242 | RuntimeException e = new RuntimeException(s); |
243 | DbException.traceThrowable(e); |
244 | throw e; |
245 | } |
246 | |
247 | /** |
248 | * Throw an internal error. This method seems to return an exception object, |
249 | * so that it can be used instead of 'return', but in fact it always throws |
250 | * the exception. |
251 | * |
252 | * @return the RuntimeException object |
253 | */ |
254 | public static RuntimeException throwInternalError() { |
255 | return throwInternalError("Unexpected code path"); |
256 | } |
257 | |
258 | /** |
259 | * Convert an exception to a SQL exception using the default mapping. |
260 | * |
261 | * @param e the root cause |
262 | * @return the SQL exception object |
263 | */ |
264 | public static SQLException toSQLException(Exception e) { |
265 | if (e instanceof SQLException) { |
266 | return (SQLException) e; |
267 | } |
268 | return convert(e).getSQLException(); |
269 | } |
270 | |
271 | /** |
272 | * Convert a throwable to an SQL exception using the default mapping. All |
273 | * errors except the following are re-thrown: StackOverflowError, |
274 | * LinkageError. |
275 | * |
276 | * @param e the root cause |
277 | * @return the exception object |
278 | */ |
279 | public static DbException convert(Throwable e) { |
280 | if (e instanceof DbException) { |
281 | return (DbException) e; |
282 | } else if (e instanceof SQLException) { |
283 | return new DbException((SQLException) e); |
284 | } else if (e instanceof InvocationTargetException) { |
285 | return convertInvocation((InvocationTargetException) e, null); |
286 | } else if (e instanceof IOException) { |
287 | return get(ErrorCode.IO_EXCEPTION_1, e, e.toString()); |
288 | } else if (e instanceof OutOfMemoryError) { |
289 | return get(ErrorCode.OUT_OF_MEMORY, e); |
290 | } else if (e instanceof StackOverflowError || e instanceof LinkageError) { |
291 | return get(ErrorCode.GENERAL_ERROR_1, e, e.toString()); |
292 | } else if (e instanceof Error) { |
293 | throw (Error) e; |
294 | } |
295 | return get(ErrorCode.GENERAL_ERROR_1, e, e.toString()); |
296 | } |
297 | |
298 | /** |
299 | * Convert an InvocationTarget exception to a database exception. |
300 | * |
301 | * @param te the root cause |
302 | * @param message the added message or null |
303 | * @return the database exception object |
304 | */ |
305 | public static DbException convertInvocation(InvocationTargetException te, |
306 | String message) { |
307 | Throwable t = te.getTargetException(); |
308 | if (t instanceof SQLException || t instanceof DbException) { |
309 | return convert(t); |
310 | } |
311 | message = message == null ? t.getMessage() : message + ": " + t.getMessage(); |
312 | return get(ErrorCode.EXCEPTION_IN_FUNCTION_1, t, message); |
313 | } |
314 | |
315 | /** |
316 | * Convert an IO exception to a database exception. |
317 | * |
318 | * @param e the root cause |
319 | * @param message the message or null |
320 | * @return the database exception object |
321 | */ |
322 | public static DbException convertIOException(IOException e, String message) { |
323 | if (message == null) { |
324 | Throwable t = e.getCause(); |
325 | if (t instanceof DbException) { |
326 | return (DbException) t; |
327 | } |
328 | return get(ErrorCode.IO_EXCEPTION_1, e, e.toString()); |
329 | } |
330 | return get(ErrorCode.IO_EXCEPTION_2, e, e.toString(), message); |
331 | } |
332 | |
333 | /** |
334 | * Gets the SQL exception object for a specific error code. |
335 | * |
336 | * @param errorCode the error code |
337 | * @param cause the cause of the exception |
338 | * @param params the list of parameters of the message |
339 | * @return the SQLException object |
340 | */ |
341 | private static JdbcSQLException getJdbcSQLException(int errorCode, |
342 | Throwable cause, String... params) { |
343 | String sqlstate = ErrorCode.getState(errorCode); |
344 | String message = translate(sqlstate, params); |
345 | return new JdbcSQLException(message, null, sqlstate, errorCode, cause, null); |
346 | } |
347 | |
348 | /** |
349 | * Convert an exception to an IO exception. |
350 | * |
351 | * @param e the root cause |
352 | * @return the IO exception |
353 | */ |
354 | public static IOException convertToIOException(Throwable e) { |
355 | if (e instanceof IOException) { |
356 | return (IOException) e; |
357 | } |
358 | if (e instanceof JdbcSQLException) { |
359 | JdbcSQLException e2 = (JdbcSQLException) e; |
360 | if (e2.getOriginalCause() != null) { |
361 | e = e2.getOriginalCause(); |
362 | } |
363 | } |
364 | return new IOException(e.toString(), e); |
365 | } |
366 | |
367 | public Object getSource() { |
368 | return source; |
369 | } |
370 | |
371 | public void setSource(Object source) { |
372 | this.source = source; |
373 | } |
374 | |
375 | /** |
376 | * Write the exception to the driver manager log writer if configured. |
377 | * |
378 | * @param e the exception |
379 | */ |
380 | public static void traceThrowable(Throwable e) { |
381 | PrintWriter writer = DriverManager.getLogWriter(); |
382 | if (writer != null) { |
383 | e.printStackTrace(writer); |
384 | } |
385 | } |
386 | |
387 | } |