001: /*
002:
003: Derby - Class org.apache.derby.client.am.Sqlca
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 org.apache.derby.shared.common.reference.SQLState;
025: import org.apache.derby.client.net.Typdef;
026:
027: public abstract class Sqlca {
028: transient protected Connection connection_;
029: SqlException exceptionThrownOnStoredProcInvocation_;
030: boolean messageTextRetrievedContainsTokensOnly_ = true;
031:
032: // data corresponding to SQLCA fields
033: protected int sqlCode_; // SQLCODE
034: private String sqlErrmc_; // A string with all error tokens delimited by sqlErrmcDelimiter
035: protected String[] sqlErrmcTokens_; // A string array with each element
036: // contain an error token
037: protected String sqlErrp_; // function name issuing error
038: protected int[] sqlErrd_; // 6 diagnostic Information
039: protected char[] sqlWarn_; // 11 warning Flags
040: protected String sqlState_; // SQLSTATE
041:
042: // raw sqlca data fields before unicode conversion
043: protected byte[] sqlErrmcBytes_;
044: protected byte[] sqlErrpBytes_;
045: protected byte[] sqlWarnBytes_;
046:
047: protected int sqlErrmcCcsid_;
048: protected boolean containsSqlcax_ = true;
049: protected long rowsetRowCount_;
050:
051: //public static final String sqlErrmcDelimiter = "\u00FF";
052: private static final String sqlErrmcDelimiter__ = ";";
053:
054: // JDK stack trace calls e.getMessage(), so we must set some state on the sqlca that says return tokens only.
055: private boolean returnTokensOnlyInMessageText_ = false;
056:
057: transient private final Agent agent_;
058:
059: private String cachedMessage;
060:
061: protected Sqlca(org.apache.derby.client.am.Connection connection) {
062: connection_ = connection;
063: agent_ = connection_ != null ? connection_.agent_ : null;
064: }
065:
066: void returnTokensOnlyInMessageText(
067: boolean returnTokensOnlyInMessageText) {
068: returnTokensOnlyInMessageText_ = returnTokensOnlyInMessageText;
069: }
070:
071: synchronized public int getSqlCode() {
072: return sqlCode_;
073: }
074:
075: synchronized public String getSqlErrmc() {
076: if (sqlErrmc_ != null) {
077: return sqlErrmc_;
078: }
079:
080: // sqlErrmc string is dependent on sqlErrmcTokens array having been built
081: if (sqlErrmcTokens_ == null) {
082: getSqlErrmcTokens();
083: }
084:
085: // sqlErrmc will be build only if sqlErrmcTokens has been build.
086: // Otherwise, a null string will be returned.
087: if (sqlErrmcTokens_ == null) {
088: return null;
089: }
090:
091: // create 0-length String if no tokens
092: if (sqlErrmcTokens_.length == 0) {
093: sqlErrmc_ = "";
094: return sqlErrmc_;
095: }
096:
097: // concatenate tokens with sqlErrmcDelimiter delimiters into one String
098: StringBuffer buffer = new StringBuffer();
099: int indx;
100: for (indx = 0; indx < sqlErrmcTokens_.length - 1; indx++) {
101: buffer.append(sqlErrmcTokens_[indx]);
102: buffer.append(sqlErrmcDelimiter__);
103: }
104: // add the last token
105: buffer.append(sqlErrmcTokens_[indx]);
106:
107: // save as a string
108: sqlErrmc_ = buffer.toString();
109: return sqlErrmc_;
110: }
111:
112: synchronized public String[] getSqlErrmcTokens() {
113: if (sqlErrmcTokens_ != null) {
114: return sqlErrmcTokens_;
115: }
116:
117: // processSqlErrmcTokens handles null sqlErrmcBytes_ case
118: sqlErrmcTokens_ = processSqlErrmcTokens(sqlErrmcBytes_);
119: return sqlErrmcTokens_;
120: }
121:
122: synchronized public String getSqlErrp() {
123: if (sqlErrp_ != null) {
124: return sqlErrp_;
125: }
126:
127: if (sqlErrpBytes_ == null) {
128: return null;
129: }
130:
131: try {
132: sqlErrp_ = bytes2String(sqlErrpBytes_, 0,
133: sqlErrpBytes_.length);
134: return sqlErrp_;
135: } catch (java.io.UnsupportedEncodingException e) {
136: // leave sqlErrp as null.
137: return null;
138: }
139: }
140:
141: public int[] getSqlErrd() {
142: if (sqlErrd_ != null) {
143: return sqlErrd_;
144: }
145:
146: sqlErrd_ = new int[6]; // create an int array.
147: return sqlErrd_;
148: }
149:
150: synchronized public char[] getSqlWarn() {
151: if (sqlWarn_ != null) {
152: return sqlWarn_;
153: }
154:
155: try {
156: if (sqlWarnBytes_ == null) {
157: sqlWarn_ = new char[] { ' ', ' ', ' ', ' ', ' ', ' ',
158: ' ', ' ', ' ', ' ', ' ' }; // 11 blank.
159: } else {
160: sqlWarn_ = bytes2String(sqlWarnBytes_, 0,
161: sqlWarnBytes_.length).toCharArray();
162: }
163: return sqlWarn_;
164: } catch (java.io.UnsupportedEncodingException e) {
165: sqlWarn_ = new char[] { ' ', ' ', ' ', ' ', ' ', ' ', ' ',
166: ' ', ' ', ' ', ' ' }; // 11 blank.
167: return sqlWarn_;
168: }
169: }
170:
171: synchronized public String getSqlState() {
172: return sqlState_;
173: }
174:
175: // Gets the formatted message, can throw an exception.
176: synchronized public String getMessage() throws SqlException {
177: // should this be traced to see if we are calling a stored proc?
178: if (cachedMessage != null) {
179: return cachedMessage;
180: }
181:
182: if (connection_ == null || connection_.isClosedX()
183: || returnTokensOnlyInMessageText_) {
184: return getUnformattedMessage();
185: }
186:
187: CallableStatement cs = null;
188: synchronized (connection_) {
189: try {
190: cs = connection_
191: .prepareMessageProc("call SYSIBM.SQLCAMESSAGE(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
192:
193: // SQLCode: SQL return code.
194: cs.setIntX(1, getSqlCode());
195: // SQLErrml: Length of SQL error message tokens.
196: cs
197: .setShortX(
198: 2,
199: (short) ((getSqlErrmc() != null) ? getSqlErrmc()
200: .length()
201: : 0));
202: // SQLErrmc: SQL error message tokens as a String (delimited by semicolon ";").
203: cs.setStringX(3, getSqlErrmc());
204: // SQLErrp: Product signature.
205: cs.setStringX(4, getSqlErrp());
206: // SQLErrd: SQL internal error code.
207: cs.setIntX(5, getSqlErrd()[0]);
208: cs.setIntX(6, getSqlErrd()[1]);
209: cs.setIntX(7, getSqlErrd()[2]);
210: cs.setIntX(8, getSqlErrd()[3]);
211: cs.setIntX(9, getSqlErrd()[4]);
212: cs.setIntX(10, getSqlErrd()[5]);
213: // SQLWarn: SQL warning flags.
214: cs.setStringX(11, new String(getSqlWarn()));
215: // SQLState: standard SQL state.
216: cs.setStringX(12, getSqlState());
217: // MessageFileName: Not used by our driver, so set to null.
218: cs.setStringX(13, null);
219: // Locale: language preference requested for the return error message.
220: cs.setStringX(14, java.util.Locale.getDefault()
221: .toString());
222: // server could return a locale different from what we requested
223: cs.registerOutParameterX(14, java.sql.Types.VARCHAR);
224: // Message: error message returned from SQLCAMessage stored procedure.
225: cs
226: .registerOutParameterX(15,
227: java.sql.Types.LONGVARCHAR);
228: // RCode: return code from SQLCAMessage stored procedure.
229: cs.registerOutParameterX(16, java.sql.Types.INTEGER);
230: cs.executeX();
231:
232: if (cs.getIntX(16) == 0) {
233: // Return the message text.
234: messageTextRetrievedContainsTokensOnly_ = false;
235: String message = cs.getStringX(15);
236: cachedMessage = message;
237: return message;
238: } else {
239: // Stored procedure can't return a valid message text, so we return
240: // unformated exception
241: return getUnformattedMessage();
242: }
243: } finally {
244: if (cs != null) {
245: try {
246: cs.closeX();
247: } catch (SqlException doNothing) {
248: }
249: }
250: }
251: }
252: }
253:
254: // May or may not get the formatted message depending upon datasource directives. cannot throw exeption.
255: public synchronized String getJDBCMessage() {
256: // The transient connection_ member will only be null if the Sqlca has been deserialized
257: if (connection_ != null && connection_.retrieveMessageText_) {
258: try {
259: return getMessage();
260: } catch (SqlException e) {
261: // Invocation of stored procedure fails, so we return error message tokens directly.
262: exceptionThrownOnStoredProcInvocation_ = e;
263: chainDeferredExceptionsToAgentOrAsConnectionWarnings((SqlException) e);
264: return getUnformattedMessage();
265: }
266: } else {
267: return getUnformattedMessage();
268: }
269: }
270:
271: private String getUnformattedMessage() {
272: return "DERBY SQL error: SQLCODE: " + getSqlCode()
273: + ", SQLSTATE: " + getSqlState() + ", SQLERRMC: "
274: + getSqlErrmc();
275: }
276:
277: private void chainDeferredExceptionsToAgentOrAsConnectionWarnings(
278: SqlException e) {
279: SqlException current = e;
280: while (current != null) {
281: SqlException next = (SqlException) current
282: .getNextException();
283: current = current
284: .copyAsUnchainedSQLException(agent_.logWriter_);
285: if (current.getErrorCode() == -440) {
286: SqlWarning warningForStoredProcFailure = new SqlWarning(
287: agent_.logWriter_,
288: new ClientMessageId(
289: SQLState.UNABLE_TO_OBTAIN_MESSAGE_TEXT_FROM_SERVER));
290: warningForStoredProcFailure.setNextException(current
291: .getSQLException());
292: connection_
293: .accumulate440WarningForMessageProcFailure(warningForStoredProcFailure);
294: } else if (current.getErrorCode() == -444) {
295: SqlWarning warningForStoredProcFailure = new SqlWarning(
296: agent_.logWriter_,
297: new ClientMessageId(
298: SQLState.UNABLE_TO_OBTAIN_MESSAGE_TEXT_FROM_SERVER));
299: warningForStoredProcFailure.setNextException(current
300: .getSQLException());
301: connection_
302: .accumulate444WarningForMessageProcFailure(warningForStoredProcFailure);
303: } else {
304: agent_.accumulateDeferredException(current);
305: }
306: current = next;
307: }
308: }
309:
310: public boolean includesSqlCode(int[] codes) {
311: for (int i = 0; i < codes.length; i++) {
312: if (codes[i] == getSqlCode()) {
313: return true;
314: }
315: }
316: return false;
317: }
318:
319: // ------------------- helper methods ----------------------------------------
320:
321: private String[] processSqlErrmcTokens(byte[] tokenBytes) {
322: if (tokenBytes == null) {
323: return null;
324: }
325:
326: // create 0-length String tokens array if tokenBytes is 0-length
327: int length = tokenBytes.length;
328: if (length == 0) {
329: return new String[0];
330: }
331:
332: try {
333: // tokenize and convert tokenBytes
334: java.io.ByteArrayOutputStream buffer = new java.io.ByteArrayOutputStream();
335: java.util.LinkedList tokens = new java.util.LinkedList();
336:
337: // parse the error message tokens
338: for (int index = 0; index < length - 1; index++) {
339:
340: // non-delimiter - continue to write into buffer
341: if (tokenBytes[index] != -1) // -1 is the delimiter '\xFF'
342: {
343: buffer.write(tokenBytes[index]);
344: }
345:
346: // delimiter - convert current token and add to list
347: else {
348: tokens.add(bytes2String(buffer.toByteArray(), 0,
349: buffer.size()));
350: buffer.reset();
351: }
352: }
353:
354: int lastIndex = length - 1;
355: // check for last byte not being a delimiter, i.e. part of last token
356: if (tokenBytes[lastIndex] != -1) {
357: // write the last byte
358: buffer.write(tokenBytes[lastIndex]);
359: // convert the last token and add to list
360: tokens.add(bytes2String(buffer.toByteArray(), 0, buffer
361: .size()));
362: }
363:
364: // last byte is delimiter implying an empty String for last token
365: else {
366: // convert current token, if one exists, and add to list
367: if (lastIndex != 0) {
368: tokens.add(bytes2String(buffer.toByteArray(), 0,
369: buffer.size()));
370: }
371: // last token is an empty String
372: tokens.add("");
373: }
374:
375: // create the String array and fill it with tokens.
376: String[] tokenStrings = new String[tokens.size()];
377:
378: java.util.Iterator iterator = tokens.iterator();
379: for (int i = 0; iterator.hasNext(); i++) {
380: tokenStrings[i] = (String) iterator.next();
381: }
382:
383: return tokenStrings;
384: } catch (java.io.UnsupportedEncodingException e) {
385: return null;
386: }
387: }
388:
389: protected String bytes2String(byte[] bytes, int offset, int length)
390: throws java.io.UnsupportedEncodingException {
391: // Network server uses utf8 encoding
392: return new String(bytes, offset, length, Typdef.UTF8ENCODING);
393: }
394:
395: public int getUpdateCount() {
396: if (sqlErrd_ == null) {
397: return 0;
398: }
399: return sqlErrd_[2];
400: }
401:
402: public long getRowCount()
403: throws org.apache.derby.client.am.DisconnectException {
404: return ((long) sqlErrd_[0] << 32) + sqlErrd_[1];
405: }
406:
407: public void setContainsSqlcax(boolean containsSqlcax) {
408: containsSqlcax_ = containsSqlcax;
409: }
410:
411: public boolean containsSqlcax() {
412: return containsSqlcax_;
413: }
414:
415: public void resetRowsetSqlca(
416: org.apache.derby.client.am.Connection connection,
417: int sqlCode, String sqlState, byte[] sqlErrpBytes) {
418: connection_ = connection;
419: sqlCode_ = sqlCode;
420: sqlState_ = sqlState;
421: sqlErrpBytes_ = sqlErrpBytes;
422: }
423:
424: public void setRowsetRowCount(long rowCount) {
425: rowsetRowCount_ = rowCount;
426: }
427:
428: public long getRowsetRowCount() {
429: return rowsetRowCount_;
430: }
431: }
|