<commit_before>/*
 * JBoss, Home of Professional Open Source
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * 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,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 *
 * (C) 2005-2006,
 * @author JBoss Inc.
 */
/*
 * Copyright (C) 2000, 2001,
 *
 * Arjuna Solutions Limited,
 * Newcastle upon Tyne,
 * Tyne and Wear,
 * UK.
 *
 * $Id: XARecoveryModule.java 2342 2006-03-30 13:06:17Z  $
 */

package com.arjuna.ats.internal.jta.recovery.arjunacore;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import com.arjuna.ats.arjuna.common.Uid;
import com.arjuna.ats.arjuna.exceptions.ObjectStoreException;
import com.arjuna.ats.arjuna.objectstore.RecoveryStore;
import com.arjuna.ats.arjuna.objectstore.StateStatus;
import com.arjuna.ats.arjuna.objectstore.StoreManager;
import com.arjuna.ats.arjuna.recovery.RecoveryManager;
import com.arjuna.ats.arjuna.recovery.RecoveryModule;
import com.arjuna.ats.arjuna.recovery.ExtendedRecoveryModule;
import com.arjuna.ats.arjuna.state.InputObjectState;
import com.arjuna.ats.internal.arjuna.common.UidHelper;
import com.arjuna.ats.internal.jta.resources.arjunacore.XAResourceRecord;
import com.arjuna.ats.jta.common.jtaPropertyManager;
import com.arjuna.ats.jta.logging.jtaLogger;
import com.arjuna.ats.jta.recovery.SerializableXAResourceDeserializer;
import com.arjuna.ats.jta.recovery.XARecoveryResource;
import com.arjuna.ats.jta.recovery.XARecoveryResourceManager;
import com.arjuna.ats.jta.recovery.XAResourceOrphanFilter;
import com.arjuna.ats.jta.recovery.XAResourceRecovery;
import com.arjuna.ats.jta.recovery.XAResourceRecoveryHelper;
import com.arjuna.ats.jta.utils.XAHelper;
import com.arjuna.ats.jta.utils.XARecoveryResourceHelper;

import com.arjuna.ats.jta.xa.XATxConverter;
import org.jboss.tm.XAResourceWrapper;

/**
 * Designed to be able to recover any XAResource.
 */

public class XARecoveryModule implements ExtendedRecoveryModule
{
	private final AtomicBoolean recoveryProblems = new AtomicBoolean(false);

	boolean isRecoveryProblems() {
		return this.recoveryProblems.get();
	}

	void setRecoveryProblems(final boolean recoveryProblems) {
		this.recoveryProblems.set(recoveryProblems);
	}

	public XARecoveryModule()
	{
		this(new com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryResourceManagerImple(),
                "Local XARecoveryModule");

		com.arjuna.ats.internal.jta.Implementations.initialise();
	}

    public Set<String> getContactedJndiNames() {
        return Collections.unmodifiableSet(contactedJndiNames);
    }

    @Override
    public boolean isPeriodicWorkSuccessful() {
        return !this.isRecoveryProblems();
    }

    public void addXAResourceRecoveryHelper(XAResourceRecoveryHelper xaResourceRecoveryHelper) {
        _xaResourceRecoveryHelpers.add(xaResourceRecoveryHelper);
    }

    public synchronized void removeXAResourceRecoveryHelper(XAResourceRecoveryHelper xaResourceRecoveryHelper) {
        switch (getScanState()) {
            case FIRST_PASS:
                waitForNotScanState(ScanStates.FIRST_PASS);
                this.removeXAResourceRecoveryHelper(xaResourceRecoveryHelper);
                return;
            case SECOND_PASS:
                if (isHelperInUse(xaResourceRecoveryHelper)) {
                    waitForScanState(ScanStates.IDLE);
                    // we are idle now and we can reiterate the call
                    this.removeXAResourceRecoveryHelper(xaResourceRecoveryHelper);
                    return;
                }
                // although in the middle of the second pass we know the XAResources
                //   are not originated from the helper, we are safe to remove them
                break;
            case BETWEEN_PASSES:
                // if we are in the between passes we still be sure the XAResources
                //  are not originated from the helper we are going to remove
                if (isHelperInUse(xaResourceRecoveryHelper)) {
                    waitForScanState(ScanStates.IDLE);
                    // we are idle now and we can reiterate the call
                    this.removeXAResourceRecoveryHelper(xaResourceRecoveryHelper);
                    return;
                }

                // XAResources managed by "the recovery helper to be removed" are not active
                //   we can finish them by simulating the second pass recovery, then removing the helper
                XAResource[] xaResources = recoveryHelpersXAResource.get(xaResourceRecoveryHelper);
                if (xaResources != null) {
                    for (XAResource xaResource : xaResources) {
						String jndiName = null;
						if(xaResource instanceof XAResourceWrapper) {
							jndiName = ((XAResourceWrapper)xaResource).getJndiName();
						}
						NameScopedXAResource nameScopedXAResource = new NameScopedXAResource(xaResource, jndiName);
                        xaRecoverySecondPass(nameScopedXAResource);
                        _resources.remove(nameScopedXAResource);
                    }
                } else {
                    jtaLogger.logger.debugf("nothing to remove - xa resources of recovery helper '%s' were null",
                            xaResourceRecoveryHelper);
                }
                break;
            case IDLE:
                // the periodic recovery is sleeping, we can remove helper safely
                break;
        }

        _xaResourceRecoveryHelpers.remove(xaResourceRecoveryHelper);
    }

    public void addXAResourceOrphanFilter(XAResourceOrphanFilter xaResourceOrphanFilter) {
        synchronized (_xaResourceOrphanFilters) {
            if(!_xaResourceOrphanFilters.contains(xaResourceOrphanFilter)) {
                _xaResourceOrphanFilters.add(xaResourceOrphanFilter);
            }
        }
    }

    public void removeXAResourceOrphanFilter(XAResourceOrphanFilter xaResourceOrphanFilter) {
        synchronized (_xaResourceOrphanFilters) {
            _xaResourceOrphanFilters.remove(xaResourceOrphanFilter);
        }
    }
    
	public void addSerializableXAResourceDeserializer(SerializableXAResourceDeserializer serializableXAResourceDeserializer) {
		_seriablizableXAResourceDeserializers.add(serializableXAResourceDeserializer);		
	}
	
	public List<SerializableXAResourceDeserializer> getSeriablizableXAResourceDeserializers() {
		return _seriablizableXAResourceDeserializers;
	}

	public synchronized void periodicWorkFirstPass() {
		periodicWorkFirstPass(ScanStates.BETWEEN_PASSES);
	}

	private synchronized void periodicWorkFirstPass(ScanStates endState)
	{
		// JBTM-1354 JCA needs to be able to recover XAResources associated with a subordinate transaction so we have to do at least
		// the start scan to make sure that we have loaded all the XAResources we possibly can to assist subordinate transactions recovering
		// the reason we can't do bottom up recovery is if this server has an XAResource which tries to recover a remote server (e.g. distributed JTA)
		// then we get deadlock on the secondpass
		if (getScanState() == ScanStates.BETWEEN_PASSES) {
			periodicWorkSecondPass();
			endState = ScanStates.BETWEEN_PASSES; // Ensure if originally we are between periodic recovery scans we continue in that state and leave XAResource in STARTRSCAN
		}

		setScanState(ScanStates.FIRST_PASS);

        if(jtaLogger.logger.isDebugEnabled()) {
            jtaLogger.logger.debugv("{0} - first pass", _logName);
        }

        contactedJndiNames.clear();
		this.setRecoveryProblems(false);

		_uids = new InputObjectState();

		/*
		 * Scan for resources in the object store.
		 */

		try
		{
			if (!_recoveryStore.allObjUids(_recoveryManagerClass.type(), _uids))
			{
				this.setRecoveryProblems(true);
                jtaLogger.i18NLogger.warn_recovery_alluids();
			}
		}
		catch (ObjectStoreException e)
		{
			this.setRecoveryProblems(true);
            jtaLogger.i18NLogger.warn_recovery_objstoreerror(e);
		}
		catch (Exception e)
		{
			this.setRecoveryProblems(true);
            jtaLogger.i18NLogger.warn_recovery_periodicfirstpass(_logName+".periodicWorkFirstPass", e);
		}
		// JBTM-1354 JCA needs to be able to recover XAResources associated with a subordinate transaction so we have to do at least
		// the start scan to make sure that we have loaded all the XAResources we possibly can to assist subordinate transactions recovering

		// scan using statically configured plugins;
		_resources = resourceInitiatedRecovery();
		// scan using dynamically configured plugins:
		_resources.addAll(resourceInitiatedRecoveryForRecoveryHelpers());

		List<NameScopedXAResource> resources = new ArrayList<>(_resources);
		for (NameScopedXAResource xaResource : resources) {
			try {
				xaRecoveryFirstPass(xaResource);
			} catch (Exception ex) {
				this.setRecoveryProblems(true);
				jtaLogger.i18NLogger.warn_recovery_getxaresource(ex);
			}
		}

		if (endState != ScanStates.BETWEEN_PASSES) {
			for (NameScopedXAResource xaResource : resources) {
				try {
					xaResource.getXaResource().recover(XAResource.TMENDRSCAN);
				} catch (Exception ex) {
					this.setRecoveryProblems(true);
					jtaLogger.i18NLogger.warn_recovery_getxaresource(ex);
				}
			}
        }

		setScanState(endState);
	}

	public synchronized void periodicWorkSecondPass()
	{
	    if (getScanState() == ScanStates.IDLE) {
	        // a call to getNewXAResource must have already ran the second pass so it is safe to return
			return;
		}

        setScanState(ScanStates.SECOND_PASS);

		if (jtaLogger.logger.isDebugEnabled())
		{
            jtaLogger.logger.debugv("{0} - second pass", _logName);
		}

		try
		{
			// do the recovery on anything from the scan in first pass

			transactionInitiatedRecovery();

			if (jtaLogger.logger.isDebugEnabled()) {
                jtaLogger.logger.debug(_logName
                        + ".transactionInitiatedRecovery completed");
            }

            bottomUpRecovery();

            if (jtaLogger.logger.isDebugEnabled()) {
                jtaLogger.logger.debug(_logName
                        + ".resourceInitiatedRecovery completed");
            }
		}
		catch (Exception e)
		{
			this.setRecoveryProblems(true);
            jtaLogger.i18NLogger.warn_recovery_periodicsecondpass(_logName+".periodicWorkSecondPass", e);
		}

		clearAllFailures();

        setScanState(ScanStates.IDLE);
 	}

 	public static XARecoveryModule getRegisteredXARecoveryModule () {
 		if (registeredXARecoveryModule == null) {
			RecoveryManager recMan = RecoveryManager.manager();
			Vector<RecoveryModule> recoveryModules = recMan.getModules();

			if (recoveryModules != null) {
				Enumeration<RecoveryModule> modules = recoveryModules.elements();

				while (modules.hasMoreElements()) {
					RecoveryModule m = modules.nextElement();

					if (m instanceof XARecoveryModule) {
						registeredXARecoveryModule = (XARecoveryModule) m;
						break;
					}
				}
			}
		}
		return registeredXARecoveryModule;
	}

	public String id()
	{
		return "XARecoveryModule:" + _recoveryManagerClass;
	}

	/**
	 * @param xid The transaction to commit/rollback.
	 *
	 * @return the XAResource than can be used to commit/rollback the specified
	 *         transaction.
	 */
	private XAResource getNewXAResource(Xid xid, String jndiName)
	{
		NameScopedXid key = new NameScopedXid(xid, jndiName);

		XAResource toReturn = getTheKey(key, false);

		if (toReturn == null) {
			synchronized (this) {
				/*
				 * run an xid scan with the lock held to avoid _xidScans being changed
				 * after the call to periodicWorkFirstPass but before the call to getTheKey
				 */
				periodicWorkFirstPass(ScanStates.IDLE);
				toReturn = getTheKey(key, false);
				if(toReturn == null) {
					// last resort, accept a weaker match if there is one
					toReturn = getTheKey(key, true);
				}
			}
		}

		return toReturn;
    }

	/*
	 An Xid value we're looking for, described from the objectstore log record by scopedXid,
	   also appears in the recovery xa scan of theKey.
	 However, that does not necessarily mean that theKey's XAResource can be used for recovering it
	   as non-unique (inflowed) xids may appear under multiple, potentially non-interchangeable, keys.
	 */
	private boolean isReasonableMatch(NameScopedXid scopedXid, NameScopedXAResource theKey, boolean relaxedMatch) {
		jtaLogger.logger.trace("isReasonableMatch "+scopedXid+" "+theKey+" "+relaxedMatch);

		// JTA Xids are always unique (see TransactionImple::createXid)
		// so can appear in only one place. Just ignore any metadata.
		if(scopedXid.getXid().getFormatId() == XATxConverter.FORMAT_ID) {
			jtaLogger.logger.trace("isReasonableMatch true by FORMAT_ID");
			return true;
		}

		// all other xids may be non-uniq branches and appear under more than one theKey
		// so we need some additional rules to try and get the right one...

		// where the jndi name in the log matches the resources name, it's a good bet.
		// this covers the ironjacamar integration case for databases in wildfly/EAP
		if(!scopedXid.isAnonymous() && scopedXid.isSameName(theKey)) {
			jtaLogger.logger.trace("isReasonableMatch true by exact name");
			return true;
		}

		// some integrations, notable HornetQ, provide name metadata in the enlistment side
		// (so it winds up in the logs and hence in scopedXid)
		// but not on the recovery side (so theKey is unnamed).
		// If we've failed to make a stronger match, then that will have to do,
		// even though it's potentially wrong.
		if(relaxedMatch && !(!scopedXid.isAnonymous() && !theKey.isAnonymous())) {
			jtaLogger.logger.trace("isReasonableMatch true by relaxed name");
			return true;
		}

		// at this point there is one remaining valid case... the names on both side are set and don't match
		// BUT it's still valid to recover the xid from theKey. That's the case only with legacy configurations
		// where N datasources point to the same RM and one could be used to recover on behalf of the others.
		// we no longer support that, since it's impossible to distinguish from matches that are simply incorrect.

		jtaLogger.logger.trace("isReasonableMatch false");
		return false;
	}

    private XAResource getTheKey(NameScopedXid scopedXid, boolean relaxedMatch) {
		if (_xidScans != null)
		{
			Enumeration<NameScopedXAResource> keys = _xidScans.keys();

			while (keys.hasMoreElements())
			{
				NameScopedXAResource theKey = keys.nextElement();

				RecoveryXids xids = _xidScans.get(theKey);

				// JBTM-1255 moved stale check back to bottomUpRecovery
				if (xids.contains(scopedXid.getXid()) && isReasonableMatch(scopedXid, theKey, relaxedMatch)) {
					// This Xid is going to be recovered by the AtomicAction
					// it is possible that the Xid is recovered by both txbridge and XATerminator - the second
					// would get noxaresource error message
					xids.remove(scopedXid.getXid());
					return theKey.getXaResource();
				}
			}
		}
		return null;
	}

       /**
        * @param xaResourceRecord The record to reassociate.
        *
        * @return the XAResource than can be used to commit/rollback the specified
        *         record.
        */
    public XAResource getNewXAResource(XAResourceRecord xaResourceRecord)
    {
		if (jtaLogger.logger.isTraceEnabled()) {
			jtaLogger.logger.trace("trying getNewXAResource for "+xaResourceRecord);
		}
        return getNewXAResource(xaResourceRecord.getXid(), xaResourceRecord.getJndiName());
    }

	protected XARecoveryModule(XARecoveryResourceManager recoveryClass, String logName)
    {
        _logName = logName;
        _recoveryManagerClass = recoveryClass;
        if(_recoveryManagerClass == null) {
			this.setRecoveryProblems(true);
            jtaLogger.i18NLogger.warn_recovery_constfail();
        }

        _xaRecoverers = jtaPropertyManager.getJTAEnvironmentBean().getXaResourceRecoveries();
        _xaResourceOrphanFilters = jtaPropertyManager.getJTAEnvironmentBean().getXaResourceOrphanFilters();
    }

	private final boolean transactionInitiatedRecovery()
	{
		Uid theUid = null;

		while (Uid.nullUid().notEquals(theUid))
		{
			try
			{
				theUid = UidHelper.unpackFrom(_uids);

				if (theUid.notEquals(Uid.nullUid()))
				{
					/*
					 * Ignore it if it isn't in the store any more. Transaction
					 * probably recovered it.
					 */

					if (_recoveryStore.currentState(theUid, _recoveryManagerClass
							.type()) != StateStatus.OS_UNKNOWN)
					{
						boolean problem = false;
						XARecoveryResource record = null;

						try
						{
							record = _recoveryManagerClass.getResource(theUid);

							problem = true;

							switch (record.recoverable())
							{
							case XARecoveryResource.RECOVERY_REQUIRED:
							{
								if (jtaLogger.logger.isDebugEnabled()) {
                                    jtaLogger.logger.debug("XARecovery attempting recovery of "
                                            + theUid);
                                }

								int recoveryStatus = record.recover();

								if (recoveryStatus != XARecoveryResource.RECOVERED_OK)
								{
									if (recoveryStatus == XARecoveryResource.WAITING_FOR_RECOVERY)
									{
									    // resource initiated recovery not possible (no distribution).

										problem = false;

                                        jtaLogger.i18NLogger.info_recovery_recoverydelayed(theUid, XARecoveryResourceHelper.stringForm(recoveryStatus));
									}
									else
									{
										this.setRecoveryProblems(true);
                                        jtaLogger.i18NLogger.warn_recovery_recoveryfailed(theUid, XARecoveryResourceHelper.stringForm(recoveryStatus));
									}
								}
								else
									problem = false;
							}
								break;
							case XARecoveryResource.INFLIGHT_TRANSACTION:
							{
								/*
								 * Transaction was inflight and between us
								 * noticing it and trying to access the state,
								 * it finished and removed the state.
								 */

								problem = false;
							}
								break;
							case XARecoveryResource.INCOMPLETE_STATE:
							default:
							{
								if (jtaLogger.logger.isDebugEnabled()) {
                                    jtaLogger.logger.debug("XARecovery " + theUid
                                            + " is non-recoverable");
                                }
							}
								break;
							}
						}
						catch (NullPointerException ex)
						{
							problem = true;
						}
						catch (Throwable e)
						{
							problem = true;

							this.setRecoveryProblems(true);
                            jtaLogger.i18NLogger.warn_recovery_recoveryerror(e);
						}

						if (problem && (record != null))
						{
							/*
							 * Some error occurred which prevented the state of
							 * the resource from being read from the log. Hence
							 * we don't have a valid key to use to insert it
							 * into the list of records to be recovered. Print a
							 * warning and move on. Force recovery via the
							 * administration tool. Should be a rare occurrence.
							 */

							if (record.getXid() == null)
							{
								this.setRecoveryProblems(true);
                                jtaLogger.i18NLogger.warn_recovery_cannotadd();
							}
							else
							{
								addFailure(record.getXid(), record.get_uid());
							}
						}
					}
				}
			}
			catch (IOException e)
			{
				theUid = Uid.nullUid();
			}
			catch (Throwable e)
			{
				this.setRecoveryProblems(true);
                jtaLogger.i18NLogger.warn_recovery_unexpectedrecoveryerror(e);
			}
		}

		return true;
	}

	/**
	 * 
	 * JBTM-895 garbage collection is now done when we return XAResources @see XARecoveryModule#getNewXAResource(XAResourceRecord)
	 * @see XARecoveryModule#getNewXAResource(XAResourceRecord)
	 */
    private void bottomUpRecovery() {
			for (NameScopedXAResource xaResource : _resources) {
				try {
					xaRecoverySecondPass(xaResource);
				} catch (Exception ex) {
					this.setRecoveryProblems(true);
					jtaLogger.i18NLogger.warn_recovery_getxaresource(ex);
				}
			}


        // JBTM-895 garbage collection is now done when we return XAResources {@see XARecoveryModule#getNewXAResource(XAResourceRecord)}
        // JBTM-924 requires this here garbage collection, see JBTM-1155:
        if (_xidScans != null) {
            Set<NameScopedXAResource> keys = new HashSet<>(_xidScans.keySet());
            for(NameScopedXAResource theKey : keys) {
                RecoveryXids recoveryXids = _xidScans.get(theKey);
                if(recoveryXids.isStale()) {
                    _xidScans.remove(theKey);
                }
            }
        }
    }

	/**
	 * Now check for any outstanding transactions. If we didn't fail to recover
	 * them, then roll them back - if they'd got through prepare we would have
	 * an entry within the object store.
	 *
	 * Rely upon _xaRecoverers being set up properly (via properties).
	 *
	 * We cannot just remember the XAResourceRecords we used (if any) to cache
	 * the JDBC connection information and use that since we may never have had
	 * any such records!
	 *
	 * IMPORTANT: resourceInitiatedRecovery may rollback transactions which are
	 * inflight: just because we have no entry for a transaction in the object
	 * store does not mean it does not exist - it may be *about* to write its
	 * intentions list. To try to reduce this probability we remember potential
	 * rollback-ees at this scan, and wait for the next scan before actually
	 * rolling them back.
	 *
	 * Note we cannot use the method that works with Transactions and
	 * TransactionalObjects, of checking with original process that created the
	 * transaction, because we don't know which process it was.
	 */

	private final List<NameScopedXAResource> resourceInitiatedRecovery()
	{
		/*
		 * Now any additional connections we may need to create. Relies upon
		 * information provided by the application.
		 */

		List<NameScopedXAResource> xaresources = new ArrayList<>();
		if (_xaRecoverers.size() > 0)
		{
			for (int i = 0; i < _xaRecoverers.size(); i++)
			{
				try
				{
					XAResourceRecovery ri = _xaRecoverers.get(i);

					while (ri.hasMoreResources())
					{
						XAResource xaResource = ri.getXAResource();

						String jndiName = null;
						if(xaResource instanceof XAResourceWrapper) {
							jndiName = ((XAResourceWrapper)xaResource).getJndiName();
						}

						xaresources.add(new NameScopedXAResource(xaResource, jndiName));
					}
				}
				catch (Exception ex)
				{
					this.setRecoveryProblems(true);
                    jtaLogger.i18NLogger.warn_recovery_getxaresource(ex);
				}
			}
		}

		return xaresources;
	}

    private List<NameScopedXAResource> resourceInitiatedRecoveryForRecoveryHelpers()
    {
		List<NameScopedXAResource> xaresources = new ArrayList<>();

        recoveryHelpersXAResource.clear();

        for (XAResourceRecoveryHelper xaResourceRecoveryHelper : _xaResourceRecoveryHelpers)
        {
            try
            {
                XAResource[] xaResources = xaResourceRecoveryHelper.getXAResources();
                if (xaResources != null)
                {
                    for (XAResource xaResource : xaResources)
                    {
						String jndiName = null;
						if(xaResource instanceof XAResourceWrapper) {
							jndiName = ((XAResourceWrapper)xaResource).getJndiName();
						}

                        xaresources.add(new NameScopedXAResource(xaResource, jndiName));
                    }
                    recoveryHelpersXAResource.put(xaResourceRecoveryHelper, xaResources);
                }
            }
            catch (Exception ex)
            {
				this.setRecoveryProblems(true);
                jtaLogger.i18NLogger.warn_recovery_getxaresource(ex);
            }
        }

        return xaresources;
    }


	private final void xaRecoveryFirstPass(NameScopedXAResource xares)
	{
		if (jtaLogger.logger.isDebugEnabled()) {
            jtaLogger.logger.debug("xarecovery of " + xares);
        }

		
			Xid[] trans = null;

			try
			{
				trans = xares.getXaResource().recover(XAResource.TMSTARTRSCAN);

				if (jtaLogger.logger.isDebugEnabled()) {
                    jtaLogger.logger.debug("Found "
                            + ((trans != null) ? trans.length : 0)
                            + " xids in doubt");
                }
				if (jtaLogger.logger.isTraceEnabled()) {
					for (Xid xid : trans) {
						byte[] globalTransactionId = xid.getGlobalTransactionId();
						byte[] branchQualifier = xid.getBranchQualifier();
	
						StringBuilder stringBuilder = new StringBuilder();
						stringBuilder.append("< ");
						stringBuilder.append(xid.getFormatId());
						stringBuilder.append(", ");
						stringBuilder.append(globalTransactionId.length);
						stringBuilder.append(", ");
						stringBuilder.append(branchQualifier.length);
						stringBuilder.append(", ");
						for (int i = 0; i < globalTransactionId.length; i++) {
							stringBuilder.append(globalTransactionId[i]);
						}
						stringBuilder.append(", ");<commit_msg>JBTM-3414 Update to add null check with trans on Xids trace logging during xaRecoveryFirstPass<commit_after>/*
 * JBoss, Home of Professional Open Source
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * 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,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 *
 * (C) 2005-2006,
 * @author JBoss Inc.
 */
/*
 * Copyright (C) 2000, 2001,
 *
 * Arjuna Solutions Limited,
 * Newcastle upon Tyne,
 * Tyne and Wear,
 * UK.
 *
 * $Id: XARecoveryModule.java 2342 2006-03-30 13:06:17Z  $
 */

package com.arjuna.ats.internal.jta.recovery.arjunacore;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import com.arjuna.ats.arjuna.common.Uid;
import com.arjuna.ats.arjuna.exceptions.ObjectStoreException;
import com.arjuna.ats.arjuna.objectstore.RecoveryStore;
import com.arjuna.ats.arjuna.objectstore.StateStatus;
import com.arjuna.ats.arjuna.objectstore.StoreManager;
import com.arjuna.ats.arjuna.recovery.RecoveryManager;
import com.arjuna.ats.arjuna.recovery.RecoveryModule;
import com.arjuna.ats.arjuna.recovery.ExtendedRecoveryModule;
import com.arjuna.ats.arjuna.state.InputObjectState;
import com.arjuna.ats.internal.arjuna.common.UidHelper;
import com.arjuna.ats.internal.jta.resources.arjunacore.XAResourceRecord;
import com.arjuna.ats.jta.common.jtaPropertyManager;
import com.arjuna.ats.jta.logging.jtaLogger;
import com.arjuna.ats.jta.recovery.SerializableXAResourceDeserializer;
import com.arjuna.ats.jta.recovery.XARecoveryResource;
import com.arjuna.ats.jta.recovery.XARecoveryResourceManager;
import com.arjuna.ats.jta.recovery.XAResourceOrphanFilter;
import com.arjuna.ats.jta.recovery.XAResourceRecovery;
import com.arjuna.ats.jta.recovery.XAResourceRecoveryHelper;
import com.arjuna.ats.jta.utils.XAHelper;
import com.arjuna.ats.jta.utils.XARecoveryResourceHelper;

import com.arjuna.ats.jta.xa.XATxConverter;
import org.jboss.tm.XAResourceWrapper;

/**
 * Designed to be able to recover any XAResource.
 */

public class XARecoveryModule implements ExtendedRecoveryModule
{
	private final AtomicBoolean recoveryProblems = new AtomicBoolean(false);

	boolean isRecoveryProblems() {
		return this.recoveryProblems.get();
	}

	void setRecoveryProblems(final boolean recoveryProblems) {
		this.recoveryProblems.set(recoveryProblems);
	}

	public XARecoveryModule()
	{
		this(new com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryResourceManagerImple(),
                "Local XARecoveryModule");

		com.arjuna.ats.internal.jta.Implementations.initialise();
	}

    public Set<String> getContactedJndiNames() {
        return Collections.unmodifiableSet(contactedJndiNames);
    }

    @Override
    public boolean isPeriodicWorkSuccessful() {
        return !this.isRecoveryProblems();
    }

    public void addXAResourceRecoveryHelper(XAResourceRecoveryHelper xaResourceRecoveryHelper) {
        _xaResourceRecoveryHelpers.add(xaResourceRecoveryHelper);
    }

    public synchronized void removeXAResourceRecoveryHelper(XAResourceRecoveryHelper xaResourceRecoveryHelper) {
        switch (getScanState()) {
            case FIRST_PASS:
                waitForNotScanState(ScanStates.FIRST_PASS);
                this.removeXAResourceRecoveryHelper(xaResourceRecoveryHelper);
                return;
            case SECOND_PASS:
                if (isHelperInUse(xaResourceRecoveryHelper)) {
                    waitForScanState(ScanStates.IDLE);
                    // we are idle now and we can reiterate the call
                    this.removeXAResourceRecoveryHelper(xaResourceRecoveryHelper);
                    return;
                }
                // although in the middle of the second pass we know the XAResources
                //   are not originated from the helper, we are safe to remove them
                break;
            case BETWEEN_PASSES:
                // if we are in the between passes we still be sure the XAResources
                //  are not originated from the helper we are going to remove
                if (isHelperInUse(xaResourceRecoveryHelper)) {
                    waitForScanState(ScanStates.IDLE);
                    // we are idle now and we can reiterate the call
                    this.removeXAResourceRecoveryHelper(xaResourceRecoveryHelper);
                    return;
                }

                // XAResources managed by "the recovery helper to be removed" are not active
                //   we can finish them by simulating the second pass recovery, then removing the helper
                XAResource[] xaResources = recoveryHelpersXAResource.get(xaResourceRecoveryHelper);
                if (xaResources != null) {
                    for (XAResource xaResource : xaResources) {
						String jndiName = null;
						if(xaResource instanceof XAResourceWrapper) {
							jndiName = ((XAResourceWrapper)xaResource).getJndiName();
						}
						NameScopedXAResource nameScopedXAResource = new NameScopedXAResource(xaResource, jndiName);
                        xaRecoverySecondPass(nameScopedXAResource);
                        _resources.remove(nameScopedXAResource);
                    }
                } else {
                    jtaLogger.logger.debugf("nothing to remove - xa resources of recovery helper '%s' were null",
                            xaResourceRecoveryHelper);
                }
                break;
            case IDLE:
                // the periodic recovery is sleeping, we can remove helper safely
                break;
        }

        _xaResourceRecoveryHelpers.remove(xaResourceRecoveryHelper);
    }

    public void addXAResourceOrphanFilter(XAResourceOrphanFilter xaResourceOrphanFilter) {
        synchronized (_xaResourceOrphanFilters) {
            if(!_xaResourceOrphanFilters.contains(xaResourceOrphanFilter)) {
                _xaResourceOrphanFilters.add(xaResourceOrphanFilter);
            }
        }
    }

    public void removeXAResourceOrphanFilter(XAResourceOrphanFilter xaResourceOrphanFilter) {
        synchronized (_xaResourceOrphanFilters) {
            _xaResourceOrphanFilters.remove(xaResourceOrphanFilter);
        }
    }
    
	public void addSerializableXAResourceDeserializer(SerializableXAResourceDeserializer serializableXAResourceDeserializer) {
		_seriablizableXAResourceDeserializers.add(serializableXAResourceDeserializer);		
	}
	
	public List<SerializableXAResourceDeserializer> getSeriablizableXAResourceDeserializers() {
		return _seriablizableXAResourceDeserializers;
	}

	public synchronized void periodicWorkFirstPass() {
		periodicWorkFirstPass(ScanStates.BETWEEN_PASSES);
	}

	private synchronized void periodicWorkFirstPass(ScanStates endState)
	{
		// JBTM-1354 JCA needs to be able to recover XAResources associated with a subordinate transaction so we have to do at least
		// the start scan to make sure that we have loaded all the XAResources we possibly can to assist subordinate transactions recovering
		// the reason we can't do bottom up recovery is if this server has an XAResource which tries to recover a remote server (e.g. distributed JTA)
		// then we get deadlock on the secondpass
		if (getScanState() == ScanStates.BETWEEN_PASSES) {
			periodicWorkSecondPass();
			endState = ScanStates.BETWEEN_PASSES; // Ensure if originally we are between periodic recovery scans we continue in that state and leave XAResource in STARTRSCAN
		}

		setScanState(ScanStates.FIRST_PASS);

        if(jtaLogger.logger.isDebugEnabled()) {
            jtaLogger.logger.debugv("{0} - first pass", _logName);
        }

        contactedJndiNames.clear();
		this.setRecoveryProblems(false);

		_uids = new InputObjectState();

		/*
		 * Scan for resources in the object store.
		 */

		try
		{
			if (!_recoveryStore.allObjUids(_recoveryManagerClass.type(), _uids))
			{
				this.setRecoveryProblems(true);
                jtaLogger.i18NLogger.warn_recovery_alluids();
			}
		}
		catch (ObjectStoreException e)
		{
			this.setRecoveryProblems(true);
            jtaLogger.i18NLogger.warn_recovery_objstoreerror(e);
		}
		catch (Exception e)
		{
			this.setRecoveryProblems(true);
            jtaLogger.i18NLogger.warn_recovery_periodicfirstpass(_logName+".periodicWorkFirstPass", e);
		}
		// JBTM-1354 JCA needs to be able to recover XAResources associated with a subordinate transaction so we have to do at least
		// the start scan to make sure that we have loaded all the XAResources we possibly can to assist subordinate transactions recovering

		// scan using statically configured plugins;
		_resources = resourceInitiatedRecovery();
		// scan using dynamically configured plugins:
		_resources.addAll(resourceInitiatedRecoveryForRecoveryHelpers());

		List<NameScopedXAResource> resources = new ArrayList<>(_resources);
		for (NameScopedXAResource xaResource : resources) {
			try {
				xaRecoveryFirstPass(xaResource);
			} catch (Exception ex) {
				this.setRecoveryProblems(true);
				jtaLogger.i18NLogger.warn_recovery_getxaresource(ex);
			}
		}

		if (endState != ScanStates.BETWEEN_PASSES) {
			for (NameScopedXAResource xaResource : resources) {
				try {
					xaResource.getXaResource().recover(XAResource.TMENDRSCAN);
				} catch (Exception ex) {
					this.setRecoveryProblems(true);
					jtaLogger.i18NLogger.warn_recovery_getxaresource(ex);
				}
			}
        }

		setScanState(endState);
	}

	public synchronized void periodicWorkSecondPass()
	{
	    if (getScanState() == ScanStates.IDLE) {
	        // a call to getNewXAResource must have already ran the second pass so it is safe to return
			return;
		}

        setScanState(ScanStates.SECOND_PASS);

		if (jtaLogger.logger.isDebugEnabled())
		{
            jtaLogger.logger.debugv("{0} - second pass", _logName);
		}

		try
		{
			// do the recovery on anything from the scan in first pass

			transactionInitiatedRecovery();

			if (jtaLogger.logger.isDebugEnabled()) {
                jtaLogger.logger.debug(_logName
                        + ".transactionInitiatedRecovery completed");
            }

            bottomUpRecovery();

            if (jtaLogger.logger.isDebugEnabled()) {
                jtaLogger.logger.debug(_logName
                        + ".resourceInitiatedRecovery completed");
            }
		}
		catch (Exception e)
		{
			this.setRecoveryProblems(true);
            jtaLogger.i18NLogger.warn_recovery_periodicsecondpass(_logName+".periodicWorkSecondPass", e);
		}

		clearAllFailures();

        setScanState(ScanStates.IDLE);
 	}

 	public static XARecoveryModule getRegisteredXARecoveryModule () {
 		if (registeredXARecoveryModule == null) {
			RecoveryManager recMan = RecoveryManager.manager();
			Vector<RecoveryModule> recoveryModules = recMan.getModules();

			if (recoveryModules != null) {
				Enumeration<RecoveryModule> modules = recoveryModules.elements();

				while (modules.hasMoreElements()) {
					RecoveryModule m = modules.nextElement();

					if (m instanceof XARecoveryModule) {
						registeredXARecoveryModule = (XARecoveryModule) m;
						break;
					}
				}
			}
		}
		return registeredXARecoveryModule;
	}

	public String id()
	{
		return "XARecoveryModule:" + _recoveryManagerClass;
	}

	/**
	 * @param xid The transaction to commit/rollback.
	 *
	 * @return the XAResource than can be used to commit/rollback the specified
	 *         transaction.
	 */
	private XAResource getNewXAResource(Xid xid, String jndiName)
	{
		NameScopedXid key = new NameScopedXid(xid, jndiName);

		XAResource toReturn = getTheKey(key, false);

		if (toReturn == null) {
			synchronized (this) {
				/*
				 * run an xid scan with the lock held to avoid _xidScans being changed
				 * after the call to periodicWorkFirstPass but before the call to getTheKey
				 */
				periodicWorkFirstPass(ScanStates.IDLE);
				toReturn = getTheKey(key, false);
				if(toReturn == null) {
					// last resort, accept a weaker match if there is one
					toReturn = getTheKey(key, true);
				}
			}
		}

		return toReturn;
    }

	/*
	 An Xid value we're looking for, described from the objectstore log record by scopedXid,
	   also appears in the recovery xa scan of theKey.
	 However, that does not necessarily mean that theKey's XAResource can be used for recovering it
	   as non-unique (inflowed) xids may appear under multiple, potentially non-interchangeable, keys.
	 */
	private boolean isReasonableMatch(NameScopedXid scopedXid, NameScopedXAResource theKey, boolean relaxedMatch) {
		jtaLogger.logger.trace("isReasonableMatch "+scopedXid+" "+theKey+" "+relaxedMatch);

		// JTA Xids are always unique (see TransactionImple::createXid)
		// so can appear in only one place. Just ignore any metadata.
		if(scopedXid.getXid().getFormatId() == XATxConverter.FORMAT_ID) {
			jtaLogger.logger.trace("isReasonableMatch true by FORMAT_ID");
			return true;
		}

		// all other xids may be non-uniq branches and appear under more than one theKey
		// so we need some additional rules to try and get the right one...

		// where the jndi name in the log matches the resources name, it's a good bet.
		// this covers the ironjacamar integration case for databases in wildfly/EAP
		if(!scopedXid.isAnonymous() && scopedXid.isSameName(theKey)) {
			jtaLogger.logger.trace("isReasonableMatch true by exact name");
			return true;
		}

		// some integrations, notable HornetQ, provide name metadata in the enlistment side
		// (so it winds up in the logs and hence in scopedXid)
		// but not on the recovery side (so theKey is unnamed).
		// If we've failed to make a stronger match, then that will have to do,
		// even though it's potentially wrong.
		if(relaxedMatch && !(!scopedXid.isAnonymous() && !theKey.isAnonymous())) {
			jtaLogger.logger.trace("isReasonableMatch true by relaxed name");
			return true;
		}

		// at this point there is one remaining valid case... the names on both side are set and don't match
		// BUT it's still valid to recover the xid from theKey. That's the case only with legacy configurations
		// where N datasources point to the same RM and one could be used to recover on behalf of the others.
		// we no longer support that, since it's impossible to distinguish from matches that are simply incorrect.

		jtaLogger.logger.trace("isReasonableMatch false");
		return false;
	}

    private XAResource getTheKey(NameScopedXid scopedXid, boolean relaxedMatch) {
		if (_xidScans != null)
		{
			Enumeration<NameScopedXAResource> keys = _xidScans.keys();

			while (keys.hasMoreElements())
			{
				NameScopedXAResource theKey = keys.nextElement();

				RecoveryXids xids = _xidScans.get(theKey);

				// JBTM-1255 moved stale check back to bottomUpRecovery
				if (xids.contains(scopedXid.getXid()) && isReasonableMatch(scopedXid, theKey, relaxedMatch)) {
					// This Xid is going to be recovered by the AtomicAction
					// it is possible that the Xid is recovered by both txbridge and XATerminator - the second
					// would get noxaresource error message
					xids.remove(scopedXid.getXid());
					return theKey.getXaResource();
				}
			}
		}
		return null;
	}

       /**
        * @param xaResourceRecord The record to reassociate.
        *
        * @return the XAResource than can be used to commit/rollback the specified
        *         record.
        */
    public XAResource getNewXAResource(XAResourceRecord xaResourceRecord)
    {
		if (jtaLogger.logger.isTraceEnabled()) {
			jtaLogger.logger.trace("trying getNewXAResource for "+xaResourceRecord);
		}
        return getNewXAResource(xaResourceRecord.getXid(), xaResourceRecord.getJndiName());
    }

	protected XARecoveryModule(XARecoveryResourceManager recoveryClass, String logName)
    {
        _logName = logName;
        _recoveryManagerClass = recoveryClass;
        if(_recoveryManagerClass == null) {
			this.setRecoveryProblems(true);
            jtaLogger.i18NLogger.warn_recovery_constfail();
        }

        _xaRecoverers = jtaPropertyManager.getJTAEnvironmentBean().getXaResourceRecoveries();
        _xaResourceOrphanFilters = jtaPropertyManager.getJTAEnvironmentBean().getXaResourceOrphanFilters();
    }

	private final boolean transactionInitiatedRecovery()
	{
		Uid theUid = null;

		while (Uid.nullUid().notEquals(theUid))
		{
			try
			{
				theUid = UidHelper.unpackFrom(_uids);

				if (theUid.notEquals(Uid.nullUid()))
				{
					/*
					 * Ignore it if it isn't in the store any more. Transaction
					 * probably recovered it.
					 */

					if (_recoveryStore.currentState(theUid, _recoveryManagerClass
							.type()) != StateStatus.OS_UNKNOWN)
					{
						boolean problem = false;
						XARecoveryResource record = null;

						try
						{
							record = _recoveryManagerClass.getResource(theUid);

							problem = true;

							switch (record.recoverable())
							{
							case XARecoveryResource.RECOVERY_REQUIRED:
							{
								if (jtaLogger.logger.isDebugEnabled()) {
                                    jtaLogger.logger.debug("XARecovery attempting recovery of "
                                            + theUid);
                                }

								int recoveryStatus = record.recover();

								if (recoveryStatus != XARecoveryResource.RECOVERED_OK)
								{
									if (recoveryStatus == XARecoveryResource.WAITING_FOR_RECOVERY)
									{
									    // resource initiated recovery not possible (no distribution).

										problem = false;

                                        jtaLogger.i18NLogger.info_recovery_recoverydelayed(theUid, XARecoveryResourceHelper.stringForm(recoveryStatus));
									}
									else
									{
										this.setRecoveryProblems(true);
                                        jtaLogger.i18NLogger.warn_recovery_recoveryfailed(theUid, XARecoveryResourceHelper.stringForm(recoveryStatus));
									}
								}
								else
									problem = false;
							}
								break;
							case XARecoveryResource.INFLIGHT_TRANSACTION:
							{
								/*
								 * Transaction was inflight and between us
								 * noticing it and trying to access the state,
								 * it finished and removed the state.
								 */

								problem = false;
							}
								break;
							case XARecoveryResource.INCOMPLETE_STATE:
							default:
							{
								if (jtaLogger.logger.isDebugEnabled()) {
                                    jtaLogger.logger.debug("XARecovery " + theUid
                                            + " is non-recoverable");
                                }
							}
								break;
							}
						}
						catch (NullPointerException ex)
						{
							problem = true;
						}
						catch (Throwable e)
						{
							problem = true;

							this.setRecoveryProblems(true);
                            jtaLogger.i18NLogger.warn_recovery_recoveryerror(e);
						}

						if (problem && (record != null))
						{
							/*
							 * Some error occurred which prevented the state of
							 * the resource from being read from the log. Hence
							 * we don't have a valid key to use to insert it
							 * into the list of records to be recovered. Print a
							 * warning and move on. Force recovery via the
							 * administration tool. Should be a rare occurrence.
							 */

							if (record.getXid() == null)
							{
								this.setRecoveryProblems(true);
                                jtaLogger.i18NLogger.warn_recovery_cannotadd();
							}
							else
							{
								addFailure(record.getXid(), record.get_uid());
							}
						}
					}
				}
			}
			catch (IOException e)
			{
				theUid = Uid.nullUid();
			}
			catch (Throwable e)
			{
				this.setRecoveryProblems(true);
                jtaLogger.i18NLogger.warn_recovery_unexpectedrecoveryerror(e);
			}
		}

		return true;
	}

	/**
	 * 
	 * JBTM-895 garbage collection is now done when we return XAResources @see XARecoveryModule#getNewXAResource(XAResourceRecord)
	 * @see XARecoveryModule#getNewXAResource(XAResourceRecord)
	 */
    private void bottomUpRecovery() {
			for (NameScopedXAResource xaResource : _resources) {
				try {
					xaRecoverySecondPass(xaResource);
				} catch (Exception ex) {
					this.setRecoveryProblems(true);
					jtaLogger.i18NLogger.warn_recovery_getxaresource(ex);
				}
			}


        // JBTM-895 garbage collection is now done when we return XAResources {@see XARecoveryModule#getNewXAResource(XAResourceRecord)}
        // JBTM-924 requires this here garbage collection, see JBTM-1155:
        if (_xidScans != null) {
            Set<NameScopedXAResource> keys = new HashSet<>(_xidScans.keySet());
            for(NameScopedXAResource theKey : keys) {
                RecoveryXids recoveryXids = _xidScans.get(theKey);
                if(recoveryXids.isStale()) {
                    _xidScans.remove(theKey);
                }
            }
        }
    }

	/**
	 * Now check for any outstanding transactions. If we didn't fail to recover
	 * them, then roll them back - if they'd got through prepare we would have
	 * an entry within the object store.
	 *
	 * Rely upon _xaRecoverers being set up properly (via properties).
	 *
	 * We cannot just remember the XAResourceRecords we used (if any) to cache
	 * the JDBC connection information and use that since we may never have had
	 * any such records!
	 *
	 * IMPORTANT: resourceInitiatedRecovery may rollback transactions which are
	 * inflight: just because we have no entry for a transaction in the object
	 * store does not mean it does not exist - it may be *about* to write its
	 * intentions list. To try to reduce this probability we remember potential
	 * rollback-ees at this scan, and wait for the next scan before actually
	 * rolling them back.
	 *
	 * Note we cannot use the method that works with Transactions and
	 * TransactionalObjects, of checking with original process that created the
	 * transaction, because we don't know which process it was.
	 */

	private final List<NameScopedXAResource> resourceInitiatedRecovery()
	{
		/*
		 * Now any additional connections we may need to create. Relies upon
		 * information provided by the application.
		 */

		List<NameScopedXAResource> xaresources = new ArrayList<>();
		if (_xaRecoverers.size() > 0)
		{
			for (int i = 0; i < _xaRecoverers.size(); i++)
			{
				try
				{
					XAResourceRecovery ri = _xaRecoverers.get(i);

					while (ri.hasMoreResources())
					{
						XAResource xaResource = ri.getXAResource();

						String jndiName = null;
						if(xaResource instanceof XAResourceWrapper) {
							jndiName = ((XAResourceWrapper)xaResource).getJndiName();
						}

						xaresources.add(new NameScopedXAResource(xaResource, jndiName));
					}
				}
				catch (Exception ex)
				{
					this.setRecoveryProblems(true);
                    jtaLogger.i18NLogger.warn_recovery_getxaresource(ex);
				}
			}
		}

		return xaresources;
	}

    private List<NameScopedXAResource> resourceInitiatedRecoveryForRecoveryHelpers()
    {
		List<NameScopedXAResource> xaresources = new ArrayList<>();

        recoveryHelpersXAResource.clear();

        for (XAResourceRecoveryHelper xaResourceRecoveryHelper : _xaResourceRecoveryHelpers)
        {
            try
            {
                XAResource[] xaResources = xaResourceRecoveryHelper.getXAResources();
                if (xaResources != null)
                {
                    for (XAResource xaResource : xaResources)
                    {
						String jndiName = null;
						if(xaResource instanceof XAResourceWrapper) {
							jndiName = ((XAResourceWrapper)xaResource).getJndiName();
						}

                        xaresources.add(new NameScopedXAResource(xaResource, jndiName));
                    }
                    recoveryHelpersXAResource.put(xaResourceRecoveryHelper, xaResources);
                }
            }
            catch (Exception ex)
            {
				this.setRecoveryProblems(true);
                jtaLogger.i18NLogger.warn_recovery_getxaresource(ex);
            }
        }

        return xaresources;
    }


	private final void xaRecoveryFirstPass(NameScopedXAResource xares)
	{
		if (jtaLogger.logger.isDebugEnabled()) {
            jtaLogger.logger.debug("xarecovery of " + xares);
        }

		
			Xid[] trans = null;

			try
			{
				trans = xares.getXaResource().recover(XAResource.TMSTARTRSCAN);

				if (jtaLogger.logger.isDebugEnabled()) {
                    jtaLogger.logger.debug("Found "
                            + ((trans != null) ? trans.length : 0)
                            + " xids in doubt");
                }
				if (jtaLogger.logger.isTraceEnabled() && trans != null) {
					for (Xid xid : trans) {
						byte[] globalTransactionId = xid.getGlobalTransactionId();
						byte[] branchQualifier = xid.getBranchQualifier();
	
						StringBuilder stringBuilder = new StringBuilder();
						stringBuilder.append("< ");
						stringBuilder.append(xid.getFormatId());
						stringBuilder.append(", ");
						stringBuilder.append(globalTransactionId.length);
						stringBuilder.append(", ");
						stringBuilder.append(branchQualifier.length);
						stringBuilder.append(", ");
						for (int i = 0; i < globalTransactionId.length; i++) {
							stringBuilder.append(globalTransactionId[i]);
						}
						stringBuilder.append(", ");
{'fraction_non_alphanumeric': 0.08222230687185739, 'fraction_numerical': 0.0044187109553557825, 'mean_word_length': 2.762990037984663, 'pattern_counts': {'":': 0, '<': 39, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 16, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4944977', 'n_tokens_mistral': 18124, 'n_tokens_neox': 15207, 'n_words': 3817}