001: /**
002: * Sequoia: Database clustering technology.
003: * Copyright (C) 2005 AmicoSoft, Inc. dba Emic Networks
004: * Contact: sequoia@continuent.org
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: *
018: * Initial developer(s): Emmanuel Cecchet.
019: * Contributor(s): ______________________.
020: */package org.continuent.sequoia.common.protocol;
021:
022: import java.io.ByteArrayInputStream;
023: import java.io.IOException;
024: import java.io.ObjectInputStream;
025: import java.math.BigDecimal;
026: import java.net.MalformedURLException;
027: import java.net.URL;
028: import java.sql.CallableStatement;
029: import java.sql.Date;
030: import java.sql.SQLException;
031: import java.sql.Time;
032: import java.sql.Timestamp;
033: import java.text.ParseException;
034: import java.text.SimpleDateFormat;
035:
036: import org.continuent.sequoia.common.sql.filters.AbstractBlobFilter;
037: import org.continuent.sequoia.common.util.Strings;
038: import org.continuent.sequoia.controller.requests.StoredProcedure;
039:
040: /**
041: * This class contains the data used to serialize PreparedStatement.
042: *
043: * @author <a href="mailto:emmanuel.cecchet@emicnetworks.com">Emmanuel Cecchet</a>
044: * @version 1.0
045: */
046: public final class PreparedStatementSerialization {
047:
048: /**
049: * Tags used to serialize the name of the setter used by the JDBC client
050: * application; one tag for each setter method. All tags must have the same
051: * length {@link #setPreparedStatement(String, java.sql.PreparedStatement)}
052: */
053: /** @see java.sql.PreparedStatement#setByte(int, byte) */
054: public static final String BYTE_TAG = "b|";
055: /** @see java.sql.PreparedStatement#setBytes(int, byte[]) */
056: public static final String BYTES_TAG = "B|";
057: /** @see java.sql.PreparedStatement#setBlob(int, java.sql.Blob) */
058: public static final String BLOB_TAG = "c|";
059: /** @see java.sql.PreparedStatement#setClob(int, java.sql.Clob) */
060: public static final String CLOB_TAG = "C|";
061: /** @see java.sql.PreparedStatement#setBoolean(int, boolean) */
062: public static final String BOOLEAN_TAG = "0|";
063: /**
064: * @see java.sql.PreparedStatement#setBigDecimal(int, java.math.BigDecimal)
065: */
066: public static final String BIG_DECIMAL_TAG = "1|";
067: /** @see java.sql.PreparedStatement#setDate(int, java.sql.Date) */
068: public static final String DATE_TAG = "d|";
069: /** @see java.sql.PreparedStatement#setDouble(int, double) */
070: public static final String DOUBLE_TAG = "D|";
071: /** @see java.sql.PreparedStatement#setFloat(int, float) */
072: public static final String FLOAT_TAG = "F|";
073: /** @see java.sql.PreparedStatement#setInt(int, int) */
074: public static final String INTEGER_TAG = "I|";
075: /** @see java.sql.PreparedStatement#setLong(int, long) */
076: public static final String LONG_TAG = "L|";
077:
078: /** Encoding of a named parameter in a CallableStatement */
079: public static final String NAMED_PARAMETER_TAG = "P|";
080:
081: /** Encoding of a NULL value. Also used to as the _TAG for setNull() */
082: public static final String NULL_VALUE = "N|";
083: /**
084: * Special escape _type_ tag used when the string parameter is unfortunately
085: * equal to an encoded null value.
086: */
087: public static final String NULL_STRING_TAG = "n|";
088:
089: /**
090: * @see java.sql.PreparedStatement#setObject(int, java.lang.Object)
091: */
092: public static final String OBJECT_TAG = "O|";
093: /** @see java.sql.PreparedStatement#setRef(int, java.sql.Ref) */
094: public static final String REF_TAG = "R|";
095: /** @see java.sql.PreparedStatement#setShort(int, short) */
096: public static final String SHORT_TAG = "s|";
097: /**
098: * @see java.sql.PreparedStatement#setString(int, java.lang.String)
099: */
100: public static final String STRING_TAG = "S|";
101: /** @see java.sql.PreparedStatement#setTime(int, java.sql.Time) */
102: public static final String TIME_TAG = "t|";
103: /**
104: * @see java.sql.PreparedStatement#setTimestamp(int, java.sql.Timestamp)
105: */
106: public static final String TIMESTAMP_TAG = "T|";
107: /** @see java.sql.PreparedStatement#setURL(int, java.net.URL) */
108: public static final String URL_TAG = "U|";
109:
110: /** Escape for callable statement out parameter */
111: public static final String REGISTER_OUT_PARAMETER = "o|";
112: /** Escape for callable statement out parameter with scale */
113: public static final String REGISTER_OUT_PARAMETER_WITH_SCALE = "w|";
114: /** Escape for callable statement out parameter with type name */
115: public static final String REGISTER_OUT_PARAMETER_WITH_NAME = "W|";
116: /**
117: * Escape for 'void' placeholder when compiling callable statement OUT or
118: * named parameter
119: */
120: public static final String CS_PARAM_TAG = "V|";
121:
122: /** Tag maker for parameters */
123: public static final String TAG_MARKER = "!%";
124: /** Escape for tag maker */
125: public static final String TAG_MARKER_ESCAPE = TAG_MARKER + ";";
126:
127: /** Tag for parameters start delimiter */
128: public static final String START_PARAM_TAG = "<" + TAG_MARKER;
129: /** Tag for parameters end delimiter */
130: public static final String END_PARAM_TAG = "|" + TAG_MARKER + ">";
131:
132: /**
133: * Static method to initialize a backend PreparedStatement by calling the
134: * appropriate setXXX methods on the request skeleton. Has to extract the
135: * tagged and inlined parameters from the sql String beforehand. Used by the
136: * controller.
137: *
138: * @param parameters encoded parameters to set
139: * @param backendPS the preparedStatement to set
140: * @throws SQLException if an error occurs
141: * @see org.continuent.sequoia.driver.PreparedStatement#setParameterWithTag(int,
142: * String, String)
143: */
144: public static void setPreparedStatement(String parameters,
145: java.sql.PreparedStatement backendPS) throws SQLException {
146: int i = 0;
147: int paramIdx = 0;
148:
149: // Set all parameters
150: while ((i = parameters.indexOf(START_PARAM_TAG, i)) > -1) {
151: paramIdx++;
152:
153: int typeStart = i + START_PARAM_TAG.length();
154:
155: // Here we assume that all tags have the same length as the boolean tag.
156: String paramType = parameters.substring(typeStart,
157: typeStart + BOOLEAN_TAG.length());
158: String paramValue = parameters.substring(typeStart
159: + BOOLEAN_TAG.length(), parameters.indexOf(
160: END_PARAM_TAG, i));
161: paramValue = Strings.replace(paramValue, TAG_MARKER_ESCAPE,
162: TAG_MARKER);
163:
164: if (!performCallOnPreparedStatement(backendPS, paramIdx,
165: paramType, paramValue)) {
166: // invalid parameter, we want to be able to store strings like
167: // <?xml version="1.0" encoding="ISO-8859-1"?>
168: paramIdx--;
169: }
170: i = typeStart + paramValue.length();
171: }
172: }
173:
174: /**
175: * Static method to initialize a backend PreparedStatement by calling the
176: * appropriate setXXX methods on the request skeleton. Has to extract the
177: * tagged and inlined parameters from the sql String beforehand. Used by the
178: * controller.
179: *
180: * @param parameters encoded parameters to set
181: * @param cs the CallableStatement to set
182: * @param proc the StoredProcedure that is called
183: * @throws SQLException if an error occurs
184: * @see org.continuent.sequoia.driver.PreparedStatement#setParameterWithTag(int,
185: * String, String)
186: * @see org.continuent.sequoia.driver.CallableStatement#setNamedParameterWithTag(String,
187: * String, String)
188: */
189: public static void setCallableStatement(String parameters,
190: CallableStatement cs, StoredProcedure proc)
191: throws SQLException {
192: int i = 0;
193: int paramIdx = 0;
194:
195: // Set all parameters
196: while ((i = parameters.indexOf(START_PARAM_TAG, i)) > -1) {
197: paramIdx++;
198:
199: int typeStart = i + START_PARAM_TAG.length();
200:
201: // Here we assume that all tags have the same length as the boolean tag.
202: String paramType = parameters.substring(typeStart,
203: typeStart + BOOLEAN_TAG.length());
204: String paramValue = parameters.substring(typeStart
205: + BOOLEAN_TAG.length(), parameters.indexOf(
206: END_PARAM_TAG, i));
207: paramValue = Strings.replace(paramValue, TAG_MARKER_ESCAPE,
208: TAG_MARKER);
209:
210: if (!performCallOnPreparedStatement(cs, paramIdx,
211: paramType, paramValue)) {
212: // Not a standard PreparedStatement call, let's check for OUT paramters
213: // and named parameters
214: int comma = paramValue.indexOf(",");
215: String paramName = paramValue.substring(0, comma);
216: if (setOutParameter(paramName, paramType, paramValue
217: .substring(comma + 1), cs, proc)) {
218: // Success, this was an out parameter
219: // else try a named parameter
220: } else if (paramType.equals(NAMED_PARAMETER_TAG)) {
221: // Value is composed of: paramName,paramTypeparamValue
222: paramType = paramValue.substring(comma + 1, comma
223: + 1 + BOOLEAN_TAG.length());
224: paramValue = paramValue.substring(comma + 1
225: + BOOLEAN_TAG.length());
226: paramValue = Strings.replace(paramValue,
227: TAG_MARKER_ESCAPE, TAG_MARKER);
228:
229: proc.setNamedParameterName(paramName);
230: setNamedParameterOnCallableStatement(cs, paramName,
231: paramType, paramValue);
232: } else {
233: // invalid parameter, we want to be able to store strings like
234: // <?xml version="1.0" encoding="ISO-8859-1"?>
235: paramIdx--;
236: }
237: }
238: i = typeStart;
239: }
240: }
241:
242: private static boolean setOutParameter(String paramName,
243: String paramType, String paramValue, CallableStatement cs,
244: StoredProcedure proc) throws SQLException {
245: if (paramType.equals(REGISTER_OUT_PARAMETER)) {
246: int sqlType = Integer.valueOf(paramValue).intValue();
247: try {
248: int paramIdx = Integer.parseInt(paramName);
249: proc.setOutParameterIndex(paramIdx);
250: cs.registerOutParameter(paramIdx, sqlType);
251: } catch (NumberFormatException e) { // This is a real named parameter
252: proc.setNamedParameterName(paramName);
253: cs.registerOutParameter(paramName, sqlType);
254: }
255: return true;
256: } else if (paramType.equals(REGISTER_OUT_PARAMETER_WITH_SCALE)) {
257: int comma = paramValue.indexOf(',');
258: int sqlType = Integer.valueOf(
259: paramValue.substring(0, comma)).intValue();
260: int scale = Integer
261: .valueOf(paramValue.substring(comma + 1))
262: .intValue();
263: try {
264: int paramIdx = Integer.parseInt(paramName);
265: proc.setOutParameterIndex(paramIdx);
266: cs.registerOutParameter(paramIdx, sqlType, scale);
267: } catch (NumberFormatException e) { // This is a real named parameter
268: proc.setNamedParameterName(paramName);
269: cs.registerOutParameter(paramName, sqlType, scale);
270: }
271: return true;
272: } else if (paramType.equals(REGISTER_OUT_PARAMETER_WITH_NAME)) {
273: int comma = paramValue.indexOf(',');
274: int sqlType = Integer.valueOf(
275: paramValue.substring(0, comma)).intValue();
276: try {
277: int paramIdx = Integer.parseInt(paramName);
278: proc.setOutParameterIndex(paramIdx);
279: cs.registerOutParameter(paramIdx, sqlType, paramValue
280: .substring(comma + 1));
281: } catch (NumberFormatException e) { // This is a real named parameter
282: proc.setNamedParameterName(paramName);
283: cs.registerOutParameter(paramName, sqlType, paramValue
284: .substring(comma + 1));
285: }
286: return true;
287: } else
288: return false;
289: }
290:
291: private static void setNamedParameterOnCallableStatement(
292: CallableStatement cs, String paramName, String paramType,
293: String paramValue) throws SQLException {
294: // Test tags in alphabetical order (to make the code easier to read)
295: if (paramType.equals(BIG_DECIMAL_TAG)) {
296: BigDecimal t = null;
297: if (!paramValue.equals(NULL_VALUE))
298: t = new BigDecimal(paramValue);
299: cs.setBigDecimal(paramName, t);
300: } else if (paramType.equals(BOOLEAN_TAG))
301: cs.setBoolean(paramName, Boolean.valueOf(paramValue)
302: .booleanValue());
303: else if (paramType.equals(BYTE_TAG)) {
304: byte t = new Integer(paramValue).byteValue();
305: cs.setByte(paramName, t);
306: } else if (paramType.equals(BYTES_TAG)) {
307: /**
308: * encoded by the driver at {@link #setBytes(int, byte[])}in order to
309: * inline it in the request (no database encoding here).
310: */
311: byte[] t = AbstractBlobFilter.getDefaultBlobFilter()
312: .decode(paramValue);
313: cs.setBytes(paramName, t);
314: } else if (paramType.equals(DATE_TAG)) {
315: if (paramValue.equals(NULL_VALUE))
316: cs.setDate(paramName, null);
317: else
318: try {
319: SimpleDateFormat sdf = new SimpleDateFormat(
320: "yyyy-MM-dd");
321: Date t = new Date(sdf.parse(paramValue).getTime());
322: cs.setDate(paramName, t);
323: } catch (ParseException p) {
324: cs.setDate(paramName, null);
325: throw new SQLException("Couldn't format date!!!");
326: }
327: } else if (paramType.equals(DOUBLE_TAG))
328: cs.setDouble(paramName, Double.valueOf(paramValue)
329: .doubleValue());
330: else if (paramType.equals(FLOAT_TAG))
331: cs.setFloat(paramName, Float.valueOf(paramValue)
332: .floatValue());
333: else if (paramType.equals(INTEGER_TAG))
334: cs
335: .setInt(paramName, Integer.valueOf(paramValue)
336: .intValue());
337: else if (paramType.equals(LONG_TAG))
338: cs.setLong(paramName, Long.valueOf(paramValue).longValue());
339: else if (paramType.equals(NULL_VALUE))
340: cs.setNull(paramName, Integer.valueOf(paramValue)
341: .intValue());
342: else if (paramType.equals(OBJECT_TAG)) {
343: if (paramValue.equals(NULL_VALUE))
344: cs.setObject(paramName, null);
345: else {
346: final String commonMsg = "Failed to deserialize object parameter of setObject()";
347: Object obj;
348: try {
349: byte[] decoded = AbstractBlobFilter
350: .getDefaultBlobFilter().decode(paramValue);
351: obj = new ObjectInputStream(
352: new ByteArrayInputStream(decoded))
353: .readObject();
354: } catch (ClassNotFoundException cnfe) {
355: throw (SQLException) new SQLException(commonMsg
356: + ", class not found on controller")
357: .initCause(cnfe);
358: } catch (IOException ioe) // like for instance invalid stream header
359: {
360: throw (SQLException) new SQLException(commonMsg
361: + ", I/O exception").initCause(ioe);
362: }
363: cs.setObject(paramName, obj);
364: }
365: } else if (paramType.equals(SHORT_TAG)) {
366: short t = new Integer(paramValue).shortValue();
367: cs.setShort(paramName, t);
368: } else if (paramType.equals(STRING_TAG)) {
369: if (paramValue.equals(NULL_VALUE))
370: cs.setString(paramName, null);
371: else
372: cs.setString(paramName, paramValue);
373: } else if (paramType.equals(NULL_STRING_TAG)) {
374: cs.setString(paramName, null);
375: } else if (paramType.equals(TIME_TAG)) {
376: if (paramValue.equals(NULL_VALUE))
377: cs.setTime(paramName, null);
378: else
379: try {
380: SimpleDateFormat sdf = new SimpleDateFormat(
381: "HH:mm:ss");
382: Time t = new Time(sdf.parse(paramValue).getTime());
383: cs.setTime(paramName, t);
384: } catch (ParseException p) {
385: cs.setTime(paramName, null);
386: throw new SQLException("Couldn't format time!!!");
387: }
388: } else if (paramType.equals(TIMESTAMP_TAG)) {
389: if (paramValue.equals(NULL_VALUE))
390: cs.setTimestamp(paramName, null);
391: else
392: try {
393: SimpleDateFormat sdf = new SimpleDateFormat(
394: "yyyy-MM-dd HH:mm:ss.S");
395: Timestamp t = new Timestamp(sdf.parse(paramValue)
396: .getTime());
397: cs.setTimestamp(paramName, t);
398: } catch (ParseException p) {
399: cs.setTimestamp(paramName, null);
400: throw new SQLException(
401: "Couldn't format timestamp!!!");
402: }
403: } else if (paramType.equals(URL_TAG)) {
404: if (paramValue.equals(NULL_VALUE))
405: cs.setURL(paramName, null);
406: else
407: try {
408: cs.setURL(paramName, new URL(paramValue));
409: } catch (MalformedURLException e) {
410: throw new SQLException("Unable to create URL "
411: + paramValue + " (" + e + ")");
412: }
413: } else {
414: throw new SQLException("Unsupported named parameter type: "
415: + paramType);
416: }
417: }
418:
419: private static boolean performCallOnPreparedStatement(
420: java.sql.PreparedStatement backendPS, int paramIdx,
421: String paramType, String paramValue) throws SQLException {
422: // Test tags in alphabetical order (to make the code easier to read)
423: if (paramType.equals(BIG_DECIMAL_TAG)) {
424: BigDecimal t = null;
425: if (!paramValue.equals(NULL_VALUE))
426: t = new BigDecimal(paramValue);
427: backendPS.setBigDecimal(paramIdx, t);
428: } else if (paramType.equals(BOOLEAN_TAG))
429: backendPS.setBoolean(paramIdx, Boolean.valueOf(paramValue)
430: .booleanValue());
431: else if (paramType.equals(BYTE_TAG)) {
432: byte t = new Integer(paramValue).byteValue();
433: backendPS.setByte(paramIdx, t);
434: } else if (paramType.equals(BYTES_TAG)) {
435: /**
436: * encoded by the driver at {@link #setBytes(int, byte[])}in order to
437: * inline it in the request (no database encoding here).
438: */
439: byte[] t = AbstractBlobFilter.getDefaultBlobFilter()
440: .decode(paramValue);
441: backendPS.setBytes(paramIdx, t);
442: } else if (paramType.equals(BLOB_TAG)) {
443: ByteArrayBlob b = null;
444: // encoded by the driver at {@link #setBlob(int, java.sql.Blob)}
445: if (!paramValue.equals(NULL_VALUE))
446: b = new ByteArrayBlob(AbstractBlobFilter
447: .getDefaultBlobFilter().decode(paramValue));
448: backendPS.setBlob(paramIdx, b);
449: } else if (paramType.equals(CLOB_TAG)) {
450: StringClob c = null;
451: if (!paramValue.equals(NULL_VALUE))
452: c = new StringClob(paramValue);
453: backendPS.setClob(paramIdx, c);
454: } else if (paramType.equals(DATE_TAG)) {
455: if (paramValue.equals(NULL_VALUE))
456: backendPS.setDate(paramIdx, null);
457: else
458: try {
459: SimpleDateFormat sdf = new SimpleDateFormat(
460: "yyyy-MM-dd");
461: Date t = new Date(sdf.parse(paramValue).getTime());
462: backendPS.setDate(paramIdx, t);
463: } catch (ParseException p) {
464: backendPS.setDate(paramIdx, null);
465: throw new SQLException("Couldn't format date!!!");
466: }
467: } else if (paramType.equals(DOUBLE_TAG))
468: backendPS.setDouble(paramIdx, Double.valueOf(paramValue)
469: .doubleValue());
470: else if (paramType.equals(FLOAT_TAG))
471: backendPS.setFloat(paramIdx, Float.valueOf(paramValue)
472: .floatValue());
473: else if (paramType.equals(INTEGER_TAG))
474: backendPS.setInt(paramIdx, Integer.valueOf(paramValue)
475: .intValue());
476: else if (paramType.equals(LONG_TAG))
477: backendPS.setLong(paramIdx, Long.valueOf(paramValue)
478: .longValue());
479: else if (paramType.equals(NULL_VALUE))
480: backendPS.setNull(paramIdx, Integer.valueOf(paramValue)
481: .intValue());
482: else if (paramType.equals(OBJECT_TAG)) {
483: if (paramValue.equals(NULL_VALUE))
484: backendPS.setObject(paramIdx, null);
485: else {
486: final String commonMsg = "Failed to deserialize object parameter of setObject()";
487: Object obj;
488: try {
489: byte[] decoded = AbstractBlobFilter
490: .getDefaultBlobFilter().decode(paramValue);
491: obj = new ObjectInputStream(
492: new ByteArrayInputStream(decoded))
493: .readObject();
494: } catch (ClassNotFoundException cnfe) {
495: throw (SQLException) new SQLException(commonMsg
496: + ", class not found on controller")
497: .initCause(cnfe);
498: } catch (IOException ioe) // like for instance invalid stream header
499: {
500: throw (SQLException) new SQLException(commonMsg
501: + ", I/O exception").initCause(ioe);
502: }
503: backendPS.setObject(paramIdx, obj);
504: }
505: } else if (paramType.equals(REF_TAG)) {
506: if (paramValue.equals(NULL_VALUE))
507: backendPS.setRef(paramIdx, null);
508: else
509: throw new SQLException("Ref type not supported");
510: } else if (paramType.equals(SHORT_TAG)) {
511: short t = new Integer(paramValue).shortValue();
512: backendPS.setShort(paramIdx, t);
513: } else if (paramType.equals(STRING_TAG)) {
514: if (paramValue.equals(NULL_VALUE))
515: backendPS.setString(paramIdx, null);
516: else
517: backendPS.setString(paramIdx, paramValue);
518: } else if (paramType.equals(NULL_STRING_TAG)) {
519: backendPS.setString(paramIdx, null);
520: } else if (paramType.equals(TIME_TAG)) {
521: if (paramValue.equals(NULL_VALUE))
522: backendPS.setTime(paramIdx, null);
523: else
524: try {
525: SimpleDateFormat sdf = new SimpleDateFormat(
526: "HH:mm:ss");
527: Time t = new Time(sdf.parse(paramValue).getTime());
528: backendPS.setTime(paramIdx, t);
529: } catch (ParseException p) {
530: backendPS.setTime(paramIdx, null);
531: throw new SQLException("Couldn't format time!!!");
532: }
533: } else if (paramType.equals(TIMESTAMP_TAG)) {
534: if (paramValue.equals(NULL_VALUE))
535: backendPS.setTimestamp(paramIdx, null);
536: else
537: try {
538: SimpleDateFormat sdf = new SimpleDateFormat(
539: "yyyy-MM-dd HH:mm:ss.S");
540: Timestamp t = new Timestamp(sdf.parse(paramValue)
541: .getTime());
542: backendPS.setTimestamp(paramIdx, t);
543: } catch (ParseException p) {
544: backendPS.setTimestamp(paramIdx, null);
545: throw new SQLException(
546: "Couldn't format timestamp!!!");
547: }
548: } else if (paramType.equals(URL_TAG)) {
549: if (paramValue.equals(NULL_VALUE))
550: backendPS.setURL(paramIdx, null);
551: else
552: try {
553: backendPS.setURL(paramIdx, new URL(paramValue));
554: } catch (MalformedURLException e) {
555: throw new SQLException("Unable to create URL "
556: + paramValue + " (" + e + ")");
557: }
558: } else if (paramType.equals(CS_PARAM_TAG))
559: return true; // ignore, will be treated in the named parameters
560: else
561: return false;
562:
563: return true;
564: }
565:
566: }
|