/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2011 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id:JOTMTransactionService.java 10445 2007-05-23 16:34:28Z sauthieg $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.tm.jotm;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;

import javax.management.ObjectName;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.resource.spi.XATerminator;
import javax.transaction.NotSupportedException;
import javax.transaction.SystemException;
import javax.transaction.TransactionSynchronizationRegistry;
import javax.transaction.UserTransaction;
import javax.transaction.xa.XAException;
import javax.transaction.xa.Xid;

import org.objectweb.jotm.Current;
import org.objectweb.jotm.TransactionFactory;
import org.objectweb.jotm.TransactionFactoryImpl;
import org.objectweb.jotm.TransactionSynchronizationRegistryImpl;
import org.objectweb.jotm.jta.rmi.JTAInterceptorInitializer;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.ow2.carol.rmi.interceptor.spi.JInitializer;
import org.ow2.carol.util.configuration.ConfigurationException;
import org.ow2.carol.util.configuration.ConfigurationRepository;
import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.lib.execution.ExecutionResult;
import org.ow2.jonas.lib.execution.IExecution;
import org.ow2.jonas.lib.execution.RunnableHelper;
import org.ow2.jonas.lib.management.javaee.J2eeObjectName;
import org.ow2.jonas.lib.service.AbsServiceImpl;
import org.ow2.jonas.lib.util.JonasObjectName;
import org.ow2.jonas.lib.util.Log;
import org.ow2.jonas.registry.RegistryService;
import org.ow2.jonas.service.ServiceException;
import org.ow2.jonas.tm.TransactionManager;
import org.ow2.jonas.tm.TransactionService;
import org.ow2.jonas.tm.jotm.mbean.JTAResource;

/**
 * Transaction Service implementation.
 * This singleton class must exist in each jonas server.
 * This class manages a unique Current object that implements both
 * TransactionManager and UserTransaction interfaces.
 * @author Philippe Durieux
 * Contributor(s): Adriana Danes
 */
public class JOTMTransactionService extends AbsServiceImpl implements TransactionService {

    /**
     * JOTM Service Logger.
     */
    private static Logger logger = Log.getLogger(JOTMTransactionService.class.getName());

    /**
     * Default Transaction timeout.
     */
    private static final int DEFAULT_TIMEOUT = 60;

    /**
     * Time to wait if the remote TM is not available.
     */
    private static final int TM_LOOKUP_WAIT_TIME = 2000;

    /**
     * Property for the transaction propagation.
     */
    private static final String TRANSACTION_PROPAGATION = "jonas.transaction.propagation";

    /**
     * TM factory may be local or remote.
     * The TM factory is used by the JTA implementation when transactions
     * are distributed among several JVM (JOnAS Server or applet client)
     */
    private TransactionFactory tmFactory = null;

    /**
     * Unique Current object implementing the standard
     * {@link javax.transaction.TransactionManager}
     * set to null while service is not started.
     */
    private JOTMTransactionManager current = null;

    // Configuration information used to start the Transaction service
    /**
     * TX timeout.
     */
    private int timeout = DEFAULT_TIMEOUT;

    /**
     * Is TM local ?
     */
    private boolean jtmlocal;

    /**
     * JNDI Context.
     */
    private InitialContext ictx;

    /**
     * jmx service reference.
     */
    private JmxService jmxService = null;

    /**
     * Registry Service.
     */
    private RegistryService registryService;

    /**
     * JTAResource ObjectName.
     */
    private ObjectName onJTAResource = null;

    /**
     * List of registered JRMP interceptors.
     */
    private List<Class<? extends JInitializer>> jrmpInterceptors = null;

    /**
     * TransactionSynchronizationRegistry object, to be shared by all components.
     */
    protected TransactionSynchronizationRegistry tsr = null;

    /**
     * Bundle Context.
     */
    private BundleContext bundleContext = null;

    /**
     * UserTransaction OSGi Service.
     */
    private ServiceRegistration osgiUserTransactionService;

    /**
     * TransactionManager OSGi Service.
     */
    private ServiceRegistration osgiTransactionManagerService;

    /**
     * TransactionSynchronization OSGi Service.
     */
    private ServiceRegistration osgiTransactionSynchronizationService;




    public JOTMTransactionService(final BundleContext bundleContext) {
        this.bundleContext = bundleContext;
    }

    /**
     * @param remote Use a remote TransactionManager ?
     */
    public void setRemote(final boolean remote) {
        jtmlocal = !remote;
    }

    /**
     * Start the Service
     * Initialization of the service is already done.
     * @throws ServiceException if service start fails
     */
    @Override
    public void doStart() throws ServiceException {
        logger.log(BasicLevel.DEBUG, "Starting JOTMTransaction Service");

        jrmpInterceptors = new ArrayList<Class<? extends JInitializer>>();

        try {
            boolean transaction = Boolean.parseBoolean(getServerProperties().getValue(TRANSACTION_PROPAGATION));
            if (transaction) {
                jrmpInterceptors.add(JTAInterceptorInitializer.class);
            }

            for (Class<? extends JInitializer> interceptor : jrmpInterceptors) {
                ConfigurationRepository.addInterceptors("jrmp", interceptor);
            }
        } catch (ConfigurationException e) {
            throw new ServiceException("Cannot init JTA interceptor for Carol", e);
        }

        // init management logger
        //super.initLogger(Log.getLogger(Log.JONAS_MANAGEMENT_PREFIX));

        // register mbeans-descriptors
        jmxService.loadDescriptors(getClass().getPackage().getName(), getClass().getClassLoader());

        // Get the InitialContext
        ictx = getRegistryService().getRegistryContext();

        // Gets the Distributed Transaction Manager (JTM)
        if (jtmlocal) {
            // Creates a JTM locally in this server
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "working with a colocated Transaction Manager ");
            }

            // TODO Recovery of the JTM

            // Creates the ControlFactory and register it in JNDI.
            try {
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Create and register TM factory");
                }

                // Create the TransactionFactory
                tmFactory = new TransactionFactoryImpl();

                ictx.rebind("TMFactory", tmFactory);
            } catch (RemoteException e) {
                logger.log(BasicLevel.ERROR, "TransactionService: Cannot create TransactionFactory:\n" + e);
                throw new ServiceException("TransactionService: Cannot create TransactionFactory", e);
            } catch (NamingException e) {
                logger.log(BasicLevel.ERROR, "TransactionService: Cannot rebind TM:\n" + e);
                throw new ServiceException("TransactionService: Cannot rebind TM", e);
            }
        } else {
            // JTM is remote, finds it by JNDI
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "working with a remote Transaction Manager ");
            }
            final int maxloops = 5;
            for (int i = 0; i <= maxloops; i++) {
                try {
                    // Create the TransactionFactory in an execution block
                    IExecution<TransactionFactory> tmFactoryBuilder = new IExecution<TransactionFactory>() {
                        public TransactionFactory execute() throws Exception {
                            return (TransactionFactory) ictx.lookup("TMFactory");
                        }
                    };

                    // Execute
                    ExecutionResult<TransactionFactory> tmFactoryResult = RunnableHelper.execute(getClass()
                            .getClassLoader(), tmFactoryBuilder);

                    // Throw an ServiceException if needed
                    if (tmFactoryResult.hasException()) {
                        throw new NamingException("TransactionService: Cannot get TM factory");
                    }
                    // Store the ref
                    tmFactory = tmFactoryResult.getResult();
                    break;
                } catch (NamingException e) {
                    if (i < maxloops) {
                        logger.log(BasicLevel.WARN, "Cannot get TM factory - retrying...");
                        try {
                            Thread.sleep(TM_LOOKUP_WAIT_TIME * (i + 1));
                        } catch (InterruptedException e2) {
                            throw new ServiceException("Cannot get TM factory", e2);
                        }
                    } else {
                        logger.log(BasicLevel.ERROR, "TransactionService: Cannot get TM factory:\n" + e);
                        throw new ServiceException("TransactionService: Cannot get TM factory", e);
                    }
                }
            }
        }
        //  Get TM factory configuration parameters
        int portNumber = 0;
        String hostName = null;
        try {
            portNumber = tmFactory.getPortNumber();
            hostName = tmFactory.getHostName();
        } catch (RemoteException e) {
            logger.log(BasicLevel.ERROR, "TransactionService: Cannot access TransactionFactory"
                                         + "when trying to get configuration parameters:\n" + e);
            throw new ServiceException("TransactionService: Cannot access TransactionFactory",
                                       e);
        }
        // Create and init the unique Current object.
        // Current is the local TransactionManager. It must be present in every
        // jonas server and implements the JTA TransactionManager interface.
        //
        // In case of a JTM standalone:
        // We must create the current only to export it via JNDI for clients
        // that want to get UserTransaction.
        current = new JOTMTransactionManager(tmFactory);
        setTimeout(timeout);

        // Admin code
        // ---------
        // Register TransactionService MBean
        try {
            jmxService.registerModelMBean(this, JonasObjectName.transactionService(getDomainName()));
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot register TransactionService MBean", e);
        }

        // Register JTAResource implemented by this Service
        String sJTAResourceName = "JTAResource";
        onJTAResource = J2eeObjectName.JTAResource(getDomainName(),
                                                   getJonasServerName(),
                                                   sJTAResourceName);
        JTAResource jtaResourceMBean = new JTAResource(onJTAResource.toString(),
                                                       this,
                                                       new Integer(timeout),
                                                       new Boolean(jtmlocal),
                                                       new Integer(portNumber),
                                                       hostName);
        try {
            jmxService.registerModelMBean(jtaResourceMBean, onJTAResource);
        } catch (Exception e) {
            e.printStackTrace();
            logger.log(BasicLevel.ERROR, "Cannot register JTAResource MBean", e);
        }

        // Register JTA Enterprise OSGi services
        osgiUserTransactionService = bundleContext.registerService(UserTransaction.class.getName(),
                                                                   getUserTransaction(),
                                                                   null);
        osgiTransactionManagerService = bundleContext.registerService(new String[] {TransactionManager.class.getName(),
                                                                                    javax.transaction.TransactionManager.class.getName()},
                                                                      getTransactionManager(),
                                                                      null);
        osgiTransactionSynchronizationService = bundleContext.registerService(TransactionSynchronizationRegistry.class.getName(),
                                                                              getTransactionSynchronizationRegistry(),
                                                                              null);

        logger.log(BasicLevel.INFO, "TransactionService started, default timeout= " + timeout);

    }

    /**
     * Stop the transaction service.
     * Not already implemented
     * @throws ServiceException if the service stop fails
     */
    @Override
    public void doStop() throws ServiceException {
        // TODO Find a way to unload mbeans-descriptors
        if (jmxService != null) {
            jmxService.unregisterModelMBean(onJTAResource);
            jmxService.unregisterModelMBean(JonasObjectName.transactionService(getDomainName()));
        }

        try {
            for (Class<? extends JInitializer> interceptor : jrmpInterceptors) {
                ConfigurationRepository.removeInterceptors("jrmp", interceptor);
            }
            jrmpInterceptors.clear();
        } catch (ConfigurationException e) {
            throw new ServiceException("Cannot remove security interceptors for Carol", e);
        }


        // Unregister JTA Enterprise OSGi services
        if (osgiUserTransactionService != null) {
            osgiUserTransactionService.unregister();
        }
        if (osgiTransactionManagerService != null) {
            osgiTransactionManagerService.unregister();
        }
        if (osgiTransactionSynchronizationService != null) {
            osgiTransactionSynchronizationService.unregister();
        }

        logger.log(BasicLevel.INFO, "TransactionService stopped");
    }

    // -------------------------------------------------------------------
    // TransactionService Implementation
    // -------------------------------------------------------------------

    /**
     * Gets the TransactionManager object instance.
     * @return the transaction manager
     */
    public TransactionManager getTransactionManager() {
        return current;
    }

    /**
     * Gets the inflow transaction object that represents the transaction context of
     * the calling thread. If the calling thread is
     * not associated with an inflow transaction, a null object reference
     * is returned.
     * @return a XATerminator handle
     * @throws XAException
     */
    public XATerminator getXATerminator() throws XAException {
        return current.getXATerminator();
    }

    /**
     * Gets the UserTransaction object instance.
     * @return the user transaction object
     */
    public UserTransaction getUserTransaction() {
        return current;
    }

    /**
     * Gets the TransactionSynchronizationRegistry object instance.
     * @return the transaction synchronization registry object
     */
    public TransactionSynchronizationRegistry getTransactionSynchronizationRegistry() {
        if (tsr == null) {
            // Get TransactionSynchronizationRegistry instance
            try {
                tsr = (TransactionSynchronizationRegistry) ictx.lookup("javax.transaction.TransactionSynchronizationRegistry");
            } catch (NamingException ne) {
                logger.log(BasicLevel.WARN, "Cannot lookup TransactionSynchronizationRegistry");
            }
        }
        logger.log(BasicLevel.DEBUG, "return TSR: " + tsr);
        return tsr;
    }

    /**
     * Start Resource Manager Recovery.
     */
    public void startResourceManagerRecovery() throws XAException {
        Current.getTransactionRecovery().startResourceManagerRecovery();
    }

    public int getTimeout() {
        return current.getDefaultTimeout();
    }

    /**
     * Sets the default transaction timeout and register Current in JNDI.
     * @param t new value for time-out
     */
    public void setTimeout(final int t) {

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "" + t);
        }

        timeout = t;

        // During the configuration step (injection), the current is null
        if (current != null) {
            current.setDefaultTimeout(t);

            if (jtmlocal) {
                // Register a UserTransactionFactory in JNDI.
                // Only if we are inside the JTM
                try {
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "Register UserTransactionFactory");
                    }
                    ictx.rebind("javax.transaction.UserTransaction", current);
                } catch (NamingException e) {
                    logger.log(BasicLevel.ERROR, "Cannot rebind UserTransaction:" + e);
                }
                // Register TransactionSynchronizationRegistry in JNDI
                // Only if we are inside the JTM
                tsr = TransactionSynchronizationRegistryImpl.getInstance();
                try {
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "Register TSR in JNDI:" + tsr);
                    }
                    ictx.rebind("javax.transaction.TransactionSynchronizationRegistry", tsr);
                } catch (NamingException e) {
                    logger.log(BasicLevel.ERROR, "Cannot rebind TransactionSynchronizationRegistry:" + e);
                }
            }
        }

    }

    /**
     * Get begun transactions number.
     * @return total number of begun transactions
     */
    public int getTotalBegunTransactions() {
        int ret = current.getTotalBegunTransactions();
        if (ret < 0) {
            logger.log(BasicLevel.ERROR, "Negative value for TotalBegunTransactions:" + ret);
        }
        return ret;
    }

    /**
     * Get committed transactions number.
     * @return total number of committed transactions
     */
    public int getTotalCommittedTransactions() {
        int ret = current.getTotalCommittedTransactions();
        if (ret < 0) {
            logger.log(BasicLevel.ERROR, "Negative value for TotalCommittedTransactions:" + ret);
        }
        return ret;
    }

    /**
     * Get current transactions number.
     * @return total number of current transactions
     */
    public int getTotalCurrentTransactions() {
        int ret = current.getTotalCurrentTransactions();
        if (ret < 0) {
            logger.log(BasicLevel.ERROR, "Negative value for TotalCurrentTransactions:" + ret);
        }
        return ret;
    }

    /**
     * Get expired transactions number.
     * @return total number of expired transactions
     */
    public int getTotalExpiredTransactions() {
        int ret = current.getTotalExpiredTransactions();
        if (ret < 0) {
            logger.log(BasicLevel.ERROR, "Negative value for TotalExpiredTransactions:" + ret);
        }
        return ret;
    }

    /**
     * Get rolled back transactions number.
     * @return total number of rolled back transactions
     */
    public int getTotalRolledbackTransactions() {
        int ret = current.getTotalRolledbackTransactions();
        if (ret < 0) {
            logger.log(BasicLevel.ERROR, "Negative value for TotalRolledbackTransactions:" + ret);
        }
        return ret;
    }

   /**
    * Reset all transaction counters.
    */
    public void resetAllTxTotalCounters() {
        current.resetAllTxTotalCounters();
    }

    /**
     * Get all currently executing Xids.
     * @return total number of executing Xids
     */
    public Xid [] getAllActiveXids() {
        return (current.getAllXid());
    }

    /**
     * Get all currently executing transactions.
     * @return total number of executing transaction
     */
    public String [] getAllActiveTx() {
        String [] mysArray;

        mysArray = current.getAllTx();
        return mysArray;
    }

    /**
     * Get all transactions that require administrator recovery action.
     * @return Transactions that require administrator recovery action
     */
    public String [] getAllRecoveryTx() {
        String [] mysArray;

        mysArray = current.getAllRcTx();
        return mysArray;
    }

    /**
     * Get all XAResoures of a transaction that require administrator recovery action.
     * @return XAResources that require administrator recovery action
     */
    public String [] getAllXAResource(final String xtx) {
        String [] mysArray;

        mysArray = current.getAllXaTx(xtx);
        return mysArray;
    }

    /**
     * administrator recovery action: Commit.
     * @return int error, or 0 if OK.
     */
    public int commitXAResource(final String xatx) {
        int commiterror;
        commiterror = current.actionXAResource("commit", xatx);
        return commiterror;
    }

    /**
     * administrator recovery action: rollback
     * @return int error, or 0 if OK.
     */
    public int rollbackXAResource(final String xatx) {
        int rollbackerror;
        rollbackerror = current.actionXAResource("rollback", xatx);
        return rollbackerror;
    }

    /**
     * administrator recovery action: forget
     * @return int error, or 0 if OK.
     */
    public int forgetXAResource(final String xatx) {
        int forgeterror;
        forgeterror = current.actionXAResource("forget", xatx);
        return forgeterror;
    }

    /**
     * {@inheritDoc}
     */
    public void attachTransaction(final Xid xid, final long timeout) throws NotSupportedException, SystemException {
        current.begin(xid, timeout);
    }

    /**
     * {@inheritDoc}
     */
    public void detachTransaction() {
        current.clearThreadTx();
    }

    /**
     * @param jmxService the jmxService to set
     */
    public void setJmxService(final JmxService jmxService) {
        this.jmxService = jmxService;
    }

    /**
     * @param registry the registry service to set
     */
    public void setRegistryService(final RegistryService registry) {
        this.registryService = registry;
    }

    /**
     * Returns the registry service.
     * @return The registry service
     */
    private RegistryService getRegistryService() {
        return registryService;
    }

}
