KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > beehive > controls > system > jdbc > parser > SqlStatement


1 /*
2  * Copyright 2005 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * $Header:$
17  */

18
19 package org.apache.beehive.controls.system.jdbc.parser;
20
21 import org.apache.beehive.controls.api.ControlException;
22 import org.apache.beehive.controls.api.context.ControlBeanContext;
23 import org.apache.beehive.controls.system.jdbc.JdbcControl;
24 import org.apache.beehive.controls.system.jdbc.TypeMappingsFactory;
25
26 import javax.sql.RowSet JavaDoc;
27 import java.io.Serializable JavaDoc;
28 import java.lang.reflect.Method JavaDoc;
29 import java.sql.CallableStatement JavaDoc;
30 import java.sql.Connection JavaDoc;
31 import java.sql.DatabaseMetaData JavaDoc;
32 import java.sql.PreparedStatement JavaDoc;
33 import java.sql.SQLException JavaDoc;
34 import java.sql.Statement JavaDoc;
35 import java.sql.Types JavaDoc;
36 import java.util.Calendar JavaDoc;
37
38 /**
39  * Represents a fully parsed SQL statement. SqlStatements can be used to generated a java.sql.PreparedStatement.
40  */

41 public final class SqlStatement extends SqlFragmentContainer implements Serializable JavaDoc {
42
43     private static final TypeMappingsFactory _tmf = TypeMappingsFactory.getInstance();
44     private boolean _callableStatement = false;
45     private boolean _cacheableStatement = true;
46
47     //
48
// set from SQL annotation element values
49
//
50
private boolean _batchUpdate;
51     private boolean _getGeneratedKeys;
52     private String JavaDoc[] _genKeyColumnNames;
53     private int _fetchSize;
54     private int _maxArray;
55     private int _maxRows;
56     private int[] _genKeyColumnIndexes;
57     private JdbcControl.ScrollType _scrollType;
58     private JdbcControl.FetchDirection _fetchDirection;
59     private JdbcControl.HoldabilityType _holdability;
60
61     /**
62      * Create a new SqlStatement.
63      */

64     SqlStatement() {
65         super();
66     }
67
68     /**
69      * Append a SqlFragment to the end of this statement.
70      *
71      * @param frag SqlFragment to append.
72      */

73     void addChild(SqlFragment frag) {
74         super.addChild(frag);
75
76         if (frag.isDynamicFragment()) {
77             _cacheableStatement = false;
78         }
79     }
80
81
82     /**
83      * Can the PreparedStatement generated by this class be cached?
84      *
85      * @return true if this statement can be cached by the SqlParser.
86      */

87     boolean isCacheable() { return _cacheableStatement; }
88
89
90     /**
91      * Does this statement generate a callable or prepared statement?
92      *
93      * @return true if this statement generates callable statement.
94      */

95     public boolean isCallableStatement() { return _callableStatement; }
96
97     /**
98      * Does this statement do a batch update?
99      *
100      * @return true if this statement should be executed as a batch update.
101      */

102     public boolean isBatchUpdate() { return _batchUpdate; }
103
104     /**
105      * Does this statement return generatedKeys?
106      *
107      * @return true if getGeneratedKeys set to true.
108      */

109     public boolean getsGeneratedKeys() { return _getGeneratedKeys; }
110
111     /**
112      * Generates the PreparedStatement the SQL statement.
113      *
114      * @param context ControlBeanContext instance.
115      * @param connection Connection to database.
116      * @param calendar Calendar instance which can be used to resolve date/time values.
117      * @param method Method the SQL is associated with.
118      * @param arguments Method parameters.
119      * @return The PreparedStatement generated by this statement.
120      * @throws SQLException If PreparedStatement cannot be created.
121      */

122     public PreparedStatement JavaDoc createPreparedStatement(ControlBeanContext context, Connection JavaDoc connection,
123                                                      Calendar JavaDoc calendar, Method JavaDoc method, Object JavaDoc[] arguments)
124             throws SQLException JavaDoc {
125
126         PreparedStatement JavaDoc preparedStatement = null;
127         loadSQLAnnotationStatmentOptions(context, method);
128         checkJdbcSupport(connection.getMetaData());
129
130         _callableStatement = setCallableStatement(arguments);
131
132         try {
133             final String JavaDoc sql = getPreparedStatementText(context, method, arguments);
134
135             //
136
// is this a request for generatedKeys ?
137
//
138
if (_getGeneratedKeys) {
139
140                 if (_callableStatement) {
141                     throw new ControlException("getGeneratedKeys not supported for CallableStatements");
142                 }
143
144                 if (_genKeyColumnNames.length > 0) {
145                     preparedStatement = connection.prepareStatement(sql, _genKeyColumnNames);
146                 } else if (_genKeyColumnIndexes.length > 0) {
147                     preparedStatement = connection.prepareStatement(sql, _genKeyColumnIndexes);
148                 } else {
149                     preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
150                 }
151
152             } else {
153
154                 if (_holdability == JdbcControl.HoldabilityType.DRIVER_DEFAULT) {
155                     if (_scrollType == JdbcControl.ScrollType.DRIVER_DEFAULT) {
156                         preparedStatement = (_callableStatement) ? connection.prepareCall(sql) : connection.prepareStatement(sql);
157                     } else {
158                         preparedStatement = (_callableStatement)
159                                 ? connection.prepareCall(sql, _scrollType.getType(), _scrollType.getConcurrencyType())
160                                 : connection.prepareStatement(sql, _scrollType.getType(), _scrollType.getConcurrencyType());
161                     }
162                 } else {
163                     preparedStatement = (_callableStatement)
164                             ? connection.prepareCall(sql, _scrollType.getType(), _scrollType.getConcurrencyType(), _holdability.getHoldability())
165                             : connection.prepareStatement(sql, _scrollType.getType(), _scrollType.getConcurrencyType(), _holdability.getHoldability());
166                 }
167             }
168
169             //
170
// If the method argument is of type SQLParameter, treat this statement as a CallableStatement,
171
//
172
if (_callableStatement) {
173                 for (SqlFragment sf : _children) {
174                     if (sf.hasParamValue()) {
175                         throw new ControlException("Cannot use parameter substution and SQLParameter array in the same method.");
176                     }
177                 }
178                 JdbcControl.SQLParameter[] params = (JdbcControl.SQLParameter[]) arguments[0];
179                 if (params == null) {
180                     return preparedStatement;
181                 }
182                 for (int i = 0; i < params.length; i++) {
183                     JdbcControl.SQLParameter p = params[i];
184                     if (p.dir != JdbcControl.SQLParameter.OUT) {
185                         Object JavaDoc value = params[i].value;
186                         setPreparedStatementParameter(preparedStatement, i + 1, value, params[i].type, calendar);
187                     }
188
189                     if (p.dir != JdbcControl.SQLParameter.IN) {
190                         ((CallableStatement JavaDoc) preparedStatement).registerOutParameter(i + 1, params[i].type);
191                     }
192                 }
193
194
195                 //
196
// special handling for batch updates
197
//
198
} else if (_batchUpdate) {
199                 doBatchUpdate(preparedStatement, arguments, calendar);
200
201
202                 //
203
// standard case, not a batch or callable
204
//
205
} else {
206                 int pIndex = 1;
207                 for (SqlFragment sf : _children) {
208                     if (sf.hasParamValue()) {
209                         Object JavaDoc values[] = sf.getParameterValues(context, method, arguments);
210                         for (Object JavaDoc value : values) {
211                             setPreparedStatementParameter(preparedStatement, pIndex++, value, sf.getParamSqlDataType(), calendar);
212                         }
213                     }
214                 }
215             }
216         } catch (SQLException JavaDoc e) {
217             if (preparedStatement != null) preparedStatement.close();
218             throw e;
219         }
220
221         preparedStatement.setFetchDirection(_fetchDirection.getDirection());
222         preparedStatement.setFetchSize(_fetchSize);
223         preparedStatement.setMaxRows(computeMaxRows(method));
224
225         return preparedStatement;
226     }
227
228     /**
229      * Generates the PreparedStatement the SQL statement.
230      *
231      * @param context ControlBeanContext instance.
232      * @param connection Connection to database.
233      * @param method Method the SQL is associated with.
234      * @param arguments Method parameters.
235      * @return The PreparedStatement generated by this statement.
236      */

237     public String JavaDoc createPreparedStatementString(ControlBeanContext context, Connection JavaDoc connection,
238                                                 Method JavaDoc method, Object JavaDoc[] arguments) {
239
240         final boolean callableStatement = setCallableStatement(arguments);
241         StringBuilder JavaDoc sqlString = new StringBuilder JavaDoc(getPreparedStatementText(context, method, arguments));
242
243         if (callableStatement) {
244             JdbcControl.SQLParameter[] params = (JdbcControl.SQLParameter[]) arguments[0];
245             if (params == null) {
246                 return sqlString.toString();
247             }
248
249             sqlString.append(" Params: {");
250             for (int i = 0; i < params.length; i++) {
251                 if (i > 0) { sqlString.append(params[i].value.toString()); }
252             }
253             sqlString.append("}");
254
255         } else if (_batchUpdate) {
256             sqlString.append(" Params: batch update.");
257
258         } else {
259             sqlString.append(" Params: {");
260             boolean first = true;
261             for (SqlFragment sf : _children) {
262                 if (sf.hasParamValue()) {
263                     Object JavaDoc values[] = sf.getParameterValues(context, method, arguments);
264                     for (Object JavaDoc value : values) {
265
266                         if (!first) sqlString.append(", "); else first = false;
267                         sqlString.append(value);
268                     }
269                 }
270             }
271             sqlString.append("}");
272         }
273         return sqlString.toString();
274     }
275
276
277     // /////////////////////////////////////////////////// PRIVATE METHODS ///////////////////////////////////////////
278

279     /**
280      * Sets the specified parameter in the prepared statement.
281      *
282      * @param ps A PreparedStatement.
283      * @param i index of parameter to set.
284      * @param value value of the parameter.
285      * @param sqlType SQL type of value.
286      * @param cal A calendar instance used to resolve date/time values.
287      * @throws SQLException If the parameter cannot be set.
288      */

289     private void setPreparedStatementParameter(PreparedStatement JavaDoc ps, int i, Object JavaDoc value, int sqlType, Calendar JavaDoc cal)
290             throws SQLException JavaDoc {
291
292         if (sqlType == Types.NULL) {
293             sqlType = _tmf.getSqlType(value);
294         }
295
296         if (value == null) {
297             ps.setNull(i, Types.NULL == sqlType ? Types.VARCHAR : sqlType);
298             return;
299         }
300
301         switch (sqlType) {
302
303             case Types.VARCHAR:
304                 if (!(value instanceof String JavaDoc)) value = value.toString();
305                 break;
306
307             case Types.BOOLEAN:
308                 if (value instanceof Boolean JavaDoc) {
309                     ps.setBoolean(i, ((Boolean JavaDoc) value).booleanValue());
310                     return;
311                 }
312                 break;
313
314             case Types.TIMESTAMP:
315                 if (value instanceof java.util.Calendar JavaDoc) {
316                     Calendar JavaDoc calValue = (Calendar JavaDoc) value;
317
318 // @todo: validate it is correct to comment out call to deprectated method
319
// if (cal == null) {
320
// /* NOTE: drivers are inconsistent in their handling of setTimestamp(i,date,cal)
321
// * so we won't use that, unless the user calls setCalendar().
322
// * I'm going with the theory that it makes sense to store
323
// * the time relative to the Calendar's timezone rather than
324
// * the system timezone otherwise, using a Calendar would be a no-op.
325
// */
326
// value = new java._sql.Timestamp(calValue.get(Calendar.YEAR) - 1900,
327
// calValue.get(Calendar.MONTH),
328
// calValue.get(Calendar.DATE),
329
// calValue.get(Calendar.HOUR_OF_DAY),
330
// calValue.get(Calendar.MINUTE),
331
// calValue.get(Calendar.SECOND),
332
// calValue.get(Calendar.MILLISECOND));
333
// } else {
334
value = new java.sql.Timestamp JavaDoc(calValue.getTimeInMillis());
335 // }
336
} else if (java.util.Date JavaDoc.class.equals(value.getClass())) {
337                     // some drivers don't like java.util.Date
338
value = new java.sql.Timestamp JavaDoc(((java.util.Date JavaDoc) value).getTime());
339                 }
340
341                 if (value instanceof java.sql.Timestamp JavaDoc) {
342                     if (cal == null)
343                         ps.setTimestamp(i, (java.sql.Timestamp JavaDoc) value);
344                     else
345                         ps.setTimestamp(i, (java.sql.Timestamp JavaDoc) value, cal);
346                     return;
347                 }
348                 break;
349
350             case Types.DATE:
351                 if (value instanceof java.util.Calendar JavaDoc) {
352                     /* NOTE: see note above
353                      Calendar cal = (Calendar)value;
354                      value = new java._sql.Date(cal.getTimeInMillis());
355                      ps.setDate(i, (java._sql.Date)value, cal);
356                      return;
357                      */

358                     Calendar JavaDoc calValue = (Calendar JavaDoc) value;
359
360                     // @todo: validate that commenting out deprected method is correct behavior
361
// if (cal == null) {
362
// value = new java._sql.Date(calValue.get(Calendar.YEAR - 1900),
363
// calValue.get(Calendar.MONTH),
364
// calValue.get(Calendar.DATE));
365
// } else {
366
value = new java.sql.Date JavaDoc(calValue.getTimeInMillis());
367 // }
368
} else if (value.getClass() == java.util.Date JavaDoc.class) {
369                     // some drivers don't like java.util.Date
370
value = new java.sql.Date JavaDoc(((java.util.Date JavaDoc) value).getTime());
371                 }
372
373                 if (value instanceof java.sql.Date JavaDoc) {
374                     if (cal == null) {
375                         ps.setDate(i, (java.sql.Date JavaDoc) value);
376                     } else {
377                         ps.setDate(i, (java.sql.Date JavaDoc) value, cal);
378                     }
379                     return;
380                 }
381                 break;
382
383             case Types.TIME:
384                 if (value instanceof java.sql.Time JavaDoc) {
385                     if (cal == null) {
386                         ps.setTime(i, (java.sql.Time JavaDoc) value);
387                     } else {
388                         ps.setTime(i, (java.sql.Time JavaDoc) value, cal);
389                     }
390                     return;
391                 }
392                 break;
393         }
394
395         if (sqlType == Types.NULL) {
396             ps.setObject(i, value);
397         } else {
398             ps.setObject(i, value, sqlType);
399         }
400     }
401
402     /**
403      * Determine if this SQL will generate a callable or prepared statement.
404      *
405      * @param args The method's argument list which this SQL annotation was assocatied with.
406      * @return true if this statement will generated a CallableStatement
407      */

408     private boolean setCallableStatement(Object JavaDoc[] args) {
409
410         // CallableStatement vs. PreparedStatement
411
if (args != null && args.length == 1 && args[0] != null) {
412             Class JavaDoc argClass = args[0].getClass();
413             if (argClass.isArray() && JdbcControl.SQLParameter.class.isAssignableFrom(argClass.getComponentType())) {
414                 return true;
415             }
416         }
417         return false;
418     }
419
420     /**
421      * Build a prepared statement for a batch update.
422      *
423      * @param ps The PreparedStatement object.
424      * @param args The parameter list of the jdbccontrol method.
425      * @param cal A Calendar instance used to resolve date/time values.
426      * @throws SQLException If a batch update cannot be performed.
427      */

428     private void doBatchUpdate(PreparedStatement JavaDoc ps, Object JavaDoc[] args, Calendar JavaDoc cal) throws SQLException JavaDoc {
429
430         final int[] sqlTypes = new int[args.length];
431         final Object JavaDoc[] objArrays = new Object JavaDoc[args.length];
432
433         // build an array of type values and object arrays
434
for (int i = 0; i < args.length; i++) {
435             sqlTypes[i] = _tmf.getSqlType(args[i].getClass().getComponentType());
436             objArrays[i] = TypeMappingsFactory.toObjectArray(args[i]);
437         }
438
439         final int rowCount = ((Object JavaDoc[]) objArrays[0]).length;
440         for (int i = 0; i < rowCount; i++) {
441             for (int j = 0; j < args.length; j++) {
442                 setPreparedStatementParameter(ps, j + 1, ((Object JavaDoc[]) objArrays[j])[i], sqlTypes[j], cal);
443             }
444             ps.addBatch();
445         }
446     }
447
448     /**
449      * Load element values from the SQL annotation which apply to Statements.
450      *
451      * @param context ControlBeanContext instance.
452      * @param method Annotated method.
453      */

454     private void loadSQLAnnotationStatmentOptions(ControlBeanContext context, Method JavaDoc method) {
455
456         final JdbcControl.SQL methodSQL = (JdbcControl.SQL) context.getMethodPropertySet(method, JdbcControl.SQL.class);
457
458         _batchUpdate = methodSQL.batchUpdate();
459         _getGeneratedKeys = methodSQL.getGeneratedKeys();
460         _genKeyColumnNames = methodSQL.generatedKeyColumnNames();
461         _genKeyColumnIndexes = methodSQL.generatedKeyColumnIndexes();
462         _scrollType = methodSQL.scrollableResultSet();
463         _fetchDirection = methodSQL.fetchDirection();
464         _fetchSize = methodSQL.fetchSize();
465         _maxRows = methodSQL.maxRows();
466         _maxArray = methodSQL.arrayMaxLength();
467
468         _holdability = methodSQL.resultSetHoldabilityOverride();
469     }
470
471     /**
472      * Checks that all statement options specified in annotation are supported by the database.
473      *
474      * @param metaData
475      * @throws SQLException
476      */

477     private void checkJdbcSupport(DatabaseMetaData JavaDoc metaData) throws SQLException JavaDoc {
478
479         if (_getGeneratedKeys && !metaData.supportsGetGeneratedKeys()) {
480             throw new ControlException("The database does not support getGeneratedKeys.");
481         }
482
483         if (_batchUpdate && !metaData.supportsBatchUpdates()) {
484             throw new ControlException("The database does not support batchUpdates.");
485         }
486
487         if (_scrollType != JdbcControl.ScrollType.DRIVER_DEFAULT
488                 && !metaData.supportsResultSetConcurrency(_scrollType.getType(), _scrollType.getConcurrencyType())) {
489             throw new ControlException("The database does not support the ResultSet concurrecy type: " + _scrollType.toString());
490         }
491
492         if (_holdability != JdbcControl.HoldabilityType.DRIVER_DEFAULT
493                 && !metaData.supportsResultSetHoldability(_holdability.getHoldability())) {
494             throw new ControlException("The database does not support the ResultSet holdability type: " + _holdability.toString());
495         }
496     }
497
498     /**
499      * The much maligned method for computing the maximum number of ResultSet rows this statement should return.
500      * The values of maxRows and arrayMaxLength are enforced at compile-time by the JdbcControlChecker to be the
501      * following: MAXROWS_ALL <= maxRows, 0 < arrayMaxLength
502      *
503      * @param method The annotated method.
504      * @return max number of resultSet rows to return from the query.
505      */

506     private int computeMaxRows(Method JavaDoc method) {
507
508         Class JavaDoc returnType = method.getReturnType();
509
510         final boolean isArray = returnType.isArray();
511         final boolean isRowSet = returnType.equals(RowSet JavaDoc.class);
512
513         int maxSet = _maxRows;
514         if (isArray && _maxArray != JdbcControl.MAXROWS_ALL) {
515             maxSet = _maxRows == JdbcControl.MAXROWS_ALL ? _maxArray + 1 : Math.min(_maxArray + 1, _maxRows);
516         } else if (isRowSet && _maxRows > 0) {
517             maxSet = _maxRows + 1;
518         }
519
520         return maxSet;
521     }
522 }
523
Popular Tags