001/*
002 * Copyright 2002-2012 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 *      https://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
017package org.springframework.jca.cci.core;
018
019import java.sql.SQLException;
020import javax.resource.NotSupportedException;
021import javax.resource.ResourceException;
022import javax.resource.cci.Connection;
023import javax.resource.cci.ConnectionFactory;
024import javax.resource.cci.ConnectionSpec;
025import javax.resource.cci.IndexedRecord;
026import javax.resource.cci.Interaction;
027import javax.resource.cci.InteractionSpec;
028import javax.resource.cci.MappedRecord;
029import javax.resource.cci.Record;
030import javax.resource.cci.RecordFactory;
031import javax.resource.cci.ResultSet;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035
036import org.springframework.dao.DataAccessException;
037import org.springframework.dao.DataAccessResourceFailureException;
038import org.springframework.jca.cci.CannotCreateRecordException;
039import org.springframework.jca.cci.CciOperationNotSupportedException;
040import org.springframework.jca.cci.InvalidResultSetAccessException;
041import org.springframework.jca.cci.RecordTypeNotSupportedException;
042import org.springframework.jca.cci.connection.ConnectionFactoryUtils;
043import org.springframework.jca.cci.connection.NotSupportedRecordFactory;
044import org.springframework.util.Assert;
045
046/**
047 * <b>This is the central class in the CCI core package.</b>
048 * It simplifies the use of CCI and helps to avoid common errors.
049 * It executes core CCI workflow, leaving application code to provide parameters
050 * to CCI and extract results. This class executes EIS queries or updates,
051 * catching ResourceExceptions and translating them to the generic exception
052 * hierarchy defined in the {@code org.springframework.dao} package.
053 *
054 * <p>Code using this class can pass in and receive {@link javax.resource.cci.Record}
055 * instances, or alternatively implement callback interfaces for creating input
056 * Records and extracting result objects from output Records (or CCI ResultSets).
057 *
058 * <p>Can be used within a service implementation via direct instantiation
059 * with a ConnectionFactory reference, or get prepared in an application context
060 * and given to services as bean reference. Note: The ConnectionFactory should
061 * always be configured as a bean in the application context, in the first case
062 * given to the service directly, in the second case to the prepared template.
063 *
064 * @author Thierry Templier
065 * @author Juergen Hoeller
066 * @since 1.2
067 * @see RecordCreator
068 * @see RecordExtractor
069 */
070public class CciTemplate implements CciOperations {
071
072        private final Log logger = LogFactory.getLog(getClass());
073
074        private ConnectionFactory connectionFactory;
075
076        private ConnectionSpec connectionSpec;
077
078        private RecordCreator outputRecordCreator;
079
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})
105         */
106        public CciTemplate(ConnectionFactory connectionFactory, ConnectionSpec connectionSpec) {
107                setConnectionFactory(connectionFactory);
108                setConnectionSpec(connectionSpec);
109                afterPropertiesSet();
110        }
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} method, CCI's {@code Interaction.execute} 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} 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("Property 'connectionFactory' is required");
168                }
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
190        @Override
191        public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
192                Assert.notNull(action, "Callback object must not be null");
193                Connection con = ConnectionFactoryUtils.getConnection(getConnectionFactory(), getConnectionSpec());
194                try {
195                        return action.doInConnection(con, getConnectionFactory());
196                }
197                catch (NotSupportedException ex) {
198                        throw new CciOperationNotSupportedException("CCI operation not supported by connector", ex);
199                }
200                catch (ResourceException ex) {
201                        throw new DataAccessResourceFailureException("CCI operation failed", ex);
202                }
203                catch (SQLException ex) {
204                        throw new InvalidResultSetAccessException("Parsing of CCI ResultSet failed", ex);
205                }
206                finally {
207                        ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory());
208                }
209        }
210
211        @Override
212        public <T> T execute(final InteractionCallback<T> action) throws DataAccessException {
213                Assert.notNull(action, "Callback object must not be null");
214                return execute(new ConnectionCallback<T>() {
215                        @Override
216                        public T doInConnection(Connection connection, ConnectionFactory connectionFactory)
217                                        throws ResourceException, SQLException, DataAccessException {
218                                Interaction interaction = connection.createInteraction();
219                                try {
220                                        return action.doInInteraction(interaction, connectionFactory);
221                                }
222                                finally {
223                                        closeInteraction(interaction);
224                                }
225                        }
226                });
227        }
228
229        @Override
230        public Record execute(InteractionSpec spec, Record inputRecord) throws DataAccessException {
231                return doExecute(spec, inputRecord, null, new SimpleRecordExtractor());
232        }
233
234        @Override
235        public void execute(InteractionSpec spec, Record inputRecord, Record outputRecord) throws DataAccessException {
236                doExecute(spec, inputRecord, outputRecord, null);
237        }
238
239        @Override
240        public Record execute(InteractionSpec spec, RecordCreator inputCreator) throws DataAccessException {
241                return doExecute(spec, createRecord(inputCreator), null, new SimpleRecordExtractor());
242        }
243
244        @Override
245        public <T> T execute(InteractionSpec spec, Record inputRecord, RecordExtractor<T> outputExtractor)
246                        throws DataAccessException {
247
248                return doExecute(spec, inputRecord, null, outputExtractor);
249        }
250
251        @Override
252        public <T> T execute(InteractionSpec spec, RecordCreator inputCreator, RecordExtractor<T> outputExtractor)
253                        throws DataAccessException {
254
255                return doExecute(spec, createRecord(inputCreator), null, outputExtractor);
256        }
257
258        /**
259         * Execute the specified interaction on an EIS with CCI.
260         * All other interaction execution methods go through this.
261         * @param spec the CCI InteractionSpec instance that defines
262         * the interaction (connector-specific)
263         * @param inputRecord the input record
264         * @param outputRecord output record (can be {@code null})
265         * @param outputExtractor object to convert the output record to a result object
266         * @return the output data extracted with the RecordExtractor object
267         * @throws DataAccessException if there is any problem
268         */
269        protected <T> T doExecute(
270                        final InteractionSpec spec, final Record inputRecord, final Record outputRecord,
271                        final RecordExtractor<T> outputExtractor) throws DataAccessException {
272
273                return execute(new InteractionCallback<T>() {
274                        @Override
275                        public T doInInteraction(Interaction interaction, ConnectionFactory connectionFactory)
276                                        throws ResourceException, SQLException, DataAccessException {
277                                Record outputRecordToUse = outputRecord;
278                                try {
279                                        if (outputRecord != null || getOutputRecordCreator() != null) {
280                                                // Use the CCI execute method with output record as parameter.
281                                                if (outputRecord == null) {
282                                                        RecordFactory recordFactory = getRecordFactory(connectionFactory);
283                                                        outputRecordToUse = getOutputRecordCreator().createRecord(recordFactory);
284                                                }
285                                                interaction.execute(spec, inputRecord, outputRecordToUse);
286                                        }
287                                        else {
288                                                outputRecordToUse = interaction.execute(spec, inputRecord);
289                                        }
290                                        return (outputExtractor != null ? outputExtractor.extractData(outputRecordToUse) : null);
291                                }
292                                finally {
293                                        if (outputRecordToUse instanceof ResultSet) {
294                                                closeResultSet((ResultSet) outputRecordToUse);
295                                        }
296                                }
297                        }
298                });
299        }
300
301
302        /**
303         * Create an indexed Record through the ConnectionFactory's RecordFactory.
304         * @param name the name of the record
305         * @return the Record
306         * @throws DataAccessException if creation of the Record failed
307         * @see #getRecordFactory(javax.resource.cci.ConnectionFactory)
308         * @see javax.resource.cci.RecordFactory#createIndexedRecord(String)
309         */
310        public IndexedRecord createIndexedRecord(String name) throws DataAccessException {
311                try {
312                        RecordFactory recordFactory = getRecordFactory(getConnectionFactory());
313                        return recordFactory.createIndexedRecord(name);
314                }
315                catch (NotSupportedException ex) {
316                        throw new RecordTypeNotSupportedException("Creation of indexed Record not supported by connector", ex);
317                }
318                catch (ResourceException ex) {
319                        throw new CannotCreateRecordException("Creation of indexed Record failed", ex);
320                }
321        }
322
323        /**
324         * Create a mapped Record from the ConnectionFactory's RecordFactory.
325         * @param name record name
326         * @return the Record
327         * @throws DataAccessException if creation of the Record failed
328         * @see #getRecordFactory(javax.resource.cci.ConnectionFactory)
329         * @see javax.resource.cci.RecordFactory#createMappedRecord(String)
330         */
331        public MappedRecord createMappedRecord(String name) throws DataAccessException {
332                try {
333                        RecordFactory recordFactory = getRecordFactory(getConnectionFactory());
334                        return recordFactory.createMappedRecord(name);
335                }
336                catch (NotSupportedException ex) {
337                        throw new RecordTypeNotSupportedException("Creation of mapped Record not supported by connector", ex);
338                }
339                catch (ResourceException ex) {
340                        throw new CannotCreateRecordException("Creation of mapped Record failed", ex);
341                }
342        }
343
344        /**
345         * Invoke the given RecordCreator, converting JCA ResourceExceptions
346         * to Spring's DataAccessException hierarchy.
347         * @param recordCreator the RecordCreator to invoke
348         * @return the created Record
349         * @throws DataAccessException if creation of the Record failed
350         * @see #getRecordFactory(javax.resource.cci.ConnectionFactory)
351         * @see RecordCreator#createRecord(javax.resource.cci.RecordFactory)
352         */
353        protected Record createRecord(RecordCreator recordCreator) throws DataAccessException {
354                try {
355                        RecordFactory recordFactory = getRecordFactory(getConnectionFactory());
356                        return recordCreator.createRecord(recordFactory);
357                }
358                catch (NotSupportedException ex) {
359                        throw new RecordTypeNotSupportedException(
360                                        "Creation of the desired Record type not supported by connector", ex);
361                }
362                catch (ResourceException ex) {
363                        throw new CannotCreateRecordException("Creation of the desired Record failed", ex);
364                }
365        }
366
367        /**
368         * Return a RecordFactory for the given ConnectionFactory.
369         * <p>Default implementation returns the connector's RecordFactory if
370         * available, falling back to a NotSupportedRecordFactory placeholder.
371         * This allows to invoke a RecordCreator callback with a non-null
372         * RecordFactory reference in any case.
373         * @param connectionFactory the CCI ConnectionFactory
374         * @return the CCI RecordFactory for the ConnectionFactory
375         * @throws ResourceException if thrown by CCI methods
376         * @see org.springframework.jca.cci.connection.NotSupportedRecordFactory
377         */
378        protected RecordFactory getRecordFactory(ConnectionFactory connectionFactory) throws ResourceException {
379                try {
380                        return connectionFactory.getRecordFactory();
381                }
382                catch (NotSupportedException ex) {
383                        return new NotSupportedRecordFactory();
384                }
385        }
386
387
388        /**
389         * Close the given CCI Interaction and ignore any thrown exception.
390         * This is useful for typical finally blocks in manual CCI code.
391         * @param interaction the CCI Interaction to close
392         * @see javax.resource.cci.Interaction#close()
393         */
394        private void closeInteraction(Interaction interaction) {
395                if (interaction != null) {
396                        try {
397                                interaction.close();
398                        }
399                        catch (ResourceException ex) {
400                                logger.trace("Could not close CCI Interaction", ex);
401                        }
402                        catch (Throwable ex) {
403                                // We don't trust the CCI driver: It might throw RuntimeException or Error.
404                                logger.trace("Unexpected exception on closing CCI Interaction", ex);
405                        }
406                }
407        }
408
409        /**
410         * Close the given CCI ResultSet and ignore any thrown exception.
411         * This is useful for typical finally blocks in manual CCI code.
412         * @param resultSet the CCI ResultSet to close
413         * @see javax.resource.cci.ResultSet#close()
414         */
415        private void closeResultSet(ResultSet resultSet) {
416                if (resultSet != null) {
417                        try {
418                                resultSet.close();
419                        }
420                        catch (SQLException ex) {
421                                logger.trace("Could not close CCI ResultSet", ex);
422                        }
423                        catch (Throwable ex) {
424                                // We don't trust the CCI driver: It might throw RuntimeException or Error.
425                                logger.trace("Unexpected exception on closing CCI ResultSet", ex);
426                        }
427                }
428        }
429
430
431        private static class SimpleRecordExtractor implements RecordExtractor<Record> {
432
433                @Override
434                public Record extractData(Record record) {
435                        return record;
436                }
437        }
438
439}