/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.bugs;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.jms.Message;
import javax.jms.MessageProducer;
import javax.jms.XASession;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import org.apache.activemq.ActiveMQXAConnection;
import org.apache.activemq.ActiveMQXAConnectionFactory;
import org.apache.activemq.broker.BrokerPlugin;
import org.apache.activemq.broker.BrokerPluginSupport;
import org.apache.activemq.broker.BrokerRegistry;
import org.apache.activemq.broker.BrokerRestartTestSupport;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.broker.ConnectionContext;
import org.apache.activemq.broker.TransactionBroker;
import org.apache.activemq.broker.TransportConnection;
import org.apache.activemq.command.ConnectionId;
import org.apache.activemq.command.TransactionId;
import org.apache.activemq.command.TransactionInfo;
import org.apache.activemq.command.XATransactionId;
import org.apache.activemq.transport.failover.FailoverTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Test for AMQ-4950.
* Simulates an error during XA prepare call.
*/
public class AMQ4950Test extends BrokerRestartTestSupport {
protected static final Logger LOG = LoggerFactory.getLogger(AMQ4950Test.class);
protected static final String simulatedExceptionMessage = "Simulating error inside tx prepare().";
public boolean prioritySupport = false;
protected String connectionUri = null;
@Override
protected void configureBroker(BrokerService broker) throws Exception {
broker.setDestinationPolicy(policyMap);
broker.setDeleteAllMessagesOnStartup(true);
broker.setUseJmx(false);
connectionUri = broker.addConnector("tcp://localhost:0").getPublishableConnectString();
broker.setPlugins(new BrokerPlugin[]{
new BrokerPluginSupport() {
@Override
public int prepareTransaction(ConnectionContext context, TransactionId xid) throws Exception {
getNext().prepareTransaction(context, xid);
LOG.debug("BrokerPlugin.prepareTransaction() will throw an exception.");
throw new XAException(simulatedExceptionMessage);
}
@Override
public void commitTransaction(ConnectionContext context, TransactionId xid, boolean onePhase) throws Exception {
LOG.debug("BrokerPlugin.commitTransaction().");
super.commitTransaction(context, xid, onePhase);
}
}
});
}
/**
* Creates XA transaction and invokes XA prepare().
* Due to registered BrokerFilter prepare will be handled by broker
* but then throw an exception.
* Prior to fixing AMQ-4950, this resulted in a ClassCastException
* in ConnectionStateTracker.PrepareReadonlyTransactionAction.onResponse()
* causing the failover transport to reconnect and replay the XA prepare().
*/
public void testXAPrepareFailure() throws Exception {
assertNotNull(connectionUri);
ActiveMQXAConnectionFactory cf = new ActiveMQXAConnectionFactory("failover:(" + connectionUri + ")");
ActiveMQXAConnection xaConnection = (ActiveMQXAConnection)cf.createConnection();
xaConnection.start();
XASession session = xaConnection.createXASession();
XAResource resource = session.getXAResource();
Xid tid = createXid();
resource.start(tid, XAResource.TMNOFLAGS);
MessageProducer producer = session.createProducer(session.createQueue(this.getClass().getName()));
Message message = session.createTextMessage("Sample Message");
producer.send(message);
resource.end(tid, XAResource.TMSUCCESS);
try {
LOG.debug("Calling XA prepare(), expecting an exception");
int ret = resource.prepare(tid);
if (XAResource.XA_OK == ret)
resource.commit(tid, false);
} catch (XAException xae) {
LOG.info("Received excpected XAException: {}", xae.getMessage());
LOG.info("Rolling back transaction {}", tid);
// with bug AMQ-4950 the thrown error reads "Cannot call prepare now"
// we check that we receive the original exception message as
// thrown by the BrokerPlugin
assertEquals(simulatedExceptionMessage, xae.getMessage());
resource.rollback(tid);
}
// couple of assertions
assertTransactionGoneFromBroker(tid);
assertTransactionGoneFromConnection(broker.getBrokerName(), xaConnection.getClientID(), xaConnection.getConnectionInfo().getConnectionId(), tid);
assertTransactionGoneFromFailoverState(xaConnection, tid);
//cleanup
producer.close();
session.close();
xaConnection.close();
LOG.debug("testXAPrepareFailure() finished.");
}
public Xid createXid() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream os = new DataOutputStream(baos);
os.writeLong(++txGenerator);
os.close();
final byte[] bs = baos.toByteArray();
return new Xid() {
public int getFormatId() {
return 86;
}
public byte[] getGlobalTransactionId() {
return bs;
}
public byte[] getBranchQualifier() {
return bs;
}
};
}
private void assertTransactionGoneFromFailoverState(
ActiveMQXAConnection connection1, Xid tid) throws Exception {
FailoverTransport transport = (FailoverTransport) connection1.getTransport().narrow(FailoverTransport.class);
TransactionInfo info = new TransactionInfo(connection1.getConnectionInfo().getConnectionId(), new XATransactionId(tid), TransactionInfo.COMMIT_ONE_PHASE);
assertNull("transaction should not exist in the state tracker",
transport.getStateTracker().processCommitTransactionOnePhase(info));
}
private void assertTransactionGoneFromBroker(Xid tid) throws Exception {
BrokerService broker = BrokerRegistry.getInstance().lookup("localhost");
TransactionBroker transactionBroker = (TransactionBroker)broker.getBroker().getAdaptor(TransactionBroker.class);
try {
transactionBroker.getTransaction(null, new XATransactionId(tid), false);
fail("expected exception on tx not found");
} catch (XAException expectedOnNotFound) {
}
}
private void assertTransactionGoneFromConnection(String brokerName, String clientId, ConnectionId connectionId, Xid tid) throws Exception {
BrokerService broker = BrokerRegistry.getInstance().lookup(brokerName);
CopyOnWriteArrayList<TransportConnection> connections = broker.getTransportConnectors().get(0).getConnections();
for (TransportConnection connection: connections) {
if (connection.getConnectionId().equals(clientId)) {
try {
connection.processPrepareTransaction(new TransactionInfo(connectionId, new XATransactionId(tid), TransactionInfo.PREPARE));
fail("did not get expected excepton on missing transaction, it must be still there in error!");
} catch (IllegalStateException expectedOnNoTransaction) {
}
}
}
}
}