001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.store.jdbc; 018 019import java.io.IOException; 020import java.sql.*; 021import java.util.LinkedList; 022import java.util.Map; 023import java.util.Properties; 024import java.util.concurrent.Executor; 025import java.util.concurrent.Executors; 026import java.util.concurrent.locks.Lock; 027import java.util.concurrent.locks.ReentrantReadWriteLock; 028 029import javax.sql.DataSource; 030 031import org.apache.activemq.util.IOExceptionSupport; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035/** 036 * Helps keep track of the current transaction/JDBC connection. 037 */ 038public class TransactionContext { 039 040 private static final Logger LOG = LoggerFactory.getLogger(TransactionContext.class); 041 042 private final DataSource dataSource; 043 private final JDBCPersistenceAdapter persistenceAdapter; 044 private Connection connection; 045 private boolean inTx; 046 private PreparedStatement addMessageStatement; 047 private PreparedStatement removedMessageStatement; 048 private PreparedStatement updateLastAckStatement; 049 // a cheap dirty level that we can live with 050 private int transactionIsolation = Connection.TRANSACTION_READ_UNCOMMITTED; 051 private LinkedList<Runnable> completions = new LinkedList<Runnable>(); 052 private ReentrantReadWriteLock exclusiveConnectionLock = new ReentrantReadWriteLock(); 053 private int networkTimeout; 054 private int queryTimeout; 055 056 public TransactionContext(JDBCPersistenceAdapter persistenceAdapter, int networkTimeout, int queryTimeout) throws IOException { 057 this.persistenceAdapter = persistenceAdapter; 058 this.dataSource = persistenceAdapter.getDataSource(); 059 this.networkTimeout = networkTimeout; 060 this.queryTimeout = queryTimeout; 061 } 062 063 public Connection getExclusiveConnection() throws IOException { 064 return lockAndWrapped(exclusiveConnectionLock.writeLock()); 065 } 066 067 public Connection getConnection() throws IOException { 068 return lockAndWrapped(exclusiveConnectionLock.readLock()); 069 } 070 071 private Connection lockAndWrapped(Lock toLock) throws IOException { 072 if (connection == null) { 073 toLock.lock(); 074 try { 075 connection = dataSource.getConnection(); 076 if (networkTimeout > 0) { 077 connection.setNetworkTimeout(Executors.newSingleThreadExecutor(), networkTimeout); 078 } 079 if (persistenceAdapter.isChangeAutoCommitAllowed()) { 080 boolean autoCommit = !inTx; 081 if (connection.getAutoCommit() != autoCommit) { 082 LOG.trace("Setting auto commit to {} on connection {}", autoCommit, connection); 083 connection.setAutoCommit(autoCommit); 084 } 085 } 086 connection = new UnlockOnCloseConnection(connection, toLock); 087 } catch (SQLException e) { 088 JDBCPersistenceAdapter.log("Could not get JDBC connection: ", e); 089 inTx = false; 090 try { 091 toLock.unlock(); 092 } catch (IllegalMonitorStateException oops) { 093 LOG.error("Thread does not hold the context lock on close of:" + connection, oops); 094 } 095 silentClose(); 096 IOException ioe = IOExceptionSupport.create(e); 097 if (persistenceAdapter.getBrokerService() != null) { 098 persistenceAdapter.getBrokerService().handleIOException(ioe); 099 } 100 throw ioe; 101 } 102 103 try { 104 connection.setTransactionIsolation(transactionIsolation); 105 } catch (Throwable e) { 106 // ignore 107 LOG.trace("Cannot set transaction isolation to " + transactionIsolation + " due " + e.getMessage() 108 + ". This exception is ignored.", e); 109 } 110 } 111 return connection; 112 } 113 114 public void executeBatch() throws SQLException { 115 try { 116 executeBatch(addMessageStatement, "Failed add a message"); 117 } finally { 118 addMessageStatement = null; 119 try { 120 executeBatch(removedMessageStatement, "Failed to remove a message"); 121 } finally { 122 removedMessageStatement = null; 123 try { 124 executeBatch(updateLastAckStatement, "Failed to ack a message"); 125 } finally { 126 updateLastAckStatement = null; 127 } 128 } 129 } 130 } 131 132 private void executeBatch(PreparedStatement p, String message) throws SQLException { 133 if (p == null) { 134 return; 135 } 136 137 try { 138 int[] rc = p.executeBatch(); 139 for (int i = 0; i < rc.length; i++) { 140 int code = rc[i]; 141 if (code < 0 && code != Statement.SUCCESS_NO_INFO) { 142 throw new SQLException(message + ". Response code: " + code); 143 } 144 } 145 } finally { 146 try { 147 p.close(); 148 } catch (Throwable ignored) {} 149 } 150 } 151 152 private void silentClose() { 153 silentClosePreparedStatements(); 154 if (connection != null) { 155 try { 156 connection.close(); 157 } catch (Throwable ignored) {} 158 connection = null; 159 } 160 } 161 162 163 public void close() throws IOException { 164 if (!inTx) { 165 try { 166 // can be null for topic ops that bypass the store via existing cursor state 167 if (connection != null) { 168 final boolean needsCommit = !connection.getAutoCommit(); 169 executeBatch(); 170 if (needsCommit) { 171 connection.commit(); 172 } 173 } 174 } catch (SQLException e) { 175 JDBCPersistenceAdapter.log("Error while closing connection: ", e); 176 IOException ioe = IOExceptionSupport.create(e); 177 persistenceAdapter.getBrokerService().handleIOException(ioe); 178 throw ioe; 179 } finally { 180 silentClose(); 181 for (Runnable completion: completions) { 182 completion.run(); 183 } 184 completions.clear(); 185 } 186 } 187 } 188 189 public void begin() throws IOException { 190 if (inTx) { 191 throw new IOException("Already started."); 192 } 193 inTx = true; 194 connection = getConnection(); 195 } 196 197 public void commit() throws IOException { 198 if (!inTx) { 199 throw new IOException("Not started."); 200 } 201 try { 202 final boolean needsCommit = !connection.getAutoCommit(); 203 executeBatch(); 204 if (needsCommit) { 205 connection.commit(); 206 } 207 } catch (SQLException e) { 208 JDBCPersistenceAdapter.log("Commit failed: ", e); 209 try { 210 doRollback(); 211 } catch (Exception ignored) {} 212 IOException ioe = IOExceptionSupport.create(e); 213 persistenceAdapter.getBrokerService().handleIOException(ioe); 214 throw ioe; 215 } finally { 216 inTx = false; 217 close(); 218 } 219 } 220 221 public void rollback() throws IOException { 222 if (!inTx) { 223 throw new IOException("Not started."); 224 } 225 try { 226 doRollback(); 227 } catch (SQLException e) { 228 JDBCPersistenceAdapter.log("Rollback failed: ", e); 229 throw IOExceptionSupport.create(e); 230 } finally { 231 inTx = false; 232 close(); 233 } 234 } 235 236 private PreparedStatement silentClosePreparedStatement(PreparedStatement preparedStatement) { 237 if (preparedStatement != null) { 238 try { 239 preparedStatement.close(); 240 } catch (Throwable ignored) {} 241 } 242 return null; 243 } 244 245 private void silentClosePreparedStatements() { 246 addMessageStatement = silentClosePreparedStatement(addMessageStatement); 247 removedMessageStatement = silentClosePreparedStatement(removedMessageStatement); 248 updateLastAckStatement = silentClosePreparedStatement(updateLastAckStatement); 249 } 250 251 private void doRollback() throws SQLException { 252 silentClosePreparedStatements(); 253 completions.clear(); 254 connection.rollback(); 255 } 256 257 public PreparedStatement getAddMessageStatement() { 258 return addMessageStatement; 259 } 260 261 public void setAddMessageStatement(PreparedStatement addMessageStatement) { 262 this.addMessageStatement = addMessageStatement; 263 } 264 265 public PreparedStatement getUpdateLastAckStatement() { 266 return updateLastAckStatement; 267 } 268 269 public void setUpdateLastAckStatement(PreparedStatement ackMessageStatement) { 270 this.updateLastAckStatement = ackMessageStatement; 271 } 272 273 public PreparedStatement getRemovedMessageStatement() { 274 return removedMessageStatement; 275 } 276 277 public void setRemovedMessageStatement(PreparedStatement removedMessageStatement) { 278 this.removedMessageStatement = removedMessageStatement; 279 } 280 281 public void setTransactionIsolation(int transactionIsolation) { 282 this.transactionIsolation = transactionIsolation; 283 } 284 285 public void onCompletion(Runnable runnable) { 286 completions.add(runnable); 287 } 288 289 final private class UnlockOnCloseConnection implements Connection { 290 291 private final Connection delegate; 292 private final Lock lock; 293 294 UnlockOnCloseConnection(Connection delegate, Lock toUnlockOnClose) { 295 this.delegate = delegate; 296 this.lock = toUnlockOnClose; 297 } 298 299 @Override 300 public void close() throws SQLException { 301 try { 302 delegate.close(); 303 } finally { 304 lock.unlock(); 305 } 306 } 307 308 // simple delegate for the rest of the impl.. 309 @Override 310 public Statement createStatement() throws SQLException { 311 Statement statement = delegate.createStatement(); 312 if (queryTimeout > 0) { 313 statement.setQueryTimeout(queryTimeout); 314 } 315 return statement; 316 } 317 318 @Override 319 public PreparedStatement prepareStatement(String sql) throws SQLException { 320 PreparedStatement statement = delegate.prepareStatement(sql); 321 if (queryTimeout > 0) { 322 statement.setQueryTimeout(queryTimeout); 323 } 324 return statement; 325 } 326 327 @Override 328 public CallableStatement prepareCall(String sql) throws SQLException { 329 CallableStatement statement = delegate.prepareCall(sql); 330 if (queryTimeout > 0) { 331 statement.setQueryTimeout(queryTimeout); 332 } 333 return statement; 334 } 335 336 @Override 337 public String nativeSQL(String sql) throws SQLException { 338 return delegate.nativeSQL(sql); 339 } 340 341 @Override 342 public void setAutoCommit(boolean autoCommit) throws SQLException { 343 delegate.setAutoCommit(autoCommit); 344 } 345 346 @Override 347 public boolean getAutoCommit() throws SQLException { 348 return delegate.getAutoCommit(); 349 } 350 351 @Override 352 public void commit() throws SQLException { 353 delegate.commit(); 354 } 355 356 @Override 357 public void rollback() throws SQLException { 358 delegate.rollback(); 359 } 360 361 @Override 362 public boolean isClosed() throws SQLException { 363 return delegate.isClosed(); 364 } 365 366 @Override 367 public DatabaseMetaData getMetaData() throws SQLException { 368 return delegate.getMetaData(); 369 } 370 371 @Override 372 public void setReadOnly(boolean readOnly) throws SQLException { 373 delegate.setReadOnly(readOnly); 374 } 375 376 @Override 377 public boolean isReadOnly() throws SQLException { 378 return delegate.isReadOnly(); 379 } 380 381 @Override 382 public void setCatalog(String catalog) throws SQLException { 383 delegate.setCatalog(catalog); 384 } 385 386 @Override 387 public String getCatalog() throws SQLException { 388 return delegate.getCatalog(); 389 } 390 391 @Override 392 public void setTransactionIsolation(int level) throws SQLException { 393 delegate.setTransactionIsolation(level); 394 } 395 396 @Override 397 public int getTransactionIsolation() throws SQLException { 398 return delegate.getTransactionIsolation(); 399 } 400 401 @Override 402 public SQLWarning getWarnings() throws SQLException { 403 return delegate.getWarnings(); 404 } 405 406 @Override 407 public void clearWarnings() throws SQLException { 408 delegate.clearWarnings(); 409 } 410 411 @Override 412 public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { 413 Statement statement = delegate.createStatement(resultSetType, resultSetConcurrency); 414 if (queryTimeout > 0) { 415 statement.setQueryTimeout(queryTimeout); 416 } 417 return statement; 418 } 419 420 @Override 421 public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { 422 PreparedStatement statement = delegate.prepareStatement(sql, resultSetType, resultSetConcurrency); 423 if (queryTimeout > 0) { 424 statement.setQueryTimeout(queryTimeout); 425 } 426 return statement; 427 } 428 429 @Override 430 public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { 431 CallableStatement statement = delegate.prepareCall(sql, resultSetType, resultSetConcurrency); 432 if (queryTimeout > 0) { 433 statement.setQueryTimeout(queryTimeout); 434 } 435 return statement; 436 } 437 438 @Override 439 public Map<String, Class<?>> getTypeMap() throws SQLException { 440 return delegate.getTypeMap(); 441 } 442 443 @Override 444 public void setTypeMap(Map<String, Class<?>> map) throws SQLException { 445 delegate.setTypeMap(map); 446 } 447 448 @Override 449 public void setHoldability(int holdability) throws SQLException { 450 delegate.setHoldability(holdability); 451 } 452 453 @Override 454 public int getHoldability() throws SQLException { 455 return delegate.getHoldability(); 456 } 457 458 @Override 459 public Savepoint setSavepoint() throws SQLException { 460 return delegate.setSavepoint(); 461 } 462 463 @Override 464 public Savepoint setSavepoint(String name) throws SQLException { 465 return delegate.setSavepoint(name); 466 } 467 468 @Override 469 public void rollback(Savepoint savepoint) throws SQLException { 470 delegate.rollback(savepoint); 471 } 472 473 @Override 474 public void releaseSavepoint(Savepoint savepoint) throws SQLException { 475 delegate.releaseSavepoint(savepoint); 476 } 477 478 @Override 479 public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { 480 Statement statement = delegate.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); 481 if (queryTimeout > 0) { 482 statement.setQueryTimeout(queryTimeout); 483 } 484 return statement; 485 } 486 487 @Override 488 public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { 489 PreparedStatement statement = delegate.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability); 490 if (queryTimeout > 0) { 491 statement.setQueryTimeout(queryTimeout); 492 } 493 return statement; 494 } 495 496 @Override 497 public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { 498 CallableStatement statement = delegate.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); 499 if (queryTimeout > 0) { 500 statement.setQueryTimeout(queryTimeout); 501 } 502 return statement; 503 } 504 505 @Override 506 public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { 507 PreparedStatement statement = delegate.prepareStatement(sql, autoGeneratedKeys); 508 if (queryTimeout > 0) { 509 statement.setQueryTimeout(queryTimeout); 510 } 511 return statement; 512 } 513 514 @Override 515 public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { 516 PreparedStatement statement = delegate.prepareStatement(sql, columnIndexes); 517 if (queryTimeout > 0) { 518 statement.setQueryTimeout(queryTimeout); 519 } 520 return statement; 521 } 522 523 @Override 524 public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { 525 PreparedStatement statement = delegate.prepareStatement(sql, columnNames); 526 if (queryTimeout > 0) { 527 statement.setQueryTimeout(queryTimeout); 528 } 529 return statement; 530 } 531 532 @Override 533 public Clob createClob() throws SQLException { 534 return delegate.createClob(); 535 } 536 537 @Override 538 public Blob createBlob() throws SQLException { 539 return delegate.createBlob(); 540 } 541 542 @Override 543 public NClob createNClob() throws SQLException { 544 return delegate.createNClob(); 545 } 546 547 @Override 548 public SQLXML createSQLXML() throws SQLException { 549 return delegate.createSQLXML(); 550 } 551 552 @Override 553 public boolean isValid(int timeout) throws SQLException { 554 return delegate.isValid(timeout); 555 } 556 557 @Override 558 public void setClientInfo(String name, String value) throws SQLClientInfoException { 559 delegate.setClientInfo(name, value); 560 } 561 562 @Override 563 public void setClientInfo(Properties properties) throws SQLClientInfoException { 564 delegate.setClientInfo(properties); 565 } 566 567 @Override 568 public String getClientInfo(String name) throws SQLException { 569 return delegate.getClientInfo(name); 570 } 571 572 @Override 573 public Properties getClientInfo() throws SQLException { 574 return delegate.getClientInfo(); 575 } 576 577 @Override 578 public Array createArrayOf(String typeName, Object[] elements) throws SQLException { 579 return delegate.createArrayOf(typeName, elements); 580 } 581 582 @Override 583 public Struct createStruct(String typeName, Object[] attributes) throws SQLException { 584 return delegate.createStruct(typeName, attributes); 585 } 586 587 @Override 588 public void setSchema(String schema) throws SQLException { 589 delegate.setSchema(schema); 590 } 591 592 @Override 593 public String getSchema() throws SQLException { 594 return delegate.getSchema(); 595 } 596 597 @Override 598 public void abort(Executor executor) throws SQLException { 599 delegate.abort(executor); 600 } 601 602 @Override 603 public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { 604 delegate.setNetworkTimeout(executor, milliseconds); 605 } 606 607 @Override 608 public int getNetworkTimeout() throws SQLException { 609 return delegate.getNetworkTimeout(); 610 } 611 612 @Override 613 public <T> T unwrap(Class<T> iface) throws SQLException { 614 return delegate.unwrap(iface); 615 } 616 617 @Override 618 public boolean isWrapperFor(Class<?> iface) throws SQLException { 619 return delegate.isWrapperFor(iface); 620 } 621 } 622}