001: /*
002: * The contents of this file are subject to the Sapient Public License
003: * Version 1.0 (the "License"); you may not use this file except in compliance
004: * with the License. You may obtain a copy of the License at
005: * http://carbon.sf.net/License.html.
006: *
007: * Software distributed under the License is distributed on an "AS IS" basis,
008: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
009: * the specific language governing rights and limitations under the License.
010: *
011: * The Original Code is The Carbon Component Framework.
012: *
013: * The Initial Developer of the Original Code is Sapient Corporation
014: *
015: * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.
016: */
017:
018: package org.sape.carbon.services.sql;
019:
020: import java.sql.CallableStatement;
021: import java.sql.Connection;
022: import java.sql.PreparedStatement;
023: import java.sql.SQLException;
024: import java.sql.Statement;
025: import java.util.HashMap;
026: import java.util.Map;
027:
028: import org.sape.carbon.core.component.ComponentConfiguration;
029: import org.sape.carbon.core.component.lifecycle.Configurable;
030: import org.sape.carbon.core.config.InvalidConfigurationException;
031: import org.sape.carbon.services.sql.connection.ConnectionFactory;
032:
033: import org.apache.commons.logging.Log;
034: import org.apache.commons.logging.LogFactory;
035:
036: /**
037: * The default implementation for the StatementFactory. The configuration
038: * interface for this component is
039: * <code>SqlFactoryConfiguration</code>.
040: * The Component configuration consists of
041: * <ul>
042: * <li> Statement Set
043: * <li> Default Configuration values
044: * </ul>
045: * Sample configuration for the component
046: * <pre>
047: * <!-- Component Configuration for a SQLStatementFactory -->
048: * <Configuration ConfigurationInterface=
049: * "org.sape.carbon.services.sql.SqlFactoryConfiguration">
050: *
051: * <FunctionalInterface>
052: * org.sape.carbon.services.sql.StatementFactory
053: * </FunctionalInterface>
054: * <FunctionalImplementationClass>
055: * org.sape.carbon.services.sql.DefaultStatementFactoryImpl
056: * </FunctionalImplementationClass>
057: * <ConnectionFactory>
058: * ref:///sql/connection/test/StandaloneConnectionFactory
059: * </ConnectionFactory>
060: * <!-- Default Values for Statement Properties -->
061: * <ResultSetType>TYPE_SCROLL_SENSITIVE</ResultSetType>
062: * <ResultSetConcurrency>CONCUR_UPDATABLE</ResultSetConcurrency>
063: * <MaxRows>5</MaxRows>
064: * <!-- Statement Definitions -->
065: * <Statement>
066: * <!-- Statement Query Name -->
067: * <QueryName>selectAccounts</QueryName>
068: * <Query>SELECT * FROM accounts</Query>
069: * <!-- Overriding Parameters -->
070: * <MaxRows>10</MaxRows>
071: * </Statement>
072: * <Statement>
073: * <QueryName>Second</QueryName>
074: * <Query>SELECT * from TEST</Query>
075: * </Statement>
076: * </Configuration>
077: * </pre>
078: * <br>
079: *
080: * Copyright 2002 Sapient
081: * @since carbon 1.0
082: * @author Vivekanand Kirubanandan, June 2002
083: * @author Greg Hinkle, December 2002
084: * @version $Revision: 1.18 $($Author: dvoet $ / $Date: 2003/05/05 21:21:36 $)
085: */
086: public class DefaultStatementFactoryImpl implements StatementFactory,
087: Configurable {
088:
089: /**
090: * Provides a handle to Apache-commons logger
091: */
092: private Log log = LogFactory.getLog(this .getClass());
093:
094: /**
095: * The name of this factory.
096: */
097: protected String statementFactoryName;
098:
099: /**
100: * Holds a reference to this factories configuration.
101: */
102: protected StatementFactoryConfiguration config;
103:
104: /**
105: * Holds the map of all query name to StatementConfigurations.
106: */
107: protected Map statementsMap;
108:
109: /**
110: * Holds the error message displayed if the user has not properly
111: * configured the result set type and concurrency.
112: */
113: protected static final String RESULT_SET_CONFIGURATION_ERROR = "You must either configure both the ResultSet Type and it's "
114: + "Concurrency, or neither.";
115:
116: /**
117: * Holds the error message displayed if the user has not properly
118: * configured a connection factory.
119: */
120: protected static final String CONNECTION_FACTORY_CONFIGURATION_ERROR = "You must configure a valid Configuration Factory reference in "
121: + "either the main StatementFactoryConfiguration or in the specific "
122: + "StatementConfiguration.";
123:
124: /**
125: * Builds a prepared statement for the given configuration
126: * with the connection given.
127: *
128: * @param statementConfig configuration to build the statement from
129: * @param connection a connection to execute with.
130: * @return a PreparedStatement constructed for the queryName given
131: * @throws SQLException indicates an error
132: * creating the CallableStatement
133: */
134: protected PreparedStatement buildPreparedStatement(
135: StatementConfiguration statementConfig,
136: Connection connection) throws SQLException {
137:
138: ResultSetConcurrencyEnum concurrency = null;
139: ResultSetTypeEnum type = null;
140:
141: concurrency = (statementConfig.getResultSetConcurrency() != null) ? statementConfig
142: .getResultSetConcurrency()
143: : config.getResultSetConcurrency();
144:
145: type = (statementConfig.getResultSetType() != null) ? statementConfig
146: .getResultSetType()
147: : config.getResultSetType();
148:
149: PreparedStatement statement = null;
150: if ((concurrency != null) && (type != null)) {
151: statement = connection.prepareStatement(statementConfig
152: .getQuery(), statementConfig.getResultSetType()
153: .getOrdinal(), statementConfig
154: .getResultSetConcurrency().getOrdinal());
155: } else if ((concurrency != null) || (type != null)) {
156: throw new InvalidConfigurationException(this .getClass(),
157: this .config.getConfigurationName(),
158: "ResultSet(Type|Concurrency)",
159: RESULT_SET_CONFIGURATION_ERROR);
160: } else {
161: statement = connection.prepareStatement(statementConfig
162: .getQuery());
163: }
164: return statement;
165: }
166:
167: /**
168: * Creates a prepared statement for the given query name
169: * with the connection given.
170: *
171: * @param queryName query to create a CallableStatement for.
172: * @param connection a connection to execute with.
173: * @return a PreparedStatement constructed for the queryName given
174: * @throws StatementFactoryException indicates an error
175: * creating the CallableStatement
176: */
177: public PreparedStatement createPreparedStatement(String queryName,
178: Connection connection) throws StatementFactoryException {
179:
180: PreparedStatement statement = null;
181:
182: StatementConfiguration statementConfig = retrieveConfiguration(
183: queryName, this .getClass());
184: try {
185:
186: statement = buildPreparedStatement(statementConfig,
187: connection);
188:
189: configureStatement(statement, statementConfig);
190:
191: } catch (SQLException se) {
192: throw new StatementFactoryException(this .getClass(),
193: "Failure to create PreparedStatement "
194: + buildDebugMessage(statementConfig), se);
195: }
196:
197: if (log.isTraceEnabled()) {
198: log.trace("Prepared Statement created: "
199: + buildDebugMessage(statementConfig));
200: }
201:
202: return statement;
203: }
204:
205: /**
206: * Creates a prepared statement for the given query name using
207: * the default connection in the factories configuration.
208: *
209: * <p>When using this interface to create a statement it is
210: * important to remember to get back and close the connection
211: * that is created. This should be done with:</p>
212: * <p><code>statement.getConnection().close()</code</p>
213: *
214: * @param queryName query to create a CallableStatement for.
215: * @return a PreparedStatement constructed for the queryName given
216: * @throws StatementFactoryException indicates an error
217: * creating the CallableStatement
218: */
219: public PreparedStatement createPreparedStatement(String queryName)
220: throws StatementFactoryException {
221:
222: PreparedStatement statement = null;
223:
224: StatementConfiguration statementConfig = retrieveConfiguration(
225: queryName, this .getClass());
226:
227: Connection connection = null;
228:
229: try {
230: ConnectionFactory connectionFactory = (statementConfig
231: .getConnectionFactory() != null) ? statementConfig
232: .getConnectionFactory() : this .config
233: .getConnectionFactory();
234:
235: if (connectionFactory == null) {
236: throw new InvalidConfigurationException(
237: this .getClass(), this .config
238: .getConfigurationName(),
239: "ConnectionFactory",
240: CONNECTION_FACTORY_CONFIGURATION_ERROR);
241: }
242:
243: // 1) get a connection
244: connection = connectionFactory.getConnection();
245:
246: // 2) build the statement
247: // If this line fails, we will release the connection below
248: statement = buildPreparedStatement(statementConfig,
249: connection);
250:
251: // 3) configure it with the appropriate settings
252: configureStatement(statement, statementConfig);
253:
254: } catch (SQLException se) {
255: // Close the connection we retrieved if there was a failure
256: // to build the statement.
257: if (connection != null) {
258: try {
259: connection.close();
260: } catch (SQLException se2) {
261: // ignore
262: }
263: }
264: throw new StatementFactoryException(this .getClass(),
265: "Failure to create prepared statement "
266: + buildDebugMessage(statementConfig), se);
267: }
268: if (log.isTraceEnabled()) {
269: log.trace("Prepared Statement created: "
270: + buildDebugMessage(statementConfig));
271: }
272: return statement;
273: }
274:
275: /**
276: * Builds a callable statement for the given configuration
277: * with the connection given.
278: *
279: * @param statementConfig configuration to build the statement from
280: * @param connection a connection to execute with.
281: * @return a CallableStatement constructed for the queryName given
282: * @throws SQLException indicates an error
283: * creating the CallableStatement
284: */
285: protected CallableStatement buildCallableStatement(
286: StatementConfiguration statementConfig,
287: Connection connection) throws SQLException {
288:
289: ResultSetConcurrencyEnum concurrency = null;
290: ResultSetTypeEnum type = null;
291:
292: concurrency = (statementConfig.getResultSetConcurrency() != null) ? statementConfig
293: .getResultSetConcurrency()
294: : config.getResultSetConcurrency();
295:
296: type = (statementConfig.getResultSetType() != null) ? statementConfig
297: .getResultSetType()
298: : config.getResultSetType();
299:
300: CallableStatement statement = null;
301: if ((concurrency != null) && (type != null)) {
302: statement = connection.prepareCall(statementConfig
303: .getQuery(), statementConfig.getResultSetType()
304: .getOrdinal(), statementConfig
305: .getResultSetConcurrency().getOrdinal());
306:
307: } else if ((concurrency != null) || (type != null)) {
308: throw new InvalidConfigurationException(this .getClass(),
309: this .config.getConfigurationName(),
310: "ResultSet(Type|Concurrency)",
311: RESULT_SET_CONFIGURATION_ERROR);
312:
313: } else {
314: statement = connection.prepareCall(statementConfig
315: .getQuery());
316: }
317: return statement;
318: }
319:
320: /**
321: * Creates a callable statement for the given query name
322: * with the connection given.
323: *
324: * @param queryName query to create a CallableStatement for.
325: * @param connection a connection to execute with.
326: * @return a CallableStatement constructed for the queryName given
327: * @throws StatementFactoryException indicates an error
328: * creating the CallableStatement
329: */
330: public CallableStatement createCallableStatement(String queryName,
331: Connection connection) throws StatementFactoryException {
332:
333: CallableStatement statement = null;
334: StatementConfiguration statementConfig = retrieveConfiguration(
335: queryName, this .getClass());
336:
337: try {
338: statement = buildCallableStatement(statementConfig,
339: connection);
340:
341: configureStatement(statement, statementConfig);
342:
343: } catch (SQLException se) {
344: throw new StatementFactoryException(this .getClass(),
345: "Failure to create callable statement "
346: + buildDebugMessage(statementConfig), se);
347: }
348:
349: if (log.isTraceEnabled()) {
350: log.trace("Callable Statement created "
351: + buildDebugMessage(statementConfig));
352: }
353:
354: return statement;
355: }
356:
357: /**
358: * Creates a callable statement for the given query name using
359: * the default connection in the factories configuration.
360: *
361: * <p>When using this interface to create a statement it is
362: * important to remember to get back and close the connection
363: * that is created. This should be done with:</p>
364: * <p><code>statement.getConnection().close()</code</p>
365: *
366: * @param queryName query to create a CallableStatement for.
367: * @return a CallableStatement constructed for the queryName given
368: * @throws StatementFactoryException indicates an error
369: * creating the CallableStatement
370: */
371: public CallableStatement createCallableStatement(String queryName)
372: throws StatementFactoryException {
373:
374: CallableStatement statement = null;
375: StatementConfiguration statementConfig = retrieveConfiguration(
376: queryName, this .getClass());
377:
378: Connection connection = null;
379: try {
380: ConnectionFactory connectionFactory = (statementConfig
381: .getConnectionFactory() != null) ? statementConfig
382: .getConnectionFactory() : this .config
383: .getConnectionFactory();
384:
385: if (connectionFactory == null) {
386: throw new InvalidConfigurationException(
387: this .getClass(), this .config
388: .getConfigurationName(),
389: "ConnectionFactory",
390: CONNECTION_FACTORY_CONFIGURATION_ERROR);
391: }
392:
393: connection = connectionFactory.getConnection();
394:
395: statement = createCallableStatement(queryName, connection);
396:
397: configureStatement(statement, statementConfig);
398:
399: } catch (SQLException se) {
400: // Close the connection we retrieved if there was a failure
401: // to build the statement.
402: if (connection != null) {
403: try {
404: connection.close();
405: } catch (SQLException se2) {
406: // ignore
407: }
408: }
409: throw new StatementFactoryException(this .getClass(),
410: "Failure to create callable statement "
411: + buildDebugMessage(statementConfig), se);
412: }
413:
414: if (log.isTraceEnabled()) {
415: log.trace("Callable Statement created "
416: + buildDebugMessage(statementConfig));
417: }
418: return statement;
419: }
420:
421: /**
422: * Looks up a statement configuration based on the name of
423: * the configuration.
424: *
425: * @param queryName name of the query to lookup
426: * @param sourceClass class to use when throwing an exception
427: * @return a StatementConfiguration for the given queryName
428: * @throws StatementNotConfiguredException indicates the queryName had no
429: * configured statement associated with it.
430: */
431: protected StatementConfiguration retrieveConfiguration(
432: String queryName, Class sourceClass)
433: throws StatementNotConfiguredException {
434:
435: StatementConfiguration statementConfiguration = (StatementConfiguration) this .statementsMap
436: .get(queryName);
437:
438: if (statementConfiguration == null) {
439: throw new StatementNotConfiguredException(sourceClass,
440: queryName, this .statementFactoryName);
441: }
442:
443: return statementConfiguration;
444: }
445:
446: /**
447: * Configure the component. This is preceded and followed by the suspend and
448: * resume operations if they are available on the component.
449: *
450: * @param configuration A SqlFactoryConfiguration
451: */
452: public void configure(ComponentConfiguration configuration) {
453: StatementConfiguration[] statements;
454:
455: this .config = (StatementFactoryConfiguration) configuration;
456:
457: this .statementsMap = new HashMap();
458:
459: StatementFactoryConfiguration config = (StatementFactoryConfiguration) configuration;
460:
461: this .statementFactoryName = config.getConfigurationName();
462:
463: statements = config.getStatement();
464:
465: for (int i = 0; i < statements.length; i++) {
466: this .statementsMap.put(statements[i].getQueryName(),
467: statements[i]);
468: }
469:
470: if (log.isTraceEnabled()) {
471: log.trace("Configured statement factory ["
472: + this .statementFactoryName + "] with [ "
473: + statements.length + "] Statements");
474: }
475: }
476:
477: /**
478: * <p>Configure the statement based on the configuration specified in the
479: * sqlStatement or using the default values.</p>
480: *
481: * @param statement A <code>PreparedStatement</code> or a
482: * <code>CallableStatement></code>
483: * @param sqlStatement The configuration information for the statement.
484: * @return Statement A configured statement
485: * @throws SQLException indicates an error configuring the statement
486: */
487: protected Statement configureStatement(Statement statement,
488: StatementConfiguration sqlStatement) throws SQLException {
489:
490: Integer maxRows = (sqlStatement.getMaxRows() != null) ? sqlStatement
491: .getMaxRows()
492: : this .config.getMaxRows();
493:
494: if (maxRows != null) {
495: statement.setMaxRows(maxRows.intValue());
496: }
497:
498: Integer fetchSize = (sqlStatement.getFetchSize() != null) ? sqlStatement
499: .getFetchSize()
500: : this .config.getFetchSize();
501:
502: if (fetchSize != null) {
503: statement.setFetchSize(fetchSize.intValue());
504: }
505:
506: Integer maxFieldSize = (sqlStatement.getMaxFieldSize() != null) ? sqlStatement
507: .getMaxFieldSize()
508: : this .config.getMaxFieldSize();
509:
510: if (maxFieldSize != null) {
511: statement.setMaxFieldSize(maxFieldSize.intValue());
512: }
513:
514: Integer queryTimeout = (sqlStatement.getQueryTimeOut() != null) ? sqlStatement
515: .getQueryTimeOut()
516: : this .config.getQueryTimeOut();
517:
518: if (queryTimeout != null) {
519: statement.setQueryTimeout(queryTimeout.intValue());
520: }
521:
522: return statement;
523: }
524:
525: /**
526: * Builds a debug message containg the configuration status of the supplied
527: * configured statement.
528: *
529: * @param statementConfig statement configuration to build debug
530: * message for
531: * @return a debug message describing the statement config
532: */
533: protected String buildDebugMessage(
534: StatementConfiguration statementConfig) {
535: StringBuffer buf = new StringBuffer();
536: buf.append("StatementFactory [");
537: buf.append(this .statementFactoryName);
538: buf.append("], Query Name [");
539: buf.append(statementConfig.getQueryName());
540: buf.append("] configured: {");
541: buf.append("Fetch Size: ");
542: buf.append(statementConfig.getFetchSize());
543: buf.append(", Max Field Size: ");
544: buf.append(statementConfig.getMaxFieldSize());
545: buf.append(", Max Rows: ");
546: buf.append(statementConfig.getMaxRows());
547: buf.append(", Query Timeout: ");
548: buf.append(statementConfig.getQueryTimeOut());
549: buf.append(", Concurrency: ");
550: buf.append(statementConfig.getResultSetConcurrency());
551: buf.append(", Result Set Type: ");
552: buf.append(statementConfig.getResultSetType());
553: buf.append(", Sql Query: ");
554: buf.append(statementConfig.getQuery());
555:
556: return buf.toString();
557: }
558: }
|