001: /*
002:
003: Derby - Class org.apache.derby.client.am.SqlException
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to You under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.client.am;
023:
024: import java.sql.SQLException;
025: import java.util.TreeMap;
026:
027: import org.apache.derby.iapi.services.info.JVMInfo;
028: import org.apache.derby.shared.common.i18n.MessageUtil;
029: import org.apache.derby.shared.common.error.ExceptionUtil;
030: import org.apache.derby.shared.common.reference.SQLState;
031:
032: // The signature of the stored procedure SQLCAMessage I have come out so far is as follows:
033: // SQLCAMessage (
034: // IN SQLCode INTEGER,
035: // IN SQLErrml SMALLINT,
036: // IN SQLErrmc VARCHAR(70),
037: // IN SQLErrp CHAR(8),
038: // IN SQLErrd0 INTEGER,
039: // IN SQLErrd1 INTEGER,
040: // IN SQLErrd2 INTEGER,
041: // IN SQLErrd3 INTEGER,
042: // IN SQLErrd4 INTEGER,
043: // IN SQLErrd5 INTEGER,
044: // IN SQLWarn CHAR(11),
045: // IN SQLState CHAR(5),
046: // IN Locale CHAR(5),
047: // IN BufferSize SMALLINT,
048: // IN LineWidth SMALLINT,
049: // OUT Message VARCHAR(2400))
050: //
051: // Some issues have been identified:
052: // 1. What would be the schema name of the stored procedue SQLCAMessage?
053: // 2. What is the format and type of the Locale parameter? If there does, I would really like to know the format of the locale in order to decide the type of the Locale parameter. Even there does not either, the Locale parameter probably still needs to be kept there for future extension, and we need to figure out the format of the locale.
054: // 3. What would be the format of the output message? Is this full message text ok or do we only need the explanation message corresponding to an SQL code. This somehow matters whether we need the Buffersize and Linewidth parameters for the stored procedure.
055: // 4. What if the invocation of stored procedure failed (due to, eg, connection dropping)? In this case, we probably need to return some client-side message.
056: //
057: // Note that this class does NOT extend java.sql.SQLException. This is because
058: // in JDBC 4 there will be multiple subclasses of SQLException defined by the
059: // spec. So we can't also extend SQLException without having to create our
060: // own mirror hierarchy of subclasses.
061: //
062: // When Derby is ready to throw an exception to the application, it catches
063: // SqlException and converts it to a java.sql.SQLException by calling the
064: // method getSQLException.
065: //
066: // It is also possible that internal routines may call public methods.
067: // In these cases, it will need to wrap a java.sql.SQLException inside
068: // a Derby SqlException so that the internal method does not have to throw
069: // java.sql.SQLException. Otherwise the chain of dependencies would quickly
070: // force the majority of internal methods to throw java.sql.SQLException.
071: // You can wrap a java.sql.SQLException inside a SqlException by using
072: // the constructor <code>new SqlException(java.sql.SQLException wrapMe)</code)
073: //
074: public class SqlException extends Exception implements Diagnosable {
075: protected static final int DEFAULT_ERRCODE = 99999;
076: protected Sqlca sqlca_ = null; // for engine generated errors only
077: protected String message_ = null;
078: private String batchPositionLabel_; // for batched exceptions only
079: protected String sqlstate_ = null;
080: protected int errorcode_ = DEFAULT_ERRCODE;
081: protected String causeString_ = null;
082: protected SqlException nextException_;
083: protected Throwable throwable_;
084:
085: public static String CLIENT_MESSAGE_RESOURCE_NAME = "org.apache.derby.loc.clientmessages";
086:
087: // Constants for message ids used in text we print out -- not used
088: // in SqlExceptions
089: public static final String CAUSED_BY_EXCEPTION_ID = "J106";
090: public static final String BATCH_POSITION_ID = "J107";
091:
092: //SQLException factory initialised with default factory
093: //It will be over written by the SQLException factory of the
094: //supported jdbc version
095: protected static SQLExceptionFactory exceptionFactory = new SQLExceptionFactory();
096:
097: /**
098: * The message utility instance we use to find messages
099: * It's primed with the name of the client message bundle so that
100: * it knows to look there if the message isn't found in the
101: * shared message bundle.
102: */
103: private static MessageUtil msgutil_;
104:
105: /**
106: * This routine provides singleton access to an instance of MessageUtil
107: * that is constructed for client messages. It is recommended to use
108: * this singleton rather than create your own instance.
109: *
110: * The only time you need this instance is if you need to directly
111: * format an internationalized message string. In most instances this
112: * is done for you when you invoke a SqlException constructor
113: *
114: * @return a singleton instance of MessageUtil configured for client
115: * messages
116: */
117: public static MessageUtil getMessageUtil() {
118: if (msgutil_ == null) {
119: msgutil_ = new MessageUtil(CLIENT_MESSAGE_RESOURCE_NAME);
120: }
121:
122: return msgutil_;
123: }
124:
125: /**
126: * The wrapped SQLException, if one exists
127: */
128: protected SQLException wrappedException_;
129:
130: //-----------------constructors-----------------------------------------------
131: // New constructors that support internationalized messages
132: // The message id is wrapped inside a class so that we can distinguish
133: // between the signatures of the new constructors and the old constructors
134:
135: /**
136: * Create a SqlException. This constructor is the "base" constructor;
137: * all other constructors (which take a ClientMessageId) delegate to this
138: * constructor
139: *
140: * @param logwriter
141: * Can be null, but if provided, it is used to log this exception
142: *
143: * @param msgid
144: * The message id for this message. ClientMessageId is a simple type-safe
145: * wrapper for org.apache.derby.shared.common.reference.SQLState message id
146: * strings.
147: *
148: * @param args
149: * The set of substitution arguments for the message. The Java message
150: * formatter will substitute these arguments into the internationalized
151: * strings using the substitution ({0}, {1}, etc.) markers in the string.
152: * Any object can be passed, but if you want it to be readable, make sure
153: * toString() for the object returns something useful.
154: *
155: * @param cause
156: * Can be null. Indicates the cause of this exception. If this is
157: * an instance of SqlException or java.sql.SQLException then the exception
158: * is chained into the nextException chain. Otherwise it is chained
159: * using initCause(). On JDK 1.3, since initCause() does not exist,
160: * a non-SQL exception can not be chained. Instead, the exception class
161: * and message text is appended to the message for this exception.
162: */
163: public SqlException(LogWriter logwriter, ClientMessageId msgid,
164: Object[] args, Throwable cause) {
165: this (logwriter, cause, getMessageUtil().getCompleteMessage(
166: msgid.msgid, args), ExceptionUtil
167: .getSQLStateFromIdentifier(msgid.msgid), ExceptionUtil
168: .getSeverityFromIdentifier(msgid.msgid));
169: }
170:
171: // Use the following SQLExceptions when you want to override the error
172: // code that is derived from the severity of the message id.
173: public SqlException(LogWriter logWriter, ClientMessageId msgid,
174: Object[] args, SqlCode sqlcode, Throwable t) {
175: this (logWriter, msgid, args, t);
176: this .errorcode_ = sqlcode.getCode();
177: }
178:
179: public SqlException(LogWriter logWriter, ClientMessageId msgid,
180: Object[] args, SqlCode sqlcode) {
181: this (logWriter, msgid, args, sqlcode, (Throwable) null);
182: }
183:
184: public SqlException(LogWriter logWriter, ClientMessageId msgid,
185: SqlCode sqlcode) {
186: this (logWriter, msgid, (Object[]) null, sqlcode);
187: }
188:
189: public SqlException(LogWriter logWriter, ClientMessageId msgid,
190: Object arg1, SqlCode sqlcode) {
191: this (logWriter, msgid, new Object[] { arg1 }, sqlcode);
192: }
193:
194: public SqlException(LogWriter logWriter, ClientMessageId msgid,
195: Object arg1, Object arg2, SqlCode sqlcode) {
196: this (logWriter, msgid, new Object[] { arg1, arg2 }, sqlcode);
197: }
198:
199: // The following constructors are all wrappers around the base constructor,
200: // created to make it easy to code against them (you don't have to pass
201: // null arguments or construct object arrays). See the javadoc for the
202: // "base" constructor for an explanation of the parameters
203: public SqlException(LogWriter logwriter, ClientMessageId msgid,
204: Throwable cause) {
205: this (logwriter, msgid, (Object[]) null, cause);
206: }
207:
208: public SqlException(LogWriter logwriter, ClientMessageId msgid,
209: Object[] args) {
210: this (logwriter, msgid, args, (Throwable) null);
211: }
212:
213: public SqlException(LogWriter logwriter, ClientMessageId msgid) {
214: this (logwriter, msgid, (Object[]) null);
215: }
216:
217: public SqlException(LogWriter logwriter, ClientMessageId msgid,
218: Object arg1) {
219: this (logwriter, msgid, new Object[] { arg1 });
220: }
221:
222: public SqlException(LogWriter logwriter, ClientMessageId msgid,
223: Object arg1, Throwable cause) {
224: this (logwriter, msgid, new Object[] { arg1 }, cause);
225: }
226:
227: public SqlException(LogWriter logwriter, ClientMessageId msgid,
228: Object arg1, Object arg2, Throwable cause) {
229: this (logwriter, msgid, new Object[] { arg1, arg2 }, cause);
230: }
231:
232: public SqlException(LogWriter logwriter, ClientMessageId msgid,
233: Object arg1, Object arg2) {
234: this (logwriter, msgid, new Object[] { arg1, arg2 });
235: }
236:
237: public SqlException(LogWriter logwriter, ClientMessageId msgid,
238: Object arg1, Object arg2, Object arg3) {
239: this (logwriter, msgid, new Object[] { arg1, arg2, arg3 });
240: }
241:
242: public SqlException(LogWriter logWriter, Sqlca sqlca) {
243: this .sqlca_ = sqlca;
244: if (logWriter != null) {
245: logWriter.traceDiagnosable(this );
246: }
247: }
248:
249: // Once all messages are internationalized, these methods should become
250: // private
251: protected SqlException(LogWriter logWriter, String reason,
252: String sqlState, int errorCode) {
253: this (logWriter, (Throwable) null, reason, sqlState, errorCode);
254: }
255:
256: protected SqlException(LogWriter logWriter,
257: java.lang.Throwable throwable, String reason,
258: String sqlState, int errorCode) {
259: message_ = reason;
260: sqlstate_ = sqlState;
261: errorcode_ = errorCode;
262:
263: setThrowable(throwable);
264:
265: if (logWriter != null) {
266: logWriter.traceDiagnosable(this );
267: }
268:
269: }
270:
271: /**
272: * Set the cause of this exception based on its type and
273: * the current runtime version of Java
274: */
275: protected void setThrowable(Throwable throwable) {
276: throwable_ = throwable;
277:
278: // If the throwable is a SQL exception, use nextException rather
279: // than chained exceptions
280: if (throwable instanceof SqlException) {
281: setNextException((SqlException) throwable);
282: } else if (throwable instanceof SQLException) {
283: setNextException((SQLException) throwable);
284: } else if (throwable != null) {
285: // Set up a string indicating the cause if the current runtime
286: // doesn't support the initCause() method. This is then used
287: // by getMessage() when it composes the message string.
288: if (JVMInfo.JDK_ID < JVMInfo.J2SE_14) {
289: causeString_ = " "
290: + getMessageUtil().getTextMessage(
291: CAUSED_BY_EXCEPTION_ID) + " "
292: + throwable.getClass() + ": "
293: + throwable.getMessage();
294: } else {
295: initCause(throwable);
296: }
297: }
298:
299: }
300:
301: /**
302: * Wrap a SQLException in a SqlException. This is used by internal routines
303: * so the don't have to throw SQLException, which, through the chain of
304: * dependencies would force more and more internal routines to throw
305: * SQLException
306: */
307: public SqlException(SQLException wrapme) {
308: wrappedException_ = wrapme;
309: }
310:
311: /**
312: * Convert this SqlException into a java.sql.SQLException
313: */
314: public SQLException getSQLException() {
315: if (wrappedException_ != null) {
316: return wrappedException_;
317: }
318:
319: // When we have support for JDBC 4 SQLException subclasses, this is
320: // where we decide which exception to create
321: SQLException sqle = exceptionFactory.getSQLException(
322: getMessage(), getSQLState(), getErrorCode());
323:
324: // If we're in a runtime that supports chained exceptions, set the cause
325: // of the SQLException to be this SqlException. Otherwise the stack
326: // trace is lost.
327: if (JVMInfo.JDK_ID >= JVMInfo.J2SE_14) {
328: sqle.initCause(this );
329: }
330:
331: // Set up the nextException chain
332: if (nextException_ != null) {
333: // The exception chain gets constructed automatically through
334: // the beautiful power of recursion
335: sqle.setNextException(nextException_.getSQLException());
336: }
337:
338: return sqle;
339: }
340:
341: // Label an exception element in a batched update exception chain.
342: // This text will be prepended onto the exceptions message text dynamically
343: // when getMessage() is called.
344: // Called by the Agent.
345: void setBatchPositionLabel(int index) {
346: batchPositionLabel_ = getMessageUtil().getTextMessage(
347: BATCH_POSITION_ID)
348: + index + ": ";
349: }
350:
351: public Sqlca getSqlca() {
352: return sqlca_;
353: }
354:
355: public java.lang.Throwable getThrowable() {
356: return throwable_;
357: }
358:
359: public String getMessage() {
360: if (wrappedException_ != null) {
361: return wrappedException_.getMessage();
362: }
363:
364: if (sqlca_ != null) {
365: message_ = ((Sqlca) sqlca_).getJDBCMessage();
366: }
367:
368: if (batchPositionLabel_ != null) {
369: message_ = batchPositionLabel_ + message_;
370: }
371:
372: if (causeString_ != null) {
373: // Append the string indicating the cause of the exception
374: // (this happens only in JDK13 environments)
375: message_ += causeString_;
376: }
377:
378: return message_;
379: }
380:
381: public String getSQLState() {
382: if (wrappedException_ != null) {
383: return wrappedException_.getSQLState();
384: }
385:
386: if (sqlca_ == null) {
387: return sqlstate_;
388: } else {
389: return sqlca_.getSqlState();
390: }
391: }
392:
393: public int getErrorCode() {
394: if (wrappedException_ != null) {
395: return wrappedException_.getErrorCode();
396: }
397:
398: if (sqlca_ == null) {
399: return errorcode_;
400: } else {
401: return sqlca_.getSqlCode();
402: }
403: }
404:
405: public SqlException getNextException() {
406: if (wrappedException_ != null) {
407: return new SqlException(wrappedException_
408: .getNextException());
409: } else {
410: return nextException_;
411: }
412: }
413:
414: public void setNextException(SqlException nextException) {
415: if (wrappedException_ != null) {
416: wrappedException_.setNextException(nextException
417: .getSQLException());
418: } else {
419: nextException_ = nextException;
420: }
421: }
422:
423: public void setNextException(SQLException nextException) {
424: if (wrappedException_ != null) {
425: wrappedException_.setNextException(nextException);
426: } else {
427: // Add this exception to the end of the chain
428: SqlException theEnd = this ;
429: while (theEnd.nextException_ != null) {
430: theEnd = theEnd.nextException_;
431: }
432: theEnd.nextException_ = new SqlException(nextException);
433: }
434: }
435:
436: public void printTrace(java.io.PrintWriter printWriter,
437: String header) {
438: ExceptionFormatter.printTrace(this , printWriter, header);
439: }
440:
441: /**
442: * Helper method to construct an exception which basically says that
443: * we encountered an underlying Java exception
444: */
445: public static SqlException javaException(LogWriter logWriter,
446: Throwable e) {
447: return new SqlException(logWriter, new ClientMessageId(
448: SQLState.JAVA_EXCEPTION), new Object[] {
449: e.getClass().getName(), e.getMessage() }, e);
450: }
451:
452: // Return a single SQLException without the "next" pointing to another SQLException.
453: // Because the "next" is a private field in java.sql.SQLException,
454: // we have to create a new SqlException in order to break the chain with "next" as null.
455: SqlException copyAsUnchainedSQLException(LogWriter logWriter) {
456: if (sqlca_ != null) {
457: return new SqlException(logWriter, sqlca_); // server error
458: } else {
459: return new SqlException(logWriter, getMessage(),
460: getSQLState(), getErrorCode()); // client error
461: }
462: }
463:
464: /**
465: * Sets the exceptionFactory to be used for creating SQLException
466: * @param factory SQLExceptionFactory
467: */
468: public static void setExceptionFactory(SQLExceptionFactory factory) {
469: exceptionFactory = factory;
470: }
471: }
472:
473: // An intermediate exception encapsulation to provide code-reuse
474: // for common ResultSet data conversion exceptions.
475:
476: class ColumnTypeConversionException extends SqlException {
477: ColumnTypeConversionException(LogWriter logWriter,
478: String sourceType, String targetType) {
479: super (logWriter, new ClientMessageId(
480: SQLState.LANG_DATA_TYPE_GET_MISMATCH), sourceType,
481: targetType);
482: }
483: }
484:
485: // An intermediate exception encapsulation to provide code-reuse
486: // for common CrossConverters data conversion exceptions.
487:
488: class LossOfPrecisionConversionException extends SqlException {
489: LossOfPrecisionConversionException(LogWriter logWriter,
490: String instance) {
491: super (logWriter, new ClientMessageId(
492: SQLState.LOSS_OF_PRECISION_EXCEPTION), instance);
493: }
494: }
|