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}