001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.jca.cci.core;
018:
019: import java.sql.SQLException;
020:
021: import javax.resource.NotSupportedException;
022: import javax.resource.ResourceException;
023: import javax.resource.cci.Connection;
024: import javax.resource.cci.ConnectionFactory;
025: import javax.resource.cci.ConnectionSpec;
026: import javax.resource.cci.IndexedRecord;
027: import javax.resource.cci.Interaction;
028: import javax.resource.cci.InteractionSpec;
029: import javax.resource.cci.MappedRecord;
030: import javax.resource.cci.Record;
031: import javax.resource.cci.RecordFactory;
032: import javax.resource.cci.ResultSet;
033:
034: import org.apache.commons.logging.Log;
035: import org.apache.commons.logging.LogFactory;
036:
037: import org.springframework.dao.DataAccessException;
038: import org.springframework.dao.DataAccessResourceFailureException;
039: import org.springframework.jca.cci.CannotCreateRecordException;
040: import org.springframework.jca.cci.CciOperationNotSupportedException;
041: import org.springframework.jca.cci.InvalidResultSetAccessException;
042: import org.springframework.jca.cci.RecordTypeNotSupportedException;
043: import org.springframework.jca.cci.connection.ConnectionFactoryUtils;
044: import org.springframework.jca.cci.connection.NotSupportedRecordFactory;
045: import org.springframework.util.Assert;
046:
047: /**
048: * <b>This is the central class in the CCI core package.</b>
049: * It simplifies the use of CCI and helps to avoid common errors.
050: * It executes core CCI workflow, leaving application code to provide parameters
051: * to CCI and extract results. This class executes EIS queries or updates,
052: * catching ResourceExceptions and translating them to the generic exception
053: * hierarchy defined in the <code>org.springframework.dao</code> package.
054: *
055: * <p>Code using this class can pass in and receive {@link javax.resource.cci.Record}
056: * instances, or alternatively implement callback interfaces for creating input
057: * Records and extracting result objects from output Records (or CCI ResultSets).
058: *
059: * <p>Can be used within a service implementation via direct instantiation
060: * with a ConnectionFactory reference, or get prepared in an application context
061: * and given to services as bean reference. Note: The ConnectionFactory should
062: * always be configured as a bean in the application context, in the first case
063: * given to the service directly, in the second case to the prepared template.
064: *
065: * @author Thierry Templier
066: * @author Juergen Hoeller
067: * @since 1.2
068: * @see RecordCreator
069: * @see RecordExtractor
070: */
071: public class CciTemplate implements CciOperations {
072:
073: private final Log logger = LogFactory.getLog(getClass());
074:
075: private ConnectionFactory connectionFactory;
076:
077: private ConnectionSpec connectionSpec;
078:
079: private RecordCreator outputRecordCreator;
080:
081: /**
082: * Construct a new CciTemplate for bean usage.
083: * <p>Note: The ConnectionFactory has to be set before using the instance.
084: * @see #setConnectionFactory
085: */
086: public CciTemplate() {
087: }
088:
089: /**
090: * Construct a new CciTemplate, given a ConnectionFactory to obtain Connections from.
091: * Note: This will trigger eager initialization of the exception translator.
092: * @param connectionFactory JCA ConnectionFactory to obtain Connections from
093: */
094: public CciTemplate(ConnectionFactory connectionFactory) {
095: setConnectionFactory(connectionFactory);
096: afterPropertiesSet();
097: }
098:
099: /**
100: * Construct a new CciTemplate, given a ConnectionFactory to obtain Connections from.
101: * Note: This will trigger eager initialization of the exception translator.
102: * @param connectionFactory JCA ConnectionFactory to obtain Connections from
103: * @param connectionSpec the CCI ConnectionSpec to obtain Connections for
104: * (may be <code>null</code>)
105: */
106: public CciTemplate(ConnectionFactory connectionFactory,
107: ConnectionSpec connectionSpec) {
108: setConnectionFactory(connectionFactory);
109: setConnectionSpec(connectionSpec);
110: afterPropertiesSet();
111: }
112:
113: /**
114: * Set the CCI ConnectionFactory to obtain Connections from.
115: */
116: public void setConnectionFactory(ConnectionFactory connectionFactory) {
117: this .connectionFactory = connectionFactory;
118: }
119:
120: /**
121: * Return the CCI ConnectionFactory used by this template.
122: */
123: public ConnectionFactory getConnectionFactory() {
124: return this .connectionFactory;
125: }
126:
127: /**
128: * Set the CCI ConnectionSpec that this template instance is
129: * supposed to obtain Connections for.
130: */
131: public void setConnectionSpec(ConnectionSpec connectionSpec) {
132: this .connectionSpec = connectionSpec;
133: }
134:
135: /**
136: * Return the CCI ConnectionSpec used by this template, if any.
137: */
138: public ConnectionSpec getConnectionSpec() {
139: return this .connectionSpec;
140: }
141:
142: /**
143: * Set a RecordCreator that should be used for creating default output Records.
144: * <p>Default is none: When no explicit output Record gets passed into an
145: * <code>execute</code> method, CCI's <code>Interaction.execute</code> variant
146: * that returns an output Record will be called.
147: * <p>Specify a RecordCreator here if you always need to call CCI's
148: * <code>Interaction.execute</code> variant with a passed-in output Record.
149: * Unless there is an explicitly specified output Record, CciTemplate will
150: * then invoke this RecordCreator to create a default output Record instance.
151: * @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record)
152: * @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record, Record)
153: */
154: public void setOutputRecordCreator(RecordCreator creator) {
155: this .outputRecordCreator = creator;
156: }
157:
158: /**
159: * Return a RecordCreator that should be used for creating default output Records.
160: */
161: public RecordCreator getOutputRecordCreator() {
162: return this .outputRecordCreator;
163: }
164:
165: public void afterPropertiesSet() {
166: if (getConnectionFactory() == null) {
167: throw new IllegalArgumentException(
168: "Property 'connectionFactory' is required");
169: }
170: }
171:
172: /**
173: * Create a template derived from this template instance,
174: * inheriting the ConnectionFactory and other settings but
175: * overriding the ConnectionSpec used for obtaining Connections.
176: * @param connectionSpec the CCI ConnectionSpec that the derived template
177: * instance is supposed to obtain Connections for
178: * @return the derived template instance
179: * @see #setConnectionSpec
180: */
181: public CciTemplate getDerivedTemplate(ConnectionSpec connectionSpec) {
182: CciTemplate derived = new CciTemplate();
183: derived.setConnectionFactory(getConnectionFactory());
184: derived.setConnectionSpec(connectionSpec);
185: derived.setOutputRecordCreator(getOutputRecordCreator());
186: return derived;
187: }
188:
189: public Object execute(ConnectionCallback action)
190: throws DataAccessException {
191: Assert.notNull(action, "Callback object must not be null");
192:
193: Connection con = ConnectionFactoryUtils.getConnection(
194: getConnectionFactory(), getConnectionSpec());
195: try {
196: return action.doInConnection(con, getConnectionFactory());
197: } catch (NotSupportedException ex) {
198: throw new CciOperationNotSupportedException(
199: "CCI operation not supported by connector", ex);
200: } catch (ResourceException ex) {
201: throw new DataAccessResourceFailureException(
202: "CCI operation failed", ex);
203: } catch (SQLException ex) {
204: throw new InvalidResultSetAccessException(
205: "Parsing of CCI ResultSet failed", ex);
206: } finally {
207: ConnectionFactoryUtils.releaseConnection(con,
208: getConnectionFactory());
209: }
210: }
211:
212: public Object execute(final InteractionCallback action)
213: throws DataAccessException {
214: Assert.notNull(action, "Callback object must not be null");
215:
216: return execute(new ConnectionCallback() {
217: public Object doInConnection(Connection connection,
218: ConnectionFactory connectionFactory)
219: throws ResourceException, SQLException,
220: DataAccessException {
221:
222: Interaction interaction = connection
223: .createInteraction();
224: try {
225: return action.doInInteraction(interaction,
226: connectionFactory);
227: } finally {
228: closeInteraction(interaction);
229: }
230: }
231: });
232: }
233:
234: public Record execute(InteractionSpec spec, Record inputRecord)
235: throws DataAccessException {
236: return (Record) doExecute(spec, inputRecord, null, null);
237: }
238:
239: public void execute(InteractionSpec spec, Record inputRecord,
240: Record outputRecord) throws DataAccessException {
241: doExecute(spec, inputRecord, outputRecord, null);
242: }
243:
244: public Record execute(InteractionSpec spec,
245: RecordCreator inputCreator) throws DataAccessException {
246: return (Record) doExecute(spec, createRecord(inputCreator),
247: null, null);
248: }
249:
250: public Object execute(InteractionSpec spec, Record inputRecord,
251: RecordExtractor outputExtractor) throws DataAccessException {
252:
253: return doExecute(spec, inputRecord, null, outputExtractor);
254: }
255:
256: public Object execute(InteractionSpec spec,
257: RecordCreator inputCreator, RecordExtractor outputExtractor)
258: throws DataAccessException {
259:
260: return doExecute(spec, createRecord(inputCreator), null,
261: outputExtractor);
262: }
263:
264: /**
265: * Execute the specified interaction on an EIS with CCI.
266: * All other interaction execution methods go through this.
267: * @param spec the CCI InteractionSpec instance that defines
268: * the interaction (connector-specific)
269: * @param inputRecord the input record
270: * @param outputRecord output record (can be <code>null</code>)
271: * @param outputExtractor object to convert the output record to a result object
272: * @return the output data extracted with the RecordExtractor object
273: * @throws DataAccessException if there is any problem
274: */
275: protected Object doExecute(final InteractionSpec spec,
276: final Record inputRecord, final Record outputRecord,
277: final RecordExtractor outputExtractor)
278: throws DataAccessException {
279:
280: return execute(new InteractionCallback() {
281: public Object doInInteraction(Interaction interaction,
282: ConnectionFactory connectionFactory)
283: throws ResourceException, SQLException,
284: DataAccessException {
285:
286: Record outputRecordToUse = outputRecord;
287: try {
288: if (outputRecord != null
289: || getOutputRecordCreator() != null) {
290: // Use the CCI execute method with output record as parameter.
291: if (outputRecord == null) {
292: RecordFactory recordFactory = getRecordFactory(connectionFactory);
293: outputRecordToUse = getOutputRecordCreator()
294: .createRecord(recordFactory);
295: }
296: interaction.execute(spec, inputRecord,
297: outputRecordToUse);
298: } else {
299: outputRecordToUse = interaction.execute(spec,
300: inputRecord);
301: }
302: if (outputExtractor != null) {
303: return outputExtractor
304: .extractData(outputRecordToUse);
305: } else {
306: return outputRecordToUse;
307: }
308: } finally {
309: if (outputRecordToUse instanceof ResultSet) {
310: closeResultSet((ResultSet) outputRecordToUse);
311: }
312: }
313: }
314: });
315: }
316:
317: /**
318: * Create an indexed Record through the ConnectionFactory's RecordFactory.
319: * @param name the name of the record
320: * @return the Record
321: * @throws DataAccessException if creation of the Record failed
322: * @see #getRecordFactory(javax.resource.cci.ConnectionFactory)
323: * @see javax.resource.cci.RecordFactory#createIndexedRecord(String)
324: */
325: public IndexedRecord createIndexedRecord(String name)
326: throws DataAccessException {
327: try {
328: RecordFactory recordFactory = getRecordFactory(getConnectionFactory());
329: return recordFactory.createIndexedRecord(name);
330: } catch (NotSupportedException ex) {
331: throw new RecordTypeNotSupportedException(
332: "Creation of indexed Record not supported by connector",
333: ex);
334: } catch (ResourceException ex) {
335: throw new CannotCreateRecordException(
336: "Creation of indexed Record failed", ex);
337: }
338: }
339:
340: /**
341: * Create a mapped Record from the ConnectionFactory's RecordFactory.
342: * @param name record name
343: * @return the Record
344: * @throws DataAccessException if creation of the Record failed
345: * @see #getRecordFactory(javax.resource.cci.ConnectionFactory)
346: * @see javax.resource.cci.RecordFactory#createMappedRecord(String)
347: */
348: public MappedRecord createMappedRecord(String name)
349: throws DataAccessException {
350: try {
351: RecordFactory recordFactory = getRecordFactory(getConnectionFactory());
352: return recordFactory.createMappedRecord(name);
353: } catch (NotSupportedException ex) {
354: throw new RecordTypeNotSupportedException(
355: "Creation of mapped Record not supported by connector",
356: ex);
357: } catch (ResourceException ex) {
358: throw new CannotCreateRecordException(
359: "Creation of mapped Record failed", ex);
360: }
361: }
362:
363: /**
364: * Invoke the given RecordCreator, converting JCA ResourceExceptions
365: * to Spring's DataAccessException hierarchy.
366: * @param recordCreator the RecordCreator to invoke
367: * @return the created Record
368: * @throws DataAccessException if creation of the Record failed
369: * @see #getRecordFactory(javax.resource.cci.ConnectionFactory)
370: * @see RecordCreator#createRecord(javax.resource.cci.RecordFactory)
371: */
372: protected Record createRecord(RecordCreator recordCreator)
373: throws DataAccessException {
374: try {
375: RecordFactory recordFactory = getRecordFactory(getConnectionFactory());
376: return recordCreator.createRecord(recordFactory);
377: } catch (NotSupportedException ex) {
378: throw new RecordTypeNotSupportedException(
379: "Creation of the desired Record type not supported by connector",
380: ex);
381: } catch (ResourceException ex) {
382: throw new CannotCreateRecordException(
383: "Creation of the desired Record failed", ex);
384: }
385: }
386:
387: /**
388: * Return a RecordFactory for the given ConnectionFactory.
389: * <p>Default implementation returns the connector's RecordFactory if
390: * available, falling back to a NotSupportedRecordFactory placeholder.
391: * This allows to invoke a RecordCreator callback with a non-null
392: * RecordFactory reference in any case.
393: * @param connectionFactory the CCI ConnectionFactory
394: * @return the CCI RecordFactory for the ConnectionFactory
395: * @throws ResourceException if thrown by CCI methods
396: * @see org.springframework.jca.cci.connection.NotSupportedRecordFactory
397: */
398: protected RecordFactory getRecordFactory(
399: ConnectionFactory connectionFactory)
400: throws ResourceException {
401: try {
402: return getConnectionFactory().getRecordFactory();
403: } catch (NotSupportedException ex) {
404: return new NotSupportedRecordFactory();
405: }
406: }
407:
408: /**
409: * Close the given CCI Interaction and ignore any thrown exception.
410: * This is useful for typical finally blocks in manual CCI code.
411: * @param interaction the CCI Interaction to close
412: * @see javax.resource.cci.Interaction#close()
413: */
414: private void closeInteraction(Interaction interaction) {
415: if (interaction != null) {
416: try {
417: interaction.close();
418: } catch (ResourceException ex) {
419: logger.debug("Could not close CCI Interaction", ex);
420: } catch (Throwable ex) {
421: // We don't trust the CCI driver: It might throw RuntimeException or Error.
422: logger
423: .debug(
424: "Unexpected exception on closing CCI Interaction",
425: ex);
426: }
427: }
428: }
429:
430: /**
431: * Close the given CCI ResultSet and ignore any thrown exception.
432: * This is useful for typical finally blocks in manual CCI code.
433: * @param resultSet the CCI ResultSet to close
434: * @see javax.resource.cci.ResultSet#close()
435: */
436: private void closeResultSet(ResultSet resultSet) {
437: if (resultSet != null) {
438: try {
439: resultSet.close();
440: } catch (SQLException ex) {
441: logger.debug("Could not close CCI ResultSet", ex);
442: } catch (Throwable ex) {
443: // We don't trust the CCI driver: It might throw RuntimeException or Error.
444: logger
445: .debug(
446: "Unexpected exception on closing CCI ResultSet",
447: ex);
448: }
449: }
450: }
451:
452: }
|