/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.jca.cci.core;
import java.sql.SQLException;
import javax.resource.NotSupportedException;
import javax.resource.ResourceException;
import javax.resource.cci.Connection;
import javax.resource.cci.ConnectionFactory;
import javax.resource.cci.ConnectionSpec;
import javax.resource.cci.IndexedRecord;
import javax.resource.cci.Interaction;
import javax.resource.cci.InteractionSpec;
import javax.resource.cci.MappedRecord;
import javax.resource.cci.Record;
import javax.resource.cci.RecordFactory;
import javax.resource.cci.ResultSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.jca.cci.CannotCreateRecordException;
import org.springframework.jca.cci.CciOperationNotSupportedException;
import org.springframework.jca.cci.InvalidResultSetAccessException;
import org.springframework.jca.cci.RecordTypeNotSupportedException;
import org.springframework.jca.cci.connection.ConnectionFactoryUtils;
import org.springframework.jca.cci.connection.NotSupportedRecordFactory;
import org.springframework.util.Assert;
/**
* <b>This is the central class in the CCI core package.</b>
* It simplifies the use of CCI and helps to avoid common errors.
* It executes core CCI workflow, leaving application code to provide parameters
* to CCI and extract results. This class executes EIS queries or updates,
* catching ResourceExceptions and translating them to the generic exception
* hierarchy defined in the <code>org.springframework.dao</code> package.
*
* <p>Code using this class can pass in and receive {@link javax.resource.cci.Record}
* instances, or alternatively implement callback interfaces for creating input
* Records and extracting result objects from output Records (or CCI ResultSets).
*
* <p>Can be used within a service implementation via direct instantiation
* with a ConnectionFactory reference, or get prepared in an application context
* and given to services as bean reference. Note: The ConnectionFactory should
* always be configured as a bean in the application context, in the first case
* given to the service directly, in the second case to the prepared template.
*
* @author Thierry Templier
* @author Juergen Hoeller
* @since 1.2
* @see RecordCreator
* @see RecordExtractor
*/
public class CciTemplate implements CciOperations {
private final Log logger = LogFactory.getLog(getClass());
private ConnectionFactory connectionFactory;
private ConnectionSpec connectionSpec;
private RecordCreator outputRecordCreator;
/**
* Construct a new CciTemplate for bean usage.
* <p>Note: The ConnectionFactory has to be set before using the instance.
* @see #setConnectionFactory
*/
public CciTemplate() {
}
/**
* Construct a new CciTemplate, given a ConnectionFactory to obtain Connections from.
* Note: This will trigger eager initialization of the exception translator.
* @param connectionFactory JCA ConnectionFactory to obtain Connections from
*/
public CciTemplate(ConnectionFactory connectionFactory) {
setConnectionFactory(connectionFactory);
afterPropertiesSet();
}
/**
* Construct a new CciTemplate, given a ConnectionFactory to obtain Connections from.
* Note: This will trigger eager initialization of the exception translator.
* @param connectionFactory JCA ConnectionFactory to obtain Connections from
* @param connectionSpec the CCI ConnectionSpec to obtain Connections for
* (may be <code>null</code>)
*/
public CciTemplate(ConnectionFactory connectionFactory, ConnectionSpec connectionSpec) {
setConnectionFactory(connectionFactory);
setConnectionSpec(connectionSpec);
afterPropertiesSet();
}
/**
* Set the CCI ConnectionFactory to obtain Connections from.
*/
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
}
/**
* Return the CCI ConnectionFactory used by this template.
*/
public ConnectionFactory getConnectionFactory() {
return this.connectionFactory;
}
/**
* Set the CCI ConnectionSpec that this template instance is
* supposed to obtain Connections for.
*/
public void setConnectionSpec(ConnectionSpec connectionSpec) {
this.connectionSpec = connectionSpec;
}
/**
* Return the CCI ConnectionSpec used by this template, if any.
*/
public ConnectionSpec getConnectionSpec() {
return this.connectionSpec;
}
/**
* Set a RecordCreator that should be used for creating default output Records.
* <p>Default is none: When no explicit output Record gets passed into an
* <code>execute</code> method, CCI's <code>Interaction.execute</code> variant
* that returns an output Record will be called.
* <p>Specify a RecordCreator here if you always need to call CCI's
* <code>Interaction.execute</code> variant with a passed-in output Record.
* Unless there is an explicitly specified output Record, CciTemplate will
* then invoke this RecordCreator to create a default output Record instance.
* @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record)
* @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, Record, Record)
*/
public void setOutputRecordCreator(RecordCreator creator) {
this.outputRecordCreator = creator;
}
/**
* Return a RecordCreator that should be used for creating default output Records.
*/
public RecordCreator getOutputRecordCreator() {
return this.outputRecordCreator;
}
public void afterPropertiesSet() {
if (getConnectionFactory() == null) {
throw new IllegalArgumentException("Property 'connectionFactory' is required");
}
}
/**
* Create a template derived from this template instance,
* inheriting the ConnectionFactory and other settings but
* overriding the ConnectionSpec used for obtaining Connections.
* @param connectionSpec the CCI ConnectionSpec that the derived template
* instance is supposed to obtain Connections for
* @return the derived template instance
* @see #setConnectionSpec
*/
public CciTemplate getDerivedTemplate(ConnectionSpec connectionSpec) {
CciTemplate derived = new CciTemplate();
derived.setConnectionFactory(getConnectionFactory());
derived.setConnectionSpec(connectionSpec);
derived.setOutputRecordCreator(getOutputRecordCreator());
return derived;
}
public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = ConnectionFactoryUtils.getConnection(getConnectionFactory(), getConnectionSpec());
try {
return action.doInConnection(con, getConnectionFactory());
}
catch (NotSupportedException ex) {
throw new CciOperationNotSupportedException("CCI operation not supported by connector", ex);
}
catch (ResourceException ex) {
throw new DataAccessResourceFailureException("CCI operation failed", ex);
}
catch (SQLException ex) {
throw new InvalidResultSetAccessException("Parsing of CCI ResultSet failed", ex);
}
finally {
ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory());
}
}
public <T> T execute(final InteractionCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
return execute(new ConnectionCallback<T>() {
public T doInConnection(Connection connection, ConnectionFactory connectionFactory)
throws ResourceException, SQLException, DataAccessException {
Interaction interaction = connection.createInteraction();
try {
return action.doInInteraction(interaction, connectionFactory);
}
finally {
closeInteraction(interaction);
}
}
});
}
public Record execute(InteractionSpec spec, Record inputRecord) throws DataAccessException {
return doExecute(spec, inputRecord, null, new SimpleRecordExtractor());
}
public void execute(InteractionSpec spec, Record inputRecord, Record outputRecord) throws DataAccessException {
doExecute(spec, inputRecord, outputRecord, null);
}
public Record execute(InteractionSpec spec, RecordCreator inputCreator) throws DataAccessException {
return doExecute(spec, createRecord(inputCreator), null, new SimpleRecordExtractor());
}
public <T> T execute(InteractionSpec spec, Record inputRecord, RecordExtractor<T> outputExtractor)
throws DataAccessException {
return doExecute(spec, inputRecord, null, outputExtractor);
}
public <T> T execute(InteractionSpec spec, RecordCreator inputCreator, RecordExtractor<T> outputExtractor)
throws DataAccessException {
return doExecute(spec, createRecord(inputCreator), null, outputExtractor);
}
/**
* Execute the specified interaction on an EIS with CCI.
* All other interaction execution methods go through this.
* @param spec the CCI InteractionSpec instance that defines
* the interaction (connector-specific)
* @param inputRecord the input record
* @param outputRecord output record (can be <code>null</code>)
* @param outputExtractor object to convert the output record to a result object
* @return the output data extracted with the RecordExtractor object
* @throws DataAccessException if there is any problem
*/
protected <T> T doExecute(
final InteractionSpec spec, final Record inputRecord, final Record outputRecord,
final RecordExtractor<T> outputExtractor) throws DataAccessException {
return execute(new InteractionCallback<T>() {
public T doInInteraction(Interaction interaction, ConnectionFactory connectionFactory)
throws ResourceException, SQLException, DataAccessException {
Record outputRecordToUse = outputRecord;
try {
if (outputRecord != null || getOutputRecordCreator() != null) {
// Use the CCI execute method with output record as parameter.
if (outputRecord == null) {
RecordFactory recordFactory = getRecordFactory(connectionFactory);
outputRecordToUse = getOutputRecordCreator().createRecord(recordFactory);
}
interaction.execute(spec, inputRecord, outputRecordToUse);
}
else {
outputRecordToUse = interaction.execute(spec, inputRecord);
}
return (outputExtractor != null ? outputExtractor.extractData(outputRecordToUse) : null);
}
finally {
if (outputRecordToUse instanceof ResultSet) {
closeResultSet((ResultSet) outputRecordToUse);
}
}
}
});
}
/**
* Create an indexed Record through the ConnectionFactory's RecordFactory.
* @param name the name of the record
* @return the Record
* @throws DataAccessException if creation of the Record failed
* @see #getRecordFactory(javax.resource.cci.ConnectionFactory)
* @see javax.resource.cci.RecordFactory#createIndexedRecord(String)
*/
public IndexedRecord createIndexedRecord(String name) throws DataAccessException {
try {
RecordFactory recordFactory = getRecordFactory(getConnectionFactory());
return recordFactory.createIndexedRecord(name);
}
catch (NotSupportedException ex) {
throw new RecordTypeNotSupportedException("Creation of indexed Record not supported by connector", ex);
}
catch (ResourceException ex) {
throw new CannotCreateRecordException("Creation of indexed Record failed", ex);
}
}
/**
* Create a mapped Record from the ConnectionFactory's RecordFactory.
* @param name record name
* @return the Record
* @throws DataAccessException if creation of the Record failed
* @see #getRecordFactory(javax.resource.cci.ConnectionFactory)
* @see javax.resource.cci.RecordFactory#createMappedRecord(String)
*/
public MappedRecord createMappedRecord(String name) throws DataAccessException {
try {
RecordFactory recordFactory = getRecordFactory(getConnectionFactory());
return recordFactory.createMappedRecord(name);
}
catch (NotSupportedException ex) {
throw new RecordTypeNotSupportedException("Creation of mapped Record not supported by connector", ex);
}
catch (ResourceException ex) {
throw new CannotCreateRecordException("Creation of mapped Record failed", ex);
}
}
/**
* Invoke the given RecordCreator, converting JCA ResourceExceptions
* to Spring's DataAccessException hierarchy.
* @param recordCreator the RecordCreator to invoke
* @return the created Record
* @throws DataAccessException if creation of the Record failed
* @see #getRecordFactory(javax.resource.cci.ConnectionFactory)
* @see RecordCreator#createRecord(javax.resource.cci.RecordFactory)
*/
protected Record createRecord(RecordCreator recordCreator) throws DataAccessException {
try {
RecordFactory recordFactory = getRecordFactory(getConnectionFactory());
return recordCreator.createRecord(recordFactory);
}
catch (NotSupportedException ex) {
throw new RecordTypeNotSupportedException(
"Creation of the desired Record type not supported by connector", ex);
}
catch (ResourceException ex) {
throw new CannotCreateRecordException("Creation of the desired Record failed", ex);
}
}
/**
* Return a RecordFactory for the given ConnectionFactory.
* <p>Default implementation returns the connector's RecordFactory if
* available, falling back to a NotSupportedRecordFactory placeholder.
* This allows to invoke a RecordCreator callback with a non-null
* RecordFactory reference in any case.
* @param connectionFactory the CCI ConnectionFactory
* @return the CCI RecordFactory for the ConnectionFactory
* @throws ResourceException if thrown by CCI methods
* @see org.springframework.jca.cci.connection.NotSupportedRecordFactory
*/
protected RecordFactory getRecordFactory(ConnectionFactory connectionFactory) throws ResourceException {
try {
return connectionFactory.getRecordFactory();
}
catch (NotSupportedException ex) {
return new NotSupportedRecordFactory();
}
}
/**
* Close the given CCI Interaction and ignore any thrown exception.
* This is useful for typical finally blocks in manual CCI code.
* @param interaction the CCI Interaction to close
* @see javax.resource.cci.Interaction#close()
*/
private void closeInteraction(Interaction interaction) {
if (interaction != null) {
try {
interaction.close();
}
catch (ResourceException ex) {
logger.trace("Could not close CCI Interaction", ex);
}
catch (Throwable ex) {
// We don't trust the CCI driver: It might throw RuntimeException or Error.
logger.trace("Unexpected exception on closing CCI Interaction", ex);
}
}
}
/**
* Close the given CCI ResultSet and ignore any thrown exception.
* This is useful for typical finally blocks in manual CCI code.
* @param resultSet the CCI ResultSet to close
* @see javax.resource.cci.ResultSet#close()
*/
private void closeResultSet(ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
}
catch (SQLException ex) {
logger.trace("Could not close CCI ResultSet", ex);
}
catch (Throwable ex) {
// We don't trust the CCI driver: It might throw RuntimeException or Error.
logger.trace("Unexpected exception on closing CCI ResultSet", ex);
}
}
}
private static class SimpleRecordExtractor implements RecordExtractor<Record> {
public Record extractData(Record record) {
return record;
}
}
}
|