mirror of https://github.com/apache/activemq.git
https://issues.apache.org/jira/browse/AMQ-3872 - Implement "exactly once" delivery with JDBC and XA in the event of a failure post prepare. jdbc xa is not a runner due to single table and update locks. implemention adds xid column to messages and acks table. a non null value indicated a prepared add/ack, so the insert is done on prepare. the result of the transaction outcome requires a row level update
git-svn-id: https://svn.apache.org/repos/asf/activemq/trunk@1345202 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
15f85cd768
commit
64f3492c85
|
@ -25,6 +25,7 @@ import org.apache.activemq.command.ConnectionId;
|
||||||
import org.apache.activemq.command.ConnectionInfo;
|
import org.apache.activemq.command.ConnectionInfo;
|
||||||
import org.apache.activemq.command.TransactionId;
|
import org.apache.activemq.command.TransactionId;
|
||||||
import org.apache.activemq.command.WireFormatInfo;
|
import org.apache.activemq.command.WireFormatInfo;
|
||||||
|
import org.apache.activemq.command.XATransactionId;
|
||||||
import org.apache.activemq.filter.MessageEvaluationContext;
|
import org.apache.activemq.filter.MessageEvaluationContext;
|
||||||
import org.apache.activemq.security.MessageAuthorizationPolicy;
|
import org.apache.activemq.security.MessageAuthorizationPolicy;
|
||||||
import org.apache.activemq.security.SecurityContext;
|
import org.apache.activemq.security.SecurityContext;
|
||||||
|
@ -60,6 +61,7 @@ public class ConnectionContext {
|
||||||
private boolean dontSendReponse;
|
private boolean dontSendReponse;
|
||||||
private boolean clientMaster = true;
|
private boolean clientMaster = true;
|
||||||
private ConnectionState connectionState;
|
private ConnectionState connectionState;
|
||||||
|
private XATransactionId xid;
|
||||||
|
|
||||||
public ConnectionContext() {
|
public ConnectionContext() {
|
||||||
this.messageEvaluationContext = new MessageEvaluationContext();
|
this.messageEvaluationContext = new MessageEvaluationContext();
|
||||||
|
@ -329,4 +331,12 @@ public class ConnectionContext {
|
||||||
public ConnectionState getConnectionState() {
|
public ConnectionState getConnectionState() {
|
||||||
return this.connectionState;
|
return this.connectionState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setXid(XATransactionId id) {
|
||||||
|
this.xid = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public XATransactionId getXid() {
|
||||||
|
return xid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,42 +137,40 @@ public class TransactionBroker extends BrokerFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerSync(Destination destination, Transaction transaction, BaseCommand command) {
|
private void registerSync(Destination destination, Transaction transaction, BaseCommand command) {
|
||||||
if (destination instanceof Queue) {
|
Synchronization sync = new PreparedDestinationCompletion(destination, command.isMessage());
|
||||||
Synchronization sync = new PreparedDestinationCompletion((Queue) destination, command.isMessage());
|
// ensure one per destination in the list
|
||||||
// ensure one per destination in the list
|
transaction.removeSynchronization(sync);
|
||||||
transaction.removeSynchronization(sync);
|
transaction.addSynchronization(sync);
|
||||||
transaction.addSynchronization(sync);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class PreparedDestinationCompletion extends Synchronization {
|
static class PreparedDestinationCompletion extends Synchronization {
|
||||||
final Queue queue;
|
final Destination destination;
|
||||||
final boolean messageSend;
|
final boolean messageSend;
|
||||||
public PreparedDestinationCompletion(final Queue queue, boolean messageSend) {
|
public PreparedDestinationCompletion(final Destination destination, boolean messageSend) {
|
||||||
this.queue = queue;
|
this.destination = destination;
|
||||||
// rollback relevant to acks, commit to sends
|
// rollback relevant to acks, commit to sends
|
||||||
this.messageSend = messageSend;
|
this.messageSend = messageSend;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return System.identityHashCode(queue) +
|
return System.identityHashCode(destination) +
|
||||||
System.identityHashCode(Boolean.valueOf(messageSend));
|
System.identityHashCode(Boolean.valueOf(messageSend));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
return other instanceof PreparedDestinationCompletion &&
|
return other instanceof PreparedDestinationCompletion &&
|
||||||
queue.equals(((PreparedDestinationCompletion) other).queue) &&
|
destination.equals(((PreparedDestinationCompletion) other).destination) &&
|
||||||
messageSend == ((PreparedDestinationCompletion) other).messageSend;
|
messageSend == ((PreparedDestinationCompletion) other).messageSend;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterRollback() throws Exception {
|
public void afterRollback() throws Exception {
|
||||||
if (!messageSend) {
|
if (!messageSend) {
|
||||||
queue.clearPendingMessages();
|
destination.clearPendingMessages();
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
LOG.debug("cleared pending from afterRollback : " + queue);
|
LOG.debug("cleared pending from afterRollback : " + destination);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,9 +178,9 @@ public class TransactionBroker extends BrokerFilter {
|
||||||
@Override
|
@Override
|
||||||
public void afterCommit() throws Exception {
|
public void afterCommit() throws Exception {
|
||||||
if (messageSend) {
|
if (messageSend) {
|
||||||
queue.clearPendingMessages();
|
destination.clearPendingMessages();
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
LOG.debug("cleared pending from afterCommit : " + queue);
|
LOG.debug("cleared pending from afterCommit : " + destination);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -233,4 +233,6 @@ public interface Destination extends Service, Task {
|
||||||
|
|
||||||
boolean isDoOptimzeMessageStorage();
|
boolean isDoOptimzeMessageStorage();
|
||||||
void setDoOptimzeMessageStorage(boolean doOptimzeMessageStorage);
|
void setDoOptimzeMessageStorage(boolean doOptimzeMessageStorage);
|
||||||
|
|
||||||
|
public void clearPendingMessages();
|
||||||
}
|
}
|
||||||
|
|
|
@ -310,4 +310,9 @@ public class DestinationFilter implements Destination {
|
||||||
next.setDoOptimzeMessageStorage(doOptimzeMessageStorage);
|
next.setDoOptimzeMessageStorage(doOptimzeMessageStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearPendingMessages() {
|
||||||
|
next.clearPendingMessages();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,10 @@ package org.apache.activemq.broker.region;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
@ -745,4 +747,47 @@ public class Topic extends BaseDestination implements Task {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* force a reread of the store - after transaction recovery completion
|
||||||
|
*/
|
||||||
|
public void clearPendingMessages() {
|
||||||
|
dispatchLock.readLock().lock();
|
||||||
|
try {
|
||||||
|
for (DurableTopicSubscription durableTopicSubscription : durableSubcribers.values()) {
|
||||||
|
clearPendingAndDispatch(durableTopicSubscription);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
dispatchLock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void clearPendingMessages(SubscriptionKey subscriptionKey) {
|
||||||
|
dispatchLock.readLock().lock();
|
||||||
|
try {
|
||||||
|
DurableTopicSubscription durableTopicSubscription = durableSubcribers.get(subscriptionKey);
|
||||||
|
clearPendingAndDispatch(durableTopicSubscription);
|
||||||
|
} finally {
|
||||||
|
dispatchLock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearPendingAndDispatch(DurableTopicSubscription durableTopicSubscription) {
|
||||||
|
synchronized (durableTopicSubscription.pendingLock) {
|
||||||
|
durableTopicSubscription.pending.clear();
|
||||||
|
try {
|
||||||
|
durableTopicSubscription.dispatchPending();
|
||||||
|
} catch (IOException exception) {
|
||||||
|
LOG.warn("After clear of pending, failed to dispatch to: " +
|
||||||
|
durableTopicSubscription + ", for :" + destination + ", pending: " +
|
||||||
|
durableTopicSubscription.pending, exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<SubscriptionKey, DurableTopicSubscription> getDurableTopicSubs() {
|
||||||
|
return durableSubcribers;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,7 +152,7 @@ public class MessageId implements DataStructure, Comparable<MessageId> {
|
||||||
MessageId copy = new MessageId(producerId, producerSequenceId);
|
MessageId copy = new MessageId(producerId, producerSequenceId);
|
||||||
copy.key = key;
|
copy.key = key;
|
||||||
copy.brokerSequenceId = brokerSequenceId;
|
copy.brokerSequenceId = brokerSequenceId;
|
||||||
copy.dataLocator = dataLocator;
|
copy.dataLocator = new AtomicReference<Object>(dataLocator.get());
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,10 +16,12 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.activemq.command;
|
package org.apache.activemq.command;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import javax.transaction.xa.Xid;
|
import javax.transaction.xa.Xid;
|
||||||
import org.apache.activemq.util.HexSupport;
|
import org.apache.activemq.util.DataByteArrayInputStream;
|
||||||
|
import org.apache.activemq.util.DataByteArrayOutputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @openwire:marshaller code="112"
|
* @openwire:marshaller code="112"
|
||||||
|
@ -32,6 +34,8 @@ public class XATransactionId extends TransactionId implements Xid, Comparable {
|
||||||
private int formatId;
|
private int formatId;
|
||||||
private byte[] branchQualifier;
|
private byte[] branchQualifier;
|
||||||
private byte[] globalTransactionId;
|
private byte[] globalTransactionId;
|
||||||
|
private transient DataByteArrayOutputStream outputStream;
|
||||||
|
private transient byte[] encodedXidBytes;
|
||||||
|
|
||||||
private transient int hash;
|
private transient int hash;
|
||||||
private transient String transactionKey;
|
private transient String transactionKey;
|
||||||
|
@ -46,14 +50,58 @@ public class XATransactionId extends TransactionId implements Xid, Comparable {
|
||||||
this.branchQualifier = xid.getBranchQualifier();
|
this.branchQualifier = xid.getBranchQualifier();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public XATransactionId(byte[] encodedBytes) {
|
||||||
|
encodedXidBytes = encodedBytes;
|
||||||
|
initFromEncodedBytes();
|
||||||
|
}
|
||||||
|
|
||||||
public byte getDataStructureType() {
|
public byte getDataStructureType() {
|
||||||
return DATA_STRUCTURE_TYPE;
|
return DATA_STRUCTURE_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final int XID_PREFIX_SIZE = 16;
|
||||||
|
//+|-,(long)lastAck,(byte)priority,(int)formatid,(short)globalLength....
|
||||||
|
private void initFromEncodedBytes() {
|
||||||
|
DataByteArrayInputStream inputStream = new DataByteArrayInputStream(encodedXidBytes);
|
||||||
|
inputStream.skipBytes(10);
|
||||||
|
formatId = inputStream.readInt();
|
||||||
|
int globalLength = inputStream.readShort();
|
||||||
|
globalTransactionId = new byte[globalLength];
|
||||||
|
try {
|
||||||
|
inputStream.read(globalTransactionId);
|
||||||
|
branchQualifier = new byte[inputStream.available()];
|
||||||
|
inputStream.read(branchQualifier);
|
||||||
|
} catch (IOException fatal) {
|
||||||
|
throw new RuntimeException(this + ", failed to decode:", fatal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized byte[] getEncodedXidBytes() {
|
||||||
|
if (encodedXidBytes == null) {
|
||||||
|
outputStream = new DataByteArrayOutputStream(XID_PREFIX_SIZE + globalTransactionId.length + branchQualifier.length);
|
||||||
|
outputStream.position(10);
|
||||||
|
outputStream.writeInt(formatId);
|
||||||
|
// global length
|
||||||
|
outputStream.writeShort(globalTransactionId.length);
|
||||||
|
try {
|
||||||
|
outputStream.write(globalTransactionId);
|
||||||
|
outputStream.write(branchQualifier);
|
||||||
|
} catch (IOException fatal) {
|
||||||
|
throw new RuntimeException(this + ", failed to encode:", fatal);
|
||||||
|
}
|
||||||
|
encodedXidBytes = outputStream.getData();
|
||||||
|
}
|
||||||
|
return encodedXidBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataByteArrayOutputStream getOutputStream() {
|
||||||
|
return outputStream;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized String getTransactionKey() {
|
public synchronized String getTransactionKey() {
|
||||||
if (transactionKey == null) {
|
if (transactionKey == null) {
|
||||||
StringBuffer s = new StringBuffer();
|
StringBuffer s = new StringBuffer();
|
||||||
s.append("XID:[globalId=");
|
s.append("XID:[" + formatId + ",globalId=");
|
||||||
for (int i = 0; i < globalTransactionId.length; i++) {
|
for (int i = 0; i < globalTransactionId.length; i++) {
|
||||||
s.append(Integer.toHexString(globalTransactionId[i]));
|
s.append(Integer.toHexString(globalTransactionId[i]));
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ public interface TopicMessageStore extends MessageStore {
|
||||||
void recoverNextMessages(String clientId, String subscriptionName, int maxReturned, MessageRecoveryListener listener) throws Exception;
|
void recoverNextMessages(String clientId, String subscriptionName, int maxReturned, MessageRecoveryListener listener) throws Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A hint to the Store to reset any batching state for a durable subsriber
|
* A hint to the Store to reset any batching state for a durable subscriber
|
||||||
*
|
*
|
||||||
* @param clientId
|
* @param clientId
|
||||||
* @param subscriptionName
|
* @param subscriptionName
|
||||||
|
|
|
@ -20,9 +20,11 @@ import java.io.IOException;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.apache.activemq.command.ActiveMQDestination;
|
import org.apache.activemq.command.ActiveMQDestination;
|
||||||
|
import org.apache.activemq.command.ActiveMQTopic;
|
||||||
import org.apache.activemq.command.MessageId;
|
import org.apache.activemq.command.MessageId;
|
||||||
import org.apache.activemq.command.ProducerId;
|
import org.apache.activemq.command.ProducerId;
|
||||||
import org.apache.activemq.command.SubscriptionInfo;
|
import org.apache.activemq.command.SubscriptionInfo;
|
||||||
|
import org.apache.activemq.command.XATransactionId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -35,7 +37,7 @@ public interface JDBCAdapter {
|
||||||
|
|
||||||
void doDropTables(TransactionContext c) throws SQLException, IOException;
|
void doDropTables(TransactionContext c) throws SQLException, IOException;
|
||||||
|
|
||||||
void doAddMessage(TransactionContext c, long sequence, MessageId messageID, ActiveMQDestination destination, byte[] data, long expiration, byte priority) throws SQLException, IOException;
|
void doAddMessage(TransactionContext c, long sequence, MessageId messageID, ActiveMQDestination destination, byte[] data, long expiration, byte priority, XATransactionId xid) throws SQLException, IOException;
|
||||||
|
|
||||||
void doAddMessageReference(TransactionContext c, long sequence, MessageId messageId, ActiveMQDestination destination, long expirationTime, String messageRef) throws SQLException, IOException;
|
void doAddMessageReference(TransactionContext c, long sequence, MessageId messageId, ActiveMQDestination destination, long expirationTime, String messageRef) throws SQLException, IOException;
|
||||||
|
|
||||||
|
@ -45,11 +47,11 @@ public interface JDBCAdapter {
|
||||||
|
|
||||||
String doGetMessageReference(TransactionContext c, long id) throws SQLException, IOException;
|
String doGetMessageReference(TransactionContext c, long id) throws SQLException, IOException;
|
||||||
|
|
||||||
void doRemoveMessage(TransactionContext c, long seq) throws SQLException, IOException;
|
void doRemoveMessage(TransactionContext c, long seq, XATransactionId xid) throws SQLException, IOException;
|
||||||
|
|
||||||
void doRecover(TransactionContext c, ActiveMQDestination destination, JDBCMessageRecoveryListener listener) throws Exception;
|
void doRecover(TransactionContext c, ActiveMQDestination destination, JDBCMessageRecoveryListener listener) throws Exception;
|
||||||
|
|
||||||
void doSetLastAck(TransactionContext c, ActiveMQDestination destination, String clientId, String subscriptionName, long seq, long prio) throws SQLException, IOException;
|
void doSetLastAck(TransactionContext c, ActiveMQDestination destination, XATransactionId xid, String clientId, String subscriptionName, long seq, long prio) throws SQLException, IOException;
|
||||||
|
|
||||||
void doRecoverSubscription(TransactionContext c, ActiveMQDestination destination, String clientId, String subscriptionName, JDBCMessageRecoveryListener listener)
|
void doRecoverSubscription(TransactionContext c, ActiveMQDestination destination, String clientId, String subscriptionName, JDBCMessageRecoveryListener listener)
|
||||||
throws Exception;
|
throws Exception;
|
||||||
|
@ -92,11 +94,17 @@ public interface JDBCAdapter {
|
||||||
|
|
||||||
long doGetLastProducerSequenceId(TransactionContext c, ProducerId id) throws SQLException, IOException;
|
long doGetLastProducerSequenceId(TransactionContext c, ProducerId id) throws SQLException, IOException;
|
||||||
|
|
||||||
void doSetLastAckWithPriority(TransactionContext c, ActiveMQDestination destination, String clientId, String subscriptionName, long re, long re1) throws SQLException, IOException;
|
void doSetLastAckWithPriority(TransactionContext c, ActiveMQDestination destination, XATransactionId xid, String clientId, String subscriptionName, long re, long re1) throws SQLException, IOException;
|
||||||
|
|
||||||
public int getMaxRows();
|
public int getMaxRows();
|
||||||
|
|
||||||
public void setMaxRows(int maxRows);
|
public void setMaxRows(int maxRows);
|
||||||
|
|
||||||
void doRecordDestination(TransactionContext c, ActiveMQDestination destination) throws SQLException, IOException;
|
void doRecordDestination(TransactionContext c, ActiveMQDestination destination) throws SQLException, IOException;
|
||||||
|
|
||||||
|
void doRecoverPreparedOps(TransactionContext c, JdbcMemoryTransactionStore jdbcMemoryTransactionStore) throws SQLException, IOException;
|
||||||
|
|
||||||
|
void doCommitAddOp(TransactionContext c, long storeSequenceIdForMessageId) throws SQLException, IOException;
|
||||||
|
|
||||||
|
void doClearLastAck(TransactionContext c, ActiveMQDestination destination, byte priority, String subId, String subName) throws SQLException, IOException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,15 +120,19 @@ public class JDBCMessageStore extends AbstractMessageStore {
|
||||||
// Get a connection and insert the message into the DB.
|
// Get a connection and insert the message into the DB.
|
||||||
TransactionContext c = persistenceAdapter.getTransactionContext(context);
|
TransactionContext c = persistenceAdapter.getTransactionContext(context);
|
||||||
try {
|
try {
|
||||||
adapter.doAddMessage(c,sequenceId, messageId, destination, data, message.getExpiration(),
|
adapter.doAddMessage(c, sequenceId, messageId, destination, data, message.getExpiration(),
|
||||||
this.isPrioritizedMessages() ? message.getPriority() : 0);
|
this.isPrioritizedMessages() ? message.getPriority() : 0, context != null ? context.getXid() : null);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
JDBCPersistenceAdapter.log("JDBC Failure: ", e);
|
JDBCPersistenceAdapter.log("JDBC Failure: ", e);
|
||||||
throw IOExceptionSupport.create("Failed to broker message: " + messageId + " in container: " + e, e);
|
throw IOExceptionSupport.create("Failed to broker message: " + messageId + " in container: " + e, e);
|
||||||
} finally {
|
} finally {
|
||||||
c.close();
|
c.close();
|
||||||
}
|
}
|
||||||
onAdd(messageId, sequenceId, message.getPriority());
|
if (context != null && context.getXid() != null) {
|
||||||
|
message.getMessageId().setDataLocator(sequenceId);
|
||||||
|
} else {
|
||||||
|
onAdd(messageId, sequenceId, message.getPriority());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onAdd(MessageId messageId, long sequenceId, byte priority) {
|
protected void onAdd(MessageId messageId, long sequenceId, byte priority) {
|
||||||
|
@ -186,19 +190,22 @@ public class JDBCMessageStore extends AbstractMessageStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeMessage(ConnectionContext context, MessageAck ack) throws IOException {
|
public void removeMessage(ConnectionContext context, MessageAck ack) throws IOException {
|
||||||
|
|
||||||
long seq = getStoreSequenceIdForMessageId(ack.getLastMessageId())[0];
|
long seq = persistenceAdapter.getStoreSequenceIdForMessageId(ack.getLastMessageId(), destination)[0];
|
||||||
|
|
||||||
// Get a connection and remove the message from the DB
|
// Get a connection and remove the message from the DB
|
||||||
TransactionContext c = persistenceAdapter.getTransactionContext(context);
|
TransactionContext c = persistenceAdapter.getTransactionContext(context);
|
||||||
try {
|
try {
|
||||||
adapter.doRemoveMessage(c, seq);
|
adapter.doRemoveMessage(c, seq, context != null ? context.getXid() : null);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
JDBCPersistenceAdapter.log("JDBC Failure: ", e);
|
JDBCPersistenceAdapter.log("JDBC Failure: ", e);
|
||||||
throw IOExceptionSupport.create("Failed to broker message: " + ack.getLastMessageId() + " in container: " + e, e);
|
throw IOExceptionSupport.create("Failed to broker message: " + ack.getLastMessageId() + " in container: " + e, e);
|
||||||
} finally {
|
} finally {
|
||||||
c.close();
|
c.close();
|
||||||
}
|
}
|
||||||
|
if (context != null && context.getXid() != null) {
|
||||||
|
ack.getLastMessageId().setDataLocator(seq);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void recover(final MessageRecoveryListener listener) throws Exception {
|
public void recover(final MessageRecoveryListener listener) throws Exception {
|
||||||
|
@ -315,7 +322,7 @@ public class JDBCMessageStore extends AbstractMessageStore {
|
||||||
@Override
|
@Override
|
||||||
public void setBatch(MessageId messageId) {
|
public void setBatch(MessageId messageId) {
|
||||||
try {
|
try {
|
||||||
long[] storedValues = getStoreSequenceIdForMessageId(messageId);
|
long[] storedValues = persistenceAdapter.getStoreSequenceIdForMessageId(messageId, destination);
|
||||||
lastRecoveredSequenceId.set(storedValues[0]);
|
lastRecoveredSequenceId.set(storedValues[0]);
|
||||||
lastRecoveredPriority.set(storedValues[1]);
|
lastRecoveredPriority.set(storedValues[1]);
|
||||||
} catch (IOException ignoredAsAlreadyLogged) {
|
} catch (IOException ignoredAsAlreadyLogged) {
|
||||||
|
@ -328,20 +335,7 @@ public class JDBCMessageStore extends AbstractMessageStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private long[] getStoreSequenceIdForMessageId(MessageId messageId) throws IOException {
|
|
||||||
long[] result = new long[]{-1, Byte.MAX_VALUE -1};
|
|
||||||
TransactionContext c = persistenceAdapter.getTransactionContext();
|
|
||||||
try {
|
|
||||||
result = adapter.getStoreSequenceId(c, destination, messageId);
|
|
||||||
} catch (SQLException e) {
|
|
||||||
JDBCPersistenceAdapter.log("JDBC Failure: ", e);
|
|
||||||
throw IOExceptionSupport.create("Failed to get store sequenceId for messageId: " + messageId +", on: " + destination + ". Reason: " + e, e);
|
|
||||||
} finally {
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrioritizedMessages(boolean prioritizedMessages) {
|
public void setPrioritizedMessages(boolean prioritizedMessages) {
|
||||||
super.setPrioritizedMessages(prioritizedMessages);
|
super.setPrioritizedMessages(prioritizedMessages);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ import org.apache.activemq.command.ActiveMQDestination;
|
||||||
import org.apache.activemq.command.ActiveMQQueue;
|
import org.apache.activemq.command.ActiveMQQueue;
|
||||||
import org.apache.activemq.command.ActiveMQTopic;
|
import org.apache.activemq.command.ActiveMQTopic;
|
||||||
import org.apache.activemq.command.Message;
|
import org.apache.activemq.command.Message;
|
||||||
|
import org.apache.activemq.command.MessageAck;
|
||||||
import org.apache.activemq.command.MessageId;
|
import org.apache.activemq.command.MessageId;
|
||||||
import org.apache.activemq.command.ProducerId;
|
import org.apache.activemq.command.ProducerId;
|
||||||
import org.apache.activemq.openwire.OpenWireFormat;
|
import org.apache.activemq.openwire.OpenWireFormat;
|
||||||
|
@ -237,7 +238,7 @@ public class JDBCPersistenceAdapter extends DataSourceSupport implements Persist
|
||||||
|
|
||||||
public TransactionStore createTransactionStore() throws IOException {
|
public TransactionStore createTransactionStore() throws IOException {
|
||||||
if (transactionStore == null) {
|
if (transactionStore == null) {
|
||||||
transactionStore = new MemoryTransactionStore(this);
|
transactionStore = new JdbcMemoryTransactionStore(this);
|
||||||
}
|
}
|
||||||
return this.transactionStore;
|
return this.transactionStore;
|
||||||
}
|
}
|
||||||
|
@ -768,4 +769,95 @@ public class JDBCPersistenceAdapter extends DataSourceSupport implements Persist
|
||||||
public void setMaxRows(int maxRows) {
|
public void setMaxRows(int maxRows) {
|
||||||
this.maxRows = maxRows;
|
this.maxRows = maxRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void recover(JdbcMemoryTransactionStore jdbcMemoryTransactionStore) throws IOException {
|
||||||
|
TransactionContext c = getTransactionContext();
|
||||||
|
try {
|
||||||
|
getAdapter().doRecoverPreparedOps(c, jdbcMemoryTransactionStore);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
JDBCPersistenceAdapter.log("JDBC Failure: ", e);
|
||||||
|
throw IOExceptionSupport.create("Failed to recover from: " + jdbcMemoryTransactionStore + ". Reason: " + e,e);
|
||||||
|
} finally {
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void commitAdd(ConnectionContext context, MessageId messageId) throws IOException {
|
||||||
|
TransactionContext c = getTransactionContext(context);
|
||||||
|
try {
|
||||||
|
long sequence = (Long)messageId.getDataLocator();
|
||||||
|
getAdapter().doCommitAddOp(c, sequence);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
JDBCPersistenceAdapter.log("JDBC Failure: ", e);
|
||||||
|
throw IOExceptionSupport.create("Failed to commit add: " + messageId + ". Reason: " + e, e);
|
||||||
|
} finally {
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void commitRemove(ConnectionContext context, MessageAck ack) throws IOException {
|
||||||
|
TransactionContext c = getTransactionContext(context);
|
||||||
|
try {
|
||||||
|
getAdapter().doRemoveMessage(c, (Long)ack.getLastMessageId().getDataLocator(), null);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
JDBCPersistenceAdapter.log("JDBC Failure: ", e);
|
||||||
|
throw IOExceptionSupport.create("Failed to commit last ack: " + ack + ". Reason: " + e,e);
|
||||||
|
} finally {
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void commitLastAck(ConnectionContext context, long xidLastAck, long priority, ActiveMQDestination destination, String subName, String clientId) throws IOException {
|
||||||
|
TransactionContext c = getTransactionContext(context);
|
||||||
|
try {
|
||||||
|
getAdapter().doSetLastAck(c, destination, null, clientId, subName, xidLastAck, priority);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
JDBCPersistenceAdapter.log("JDBC Failure: ", e);
|
||||||
|
throw IOExceptionSupport.create("Failed to commit last ack with priority: " + priority + " on " + destination + " for " + subName + ":" + clientId + ". Reason: " + e,e);
|
||||||
|
} finally {
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rollbackLastAck(ConnectionContext context, JDBCTopicMessageStore store, MessageAck ack, String subName, String clientId) throws IOException {
|
||||||
|
TransactionContext c = getTransactionContext(context);
|
||||||
|
try {
|
||||||
|
byte priority = (byte) store.getCachedStoreSequenceId(c, store.getDestination(), ack.getLastMessageId())[1];
|
||||||
|
getAdapter().doClearLastAck(c, store.getDestination(), priority, clientId, subName);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
JDBCPersistenceAdapter.log("JDBC Failure: ", e);
|
||||||
|
throw IOExceptionSupport.create("Failed to rollback last ack: " + ack + " on " + store.getDestination() + " for " + subName + ":" + clientId + ". Reason: " + e,e);
|
||||||
|
} finally {
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// after recovery there is no record of the original messageId for the ack
|
||||||
|
public void rollbackLastAck(ConnectionContext context, byte priority, ActiveMQDestination destination, String subName, String clientId) throws IOException {
|
||||||
|
TransactionContext c = getTransactionContext(context);
|
||||||
|
try {
|
||||||
|
getAdapter().doClearLastAck(c, destination, priority, clientId, subName);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
JDBCPersistenceAdapter.log("JDBC Failure: ", e);
|
||||||
|
throw IOExceptionSupport.create("Failed to rollback last ack with priority: " + priority + " on " + destination + " for " + subName + ":" + clientId + ". Reason: " + e, e);
|
||||||
|
} finally {
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long[] getStoreSequenceIdForMessageId(MessageId messageId, ActiveMQDestination destination) throws IOException {
|
||||||
|
long[] result = new long[]{-1, Byte.MAX_VALUE -1};
|
||||||
|
TransactionContext c = getTransactionContext();
|
||||||
|
try {
|
||||||
|
result = adapter.getStoreSequenceId(c, destination, messageId);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
JDBCPersistenceAdapter.log("JDBC Failure: ", e);
|
||||||
|
throw IOExceptionSupport.create("Failed to get store sequenceId for messageId: " + messageId +", on: " + destination + ". Reason: " + e, e);
|
||||||
|
} finally {
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,11 @@ package org.apache.activemq.store.jdbc;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
|
@ -48,6 +50,7 @@ public class JDBCTopicMessageStore extends JDBCMessageStore implements TopicMess
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JDBCTopicMessageStore.class);
|
private static final Logger LOG = LoggerFactory.getLogger(JDBCTopicMessageStore.class);
|
||||||
private Map<String, LastRecovered> subscriberLastRecoveredMap = new ConcurrentHashMap<String, LastRecovered>();
|
private Map<String, LastRecovered> subscriberLastRecoveredMap = new ConcurrentHashMap<String, LastRecovered>();
|
||||||
|
private Set<String> pendingCompletion = new HashSet<String>();
|
||||||
|
|
||||||
public static final String PROPERTY_SEQUENCE_ID_CACHE_SIZE = "org.apache.activemq.store.jdbc.SEQUENCE_ID_CACHE_SIZE";
|
public static final String PROPERTY_SEQUENCE_ID_CACHE_SIZE = "org.apache.activemq.store.jdbc.SEQUENCE_ID_CACHE_SIZE";
|
||||||
private static final int SEQUENCE_ID_CACHE_SIZE = Integer.parseInt(System.getProperty(
|
private static final int SEQUENCE_ID_CACHE_SIZE = Integer.parseInt(System.getProperty(
|
||||||
|
@ -75,9 +78,9 @@ public class JDBCTopicMessageStore extends JDBCMessageStore implements TopicMess
|
||||||
try {
|
try {
|
||||||
long[] res = getCachedStoreSequenceId(c, destination, messageId);
|
long[] res = getCachedStoreSequenceId(c, destination, messageId);
|
||||||
if (this.isPrioritizedMessages()) {
|
if (this.isPrioritizedMessages()) {
|
||||||
adapter.doSetLastAckWithPriority(c, destination, clientId, subscriptionName, res[0], res[1]);
|
adapter.doSetLastAckWithPriority(c, destination, context != null ? context.getXid() : null, clientId, subscriptionName, res[0], res[1]);
|
||||||
} else {
|
} else {
|
||||||
adapter.doSetLastAck(c, destination, clientId, subscriptionName, res[0], res[1]);
|
adapter.doSetLastAck(c, destination, context != null ? context.getXid() : null, clientId, subscriptionName, res[0], res[1]);
|
||||||
}
|
}
|
||||||
if (LOG.isTraceEnabled()) {
|
if (LOG.isTraceEnabled()) {
|
||||||
LOG.trace(clientId + ":" + subscriptionName + " ack, seq: " + res[0] + ", priority: " + res[1] + " mid:" + messageId);
|
LOG.trace(clientId + ":" + subscriptionName + " ack, seq: " + res[0] + ", priority: " + res[1] + " mid:" + messageId);
|
||||||
|
@ -90,7 +93,7 @@ public class JDBCTopicMessageStore extends JDBCMessageStore implements TopicMess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private long[] getCachedStoreSequenceId(TransactionContext transactionContext, ActiveMQDestination destination, MessageId messageId) throws SQLException, IOException {
|
public long[] getCachedStoreSequenceId(TransactionContext transactionContext, ActiveMQDestination destination, MessageId messageId) throws SQLException, IOException {
|
||||||
long[] val = null;
|
long[] val = null;
|
||||||
sequenceIdCacheSizeLock.readLock().lock();
|
sequenceIdCacheSizeLock.readLock().lock();
|
||||||
try {
|
try {
|
||||||
|
@ -254,7 +257,7 @@ public class JDBCTopicMessageStore extends JDBCMessageStore implements TopicMess
|
||||||
LastRecoveredAwareListener recoveredAwareListener = new LastRecoveredAwareListener(listener, maxReturned);
|
LastRecoveredAwareListener recoveredAwareListener = new LastRecoveredAwareListener(listener, maxReturned);
|
||||||
try {
|
try {
|
||||||
if (LOG.isTraceEnabled()) {
|
if (LOG.isTraceEnabled()) {
|
||||||
LOG.trace(key + " existing last recovered: " + lastRecovered);
|
LOG.trace(this + ", " + key + " existing last recovered: " + lastRecovered);
|
||||||
}
|
}
|
||||||
if (isPrioritizedMessages()) {
|
if (isPrioritizedMessages()) {
|
||||||
Iterator<LastRecoveredEntry> it = lastRecovered.iterator();
|
Iterator<LastRecoveredEntry> it = lastRecovered.iterator();
|
||||||
|
@ -291,7 +294,26 @@ public class JDBCTopicMessageStore extends JDBCMessageStore implements TopicMess
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetBatching(String clientId, String subscriptionName) {
|
public void resetBatching(String clientId, String subscriptionName) {
|
||||||
subscriberLastRecoveredMap.remove(getSubscriptionKey(clientId, subscriptionName));
|
String key = getSubscriptionKey(clientId, subscriptionName);
|
||||||
|
if (!pendingCompletion.contains(key)) {
|
||||||
|
subscriberLastRecoveredMap.remove(key);
|
||||||
|
} else {
|
||||||
|
LOG.trace(this + ", skip resetBatch during pending completion for: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pendingCompletion(String clientId, String subscriptionName, long sequenceId, byte priority) {
|
||||||
|
final String key = getSubscriptionKey(clientId, subscriptionName);
|
||||||
|
LastRecovered recovered = new LastRecovered();
|
||||||
|
recovered.perPriority[isPrioritizedMessages() ? priority : javax.jms.Message.DEFAULT_PRIORITY].recovered = sequenceId;
|
||||||
|
subscriberLastRecoveredMap.put(key, recovered);
|
||||||
|
pendingCompletion.add(key);
|
||||||
|
LOG.trace(this + ", pending completion: " + key + ", last: " + recovered);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void complete(String clientId, String subscriptionName) {
|
||||||
|
pendingCompletion.remove(getSubscriptionKey(clientId, subscriptionName));
|
||||||
|
LOG.trace(this + ", completion for: " + getSubscriptionKey(clientId, subscriptionName));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onAdd(MessageId messageId, long sequenceId, byte priority) {
|
protected void onAdd(MessageId messageId, long sequenceId, byte priority) {
|
||||||
|
|
|
@ -0,0 +1,379 @@
|
||||||
|
/**
|
||||||
|
* 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.store.jdbc;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.apache.activemq.broker.ConnectionContext;
|
||||||
|
import org.apache.activemq.broker.region.Destination;
|
||||||
|
import org.apache.activemq.broker.region.DurableTopicSubscription;
|
||||||
|
import org.apache.activemq.broker.region.Subscription;
|
||||||
|
import org.apache.activemq.broker.region.Topic;
|
||||||
|
import org.apache.activemq.command.ActiveMQDestination;
|
||||||
|
import org.apache.activemq.command.Message;
|
||||||
|
import org.apache.activemq.command.MessageAck;
|
||||||
|
import org.apache.activemq.command.MessageId;
|
||||||
|
import org.apache.activemq.command.TransactionId;
|
||||||
|
import org.apache.activemq.command.XATransactionId;
|
||||||
|
import org.apache.activemq.store.MessageStore;
|
||||||
|
import org.apache.activemq.store.ProxyTopicMessageStore;
|
||||||
|
import org.apache.activemq.store.TopicMessageStore;
|
||||||
|
import org.apache.activemq.store.TransactionRecoveryListener;
|
||||||
|
import org.apache.activemq.store.jdbc.adapter.DefaultJDBCAdapter;
|
||||||
|
import org.apache.activemq.store.memory.MemoryTransactionStore;
|
||||||
|
import org.apache.activemq.util.ByteSequence;
|
||||||
|
import org.apache.activemq.util.DataByteArrayInputStream;
|
||||||
|
import org.apache.activemq.util.SubscriptionKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* respect 2pc prepare
|
||||||
|
* uses local transactions to maintain prepared state
|
||||||
|
* xid column provides transaction flag for additions and removals
|
||||||
|
* a commit clears that context and completes the work
|
||||||
|
* a rollback clears the flag and removes the additions
|
||||||
|
* Essentially a prepare is an insert &| update transaction
|
||||||
|
* commit|rollback is an update &| remove
|
||||||
|
*/
|
||||||
|
public class JdbcMemoryTransactionStore extends MemoryTransactionStore {
|
||||||
|
|
||||||
|
|
||||||
|
private HashMap<ActiveMQDestination, MessageStore> topicStores = new HashMap<ActiveMQDestination, MessageStore>();
|
||||||
|
|
||||||
|
public JdbcMemoryTransactionStore(JDBCPersistenceAdapter jdbcPersistenceAdapter) {
|
||||||
|
super(jdbcPersistenceAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepare(TransactionId txid) throws IOException {
|
||||||
|
Tx tx = inflightTransactions.remove(txid);
|
||||||
|
if (tx == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectionContext ctx = new ConnectionContext();
|
||||||
|
// setting the xid modifies the add/remove to be pending transaction outcome
|
||||||
|
ctx.setXid((XATransactionId) txid);
|
||||||
|
persistenceAdapter.beginTransaction(ctx);
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Do all the message adds.
|
||||||
|
for (Iterator<AddMessageCommand> iter = tx.messages.iterator(); iter.hasNext();) {
|
||||||
|
AddMessageCommand cmd = iter.next();
|
||||||
|
cmd.run(ctx);
|
||||||
|
}
|
||||||
|
// And removes..
|
||||||
|
for (Iterator<RemoveMessageCommand> iter = tx.acks.iterator(); iter.hasNext();) {
|
||||||
|
RemoveMessageCommand cmd = iter.next();
|
||||||
|
cmd.run(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch ( IOException e ) {
|
||||||
|
persistenceAdapter.rollbackTransaction(ctx);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
persistenceAdapter.commitTransaction(ctx);
|
||||||
|
|
||||||
|
ctx.setXid(null);
|
||||||
|
// setup for commit outcome
|
||||||
|
ArrayList<AddMessageCommand> updateFromPreparedStateCommands = new ArrayList<AddMessageCommand>();
|
||||||
|
for (Iterator<AddMessageCommand> iter = tx.messages.iterator(); iter.hasNext();) {
|
||||||
|
final AddMessageCommand addMessageCommand = iter.next();
|
||||||
|
updateFromPreparedStateCommands.add(new AddMessageCommand() {
|
||||||
|
@Override
|
||||||
|
public Message getMessage() {
|
||||||
|
return addMessageCommand.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageStore getMessageStore() {
|
||||||
|
return addMessageCommand.getMessageStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(ConnectionContext context) throws IOException {
|
||||||
|
JDBCPersistenceAdapter jdbcPersistenceAdapter = (JDBCPersistenceAdapter) persistenceAdapter;
|
||||||
|
Message message = addMessageCommand.getMessage();
|
||||||
|
jdbcPersistenceAdapter.commitAdd(context, message.getMessageId());
|
||||||
|
((JDBCMessageStore)addMessageCommand.getMessageStore()).onAdd(
|
||||||
|
message.getMessageId(),
|
||||||
|
(Long)message.getMessageId().getDataLocator(),
|
||||||
|
message.getPriority());
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tx.messages = updateFromPreparedStateCommands;
|
||||||
|
preparedTransactions.put(txid, tx);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rollback(TransactionId txid) throws IOException {
|
||||||
|
|
||||||
|
Tx tx = inflightTransactions.remove(txid);
|
||||||
|
if (tx == null) {
|
||||||
|
tx = preparedTransactions.remove(txid);
|
||||||
|
if (tx != null) {
|
||||||
|
// undo prepare work
|
||||||
|
ConnectionContext ctx = new ConnectionContext();
|
||||||
|
persistenceAdapter.beginTransaction(ctx);
|
||||||
|
try {
|
||||||
|
|
||||||
|
for (Iterator<AddMessageCommand> iter = tx.messages.iterator(); iter.hasNext(); ) {
|
||||||
|
final Message message = iter.next().getMessage();
|
||||||
|
// need to delete the row
|
||||||
|
((JDBCPersistenceAdapter) persistenceAdapter).commitRemove(ctx, new MessageAck(message, MessageAck.STANDARD_ACK_TYPE, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Iterator<RemoveMessageCommand> iter = tx.acks.iterator(); iter.hasNext(); ) {
|
||||||
|
RemoveMessageCommand removeMessageCommand = iter.next();
|
||||||
|
if (removeMessageCommand instanceof LastAckCommand ) {
|
||||||
|
((LastAckCommand)removeMessageCommand).rollback(ctx);
|
||||||
|
} else {
|
||||||
|
// need to unset the txid flag on the existing row
|
||||||
|
((JDBCPersistenceAdapter) persistenceAdapter).commitAdd(ctx,
|
||||||
|
removeMessageCommand.getMessageAck().getLastMessageId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
persistenceAdapter.rollbackTransaction(ctx);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
persistenceAdapter.commitTransaction(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recover(TransactionRecoveryListener listener) throws IOException {
|
||||||
|
((JDBCPersistenceAdapter)persistenceAdapter).recover(this);
|
||||||
|
super.recover(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recoverAdd(long id, byte[] messageBytes) throws IOException {
|
||||||
|
final Message message = (Message) ((JDBCPersistenceAdapter)persistenceAdapter).getWireFormat().unmarshal(new ByteSequence(messageBytes));
|
||||||
|
message.getMessageId().setDataLocator(id);
|
||||||
|
Tx tx = getPreparedTx(message.getTransactionId());
|
||||||
|
tx.add(new AddMessageCommand() {
|
||||||
|
@Override
|
||||||
|
public Message getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageStore getMessageStore() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(ConnectionContext context) throws IOException {
|
||||||
|
((JDBCPersistenceAdapter)persistenceAdapter).commitAdd(null, message.getMessageId());
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recoverAck(long id, byte[] xid, byte[] message) throws IOException {
|
||||||
|
Message msg = (Message) ((JDBCPersistenceAdapter)persistenceAdapter).getWireFormat().unmarshal(new ByteSequence(message));
|
||||||
|
msg.getMessageId().setDataLocator(id);
|
||||||
|
Tx tx = getPreparedTx(new XATransactionId(xid));
|
||||||
|
final MessageAck ack = new MessageAck(msg, MessageAck.STANDARD_ACK_TYPE, 1);
|
||||||
|
tx.add(new RemoveMessageCommand() {
|
||||||
|
@Override
|
||||||
|
public MessageAck getMessageAck() {
|
||||||
|
return ack;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(ConnectionContext context) throws IOException {
|
||||||
|
((JDBCPersistenceAdapter)persistenceAdapter).commitRemove(context, ack);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageStore getMessageStore() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LastAckCommand extends RemoveMessageCommand {
|
||||||
|
void rollback(ConnectionContext context) throws IOException;
|
||||||
|
|
||||||
|
String getClientId();
|
||||||
|
|
||||||
|
String getSubName();
|
||||||
|
|
||||||
|
long getSequence();
|
||||||
|
|
||||||
|
byte getPriority();
|
||||||
|
|
||||||
|
void setMessageStore(JDBCTopicMessageStore jdbcTopicMessageStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recoverLastAck(byte[] encodedXid, final ActiveMQDestination destination, final String subName, final String clientId) throws IOException {
|
||||||
|
Tx tx = getPreparedTx(new XATransactionId(encodedXid));
|
||||||
|
DataByteArrayInputStream inputStream = new DataByteArrayInputStream(encodedXid);
|
||||||
|
inputStream.skipBytes(1); // +|-
|
||||||
|
final long lastAck = inputStream.readLong();
|
||||||
|
final byte priority = inputStream.readByte();
|
||||||
|
final MessageAck ack = new MessageAck();
|
||||||
|
ack.setDestination(destination);
|
||||||
|
tx.add(new LastAckCommand() {
|
||||||
|
JDBCTopicMessageStore jdbcTopicMessageStore;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageAck getMessageAck() {
|
||||||
|
return ack;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageStore getMessageStore() {
|
||||||
|
return jdbcTopicMessageStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(ConnectionContext context) throws IOException {
|
||||||
|
((JDBCPersistenceAdapter)persistenceAdapter).commitLastAck(context, lastAck, priority, destination, subName, clientId);
|
||||||
|
jdbcTopicMessageStore.complete(clientId, subName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rollback(ConnectionContext context) throws IOException {
|
||||||
|
((JDBCPersistenceAdapter)persistenceAdapter).rollbackLastAck(context, priority, jdbcTopicMessageStore.getDestination(), subName, clientId);
|
||||||
|
jdbcTopicMessageStore.complete(clientId, subName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientId() {
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSubName() {
|
||||||
|
return subName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getSequence() {
|
||||||
|
return lastAck;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMessageStore(JDBCTopicMessageStore jdbcTopicMessageStore) {
|
||||||
|
this.jdbcTopicMessageStore = jdbcTopicMessageStore;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onProxyTopicStore(ProxyTopicMessageStore proxyTopicMessageStore) {
|
||||||
|
topicStores.put(proxyTopicMessageStore.getDestination(), proxyTopicMessageStore.getDelegate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRecovered(Tx tx) {
|
||||||
|
for (RemoveMessageCommand removeMessageCommand: tx.acks) {
|
||||||
|
if (removeMessageCommand instanceof LastAckCommand) {
|
||||||
|
LastAckCommand lastAckCommand = (LastAckCommand) removeMessageCommand;
|
||||||
|
JDBCTopicMessageStore jdbcTopicMessageStore = (JDBCTopicMessageStore) topicStores.get(lastAckCommand.getMessageAck().getDestination());
|
||||||
|
jdbcTopicMessageStore.pendingCompletion(lastAckCommand.getClientId(), lastAckCommand.getSubName(), lastAckCommand.getSequence(), lastAckCommand.getPriority());
|
||||||
|
lastAckCommand.setMessageStore(jdbcTopicMessageStore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void acknowledge(final TopicMessageStore topicMessageStore, final String clientId, final String subscriptionName,
|
||||||
|
final MessageId messageId, final MessageAck ack) throws IOException {
|
||||||
|
|
||||||
|
if (ack.isInTransaction()) {
|
||||||
|
Tx tx = getTx(ack.getTransactionId());
|
||||||
|
tx.add(new LastAckCommand() {
|
||||||
|
public MessageAck getMessageAck() {
|
||||||
|
return ack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run(ConnectionContext ctx) throws IOException {
|
||||||
|
topicMessageStore.acknowledge(ctx, clientId, subscriptionName, messageId, ack);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageStore getMessageStore() {
|
||||||
|
return topicMessageStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rollback(ConnectionContext context) throws IOException {
|
||||||
|
JDBCTopicMessageStore jdbcTopicMessageStore = (JDBCTopicMessageStore)topicMessageStore;
|
||||||
|
((JDBCPersistenceAdapter)persistenceAdapter).rollbackLastAck(context,
|
||||||
|
jdbcTopicMessageStore,
|
||||||
|
ack,
|
||||||
|
subscriptionName, clientId);
|
||||||
|
jdbcTopicMessageStore.complete(clientId, subscriptionName);
|
||||||
|
|
||||||
|
Map<ActiveMQDestination, Destination> destinations = ((JDBCPersistenceAdapter) persistenceAdapter).getBrokerService().getRegionBroker().getDestinationMap();
|
||||||
|
Topic topic = (Topic) destinations.get(topicMessageStore.getDestination());
|
||||||
|
SubscriptionKey key = new SubscriptionKey(clientId, subscriptionName);
|
||||||
|
topic.getDurableTopicSubs().get(key).getPending().rollback(ack.getLastMessageId());
|
||||||
|
topic.clearPendingMessages(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientId() {
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSubName() {
|
||||||
|
return subscriptionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getSequence() {
|
||||||
|
throw new IllegalStateException("Sequence id must be inferred from ack");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte getPriority() {
|
||||||
|
throw new IllegalStateException("Priority must be inferred from ack or row");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMessageStore(JDBCTopicMessageStore jdbcTopicMessageStore) {
|
||||||
|
throw new IllegalStateException("message store already known!");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
topicMessageStore.acknowledge(null, clientId, subscriptionName, messageId, ack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -76,6 +76,15 @@ public class Statements {
|
||||||
private String deleteOldMessagesStatementWithPriority;
|
private String deleteOldMessagesStatementWithPriority;
|
||||||
private String durableSubscriberMessageCountStatementWithPriority;
|
private String durableSubscriberMessageCountStatementWithPriority;
|
||||||
private String dropAckPKAlterStatementEnd;
|
private String dropAckPKAlterStatementEnd;
|
||||||
|
private String updateXidFlagStatement;
|
||||||
|
private String findOpsPendingOutcomeStatement;
|
||||||
|
private String clearXidFlagStatement;
|
||||||
|
private String updateDurableLastAckInTxStatement;
|
||||||
|
private String findAcksPendingOutcomeStatement;
|
||||||
|
private String clearDurableLastAckInTxStatement;
|
||||||
|
private String updateDurableLastAckWithPriorityStatement;
|
||||||
|
private String updateDurableLastAckWithPriorityInTxStatement;
|
||||||
|
private String findXidByIdStatement;
|
||||||
|
|
||||||
public String[] getCreateSchemaStatements() {
|
public String[] getCreateSchemaStatements() {
|
||||||
if (createSchemaStatements == null) {
|
if (createSchemaStatements == null) {
|
||||||
|
@ -99,7 +108,9 @@ public class Statements {
|
||||||
"INSERT INTO " + getFullLockTableName() + "(ID) VALUES (1)",
|
"INSERT INTO " + getFullLockTableName() + "(ID) VALUES (1)",
|
||||||
"ALTER TABLE " + getFullMessageTableName() + " ADD PRIORITY " + sequenceDataType,
|
"ALTER TABLE " + getFullMessageTableName() + " ADD PRIORITY " + sequenceDataType,
|
||||||
"CREATE INDEX " + getFullMessageTableName() + "_PIDX ON " + getFullMessageTableName() + " (PRIORITY)",
|
"CREATE INDEX " + getFullMessageTableName() + "_PIDX ON " + getFullMessageTableName() + " (PRIORITY)",
|
||||||
|
"ALTER TABLE " + getFullMessageTableName() + " ADD XID " + binaryDataType,
|
||||||
"ALTER TABLE " + getFullAckTableName() + " ADD PRIORITY " + sequenceDataType + " DEFAULT 5 NOT NULL",
|
"ALTER TABLE " + getFullAckTableName() + " ADD PRIORITY " + sequenceDataType + " DEFAULT 5 NOT NULL",
|
||||||
|
"ALTER TABLE " + getFullAckTableName() + " ADD XID " + binaryDataType,
|
||||||
"ALTER TABLE " + getFullAckTableName() + " " + getDropAckPKAlterStatementEnd(),
|
"ALTER TABLE " + getFullAckTableName() + " " + getDropAckPKAlterStatementEnd(),
|
||||||
"ALTER TABLE " + getFullAckTableName() + " ADD PRIMARY KEY (CONTAINER, CLIENT_ID, SUB_NAME, PRIORITY)",
|
"ALTER TABLE " + getFullAckTableName() + " ADD PRIMARY KEY (CONTAINER, CLIENT_ID, SUB_NAME, PRIORITY)",
|
||||||
};
|
};
|
||||||
|
@ -131,7 +142,7 @@ public class Statements {
|
||||||
if (addMessageStatement == null) {
|
if (addMessageStatement == null) {
|
||||||
addMessageStatement = "INSERT INTO "
|
addMessageStatement = "INSERT INTO "
|
||||||
+ getFullMessageTableName()
|
+ getFullMessageTableName()
|
||||||
+ "(ID, MSGID_PROD, MSGID_SEQ, CONTAINER, EXPIRATION, PRIORITY, MSG) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
+ "(ID, MSGID_PROD, MSGID_SEQ, CONTAINER, EXPIRATION, PRIORITY, MSG, XID) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||||
}
|
}
|
||||||
return addMessageStatement;
|
return addMessageStatement;
|
||||||
}
|
}
|
||||||
|
@ -171,7 +182,14 @@ public class Statements {
|
||||||
}
|
}
|
||||||
return findMessageByIdStatement;
|
return findMessageByIdStatement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getFindXidByIdStatement() {
|
||||||
|
if (findXidByIdStatement == null) {
|
||||||
|
findXidByIdStatement = "SELECT XID FROM " + getFullMessageTableName() + " WHERE ID=?";
|
||||||
|
}
|
||||||
|
return findXidByIdStatement;
|
||||||
|
}
|
||||||
|
|
||||||
public String getFindAllMessagesStatement() {
|
public String getFindAllMessagesStatement() {
|
||||||
if (findAllMessagesStatement == null) {
|
if (findAllMessagesStatement == null) {
|
||||||
findAllMessagesStatement = "SELECT ID, MSG FROM " + getFullMessageTableName()
|
findAllMessagesStatement = "SELECT ID, MSG FROM " + getFullMessageTableName()
|
||||||
|
@ -271,6 +289,7 @@ public class Statements {
|
||||||
findDurableSubMessagesStatement = "SELECT M.ID, M.MSG FROM " + getFullMessageTableName() + " M, "
|
findDurableSubMessagesStatement = "SELECT M.ID, M.MSG FROM " + getFullMessageTableName() + " M, "
|
||||||
+ getFullAckTableName() + " D "
|
+ getFullAckTableName() + " D "
|
||||||
+ " WHERE D.CONTAINER=? AND D.CLIENT_ID=? AND D.SUB_NAME=?"
|
+ " WHERE D.CONTAINER=? AND D.CLIENT_ID=? AND D.SUB_NAME=?"
|
||||||
|
+ " AND M.XID IS NULL"
|
||||||
+ " AND M.CONTAINER=D.CONTAINER AND M.ID > D.LAST_ACKED_ID"
|
+ " AND M.CONTAINER=D.CONTAINER AND M.ID > D.LAST_ACKED_ID"
|
||||||
+ " AND M.ID > ?"
|
+ " AND M.ID > ?"
|
||||||
+ " ORDER BY M.ID";
|
+ " ORDER BY M.ID";
|
||||||
|
@ -283,6 +302,7 @@ public class Statements {
|
||||||
findDurableSubMessagesByPriorityStatement = "SELECT M.ID, M.MSG FROM " + getFullMessageTableName() + " M,"
|
findDurableSubMessagesByPriorityStatement = "SELECT M.ID, M.MSG FROM " + getFullMessageTableName() + " M,"
|
||||||
+ " " + getFullAckTableName() + " D"
|
+ " " + getFullAckTableName() + " D"
|
||||||
+ " WHERE D.CONTAINER=? AND D.CLIENT_ID=? AND D.SUB_NAME=?"
|
+ " WHERE D.CONTAINER=? AND D.CLIENT_ID=? AND D.SUB_NAME=?"
|
||||||
|
+ " AND M.XID IS NULL"
|
||||||
+ " AND M.CONTAINER=D.CONTAINER"
|
+ " AND M.CONTAINER=D.CONTAINER"
|
||||||
+ " AND M.PRIORITY=D.PRIORITY AND M.ID > D.LAST_ACKED_ID"
|
+ " AND M.PRIORITY=D.PRIORITY AND M.ID > D.LAST_ACKED_ID"
|
||||||
+ " AND M.ID > ? AND M.PRIORITY = ?"
|
+ " AND M.ID > ? AND M.PRIORITY = ?"
|
||||||
|
@ -414,7 +434,7 @@ public class Statements {
|
||||||
public String getDestinationMessageCountStatement() {
|
public String getDestinationMessageCountStatement() {
|
||||||
if (destinationMessageCountStatement == null) {
|
if (destinationMessageCountStatement == null) {
|
||||||
destinationMessageCountStatement = "SELECT COUNT(*) FROM " + getFullMessageTableName()
|
destinationMessageCountStatement = "SELECT COUNT(*) FROM " + getFullMessageTableName()
|
||||||
+ " WHERE CONTAINER=?";
|
+ " WHERE CONTAINER=? AND XID IS NULL";
|
||||||
}
|
}
|
||||||
return destinationMessageCountStatement;
|
return destinationMessageCountStatement;
|
||||||
}
|
}
|
||||||
|
@ -425,7 +445,7 @@ public class Statements {
|
||||||
public String getFindNextMessagesStatement() {
|
public String getFindNextMessagesStatement() {
|
||||||
if (findNextMessagesStatement == null) {
|
if (findNextMessagesStatement == null) {
|
||||||
findNextMessagesStatement = "SELECT ID, MSG FROM " + getFullMessageTableName()
|
findNextMessagesStatement = "SELECT ID, MSG FROM " + getFullMessageTableName()
|
||||||
+ " WHERE CONTAINER=? AND ID > ? ORDER BY ID";
|
+ " WHERE CONTAINER=? AND ID > ? AND XID IS NULL ORDER BY ID";
|
||||||
}
|
}
|
||||||
return findNextMessagesStatement;
|
return findNextMessagesStatement;
|
||||||
}
|
}
|
||||||
|
@ -437,6 +457,7 @@ public class Statements {
|
||||||
if (findNextMessagesByPriorityStatement == null) {
|
if (findNextMessagesByPriorityStatement == null) {
|
||||||
findNextMessagesByPriorityStatement = "SELECT ID, MSG FROM " + getFullMessageTableName()
|
findNextMessagesByPriorityStatement = "SELECT ID, MSG FROM " + getFullMessageTableName()
|
||||||
+ " WHERE CONTAINER=?"
|
+ " WHERE CONTAINER=?"
|
||||||
|
+ " AND XID IS NULL"
|
||||||
+ " AND ((ID > ? AND PRIORITY = ?) OR PRIORITY < ?)"
|
+ " AND ((ID > ? AND PRIORITY = ?) OR PRIORITY < ?)"
|
||||||
+ " ORDER BY PRIORITY DESC, ID";
|
+ " ORDER BY PRIORITY DESC, ID";
|
||||||
}
|
}
|
||||||
|
@ -478,11 +499,76 @@ public class Statements {
|
||||||
public String getUpdateDurableLastAckStatement() {
|
public String getUpdateDurableLastAckStatement() {
|
||||||
if (updateDurableLastAckStatement == null) {
|
if (updateDurableLastAckStatement == null) {
|
||||||
updateDurableLastAckStatement = "UPDATE " + getFullAckTableName()
|
updateDurableLastAckStatement = "UPDATE " + getFullAckTableName()
|
||||||
+ " SET LAST_ACKED_ID = ? WHERE CONTAINER=? AND CLIENT_ID=? AND SUB_NAME=?";
|
+ " SET LAST_ACKED_ID=?, XID = NULL WHERE CONTAINER=? AND CLIENT_ID=? AND SUB_NAME=?";
|
||||||
}
|
}
|
||||||
return updateDurableLastAckStatement;
|
return updateDurableLastAckStatement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getUpdateDurableLastAckInTxStatement() {
|
||||||
|
if (updateDurableLastAckInTxStatement == null) {
|
||||||
|
updateDurableLastAckInTxStatement = "UPDATE " + getFullAckTableName()
|
||||||
|
+ " SET XID=? WHERE CONTAINER=? AND CLIENT_ID=? AND SUB_NAME=?";
|
||||||
|
}
|
||||||
|
return updateDurableLastAckInTxStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUpdateDurableLastAckWithPriorityStatement() {
|
||||||
|
if (updateDurableLastAckWithPriorityStatement == null) {
|
||||||
|
updateDurableLastAckWithPriorityStatement = "UPDATE " + getFullAckTableName()
|
||||||
|
+ " SET LAST_ACKED_ID=?, XID = NULL WHERE CONTAINER=? AND CLIENT_ID=? AND SUB_NAME=? AND PRIORITY=?";
|
||||||
|
}
|
||||||
|
return updateDurableLastAckWithPriorityStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUpdateDurableLastAckWithPriorityInTxStatement() {
|
||||||
|
if (updateDurableLastAckWithPriorityInTxStatement == null) {
|
||||||
|
updateDurableLastAckWithPriorityInTxStatement = "UPDATE " + getFullAckTableName()
|
||||||
|
+ " SET XID=? WHERE CONTAINER=? AND CLIENT_ID=? AND SUB_NAME=? AND PRIORITY=?";
|
||||||
|
}
|
||||||
|
return updateDurableLastAckWithPriorityInTxStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClearDurableLastAckInTxStatement() {
|
||||||
|
if (clearDurableLastAckInTxStatement == null) {
|
||||||
|
clearDurableLastAckInTxStatement = "UPDATE " + getFullAckTableName()
|
||||||
|
+ " SET XID = NULL WHERE CONTAINER=? AND CLIENT_ID=? AND SUB_NAME=? AND PRIORITY=?";
|
||||||
|
}
|
||||||
|
return clearDurableLastAckInTxStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFindOpsPendingOutcomeStatement() {
|
||||||
|
if (findOpsPendingOutcomeStatement == null) {
|
||||||
|
findOpsPendingOutcomeStatement = "SELECT ID, XID, MSG FROM " + getFullMessageTableName()
|
||||||
|
+ " WHERE XID IS NOT NULL ORDER BY ID";
|
||||||
|
}
|
||||||
|
return findOpsPendingOutcomeStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFindAcksPendingOutcomeStatement() {
|
||||||
|
if (findAcksPendingOutcomeStatement == null) {
|
||||||
|
findAcksPendingOutcomeStatement = "SELECT XID," +
|
||||||
|
" CONTAINER, CLIENT_ID, SUB_NAME FROM " + getFullAckTableName()
|
||||||
|
+ " WHERE XID IS NOT NULL";
|
||||||
|
}
|
||||||
|
return findAcksPendingOutcomeStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUpdateXidFlagStatement() {
|
||||||
|
if (updateXidFlagStatement == null) {
|
||||||
|
updateXidFlagStatement = "UPDATE " + getFullMessageTableName()
|
||||||
|
+ " SET XID = ? WHERE ID = ?";
|
||||||
|
}
|
||||||
|
return updateXidFlagStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClearXidFlagStatement() {
|
||||||
|
if (clearXidFlagStatement == null) {
|
||||||
|
clearXidFlagStatement = "UPDATE " + getFullMessageTableName()
|
||||||
|
+ " SET XID = NULL WHERE ID = ?";
|
||||||
|
}
|
||||||
|
return clearXidFlagStatement;
|
||||||
|
}
|
||||||
|
|
||||||
public String getFullMessageTableName() {
|
public String getFullMessageTableName() {
|
||||||
return getTablePrefix() + getMessageTableName();
|
return getTablePrefix() + getMessageTableName();
|
||||||
}
|
}
|
||||||
|
@ -788,5 +874,41 @@ public class Statements {
|
||||||
|
|
||||||
public void setUpdateDurableLastAckStatement(String updateDurableLastAckStatement) {
|
public void setUpdateDurableLastAckStatement(String updateDurableLastAckStatement) {
|
||||||
this.updateDurableLastAckStatement = updateDurableLastAckStatement;
|
this.updateDurableLastAckStatement = updateDurableLastAckStatement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUpdateXidFlagStatement(String updateXidFlagStatement) {
|
||||||
|
this.updateXidFlagStatement = updateXidFlagStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFindOpsPendingOutcomeStatement(String findOpsPendingOutcomeStatement) {
|
||||||
|
this.findOpsPendingOutcomeStatement = findOpsPendingOutcomeStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClearXidFlagStatement(String clearXidFlagStatement) {
|
||||||
|
this.clearXidFlagStatement = clearXidFlagStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdateDurableLastAckInTxStatement(String updateDurableLastAckInTxStatement) {
|
||||||
|
this.updateDurableLastAckInTxStatement = updateDurableLastAckInTxStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFindAcksPendingOutcomeStatement(String findAcksPendingOutcomeStatement) {
|
||||||
|
this.findAcksPendingOutcomeStatement = findAcksPendingOutcomeStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClearDurableLastAckInTxStatement(String clearDurableLastAckInTxStatement) {
|
||||||
|
this.clearDurableLastAckInTxStatement = clearDurableLastAckInTxStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdateDurableLastAckWithPriorityStatement(String updateDurableLastAckWithPriorityStatement) {
|
||||||
|
this.updateDurableLastAckWithPriorityStatement = updateDurableLastAckWithPriorityStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdateDurableLastAckWithPriorityInTxStatement(String updateDurableLastAckWithPriorityInTxStatement) {
|
||||||
|
this.updateDurableLastAckWithPriorityInTxStatement = updateDurableLastAckWithPriorityInTxStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFindXidByIdStatement(String findXidByIdStatement) {
|
||||||
|
this.findXidByIdStatement = findXidByIdStatement;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -18,18 +18,16 @@ package org.apache.activemq.store.jdbc.adapter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.sql.Blob;
|
import java.sql.Blob;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
||||||
import javax.jms.JMSException;
|
|
||||||
import javax.sql.rowset.serial.SerialBlob;
|
|
||||||
|
|
||||||
import org.apache.activemq.command.ActiveMQDestination;
|
import org.apache.activemq.command.ActiveMQDestination;
|
||||||
import org.apache.activemq.command.MessageId;
|
import org.apache.activemq.command.MessageId;
|
||||||
|
import org.apache.activemq.command.XATransactionId;
|
||||||
|
import org.apache.activemq.store.jdbc.Statements;
|
||||||
import org.apache.activemq.store.jdbc.TransactionContext;
|
import org.apache.activemq.store.jdbc.TransactionContext;
|
||||||
import org.apache.activemq.util.ByteArrayOutputStream;
|
import org.apache.activemq.util.ByteArrayOutputStream;
|
||||||
|
|
||||||
|
@ -52,11 +50,25 @@ import org.apache.activemq.util.ByteArrayOutputStream;
|
||||||
*/
|
*/
|
||||||
public class BlobJDBCAdapter extends DefaultJDBCAdapter {
|
public class BlobJDBCAdapter extends DefaultJDBCAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatements(Statements statements) {
|
||||||
|
|
||||||
|
String addMessageStatement = "INSERT INTO "
|
||||||
|
+ statements.getFullMessageTableName()
|
||||||
|
+ "(ID, MSGID_PROD, MSGID_SEQ, CONTAINER, EXPIRATION, PRIORITY, MSG) VALUES (?, ?, ?, ?, ?, ?, empty_blob(), empty_blob())";
|
||||||
|
statements.setAddMessageStatement(addMessageStatement);
|
||||||
|
|
||||||
|
String findMessageByIdStatement = "SELECT MSG FROM " +
|
||||||
|
statements.getFullMessageTableName() + " WHERE ID=? FOR UPDATE";
|
||||||
|
statements.setFindMessageByIdStatement(findMessageByIdStatement);
|
||||||
|
|
||||||
|
super.setStatements(statements);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doAddMessage(TransactionContext c, long sequence, MessageId messageID, ActiveMQDestination destination, byte[] data,
|
public void doAddMessage(TransactionContext c, long sequence, MessageId messageID, ActiveMQDestination destination, byte[] data,
|
||||||
long expiration, byte priority) throws SQLException, IOException {
|
long expiration, byte priority, XATransactionId xid) throws SQLException, IOException {
|
||||||
PreparedStatement s = null;
|
PreparedStatement s = null;
|
||||||
ResultSet rs = null;
|
|
||||||
cleanupExclusiveLock.readLock().lock();
|
cleanupExclusiveLock.readLock().lock();
|
||||||
try {
|
try {
|
||||||
// Add the Blob record.
|
// Add the Blob record.
|
||||||
|
@ -74,12 +86,29 @@ public class BlobJDBCAdapter extends DefaultJDBCAdapter {
|
||||||
s.close();
|
s.close();
|
||||||
|
|
||||||
// Select the blob record so that we can update it.
|
// Select the blob record so that we can update it.
|
||||||
s = c.getConnection().prepareStatement(statements.getFindMessageByIdStatement(),
|
updateBlob(c.getConnection(), statements.getFindMessageByIdStatement(), sequence, data);
|
||||||
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
|
if (xid != null) {
|
||||||
|
byte[] xidVal = xid.getEncodedXidBytes();
|
||||||
|
xidVal[0] = '+';
|
||||||
|
updateBlob(c.getConnection(), statements.getFindXidByIdStatement(), sequence, xidVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
cleanupExclusiveLock.readLock().unlock();
|
||||||
|
close(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBlob(Connection connection, String findMessageByIdStatement, long sequence, byte[] data) throws SQLException, IOException {
|
||||||
|
PreparedStatement s = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
s = connection.prepareStatement(statements.getFindMessageByIdStatement(),
|
||||||
|
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
|
||||||
s.setLong(1, sequence);
|
s.setLong(1, sequence);
|
||||||
rs = s.executeQuery();
|
rs = s.executeQuery();
|
||||||
if (!rs.next()) {
|
if (!rs.next()) {
|
||||||
throw new IOException("Failed select blob for message: " + messageID + " in container.");
|
throw new IOException("Failed select blob for message: " + sequence + " in container.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the blob
|
// Update the blob
|
||||||
|
@ -88,9 +117,7 @@ public class BlobJDBCAdapter extends DefaultJDBCAdapter {
|
||||||
blob.setBytes(1, data);
|
blob.setBytes(1, data);
|
||||||
rs.updateBlob(1, blob);
|
rs.updateBlob(1, blob);
|
||||||
rs.updateRow(); // Update the row with the updated blob
|
rs.updateRow(); // Update the row with the updated blob
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
cleanupExclusiveLock.readLock().unlock();
|
|
||||||
close(rs);
|
close(rs);
|
||||||
close(s);
|
close(s);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,8 @@
|
||||||
package org.apache.activemq.store.jdbc.adapter;
|
package org.apache.activemq.store.jdbc.adapter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.ResultSetMetaData;
|
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -35,12 +32,15 @@ import org.apache.activemq.command.ActiveMQDestination;
|
||||||
import org.apache.activemq.command.MessageId;
|
import org.apache.activemq.command.MessageId;
|
||||||
import org.apache.activemq.command.ProducerId;
|
import org.apache.activemq.command.ProducerId;
|
||||||
import org.apache.activemq.command.SubscriptionInfo;
|
import org.apache.activemq.command.SubscriptionInfo;
|
||||||
|
import org.apache.activemq.command.XATransactionId;
|
||||||
import org.apache.activemq.store.jdbc.JDBCAdapter;
|
import org.apache.activemq.store.jdbc.JDBCAdapter;
|
||||||
import org.apache.activemq.store.jdbc.JDBCMessageIdScanListener;
|
import org.apache.activemq.store.jdbc.JDBCMessageIdScanListener;
|
||||||
import org.apache.activemq.store.jdbc.JDBCMessageRecoveryListener;
|
import org.apache.activemq.store.jdbc.JDBCMessageRecoveryListener;
|
||||||
import org.apache.activemq.store.jdbc.JDBCPersistenceAdapter;
|
import org.apache.activemq.store.jdbc.JDBCPersistenceAdapter;
|
||||||
|
import org.apache.activemq.store.jdbc.JdbcMemoryTransactionStore;
|
||||||
import org.apache.activemq.store.jdbc.Statements;
|
import org.apache.activemq.store.jdbc.Statements;
|
||||||
import org.apache.activemq.store.jdbc.TransactionContext;
|
import org.apache.activemq.store.jdbc.TransactionContext;
|
||||||
|
import org.apache.activemq.util.DataByteArrayOutputStream;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -201,10 +201,13 @@ public class DefaultJDBCAdapter implements JDBCAdapter {
|
||||||
close(s);
|
close(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A non null xid indicated the op is part of 2pc prepare, so ops are flagged pending outcome
|
||||||
|
*/
|
||||||
public void doAddMessage(TransactionContext c, long sequence, MessageId messageID, ActiveMQDestination destination, byte[] data,
|
public void doAddMessage(TransactionContext c, long sequence, MessageId messageID, ActiveMQDestination destination, byte[] data,
|
||||||
long expiration, byte priority) throws SQLException, IOException {
|
long expiration, byte priority, XATransactionId xid) throws SQLException, IOException {
|
||||||
PreparedStatement s = c.getAddMessageStatement();
|
PreparedStatement s = c.getAddMessageStatement();
|
||||||
cleanupExclusiveLock.readLock().lock();
|
cleanupExclusiveLock.readLock().lock();
|
||||||
try {
|
try {
|
||||||
|
@ -221,6 +224,13 @@ public class DefaultJDBCAdapter implements JDBCAdapter {
|
||||||
s.setLong(5, expiration);
|
s.setLong(5, expiration);
|
||||||
s.setLong(6, priority);
|
s.setLong(6, priority);
|
||||||
setBinaryData(s, 7, data);
|
setBinaryData(s, 7, data);
|
||||||
|
if (xid != null) {
|
||||||
|
byte[] xidVal = xid.getEncodedXidBytes();
|
||||||
|
xidVal[0] = '+';
|
||||||
|
setBinaryData(s, 8, xidVal);
|
||||||
|
} else {
|
||||||
|
setBinaryData(s, 8, null);
|
||||||
|
}
|
||||||
if (this.batchStatments) {
|
if (this.batchStatments) {
|
||||||
s.addBatch();
|
s.addBatch();
|
||||||
} else if (s.executeUpdate() != 1) {
|
} else if (s.executeUpdate() != 1) {
|
||||||
|
@ -326,17 +336,27 @@ public class DefaultJDBCAdapter implements JDBCAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doRemoveMessage(TransactionContext c, long seq) throws SQLException, IOException {
|
/**
|
||||||
|
* A non null xid indicated the op is part of 2pc prepare, so ops are flagged pending outcome
|
||||||
|
*/
|
||||||
|
public void doRemoveMessage(TransactionContext c, long seq, XATransactionId xid) throws SQLException, IOException {
|
||||||
PreparedStatement s = c.getRemovedMessageStatement();
|
PreparedStatement s = c.getRemovedMessageStatement();
|
||||||
cleanupExclusiveLock.readLock().lock();
|
cleanupExclusiveLock.readLock().lock();
|
||||||
try {
|
try {
|
||||||
if (s == null) {
|
if (s == null) {
|
||||||
s = c.getConnection().prepareStatement(this.statements.getRemoveMessageStatement());
|
s = c.getConnection().prepareStatement(xid == null ?
|
||||||
|
this.statements.getRemoveMessageStatement() : this.statements.getUpdateXidFlagStatement());
|
||||||
if (this.batchStatments) {
|
if (this.batchStatments) {
|
||||||
c.setRemovedMessageStatement(s);
|
c.setRemovedMessageStatement(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.setLong(1, seq);
|
if (xid == null) {
|
||||||
|
s.setLong(1, seq);
|
||||||
|
} else {
|
||||||
|
byte[] xidVal = xid.getEncodedXidBytes();
|
||||||
|
setBinaryData(s, 1, xidVal);
|
||||||
|
s.setLong(2, seq);
|
||||||
|
}
|
||||||
if (this.batchStatments) {
|
if (this.batchStatments) {
|
||||||
s.addBatch();
|
s.addBatch();
|
||||||
} else if (s.executeUpdate() != 1) {
|
} else if (s.executeUpdate() != 1) {
|
||||||
|
@ -406,26 +426,33 @@ public class DefaultJDBCAdapter implements JDBCAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doSetLastAckWithPriority(TransactionContext c, ActiveMQDestination destination, String clientId,
|
public void doSetLastAckWithPriority(TransactionContext c, ActiveMQDestination destination, XATransactionId xid, String clientId,
|
||||||
String subscriptionName, long seq, long prio) throws SQLException, IOException {
|
String subscriptionName, long seq, long priority) throws SQLException, IOException {
|
||||||
PreparedStatement s = c.getUpdateLastAckStatement();
|
PreparedStatement s = c.getUpdateLastAckStatement();
|
||||||
cleanupExclusiveLock.readLock().lock();
|
cleanupExclusiveLock.readLock().lock();
|
||||||
try {
|
try {
|
||||||
if (s == null) {
|
if (s == null) {
|
||||||
s = c.getConnection().prepareStatement(this.statements.getUpdateLastPriorityAckRowOfDurableSubStatement());
|
s = c.getConnection().prepareStatement(xid == null ?
|
||||||
|
this.statements.getUpdateDurableLastAckWithPriorityStatement() :
|
||||||
|
this.statements.getUpdateDurableLastAckWithPriorityInTxStatement());
|
||||||
if (this.batchStatments) {
|
if (this.batchStatments) {
|
||||||
c.setUpdateLastAckStatement(s);
|
c.setUpdateLastAckStatement(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.setLong(1, seq);
|
if (xid != null) {
|
||||||
|
byte[] xidVal = encodeXid(xid, seq, priority);
|
||||||
|
setBinaryData(s, 1, xidVal);
|
||||||
|
} else {
|
||||||
|
s.setLong(1, seq);
|
||||||
|
}
|
||||||
s.setString(2, destination.getQualifiedName());
|
s.setString(2, destination.getQualifiedName());
|
||||||
s.setString(3, clientId);
|
s.setString(3, clientId);
|
||||||
s.setString(4, subscriptionName);
|
s.setString(4, subscriptionName);
|
||||||
s.setLong(5, prio);
|
s.setLong(5, priority);
|
||||||
if (this.batchStatments) {
|
if (this.batchStatments) {
|
||||||
s.addBatch();
|
s.addBatch();
|
||||||
} else if (s.executeUpdate() != 1) {
|
} else if (s.executeUpdate() != 1) {
|
||||||
throw new SQLException("Failed update last ack with priority: " + prio + ", for sub: " + subscriptionName);
|
throw new SQLException("Failed update last ack with priority: " + priority + ", for sub: " + subscriptionName);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
cleanupExclusiveLock.readLock().unlock();
|
cleanupExclusiveLock.readLock().unlock();
|
||||||
|
@ -436,18 +463,25 @@ public class DefaultJDBCAdapter implements JDBCAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void doSetLastAck(TransactionContext c, ActiveMQDestination destination, String clientId,
|
public void doSetLastAck(TransactionContext c, ActiveMQDestination destination, XATransactionId xid, String clientId,
|
||||||
String subscriptionName, long seq, long priority) throws SQLException, IOException {
|
String subscriptionName, long seq, long priority) throws SQLException, IOException {
|
||||||
PreparedStatement s = c.getUpdateLastAckStatement();
|
PreparedStatement s = c.getUpdateLastAckStatement();
|
||||||
cleanupExclusiveLock.readLock().lock();
|
cleanupExclusiveLock.readLock().lock();
|
||||||
try {
|
try {
|
||||||
if (s == null) {
|
if (s == null) {
|
||||||
s = c.getConnection().prepareStatement(this.statements.getUpdateDurableLastAckStatement());
|
s = c.getConnection().prepareStatement(xid == null ?
|
||||||
|
this.statements.getUpdateDurableLastAckStatement() :
|
||||||
|
this.statements.getUpdateDurableLastAckInTxStatement());
|
||||||
if (this.batchStatments) {
|
if (this.batchStatments) {
|
||||||
c.setUpdateLastAckStatement(s);
|
c.setUpdateLastAckStatement(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.setLong(1, seq);
|
if (xid != null) {
|
||||||
|
byte[] xidVal = encodeXid(xid, seq, priority);
|
||||||
|
setBinaryData(s, 1, xidVal);
|
||||||
|
} else {
|
||||||
|
s.setLong(1, seq);
|
||||||
|
}
|
||||||
s.setString(2, destination.getQualifiedName());
|
s.setString(2, destination.getQualifiedName());
|
||||||
s.setString(3, clientId);
|
s.setString(3, clientId);
|
||||||
s.setString(4, subscriptionName);
|
s.setString(4, subscriptionName);
|
||||||
|
@ -466,6 +500,35 @@ public class DefaultJDBCAdapter implements JDBCAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private byte[] encodeXid(XATransactionId xid, long seq, long priority) {
|
||||||
|
byte[] xidVal = xid.getEncodedXidBytes();
|
||||||
|
// encode the update
|
||||||
|
DataByteArrayOutputStream outputStream = xid.getOutputStream();
|
||||||
|
outputStream.position(1);
|
||||||
|
outputStream.writeLong(seq);
|
||||||
|
outputStream.writeByte(Long.valueOf(priority).byteValue());
|
||||||
|
return xidVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doClearLastAck(TransactionContext c, ActiveMQDestination destination, byte priority, String clientId, String subName) throws SQLException, IOException {
|
||||||
|
PreparedStatement s = null;
|
||||||
|
cleanupExclusiveLock.readLock().lock();
|
||||||
|
try {
|
||||||
|
s = c.getConnection().prepareStatement(this.statements.getClearDurableLastAckInTxStatement());
|
||||||
|
s.setString(1, destination.getQualifiedName());
|
||||||
|
s.setString(2, clientId);
|
||||||
|
s.setString(3, subName);
|
||||||
|
s.setLong(4, priority);
|
||||||
|
if (s.executeUpdate() != 1) {
|
||||||
|
throw new IOException("Could not remove prepared transaction state from message ack for: " + clientId + ":" + subName);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
cleanupExclusiveLock.readLock().unlock();
|
||||||
|
close(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void doRecoverSubscription(TransactionContext c, ActiveMQDestination destination, String clientId,
|
public void doRecoverSubscription(TransactionContext c, ActiveMQDestination destination, String clientId,
|
||||||
String subscriptionName, JDBCMessageRecoveryListener listener) throws Exception {
|
String subscriptionName, JDBCMessageRecoveryListener listener) throws Exception {
|
||||||
// dumpTables(c,
|
// dumpTables(c,
|
||||||
|
@ -879,30 +942,38 @@ public class DefaultJDBCAdapter implements JDBCAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* @param c
|
public void doRecoverPreparedOps(TransactionContext c, JdbcMemoryTransactionStore jdbcMemoryTransactionStore) throws SQLException, IOException {
|
||||||
* @param destination
|
|
||||||
* @param clientId
|
|
||||||
* @param subscriberName
|
|
||||||
* @return
|
|
||||||
* @throws SQLException
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public byte[] doGetNextDurableSubscriberMessageStatement(TransactionContext c, ActiveMQDestination destination,
|
|
||||||
String clientId, String subscriberName) throws SQLException, IOException {
|
|
||||||
PreparedStatement s = null;
|
PreparedStatement s = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
cleanupExclusiveLock.readLock().lock();
|
cleanupExclusiveLock.readLock().lock();
|
||||||
try {
|
try {
|
||||||
s = c.getConnection().prepareStatement(this.statements.getNextDurableSubscriberMessageStatement());
|
s = c.getConnection().prepareStatement(this.statements.getFindOpsPendingOutcomeStatement());
|
||||||
s.setString(1, destination.getQualifiedName());
|
|
||||||
s.setString(2, clientId);
|
|
||||||
s.setString(3, subscriberName);
|
|
||||||
rs = s.executeQuery();
|
rs = s.executeQuery();
|
||||||
if (!rs.next()) {
|
while (rs.next()) {
|
||||||
return null;
|
long id = rs.getLong(1);
|
||||||
|
byte[] encodedXid = getBinaryData(rs, 2);
|
||||||
|
if (encodedXid[0] == '+') {
|
||||||
|
jdbcMemoryTransactionStore.recoverAdd(id, getBinaryData(rs, 3));
|
||||||
|
} else {
|
||||||
|
jdbcMemoryTransactionStore.recoverAck(id, encodedXid, getBinaryData(rs, 3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(rs);
|
||||||
|
close(s);
|
||||||
|
|
||||||
|
s = c.getConnection().prepareStatement(this.statements.getFindAcksPendingOutcomeStatement());
|
||||||
|
rs = s.executeQuery();
|
||||||
|
while (rs.next()) {
|
||||||
|
byte[] encodedXid = getBinaryData(rs, 1);
|
||||||
|
String destination = rs.getString(2);
|
||||||
|
String subName = rs.getString(3);
|
||||||
|
String subId = rs.getString(4);
|
||||||
|
jdbcMemoryTransactionStore.recoverLastAck(encodedXid,
|
||||||
|
ActiveMQDestination.createDestination(destination, ActiveMQDestination.TOPIC_TYPE),
|
||||||
|
subName, subId);
|
||||||
}
|
}
|
||||||
return getBinaryData(rs, 1);
|
|
||||||
} finally {
|
} finally {
|
||||||
close(rs);
|
close(rs);
|
||||||
cleanupExclusiveLock.readLock().unlock();
|
cleanupExclusiveLock.readLock().unlock();
|
||||||
|
@ -910,6 +981,23 @@ public class DefaultJDBCAdapter implements JDBCAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doCommitAddOp(TransactionContext c, long sequence) throws SQLException, IOException {
|
||||||
|
PreparedStatement s = null;
|
||||||
|
cleanupExclusiveLock.readLock().lock();
|
||||||
|
try {
|
||||||
|
s = c.getConnection().prepareStatement(this.statements.getClearXidFlagStatement());
|
||||||
|
s.setLong(1, sequence);
|
||||||
|
if (s.executeUpdate() != 1) {
|
||||||
|
throw new IOException("Could not remove prepared transaction state from message add for sequenceId: " + sequence);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
cleanupExclusiveLock.readLock().unlock();
|
||||||
|
close(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public int doGetMessageCount(TransactionContext c, ActiveMQDestination destination) throws SQLException,
|
public int doGetMessageCount(TransactionContext c, ActiveMQDestination destination) throws SQLException,
|
||||||
IOException {
|
IOException {
|
||||||
PreparedStatement s = null;
|
PreparedStatement s = null;
|
||||||
|
@ -978,7 +1066,28 @@ public class DefaultJDBCAdapter implements JDBCAdapter {
|
||||||
close(s);
|
close(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long doGetLastProducerSequenceId(TransactionContext c, ProducerId id)
|
||||||
|
throws SQLException, IOException {
|
||||||
|
PreparedStatement s = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
cleanupExclusiveLock.readLock().lock();
|
||||||
|
try {
|
||||||
|
s = c.getConnection().prepareStatement(this.statements.getLastProducerSequenceIdStatement());
|
||||||
|
s.setString(1, id.toString());
|
||||||
|
rs = s.executeQuery();
|
||||||
|
long seq = -1;
|
||||||
|
if (rs.next()) {
|
||||||
|
seq = rs.getLong(1);
|
||||||
|
}
|
||||||
|
return seq;
|
||||||
|
} finally {
|
||||||
|
cleanupExclusiveLock.readLock().unlock();
|
||||||
|
close(rs);
|
||||||
|
close(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* public void dumpTables(Connection c, String destinationName, String clientId, String
|
/* public void dumpTables(Connection c, String destinationName, String clientId, String
|
||||||
subscriptionName) throws SQLException {
|
subscriptionName) throws SQLException {
|
||||||
printQuery(c, "Select * from ACTIVEMQ_MSGS", System.out);
|
printQuery(c, "Select * from ACTIVEMQ_MSGS", System.out);
|
||||||
|
@ -1034,25 +1143,4 @@ public class DefaultJDBCAdapter implements JDBCAdapter {
|
||||||
}
|
}
|
||||||
} */
|
} */
|
||||||
|
|
||||||
public long doGetLastProducerSequenceId(TransactionContext c, ProducerId id)
|
|
||||||
throws SQLException, IOException {
|
|
||||||
PreparedStatement s = null;
|
|
||||||
ResultSet rs = null;
|
|
||||||
cleanupExclusiveLock.readLock().lock();
|
|
||||||
try {
|
|
||||||
s = c.getConnection().prepareStatement(this.statements.getLastProducerSequenceIdStatement());
|
|
||||||
s.setString(1, id.toString());
|
|
||||||
rs = s.executeQuery();
|
|
||||||
long seq = -1;
|
|
||||||
if (rs.next()) {
|
|
||||||
seq = rs.getLong(1);
|
|
||||||
}
|
|
||||||
return seq;
|
|
||||||
} finally {
|
|
||||||
cleanupExclusiveLock.readLock().unlock();
|
|
||||||
close(rs);
|
|
||||||
close(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,15 +48,6 @@ public class OracleBlobJDBCAdapter extends BlobJDBCAdapter {
|
||||||
statements.setLongDataType("NUMBER");
|
statements.setLongDataType("NUMBER");
|
||||||
statements.setSequenceDataType("NUMBER");
|
statements.setSequenceDataType("NUMBER");
|
||||||
|
|
||||||
String addMessageStatement = "INSERT INTO "
|
|
||||||
+ statements.getFullMessageTableName()
|
|
||||||
+ "(ID, MSGID_PROD, MSGID_SEQ, CONTAINER, EXPIRATION, PRIORITY, MSG) VALUES (?, ?, ?, ?, ?, ?, empty_blob())";
|
|
||||||
statements.setAddMessageStatement(addMessageStatement);
|
|
||||||
|
|
||||||
String findMessageByIdStatement = "SELECT MSG FROM " +
|
|
||||||
statements.getFullMessageTableName() + " WHERE ID=? FOR UPDATE";
|
|
||||||
statements.setFindMessageByIdStatement(findMessageByIdStatement);
|
|
||||||
|
|
||||||
super.setStatements(statements);
|
super.setStatements(statements);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.apache.activemq.store.ProxyTopicMessageStore;
|
||||||
import org.apache.activemq.store.TopicMessageStore;
|
import org.apache.activemq.store.TopicMessageStore;
|
||||||
import org.apache.activemq.store.TransactionRecoveryListener;
|
import org.apache.activemq.store.TransactionRecoveryListener;
|
||||||
import org.apache.activemq.store.TransactionStore;
|
import org.apache.activemq.store.TransactionStore;
|
||||||
|
import org.apache.activemq.store.jdbc.JDBCMessageStore;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -45,16 +46,16 @@ import java.util.concurrent.Future;
|
||||||
*/
|
*/
|
||||||
public class MemoryTransactionStore implements TransactionStore {
|
public class MemoryTransactionStore implements TransactionStore {
|
||||||
|
|
||||||
ConcurrentHashMap<Object, Tx> inflightTransactions = new ConcurrentHashMap<Object, Tx>();
|
protected ConcurrentHashMap<Object, Tx> inflightTransactions = new ConcurrentHashMap<Object, Tx>();
|
||||||
ConcurrentHashMap<TransactionId, Tx> preparedTransactions = new ConcurrentHashMap<TransactionId, Tx>();
|
protected ConcurrentHashMap<TransactionId, Tx> preparedTransactions = new ConcurrentHashMap<TransactionId, Tx>();
|
||||||
final PersistenceAdapter persistenceAdapter;
|
protected final PersistenceAdapter persistenceAdapter;
|
||||||
|
|
||||||
private boolean doingRecover;
|
private boolean doingRecover;
|
||||||
|
|
||||||
public class Tx {
|
public class Tx {
|
||||||
private final ArrayList<AddMessageCommand> messages = new ArrayList<AddMessageCommand>();
|
public ArrayList<AddMessageCommand> messages = new ArrayList<AddMessageCommand>();
|
||||||
|
|
||||||
private final ArrayList<RemoveMessageCommand> acks = new ArrayList<RemoveMessageCommand>();
|
public final ArrayList<RemoveMessageCommand> acks = new ArrayList<RemoveMessageCommand>();
|
||||||
|
|
||||||
public void add(AddMessageCommand msg) {
|
public void add(AddMessageCommand msg) {
|
||||||
messages.add(msg);
|
messages.add(msg);
|
||||||
|
@ -114,6 +115,8 @@ public class MemoryTransactionStore implements TransactionStore {
|
||||||
public interface AddMessageCommand {
|
public interface AddMessageCommand {
|
||||||
Message getMessage();
|
Message getMessage();
|
||||||
|
|
||||||
|
MessageStore getMessageStore();
|
||||||
|
|
||||||
void run(ConnectionContext context) throws IOException;
|
void run(ConnectionContext context) throws IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,6 +124,8 @@ public class MemoryTransactionStore implements TransactionStore {
|
||||||
MessageAck getMessageAck();
|
MessageAck getMessageAck();
|
||||||
|
|
||||||
void run(ConnectionContext context) throws IOException;
|
void run(ConnectionContext context) throws IOException;
|
||||||
|
|
||||||
|
MessageStore getMessageStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MemoryTransactionStore(PersistenceAdapter persistenceAdapter) {
|
public MemoryTransactionStore(PersistenceAdapter persistenceAdapter) {
|
||||||
|
@ -164,7 +169,7 @@ public class MemoryTransactionStore implements TransactionStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
public TopicMessageStore proxy(TopicMessageStore messageStore) {
|
public TopicMessageStore proxy(TopicMessageStore messageStore) {
|
||||||
return new ProxyTopicMessageStore(messageStore) {
|
ProxyTopicMessageStore proxyTopicMessageStore = new ProxyTopicMessageStore(messageStore) {
|
||||||
@Override
|
@Override
|
||||||
public void addMessage(ConnectionContext context, final Message send) throws IOException {
|
public void addMessage(ConnectionContext context, final Message send) throws IOException {
|
||||||
MemoryTransactionStore.this.addMessage(getDelegate(), send);
|
MemoryTransactionStore.this.addMessage(getDelegate(), send);
|
||||||
|
@ -204,12 +209,17 @@ public class MemoryTransactionStore implements TransactionStore {
|
||||||
subscriptionName, messageId, ack);
|
subscriptionName, messageId, ack);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
onProxyTopicStore(proxyTopicMessageStore);
|
||||||
|
return proxyTopicMessageStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onProxyTopicStore(ProxyTopicMessageStore proxyTopicMessageStore) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.apache.activemq.store.TransactionStore#prepare(TransactionId)
|
* @see org.apache.activemq.store.TransactionStore#prepare(TransactionId)
|
||||||
*/
|
*/
|
||||||
public void prepare(TransactionId txid) {
|
public void prepare(TransactionId txid) throws IOException {
|
||||||
Tx tx = inflightTransactions.remove(txid);
|
Tx tx = inflightTransactions.remove(txid);
|
||||||
if (tx == null) {
|
if (tx == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -226,6 +236,15 @@ public class MemoryTransactionStore implements TransactionStore {
|
||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Tx getPreparedTx(TransactionId txid) {
|
||||||
|
Tx tx = preparedTransactions.get(txid);
|
||||||
|
if (tx == null) {
|
||||||
|
tx = new Tx();
|
||||||
|
preparedTransactions.put(txid, tx);
|
||||||
|
}
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
|
||||||
public void commit(TransactionId txid, boolean wasPrepared, Runnable preCommit,Runnable postCommit) throws IOException {
|
public void commit(TransactionId txid, boolean wasPrepared, Runnable preCommit,Runnable postCommit) throws IOException {
|
||||||
if (preCommit != null) {
|
if (preCommit != null) {
|
||||||
preCommit.run();
|
preCommit.run();
|
||||||
|
@ -248,7 +267,7 @@ public class MemoryTransactionStore implements TransactionStore {
|
||||||
/**
|
/**
|
||||||
* @see org.apache.activemq.store.TransactionStore#rollback(TransactionId)
|
* @see org.apache.activemq.store.TransactionStore#rollback(TransactionId)
|
||||||
*/
|
*/
|
||||||
public void rollback(TransactionId txid) {
|
public void rollback(TransactionId txid) throws IOException {
|
||||||
preparedTransactions.remove(txid);
|
preparedTransactions.remove(txid);
|
||||||
inflightTransactions.remove(txid);
|
inflightTransactions.remove(txid);
|
||||||
}
|
}
|
||||||
|
@ -268,12 +287,16 @@ public class MemoryTransactionStore implements TransactionStore {
|
||||||
Object txid = iter.next();
|
Object txid = iter.next();
|
||||||
Tx tx = preparedTransactions.get(txid);
|
Tx tx = preparedTransactions.get(txid);
|
||||||
listener.recover((XATransactionId)txid, tx.getMessages(), tx.getAcks());
|
listener.recover((XATransactionId)txid, tx.getMessages(), tx.getAcks());
|
||||||
|
onRecovered(tx);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.doingRecover = false;
|
this.doingRecover = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void onRecovered(Tx tx) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param message
|
* @param message
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
|
@ -291,6 +314,11 @@ public class MemoryTransactionStore implements TransactionStore {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageStore getMessageStore() {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
public void run(ConnectionContext ctx) throws IOException {
|
public void run(ConnectionContext ctx) throws IOException {
|
||||||
destination.addMessage(ctx, message);
|
destination.addMessage(ctx, message);
|
||||||
}
|
}
|
||||||
|
@ -320,13 +348,18 @@ public class MemoryTransactionStore implements TransactionStore {
|
||||||
public void run(ConnectionContext ctx) throws IOException {
|
public void run(ConnectionContext ctx) throws IOException {
|
||||||
destination.removeMessage(ctx, ack);
|
destination.removeMessage(ctx, ack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageStore getMessageStore() {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
destination.removeMessage(null, ack);
|
destination.removeMessage(null, ack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final void acknowledge(final TopicMessageStore destination, final String clientId, final String subscriptionName,
|
public void acknowledge(final TopicMessageStore destination, final String clientId, final String subscriptionName,
|
||||||
final MessageId messageId, final MessageAck ack) throws IOException {
|
final MessageId messageId, final MessageAck ack) throws IOException {
|
||||||
if (doingRecover) {
|
if (doingRecover) {
|
||||||
return;
|
return;
|
||||||
|
@ -342,6 +375,11 @@ public class MemoryTransactionStore implements TransactionStore {
|
||||||
public void run(ConnectionContext ctx) throws IOException {
|
public void run(ConnectionContext ctx) throws IOException {
|
||||||
destination.acknowledge(ctx, clientId, subscriptionName, messageId, ack);
|
destination.acknowledge(ctx, clientId, subscriptionName, messageId, ack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageStore getMessageStore() {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
destination.acknowledge(null, clientId, subscriptionName, messageId, ack);
|
destination.acknowledge(null, clientId, subscriptionName, messageId, ack);
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
* 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.broker;
|
||||||
|
|
||||||
|
import junit.framework.Test;
|
||||||
|
import org.apache.activemq.command.ActiveMQDestination;
|
||||||
|
import org.apache.activemq.command.ActiveMQQueue;
|
||||||
|
import org.apache.activemq.store.jdbc.JDBCPersistenceAdapter;
|
||||||
|
import org.apache.derby.jdbc.EmbeddedDataSource;
|
||||||
|
import org.apache.derby.jdbc.EmbeddedXADataSource;
|
||||||
|
|
||||||
|
public class JdbcXARecoveryBrokerTest extends XARecoveryBrokerTest {
|
||||||
|
|
||||||
|
EmbeddedXADataSource dataSource;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
dataSource = new EmbeddedXADataSource();
|
||||||
|
dataSource.setDatabaseName("derbyDb");
|
||||||
|
dataSource.setCreateDatabase("create");
|
||||||
|
super.setUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void tearDown() throws Exception {
|
||||||
|
super.tearDown();
|
||||||
|
stopDerby();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureBroker(BrokerService broker) throws Exception {
|
||||||
|
super.configureBroker(broker);
|
||||||
|
|
||||||
|
JDBCPersistenceAdapter jdbc = new JDBCPersistenceAdapter();
|
||||||
|
jdbc.setDataSource(dataSource);
|
||||||
|
broker.setPersistenceAdapter(jdbc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void restartBroker() throws Exception {
|
||||||
|
broker.stop();
|
||||||
|
stopDerby();
|
||||||
|
dataSource = new EmbeddedXADataSource();
|
||||||
|
dataSource.setDatabaseName("derbyDb");
|
||||||
|
dataSource.setCreateDatabase("create");
|
||||||
|
|
||||||
|
broker = createRestartedBroker();
|
||||||
|
broker.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopDerby() {
|
||||||
|
LOG.info("STOPPING DB!@!!!!");
|
||||||
|
final EmbeddedDataSource ds = dataSource;
|
||||||
|
try {
|
||||||
|
ds.setShutdownDatabase("shutdown");
|
||||||
|
ds.getConnection();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Test suite() {
|
||||||
|
return suite(JdbcXARecoveryBrokerTest.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
junit.textui.TestRunner.run(suite());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ActiveMQDestination createDestination() {
|
||||||
|
return new ActiveMQQueue("test,special");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,9 +23,13 @@ import javax.management.MalformedObjectNameException;
|
||||||
import javax.management.ObjectName;
|
import javax.management.ObjectName;
|
||||||
import junit.framework.Test;
|
import junit.framework.Test;
|
||||||
|
|
||||||
|
import org.apache.activemq.broker.jmx.DestinationView;
|
||||||
|
import org.apache.activemq.broker.jmx.DestinationViewMBean;
|
||||||
import org.apache.activemq.broker.jmx.RecoveredXATransactionViewMBean;
|
import org.apache.activemq.broker.jmx.RecoveredXATransactionViewMBean;
|
||||||
|
import org.apache.activemq.broker.region.policy.PolicyEntry;
|
||||||
import org.apache.activemq.command.ActiveMQDestination;
|
import org.apache.activemq.command.ActiveMQDestination;
|
||||||
import org.apache.activemq.command.ActiveMQQueue;
|
import org.apache.activemq.command.ActiveMQQueue;
|
||||||
|
import org.apache.activemq.command.ActiveMQTopic;
|
||||||
import org.apache.activemq.command.ConnectionInfo;
|
import org.apache.activemq.command.ConnectionInfo;
|
||||||
import org.apache.activemq.command.ConsumerInfo;
|
import org.apache.activemq.command.ConsumerInfo;
|
||||||
import org.apache.activemq.command.DataArrayResponse;
|
import org.apache.activemq.command.DataArrayResponse;
|
||||||
|
@ -37,6 +41,7 @@ import org.apache.activemq.command.SessionInfo;
|
||||||
import org.apache.activemq.command.TransactionId;
|
import org.apache.activemq.command.TransactionId;
|
||||||
import org.apache.activemq.command.TransactionInfo;
|
import org.apache.activemq.command.TransactionInfo;
|
||||||
import org.apache.activemq.command.XATransactionId;
|
import org.apache.activemq.command.XATransactionId;
|
||||||
|
import org.apache.activemq.store.jdbc.JDBCPersistenceAdapter;
|
||||||
import org.apache.activemq.util.JMXSupport;
|
import org.apache.activemq.util.JMXSupport;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -48,6 +53,8 @@ import org.slf4j.LoggerFactory;
|
||||||
*/
|
*/
|
||||||
public class XARecoveryBrokerTest extends BrokerRestartTestSupport {
|
public class XARecoveryBrokerTest extends BrokerRestartTestSupport {
|
||||||
protected static final Logger LOG = LoggerFactory.getLogger(XARecoveryBrokerTest.class);
|
protected static final Logger LOG = LoggerFactory.getLogger(XARecoveryBrokerTest.class);
|
||||||
|
public boolean prioritySupport = false;
|
||||||
|
|
||||||
public void testPreparedJmxView() throws Exception {
|
public void testPreparedJmxView() throws Exception {
|
||||||
|
|
||||||
ActiveMQDestination destination = createDestination();
|
ActiveMQDestination destination = createDestination();
|
||||||
|
@ -96,6 +103,10 @@ public class XARecoveryBrokerTest extends BrokerRestartTestSupport {
|
||||||
dar = (DataArrayResponse)response;
|
dar = (DataArrayResponse)response;
|
||||||
assertEquals(4, dar.getData().length);
|
assertEquals(4, dar.getData().length);
|
||||||
|
|
||||||
|
// validate destination depth via jmx
|
||||||
|
DestinationViewMBean destinationView = getProxyToDestination(destinationList(destination)[0]);
|
||||||
|
assertEquals("enqueue count does not see prepared", 0, destinationView.getQueueSize());
|
||||||
|
|
||||||
TransactionId first = (TransactionId)dar.getData()[0];
|
TransactionId first = (TransactionId)dar.getData()[0];
|
||||||
// via jmx, force outcome
|
// via jmx, force outcome
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
|
@ -131,6 +142,16 @@ public class XARecoveryBrokerTest extends BrokerRestartTestSupport {
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DestinationViewMBean getProxyToDestination(ActiveMQDestination destination) throws MalformedObjectNameException, JMSException {
|
||||||
|
|
||||||
|
ObjectName objectName = new ObjectName("org.apache.activemq:Type=" + (destination.isQueue() ? "Queue" : "Topic") + ",Destination=" +
|
||||||
|
JMXSupport.encodeObjectNamePart(destination.getPhysicalName()) + ",BrokerName=localhost");
|
||||||
|
DestinationViewMBean proxy = (DestinationViewMBean) broker.getManagementContext().newProxyInstance(objectName,
|
||||||
|
DestinationViewMBean.class, true);
|
||||||
|
return proxy;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public void testPreparedTransactionRecoveredOnRestart() throws Exception {
|
public void testPreparedTransactionRecoveredOnRestart() throws Exception {
|
||||||
|
|
||||||
ActiveMQDestination destination = createDestination();
|
ActiveMQDestination destination = createDestination();
|
||||||
|
@ -213,6 +234,94 @@ public class XARecoveryBrokerTest extends BrokerRestartTestSupport {
|
||||||
assertNoMessagesLeft(connection);
|
assertNoMessagesLeft(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testTopicPreparedTransactionRecoveredOnRestart() throws Exception {
|
||||||
|
ActiveMQDestination destination = new ActiveMQTopic("TryTopic");
|
||||||
|
|
||||||
|
StubConnection connection = createConnection();
|
||||||
|
ConnectionInfo connectionInfo = createConnectionInfo();
|
||||||
|
connectionInfo.setClientId("durable");
|
||||||
|
SessionInfo sessionInfo = createSessionInfo(connectionInfo);
|
||||||
|
ProducerInfo producerInfo = createProducerInfo(sessionInfo);
|
||||||
|
connection.send(connectionInfo);
|
||||||
|
connection.send(sessionInfo);
|
||||||
|
connection.send(producerInfo);
|
||||||
|
ConsumerInfo consumerInfo = createConsumerInfo(sessionInfo, destination);
|
||||||
|
consumerInfo.setSubscriptionName("durable");
|
||||||
|
connection.send(consumerInfo);
|
||||||
|
|
||||||
|
// Prepare 4 message sends.
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
// Begin the transaction.
|
||||||
|
XATransactionId txid = createXATransaction(sessionInfo);
|
||||||
|
connection.send(createBeginTransaction(connectionInfo, txid));
|
||||||
|
|
||||||
|
Message message = createMessage(producerInfo, destination);
|
||||||
|
message.setPersistent(true);
|
||||||
|
message.setTransactionId(txid);
|
||||||
|
connection.send(message);
|
||||||
|
|
||||||
|
// Prepare
|
||||||
|
connection.send(createPrepareTransaction(connectionInfo, txid));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since prepared but not committed.. they should not get delivered.
|
||||||
|
assertNull(receiveMessage(connection));
|
||||||
|
assertNoMessagesLeft(connection);
|
||||||
|
connection.request(closeConnectionInfo(connectionInfo));
|
||||||
|
|
||||||
|
// restart the broker.
|
||||||
|
restartBroker();
|
||||||
|
|
||||||
|
// Setup the consumer and try receive the message.
|
||||||
|
connection = createConnection();
|
||||||
|
connectionInfo = createConnectionInfo();
|
||||||
|
connectionInfo.setClientId("durable");
|
||||||
|
|
||||||
|
sessionInfo = createSessionInfo(connectionInfo);
|
||||||
|
connection.send(connectionInfo);
|
||||||
|
connection.send(sessionInfo);
|
||||||
|
consumerInfo = createConsumerInfo(sessionInfo, destination);
|
||||||
|
consumerInfo.setSubscriptionName("durable");
|
||||||
|
connection.send(consumerInfo);
|
||||||
|
|
||||||
|
// Since prepared but not committed.. they should not get delivered.
|
||||||
|
assertNull(receiveMessage(connection));
|
||||||
|
assertNoMessagesLeft(connection);
|
||||||
|
|
||||||
|
Response response = connection.request(new TransactionInfo(connectionInfo.getConnectionId(), null, TransactionInfo.RECOVER));
|
||||||
|
assertNotNull(response);
|
||||||
|
DataArrayResponse dar = (DataArrayResponse) response;
|
||||||
|
assertEquals(4, dar.getData().length);
|
||||||
|
|
||||||
|
// ensure we can close a connection with prepared transactions
|
||||||
|
connection.request(closeConnectionInfo(connectionInfo));
|
||||||
|
|
||||||
|
// open again to deliver outcome
|
||||||
|
connection = createConnection();
|
||||||
|
connectionInfo = createConnectionInfo();
|
||||||
|
connectionInfo.setClientId("durable");
|
||||||
|
sessionInfo = createSessionInfo(connectionInfo);
|
||||||
|
connection.send(connectionInfo);
|
||||||
|
connection.send(sessionInfo);
|
||||||
|
consumerInfo = createConsumerInfo(sessionInfo, destination);
|
||||||
|
consumerInfo.setSubscriptionName("durable");
|
||||||
|
connection.send(consumerInfo);
|
||||||
|
|
||||||
|
// Commit the prepared transactions.
|
||||||
|
for (int i = 0; i < dar.getData().length; i++) {
|
||||||
|
connection.send(createCommitTransaction2Phase(connectionInfo, (TransactionId) dar.getData()[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should get the committed transactions.
|
||||||
|
for (int i = 0; i < expectedMessageCount(4, destination); i++) {
|
||||||
|
Message m = receiveMessage(connection, TimeUnit.SECONDS.toMillis(10));
|
||||||
|
assertNotNull(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNoMessagesLeft(connection);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public void testQueuePersistentCommitedMessagesNotLostOnRestart() throws Exception {
|
public void testQueuePersistentCommitedMessagesNotLostOnRestart() throws Exception {
|
||||||
|
|
||||||
ActiveMQDestination destination = createDestination();
|
ActiveMQDestination destination = createDestination();
|
||||||
|
@ -260,6 +369,55 @@ public class XARecoveryBrokerTest extends BrokerRestartTestSupport {
|
||||||
assertNoMessagesLeft(connection);
|
assertNoMessagesLeft(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testQueuePersistentCommited2PhaseMessagesNotLostOnRestart() throws Exception {
|
||||||
|
|
||||||
|
ActiveMQDestination destination = createDestination();
|
||||||
|
|
||||||
|
// Setup the producer and send the message.
|
||||||
|
StubConnection connection = createConnection();
|
||||||
|
ConnectionInfo connectionInfo = createConnectionInfo();
|
||||||
|
SessionInfo sessionInfo = createSessionInfo(connectionInfo);
|
||||||
|
ProducerInfo producerInfo = createProducerInfo(sessionInfo);
|
||||||
|
connection.send(connectionInfo);
|
||||||
|
connection.send(sessionInfo);
|
||||||
|
connection.send(producerInfo);
|
||||||
|
|
||||||
|
// Begin the transaction.
|
||||||
|
XATransactionId txid = createXATransaction(sessionInfo);
|
||||||
|
connection.send(createBeginTransaction(connectionInfo, txid));
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
Message message = createMessage(producerInfo, destination);
|
||||||
|
message.setPersistent(true);
|
||||||
|
message.setTransactionId(txid);
|
||||||
|
connection.send(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit 2 phase
|
||||||
|
connection.request(createPrepareTransaction(connectionInfo, txid));
|
||||||
|
connection.send(createCommitTransaction2Phase(connectionInfo, txid));
|
||||||
|
|
||||||
|
connection.request(closeConnectionInfo(connectionInfo));
|
||||||
|
// restart the broker.
|
||||||
|
restartBroker();
|
||||||
|
|
||||||
|
// Setup the consumer and receive the message.
|
||||||
|
connection = createConnection();
|
||||||
|
connectionInfo = createConnectionInfo();
|
||||||
|
sessionInfo = createSessionInfo(connectionInfo);
|
||||||
|
connection.send(connectionInfo);
|
||||||
|
connection.send(sessionInfo);
|
||||||
|
ConsumerInfo consumerInfo = createConsumerInfo(sessionInfo, destination);
|
||||||
|
connection.send(consumerInfo);
|
||||||
|
|
||||||
|
for (int i = 0; i < expectedMessageCount(4, destination); i++) {
|
||||||
|
Message m = receiveMessage(connection);
|
||||||
|
assertNotNull(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNoMessagesLeft(connection);
|
||||||
|
}
|
||||||
|
|
||||||
public void testQueuePersistentCommitedAcksNotLostOnRestart() throws Exception {
|
public void testQueuePersistentCommitedAcksNotLostOnRestart() throws Exception {
|
||||||
|
|
||||||
ActiveMQDestination destination = createDestination();
|
ActiveMQDestination destination = createDestination();
|
||||||
|
@ -396,6 +554,90 @@ public class XARecoveryBrokerTest extends BrokerRestartTestSupport {
|
||||||
assertEquals("there are no prepared tx", 0, dataArrayResponse.getData().length);
|
assertEquals("there are no prepared tx", 0, dataArrayResponse.getData().length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void initCombosForTestTopicPersistentPreparedAcksNotLostOnRestart() {
|
||||||
|
addCombinationValues("prioritySupport", new Boolean[]{Boolean.FALSE, Boolean.TRUE});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTopicPersistentPreparedAcksNotLostOnRestart() throws Exception {
|
||||||
|
// REVISIT for kahadb
|
||||||
|
if (! (broker.getPersistenceAdapter() instanceof JDBCPersistenceAdapter)) {
|
||||||
|
LOG.warn("only works on jdbc");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ActiveMQDestination destination = new ActiveMQTopic("TryTopic");
|
||||||
|
|
||||||
|
// Setup the producer and send the message.
|
||||||
|
StubConnection connection = createConnection();
|
||||||
|
ConnectionInfo connectionInfo = createConnectionInfo();
|
||||||
|
connectionInfo.setClientId("durable");
|
||||||
|
SessionInfo sessionInfo = createSessionInfo(connectionInfo);
|
||||||
|
ProducerInfo producerInfo = createProducerInfo(sessionInfo);
|
||||||
|
connection.send(connectionInfo);
|
||||||
|
connection.send(sessionInfo);
|
||||||
|
connection.send(producerInfo);
|
||||||
|
|
||||||
|
// setup durable subs
|
||||||
|
ConsumerInfo consumerInfo = createConsumerInfo(sessionInfo, destination);
|
||||||
|
consumerInfo.setSubscriptionName("durable");
|
||||||
|
connection.send(consumerInfo);
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
Message message = createMessage(producerInfo, destination);
|
||||||
|
message.setPersistent(true);
|
||||||
|
connection.send(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin the transaction.
|
||||||
|
XATransactionId txid = createXATransaction(sessionInfo);
|
||||||
|
connection.send(createBeginTransaction(connectionInfo, txid));
|
||||||
|
|
||||||
|
final int messageCount = expectedMessageCount(4, destination);
|
||||||
|
Message m = null;
|
||||||
|
for (int i = 0; i < messageCount; i++) {
|
||||||
|
m = receiveMessage(connection);
|
||||||
|
assertNotNull("unexpected null on: " + i, m);
|
||||||
|
}
|
||||||
|
|
||||||
|
// one ack with last received, mimic a beforeEnd synchronization
|
||||||
|
MessageAck ack = createAck(consumerInfo, m, messageCount, MessageAck.STANDARD_ACK_TYPE);
|
||||||
|
ack.setTransactionId(txid);
|
||||||
|
connection.send(ack);
|
||||||
|
|
||||||
|
connection.request(createPrepareTransaction(connectionInfo, txid));
|
||||||
|
|
||||||
|
// restart the broker.
|
||||||
|
restartBroker();
|
||||||
|
|
||||||
|
connection = createConnection();
|
||||||
|
connectionInfo = createConnectionInfo();
|
||||||
|
connectionInfo.setClientId("durable");
|
||||||
|
connection.send(connectionInfo);
|
||||||
|
|
||||||
|
// validate recovery
|
||||||
|
TransactionInfo recoverInfo = new TransactionInfo(connectionInfo.getConnectionId(), null, TransactionInfo.RECOVER);
|
||||||
|
DataArrayResponse dataArrayResponse = (DataArrayResponse)connection.request(recoverInfo);
|
||||||
|
|
||||||
|
assertEquals("there is a prepared tx", 1, dataArrayResponse.getData().length);
|
||||||
|
assertEquals("it matches", txid, dataArrayResponse.getData()[0]);
|
||||||
|
|
||||||
|
sessionInfo = createSessionInfo(connectionInfo);
|
||||||
|
connection.send(sessionInfo);
|
||||||
|
consumerInfo = createConsumerInfo(sessionInfo, destination);
|
||||||
|
consumerInfo.setSubscriptionName("durable");
|
||||||
|
connection.send(consumerInfo);
|
||||||
|
|
||||||
|
// no redelivery, exactly once semantics unless there is rollback
|
||||||
|
m = receiveMessage(connection);
|
||||||
|
assertNull(m);
|
||||||
|
assertNoMessagesLeft(connection);
|
||||||
|
|
||||||
|
connection.request(createCommitTransaction2Phase(connectionInfo, txid));
|
||||||
|
|
||||||
|
// validate recovery complete
|
||||||
|
dataArrayResponse = (DataArrayResponse)connection.request(recoverInfo);
|
||||||
|
assertEquals("there are no prepared tx", 0, dataArrayResponse.getData().length);
|
||||||
|
}
|
||||||
|
|
||||||
public void testQueuePersistentPreparedAcksAvailableAfterRestartAndRollback() throws Exception {
|
public void testQueuePersistentPreparedAcksAvailableAfterRestartAndRollback() throws Exception {
|
||||||
|
|
||||||
ActiveMQDestination destination = createDestination();
|
ActiveMQDestination destination = createDestination();
|
||||||
|
@ -409,7 +651,8 @@ public class XARecoveryBrokerTest extends BrokerRestartTestSupport {
|
||||||
connection.send(sessionInfo);
|
connection.send(sessionInfo);
|
||||||
connection.send(producerInfo);
|
connection.send(producerInfo);
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
int numMessages = 4;
|
||||||
|
for (int i = 0; i < numMessages; i++) {
|
||||||
Message message = createMessage(producerInfo, destination);
|
Message message = createMessage(producerInfo, destination);
|
||||||
message.setPersistent(true);
|
message.setPersistent(true);
|
||||||
connection.send(message);
|
connection.send(message);
|
||||||
|
@ -426,13 +669,13 @@ public class XARecoveryBrokerTest extends BrokerRestartTestSupport {
|
||||||
consumerInfo = createConsumerInfo(sessionInfo, dest);
|
consumerInfo = createConsumerInfo(sessionInfo, dest);
|
||||||
connection.send(consumerInfo);
|
connection.send(consumerInfo);
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < numMessages; i++) {
|
||||||
message = receiveMessage(connection);
|
message = receiveMessage(connection);
|
||||||
assertNotNull(message);
|
assertNotNull(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// one ack with last received, mimic a beforeEnd synchronization
|
// one ack with last received, mimic a beforeEnd synchronization
|
||||||
MessageAck ack = createAck(consumerInfo, message, 4, MessageAck.STANDARD_ACK_TYPE);
|
MessageAck ack = createAck(consumerInfo, message, numMessages, MessageAck.STANDARD_ACK_TYPE);
|
||||||
ack.setTransactionId(txid);
|
ack.setTransactionId(txid);
|
||||||
connection.send(ack);
|
connection.send(ack);
|
||||||
}
|
}
|
||||||
|
@ -466,7 +709,7 @@ public class XARecoveryBrokerTest extends BrokerRestartTestSupport {
|
||||||
// rollback so we get redelivery
|
// rollback so we get redelivery
|
||||||
connection.request(createRollbackTransaction(connectionInfo, txid));
|
connection.request(createRollbackTransaction(connectionInfo, txid));
|
||||||
|
|
||||||
// Begin new transaction for redelivery
|
LOG.info("new tx for redelivery");
|
||||||
txid = createXATransaction(sessionInfo);
|
txid = createXATransaction(sessionInfo);
|
||||||
connection.send(createBeginTransaction(connectionInfo, txid));
|
connection.send(createBeginTransaction(connectionInfo, txid));
|
||||||
|
|
||||||
|
@ -475,11 +718,11 @@ public class XARecoveryBrokerTest extends BrokerRestartTestSupport {
|
||||||
consumerInfo = createConsumerInfo(sessionInfo, dest);
|
consumerInfo = createConsumerInfo(sessionInfo, dest);
|
||||||
connection.send(consumerInfo);
|
connection.send(consumerInfo);
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < numMessages; i++) {
|
||||||
message = receiveMessage(connection);
|
message = receiveMessage(connection);
|
||||||
assertNotNull(message);
|
assertNotNull("unexpected null on:" + i, message);
|
||||||
}
|
}
|
||||||
MessageAck ack = createAck(consumerInfo, message, 4, MessageAck.STANDARD_ACK_TYPE);
|
MessageAck ack = createAck(consumerInfo, message, numMessages, MessageAck.STANDARD_ACK_TYPE);
|
||||||
ack.setTransactionId(txid);
|
ack.setTransactionId(txid);
|
||||||
connection.send(ack);
|
connection.send(ack);
|
||||||
}
|
}
|
||||||
|
@ -492,6 +735,180 @@ public class XARecoveryBrokerTest extends BrokerRestartTestSupport {
|
||||||
assertEquals("there are no prepared tx", 0, dataArrayResponse.getData().length);
|
assertEquals("there are no prepared tx", 0, dataArrayResponse.getData().length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void initCombosForTestTopicPersistentPreparedAcksAvailableAfterRestartAndRollback() {
|
||||||
|
addCombinationValues("prioritySupport", new Boolean[]{Boolean.FALSE, Boolean.TRUE});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTopicPersistentPreparedAcksAvailableAfterRestartAndRollback() throws Exception {
|
||||||
|
|
||||||
|
// REVISIT for kahadb
|
||||||
|
if (! (broker.getPersistenceAdapter() instanceof JDBCPersistenceAdapter)) {
|
||||||
|
LOG.warn("only works on jdbc");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveMQDestination destination = new ActiveMQTopic("TryTopic");
|
||||||
|
|
||||||
|
// Setup the producer and send the message.
|
||||||
|
StubConnection connection = createConnection();
|
||||||
|
ConnectionInfo connectionInfo = createConnectionInfo();
|
||||||
|
connectionInfo.setClientId("durable");
|
||||||
|
SessionInfo sessionInfo = createSessionInfo(connectionInfo);
|
||||||
|
ProducerInfo producerInfo = createProducerInfo(sessionInfo);
|
||||||
|
connection.send(connectionInfo);
|
||||||
|
connection.send(sessionInfo);
|
||||||
|
connection.send(producerInfo);
|
||||||
|
|
||||||
|
// setup durable subs
|
||||||
|
ConsumerInfo consumerInfo = createConsumerInfo(sessionInfo, destination);
|
||||||
|
consumerInfo.setSubscriptionName("durable");
|
||||||
|
connection.send(consumerInfo);
|
||||||
|
|
||||||
|
int numMessages = 4;
|
||||||
|
for (int i = 0; i < numMessages; i++) {
|
||||||
|
Message message = createMessage(producerInfo, destination);
|
||||||
|
message.setPersistent(true);
|
||||||
|
connection.send(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin the transaction.
|
||||||
|
XATransactionId txid = createXATransaction(sessionInfo);
|
||||||
|
connection.send(createBeginTransaction(connectionInfo, txid));
|
||||||
|
|
||||||
|
Message message = null;
|
||||||
|
for (int i = 0; i < numMessages; i++) {
|
||||||
|
message = receiveMessage(connection);
|
||||||
|
assertNotNull(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// one ack with last received, mimic a beforeEnd synchronization
|
||||||
|
MessageAck ack = createAck(consumerInfo, message, numMessages, MessageAck.STANDARD_ACK_TYPE);
|
||||||
|
ack.setTransactionId(txid);
|
||||||
|
connection.send(ack);
|
||||||
|
|
||||||
|
connection.request(createPrepareTransaction(connectionInfo, txid));
|
||||||
|
|
||||||
|
// restart the broker.
|
||||||
|
restartBroker();
|
||||||
|
|
||||||
|
connection = createConnection();
|
||||||
|
connectionInfo = createConnectionInfo();
|
||||||
|
connectionInfo.setClientId("durable");
|
||||||
|
connection.send(connectionInfo);
|
||||||
|
|
||||||
|
// validate recovery
|
||||||
|
TransactionInfo recoverInfo = new TransactionInfo(connectionInfo.getConnectionId(), null, TransactionInfo.RECOVER);
|
||||||
|
DataArrayResponse dataArrayResponse = (DataArrayResponse)connection.request(recoverInfo);
|
||||||
|
|
||||||
|
assertEquals("there is a prepared tx", 1, dataArrayResponse.getData().length);
|
||||||
|
assertEquals("it matches", txid, dataArrayResponse.getData()[0]);
|
||||||
|
|
||||||
|
sessionInfo = createSessionInfo(connectionInfo);
|
||||||
|
connection.send(sessionInfo);
|
||||||
|
consumerInfo = createConsumerInfo(sessionInfo, destination);
|
||||||
|
consumerInfo.setSubscriptionName("durable");
|
||||||
|
connection.send(consumerInfo);
|
||||||
|
|
||||||
|
// no redelivery, exactly once semantics while prepared
|
||||||
|
message = receiveMessage(connection);
|
||||||
|
assertNull(message);
|
||||||
|
assertNoMessagesLeft(connection);
|
||||||
|
|
||||||
|
// rollback so we get redelivery
|
||||||
|
connection.request(createRollbackTransaction(connectionInfo, txid));
|
||||||
|
|
||||||
|
LOG.info("new tx for redelivery");
|
||||||
|
txid = createXATransaction(sessionInfo);
|
||||||
|
connection.send(createBeginTransaction(connectionInfo, txid));
|
||||||
|
|
||||||
|
for (int i = 0; i < numMessages; i++) {
|
||||||
|
message = receiveMessage(connection);
|
||||||
|
assertNotNull("unexpected null on:" + i, message);
|
||||||
|
}
|
||||||
|
ack = createAck(consumerInfo, message, numMessages, MessageAck.STANDARD_ACK_TYPE);
|
||||||
|
ack.setTransactionId(txid);
|
||||||
|
connection.send(ack);
|
||||||
|
|
||||||
|
// Commit
|
||||||
|
connection.request(createCommitTransaction1Phase(connectionInfo, txid));
|
||||||
|
|
||||||
|
// validate recovery complete
|
||||||
|
dataArrayResponse = (DataArrayResponse)connection.request(recoverInfo);
|
||||||
|
assertEquals("there are no prepared tx", 0, dataArrayResponse.getData().length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initCombosForTestTopicPersistentPreparedAcksAvailableAfterRollback() {
|
||||||
|
addCombinationValues("prioritySupport", new Boolean[]{Boolean.FALSE, Boolean.TRUE});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTopicPersistentPreparedAcksAvailableAfterRollback() throws Exception {
|
||||||
|
|
||||||
|
// REVISIT for kahadb
|
||||||
|
if (! (broker.getPersistenceAdapter() instanceof JDBCPersistenceAdapter)) {
|
||||||
|
LOG.warn("only works on jdbc");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveMQDestination destination = new ActiveMQTopic("TryTopic");
|
||||||
|
|
||||||
|
// Setup the producer and send the message.
|
||||||
|
StubConnection connection = createConnection();
|
||||||
|
ConnectionInfo connectionInfo = createConnectionInfo();
|
||||||
|
connectionInfo.setClientId("durable");
|
||||||
|
SessionInfo sessionInfo = createSessionInfo(connectionInfo);
|
||||||
|
ProducerInfo producerInfo = createProducerInfo(sessionInfo);
|
||||||
|
connection.send(connectionInfo);
|
||||||
|
connection.send(sessionInfo);
|
||||||
|
connection.send(producerInfo);
|
||||||
|
|
||||||
|
// setup durable subs
|
||||||
|
ConsumerInfo consumerInfo = createConsumerInfo(sessionInfo, destination);
|
||||||
|
consumerInfo.setSubscriptionName("durable");
|
||||||
|
connection.send(consumerInfo);
|
||||||
|
|
||||||
|
int numMessages = 4;
|
||||||
|
for (int i = 0; i < numMessages; i++) {
|
||||||
|
Message message = createMessage(producerInfo, destination);
|
||||||
|
message.setPersistent(true);
|
||||||
|
connection.send(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin the transaction.
|
||||||
|
XATransactionId txid = createXATransaction(sessionInfo);
|
||||||
|
connection.send(createBeginTransaction(connectionInfo, txid));
|
||||||
|
|
||||||
|
Message message = null;
|
||||||
|
for (int i = 0; i < numMessages; i++) {
|
||||||
|
message = receiveMessage(connection);
|
||||||
|
assertNotNull(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// one ack with last received, mimic a beforeEnd synchronization
|
||||||
|
MessageAck ack = createAck(consumerInfo, message, numMessages, MessageAck.STANDARD_ACK_TYPE);
|
||||||
|
ack.setTransactionId(txid);
|
||||||
|
connection.send(ack);
|
||||||
|
|
||||||
|
connection.request(createPrepareTransaction(connectionInfo, txid));
|
||||||
|
|
||||||
|
// rollback so we get redelivery
|
||||||
|
connection.request(createRollbackTransaction(connectionInfo, txid));
|
||||||
|
|
||||||
|
LOG.info("new tx for redelivery");
|
||||||
|
txid = createXATransaction(sessionInfo);
|
||||||
|
connection.send(createBeginTransaction(connectionInfo, txid));
|
||||||
|
|
||||||
|
for (int i = 0; i < numMessages; i++) {
|
||||||
|
message = receiveMessage(connection);
|
||||||
|
assertNotNull("unexpected null on:" + i, message);
|
||||||
|
}
|
||||||
|
ack = createAck(consumerInfo, message, numMessages, MessageAck.STANDARD_ACK_TYPE);
|
||||||
|
ack.setTransactionId(txid);
|
||||||
|
connection.send(ack);
|
||||||
|
|
||||||
|
// Commit
|
||||||
|
connection.request(createCommitTransaction1Phase(connectionInfo, txid));
|
||||||
|
}
|
||||||
|
|
||||||
private ActiveMQDestination[] destinationList(ActiveMQDestination dest) {
|
private ActiveMQDestination[] destinationList(ActiveMQDestination dest) {
|
||||||
return dest.isComposite() ? dest.getCompositeDestinations() : new ActiveMQDestination[]{dest};
|
return dest.isComposite() ? dest.getCompositeDestinations() : new ActiveMQDestination[]{dest};
|
||||||
}
|
}
|
||||||
|
@ -564,6 +981,13 @@ public class XARecoveryBrokerTest extends BrokerRestartTestSupport {
|
||||||
assertNoMessagesLeft(connection);
|
assertNoMessagesLeft(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PolicyEntry getDefaultPolicy() {
|
||||||
|
PolicyEntry policyEntry = super.getDefaultPolicy();
|
||||||
|
policyEntry.setPrioritizedMessages(prioritySupport);
|
||||||
|
return policyEntry;
|
||||||
|
}
|
||||||
|
|
||||||
public static Test suite() {
|
public static Test suite() {
|
||||||
return suite(XARecoveryBrokerTest.class);
|
return suite(XARecoveryBrokerTest.class);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue