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.Connection; 021import java.sql.PreparedStatement; 022import java.sql.SQLException; 023import java.sql.Statement; 024import java.util.LinkedList; 025import java.util.List; 026 027import javax.sql.DataSource; 028 029import org.apache.activemq.util.IOExceptionSupport; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033/** 034 * Helps keep track of the current transaction/JDBC connection. 035 */ 036public class TransactionContext { 037 038 private static final Logger LOG = LoggerFactory.getLogger(TransactionContext.class); 039 040 private final DataSource dataSource; 041 private final JDBCPersistenceAdapter persistenceAdapter; 042 private Connection connection; 043 private boolean inTx; 044 private PreparedStatement addMessageStatement; 045 private PreparedStatement removedMessageStatement; 046 private PreparedStatement updateLastAckStatement; 047 // a cheap dirty level that we can live with 048 private int transactionIsolation = Connection.TRANSACTION_READ_UNCOMMITTED; 049 private LinkedList<Runnable> completions = new LinkedList<Runnable>(); 050 051 public TransactionContext(JDBCPersistenceAdapter persistenceAdapter) throws IOException { 052 this.persistenceAdapter = persistenceAdapter; 053 this.dataSource = persistenceAdapter.getDataSource(); 054 } 055 056 public Connection getConnection() throws IOException { 057 if (connection == null) { 058 try { 059 connection = dataSource.getConnection(); 060 if (persistenceAdapter.isChangeAutoCommitAllowed()) { 061 boolean autoCommit = !inTx; 062 if (connection.getAutoCommit() != autoCommit) { 063 LOG.trace("Setting auto commit to {} on connection {}", autoCommit, connection); 064 connection.setAutoCommit(autoCommit); 065 } 066 } 067 } catch (SQLException e) { 068 JDBCPersistenceAdapter.log("Could not get JDBC connection: ", e); 069 inTx = false; 070 close(); 071 IOException ioe = IOExceptionSupport.create(e); 072 persistenceAdapter.getBrokerService().handleIOException(ioe); 073 throw ioe; 074 } 075 076 try { 077 connection.setTransactionIsolation(transactionIsolation); 078 } catch (Throwable e) { 079 // ignore 080 LOG.trace("Cannot set transaction isolation to " + transactionIsolation + " due " + e.getMessage() 081 + ". This exception is ignored.", e); 082 } 083 } 084 return connection; 085 } 086 087 public void executeBatch() throws SQLException { 088 try { 089 executeBatch(addMessageStatement, "Failed add a message"); 090 } finally { 091 addMessageStatement = null; 092 try { 093 executeBatch(removedMessageStatement, "Failed to remove a message"); 094 } finally { 095 removedMessageStatement = null; 096 try { 097 executeBatch(updateLastAckStatement, "Failed to ack a message"); 098 } finally { 099 updateLastAckStatement = null; 100 } 101 } 102 } 103 } 104 105 private void executeBatch(PreparedStatement p, String message) throws SQLException { 106 if (p == null) { 107 return; 108 } 109 110 try { 111 int[] rc = p.executeBatch(); 112 for (int i = 0; i < rc.length; i++) { 113 int code = rc[i]; 114 if (code < 0 && code != Statement.SUCCESS_NO_INFO) { 115 throw new SQLException(message + ". Response code: " + code); 116 } 117 } 118 } finally { 119 try { 120 p.close(); 121 } catch (Throwable e) { 122 } 123 } 124 } 125 126 public void close() throws IOException { 127 if (!inTx) { 128 try { 129 130 /** 131 * we are not in a transaction so should not be committing ?? 132 * This was previously commented out - but had adverse affects 133 * on testing - so it's back! 134 * 135 */ 136 try { 137 executeBatch(); 138 } finally { 139 if (connection != null && !connection.getAutoCommit()) { 140 connection.commit(); 141 } 142 } 143 144 } catch (SQLException e) { 145 JDBCPersistenceAdapter.log("Error while closing connection: ", e); 146 IOException ioe = IOExceptionSupport.create(e); 147 persistenceAdapter.getBrokerService().handleIOException(ioe); 148 throw ioe; 149 } finally { 150 try { 151 if (connection != null) { 152 connection.close(); 153 } 154 } catch (Throwable e) { 155 // ignore 156 LOG.trace("Closing connection failed due: " + e.getMessage() + ". This exception is ignored.", e); 157 } finally { 158 connection = null; 159 } 160 for (Runnable completion: completions) { 161 completion.run(); 162 } 163 } 164 } 165 } 166 167 public void begin() throws IOException { 168 if (inTx) { 169 throw new IOException("Already started."); 170 } 171 inTx = true; 172 connection = getConnection(); 173 } 174 175 public void commit() throws IOException { 176 if (!inTx) { 177 throw new IOException("Not started."); 178 } 179 try { 180 executeBatch(); 181 if (!connection.getAutoCommit()) { 182 connection.commit(); 183 } 184 } catch (SQLException e) { 185 JDBCPersistenceAdapter.log("Commit failed: ", e); 186 try { 187 doRollback(); 188 } catch (Exception ignored) {} 189 IOException ioe = IOExceptionSupport.create(e); 190 persistenceAdapter.getBrokerService().handleIOException(ioe); 191 throw ioe; 192 } finally { 193 inTx = false; 194 close(); 195 } 196 } 197 198 public void rollback() throws IOException { 199 if (!inTx) { 200 throw new IOException("Not started."); 201 } 202 try { 203 doRollback(); 204 } catch (SQLException e) { 205 JDBCPersistenceAdapter.log("Rollback failed: ", e); 206 throw IOExceptionSupport.create(e); 207 } finally { 208 inTx = false; 209 close(); 210 } 211 } 212 213 private void doRollback() throws SQLException { 214 if (addMessageStatement != null) { 215 addMessageStatement.close(); 216 addMessageStatement = null; 217 } 218 if (removedMessageStatement != null) { 219 removedMessageStatement.close(); 220 removedMessageStatement = null; 221 } 222 if (updateLastAckStatement != null) { 223 updateLastAckStatement.close(); 224 updateLastAckStatement = null; 225 } 226 connection.rollback(); 227 } 228 229 public PreparedStatement getAddMessageStatement() { 230 return addMessageStatement; 231 } 232 233 public void setAddMessageStatement(PreparedStatement addMessageStatement) { 234 this.addMessageStatement = addMessageStatement; 235 } 236 237 public PreparedStatement getUpdateLastAckStatement() { 238 return updateLastAckStatement; 239 } 240 241 public void setUpdateLastAckStatement(PreparedStatement ackMessageStatement) { 242 this.updateLastAckStatement = ackMessageStatement; 243 } 244 245 public PreparedStatement getRemovedMessageStatement() { 246 return removedMessageStatement; 247 } 248 249 public void setRemovedMessageStatement(PreparedStatement removedMessageStatement) { 250 this.removedMessageStatement = removedMessageStatement; 251 } 252 253 public void setTransactionIsolation(int transactionIsolation) { 254 this.transactionIsolation = transactionIsolation; 255 } 256 257 public void onCompletion(Runnable runnable) { 258 completions.add(runnable); 259 } 260}