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