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}