/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2005 Bull S.A.
 * Contact: jonas-team@objectweb.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$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas.jtm;

import java.rmi.RemoteException;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.modelmbean.ModelMBean;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.UserTransaction;
import javax.transaction.xa.Xid;

import org.apache.commons.modeler.ManagedBean;
import org.apache.commons.modeler.Registry;

import org.objectweb.jotm.Current;
import org.objectweb.jotm.TransactionFactory;
import org.objectweb.jotm.TransactionFactoryImpl;
import org.objectweb.transaction.jta.TransactionManager;

import org.objectweb.jonas.common.Log;
import org.objectweb.jonas.jmx.J2eeObjectName;
import org.objectweb.jonas.jmx.JmxService;
import org.objectweb.jonas.jmx.JonasObjectName;
import org.objectweb.jonas.management.JonasMBeanTools;
import org.objectweb.jonas.naming.NamingManager;
import org.objectweb.jonas.service.AbsServiceImpl;
import org.objectweb.jonas.service.ServiceException;
import org.objectweb.jonas.service.ServiceManager;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

/**
 * 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 TransactionServiceImpl extends AbsServiceImpl implements TransactionService, TransactionServiceImplMBean {

    private static Logger logger = null;

    /**
     * Service name as used to label configuration properties
     */
    public static final String SERVICE_NAME = "jtm";
    // Transaction Service configuration properties
    /**
     * name of the 'timeout' configuration parameter
     */
    static final String TIMEOUT = "jonas.service.jtm.timeout";
    /**
     * name of the 'remote' configuration parameter
     */
    static final String REMOTE = "jonas.service.jtm.remote";
    /**
     * name of the 'class' configuration parameter
     */
    static final String CLASS = "jonas.service.jtm.class";

    /**
    /* 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 tm = null;

    /**
     * Unique Current object implementing the standard TransactionManager
    /* set to null while service is not started.
     */
    private Current current = null;

    private Xid[] myXid = null;

    // Configuration information used to start the Transaction service
    private int timeout;
    private boolean jtmlocal;
    private InitialContext ictx;

    /**
     * Registry object containing the information declared in mbean-descriptors configuration files
     */
    private Registry oRegistry = null;
    /**
     * Reference of the current MBean server needed to register MBeans
     */
    private MBeanServer mbeanServer = null;
    // -------------------------------------------------------------------
    // JOnAS Service Implementation
    // -------------------------------------------------------------------

    /**
     * Init the Service.
     * Configuration information is passed thru a Context object.
     * @param ctx naming context containing configuration parameters
     * @throws ServiceException if service initialization failes
     */
    public void doInit(Context ctx) throws ServiceException {

        // get logger for this service
        logger = Log.getLogger(Log.JONAS_SERVER_PREFIX);
        super.initLogger(Log.getLogger(Log.JONAS_MANAGEMENT_PREFIX));

        // Gets the configuration
        String remote = "false";
        try {
            remote = (String) ctx.lookup(REMOTE);
        } catch (NamingException e) {
            // No problem if there is no value --> default value
        }
        if ((remote != null) && (!remote.equals(""))) {
            jtmlocal = !remote.equalsIgnoreCase("true");
        } else {
            jtmlocal = false;
        }

        String tstr = "60";
        try {
            tstr = (String) ctx.lookup(TIMEOUT);
        } catch (NamingException e) {
            // No problem if there is no value --> default value
        }
        if ((tstr != null) && (!tstr.equals(""))) {
            timeout = (new Integer(tstr)).intValue();
        } else {
            timeout = 60;
        }

        // Get the JMX Server via JMX Service
        try {
            mbeanServer =
                ((JmxService) ServiceManager.getInstance().getJmxService()).getJmxServer();
        } catch (Exception e) {
            // the JMX service may not be started
            mbeanServer = null;
        }
        // Use Jakarta Common Modeler API
        oRegistry = JonasMBeanTools.getRegistry();

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "TransactionService initialized. Timeout = " + timeout);
        }
    }

    /**
     * Start the Service
     * Initialization of the service is already done.
     * @throws ServiceException if service start fails
     */
    public void doStart() throws ServiceException {
        try {
            ictx = NamingManager.getInstance().getInitialContext();
        } catch (NamingException e) {
            logger.log(BasicLevel.ERROR, "TransactionService: Cannot get InitialContext:\n" + e);
            throw new ServiceException("TransactionService: Cannot get InitialContext", e);
        }

        // 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");
                }
                tm = new TransactionFactoryImpl();
                ictx.rebind("TMFactory", tm);
            } 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 {
                    tm = (TransactionFactory) ictx.lookup("TMFactory");
                    break;
                } catch (NamingException e) {
                    if (i < maxloops) {
                        logger.log(BasicLevel.WARN, "Cannot get TM factory - retrying...");
                        try {
                            Thread.sleep(2000 * (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);
                    }
                }
            }
        }

        // 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 Current(tm);
        setTimeout(timeout);

       try {
            if (mbeanServer != null) {
                // Register TransactionService MBean : only used by jo,nasAdmin's tree builder
                mbeanServer.registerMBean(this, JonasObjectName.transactionService());

                // JTAResource implemented by this Service
                // ------------------------------------------------
                try {
                    String sJTAResourceName = "JTAResource";
                    ObjectName onJTAResource =
                        J2eeObjectName.JTAResource(getDomainName(),
                                                   getJonasServerName(),
                                                   sJTAResourceName);
                    JTAResource jtaResourceMBean = new JTAResource(onJTAResource.toString(), this,
                            new Integer(timeout),
                            new Boolean(jtmlocal),
                            new Integer(tm.getPortNumber()),
                            tm.getHostName());
                    ManagedBean oManaged = oRegistry.findManagedBean("JTAResource");
                    ModelMBean oMBean = oManaged.createMBean(jtaResourceMBean);
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "JTAResource J2EEResource created");
                    }
                    mbeanServer.registerMBean(oMBean, onJTAResource);
                } catch (Exception e) {
                    e.printStackTrace();
                    logger.log(BasicLevel.ERROR, "JOnAS: Cannot register JTAResource mBean" + e);
                }
            }
        }  catch (ServiceException se) {
            // Jmx Service not available, do nothing
        }  catch (Exception e) {
            logger.log(BasicLevel.ERROR, "TransactionService: Cannot start the Transaction service:\n" + e);
            throw new ServiceException("TransactionService: Cannot start the Transaction service", e);
        }

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "TransactionService started, default timeout= " + timeout);
        }

    }

    /**
     * Stop the transaction service
     * Not already implementated
     * @throws ServiceException if the service stop fails
     */
    public void doStop() throws ServiceException {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Stop of TransactionService not already implemented");
        }
    }

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

    /**
     * Gets the Current object instance
     * @return the current object
     */
    public Current getCurrent() {
        return current;
    }

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

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

    /**
     * Gets the TransactionFactory object (JTM factory)
     * @return the transaction factory (JTM object)
     */
    public TransactionFactory getTransactionFactory() {
        return tm;
    }

    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(int t) {

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

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

    }

    /**
     * Get begun transactions number
     * @return total number of begun transactions
     */
    protected int getTotalBegunTransactions() {
        return current.getTotalBegunTransactions();
    }

    /**
     * Get committed transactions number
     * @return total number of committed transactions
     */
    protected int getTotalCommittedTransactions() {
        return current.getTotalCommittedTransactions();
    }

    /**
     * Get current transactions number
     * @return total number of current transactions
     */
    protected int getTotalCurrentTransactions() {
        return current.getTotalCurrentTransactions();
    }

    /**
     * Get expired transactions number
     * @return total number of expired transactions
     */
    protected int getTotalExpiredTransactions() {
        return current.getTotalExpiredTransactions();
    }

    /**
     * Get rollebacked transactions number
     * @return total number of rollbacked transactions
     */
    protected int getTotalRolledbackTransactions() {
        return current.getTotalRolledbackTransactions();
    }

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

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

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

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

    /**
     * Get all transactions that require administrator recovery action
     * @return Transactions that require administrator recovery action
     */
    protected 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
     */
    protected String [] getAllXAResource(String xtx) {
        String [] mysArray;

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

    /**
     * @return Returns all XAResources that require administrator recovery action.
     */
    protected int commitXAResource(String xatx) {
        int commiterror;
        commiterror = current.actionXAResource("commit", xatx);
        return commiterror;
    }

    /**
     * @return Returns all XAResources that require administrator recovery action.
     */
    protected int rollbackXAResource(String xatx) {
        int rollbackerror;
        rollbackerror = current.actionXAResource("rollback", xatx);
        return rollbackerror;
    }

    /**
     * @return Returns all XAResources that require administrator recovery action.
     */
    protected int forgetXAResource(String xatx) {
        int forgeterror;
        forgeterror = current.actionXAResource("forget", xatx);
        return forgeterror;
    }
}

