/*
 * 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) 1998, 1999, 2000, 2001, 2002, 2003
 *
 * Arjuna Technologies Ltd.
 * Newcastle upon Tyne,
 * Tyne and Wear,
 * UK.
 *
 * $Id: TransactionManagerService.java,v 1.17 2005/06/24 15:24:14 kconner Exp $
 */
package com.arjuna.ats.jbossatx.jts;

import org.jboss.bootstrap.spi.ServerConfig;
import org.jboss.iiop.CorbaORBService;
import org.jboss.tm.*;
import org.jboss.logging.Logger;
import com.arjuna.ats.internal.jbossatx.jts.PropagationContextWrapper;
import com.arjuna.ats.internal.jbossatx.jts.jca.XATerminator;
import com.arjuna.ats.internal.jbossatx.agent.LocalJBossAgentImpl;
import com.arjuna.ats.internal.jta.transaction.jts.UserTransactionImple;
import com.arjuna.ats.internal.jta.transaction.jts.TransactionSynchronizationRegistryImple;

// yes we do want the arjunacore version of XARecoveryModule. It's the base class for the
// JTS version and misconfiuration is common, so it's better to use core, thus allowing for either.
import com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule;
// the resource wrapper code is common to JTA and JTS.  Import it from JTA rather than duplicating.
import com.arjuna.ats.internal.jbossatx.jta.XAResourceRecoveryHelperWrapper;

import com.arjuna.ats.jta.utils.JNDIManager;
import com.arjuna.ats.jta.common.Environment;
import com.arjuna.ats.jta.common.jtaPropertyManager;
import com.arjuna.ats.jts.common.jtsPropertyManager;
import com.arjuna.ats.jts.common.Configuration;
import com.arjuna.ats.arjuna.coordinator.TransactionReaper;
import com.arjuna.ats.arjuna.coordinator.TxStats;
import com.arjuna.ats.arjuna.recovery.RecoveryManager;
import com.arjuna.ats.arjuna.recovery.RecoveryModule;
import com.arjuna.ats.arjuna.utils.Utility;
import com.arjuna.ats.arjuna.common.arjPropertyManager;
import com.arjuna.orbportability.ORB;
import com.arjuna.orbportability.OA;

import com.arjuna.ats.internal.tsmx.mbeans.PropertyServiceJMXPlugin;
import com.arjuna.ats.internal.jts.recovery.RecoveryORBManager;
import com.arjuna.ats.internal.jts.ORBManager;
import com.arjuna.common.util.propertyservice.PropertyManagerFactory;
import com.arjuna.common.util.propertyservice.PropertyManager;
import com.arjuna.common.util.logging.LogFactory;

import javax.management.*;
import javax.naming.Reference;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import javax.transaction.TransactionSynchronizationRegistry;
import java.net.Socket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.Vector;

/**
 * JBoss Transaction Manager Service.
 *
 * @author Richard A. Begg (richard.begg@arjuna.com)
 * @version $Id: TransactionManagerService.java,v 1.17 2005/06/24 15:24:14 kconner Exp $
 */
public class TransactionManagerService implements TransactionManagerServiceMBean, XAResourceRecoveryRegistry
{
    /*
    deploy/transaction-jboss-beans.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <deployment xmlns="urn:jboss:bean-deployer:2.0">

    <bean name="TransactionManager" class="com.arjuna.ats.jbossatx.jts.TransactionManagerService">
        <annotation>@org.jboss.aop.microcontainer.aspects.jmx.JMX(name="jboss:service=TransactionManager", exposedInterface=
com.arjuna.ats.jbossatx.jts.TransactionManagerServiceMBean.class, registerDirectly=true)</annotation>

        <property name="transactionTimeout">300</property>
        <property name="objectStoreDir">${jboss.server.data.dir}/tx-object-store</property>
        <property name="mbeanServer"><inject bean="JMXKernel" property="mbeanServer"/></property>

        <start>
               <parameter><inject bean="jboss:service=CorbaORB" property="ORB"/></parameter>
        </start>

    </bean>


    </deployment>
     */

    static {
		/*
		 * Override the default logging config, force use of the plugin that rewrites log levels to reflect app server level semantics.
		 * This must be done before the loading of anything that uses the logging, otherwise it's too late to take effect.
		 * Hence the static initializer block.
		 * see also http://jira.jboss.com/jira/browse/JBTM-20
		 */
		com.arjuna.ats.arjuna.common.arjPropertyManager.propertyManager.setProperty(LogFactory.LOGGER_PROPERTY, "log4j_releveler");
		//System.setProperty(LogFactory.LOGGER_PROPERTY, "log4j_releveler") ;
	}

    private final Logger log = org.jboss.logging.Logger.getLogger(TransactionManagerService.class);

    private final static String SERVICE_NAME = "TransactionManagerService";
    private final static String PROPAGATION_CONTEXT_IMPORTER_JNDI_REFERENCE = "java:/TransactionPropagationContextImporter";
    private final static String PROPAGATION_CONTEXT_EXPORTER_JNDI_REFERENCE = "java:/TransactionPropagationContextExporter";
    private static final JBossXATerminator TERMINATOR = new XATerminator() ;

    private RecoveryManager _recoveryManager;
    private boolean _runRM = true;
    private boolean alwaysPropagateContext = true ;

    private boolean configured;
    private byte[] configuredLock = new byte[0] ;

    private boolean isTransactionStatusManagerBindAddressSet;
    private boolean isRecoveryManagerBindAddressSet;

    private MBeanServer mbeanServer;
    /**
     * Use the short class name as the default for the service name.
     */
    public String getName()
    {
        return SERVICE_NAME;
    }

    public TransactionManagerService() {}


    public void create() throws Exception
    {
        synchronized(configuredLock)
        {
            // if the app server's ServerBindingManager has NOT been used to configure the listen addresses
            // (which it should be by default via the binding.xml and transaction-jboss-beans.xml) then we
            // want to fall back to using the SERVER_BIND_ADDRESS in preference to the value from the
            // jbossts-properties.xml. Belt and Braces...

            InetAddress serverInetAddress = InetAddress.getByName(System.getProperty(ServerConfig.SERVER_BIND_ADDRESS));
            if(!isRecoveryManagerBindAddressSet) {
                setRecoveryInetAddress(serverInetAddress);
            }
            if(!isTransactionStatusManagerBindAddressSet) {
                setTransactionStatusManagerInetAddress(serverInetAddress);
            }

            // we are now done configuring things. The transaction system gets unhappy if we change config
            // once it's running, so prevent any further alterations before we start it up...

            configured = true ;
        }

        String tag = Configuration.getBuildTimeProperty("SOURCEID");

        log.info("JBossTS Transaction Service (JTS version - tag:"+tag+") - JBoss Inc.");

        log.info("Setting up property manager MBean and JMX layer");

        // recovery manager properties must be installed before the tx system is initialized. JBTM-598
        arjPropertyManager.propertyManager = PropertyManagerFactory.getPropertyManager("com.arjuna.ats.propertymanager", "recoverymanager");
        Utility.getpid(); // socketProcessId needs to own the port, not the tsm. yuck.
        
        /** Set the tsmx agent implementation to the local JBOSS agent impl **/
        LocalJBossAgentImpl.setLocalAgent( getMbeanServer() );
        System.setProperty(com.arjuna.ats.tsmx.TransactionServiceMX.AGENT_IMPLEMENTATION_PROPERTY,
                com.arjuna.ats.internal.jbossatx.agent.LocalJBossAgentImpl.class.getName());
        System.setProperty(Environment.LAST_RESOURCE_OPTIMISATION_INTERFACE, LastResource.class.getName()) ;

        final String alwaysPropagateProperty = alwaysPropagateContext ? "YES" : "NO" ;
        System.setProperty(com.arjuna.ats.jts.common.Environment.ALWAYS_PROPAGATE_CONTEXT, alwaysPropagateProperty);

        /** Register management plugin **/
        PropertyManagerFactory.getPropertyManager("com.arjuna.ats.propertymanager", "arjuna").addManagementPlugin(new PropertyServiceJMXPlugin());

        // Associate transaction reaper with our context classloader.
        TransactionReaper.transactionReaper() ;

		/** Register propagation context manager **/
        try
        {
            /** Bind the propagation context manager **/
            bindRef(PROPAGATION_CONTEXT_IMPORTER_JNDI_REFERENCE, com.arjuna.ats.internal.jbossatx.jts.PropagationContextManager.class.getName());
            bindRef(PROPAGATION_CONTEXT_EXPORTER_JNDI_REFERENCE, com.arjuna.ats.internal.jbossatx.jts.PropagationContextManager.class.getName());
        }
        catch (Exception e)
        {
            log.fatal("Failed to create and register Propagation Context Manager", e);
        }

        /** Bind the transaction manager and tsr JNDI reference **/
        log.info("Binding TransactionManager JNDI Reference");

        jtaPropertyManager.propertyManager.setProperty(Environment.JTA_TM_IMPLEMENTATION, TransactionManagerDelegate.class.getName());
        jtaPropertyManager.propertyManager.setProperty(Environment.JTA_UT_IMPLEMENTATION, UserTransactionImple.class.getName());

        jtaPropertyManager.propertyManager.setProperty(Environment.JTA_TSR_IMPLEMENTATION, TransactionSynchronizationRegistryImple.class.getName());
        // When running inside the app server, we bind TSR in the JNDI java:/ space, not its required location.
        // It's the job of individual components (EJB3, web, etc) to copy the ref to the java:/comp space)
        jtaPropertyManager.propertyManager.setProperty(Environment.TSR_JNDI_CONTEXT, "java:/TransactionSynchronizationRegistry");

        JNDIManager.bindJTATransactionManagerImplementation();
        JNDIManager.bindJTATransactionSynchronizationRegistryImplementation();

    }

    public void destroy()
    {
        log.info("Destroying TransactionManagerService");

        // unregister the JNDI entries that were registered by create()
        try
        {
            unbind(PROPAGATION_CONTEXT_IMPORTER_JNDI_REFERENCE);
            unbind(PROPAGATION_CONTEXT_EXPORTER_JNDI_REFERENCE);

            JNDIManager.unbindJTATransactionManagerImplementation();
            JNDIManager.unbindJTATransactionSynchronizationRegistryImplementation();
        }
        catch(NamingException e)
        {
            log.warn("Unable to unbind TransactionManagerService JNDI entries ", e);
        }
    }

    public void start(org.omg.CORBA.ORB theCorbaORB) throws Exception
    {
        log.info("Starting transaction recovery manager");

        try
        {
            /** Create an ORB portability wrapper around the CORBA ORB services orb **/
            ORB orb = ORB.getInstance("jboss-atx");

            org.omg.PortableServer.POA rootPOA = org.omg.PortableServer.POAHelper.narrow(theCorbaORB.resolve_initial_references("RootPOA"));

            orb.setOrb(theCorbaORB);
            OA oa = OA.getRootOA(orb);
            oa.setPOA(rootPOA);

            RecoveryORBManager.setORB(orb);
            RecoveryORBManager.setPOA(oa);

            try
            {
                // OTSManager won't play nice unless we explicity bootstrap the portability layer:
                ORBManager.setORB(orb);
                ORBManager.setPOA(oa);

                org.omg.CosTransactions.TransactionFactory factory = com.arjuna.ats.jts.OTSManager.get_factory();
                final int resolver = com.arjuna.ats.jts.TransactionServer.getResolver();

                com.arjuna.ats.jts.TransactionServer.registerTransactionManager(resolver, orb, factory);
            }
            catch (final Exception ex)
            {
                log.fatal("Problem encountered while trying to register transaction manager with ORB!");

                throw new Exception("Problem encountered while trying to register transaction manager with ORB! "+ex);
            }


            // Start the recovery manager
            if (_runRM)
            {
                log.info("Initializing recovery manager");

                RecoveryManager.delayRecoveryManagerThread() ;
                _recoveryManager = RecoveryManager.manager() ; // RecoveryORBManager must be set up before this
                _recoveryManager.initialize();
                _recoveryManager.startRecoveryManagerThread() ;

                log.info("Recovery manager configured and started");
            }
            else
            {
                if (isRecoveryManagerRunning())
                {
                    log.info("Using external recovery manager");
                }
                else
                {
                    log.fatal("Recovery manager not found - please refer to the JBossTS documentation for details");

                    throw new Exception("Recovery manager not found - please refer to the JBossTS documentation for details");
                }
            }
        }
        catch (Exception e)
        {
            log.fatal("Failed to initialize recovery manager", e);
            throw e;
        }
    }



    private boolean isRecoveryManagerRunning() throws Exception
    {
        boolean active = false;
        PropertyManager pm = PropertyManagerFactory.getPropertyManager("com.arjuna.ats.propertymanager", "recoverymanager");

        if ( pm != null )
        {
            BufferedReader in = null;
            PrintStream out = null;

            try
            {
                Socket sckt = RecoveryManager.getClientSocket();

                in = new BufferedReader(new InputStreamReader(sckt.getInputStream()));
                out = new PrintStream(sckt.getOutputStream());

                /** Output ping message **/
                out.println("PING");

                /** Receive pong message **/
                String inMessage = in.readLine();

                active = inMessage != null ? (inMessage.equals("PONG")) : false;
            }
            catch (Exception ex)
            {
                try
                {
                    InetAddress host = RecoveryManager.getRecoveryManagerHost();
                    int port = RecoveryManager.getRecoveryManagerPort();

                    log.error("Failed to connect to recovery manager on " + host.getHostAddress() + ':' + port);
                }
                catch (UnknownHostException e)
                {
                    log.error("Failed to connect to recovery manager", ex);
                }

                active = false;
            }
            finally
            {
                if ( in != null )
                {
                    in.close();
                }

                if ( out != null )
                {
                    out.close();
                }
            }
        }

        return active;
    }

    public void stop() throws Exception
    {
        if (_runRM)
        {
            log.info("Stopping transaction recovery manager");

            _recoveryManager.terminate();
        }
    }

    /**
     * Set the default transaction timeout used by this transaction manager.
     *
     * @param timeout The default timeout in seconds for all transactions created
     * using this transaction manager.
     *
     * @throws IllegalStateException if the mbean has already started.
     */
    public void setTransactionTimeout(int timeout) throws IllegalStateException
    {
	synchronized(configuredLock)
	{
            if (configured)
            {
        	final int currentTimeout = getTransactionTimeout() ;
        	if (currentTimeout != timeout)
        	{
        	    throw new IllegalStateException("Cannot set transaction timeout once MBean has configured") ;
        	}
            }
            else
            {
        	jtsPropertyManager.propertyManager.setProperty(com.arjuna.ats.jts.common.Environment.DEFAULT_TIMEOUT, Integer.toString(timeout));
            }
        }
    }


    /**
     * Get the default transaction timeout used by this transaction manager.
     *
     * @return The default timeout in seconds for all transactions created
     * using this transaction manager.
     */
    public int getTransactionTimeout()
    {
        final String timeout = jtsPropertyManager.propertyManager.getProperty(com.arjuna.ats.jts.common.Environment.DEFAULT_TIMEOUT);
        if (timeout != null)
        {
            try
            {
        	return Integer.parseInt(timeout) ;
            }
            catch (final NumberFormatException nfe) {} // Invalid property
        }
        return 0 ;
    }

    /**
     * Retrieve a reference to the JTA transaction manager.
     *
     * @return A reference to the JTA transaction manager.
     */
    public TransactionManager getTransactionManager()
    {
        return com.arjuna.ats.jta.TransactionManager.transactionManager();
    }

    /**
     * Retrieve a reference ot the JTA TransactionSynchronizationRegistry.
     *
     * @return a reference to the JTA TransactionSynchronizationRegistry.
     */
    public TransactionSynchronizationRegistry getTransactionSynchronizationRegistry()
    {
        // rely on the imple being stateless:
        return new TransactionSynchronizationRegistryImple();
    }

    /**
     * Get the XA Terminator
     *
     * @return the XA Terminator
     */
    public JBossXATerminator getXATerminator()
    {
       return TERMINATOR ;
    }

    /**
     * Retrieve a reference to the JTA user transaction manager.
     *
     * @return A reference to the JTA user transaction manager.
     */
    public UserTransaction getUserTransaction()
    {
        return com.arjuna.ats.jta.UserTransaction.userTransaction();
    }

    /**
     * Set whether the transaction propagation context manager should propagate a
     * full PropagationContext (JTS) or just a cut-down version (for JTA).
     *
     * @param propagateFullContext
     */
    public void setPropagateFullContext(boolean propagateFullContext)
    {
        PropagationContextWrapper.setPropagateFullContext(propagateFullContext);
    }

    /**
     * Retrieve whether the transaction propagation context manager should propagate a
     * full PropagationContext (JTS) or just a cut-down version (for JTA).
     */
    public boolean getPropagateFullContext()
    {
        return PropagationContextWrapper.getPropagateFullContext();
    }

    /**
     * Sets whether the transaction service should collate transaction service statistics.
     *
     * @param enabled
     */
    public void setStatisticsEnabled(boolean enabled)
    {
        System.setProperty(com.arjuna.ats.arjuna.common.Environment.ENABLE_STATISTICS, enabled ? "YES" : "NO");
    }

    /**
     * Retrieves whether the statistics are enabled.
     * @return true if enabled, false otherwise.
     */
    public boolean getStatisticsEnabled()
    {
        boolean enabled = System.getProperty(com.arjuna.ats.arjuna.common.Environment.ENABLE_STATISTICS, "NO").equals("YES");

        return enabled;
    }

    /**
     * This method has been put in here so that it is compatible with the JBoss standard Transaction Manager.
     * As we do not support exception formatters just display a warning for the moment.
     */
    public void registerXAExceptionFormatter(Class c, XAExceptionFormatter f)
    {
        log.warn("XAExceptionFormatters are not supported by the JBossTS Transaction Service - this warning can safely be ignored");
    }

    /**
     * This method has been put in here so that it is compatible with the JBoss standard Transaction Manager.
     * As we do not support exception formatters just display a warning for the moment.
     */
    public void unregisterXAExceptionFormatter(Class c)
    {
        // Ignore
    }

    /**
     * Returns the total number of transactions started over the lifetime of the server
     * @return Returns the total number of transactions started over the lifetime of the server
     */
    public long getTransactionCount()
    {
        return TxStats.numberOfTransactions();
    }

    /**
     * Returns the total number of nested transactions started over the lifetime of the server.
     * This will usually be 0 even when stats are enabled, since JTA disallows nested tx by default.
     */
    public long getNestedTransactonCount() {
        return TxStats.numberOfNestedTransactions();
    }

    /**
     * Returns the number of heuristics that have occurred.
     * @return the number of heuristics that have occurred.
     */
    public long getHeuristicCount() {
        return TxStats.numberOfHeuristics();
    }

    /**
     * Returns the number of committed transactions
     * @return The number of committed transactions.
     */
    public long getCommitCount()
    {
        return TxStats.numberOfCommittedTransactions();
    }

    /**
     * Returns the number of rolledback transactions
     * @return The number of rolledback transactions.
     */
    public long getRollbackCount()
    {
        return TxStats.numberOfAbortedTransactions();
    }

    /**
     * Get the number of transactions that have begun but not yet terminated.
     * This count is approximate, particularly in recovery situations.
     */
    public long getRunningTransactionCount() {
        return TxStats.numberOfInflightTransactions();
    }

    /**
     * Returns the number of transactions that have been timed out by the transaction reaper.
     * @return the number of transactions that have been timed out by the transaction reaper.
     */
    public long getTimedoutCount() {
        return TxStats.numberOfTimedOutTransactions();
    }

    /**
     * Returns the number of transactions that have been rolled back by application request.
     * This includes those that timeout, since the timeout behaviour is considered an
     * attribute of the application configuration.
     * @return the number of transactions that have been rolled back by application request.
     */
    public long getApplicationRollbackCount() {
        return TxStats.numberOfApplicationRollbacks();
    }

    /**
     * Returns the number of transactions that rolled back due to resource failure.
     * @return the number of transactions that rolled back due to resource failure.
     */
    public long getResourceRollbackCount() {
        return TxStats.numberOfResourceRollbacks();
    }

    /**
     * Set whether the recovery manager should be ran in the same VM as
     * JBoss.  If this is false the Recovery Manager is already expected to
     * be running when JBoss starts.
     * @param runRM
     *
     * @throws IllegalStateException If the MBean has already started.
     */
    public void setRunInVMRecoveryManager(boolean runRM)
        throws IllegalStateException
    {
        synchronized(configuredLock)
        {
            if (configured)
            {
        	if (this._runRM != runRM)
        	{
        	    throw new IllegalStateException("Cannot set run in VM recovery manager once MBean has configured") ;
        	}
            }
            else
            {
        	_runRM = runRM;
            }
        }
    }

    /**
     * Get whether the recovery manager should be ran in the same VM as
     * JBoss.  If this is false the Recovery Manager is already expected to
     * be running when JBoss starts.
     *
     * @return true if the recover manager is running in the same VM, false otherwise.
     */
    public boolean getRunInVMRecoveryManager()
    {
	synchronized(configuredLock)
	{
	    return _runRM ;
	}
    }

    /**
     * Set the object store directory.
     * @param objectStoreDir The object store directory.
     *
     * @throws IllegalStateException if the MBean has already started
     */
    public void setObjectStoreDir(final String objectStoreDir)
    	throws IllegalStateException
    {
        synchronized(configuredLock)
        {
            if (configured)
            {
        	final String currentDir = getObjectStoreDir() ;
        	final boolean equal = (currentDir == null ? objectStoreDir == null : currentDir.equals(objectStoreDir)) ;
        	if (!equal)
        	{
        	    throw new IllegalStateException("Cannot set object store dir once MBean has configured") ;
        	}
            }
            else
            {
        	System.setProperty(com.arjuna.ats.arjuna.common.Environment.OBJECTSTORE_DIR, objectStoreDir) ;
            }
        }
    }

    /**
     * Get the object store directory.
     * @return objectStoreDir The object store directory.
     */
    public String getObjectStoreDir()
    {
	return System.getProperty(com.arjuna.ats.arjuna.common.Environment.OBJECTSTORE_DIR) ;
    }


    public MBeanServer getMbeanServer()
    {
        return mbeanServer;
    }

    public void setMbeanServer(MBeanServer mbeanServer)
    {
        synchronized(configuredLock)
        {
            if (configured)
            {
                if (this.mbeanServer != mbeanServer)
                {
                    throw new IllegalStateException("Cannot set MBeanServer once MBean has configured") ;
                }
            }
            else
            {
                this.mbeanServer = mbeanServer;
            }
        }
    }


    public void addXAResourceRecovery(XAResourceRecovery xaResourceRecovery)
    {
        if(_recoveryManager == null) {
            log.error("No recovery system in which to register XAResourceRecovery instance");
            throw new IllegalStateException("No recovery system present in this server");
        }

        XARecoveryModule xaRecoveryModule = null;
        for(RecoveryModule recoveryModule : ((Vector<RecoveryModule>)_recoveryManager.getModules())) {
            if(recoveryModule instanceof XARecoveryModule) {
                xaRecoveryModule = (XARecoveryModule)recoveryModule;
                break;
            }
        }

        if(xaRecoveryModule == null) {
            log.error("No suitable recovery module in which to register XAResourceRecovery instance");
            throw new IllegalStateException("No xa recovery module present in this server");
        }

        xaRecoveryModule.addXAResourceRecoveryHelper(new XAResourceRecoveryHelperWrapper(xaResourceRecovery));
    }

    public void removeXAResourceRecovery(XAResourceRecovery xaResourceRecovery)
    {
        if(_recoveryManager == null) {
            log.warn("No recovery system from which to remove XAResourceRecovery instance");
            return;
        }

        XARecoveryModule xaRecoveryModule = null;
        for(RecoveryModule recoveryModule : ((Vector <RecoveryModule>)_recoveryManager.getModules())) {
            if(recoveryModule instanceof XARecoveryModule) {
                xaRecoveryModule = (XARecoveryModule)recoveryModule;
                break;
            }
        }

        if(xaRecoveryModule == null) {
            log.warn("No suitable recovery module in which to register XAResourceRecovery instance");
            return;
        }

        xaRecoveryModule.removeXAResourceRecoveryHelper(new XAResourceRecoveryHelperWrapper(xaResourceRecovery));
    }
    private void bindRef(String jndiName, String className)
            throws Exception
    {
        Reference ref = new Reference(className, className, null);
        new InitialContext().bind(jndiName, ref);
    }

    private void unbind(String jndiName) throws NamingException
    {
        new InitialContext().unbind(jndiName);
    }

    /**
     * Set the flag indicating whether the propagation context should always be propagated.
     * @param alwaysPropagateContext true if the context should always be propagated, false if only propagated to OTS transactional objects.
     *
     * @throws IllegalStateException If the MBean has already started.
     */
    public void setAlwaysPropagateContext(final boolean alwaysPropagateContext)
    	throws IllegalStateException
    {
	synchronized(configuredLock)
	{
            if (configured)
            {
        	if (this.alwaysPropagateContext != alwaysPropagateContext)
        	{
        	    throw new IllegalStateException("Cannot set always propagate context once MBean has configured") ;
        	}
            }
            else
            {
        	this.alwaysPropagateContext = alwaysPropagateContext ;
            }
	}
    }

    /**
     * Get the flag indicating whether the propagation context should always be propagated.
     * @return true if the context should always be propagated, false if only propagated to OTS transactional objects.
     */
    public boolean getAlwaysPropagateContext()
    {
	synchronized(configuredLock)
	{
	    return alwaysPropagateContext ;
	}
    }

    ///////////////////

    // note that the address/port setters rely on having the property manager initialized first, or their settings
    // would be overridden by those in the config files when the properties are loaded from file. Fortunatly the static
    // block for the logging has the nice side effect of loading the config too, so it works out ok.

    public InetAddress getTransactionStatusManagerInetAddress()
    {
        PropertyManager pm = PropertyManagerFactory.getPropertyManager("com.arjuna.ats.propertymanager", "recoverymanager");
        try {
            return Utility.hostNameToInetAddress(pm.getProperty(com.arjuna.ats.arjuna.common.Environment.TRANSACTION_STATUS_MANAGER_ADDRESS), "");
        } catch(UnknownHostException e) {
            log.warn("UnknownHostException from getTransactionStatusManagerInetAddress, input was: "+pm.getProperty(com.arjuna.ats.arjuna.common.Environment.TRANSACTION_STATUS_MANAGER_ADDRESS));
            return null;
        }
    }

    public void setTransactionStatusManagerInetAddress(InetAddress tsmInetAddress) throws IllegalStateException
    {
        synchronized(configuredLock)
        {
            if (configured)
            {
                if(!tsmInetAddress.getAddress().equals(getTransactionStatusManagerInetAddress().getAddress())) {
                    throw new IllegalStateException("Cannot set transactionStatusManagerInetAddress once the MBean has configured");
                }
            }
            else
            {
                PropertyManager pm = PropertyManagerFactory.getPropertyManager("com.arjuna.ats.propertymanager", "recoverymanager");
                pm.setProperty(com.arjuna.ats.arjuna.common.Environment.TRANSACTION_STATUS_MANAGER_ADDRESS, tsmInetAddress.getHostAddress());
                isTransactionStatusManagerBindAddressSet = true;
            }
        }
    }

    public int getTransactionStatusManagerPort()
    {
        PropertyManager pm = PropertyManagerFactory.getPropertyManager("com.arjuna.ats.propertymanager", "recoverymanager");
        return Integer.parseInt(pm.getProperty(com.arjuna.ats.arjuna.common.Environment.TRANSACTION_STATUS_MANAGER_PORT));
    }

    public void setTransactionStatusManagerPort(int port) throws IllegalStateException
    {
        if (configured)
        {
            if(port != getTransactionStatusManagerPort()) {
                throw new IllegalStateException("Cannot set transactionStatusManagerPort once the MBean has configured");
            }
        }
        else
        {
            PropertyManager pm = PropertyManagerFactory.getPropertyManager("com.arjuna.ats.propertymanager", "recoverymanager");
            pm.setProperty(com.arjuna.ats.arjuna.common.Environment.TRANSACTION_STATUS_MANAGER_PORT, ""+port);
        }
    }



    public InetAddress getRecoveryInetAddress()
    {
        PropertyManager pm = PropertyManagerFactory.getPropertyManager("com.arjuna.ats.propertymanager", "recoverymanager");
        try {
            return Utility.hostNameToInetAddress(pm.getProperty(com.arjuna.ats.arjuna.common.Environment.RECOVERY_MANAGER_ADDRESS), "");
        } catch(UnknownHostException e) {
            log.warn("UnknownHostException from getRecoveryInetAddress, input was: "+pm.getProperty(com.arjuna.ats.arjuna.common.Environment.RECOVERY_MANAGER_ADDRESS));
            return null;
        }
    }

    public void setRecoveryInetAddress(InetAddress recoveryInetAddress) throws IllegalStateException
    {
        if (configured)
        {
            if(!recoveryInetAddress.getAddress().equals(getRecoveryInetAddress().getAddress())) {
                throw new IllegalStateException("Cannot set recoveryInetAddress once the MBean has configured");
            }
        }
        else
        {
            PropertyManager pm = PropertyManagerFactory.getPropertyManager("com.arjuna.ats.propertymanager", "recoverymanager");
            pm.setProperty(com.arjuna.ats.arjuna.common.Environment.RECOVERY_MANAGER_ADDRESS, recoveryInetAddress.getHostAddress());
            isRecoveryManagerBindAddressSet = true;
        }
    }

    public int getRecoveryPort()
    {
        PropertyManager pm = PropertyManagerFactory.getPropertyManager("com.arjuna.ats.propertymanager", "recoverymanager");
        return Integer.parseInt(pm.getProperty(com.arjuna.ats.arjuna.common.Environment.RECOVERY_MANAGER_PORT));
    }

    public void setRecoveryPort(int port) throws IllegalStateException
    {
        if (configured)
        {
            if(port != getRecoveryPort()) {
                throw new IllegalStateException("Cannot set recoveryPort once the MBean has configured");
            }
        }
        else
        {
            PropertyManager pm = PropertyManagerFactory.getPropertyManager("com.arjuna.ats.propertymanager", "recoverymanager");
            pm.setProperty(com.arjuna.ats.arjuna.common.Environment.RECOVERY_MANAGER_PORT, ""+port);
        }
    }


    public int getSocketProcessIdPort()
    {
        PropertyManager pm = arjPropertyManager.getPropertyManager();
        return Integer.parseInt(pm.getProperty(com.arjuna.ats.arjuna.common.Environment.SOCKET_PROCESS_ID_PORT));
    }

    public void setSocketProcessIdPort(int port) throws IllegalStateException
    {
        if (configured)
        {
            if(port != getSocketProcessIdPort()) {
                throw new IllegalStateException("Cannot set socketProcessIdPort once the MBean has configured");
            }
        }
        else
        {
            PropertyManager pm = arjPropertyManager.getPropertyManager();
            pm.setProperty(com.arjuna.ats.arjuna.common.Environment.SOCKET_PROCESS_ID_PORT, ""+port);
        }
    }
}
