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.broker; 018 019 020import java.util.ArrayList; 021import java.util.Iterator; 022import java.util.LinkedHashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.concurrent.ConcurrentHashMap; 026 027import javax.jms.JMSException; 028import javax.transaction.xa.XAException; 029 030import org.apache.activemq.ActiveMQMessageAudit; 031import org.apache.activemq.broker.jmx.ManagedRegionBroker; 032import org.apache.activemq.broker.region.Destination; 033import org.apache.activemq.command.ActiveMQDestination; 034import org.apache.activemq.command.BaseCommand; 035import org.apache.activemq.command.ConnectionInfo; 036import org.apache.activemq.command.LocalTransactionId; 037import org.apache.activemq.command.Message; 038import org.apache.activemq.command.MessageAck; 039import org.apache.activemq.command.ProducerInfo; 040import org.apache.activemq.command.TransactionId; 041import org.apache.activemq.command.XATransactionId; 042import org.apache.activemq.state.ProducerState; 043import org.apache.activemq.store.TransactionRecoveryListener; 044import org.apache.activemq.store.TransactionStore; 045import org.apache.activemq.transaction.LocalTransaction; 046import org.apache.activemq.transaction.Synchronization; 047import org.apache.activemq.transaction.Transaction; 048import org.apache.activemq.transaction.XATransaction; 049import org.apache.activemq.util.IOExceptionSupport; 050import org.apache.activemq.util.WrappedException; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054/** 055 * This broker filter handles the transaction related operations in the Broker 056 * interface. 057 * 058 * 059 */ 060public class TransactionBroker extends BrokerFilter { 061 062 private static final Logger LOG = LoggerFactory.getLogger(TransactionBroker.class); 063 064 // The prepared XA transactions. 065 private TransactionStore transactionStore; 066 private Map<TransactionId, XATransaction> xaTransactions = new LinkedHashMap<TransactionId, XATransaction>(); 067 private ActiveMQMessageAudit audit; 068 069 public TransactionBroker(Broker next, TransactionStore transactionStore) { 070 super(next); 071 this.transactionStore = transactionStore; 072 } 073 074 // //////////////////////////////////////////////////////////////////////////// 075 // 076 // Life cycle Methods 077 // 078 // //////////////////////////////////////////////////////////////////////////// 079 080 /** 081 * Recovers any prepared transactions. 082 */ 083 public void start() throws Exception { 084 transactionStore.start(); 085 try { 086 final ConnectionContext context = new ConnectionContext(); 087 context.setBroker(this); 088 context.setInRecoveryMode(true); 089 context.setTransactions(new ConcurrentHashMap<TransactionId, Transaction>()); 090 context.setProducerFlowControl(false); 091 final ProducerBrokerExchange producerExchange = new ProducerBrokerExchange(); 092 producerExchange.setMutable(true); 093 producerExchange.setConnectionContext(context); 094 producerExchange.setProducerState(new ProducerState(new ProducerInfo())); 095 final ConsumerBrokerExchange consumerExchange = new ConsumerBrokerExchange(); 096 consumerExchange.setConnectionContext(context); 097 transactionStore.recover(new TransactionRecoveryListener() { 098 public void recover(XATransactionId xid, Message[] addedMessages, MessageAck[] aks) { 099 try { 100 beginTransaction(context, xid); 101 XATransaction transaction = (XATransaction) getTransaction(context, xid, false); 102 for (int i = 0; i < addedMessages.length; i++) { 103 forceDestinationWakeupOnCompletion(context, transaction, addedMessages[i].getDestination(), addedMessages[i]); 104 } 105 for (int i = 0; i < aks.length; i++) { 106 forceDestinationWakeupOnCompletion(context, transaction, aks[i].getDestination(), aks[i]); 107 } 108 transaction.setState(Transaction.PREPARED_STATE); 109 registerMBean(transaction); 110 LOG.debug("recovered prepared transaction: {}", transaction.getTransactionId()); 111 } catch (Throwable e) { 112 throw new WrappedException(e); 113 } 114 } 115 }); 116 } catch (WrappedException e) { 117 Throwable cause = e.getCause(); 118 throw IOExceptionSupport.create("Recovery Failed: " + cause.getMessage(), cause); 119 } 120 next.start(); 121 } 122 123 private void registerMBean(XATransaction transaction) { 124 if (getBrokerService().getRegionBroker() instanceof ManagedRegionBroker ) { 125 ManagedRegionBroker managedRegionBroker = (ManagedRegionBroker) getBrokerService().getRegionBroker(); 126 managedRegionBroker.registerRecoveredTransactionMBean(transaction); 127 } 128 } 129 130 private void forceDestinationWakeupOnCompletion(ConnectionContext context, Transaction transaction, 131 ActiveMQDestination amqDestination, BaseCommand ack) throws Exception { 132 Destination destination = addDestination(context, amqDestination, false); 133 registerSync(destination, transaction, ack); 134 } 135 136 private void registerSync(Destination destination, Transaction transaction, BaseCommand command) { 137 Synchronization sync = new PreparedDestinationCompletion(destination, command.isMessage()); 138 // ensure one per destination in the list 139 Synchronization existing = transaction.findMatching(sync); 140 if (existing != null) { 141 ((PreparedDestinationCompletion)existing).incrementOpCount(); 142 } else { 143 transaction.addSynchronization(sync); 144 } 145 } 146 147 static class PreparedDestinationCompletion extends Synchronization { 148 final Destination destination; 149 final boolean messageSend; 150 int opCount = 1; 151 public PreparedDestinationCompletion(final Destination destination, boolean messageSend) { 152 this.destination = destination; 153 // rollback relevant to acks, commit to sends 154 this.messageSend = messageSend; 155 } 156 157 public void incrementOpCount() { 158 opCount++; 159 } 160 161 @Override 162 public int hashCode() { 163 return System.identityHashCode(destination) + 164 System.identityHashCode(Boolean.valueOf(messageSend)); 165 } 166 167 @Override 168 public boolean equals(Object other) { 169 return other instanceof PreparedDestinationCompletion && 170 destination.equals(((PreparedDestinationCompletion) other).destination) && 171 messageSend == ((PreparedDestinationCompletion) other).messageSend; 172 } 173 174 @Override 175 public void afterRollback() throws Exception { 176 if (!messageSend) { 177 destination.clearPendingMessages(); 178 LOG.debug("cleared pending from afterRollback: {}", destination); 179 } 180 } 181 182 @Override 183 public void afterCommit() throws Exception { 184 if (messageSend) { 185 destination.clearPendingMessages(); 186 destination.getDestinationStatistics().getEnqueues().add(opCount); 187 destination.getDestinationStatistics().getMessages().add(opCount); 188 LOG.debug("cleared pending from afterCommit: {}", destination); 189 } else { 190 destination.getDestinationStatistics().getDequeues().add(opCount); 191 destination.getDestinationStatistics().getMessages().subtract(opCount); 192 } 193 } 194 } 195 196 public void stop() throws Exception { 197 transactionStore.stop(); 198 next.stop(); 199 } 200 201 // //////////////////////////////////////////////////////////////////////////// 202 // 203 // BrokerFilter overrides 204 // 205 // //////////////////////////////////////////////////////////////////////////// 206 public TransactionId[] getPreparedTransactions(ConnectionContext context) throws Exception { 207 List<TransactionId> txs = new ArrayList<TransactionId>(); 208 synchronized (xaTransactions) { 209 for (Iterator<XATransaction> iter = xaTransactions.values().iterator(); iter.hasNext();) { 210 Transaction tx = iter.next(); 211 if (tx.isPrepared()) { 212 LOG.debug("prepared transaction: {}", tx.getTransactionId()); 213 txs.add(tx.getTransactionId()); 214 } 215 } 216 } 217 XATransactionId rc[] = new XATransactionId[txs.size()]; 218 txs.toArray(rc); 219 LOG.debug("prepared transaction list size: {}", rc.length); 220 return rc; 221 } 222 223 public void beginTransaction(ConnectionContext context, TransactionId xid) throws Exception { 224 // the transaction may have already been started. 225 if (xid.isXATransaction()) { 226 XATransaction transaction = null; 227 synchronized (xaTransactions) { 228 transaction = xaTransactions.get(xid); 229 if (transaction != null) { 230 return; 231 } 232 transaction = new XATransaction(transactionStore, (XATransactionId)xid, this, context.getConnectionId()); 233 xaTransactions.put(xid, transaction); 234 } 235 } else { 236 Map<TransactionId, Transaction> transactionMap = context.getTransactions(); 237 Transaction transaction = transactionMap.get(xid); 238 if (transaction != null) { 239 throw new JMSException("Transaction '" + xid + "' has already been started."); 240 } 241 transaction = new LocalTransaction(transactionStore, (LocalTransactionId)xid, context); 242 transactionMap.put(xid, transaction); 243 } 244 } 245 246 public int prepareTransaction(ConnectionContext context, TransactionId xid) throws Exception { 247 Transaction transaction = getTransaction(context, xid, false); 248 return transaction.prepare(); 249 } 250 251 public void commitTransaction(ConnectionContext context, TransactionId xid, boolean onePhase) throws Exception { 252 Transaction transaction = getTransaction(context, xid, true); 253 transaction.commit(onePhase); 254 } 255 256 public void rollbackTransaction(ConnectionContext context, TransactionId xid) throws Exception { 257 Transaction transaction = getTransaction(context, xid, true); 258 transaction.rollback(); 259 } 260 261 public void forgetTransaction(ConnectionContext context, TransactionId xid) throws Exception { 262 Transaction transaction = getTransaction(context, xid, true); 263 transaction.rollback(); 264 } 265 266 public void acknowledge(ConsumerBrokerExchange consumerExchange, MessageAck ack) throws Exception { 267 // This method may be invoked recursively. 268 // Track original tx so that it can be restored. 269 final ConnectionContext context = consumerExchange.getConnectionContext(); 270 Transaction originalTx = context.getTransaction(); 271 Transaction transaction = null; 272 if (ack.isInTransaction()) { 273 transaction = getTransaction(context, ack.getTransactionId(), false); 274 } 275 context.setTransaction(transaction); 276 try { 277 next.acknowledge(consumerExchange, ack); 278 } finally { 279 context.setTransaction(originalTx); 280 } 281 } 282 283 public void send(ProducerBrokerExchange producerExchange, final Message message) throws Exception { 284 // This method may be invoked recursively. 285 // Track original tx so that it can be restored. 286 final ConnectionContext context = producerExchange.getConnectionContext(); 287 Transaction originalTx = context.getTransaction(); 288 Transaction transaction = null; 289 Synchronization sync = null; 290 if (message.getTransactionId() != null) { 291 transaction = getTransaction(context, message.getTransactionId(), false); 292 if (transaction != null) { 293 sync = new Synchronization() { 294 295 public void afterRollback() { 296 if (audit != null) { 297 audit.rollback(message); 298 } 299 } 300 }; 301 transaction.addSynchronization(sync); 302 } 303 } 304 if (audit == null || !audit.isDuplicate(message)) { 305 context.setTransaction(transaction); 306 try { 307 next.send(producerExchange, message); 308 } finally { 309 context.setTransaction(originalTx); 310 } 311 } else { 312 if (sync != null && transaction != null) { 313 transaction.removeSynchronization(sync); 314 } 315 LOG.debug("IGNORING duplicate message {}", message); 316 } 317 } 318 319 public void removeConnection(ConnectionContext context, ConnectionInfo info, Throwable error) throws Exception { 320 for (Iterator<Transaction> iter = context.getTransactions().values().iterator(); iter.hasNext();) { 321 try { 322 Transaction transaction = iter.next(); 323 transaction.rollback(); 324 } catch (Exception e) { 325 LOG.warn("ERROR Rolling back disconnected client's transactions: ", e); 326 } 327 iter.remove(); 328 } 329 330 synchronized (xaTransactions) { 331 // first find all txs that belongs to the connection 332 ArrayList<XATransaction> txs = new ArrayList<XATransaction>(); 333 for (XATransaction tx : xaTransactions.values()) { 334 if (tx.getConnectionId() != null && tx.getConnectionId().equals(info.getConnectionId()) && !tx.isPrepared()) { 335 txs.add(tx); 336 } 337 } 338 339 // then remove them 340 // two steps needed to avoid ConcurrentModificationException, from removeTransaction() 341 for (XATransaction tx : txs) { 342 try { 343 tx.rollback(); 344 } catch (Exception e) { 345 LOG.warn("ERROR Rolling back disconnected client's xa transactions: ", e); 346 } 347 } 348 349 } 350 next.removeConnection(context, info, error); 351 } 352 353 // //////////////////////////////////////////////////////////////////////////// 354 // 355 // Implementation help methods. 356 // 357 // //////////////////////////////////////////////////////////////////////////// 358 public Transaction getTransaction(ConnectionContext context, TransactionId xid, boolean mightBePrepared) throws JMSException, XAException { 359 Map transactionMap = null; 360 synchronized (xaTransactions) { 361 transactionMap = xid.isXATransaction() ? xaTransactions : context.getTransactions(); 362 } 363 Transaction transaction = (Transaction)transactionMap.get(xid); 364 if (transaction != null) { 365 return transaction; 366 } 367 if (xid.isXATransaction()) { 368 XAException e = XATransaction.newXAException("Transaction '" + xid + "' has not been started.", XAException.XAER_NOTA); 369 throw e; 370 } else { 371 throw new JMSException("Transaction '" + xid + "' has not been started."); 372 } 373 } 374 375 public void removeTransaction(XATransactionId xid) { 376 synchronized (xaTransactions) { 377 xaTransactions.remove(xid); 378 } 379 } 380 381 public synchronized void brokerServiceStarted() { 382 super.brokerServiceStarted(); 383 if (getBrokerService().isSupportFailOver() && audit == null) { 384 audit = new ActiveMQMessageAudit(); 385 } 386 } 387 388}