
org.jboss.ejb.client.EJBClientManagedTransactionContext Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ejb.client;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.TransactionSynchronizationRegistry;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/**
* A transaction context for environments with a {@link TransactionManager}.
*
* @author David M. Lloyd
*/
public final class EJBClientManagedTransactionContext extends EJBClientTransactionContext {
private final TransactionManager transactionManager;
private final TransactionSynchronizationRegistry synchronizationRegistry;
private static final AtomicReferenceFieldUpdater stateUpdater = AtomicReferenceFieldUpdater.newUpdater(ResourceImpl.class, State.class, "state");
EJBClientManagedTransactionContext(final TransactionManager transactionManager, final TransactionSynchronizationRegistry synchronizationRegistry) {
this.transactionManager = transactionManager;
this.synchronizationRegistry = synchronizationRegistry;
}
static class NodeKey {
private final String nodeName;
NodeKey(final String nodeName) {
this.nodeName = nodeName;
}
public boolean equals(final Object obj) {
return obj instanceof NodeKey && equals((NodeKey) obj);
}
boolean equals(final NodeKey obj) {
return obj != null && nodeName.equals(obj.nodeName);
}
public int hashCode() {
return nodeName.hashCode();
}
}
protected TransactionID getAssociatedTransactionID(final EJBClientInvocationContext invocationContext) throws Exception {
final Transaction transaction = transactionManager.getTransaction();
if (transaction == null) {
// no txn
return null;
}
final int txStatus = transaction.getStatus();
switch (txStatus) {
case Status.STATUS_ACTIVE:
// this is the only state we are interested in, for enlisting our XAResource
break;
default:
// we won't be enlisting our XAResource for tx state other than ACTIVE
return null;
}
final Object transactionKey = synchronizationRegistry.getTransactionKey();
final EJBReceiver receiver = invocationContext.getReceiver();
final String nodeName = receiver.getNodeName();
final ResourceImpl resource = new ResourceImpl(invocationContext, transactionKey);
XidTransactionID transactionID;
if (transaction.enlistResource(resource)) {
transactionID = resource.getTransactionID();
if (transactionID != null) {
return transactionID;
}
throw Logs.MAIN.txEnlistmentDidNotYieldTxId();
}
// another resource exists for this transaction ID
transactionID = (XidTransactionID) synchronizationRegistry.getResource(new NodeKey(nodeName));
if (transactionID == null) {
throw Logs.MAIN.cannotEnlistTx();
}
synchronizationRegistry.registerInterposedSynchronization(new SynchronizationImpl(invocationContext, transactionID));
return transactionID;
}
protected String getTransactionNode() {
return null;
}
final class SynchronizationImpl implements Synchronization {
private final EJBClientContext ejbClientContext;
private final String nodeName;
private final XidTransactionID transactionID;
SynchronizationImpl(final EJBClientInvocationContext ejbClientInvocationContext, final XidTransactionID transactionID) {
this.ejbClientContext = ejbClientInvocationContext.getClientContext();
this.nodeName = ejbClientInvocationContext.getReceiver().getNodeName();
this.transactionID = transactionID;
}
public void beforeCompletion() {
final EJBReceiverContext receiverContext = ejbClientContext.requireNodeEJBReceiverContext(this.nodeName);
final EJBReceiver receiver = receiverContext.getReceiver();
receiver.beforeCompletion(receiverContext, transactionID); // block for result
}
public void afterCompletion(final int status) {
}
}
static final class State {
private final XidTransactionID transactionID;
private final boolean suspended;
private final AtomicInteger participantCnt;
State(final XidTransactionID transactionID, final boolean suspended, final AtomicInteger cnt) {
this.transactionID = transactionID;
this.suspended = suspended;
participantCnt = cnt;
}
State(final State old, final boolean suspended) {
transactionID = old.transactionID;
participantCnt = old.participantCnt;
this.suspended = suspended;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("State");
sb.append("{transactionID=").append(transactionID);
sb.append(", suspended=").append(suspended);
sb.append(", participantCnt=").append(participantCnt);
sb.append('}');
return sb.toString();
}
}
final class ResourceImpl implements XAResource, Serializable {
private final Object transactionKey;
private final EJBClientContext ejbClientContext;
private final String nodeName;
@SuppressWarnings("unused")
volatile State state;
ResourceImpl(final EJBClientInvocationContext ejbClientInvocationContext, final Object transactionKey) {
this.ejbClientContext = ejbClientInvocationContext.getClientContext();
this.transactionKey = transactionKey;
this.nodeName = ejbClientInvocationContext.getReceiver().getNodeName();
}
XidTransactionID getTransactionID() {
final State state = this.state;
return state == null ? null : state.transactionID;
}
public void start(final Xid xid, final int flags) throws XAException {
if (flags == TMNOFLAGS || flags == TMJOIN) {
// Not associated (T₀) -> Associated (T₁)
final XidTransactionID transactionID = new XidTransactionID(xid);
if (!stateUpdater.compareAndSet(this, null, new State(transactionID, false, new AtomicInteger()))) {
// XAResource is already busy on a transaction
// this should be impossible though since XAResource is bound to a transaction
throw new XAException(XAException.XAER_INVAL);
}
} else if (flags == TMRESUME) {
// Suspended (T₂) -> Associated (T₁)
State state;
do {
state = this.state;
if (state == null || !state.suspended) {
throw new XAException(XAException.XAER_INVAL);
}
} while (!stateUpdater.compareAndSet(this, state, state = new State(state, false)));
} else {
throw new XAException(XAException.XAER_INVAL);
}
}
public void end(final Xid xid, final int flags) throws XAException {
if (flags == TMSUSPEND) {
// Associated (T₁) -> Suspended (T₂)
State state;
do {
state = this.state;
if (state == null || state.suspended) {
throw new XAException(XAException.XAER_INVAL);
}
} while (!stateUpdater.compareAndSet(this, state, state = new State(state, true)));
} else if (flags == TMFAIL || flags == TMSUCCESS) {
// Associated (T₁) | Suspended (T₂) -> Not associated (T₀)
if (stateUpdater.getAndSet(this, null) == null) {
throw new XAException(XAException.XAER_INVAL);
}
} else {
throw new XAException(XAException.XAER_INVAL);
}
}
public int prepare(final Xid xid) throws XAException {
final XidTransactionID transactionID = new XidTransactionID(xid);
final EJBReceiverContext receiverContext = ejbClientContext.requireNodeEJBReceiverContext(this.nodeName);
final EJBReceiver receiver = receiverContext.getReceiver();
return receiver.sendPrepare(receiverContext, transactionID);
}
public void commit(final Xid xid, final boolean onePhase) throws XAException {
final XidTransactionID transactionID = new XidTransactionID(xid);
final EJBReceiverContext receiverContext = ejbClientContext.requireNodeEJBReceiverContext(this.nodeName);
final EJBReceiver receiver = receiverContext.getReceiver();
receiver.sendCommit(receiverContext, transactionID, onePhase);
}
public void forget(final Xid xid) throws XAException {
final XidTransactionID transactionID = new XidTransactionID(xid);
final EJBReceiverContext receiverContext = ejbClientContext.requireNodeEJBReceiverContext(this.nodeName);
final EJBReceiver receiver = receiverContext.getReceiver();
receiver.sendForget(receiverContext, transactionID);
}
public void rollback(final Xid xid) throws XAException {
final XidTransactionID transactionID = new XidTransactionID(xid);
final EJBReceiverContext receiverContext = ejbClientContext.requireNodeEJBReceiverContext(this.nodeName);
final EJBReceiver receiver = receiverContext.getReceiver();
receiver.sendRollback(receiverContext, transactionID);
}
public boolean isSameRM(final XAResource resource) throws XAException {
return resource instanceof ResourceImpl && isSameRM((ResourceImpl) resource);
}
boolean isSameRM(final ResourceImpl resource) throws XAException {
return resource != null && transactionKey == resource.transactionKey && nodeName.equals(resource.nodeName);
}
public boolean setTransactionTimeout(final int seconds) throws XAException {
return false;
}
public int getTransactionTimeout() throws XAException {
return 0;
}
public Xid[] recover(final int flags) throws XAException {
return new Xid[0];
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("ResourceImpl");
sb.append("{transactionKey=").append(transactionKey);
sb.append(", ejbClientContext=").append(ejbClientContext);
sb.append(", nodeName='").append(nodeName).append('\'');
sb.append(", state=").append(state);
sb.append('}');
return sb.toString();
}
// serializes to RecoveryOnlySerializedEJBXAResource
private Object writeReplace() throws ObjectStreamException {
return new RecoveryOnlySerializedEJBXAResource(this.nodeName);
}
}
/**
* Returns a EJB {@link XAResource} which can be used *only* during transaction recovery process
*
* @param transactionOriginNodeIdentifier
* The node identifier of the node from which the transaction originated
* @param receiverContext The EJB receiver context of the target EJB receiver/server which will be used to fetch the Xid(s)
* which need to be recovered
* @return
*/
public static XAResource getEJBXAResourceForRecovery(final EJBReceiverContext receiverContext, final String transactionOriginNodeIdentifier) {
return new RecoveryOnlyEJBXAResource(transactionOriginNodeIdentifier, receiverContext);
}
/**
* Returns true if the passed className
corresponds to the fully qualified classname of any of the known EJB XAResource(s).
* Else returns false.
*
* @param className The fully qualified name of the class which is being checked for being a EJB XAResource
* @return
*/
public static boolean isEJBXAResourceClass(final String className) {
return RecoveryOnlySerializedEJBXAResource.class.getName().equals(className)
|| ResourceImpl.class.getName().equals(className)
|| RecoveryOnlyEJBXAResource.class.getName().equals(className);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy