Open Source Repository

Home /spring/spring-transaction-3.0.5 | Repository Home



org/springframework/jca/cci/core/CciTemplate.java
/*
 * 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> actionthrows 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> actionthrows 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 inputRecordthrows DataAccessException {
    return doExecute(spec, inputRecord, null, new SimpleRecordExtractor());
  }

  public void execute(InteractionSpec spec, Record inputRecord, Record outputRecordthrows DataAccessException {
    doExecute(spec, inputRecord, outputRecord, null);
  }

  public Record execute(InteractionSpec spec, RecordCreator inputCreatorthrows 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> outputExtractorthrows 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(outputRecordToUsenull);
        }
        finally {
          if (outputRecordToUse instanceof ResultSet) {
            closeResultSet((ResultSetoutputRecordToUse);
          }
        }
      }
    });
  }


  /**
   * 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 namethrows 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 namethrows 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 recordCreatorthrows 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 connectionFactorythrows 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;
    }
  }

}