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: * @see org.springframework.dao
071: * @see org.springframework.jca.cci.connection
072: * @see org.springframework.jca.cci.object
073: */
074: public class CciTemplate implements CciOperations {
075:
076: private final Log logger = LogFactory.getLog(getClass());
077:
078: private ConnectionFactory connectionFactory;
079:
080: private ConnectionSpec connectionSpec;
081:
082: private RecordCreator outputRecordCreator;
083:
084: /**
085: * Construct a new CciTemplate for bean usage.
086: * <p>Note: The ConnectionFactory has to be set before using the instance.
087: * @see #setConnectionFactory
088: */
089: public CciTemplate() {
090: }
091:
092: /**
093: * Construct a new CciTemplate, given a ConnectionFactory to obtain Connections from.
094: * Note: This will trigger eager initialization of the exception translator.
095: * @param connectionFactory JCA ConnectionFactory to obtain Connections from
096: */
097: public CciTemplate(ConnectionFactory connectionFactory) {
098: setConnectionFactory(connectionFactory);
099: afterPropertiesSet();
100: }
101:
102: /**
103: * Construct a new CciTemplate, given a ConnectionFactory to obtain Connections from.
104: * Note: This will trigger eager initialization of the exception translator.
105: * @param connectionFactory JCA ConnectionFactory to obtain Connections from
106: * @param connectionSpec the CCI ConnectionSpec to obtain Connections for
107: * (may be <code>null</code>)
108: */
109: public CciTemplate(ConnectionFactory connectionFactory,
110: ConnectionSpec connectionSpec) {
111: setConnectionFactory(connectionFactory);
112: setConnectionSpec(connectionSpec);
113: afterPropertiesSet();
114: }
115:
116: /**
117: * Set the CCI ConnectionFactory to obtain Connections from.
118: */
119: public void setConnectionFactory(ConnectionFactory connectionFactory) {
120: this .connectionFactory = connectionFactory;
121: }
122:
123: /**
124: * Return the CCI ConnectionFactory used by this template.
125: */
126: public ConnectionFactory getConnectionFactory() {
127: return this .connectionFactory;
128: }
129:
130: /**
131: * Set the CCI ConnectionSpec that this template instance is
132: * supposed to obtain Connections for.
133: */
134: public void setConnectionSpec(ConnectionSpec connectionSpec) {
135: this .connectionSpec = connectionSpec;
136: }
137:
138: /**
139: * Return the CCI ConnectionSpec used by this template, if any.
140: */
141: public ConnectionSpec getConnectionSpec() {
142: return this .connectionSpec;
143: }
144:
145: /**
146: * Set a RecordCreator that should be used for creating default output Records.
147: * <p>Default is none: When no explicit output Record gets passed into an
148: * <code>execute</code> method, CCI's <code>Interaction.execute</code> variant
149: * that returns an output Record will be called.
150: * <p>Specify a RecordCreator here if you always need to call CCI's
151: * <code>Interaction.execute</code> variant with a passed-in output Record.
152: * Unless there is an explicitly specified output Record, CciTemplate will
153: * then invoke this RecordCreator to create a default output Record instance.
154: * @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record)
155: * @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record, Record)
156: */
157: public void setOutputRecordCreator(RecordCreator creator) {
158: outputRecordCreator = creator;
159: }
160:
161: /**
162: * Return a RecordCreator that should be used for creating default output Records.
163: */
164: public RecordCreator getOutputRecordCreator() {
165: return this .outputRecordCreator;
166: }
167:
168: public void afterPropertiesSet() {
169: if (getConnectionFactory() == null) {
170: throw new IllegalArgumentException(
171: "Property 'connectionFactory' is required");
172: }
173: }
174:
175: /**
176: * Create a template derived from this template instance,
177: * inheriting the ConnectionFactory and other settings but
178: * overriding the ConnectionSpec used for obtaining Connections.
179: * @param connectionSpec the CCI ConnectionSpec that the derived template
180: * instance is supposed to obtain Connections for
181: * @return the derived template instance
182: * @see #setConnectionSpec
183: */
184: public CciTemplate getDerivedTemplate(ConnectionSpec connectionSpec) {
185: CciTemplate derived = new CciTemplate();
186: derived.setConnectionFactory(getConnectionFactory());
187: derived.setConnectionSpec(connectionSpec);
188: derived.setOutputRecordCreator(getOutputRecordCreator());
189: return derived;
190: }
191:
192: public Object execute(ConnectionCallback action)
193: throws DataAccessException {
194: Assert.notNull(action, "Callback object must not be null");
195:
196: Connection con = ConnectionFactoryUtils.getConnection(
197: getConnectionFactory(), getConnectionSpec());
198: try {
199: return action.doInConnection(con, getConnectionFactory());
200: } catch (NotSupportedException ex) {
201: throw new CciOperationNotSupportedException(
202: "CCI operation not supported by connector", ex);
203: } catch (ResourceException ex) {
204: throw new DataAccessResourceFailureException(
205: "CCI operation failed", ex);
206: } catch (SQLException ex) {
207: throw new InvalidResultSetAccessException(
208: "Parsing of CCI ResultSet failed", ex);
209: } finally {
210: ConnectionFactoryUtils.releaseConnection(con,
211: getConnectionFactory());
212: }
213: }
214:
215: public Object execute(final InteractionCallback action)
216: throws DataAccessException {
217: Assert.notNull(action, "Callback object must not be null");
218:
219: return execute(new ConnectionCallback() {
220: public Object doInConnection(Connection connection,
221: ConnectionFactory connectionFactory)
222: throws ResourceException, SQLException,
223: DataAccessException {
224:
225: Interaction interaction = connection
226: .createInteraction();
227: try {
228: return action.doInInteraction(interaction,
229: connectionFactory);
230: } finally {
231: closeInteraction(interaction);
232: }
233: }
234: });
235: }
236:
237: public Record execute(InteractionSpec spec, Record inputRecord)
238: throws DataAccessException {
239: return (Record) doExecute(spec, inputRecord, null, null);
240: }
241:
242: public void execute(InteractionSpec spec, Record inputRecord,
243: Record outputRecord) throws DataAccessException {
244: doExecute(spec, inputRecord, outputRecord, null);
245: }
246:
247: public Record execute(InteractionSpec spec,
248: RecordCreator inputCreator) throws DataAccessException {
249: return (Record) doExecute(spec, createRecord(inputCreator),
250: null, null);
251: }
252:
253: public Object execute(InteractionSpec spec, Record inputRecord,
254: RecordExtractor outputExtractor) throws DataAccessException {
255:
256: return doExecute(spec, inputRecord, null, outputExtractor);
257: }
258:
259: public Object execute(InteractionSpec spec,
260: RecordCreator inputCreator, RecordExtractor outputExtractor)
261: throws DataAccessException {
262:
263: return doExecute(spec, createRecord(inputCreator), null,
264: outputExtractor);
265: }
266:
267: /**
268: * Execute the specified interaction on an EIS with CCI.
269: * All other interaction execution methods go through this.
270: * @param spec the CCI InteractionSpec instance that defines
271: * the interaction (connector-specific)
272: * @param inputRecord the input record
273: * @param outputRecord output record (can be <code>null</code>)
274: * @param outputExtractor object to convert the output record to a result object
275: * @return the output data extracted with the RecordExtractor object
276: * @throws DataAccessException if there is any problem
277: */
278: protected Object doExecute(final InteractionSpec spec,
279: final Record inputRecord, final Record outputRecord,
280: final RecordExtractor outputExtractor)
281: throws DataAccessException {
282:
283: return execute(new InteractionCallback() {
284: public Object doInInteraction(Interaction interaction,
285: ConnectionFactory connectionFactory)
286: throws ResourceException, SQLException,
287: DataAccessException {
288:
289: Record outputRecordToUse = outputRecord;
290: try {
291: if (outputRecord != null
292: || getOutputRecordCreator() != null) {
293: // Use the CCI execute method with output record as parameter.
294: if (outputRecord == null) {
295: RecordFactory recordFactory = getRecordFactory(connectionFactory);
296: outputRecordToUse = getOutputRecordCreator()
297: .createRecord(recordFactory);
298: }
299: interaction.execute(spec, inputRecord,
300: outputRecordToUse);
301: } else {
302: outputRecordToUse = interaction.execute(spec,
303: inputRecord);
304: }
305: if (outputExtractor != null) {
306: return outputExtractor
307: .extractData(outputRecordToUse);
308: } else {
309: return outputRecordToUse;
310: }
311: } finally {
312: if (outputRecordToUse instanceof ResultSet) {
313: closeResultSet((ResultSet) outputRecordToUse);
314: }
315: }
316: }
317: });
318: }
319:
320: /**
321: * Create an indexed Record through the ConnectionFactory's RecordFactory.
322: * @param name the name of the record
323: * @return the Record
324: * @throws DataAccessException if creation of the Record failed
325: * @see #getRecordFactory(javax.resource.cci.ConnectionFactory)
326: * @see javax.resource.cci.RecordFactory#createIndexedRecord(String)
327: */
328: public IndexedRecord createIndexedRecord(String name)
329: throws DataAccessException {
330: try {
331: RecordFactory recordFactory = getRecordFactory(getConnectionFactory());
332: return recordFactory.createIndexedRecord(name);
333: } catch (NotSupportedException ex) {
334: throw new RecordTypeNotSupportedException(
335: "Creation of indexed Record not supported by connector",
336: ex);
337: } catch (ResourceException ex) {
338: throw new CannotCreateRecordException(
339: "Creation of indexed Record failed", ex);
340: }
341: }
342:
343: /**
344: * Create a mapped Record from the ConnectionFactory's RecordFactory.
345: * @param name record name
346: * @return the Record
347: * @throws DataAccessException if creation of the Record failed
348: * @see #getRecordFactory(javax.resource.cci.ConnectionFactory)
349: * @see javax.resource.cci.RecordFactory#createMappedRecord(String)
350: */
351: public MappedRecord createMappedRecord(String name)
352: throws DataAccessException {
353: try {
354: RecordFactory recordFactory = getRecordFactory(getConnectionFactory());
355: return recordFactory.createMappedRecord(name);
356: } catch (NotSupportedException ex) {
357: throw new RecordTypeNotSupportedException(
358: "Creation of mapped Record not supported by connector",
359: ex);
360: } catch (ResourceException ex) {
361: throw new CannotCreateRecordException(
362: "Creation of mapped Record failed", ex);
363: }
364: }
365:
366: /**
367: * Invoke the given RecordCreator, converting JCA ResourceExceptions
368: * to Spring's DataAccessException hierarchy.
369: * @param recordCreator the RecordCreator to invoke
370: * @return the created Record
371: * @throws DataAccessException if creation of the Record failed
372: * @see #getRecordFactory(javax.resource.cci.ConnectionFactory)
373: * @see RecordCreator#createRecord(javax.resource.cci.RecordFactory)
374: */
375: protected Record createRecord(RecordCreator recordCreator)
376: throws DataAccessException {
377: try {
378: RecordFactory recordFactory = getRecordFactory(getConnectionFactory());
379: return recordCreator.createRecord(recordFactory);
380: } catch (NotSupportedException ex) {
381: throw new RecordTypeNotSupportedException(
382: "Creation of the desired Record type not supported by connector",
383: ex);
384: } catch (ResourceException ex) {
385: throw new CannotCreateRecordException(
386: "Creation of the desired Record failed", ex);
387: }
388: }
389:
390: /**
391: * Return a RecordFactory for the given ConnectionFactory.
392: * <p>Default implementation returns the connector's RecordFactory if
393: * available, falling back to a NotSupportedRecordFactory placeholder.
394: * This allows to invoke a RecordCreator callback with a non-null
395: * RecordFactory reference in any case.
396: * @param connectionFactory the CCI ConnectionFactory
397: * @return the CCI RecordFactory for the ConnectionFactory
398: * @throws ResourceException if thrown by CCI methods
399: * @see org.springframework.jca.cci.connection.NotSupportedRecordFactory
400: */
401: protected RecordFactory getRecordFactory(
402: ConnectionFactory connectionFactory)
403: throws ResourceException {
404: try {
405: return getConnectionFactory().getRecordFactory();
406: } catch (NotSupportedException ex) {
407: return new NotSupportedRecordFactory();
408: }
409: }
410:
411: /**
412: * Close the given CCI Interaction and ignore any thrown exception.
413: * This is useful for typical finally blocks in manual CCI code.
414: * @param interaction the CCI Interaction to close
415: * @see javax.resource.cci.Interaction#close()
416: */
417: private void closeInteraction(Interaction interaction) {
418: if (interaction != null) {
419: try {
420: interaction.close();
421: } catch (ResourceException ex) {
422: logger.debug("Could not close CCI Interaction", ex);
423: } catch (Throwable ex) {
424: // We don't trust the CCI driver: It might throw RuntimeException or Error.
425: logger
426: .debug(
427: "Unexpected exception on closing CCI Interaction",
428: ex);
429: }
430: }
431: }
432:
433: /**
434: * Close the given CCI ResultSet and ignore any thrown exception.
435: * This is useful for typical finally blocks in manual CCI code.
436: * @param resultSet the CCI ResultSet to close
437: * @see javax.resource.cci.ResultSet#close()
438: */
439: private void closeResultSet(ResultSet resultSet) {
440: if (resultSet != null) {
441: try {
442: resultSet.close();
443: } catch (SQLException ex) {
444: logger.debug("Could not close CCI ResultSet", ex);
445: } catch (Throwable ex) {
446: // We don't trust the CCI driver: It might throw RuntimeException or Error.
447: logger
448: .debug(
449: "Unexpected exception on closing CCI ResultSet",
450: ex);
451: }
452: }
453: }
454:
455: }
|