/* * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, * Version 1.0, and under the Eclipse Public License, Version 1.0 * (http://h2database.com/html/license.html). * Initial Developer: H2 Group */ package org.h2.message; import java.io.ByteArrayInputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.sql.SQLException; import java.text.MessageFormat; import java.util.Locale; import java.util.Properties; import java.util.Map.Entry; import org.h2.constant.ErrorCode; import org.h2.jdbc.JdbcSQLException; import org.h2.util.SortedProperties; import org.h2.util.StringUtils; import org.h2.util.Utils; /** * This exception wraps a checked exception. * It is used in methods where checked exceptions are not supported, * for example in a Comparator. */ public class DbException extends RuntimeException { private static final long serialVersionUID = 1L; private static final Properties MESSAGES = new Properties(); private Object source; static { try { byte[] messages = Utils.getResource("/org/h2/res/_messages_en.prop"); if (messages != null) { MESSAGES.load(new ByteArrayInputStream(messages)); } String language = Locale.getDefault().getLanguage(); if (!"en".equals(language)) { byte[] translations = Utils.getResource("/org/h2/res/_messages_" + language + ".prop"); // message: translated message + english // (otherwise certain applications don't work) if (translations != null) { Properties p = SortedProperties.fromLines(new String(translations, "UTF-8")); for (Entry<Object, Object> e : p.entrySet()) { String key = (String) e.getKey(); String translation = (String) e.getValue(); if (translation != null && !translation.startsWith("#")) { String original = MESSAGES.getProperty(key); String message = translation + "\n" + original; MESSAGES.put(key, message); } } } } } catch (IOException e) { TraceSystem.traceThrowable(e); } } private DbException(SQLException e) { super(e.getMessage(), e); } private static String translate(String key, String... params) { String message = null; if (MESSAGES != null) { // Tomcat sets final static fields to null sometimes message = MESSAGES.getProperty(key); } if (message == null) { message = "(Message " + key + " not found)"; } if (params != null) { for (int i = 0; i < params.length; i++) { String s = params[i]; if (s != null && s.length() > 0) { params[i] = StringUtils.quoteIdentifier(s); } } message = MessageFormat.format(message, (Object[]) params); } return message; } /** * Get the SQLException object. * * @return the exception */ public SQLException getSQLException() { return (SQLException) getCause(); } /** * Get the error code. * * @return the error code */ public int getErrorCode() { return getSQLException().getErrorCode(); } /** * Set the SQL statement of the given exception. * This method may create a new object. * * @param sql the SQL statement * @return the exception */ public DbException addSQL(String sql) { SQLException e = getSQLException(); if (e instanceof JdbcSQLException) { JdbcSQLException j = (JdbcSQLException) e; if (j.getSQL() == null) { j.setSQL(sql); } return this; } e = new JdbcSQLException(e.getMessage(), sql, e.getSQLState(), e.getErrorCode(), e, null); return new DbException(e); } /** * Create a database exception for a specific error code. * * @param errorCode the error code * @return the exception */ public static DbException get(int errorCode) { return get(errorCode, (String) null); } /** * Create a database exception for a specific error code. * * @param errorCode the error code * @param p1 the first parameter of the message * @return the exception */ public static DbException get(int errorCode, String p1) { return get(errorCode, new String[] { p1 }); } /** * Create a database exception for a specific error code. * * @param errorCode the error code * @param cause the cause of the exception * @param params the list of parameters of the message * @return the exception */ public static DbException get(int errorCode, Throwable cause, String... params) { return new DbException(getJdbcSQLException(errorCode, cause, params)); } /** * Create a database exception for a specific error code. * * @param errorCode the error code * @param params the list of parameters of the message * @return the exception */ public static DbException get(int errorCode, String... params) { return new DbException(getJdbcSQLException(errorCode, null, params)); } /** * Create a syntax error exception. * * @param sql the SQL statement * @param index the position of the error in the SQL statement * @return the exception */ public static DbException getSyntaxError(String sql, int index) { sql = StringUtils.addAsterisk(sql, index); return get(ErrorCode.SYNTAX_ERROR_1, sql); } /** * Create a syntax error exception. * * @param sql the SQL statement * @param index the position of the error in the SQL statement * @param expected the expected keyword at the given position * @return the exception */ public static DbException getSyntaxError(String sql, int index, String expected) { sql = StringUtils.addAsterisk(sql, index); return get(ErrorCode.SYNTAX_ERROR_2, sql, expected); } /** * Gets a SQL exception meaning this feature is not supported. * * @param message what exactly is not supported * @return the exception */ public static DbException getUnsupportedException(String message) { return get(ErrorCode.FEATURE_NOT_SUPPORTED_1, message); } /** * Gets a SQL exception meaning this value is invalid. * * @param param the name of the parameter * @param value the value passed * @return the IllegalArgumentException object */ public static DbException getInvalidValueException(String param, Object value) { return get(ErrorCode.INVALID_VALUE_2, value == null ? "null" : value.toString(), param); } /** * Throw an internal error. This method seems to return an exception object, * so that it can be used instead of 'return', but in fact it always throws * the exception. * * @param s the message * @return the RuntimeException object * @throws RuntimeException the exception */ public static RuntimeException throwInternalError(String s) { RuntimeException e = new RuntimeException(s); TraceSystem.traceThrowable(e); throw e; } /** * Throw an internal error. This method seems to return an exception object, * so that it can be used instead of 'return', but in fact it always throws * the exception. * * @return the RuntimeException object */ public static RuntimeException throwInternalError() { return throwInternalError("Unexpected code path"); } /** * Convert an exception to a SQL exception using the default mapping. * * @param e the root cause * @return the SQL exception object */ public static SQLException toSQLException(Exception e) { if (e instanceof SQLException) { return (SQLException) e; } return convert(e).getSQLException(); } /** * Convert a throwable to an SQL exception using the default mapping. All * errors except the following are re-thrown: StackOverflowError, * LinkageError. * * @param e the root cause * @return the exception object */ public static DbException convert(Throwable e) { if (e instanceof DbException) { return (DbException) e; } else if (e instanceof SQLException) { return new DbException((SQLException) e); } else if (e instanceof InvocationTargetException) { return convertInvocation((InvocationTargetException) e, null); } else if (e instanceof IOException) { return get(ErrorCode.IO_EXCEPTION_1, e, e.toString()); } else if (e instanceof OutOfMemoryError) { return get(ErrorCode.OUT_OF_MEMORY, e); } else if (e instanceof StackOverflowError || e instanceof LinkageError) { return get(ErrorCode.GENERAL_ERROR_1, e, e.toString()); } else if (e instanceof Error) { throw (Error) e; } return get(ErrorCode.GENERAL_ERROR_1, e, e.toString()); } /** * Convert an InvocationTarget exception to a database exception. * * @param te the root cause * @param message the added message or null * @return the database exception object */ public static DbException convertInvocation(InvocationTargetException te, String message) { Throwable t = te.getTargetException(); if (t instanceof SQLException || t instanceof DbException) { return convert(t); } message = message == null ? t.getMessage() : message + ": " + t.getMessage(); return get(ErrorCode.EXCEPTION_IN_FUNCTION_1, t, message); } /** * Convert an IO exception to a database exception. * * @param e the root cause * @param message the message or null * @return the database exception object */ public static DbException convertIOException(IOException e, String message) { if (message == null) { Throwable t = e.getCause(); if (t != null && t instanceof DbException) { return (DbException) t; } return get(ErrorCode.IO_EXCEPTION_1, e, e.toString()); } return get(ErrorCode.IO_EXCEPTION_2, e, e.toString(), message); } /** * Gets the SQL exception object for a specific error code. * * @param errorCode the error code * @param cause the cause of the exception * @param params the list of parameters of the message * @return the SQLException object */ private static JdbcSQLException getJdbcSQLException(int errorCode, Throwable cause, String... params) { String sqlstate = ErrorCode.getState(errorCode); String message = translate(sqlstate, params); return new JdbcSQLException(message, null, sqlstate, errorCode, cause, null); } /** * Convert an exception to an IO exception. * * @param e the root cause * @return the IO exception */ public static IOException convertToIOException(Throwable e) { if (e instanceof IOException) { return (IOException) e; } if (e instanceof JdbcSQLException) { JdbcSQLException e2 = (JdbcSQLException) e; if (e2.getOriginalCause() != null) { e = e2.getOriginalCause(); } } IOException io = new IOException(e.toString()); io.initCause(e); return io; } public Object getSource() { return source; } public void setSource(Object source) { this.source = source; } }