org.wildfly.transaction.client.LocalTransactionContext Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2016 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed 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.wildfly.transaction.client;
import static java.security.AccessController.doPrivileged;
import java.security.PrivilegedAction;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Supplier;
import jakarta.resource.spi.XATerminator;
import jakarta.transaction.SystemException;
import jakarta.transaction.Transaction;
import javax.transaction.xa.XAException;
import javax.transaction.xa.Xid;
import org.wildfly.common.Assert;
import org.wildfly.common.annotation.NotNull;
import org.wildfly.common.context.ContextManager;
import org.wildfly.common.context.Contextual;
import org.wildfly.transaction.TransactionPermission;
import org.wildfly.transaction.client._private.Log;
import org.wildfly.transaction.client.spi.LocalTransactionProvider;
/**
* The local transaction context, which manages the creation and usage of local transactions.
*
* @author David M. Lloyd
*/
public final class LocalTransactionContext implements Contextual {
/**
* The default transaction timeout (5 minutes).
*/
public static final int DEFAULT_TXN_TIMEOUT = 300;
private static final ContextManager CONTEXT_MANAGER = new ContextManager(LocalTransactionContext.class, "org.wildfly.transaction.client.context.local");
private static final Supplier PRIVILEGED_SUPPLIER = doPrivileged((PrivilegedAction>) CONTEXT_MANAGER::getPrivilegedSupplier);
private static final Object LOCAL_TXN_KEY = new Object();
private static final TransactionPermission CREATION_LISTENER_PERMISSION = TransactionPermission.forName("registerCreationListener");
private static final TransactionPermission SUSPEND_REQUESTS_PERMISSION = TransactionPermission.forName("suspendRequests");
private static final TransactionPermission RESUME_REQUESTS_PERMISSION = TransactionPermission.forName("resumeRequests");
private static final TransactionPermission GET_XA_TERMINATOR_PERMISSION = TransactionPermission.forName("getXATerminator");
private static final TransactionPermission GET_RECOVERY_INTERFACE_PERMISSION = TransactionPermission.forName("getRecoveryInterface");
static {
doPrivileged((PrivilegedAction>) () -> {
CONTEXT_MANAGER.setGlobalDefault(new LocalTransactionContext(LocalTransactionProvider.EMPTY));
return null;
});
}
private final LocalTransactionProvider provider;
private final XATerminator xaTerminator = new ContextXATerminator(this);
private final List creationListeners = new CopyOnWriteArrayList<>();
private volatile boolean requestsSuspended;
/**
* Construct a new instance. The given provider will be used to manage local transactions.
*
* @param provider the local transaction provider
*/
public LocalTransactionContext(final LocalTransactionProvider provider) {
Assert.checkNotNullParam("provider", provider);
this.provider = provider;
}
/**
* Get the context manager.
*
* @return the context manager (not {@code null})
*/
@NotNull
public static ContextManager getContextManager() {
return CONTEXT_MANAGER;
}
/**
* Get the context manager; delegates to {@link #getContextManager()}.
*
* @return the context manager (not {@code null})
*/
@NotNull
public ContextManager getInstanceContextManager() {
return getContextManager();
}
/**
* Get the current local transaction context. The default context does not support initiating new transactions.
*
* @return the current local transaction context (not {@code null})
*/
@NotNull
public static LocalTransactionContext getCurrent() {
return PRIVILEGED_SUPPLIER.get();
}
private LocalTransaction notifyCreationListeners(LocalTransaction transaction, CreationListener.CreatedBy createdBy) {
for (CreationListener creationListener : creationListeners) {
try {
creationListener.transactionCreated(transaction, createdBy);
} catch (Throwable t) {
Log.log.trace("Transaction creation listener throws an exception", t);
}
}
return transaction;
}
/**
* Register a transaction creation listener.
*
* @param creationListener the creation listener (must not be {@code null})
*/
public void registerCreationListener(CreationListener creationListener) {
Assert.checkNotNullParam("creationListener", creationListener);
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(CREATION_LISTENER_PERMISSION);
}
creationListeners.add(creationListener);
}
/**
* Remove a transaction creation listener.
*
* @param creationListener the creation listener (must not be {@code null})
*/
public void removeCreationListener(CreationListener creationListener) {
Assert.checkNotNullParam("creationListener", creationListener);
creationListeners.removeIf(c -> c == creationListener);
}
/**
* Begin a new, local transaction on behalf of a local peer.
*
* @param timeout the transaction timeout to use for this transaction
* @return the local transaction (not {@code null})
* @throws SystemException if the transaction creation failed for some reason, one of the possible reasons being
* suspended server
* @throws SecurityException if the caller is not authorized to create a local transaction in this context
*/
@NotNull
public LocalTransaction beginTransaction(final int timeout) throws SystemException, SecurityException {
return beginTransaction(timeout, false);
}
/**
* Begin a new, local transaction on behalf of a local or remote peer.
*
* @param timeout the transaction timeout to use for this transaction
* @param failOnSuspend {@code true} to fail if the server is suspended, {@code false} to begin the transaction even if the server is suspended
* @return the local transaction (not {@code null})
* @throws SystemException if the transaction creation failed for some reason, one of the possible reasons being
* suspended server
* @throws SecurityException if the caller is not authorized to create a local transaction in this context
*/
@NotNull
public LocalTransaction beginTransaction(final int timeout, final boolean failOnSuspend) throws SystemException, SecurityException {
return beginTransaction(timeout, failOnSuspend, CreationListener.CreatedBy.TRANSACTION_MANAGER);
}
@NotNull
LocalTransaction beginTransaction(final int timeout, final boolean failOnSuspend, CreationListener.CreatedBy createdBy) throws SystemException, SecurityException {
Assert.checkMinimumParameter("timeout", 0, timeout);
if (failOnSuspend && requestsSuspended) {
throw Log.log.suspendedCannotCreateNew();
}
final Transaction newTransaction = provider.createNewTransaction(timeout);
//noinspection ConstantConditions
if (newTransaction == null) {
throw Log.log.providerCreatedNullTransaction();
}
return getOrAttach(newTransaction, createdBy);
}
/**
* Attempt to import a transaction, which subsequently may be controlled by its XID or by the returned handle.
*
* @param xid the XID of the transaction to import (must not be {@code null})
* @param timeout the transaction timeout to use, if new
* @param doNotImport {@code true} to indicate that a non-existing transaction should not be imported, {@code false} otherwise
* @return the transaction import result, or {@code null} if (and only if) {@code doNotImport} is {@code true} and the transaction didn't exist locally
* @throws XAException if a problem occurred while importing the transaction
*/
public ImportResult findOrImportTransaction(Xid xid, int timeout, boolean doNotImport) throws XAException {
Assert.checkNotNullParam("xid", xid);
Assert.checkMinimumParameter("timeout", 0, timeout);
XAImporter xaImporter = provider.getXAImporter();
final boolean requestsSuspended = this.requestsSuspended;
final ImportResult> result = xaImporter.findOrImportTransaction(xid, timeout, doNotImport || requestsSuspended);
if (result == null) {
if (! doNotImport) {
if (requestsSuspended) {
throw Log.log.suspendedCannotImportXa(XAException.XAER_RMERR);
}
throw Log.log.providerCreatedNullTransaction();
}
return null;
}
return result.withTransaction(getOrAttach(result.getTransaction(), result.isNew() ? CreationListener.CreatedBy.IMPORT : CreationListener.CreatedBy.MERGE));
}
/**
* Attempt to import a transaction, which subsequently may be controlled by its XID or by the returned handle.
*
* @param xid the XID of the transaction to import (must not be {@code null})
* @param timeout the transaction timeout to use, if new
* @return the transaction import result (not {@code null})
* @throws XAException if a problem occurred while importing the transaction
*/
@NotNull
public ImportResult findOrImportTransaction(Xid xid, int timeout) throws XAException {
return Assert.assertNotNull(findOrImportTransaction(xid, timeout, false));
}
/**
* Attempt to import a provider's current transaction as a local transaction.
*
* @return {@code true} if the transaction was associated, {@code false} if the provider had no current transaction
* @throws SystemException if an error occurred acquiring the current transaction from the provider
* @throws IllegalStateException if the thread is associated with a transaction that isn't equal to the provider's transaction
*/
public boolean importProviderTransaction() throws SystemException {
final ContextTransactionManager.State state = ContextTransactionManager.INSTANCE.getStateRef().get();
final Transaction transaction = provider.getTransactionManager().getTransaction();
if (transaction == null) {
return false;
}
final LocalTransaction localTransaction = getOrAttach(transaction, CreationListener.CreatedBy.MERGE);
if (state.transaction == null) {
state.transaction = localTransaction;
} else {
localTransaction.verifyAssociation();
}
return true;
}
LocalTransaction getOrAttach(Transaction transaction, CreationListener.CreatedBy createdBy) {
LocalTransaction txn = (LocalTransaction) provider.getResource(transaction, LOCAL_TXN_KEY);
boolean isNew = false;
if (txn == null) {
synchronized (transaction) {
txn = (LocalTransaction) provider.getResource(transaction, LOCAL_TXN_KEY);
if (txn == null) {
provider.putResource(transaction, LOCAL_TXN_KEY, txn = new LocalTransaction(this, transaction));
isNew = true;
}
}
}
if (isNew) {
notifyCreationListeners(txn, createdBy);
}
return txn;
}
/**
* Get the recovery interface for this context. The recovery interface can be used to recover transactions which
* were imported into this context via {@link #findOrImportTransaction(Xid, int)}.
*
* @return the recovery interface for this context (not {@code null})
*/
@NotNull
public XARecoverable getRecoveryInterface() {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(GET_RECOVERY_INTERFACE_PERMISSION);
}
final XAImporter xaImporter = provider.getXAImporter();
return new XARecoverable() {
public Xid[] recover(final int flag, final String parentName) throws XAException {
return xaImporter.recover(flag, parentName);
}
public void commit(final Xid xid, final boolean onePhase) throws XAException {
xaImporter.commit(xid, onePhase);
}
public void forget(final Xid xid) throws XAException {
xaImporter.forget(xid);
}
};
}
@NotNull
public XATerminator getXATerminator() {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(GET_XA_TERMINATOR_PERMISSION);
}
return xaTerminator;
}
/**
* Cause requests to create new transactions to be refused.
*
* @throws SecurityException if a security manager is present and the caller does not have the {@code suspendRequests} {@link TransactionPermission}
*/
public void suspendRequests() throws SecurityException {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(SUSPEND_REQUESTS_PERMISSION);
}
requestsSuspended = true;
}
/**
* Cause requests to create new transactions to be allowed after a previous call to {@link #suspendRequests()}.
*
* @throws SecurityException if a security manager is present and the caller does not have the {@code resumeRequests} {@link TransactionPermission}
*/
public void resumeRequests() throws SecurityException {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(RESUME_REQUESTS_PERMISSION);
}
requestsSuspended = false;
}
LocalTransactionProvider getProvider() {
return provider;
}
public String toString() {
return String.format("Local transaction context for provider %s", provider);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy