/*
 * JBoss, Home of Professional Open Source
 * Copyright 2007, 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) 2007,
 * @author JBoss Inc.
 */
package org.jboss.jbossts;

import org.jboss.logging.Logger;
import org.jboss.jbossts.xts.recovery.coordinator.at.ACCoordinatorRecoveryModule;
import org.jboss.jbossts.xts.recovery.coordinator.at.SubordinateCoordinatorRecoveryModule;
import org.jboss.jbossts.xts.recovery.coordinator.ba.BACoordinatorRecoveryModule;
import org.jboss.jbossts.xts.recovery.participant.at.ATParticipantRecoveryModule;
import org.jboss.jbossts.xts.recovery.participant.ba.BAParticipantRecoveryModule;

//import com.arjuna.mw.wst.deploy.WSTXInitialisation;
//import com.arjuna.mw.wst.UserTransaction;
//import com.arjuna.mw.wst.TransactionManager;
//import com.arjuna.mw.wst.UserBusinessActivity;
//import com.arjuna.mw.wst.BusinessActivityManager;
//import com.arjuna.wsc.ContextFactoryMapper;
//import com.arjuna.wsc.messaging.ActivationCoordinatorProcessorImpl;
//import com.arjuna.wsc.messaging.RegistrationCoordinatorProcessorImpl;
//import com.arjuna.mwlabs.wsc.ContextFactoryMapperImple;
import com.arjuna.services.framework.task.TaskManager;
import com.arjuna.services.framework.startup.Sequencer;
//import com.arjuna.webservices.HandlerRegistry;
//import com.arjuna.webservices.SoapRegistry;
//import com.arjuna.webservices.SoapService;
//import com.arjuna.webservices.SoapClient;
//import com.arjuna.webservices.util.ClassLoaderHelper;
//import com.arjuna.webservices.wsba.policy.CoordinatorCompletionCoordinatorPolicy;
//import com.arjuna.webservices.wsba.policy.CoordinatorCompletionParticipantPolicy;
//import com.arjuna.webservices.wsba.policy.ParticipantCompletionCoordinatorPolicy;
//import com.arjuna.webservices.wsba.policy.ParticipantCompletionParticipantPolicy;
//import com.arjuna.webservices.wsba.BusinessActivityConstants;
//import com.arjuna.webservices.wsba.processors.CoordinatorCompletionParticipantProcessor;
//import com.arjuna.webservices.wsba.processors.ParticipantCompletionParticipantProcessor;
//import com.arjuna.webservices.wsba.processors.CoordinatorCompletionCoordinatorProcessor;
//import com.arjuna.webservices.wsba.processors.ParticipantCompletionCoordinatorProcessor;
//import com.arjuna.webservices.wsat.policy.CompletionCoordinatorPolicy;
//import com.arjuna.webservices.wsat.policy.CompletionInitiatorPolicy;
//import com.arjuna.webservices.wsat.policy.CoordinatorPolicy;
//import com.arjuna.webservices.wsat.policy.ParticipantPolicy;
//import com.arjuna.webservices.wsat.AtomicTransactionConstants;
//import com.arjuna.webservices.wsat.processors.CompletionCoordinatorProcessor;
//import com.arjuna.webservices.wsat.processors.ParticipantProcessor;
//import com.arjuna.webservices.wsat.processors.CoordinatorProcessor;
//import com.arjuna.webservices.wsarjtx.policy.TerminationParticipantPolicy;
//import com.arjuna.webservices.wsarjtx.policy.TerminationCoordinatorPolicy;
//import com.arjuna.webservices.wsarjtx.ArjunaTXConstants;
//import com.arjuna.webservices.wsarjtx.processors.TerminationCoordinatorProcessor;
//import com.arjuna.webservices.transport.http.HttpClient;
//import com.arjuna.webservices.wsarj.policy.ArjunaPolicy;
//import com.arjuna.webservices.wscoor.policy.ActivationCoordinatorPolicy;
//import com.arjuna.webservices.wscoor.policy.ActivationRequesterPolicy;
//import com.arjuna.webservices.wscoor.policy.RegistrationCoordinatorPolicy;
//import com.arjuna.webservices.wscoor.policy.RegistrationRequesterPolicy;
//import com.arjuna.webservices.wscoor.CoordinationConstants;
//import com.arjuna.webservices.wscoor.processors.ActivationCoordinatorProcessor;
//import com.arjuna.webservices.wscoor.processors.RegistrationCoordinatorProcessor;
//import com.arjuna.webservices.wsaddr.policy.AddressingPolicy;
//import com.arjuna.wst.messaging.*;
import com.arjuna.ats.arjuna.recovery.RecoveryManager;
import com.arjuna.webservices.util.TransportTimer;

import javax.management.MBeanServer;
import java.net.InetAddress;
//import com.arjuna.ats.arjuna.recovery.RecoveryModule;

/**
 * $Id$
 */
public class XTSService implements XTSServiceMBean {

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

    <bean name="XTSService" class="org.jboss.transactions.XTSService">
        <annotation>@org.jboss.aop.microcontainer.aspects.jmx.JMX(name="jboss.xts:service=XTSService", exposedInterface=org.jboss.transactions.XTSServiceMBean.class, registerDirectly=true)</annotation>

       <depends>jboss.web:service=WebServer</depends>
       <depends>jboss:service=TransactionManager</depends>
   </bean>

</deployment>

 */

    // TODO expose as bean properties
    private int taskManagerMinWorkerCount = 0;
    private int taskManagerMaxWorkerCount = 10;

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

    private ACCoordinatorRecoveryModule acCoordinatorRecoveryModule = null;
    private SubordinateCoordinatorRecoveryModule atSubordinateCoordinatorRecoveryModule = null;
    private ATParticipantRecoveryModule atParticipantRecoveryModule = null;

    private BACoordinatorRecoveryModule baCoordinatorRecoveryModule = null;
    private BAParticipantRecoveryModule baParticipantRecoveryModule = null;

    // TODO: how to use a (per application) remote coordinator?
    // does the http servlet param indicate its own location and the
    // coordinatorURL indicate the coord??

    // ./HashedActionStore/defaultStore/StateManager/BasicAction/AtomicAction/TwoPhaseCoordinator/TwoPhase/ACCoordinator/

//    	    <property
//	    name="com.arjuna.ats.arjuna.recovery.recoveryExtension1"
//	    value="com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule"/>
    // recovers /StateManager/BasicAction/TwoPhaseCoordinator/AtomicAction
    // not what we need.

    // TODO: new recovery mod, inheriting from existing base version e.g. AtomicActionRecoveryModule - change typex

    public XTSService() {}

    public void start() throws Exception
    {
        log.info("JBossTS XTS Transaction Service - starting");

        // read unified properties file (replaces wscf.xml and wstx.xml)
        // Configuration.initialise("/jbossxts.xml");

        // before we can allow the services to start up we need to identify the server
        // bind address and the web service port so they services register themselves
        // in the registry using the correct URL

        // the app server's MicroContainer and ServiceBindingManager should work together
        // to supply us the properties used by the web server. Check that it's done so:
        // if this blows up, changes are binding.xml or XTS's jboss-beans.xml is broken.
        if(httpBindInetAddress == null || (httpPort == 0 && httpsPort == 0)) {
            log.error("insufficient webserver address:port information available - unable to start XTS.");
            throw new Exception("insufficient webserver address:port information available - unable to start XTS.");
        }

        // The app servers gives us a InetAddress, but our config system is String based.
        // so we convert it here, then XTS internally convert it back later. sigh.
        // likewise for ints to Strings for hte port numbers.

        String bindAddress = httpBindInetAddress.getHostName();

        if (bindAddress == null || "".equals(bindAddress)) {
            // use the ip address instead of the name
            bindAddress= httpBindInetAddress.getHostAddress();
        }

        System.setProperty(com.arjuna.wsc.common.Environment.XTS_BIND_ADDRESS, bindAddress);
        System.setProperty(com.arjuna.wsc11.common.Environment.XTS_BIND_ADDRESS, bindAddress);

        if(httpPort != 0) {
            System.setProperty(com.arjuna.wsc.common.Environment.XTS_BIND_PORT, ""+httpPort);
            System.setProperty(com.arjuna.wsc11.common.Environment.XTS_BIND_PORT, ""+httpPort);
        }

        if(httpsPort != 0) {
            System.setProperty(com.arjuna.wsc.common.Environment.XTS_SECURE_BIND_PORT, ""+httpsPort);
            System.setProperty(com.arjuna.wsc11.common.Environment.XTS_SECURE_BIND_PORT, ""+httpsPort);
        }

        // see if the coordinatorURL or host/port has been specified on the command line
        // if so then we need to record that fact here so we override any value
        // supplied in the  config file
        // but we don't do this if we have already saved it and we are now reloading XTS
        // yeeurrch really need to stop using System properties

        if (System.getProperty(com.arjuna.wsc.common.Environment.XTS_COMMAND_LINE_COORDINATOR_URL) == null) {
            String coordinatorURL = System.getProperty(com.arjuna.mw.wst.common.Environment.COORDINATOR_URL);
            String coordinatorScheme = System.getProperty(com.arjuna.mw.wst.common.Environment.COORDINATOR_SCHEME);
            String coordinatorHost = System.getProperty(com.arjuna.mw.wst.common.Environment.COORDINATOR_HOST);
            String coordinatorPort = System.getProperty(com.arjuna.mw.wst.common.Environment.COORDINATOR_PORT);
            String coordinatorPath = System.getProperty(com.arjuna.mw.wst.common.Environment.COORDINATOR_PATH);
            if (coordinatorURL != null) {
                System.setProperty(com.arjuna.wsc.common.Environment.XTS_COMMAND_LINE_COORDINATOR_URL, coordinatorURL);
            } else if (coordinatorScheme != null || coordinatorHost != null || coordinatorPort != null || coordinatorPath != null) {
                if (coordinatorScheme == null) {
                    coordinatorScheme = "http";
                }
                if (coordinatorHost == null) {
                    coordinatorHost = (bindAddress != null ? bindAddress : "127.0.0.1");
                }
                if (coordinatorPort == null) {
                    if ("https".equals(coordinatorScheme)) {
                        coordinatorPort = (httpsPort != 0 ? ""+httpsPort : "8443");
                    } else {
                        coordinatorPort = (httpPort != 0 ? ""+httpPort : "8080");
                    }
                }
                if (coordinatorPath == null) {
                    coordinatorPath = "ws-c10/soap/ActivationCoordinator";
                }
                coordinatorURL = coordinatorScheme + "://" + coordinatorHost + ":" + coordinatorPort + "/" + coordinatorPath;
                System.setProperty(com.arjuna.wsc.common.Environment.XTS_COMMAND_LINE_COORDINATOR_URL, coordinatorURL);
                System.setProperty(com.arjuna.mw.wst.common.Environment.COORDINATOR_URL, coordinatorURL);
            }
        }

        // ok now do the same for the 1.1 env settings

        if (System.getProperty(com.arjuna.wsc11.common.Environment.XTS_COMMAND_LINE_COORDINATOR_URL) == null) {
            String coordinatorURL = System.getProperty(com.arjuna.mw.wst11.common.Environment.COORDINATOR_URL);
            String coordinatorScheme = System.getProperty(com.arjuna.mw.wst11.common.Environment.COORDINATOR_SCHEME);
            String coordinatorHost = System.getProperty(com.arjuna.mw.wst11.common.Environment.COORDINATOR_HOST);
            String coordinatorPort = System.getProperty(com.arjuna.mw.wst11.common.Environment.COORDINATOR_PORT);
            String coordinatorPath = System.getProperty(com.arjuna.mw.wst11.common.Environment.COORDINATOR_PATH);
            if (coordinatorURL != null) {
                System.setProperty(com.arjuna.wsc11.common.Environment.XTS_COMMAND_LINE_COORDINATOR_URL, coordinatorURL);
            } else if (coordinatorScheme != null || coordinatorHost != null || coordinatorPort != null || coordinatorPath != null) {
                if (coordinatorScheme == null) {
                    coordinatorScheme = "http";
                }
                if (coordinatorHost == null) {
                    coordinatorHost = (bindAddress != null ? bindAddress : "127.0.0.1");
                }
                if (coordinatorPort == null) {
                    if ("https".equals(coordinatorScheme)) {
                        coordinatorPort = (httpsPort != 0 ? ""+httpsPort : "8443");
                    } else {
                        coordinatorPort = (httpPort != 0 ? ""+httpPort : "8080");
                    }
                }
                if (coordinatorPath == null) {
                    coordinatorPath = "ws-c11/ActivationService";
                }
                coordinatorURL = coordinatorScheme + "://" + coordinatorHost + ":" + coordinatorPort + "/" + coordinatorPath;
                System.setProperty(com.arjuna.wsc11.common.Environment.XTS_COMMAND_LINE_COORDINATOR_URL, coordinatorURL);
                System.setProperty(com.arjuna.mw.wst11.common.Environment.COORDINATOR_URL, coordinatorURL);
            }
        }
        // now it is safe to let the Sequencer class run any intiialisation routines it needs

        Sequencer.unlatch();

        TaskManagerInitialisation(); // com.arjuna.services.framework.admin.TaskManagerInitialisation : initialise the Task Manager


        // install the AT recovery modules
        
        acCoordinatorRecoveryModule = new ACCoordinatorRecoveryModule();

        // ensure Implementations are installed into the inventory before we register the module

        acCoordinatorRecoveryModule.install();

        // we don't need to install anything in the Inventory for this recovery module as it
        // uses the same records as those employed by ACCoordinatorRecoveryModule

        atSubordinateCoordinatorRecoveryModule = new SubordinateCoordinatorRecoveryModule();

        atSubordinateCoordinatorRecoveryModule.install();

        // we don't need to install anything in the Inventory for this recovery module as it
        // manages its own ObjectStore records but we do need it to create the recovery manager
        // singleton.

        atParticipantRecoveryModule = new ATParticipantRecoveryModule();

        atParticipantRecoveryModule.install();

        // ok, now the recovery mocules for the BA coordinator and participant

        baCoordinatorRecoveryModule = new BACoordinatorRecoveryModule();

        // ensure Implementations are installed into the inventory before we register the module

        baCoordinatorRecoveryModule.install();

        // we don't need to install anything in the Inventory for this recovery module as it
        // manages its own ObjectStore records but we do need it to create the recovery manager
        // singleton.

        baParticipantRecoveryModule = new BAParticipantRecoveryModule();

        baParticipantRecoveryModule.install();

        // we assume the tx manager has started, hence initializing the recovery manager.
        // to guarantee this our mbean should depend on the tx mgr mbean. (but does that g/tee start or just load?)

        //  recovery should perform better if we register partiicpants first since this allows the XTS client
        // recovery module to have a try at recreating the participant before its coordinator attempts to
        // talk to it when they are both in the same VM. it also means the participant nay attempt bottom-up
        // recovery before the coordinator is ready but coordinator recovery is probably going to happen quicker.

        // similarly, it is better to recreate subordinate coordinators before recreating ordinary coordinators
        // because the latter may need the former to be present when the parent and subordinate are both in the
        // same VM.

        RecoveryManager.manager().addModule(atParticipantRecoveryModule);
        RecoveryManager.manager().addModule(baParticipantRecoveryModule);

        RecoveryManager.manager().addModule(atSubordinateCoordinatorRecoveryModule);

        RecoveryManager.manager().addModule(acCoordinatorRecoveryModule);
        RecoveryManager.manager().addModule(baCoordinatorRecoveryModule);
    }

    public void stop() throws Exception
    {
        log.info("JBossTS XTS Transaction Service - stopping");

        if (baCoordinatorRecoveryModule != null) {
            // remove the module, making sure any scan which might be using it has completed
            RecoveryManager.manager().removeModule(baCoordinatorRecoveryModule, true);
            // ok, now it is safe to get the recovery manager to uninstall its Implementations from the inventory
            baCoordinatorRecoveryModule.uninstall();
        }
        if (acCoordinatorRecoveryModule != null) {
            // remove the module, making sure any scan which might be using it has completed
            RecoveryManager.manager().removeModule(acCoordinatorRecoveryModule, true);
            // ok, now it is safe to get the recovery manager to uninstall its Implementations from the inventory
            acCoordinatorRecoveryModule.uninstall();
        }
        if (atSubordinateCoordinatorRecoveryModule != null) {
            // remove the module, making sure any scan which might be using it has completed
            RecoveryManager.manager().removeModule(atSubordinateCoordinatorRecoveryModule, true);
            // ok, now it is safe to get the recovery manager to uninstall its Implementations from the inventory
            atSubordinateCoordinatorRecoveryModule.uninstall();
        }
        if (baParticipantRecoveryModule != null) {
            // remove the module, making sure any scan which might be using it has completed
            RecoveryManager.manager().removeModule(baParticipantRecoveryModule, true);
            // call uninstall even though it is currently a null op for this module
            baParticipantRecoveryModule.uninstall();
        }
        if (atParticipantRecoveryModule != null) {
            // remove the module, making sure any scan which might be using it has completed
            RecoveryManager.manager().removeModule(atParticipantRecoveryModule, true);
            // call uninstall even though it is currently a null op for this module
            atParticipantRecoveryModule.uninstall();
        }

        TaskManager.getManager().shutdown() ; // com.arjuna.services.framework.admin.TaskManagerInitialisation

        /*
         * this will be done by the servlet shutdown code
        // HttpClientInitialisation
        final SoapRegistry soapRegistry = SoapRegistry.getRegistry() ;
        soapRegistry.removeSoapClient("http") ;
        soapRegistry.removeSoapClient("https") ;
        */
    }

    private void TaskManagerInitialisation()
    {
        final TaskManager taskManager = TaskManager.getManager() ;
        taskManager.setMinimumWorkerCount(taskManagerMinWorkerCount) ;
        taskManager.setMaximumWorkerCount(taskManagerMaxWorkerCount) ;
    }

    public MBeanServer getMbeanServer()
    {
       return mbeanServer;
    }

    public void setMbeanServer(MBeanServer mbeanServer)
    {
       this.mbeanServer = mbeanServer;
    }

    private MBeanServer mbeanServer = null;

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

    // These setters are used to allow MC/ServiceBindingManger to relay information from the Web server
    // seee bindings.xml and jboss-beans.xml

    public InetAddress getHttpBindInetAddress() {
        return httpBindInetAddress;
    }

    public void setHttpBindInetAddress(InetAddress httpBindInetAddress) {
        this.httpBindInetAddress = httpBindInetAddress;
    }

    private InetAddress httpBindInetAddress = null;

    public int getHttpPort() {
        return httpPort;
    }

    public void setHttpPort(int httpPort) {
        this.httpPort = httpPort;
    }

    int httpPort = 0;

    public int getHttpsPort() {
        return httpsPort;
    }

    public void setHttpsPort(int httpsPort) {
        this.httpsPort = httpsPort;
    }

    int httpsPort = 0;

    // transport timing property reads/writes are redirected to the TransportTimer class
    // however we cap the period settings to a minimum of 5 seconds and the timeout setting
    // to a minimum of 10 seconds

    private static final long MIN_PERIOD = 5 * 1000;
    private static final long MIN_TIMEOUT = 10 * 1000;

    public long getInitialTransportPeriod()
    {
        return TransportTimer.getTransportPeriod();
    }

    public void setInitialTransportPeriod(long initialTransportPeriod)
    {
        if (initialTransportPeriod > MIN_PERIOD) {
            TransportTimer.setTransportPeriod(initialTransportPeriod);
        } else {
            TransportTimer.setTransportPeriod(MIN_PERIOD);
        }
    }

    public long getMaximumTransportPeriod()
    {
        return TransportTimer.getMaximumTransportPeriod();
    }

    public void setMaximumTransportPeriod(long maximumTransportPeriod)
    {
        if (maximumTransportPeriod > MIN_PERIOD) {
            TransportTimer.setMaximumTransportPeriod(maximumTransportPeriod);
        } else {
            TransportTimer.setMaximumTransportPeriod(MIN_PERIOD);
        }
    }

    public long getTransportTimeout()
    {
        return TransportTimer.getTransportTimeout();
    }

    public void setTransportTimeout(long transportTimeout)
    {
        if (transportTimeout > MIN_TIMEOUT) {
            TransportTimer.setTransportTimeout(transportTimeout);
        } else {
            TransportTimer.setTransportTimeout(MIN_TIMEOUT);
        }
    }
}