org.jboss.ejb.client.EJBClientManagedTransactionContext Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote Jakarta Enterprise Beans and Jakarta Messaging, including
all dependencies. It is intended for use by those not using maven, maven users should just import the Jakarta Enterprise Beans and
Jakarta Messaging BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* 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