All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.wildfly.transaction.client.provider.jboss.JBossLocalTransactionProvider Maven / Gradle / Ivy

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2017 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.provider.jboss;

import static com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome.INVALID_TRANSACTION;
import static com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome.PREPARE_NOTOK;
import static com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome.PREPARE_OK;
import static com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome.PREPARE_READONLY;
import static java.lang.Long.signum;

import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListSet;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import com.arjuna.ats.arjuna.common.arjPropertyManager;
import jakarta.transaction.HeuristicCommitException;
import jakarta.transaction.HeuristicMixedException;
import jakarta.transaction.HeuristicRollbackException;
import jakarta.transaction.InvalidTransactionException;
import jakarta.transaction.NotSupportedException;
import jakarta.transaction.RollbackException;
import jakarta.transaction.Status;
import jakarta.transaction.Synchronization;
import jakarta.transaction.SystemException;
import jakarta.transaction.Transaction;
import jakarta.transaction.TransactionManager;
import org.jboss.tm.ExtendedJBossXATerminator;
import org.jboss.tm.ImportedTransaction;
import org.jboss.tm.TransactionImportResult;
import org.jboss.tm.XAResourceRecovery;
import org.jboss.tm.XAResourceRecoveryRegistry;
import org.wildfly.common.Assert;
import org.wildfly.common.annotation.NotNull;
import org.wildfly.transaction.client.ImportResult;
import org.wildfly.transaction.client.LocalTransaction;
import org.wildfly.transaction.client.SimpleXid;
import org.wildfly.transaction.client.XAImporter;
import org.wildfly.transaction.client.XAResourceRegistry;
import org.wildfly.transaction.client.XAResourceRegistryProviderHolder;
import org.wildfly.transaction.client._private.Log;
import org.wildfly.transaction.client.spi.LocalTransactionProvider;
import org.wildfly.transaction.client.spi.SubordinateTransactionControl;

/**
 * The local transaction provider for JBoss application servers.
 *
 * @author David M. Lloyd
 */
public abstract class JBossLocalTransactionProvider implements LocalTransactionProvider {
    private static final Object ENTRY_KEY = new Object();

    private final int staleTransactionTime;
    private final ExtendedJBossXATerminator ext;
    private final TransactionManager tm;
    private final XAImporterImpl xi = new XAImporterImpl();
    private final ConcurrentSkipListSet timeoutSet = new ConcurrentSkipListSet<>();
    private final ConcurrentMap known = new ConcurrentHashMap<>();
    private final FileSystemXAResourceRegistry fileSystemXAResourceRegistry;
    private final XAResourceRecovery xaResourceRecovery;

    JBossLocalTransactionProvider(final ExtendedJBossXATerminator ext, final int staleTransactionTime, final TransactionManager tm,
                                  final XAResourceRecoveryRegistry registry, final Path xaRecoveryDirRelativeToPath) {
        Assert.checkMinimumParameter("setTransactionTimeout", 0, staleTransactionTime);
        this.staleTransactionTime = staleTransactionTime;
        this.ext = Assert.checkNotNullParam("ext", ext);
        this.tm = Assert.checkNotNullParam("tm", tm);

        try {
            ext.doRecover(null, null);
        } catch (Exception e) {
            // the recover method is called to load transactions from Narayana object store at startup
            // if it fails we ignore, troubles will be adjusted during runtime
            Log.log.doRecoverFailureOnIntialization(e);
        }
        this.fileSystemXAResourceRegistry = new FileSystemXAResourceRegistry(this, xaRecoveryDirRelativeToPath);
        XAResourceRegistryProviderHolder.register(fileSystemXAResourceRegistry::getInDoubtXAResources);
        xaResourceRecovery = fileSystemXAResourceRegistry::getInDoubtXAResources;
        registry.addXAResourceRecovery(xaResourceRecovery);
    }

    /**
     * Remove the registered xaResourceRecovery from the registry passed as parameter.
     * Allow the consumer to remove the xaResourceRecovery from the registry.
     * If not removed, the xaResourceRecovery is held by XAResourceRegistryProviderHolder.INSTANCE
     * and it cannot be garbage collected (i.e. during consumer's stop)
     *
     * @param registry XAResourceRecovery registry
     */
    public void removeXAResourceRecovery(XAResourceRecoveryRegistry registry) {
        registry.removeXAResourceRecovery(xaResourceRecovery);
    }

    /**
     * Create a builder for the transaction provider.
     *
     * @return the builder
     */
    public static Builder builder() {
        return new Builder();
    }

    @NotNull
    public TransactionManager getTransactionManager() {
        return tm;
    }

    @NotNull
    public XAImporter getXAImporter() {
        return xi;
    }

    @NotNull
    public Transaction createNewTransaction(final int timeout) throws SystemException, SecurityException {
        final TransactionManager tm = this.tm;
        final int oldTimeout = getTransactionManagerTimeout();
        tm.setTransactionTimeout(timeout);
        try {
            final Transaction suspended = tm.suspend();
            try {
                tm.begin();
                final Transaction transaction = tm.suspend();
                SimpleXid gtid = SimpleXid.of(getXid(transaction)).withoutBranch();
                known.put(gtid, getEntryFor(transaction, gtid));
                // Narayana doesn't actually throw exceptions here so this should be fine
                tm.setTransactionTimeout(oldTimeout);
                return transaction;
            } catch (NotSupportedException e) {
                throw Log.log.unexpectedFailure(e);
            } catch (Throwable t) {
                if (suspended != null) try {
                    tm.resume(suspended);
                } catch (InvalidTransactionException e) {
                    e.addSuppressed(t);
                    throw Log.log.unexpectedFailure(e);
                }
                throw t;
            }
        } catch (Throwable t) {
            try {
                tm.setTransactionTimeout(oldTimeout);
            } catch (Throwable t2) {
                // Narayana doesn't actually throw exceptions here so this should be fine
                t2.addSuppressed(t);
                throw t2;
            }
            throw t;
        }
    }

    @Override
    public XAResourceRegistry getXAResourceRegistry(LocalTransaction transaction) throws SystemException {
        return fileSystemXAResourceRegistry.getXAResourceRegistryFile(transaction);
    }

    abstract int getTransactionManagerTimeout() throws SystemException;

    public boolean isImported(@NotNull final Transaction transaction) throws IllegalArgumentException {
        return transaction instanceof ImportedTransaction;
    }

    public abstract void registerInterposedSynchronization(@NotNull Transaction transaction, @NotNull Synchronization sync) throws IllegalArgumentException;

    @NotNull
    public abstract Object getKey(@NotNull Transaction transaction) throws IllegalArgumentException;

    public void dropLocal(@NotNull final Transaction transaction) {
        final Xid xid = getXid(transaction);
        final SimpleXid gtid = SimpleXid.of(xid).withoutBranch();
        final Entry entry = known.remove(gtid);
        if (entry != null) {
            timeoutSet.remove(entry.getXidKey());
        }
    }

    public void dropRemote(@NotNull final Transaction transaction) {
        final Xid xid = getXid(transaction);
        final SimpleXid gtid = SimpleXid.of(xid).withoutBranch();
        final Entry entry = known.get(gtid);
        if (entry != null) {
            final XidKey oldXidKey = entry.getXidKey();
            // redefinition of time when memory mapping is cleaned from the transaction with remote enlistment
            // the transaction with remote enlistment will be removed from when stale txn time elapsed
            final XidKey newXidKey = new XidKey(gtid, getTimeTick() + staleTransactionTime * 1_000_000_000L);
            timeoutSet.add(newXidKey);
            timeoutSet.remove(oldXidKey);
        }
    }

    public abstract int getTimeout(@NotNull Transaction transaction);

    @NotNull
    public abstract Xid getXid(@NotNull Transaction transaction);

    @NotNull
    public String getNodeName() {
        final String nodeIdentifier = arjPropertyManager.getCoreEnvironmentBean().getNodeIdentifier();
        if (nodeIdentifier == null) {
            throw Log.log.noLocalTransactionProviderNodeName();
        }
        return nodeIdentifier;
    }

    Entry getEntryFor(Transaction transaction, SimpleXid gtid) {
        Entry entry = (Entry) getResource(transaction, ENTRY_KEY);
        if (entry != null) {
            return entry;
        }
        final XidKey xidKey;
        synchronized (transaction) {
            entry = (Entry) getResource(transaction, ENTRY_KEY);
            if (entry != null) {
                return entry;
            }
            int lifetime = getTimeout(transaction) + staleTransactionTime;
            final long timeTick = getTimeTick();
            // this is the maximum amount of time we expect any potential incoming peer might know about this transaction ID
            xidKey = new XidKey(gtid, timeTick + lifetime * 1_000_000_000L);
            putResource(transaction, ENTRY_KEY, entry = new Entry(gtid, transaction, xidKey));
        }
        timeoutSet.add(xidKey);
        if (! isStatusInactive(transaction)) {
            try {
                registerInterposedSynchronization(transaction, new Synchronization() {
                    public void beforeCompletion() {
                        // no operation
                    }

                    public void afterCompletion(final int status) {
                        // let the TM do some heavy lifting for us
                        final long timeTick = getTimeTick();
                        // clear off all expired entries
                        final ConcurrentMap known = JBossLocalTransactionProvider.this.known;
                        final Iterator iterator = timeoutSet.headSet(new XidKey(SimpleXid.EMPTY, timeTick)).iterator();
                        while (iterator.hasNext()) {
                            SimpleXid xidToRemove = iterator.next().getId();
                            Entry knownEntry = known.remove(xidToRemove);

                            if (knownEntry == null) {
                                Log.log.unknownXidToBeRemovedFromTheKnownTransactionInstances(xidToRemove);
                            } else {
                                final Transaction transaction = knownEntry.getTransaction();
                                if (transaction instanceof ImportedTransaction) {
                                    try {
                                        ext.removeImportedTransaction(getXid(transaction));
                                    } catch (XAException xae) {
                                        Log.log.cannotRemoveImportedTransaction(xidToRemove, xae);
                                    }
                                }
                            }

                            iterator.remove();
                        }
                    }
                });
            } catch (IllegalStateException e) {
                if (! isStatusInactive(transaction)) {
                    // we don't know why it happened because the tx is not inactive; could be unknown or something weird
                    throw e;
                }
            }
        }
        return entry;
    }

    boolean isStatusInactive(Transaction transaction) {
        switch (getStatus(transaction)) {
            case Status.STATUS_PREPARING:
            case Status.STATUS_PREPARED:
            case Status.STATUS_COMMITTING:
            case Status.STATUS_COMMITTED:
            case Status.STATUS_ROLLING_BACK:
            case Status.STATUS_ROLLEDBACK: {
                return true;
            }
            default: {
                return false;
            }
        }
    }

    int getStatus(Transaction transaction) {
        try {
            return transaction.getStatus();
        } catch (SystemException e) {
            return Status.STATUS_UNKNOWN;
        }
    }

    private static final long TIME_START = System.nanoTime();

    long getTimeTick() {
        return System.nanoTime() - TIME_START;
    }

    private static final int UID_LEN = 28;

    public String getNameFromXid(@NotNull final Xid xid) {
        final int formatId = xid.getFormatId();
        if (formatId == 0x20000 || formatId == 0x20005 || formatId == 0x20008) {
            final byte[] gtid = xid.getGlobalTransactionId();
            final int length = gtid.length;
            if (length <= UID_LEN) {
                // no parent name encoded there
                return null;
            }
            return new String(gtid, UID_LEN, length - UID_LEN, StandardCharsets.UTF_8);
        } else {
            return null;
        }
    }

    public  T getProviderInterface(final Transaction transaction, final Class providerInterfaceType) {
        // access to underlying txn
        return providerInterfaceType.isInstance(transaction) ? providerInterfaceType.cast(transaction) : null;
    }

    static final class XidKey implements Comparable {
        private final SimpleXid gtid;
        private final long expiration;

        XidKey(final SimpleXid gtid, final long expiration) {
            this.gtid = gtid;
            this.expiration = expiration;
        }

        public int compareTo(final XidKey o) {
            final int res = signum(expiration - o.expiration);
            return res == 0 ? gtid.compareTo(o.gtid) : res;
        }

        SimpleXid getId() {
            return gtid;
        }
    }

    final class Entry implements SubordinateTransactionControl {
        private final SimpleXid gtid;
        private final Transaction transaction;
        private final XidKey xidKey;

        Entry(final SimpleXid gtid, final Transaction transaction, final XidKey xidKey) {
            this.gtid = gtid;
            this.transaction = transaction;
            this.xidKey = xidKey;
        }

        XidKey getXidKey() {
            return xidKey;
        }

        Transaction getTransaction() {
            return transaction;
        }

        void rollbackLocal() throws SystemException {
            if (transaction instanceof ImportedTransaction) {
                throw Log.log.rollbackOnImported();
            }
            transaction.rollback();
        }

        void commitLocal() throws HeuristicRollbackException, RollbackException, HeuristicMixedException, SystemException {
            if (transaction instanceof ImportedTransaction) {
                throw Log.log.commitOnImported();
            }
            transaction.commit();
        }

        public void rollback() throws XAException {
            final Transaction transaction = this.transaction;
            try {
                final int status = transaction.getStatus();
                if (status == Status.STATUS_ROLLING_BACK || status == Status.STATUS_ROLLEDBACK) {
                    // no harm here
                    return;
                }
            } catch (SystemException e) {
                // can't determine status; fall out
            }
            if (! (transaction instanceof ImportedTransaction)) {
                throw Log.log.notImportedXa(XAException.XAER_NOTA);
            }
            final ImportedTransaction importedTransaction = (ImportedTransaction) transaction;
            if (importedTransaction.activated()) try {
                importedTransaction.doRollback();
            } catch (HeuristicCommitException e) {
                throw Log.log.heuristicCommitXa(XAException.XA_HEURCOM, e);
            } catch (HeuristicMixedException e) {
                throw Log.log.heuristicMixedXa(XAException.XA_HEURMIX, e);
            } catch (HeuristicRollbackException e) {
                throw Log.log.heuristicRollbackXa(XAException.XA_HEURRB, e);
            } catch (IllegalStateException e) {
                throw Log.log.illegalStateXa(XAException.XAER_NOTA, e);
            } catch (Throwable /* RuntimeException | SystemException */ e) {
                throw Log.log.resourceManagerErrorXa(XAException.XAER_RMERR, e);
            } finally {
                ext.removeImportedTransaction(gtid);
            }
        }

        public void end(final int flags) throws XAException {
            if (flags != XAResource.TMFAIL) {
                return;
            }
            final Transaction transaction = this.transaction;
            try {
                final int status = transaction.getStatus();
                if (status == Status.STATUS_MARKED_ROLLBACK || status == Status.STATUS_ROLLING_BACK || status == Status.STATUS_ROLLEDBACK) {
                    // no harm here
                    return;
                }
            } catch (SystemException e) {
                // can't determine status; fall out
            }
            if (! (transaction instanceof ImportedTransaction)) {
                throw Log.log.notImportedXa(XAException.XAER_NOTA);
            }
            try {
                transaction.setRollbackOnly();
            } catch (IllegalStateException e) {
                throw Log.log.illegalStateXa(XAException.XAER_NOTA, e);
            } catch (Throwable /* RuntimeException | SystemException */ e) {
                throw Log.log.resourceManagerErrorXa(XAException.XAER_RMERR, e);
            }
        }

        public void beforeCompletion() throws XAException {
            final Transaction transaction = this.transaction;
            if (! (transaction instanceof ImportedTransaction)) {
                throw Log.log.notImportedXa(XAException.XAER_NOTA);
            }
            final ImportedTransaction importedTransaction = (ImportedTransaction) transaction;
            try {
                if (! importedTransaction.doBeforeCompletion()) {
                    throw new XAException(XAException.XAER_RMERR);
                }
            } catch (IllegalStateException e) {
                throw Log.log.illegalStateXa(XAException.XAER_NOTA, e);
            } catch (Throwable /* RuntimeException | SystemException */ e) {
                throw Log.log.resourceManagerErrorXa(XAException.XAER_RMERR, e);
            }
        }

        public int prepare() throws XAException {
            final Transaction transaction = this.transaction;
            if (! (transaction instanceof ImportedTransaction)) {
                throw Log.log.notImportedXa(XAException.XAER_NOTA);
            }
            final ImportedTransaction importedTransaction = (ImportedTransaction) transaction;
            final int tpo = importedTransaction.doPrepare();
            switch (tpo) {
                case PREPARE_READONLY:
                    ext.removeImportedTransaction(gtid);
                    return XAResource.XA_RDONLY;

                case PREPARE_OK:
                    return XAResource.XA_OK;

                case PREPARE_NOTOK:
                    //noinspection TryWithIdenticalCatches
                    try {
                        importedTransaction.doRollback();
                    } catch (HeuristicCommitException | HeuristicMixedException | HeuristicRollbackException | SystemException e) {
                        // TODO: the old code removes the transaction here, but JBTM-427 implies that the TM should do this explicitly later; for now keep old behavior
                        ext.removeImportedTransaction(gtid);
                        // JBTM-427; JTA doesn't allow heuristic codes on prepare :(
                        throw initializeSuppressed(Log.log.resourceManagerErrorXa(XAException.XAER_RMERR, e), importedTransaction);
                    } catch (Throwable t) {
                        // maybe still remove the transaction...
                        ext.removeImportedTransaction(gtid);
                        throw initializeSuppressed(Log.log.resourceManagerErrorXa(XAException.XAER_RMERR, t), importedTransaction);
                    }
                    throw initializeSuppressed(new XAException(XAException.XA_RBROLLBACK), importedTransaction);

                case INVALID_TRANSACTION:
                    throw new XAException(XAException.XAER_NOTA);

                default:
                    throw new XAException(XAException.XA_RBOTHER);
            }
        }

        public void forget() throws XAException {
            final Transaction transaction = this.transaction;
            if (! (transaction instanceof ImportedTransaction)) {
                throw Log.log.notImportedXa(XAException.XAER_NOTA);
            }
            final ImportedTransaction importedTransaction = (ImportedTransaction) transaction;
            try {
                importedTransaction.doForget();
            } catch (IllegalStateException e) {
                throw Log.log.illegalStateXa(XAException.XAER_NOTA, e);
            } catch (Throwable /* RuntimeException | SystemException */ e) {
                throw Log.log.resourceManagerErrorXa(XAException.XAER_RMERR, e);
            }
        }

        public void commit(final boolean onePhase) throws XAException {
            final Transaction transaction = this.transaction;
            if (! (transaction instanceof ImportedTransaction)) {
                throw Log.log.notImportedXa(XAException.XAER_NOTA);
            }
            final ImportedTransaction importedTransaction = (ImportedTransaction) transaction;
            try {
                if (onePhase) {
                    importedTransaction.doOnePhaseCommit();
                } else {
                    if (! importedTransaction.doCommit()) {
                        dropLocal(importedTransaction);
                        ext.doRecover(null, null);
                        throw new XAException(XAException.XA_RETRY);
                    }
                }
            } catch (XAException e) {
                throw initializeSuppressed(e, importedTransaction);
            } catch (HeuristicMixedException e) {
                throw initializeSuppressed(Log.log.heuristicMixedXa(XAException.XA_HEURMIX, e), importedTransaction);
            } catch (RollbackException e) {
                throw initializeSuppressed(Log.log.rollbackXa(XAException.XA_RBROLLBACK, e), importedTransaction);
            } catch (HeuristicCommitException e) {
                throw initializeSuppressed(Log.log.heuristicCommitXa(XAException.XA_HEURCOM, e), importedTransaction);
            } catch (HeuristicRollbackException e) {
                throw initializeSuppressed(Log.log.heuristicRollbackXa(XAException.XA_HEURRB, e), importedTransaction);
            } catch (IllegalStateException e) {
                throw initializeSuppressed(Log.log.illegalStateXa(XAException.XAER_NOTA, e), importedTransaction);
            } catch (Throwable e) {
                throw initializeSuppressed(Log.log.resourceManagerErrorXa(XAException.XAER_RMERR, e), importedTransaction);
            }
        }

        private XAException initializeSuppressed(final XAException ex, final ImportedTransaction transaction) {
            if(ex != null && transaction.supportsDeferredThrowables()) {
                for(Throwable suppressedThrowable: transaction.getDeferredThrowables()) {
                    ex.addSuppressed(suppressedThrowable);
                }
            }
            return ex;
        }
    }

    final class XAImporterImpl implements XAImporter {

        public ImportResult findOrImportTransaction(final Xid xid, final int timeout, final boolean doNotImport) throws XAException {
            try {
                final SimpleXid simpleXid = SimpleXid.of(xid);
                final SimpleXid gtid = simpleXid.withoutBranch();
                final ConcurrentMap known = JBossLocalTransactionProvider.this.known;
                Entry entry = known.get(gtid);
                if (entry != null) {
                    return new ImportResult(entry.getTransaction(), entry, false);
                }
                final boolean imported;
                Transaction transaction;
                if (doNotImport) {
                    imported = false;
                    transaction = ext.getTransaction(xid);

                    if (transaction == null) {
                        return null;
                    }
                } else {
                    final TransactionImportResult result = ext.importTransaction(xid, timeout);
                    transaction = result.getTransaction();
                    imported = result.isNewImportedTransaction();
                }
                entry = getEntryFor(transaction, gtid);
                final Entry appearing = known.putIfAbsent(gtid, entry);
                if (appearing != null) {
                    // even if someone else beat us to the map, we still might have imported first... preserve the original Entry for economy though
                    return new ImportResult(transaction, appearing, imported);
                } else {
                    return new ImportResult(transaction, entry, imported);
                }
            } catch (XAException e) {
                throw e;
            } catch (Throwable t) {
                throw Log.log.resourceManagerErrorXa(XAException.XAER_RMFAIL, t);
            }
        }

        public Transaction findExistingTransaction(final Xid xid) throws XAException {
            try {
                final SimpleXid simpleXid = SimpleXid.of(xid);
                final SimpleXid gtid = simpleXid.withoutBranch();
                final ConcurrentMap known = JBossLocalTransactionProvider.this.known;
                Entry entry = known.get(gtid);
                if (entry != null) {
                    return entry.getTransaction();
                }
                final Transaction transaction = ext.getTransaction(xid);
                if (transaction == null) {
                    return null;
                }
                return known.computeIfAbsent(gtid, g -> getEntryFor(transaction, g)).getTransaction();
            } catch (XAException e) {
                throw e;
            } catch (Throwable t) {
                throw Log.log.resourceManagerErrorXa(XAException.XAER_RMFAIL, t);
            }
        }

        public void commit(final Xid xid, final boolean onePhase) throws XAException {
            try {
                Entry entry = known.get(SimpleXid.of(xid).withoutBranch());
                if (entry != null) {
                    entry.commit(onePhase);
                } else {
                    throw new XAException(XAException.XAER_NOTA);
                }
            } catch (XAException e) {
                throw e;
            } catch (Throwable t) {
                throw Log.log.resourceManagerErrorXa(XAException.XAER_RMFAIL, t);
            }
        }

        public void forget(final Xid xid) throws XAException {
            try {
                Entry entry = known.get(SimpleXid.of(xid).withoutBranch());
                if (entry != null) {
                    entry.forget();
                } else {
                    throw new XAException(XAException.XAER_NOTA);
                }
            } catch (XAException e) {
                throw e;
            } catch (Throwable t) {
                throw Log.log.resourceManagerErrorXa(XAException.XAER_RMFAIL, t);
            }
        }

        @NotNull
        public Xid[] recover(final int flag, final String parentNodeName) throws XAException {
            try {
                try {
                    final Xid[] xids = ext.doRecover(null, parentNodeName);
                    return xids == null ? SimpleXid.NO_XIDS : xids;
                } catch (NotSupportedException e) {
                    throw new XAException(XAException.XAER_RMFAIL);
                }
            } catch (XAException e) {
                throw e;
            } catch (Throwable t) {
                throw Log.log.resourceManagerErrorXa(XAException.XAER_RMFAIL, t);
            }
        }
    }

    /**
     * A builder for a JBoss local transaction provider.
     */
    public static final class Builder {
        private int staleTransactionTime = 600;
        private ExtendedJBossXATerminator extendedJBossXATerminator;
        private TransactionManager transactionManager;
        private XAResourceRecoveryRegistry xaResourceRecoveryRegistry;
        private Path xaRecoveryLogDirRelativeToPath;

        Builder() {
        }

        /**
         * Get the stale transaction time, in seconds.
         *
         * @return the stale transaction time, in seconds
         */
        public int getStaleTransactionTime() {
            return staleTransactionTime;
        }

        /**
         * Set the stale transaction time, in seconds.  The time must be no less than one second.
         *
         * @param staleTransactionTime the stale transaction time, in seconds
         */
        public Builder setStaleTransactionTime(final int staleTransactionTime) {
            Assert.checkMinimumParameter("staleTransactionTime", 1, staleTransactionTime);
            this.staleTransactionTime = staleTransactionTime;
            return this;
        }

        /**
         * Get the extended JBoss XA terminator.
         *
         * @return the extended JBoss XA terminator
         */
        public ExtendedJBossXATerminator getExtendedJBossXATerminator() {
            return extendedJBossXATerminator;
        }

        /**
         * Set the extended JBoss XA terminator.
         *
         * @param ext the extended JBoss XA terminator (must not be {@code null})
         */
        public Builder setExtendedJBossXATerminator(final ExtendedJBossXATerminator ext) {
            Assert.checkNotNullParam("ext", ext);
            this.extendedJBossXATerminator = ext;
            return this;
        }

        /**
         * Get the transaction manager.
         *
         * @return the transaction manager
         */
        public TransactionManager getTransactionManager() {
            return transactionManager;
        }

        /**
         * Set the transaction manager.
         *
         * @param tm the transaction manager
         */
        public Builder setTransactionManager(final TransactionManager tm) {
            Assert.checkNotNullParam("tm", tm);
            this.transactionManager = tm;
            return this;
        }

        /**
         * Set the xa resource recovery registry.
         *
         * @param reg xa resource recovery registry (must not be {@code null})
         */
        public Builder setXAResourceRecoveryRegistry(final XAResourceRecoveryRegistry reg) {
            Assert.checkNotNullParam("reg", reg);
            this.xaResourceRecoveryRegistry = reg;
            return this;
        }

        /**
         * Set the xa recovery log dir relative to path
         *
         * @param path the xa recovery log file relative to path (must not be {@code null}
         */
        public Builder setXARecoveryLogDirRelativeToPath(Path path) {
            Assert.checkNotNullParam("path", path);
            this.xaRecoveryLogDirRelativeToPath = path;
            return this;
        }

        /**
         * Build this provider.  If any required properties are {@code null}, an exception is thrown.
         *
         * @return the built provider (not {@code null})
         * @throws IllegalArgumentException if a required property is {@code null}
         */
        public JBossLocalTransactionProvider build() {
            ExtendedJBossXATerminator extendedJBossXATerminator = this.extendedJBossXATerminator;
            TransactionManager transactionManager = this.transactionManager;
            int staleTransactionTime = this.staleTransactionTime;
            Assert.checkNotNullParam("extendedJBossXATerminator", extendedJBossXATerminator);
            Assert.checkNotNullParam("transactionManager", transactionManager);
            Assert.checkMinimumParameter("staleTransactionTime", 0, staleTransactionTime);
            Assert.checkNotNullParam("xaResourceRecoveryRegistry", xaResourceRecoveryRegistry);
            if (transactionManager instanceof com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple
             || transactionManager instanceof com.arjuna.ats.jbossatx.jta.TransactionManagerDelegate) {
                return new JBossJTALocalTransactionProvider(staleTransactionTime, extendedJBossXATerminator,
                        transactionManager, xaResourceRecoveryRegistry, xaRecoveryLogDirRelativeToPath);
            } else if (transactionManager instanceof com.arjuna.ats.internal.jta.transaction.jts.TransactionManagerImple
             || transactionManager instanceof com.arjuna.ats.jbossatx.jts.TransactionManagerDelegate) {
                return new JBossJTSLocalTransactionProvider(staleTransactionTime, extendedJBossXATerminator,
                        transactionManager, xaResourceRecoveryRegistry, xaRecoveryLogDirRelativeToPath);
            } else {
                throw Log.log.unknownTransactionManagerType(transactionManager.getClass());
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy