/**
 * JaDOrT: JASMINe Deployment Orchestration Tool
 * Copyright (C) 2008-2009 Bull S.A.S.
 * Copyright (C) 2008-2009 France Telecom R&D
 * Contact: jasmine@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: JonasServerAction.java 5672 2009-12-03 15:14:55Z alitokmen $
 * --------------------------------------------------------------------------
 */
package org.ow2.jasmine.jadort.service.action;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

import javax.management.Attribute;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXServiceURL;

import org.ow2.jasmine.jadort.api.entities.deployment.ApplicationBean;
import org.ow2.jasmine.jadort.api.entities.topology.ConnectorBean;
import org.ow2.jasmine.jadort.api.entities.topology.ServerBean;

/**
 * Action for the JOnAS 5 server.
 * 
 * @author Malek Chahine
 * @author Remy Bresson
 * @author S. Ali Tokmen
 */
public class JonasServerAction extends ServerAction {

    private String name;

    private ConnectorBean serverConnector;

    private ConnectorBean managerConnector;

    private ObjectName j2eeServer;

    private ObjectName versioningService;

    private boolean canDeployApplications;

    private String stopMethod;

    private boolean hasState;

    protected class VersionInformation {
        public String policy;

        public ObjectName mbean;

        public VersionInformation(final String policy, final ObjectName mbean) {
            this.policy = policy;
            this.mbean = mbean;
        }
    }

    protected class ApplicationInformation {
        public Map<String, VersionInformation> versions;

        public Set<ObjectName> managers;

        public ApplicationInformation() {
            this.versions = new HashMap<String, VersionInformation>();
            this.managers = new HashSet<ObjectName>();
        }
    }

    protected JonasServerAction(final ServerBean server) {
        this.name = server.getName();
        this.serverConnector = server.getServerConnector();
        this.managerConnector = server.getManagerConnector();
        this.appendToLog("Created JonasServerAction for server '" + this.name + "'");
    }

    @Override
    @SuppressWarnings("unchecked")
    public synchronized void checkJMXConnection() throws Exception {
        super.checkJMXConnection();

        if (this.versioningService == null) {
            Set<ObjectName> versioningService = this.mbscnx.queryNames(new ObjectName("*:type=service,name=versioning"), null);
            if (versioningService != null && !versioningService.isEmpty()) {
                ObjectName versioning = versioningService.iterator().next();
                if (((Boolean) this.mbscnx.getAttribute(versioning, "VersioningEnabled")).booleanValue()) {
                    this.versioningService = versioning;
                }
            }
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    protected void connectViaJMX() throws Exception {
        String url = this.serverConnector.getConnectorUrl();
        String username = this.serverConnector.getUsername();
        String password = this.serverConnector.getPassword();

        Map<String, Object> env = new HashMap<String, Object>();
        if (username != null && password != null) {
            String[] creds = {username, password};
            env.put(JMXConnector.CREDENTIALS, creds);
        }
        this.establishJMXConnection(new JMXServiceURL(url), env);
        this.j2eeServer = ((Set<ObjectName>) this.mbscnx.queryNames(new ObjectName("*:j2eeType=J2EEServer,*"), null))
            .iterator().next();

        this.stopMethod = null;
        this.canDeployApplications = false;
        for (MBeanOperationInfo operation : this.mbscnx.getMBeanInfo(this.j2eeServer).getOperations()) {
            if ("halt".equals(operation.getName())) {
                this.stopMethod = "halt";
            } else if ("deploy".equals(operation.getName())) {
                this.canDeployApplications = true;
            }
        }
        if (this.stopMethod == null) {
            this.stopMethod = "stop";
        }

        this.hasState = false;
        for (MBeanAttributeInfo attribute : this.mbscnx.getMBeanInfo(this.j2eeServer).getAttributes()) {
            if ("state".equals(attribute.getName())) {
                this.hasState = true;
                break;
            }
        }

        this.versioningService = null;

        this.appendToLog("JMX server connection OK for server '" + this.name + "', J2EEServer is '" + this.j2eeServer + "'");
    }

    @Override
    public boolean canDeployApplications() throws Exception {
        this.checkJMXConnection();

        return this.canDeployApplications;
    }

    @Override
    public boolean canStartStopServer() throws Exception {
        return true;
    }

    /**
     * Gets the list of deployed applications from the server.
     * 
     * @return Map of applications. The map should be read the following way:
     *         <ul>
     *         <li>Keys are the module name (EAR or standalone)
     *         <li>Values are the version details for the corresponding
     *         versioned module (map of versions + policy as well as all MBeans
     *         used for switching between versions). If the module is not
     *         versioned, its value is null.
     *         </ul>
     */
    @SuppressWarnings("unchecked")
    protected Map<String, ApplicationInformation> getApplicationsList() throws Exception {
        Map<String, ApplicationInformation> applications = new HashMap<String, ApplicationInformation>();

        // Non-versioned EJB-JARs and WARs, including those that are in EARs.
        Set<ObjectName> nonVersionedEjbsAndWars = new HashSet<ObjectName>();

        // All EARs, will later only contain non-versioned EARs
        Set<ObjectName> ears = (Set<ObjectName>) this.mbscnx.queryNames(new ObjectName("*:j2eeType=J2EEApplication,*"), null);

        // Key = real ObjectName, Value = manager ObjectName. This therefore
        // implies that there'll be multiple keys pointing to the same value if
        // there are multiple versions of an application that're deployed.
        Map<ObjectName, ObjectName> versionedEjbsAndWars = new HashMap<ObjectName, ObjectName>();

        // Get the list of all deployed applications, use the obtained
        // information in order to fill nonVersionedEjbsAndWars and
        // versionedEjbsAndWars.
        {
            Set<ObjectName> realEjbMBeans = new HashSet<ObjectName>();
            Set<ObjectName> virtualEjbMBeans = new HashSet<ObjectName>();
            for (ObjectName ejbjar : (Set<ObjectName>) this.mbscnx.queryNames(new ObjectName("*:j2eeType=EJBModule,*"), null)) {
                if ("true".equals(ejbjar.getKeyProperty("virtualContext"))) {
                    virtualEjbMBeans.add(ejbjar);
                } else {
                    realEjbMBeans.add(ejbjar);
                }
            }
            for (ObjectName virtualEjbMBean : virtualEjbMBeans) {
                String baseName = virtualEjbMBean.getKeyProperty("name").substring("VirtualContainer-".length());
                for (ObjectName ejbMBean : realEjbMBeans) {
                    // If the EJB-JAR is in an EAR, then the JNDI prefix will
                    // be set using the EAR's (J2EEApplication) name. Therefore
                    // check for the naming base in both attributes.
                    if (ejbMBean.getKeyProperty("name").startsWith(baseName)) {
                        versionedEjbsAndWars.put(ejbMBean, virtualEjbMBean);
                    } else {
                        String appName = ejbMBean.getKeyProperty("J2EEApplication");
                        if (appName != null && !appName.equals("none") && appName.startsWith(baseName)) {
                            versionedEjbsAndWars.put(ejbMBean, virtualEjbMBean);
                        }
                    }
                }
            }

            Set<ObjectName> realWarMBeans = new HashSet<ObjectName>();
            Set<ObjectName> virtualWarMBeans = new HashSet<ObjectName>();
            for (ObjectName war : (Set<ObjectName>) this.mbscnx.queryNames(new ObjectName("*:j2eeType=WebModule,*"), null)) {
                if ("true".equals(war.getKeyProperty("virtualContext"))) {
                    virtualWarMBeans.add(war);
                } else {
                    realWarMBeans.add(war);
                }
            }
            for (ObjectName virtualWarMBean : virtualWarMBeans) {
                Map<?, ?> contexts = (Map<?, ?>) this.mbscnx.getAttribute(virtualWarMBean, "Contexts");
                for (Object contextKey : contexts.keySet()) {
                    for (ObjectName warMBean : realWarMBeans) {
                        if (warMBean.getKeyProperty("name").startsWith(virtualWarMBean.getKeyProperty("name"))
                            && warMBean.getKeyProperty("name").endsWith(contextKey.toString())) {
                            versionedEjbsAndWars.put(warMBean, virtualWarMBean);
                            break;
                        }
                    }
                }
            }
            realEjbMBeans.removeAll(versionedEjbsAndWars.keySet());
            realWarMBeans.removeAll(versionedEjbsAndWars.keySet());
            nonVersionedEjbsAndWars.addAll(realEjbMBeans);
            nonVersionedEjbsAndWars.addAll(realWarMBeans);
        }

        // Process all versioned items
        for (Map.Entry<ObjectName, ObjectName> versionedEjbOrWar : versionedEjbsAndWars.entrySet()) {
            for (Map.Entry<String, String> context : ((Map<String, String>) this.mbscnx.getAttribute(versionedEjbOrWar
                .getValue(), "Contexts")).entrySet()) {
                ObjectName j2eeApplication;
                String applicationName = versionedEjbOrWar.getKey().getKeyProperty("J2EEApplication");
                if (applicationName == null || applicationName.equals("none") || applicationName.equals("null")) {
                    // Not an EAR, therefore applicationName is the WAR or
                    // EJB-JAR archive's name
                    applicationName = versionedEjbOrWar.getKey().getKeyProperty("name");
                    j2eeApplication = versionedEjbOrWar.getKey();

                    // Filter out bizarre characters from applicationName
                    int doubleSlash = applicationName.indexOf("//");
                    if (doubleSlash >= 0) {
                        int nextSlash = applicationName.indexOf('/', doubleSlash + 2);
                        if (nextSlash >= 0) {
                            // URL like //localhost/applicationName
                            applicationName = applicationName.substring(nextSlash + 1);
                        }
                    }
                } else {
                    Set<ObjectName> j2eeApplications = this.mbscnx.queryNames(new ObjectName("*:j2eeType=J2EEApplication,name="
                        + applicationName + ",*"), null);
                    if (j2eeApplications.size() == 0) {
                        throw new IllegalStateException("The application " + applicationName + " is not deployed correctly!");
                    }
                    j2eeApplication = j2eeApplications.iterator().next();
                    ears.remove(j2eeApplication);
                }

                String versionID;
                try {
                    URL j2eeApplicationURL = new URL("file:" + this.getPath(j2eeApplication));
                    Object[] opParams = new Object[] {j2eeApplicationURL};
                    String[] opSignature = new String[] {URL.class.getName()};
                    versionID = (String) this.mbscnx.invoke(this.versioningService, "getVersionID", opParams, opSignature);
                } catch (AttributeNotFoundException e) {
                    // The MBean doesn't have any path, therefore only has
                    // virtual versions. Deduce version.
                    int versionIdStart = applicationName.lastIndexOf("-version");
                    if (versionIdStart != -1) {
                        versionID = applicationName.substring(versionIdStart);
                    } else {
                        versionID = null;
                    }
                }
                if (versionID != null) {
                    String versionNumber = versionID.substring(8);
                    if (applicationName.endsWith(versionID)) {
                        // JOnAS-style naming, example: app-version1.2.3
                        applicationName = applicationName.substring(0, applicationName.length() - versionID.length());
                    }
                    if (applicationName.endsWith("-" + versionNumber)) {
                        // Maven-style naming, example: app-1.2.3
                        applicationName = applicationName.substring(0, applicationName.length() - versionNumber.length() - 1);
                    }
                    if (applicationName.length() == 0) {
                        applicationName = "[ ROOT ]";
                    }

                    // "-version" + versionNumber is for WARs,
                    // "version" + versionNumber + "/" is for EJBs
                    if (context.getKey().endsWith("-version" + versionNumber)
                        || context.getKey().endsWith("version" + versionNumber + "/")) {
                        // The version currently being processed [context.key]
                        // and the current application version [version] are the
                        // same, save details
                        ApplicationInformation applicationInformation = applications.get(applicationName);
                        if (applicationInformation == null) {
                            applicationInformation = new ApplicationInformation();
                            applications.put(applicationName, applicationInformation);
                        }
                        applicationInformation.versions.put(versionNumber, new VersionInformation(context.getValue(),
                            j2eeApplication));
                        applicationInformation.managers.add(versionedEjbOrWar.getValue());
                    }
                }
            }
        }

        // Process non-versioned EARs. This will also clean up
        // nonVersionedEjbsAndWars to make it have only the standalone archives
        for (ObjectName ear : ears) {
            String applicationName = ear.getKeyProperty("name");
            if (applications.containsKey(applicationName)) {
                throw new IllegalStateException("The application \"" + applicationName
                    + "\" seems not be versioned but has versioned WARs or EJBs!");
            }

            applications.put(applicationName, null);

            for (String module : (String[]) this.mbscnx.getAttribute(ear, "modules")) {
                nonVersionedEjbsAndWars.remove(new ObjectName(module));
            }
        }

        // Process non-versioned standalone archives
        for (ObjectName nonVersionedEjbOrWar : nonVersionedEjbsAndWars) {
            String applicationName = nonVersionedEjbOrWar.getKeyProperty("name");

            // Filter out bizarre characters from applicationName
            int doubleSlash = applicationName.indexOf("//");
            if (doubleSlash >= 0) {
                int nextSlash = applicationName.indexOf('/', doubleSlash + 2);
                if (nextSlash >= 0) {
                    // URL like //localhost/applicationName
                    applicationName = applicationName.substring(nextSlash + 1);
                }
            }

            if (applicationName.length() == 0) {
                applicationName = "[ ROOT ]";
            }

            if (!applications.containsKey(applicationName)) {
                applications.put(applicationName, null);
            }
        }

        return applications;
    }

    @Override
    @SuppressWarnings("unchecked")
    public List<ApplicationBean> listOfApplications() throws Exception {
        this.checkJMXConnection();

        List<ApplicationBean> applications = new ArrayList<ApplicationBean>();

        // List of deployed applications
        for (Map.Entry<String, ApplicationInformation> application : this.getApplicationsList().entrySet()) {
            if (application.getValue() == null) {
                ApplicationBean applicationBean = new ApplicationBean(application.getKey());
                applicationBean.setState(ServerAction.STATE_DEPLOYED);
                applications.add(applicationBean);
            } else {
                for (Map.Entry<String, VersionInformation> versionInformation : application.getValue().versions.entrySet()) {
                    ApplicationBean applicationBean = new ApplicationBean(application.getKey(), versionInformation.getKey());
                    applicationBean.setState(ServerAction.STATE_DEPLOYED);
                    applicationBean.setPolicy(versionInformation.getValue().policy);
                    applications.add(applicationBean);
                }
            }
        }

        // List of present yet not deployed applications
        try {
            for (String application : (List<String>) this.mbscnx.getAttribute(this.j2eeServer, "deployableEars")) {
                File applicationFile = new File(application);
                ApplicationBean applicationBean = new ApplicationBean(applicationFile.getName());
                applicationBean.setState(ServerAction.STATE_PRESENT);
                applicationBean.setFile(applicationFile);
                applications.add(applicationBean);
            }
        } catch (AttributeNotFoundException ignored) {
            // The deployableEars attribute is hidden if the optional EAR
            // service is not active. In that case, a AttributeNotFoundException
            // is thrown. We can therefore ignore that exception, it simply
            // means that the EAR service is not active.
        }

        try {
            for (String application : (List<String>) this.mbscnx.getAttribute(this.j2eeServer, "deployableJars")) {
                File applicationFile = new File(application);
                ApplicationBean applicationBean = new ApplicationBean(applicationFile.getName());
                applicationBean.setState(ServerAction.STATE_PRESENT);
                applicationBean.setFile(applicationFile);
                applications.add(applicationBean);
            }
        } catch (AttributeNotFoundException ignored) {
            // The deployableJars attribute is hidden if the optional EJB
            // service is not active. In that case, a AttributeNotFoundException
            // is thrown. We can therefore ignore that exception, it simply
            // means that the EJB service is not active.
        }

        try {
            for (String application : (List<String>) this.mbscnx.getAttribute(this.j2eeServer, "deployableWars")) {
                File applicationFile = new File(application);
                ApplicationBean applicationBean = new ApplicationBean(applicationFile.getName());
                applicationBean.setState(ServerAction.STATE_PRESENT);
                applicationBean.setFile(applicationFile);
                applications.add(applicationBean);
            }
        } catch (AttributeNotFoundException ignored) {
            // The deployableWars attribute is hidden if the optional WAR
            // service is not active. In that case, a AttributeNotFoundException
            // is thrown. We can therefore ignore that exception, it simply
            // means that the WAR service is not active.
        }

        return applications;
    }

    @Override
    @SuppressWarnings("unchecked")
    public String upload(final ApplicationBean application) throws Exception {
        this.checkJMXConnection();

        // If the depmonitor service is here, make sure it is not in development
        Set<ObjectName> depmonitor = (Set<ObjectName>) this.mbscnx.queryNames(new ObjectName("*:type=service,name=depmonitor"),
            null);
        if (!depmonitor.isEmpty()) {
            ObjectName depmonitorService = depmonitor.iterator().next();
            boolean developmentMode;
            try {
                // JOnAS 5.0.x and 5.1.x
                developmentMode = ((Boolean) this.mbscnx.getAttribute(depmonitorService, "developmentMode")).booleanValue();
            } catch (AttributeNotFoundException e) {
                // JOnAS 5.2.x
                developmentMode = ((Boolean) this.mbscnx.getAttribute(depmonitorService, "development")).booleanValue();
            }

            if (developmentMode) {
                throw new IllegalStateException(
                    "The JOnAS Deployment Monitor service's Development Mode is active on the target server. "
                        + "JaDOrT requires it to be disabled.");
            }
        }

        this.appendToLog("Uploading ApplicationBean '" + application.toString() + "'");

        byte[] bytesOfFile = ServerAction.obtainByteData(application.getFile());

        Object[] opParams = new Object[] {bytesOfFile, application.getFile().getName(), false};
        String[] opSignature = new String[] {"[B", "java.lang.String", "boolean"};

        String sendFile = (String) this.mbscnx.invoke(this.j2eeServer, "sendFile", opParams, opSignature);
        if (!sendFile.startsWith("/")) {
            sendFile = "/" + sendFile;
        }
        URL url = new URL("file:" + sendFile);
        this.appendToLog("Call to J2EEServer.sendFile successful, ApplicationBean saved as '" + url.toString() + "'");
        String result = url.getFile();

        return result;
    }

    @Override
    public void deploy(final String appName) throws Exception {
        this.checkJMXConnection();

        if (this.versioningService != null) {
            // Check versioning service state
            Object ddp = this.mbscnx.getAttribute(this.versioningService, "DefaultDeploymentPolicy");
            String defaultDeploymentPolicy = ddp.toString();

            if (!"Reserved".equalsIgnoreCase(defaultDeploymentPolicy)) {
                throw new IllegalStateException("The versioning service's default deployment policy is currently \""
                    + defaultDeploymentPolicy + "\". JaDOrT requires it to be \"Reserved\".");
            }
        }

        Object[] opParams = new Object[] {appName};
        String[] opSignature = new String[] {"java.lang.String"};

        this.appendToLog("Deploying application '" + appName + "'");
        this.mbscnx.invoke(this.j2eeServer, "deploy", opParams, opSignature);
        this.appendToLog("Call to J2EEServer.deploy successful for application '" + appName + "'");
    }

    @Override
    public String setDefault(final String appName) throws Exception {
        this.checkJMXConnection();
        this.appendToLog("Setting application '" + appName + "' as default");

        if (this.versioningService == null) {
            throw new IllegalStateException("The versioning service is not enabled. Please enable it and "
                + "redeploy all versioned applications before setting any version as default.");
        }

        // Find the application
        String newDefaultVersion = null;
        String oldDefaultVersionPath = null;
        ApplicationInformation applicationInformation = null;
        for (ApplicationInformation ai : this.getApplicationsList().values()) {
            if (ai != null) {
                for (Map.Entry<String, VersionInformation> vi : ai.versions.entrySet()) {
                    String path;
                    try {
                        path = this.getPath(vi.getValue().mbean);
                    } catch (AttributeNotFoundException ignored) {
                        // The MBean doesn't have any path, therefore only has
                        // virtual versions. Ignore for now.
                        continue;
                    }
                    if (path.endsWith(appName)) {
                        applicationInformation = ai;
                        newDefaultVersion = vi.getKey();
                        break;
                    }
                }
                if (applicationInformation != null) {
                    for (Map.Entry<String, VersionInformation> vi : ai.versions.entrySet()) {
                        if ("Default".equals(vi.getValue().policy)) {
                            oldDefaultVersionPath = this.getPath(vi.getValue().mbean);
                            break;
                        }
                    }
                    break;
                }
            }
        }

        if (applicationInformation == null) {
            this.appendToLog("   The application does not have any version manager");
            this.appendToLog("   It therefore will always stay as \"Default\"");
            return null;
        }

        // Set new default
        this.setPolicy(applicationInformation.managers, newDefaultVersion, "Default");
        if (oldDefaultVersionPath != null) {
            this.appendToLog("   Old default was '" + oldDefaultVersionPath + "'");
        } else {
            this.appendToLog("   There was no old default version");
        }
        return oldDefaultVersionPath;
    }

    protected String getPath(final ObjectName mbean) throws AttributeNotFoundException, InstanceNotFoundException,
        MBeanException, ReflectionException, IOException {
        String j2eeType = mbean.getKeyProperty("j2eeType");
        String path;
        if ("J2EEApplication".equals(j2eeType)) {
            path = this.mbscnx.getAttribute(mbean, "earUrl").toString();
        } else if ("EJBModule".equals(j2eeType)) {
            path = this.mbscnx.getAttribute(mbean, "url").toString();
        } else if ("WebModule".equals(j2eeType)) {
            path = this.mbscnx.getAttribute(mbean, "warURL").toString();
        } else {
            throw new IllegalArgumentException("Unknown j2eeType: \"" + j2eeType + "\"");
        }
        if (path.startsWith("file:")) {
            return path.substring(5);
        } else {
            return path;
        }
    }

    @SuppressWarnings("unchecked")
    protected void setPolicy(final Set<ObjectName> managers, final String version, final String policy)
        throws AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException, IOException {
        this.appendToLog("   Setting version '" + version + "' as " + policy);
        String versionID_WAR = "-version" + version;
        String versionID_EJB = "version" + version + "/";
        for (ObjectName manager : managers) {
            for (String context : ((Map<String, String>) this.mbscnx.getAttribute(manager, "Contexts")).keySet()) {
                if (context.contains(versionID_WAR) || context.contains(versionID_EJB)) {
                    Object[] opParams = new Object[] {context, policy};
                    String[] opSignature = new String[] {"java.lang.String", "java.lang.String"};
                    this.mbscnx.invoke(manager, "rebindContext", opParams, opSignature);
                    break;
                }
            }
        }
    }

    @Override
    public void undeploy(final String appName) throws Exception {
        this.checkJMXConnection();
        this.appendToLog("Undeploying application '" + appName + "'");

        Object[] opParams = new Object[] {appName};
        String[] opSignature = new String[] {"java.lang.String"};
        this.mbscnx.invoke(this.j2eeServer, "undeploy", opParams, opSignature);
        this.appendToLog("Call to J2EEServer.undeploy successful for application '" + appName + "'");
    }

    @Override
    public void erase(final String appName) throws Exception {
        this.checkJMXConnection();
        this.appendToLog("Erasing application '" + appName + "'");

        Object[] opParams = new Object[] {appName};
        String[] opSignature = new String[] {"java.lang.String"};
        this.mbscnx.invoke(this.j2eeServer, "removeModuleFile", opParams, opSignature);
        this.appendToLog("Call to J2EEServer.removeModuleFile successful for application '" + appName + "'");
    }

    @Override
    @SuppressWarnings("unchecked")
    public ApplicationBean getApplicationBean(final String appName) throws Exception {
        this.checkJMXConnection();

        URL url = new URL("file:" + appName);
        for (Map.Entry<String, ApplicationInformation> application : this.getApplicationsList().entrySet()) {
            if (application.getValue() != null) {
                for (Map.Entry<String, VersionInformation> version : application.getValue().versions.entrySet()) {
                    String path;
                    try {
                        path = this.getPath(version.getValue().mbean);
                    } catch (AttributeNotFoundException ignored) {
                        // The MBean doesn't have any path, therefore only has
                        // virtual versions. Ignore for now.
                        continue;
                    }
                    if (url.equals(new URL("file:" + path))) {
                        ApplicationBean applicationBean = new ApplicationBean();
                        applicationBean.setName(application.getKey());
                        applicationBean.setPolicy(version.getValue().policy);
                        applicationBean.setState(ServerAction.STATE_DEPLOYED);
                        applicationBean.setVersion(version.getKey());
                        this.appendToLog("ApplicationBean for application '" + appName
                            + "' is a deployed versioned application, returning '" + applicationBean.toString() + "'");
                        return applicationBean;
                    }
                }
            }
        }

        for (String application : (List<String>) this.mbscnx.getAttribute(this.j2eeServer, "deployedEars")) {
            if (url.equals(new URL("file:" + application))) {
                ApplicationBean applicationBean = new ApplicationBean(new File(application).getName());
                applicationBean.setState(ServerAction.STATE_DEPLOYED);
                this.appendToLog("ApplicationBean for application '" + appName
                    + "' is a deployed non-versioned application, returning '" + applicationBean.toString() + "'");
                return applicationBean;
            }
        }

        for (String application : (List<String>) this.mbscnx.getAttribute(this.j2eeServer, "deployedJars")) {
            if (url.equals(new URL("file:" + application))) {
                ApplicationBean applicationBean = new ApplicationBean(new File(application).getName());
                applicationBean.setState(ServerAction.STATE_DEPLOYED);
                this.appendToLog("ApplicationBean for application '" + appName
                    + "' is a deployed non-versioned application, returning '" + applicationBean.toString() + "'");
                return applicationBean;
            }
        }

        for (String application : (List<String>) this.mbscnx.getAttribute(this.j2eeServer, "deployedWars")) {
            if (url.equals(new URL("file:" + application))) {
                ApplicationBean applicationBean = new ApplicationBean(new File(application).getName());
                applicationBean.setState(ServerAction.STATE_DEPLOYED);
                this.appendToLog("ApplicationBean for application '" + appName
                    + "' is a deployed non-versioned application, returning '" + applicationBean.toString() + "'");
                return applicationBean;
            }
        }

        for (String application : (List<String>) this.mbscnx.getAttribute(this.j2eeServer, "deployableEars")) {
            if (url.equals(new URL("file:" + application))) {
                ApplicationBean applicationBean = new ApplicationBean(new File(application).getName());
                applicationBean.setState(ServerAction.STATE_PRESENT);
                this.appendToLog("ApplicationBean for application '" + appName + "' is a present application, returning '"
                    + applicationBean.toString() + "'");
                return applicationBean;
            }
        }

        for (String application : (List<String>) this.mbscnx.getAttribute(this.j2eeServer, "deployableJars")) {
            if (url.equals(new URL("file:" + application))) {
                ApplicationBean applicationBean = new ApplicationBean(new File(application).getName());
                applicationBean.setState(ServerAction.STATE_PRESENT);
                this.appendToLog("ApplicationBean for application '" + appName + "' is a present application, returning '"
                    + applicationBean.toString() + "'");
                return applicationBean;
            }
        }

        for (String application : (List<String>) this.mbscnx.getAttribute(this.j2eeServer, "deployableWars")) {
            if (url.equals(new URL("file:" + application))) {
                ApplicationBean applicationBean = new ApplicationBean(new File(application).getName());
                applicationBean.setState(ServerAction.STATE_PRESENT);
                this.appendToLog("ApplicationBean for application '" + appName + "' is a present application, returning '"
                    + applicationBean.toString() + "'");
                return applicationBean;
            }
        }

        this.appendToLog("Application '" + appName + "' not found");
        return null;
    }

    @Override
    @SuppressWarnings("unchecked")
    public int getActiveSessions(final String appName) throws Exception {
        this.checkJMXConnection();

        List<String> names = new ArrayList<String>();
        // Find all names for the application (EARs may have many)
        {
            URL url = new URL("file:" + appName);

            if (names.size() == 0) {
                for (ObjectName ear : (Set<ObjectName>) this.mbscnx.queryNames(new ObjectName("*:j2eeType=J2EEApplication,*"),
                    null)) {
                    if (url.equals(this.mbscnx.getAttribute(ear, "earUrl"))) {
                        String[] modules = (String[]) this.mbscnx.getAttribute(ear, "modules");
                        for (String module : modules) {
                            ObjectName moduleON = new ObjectName(module);
                            if ("WebModule".equals(moduleON.getKeyProperty("j2eeType"))) {
                                names.add(moduleON.getKeyProperty("name"));
                            }
                        }
                    }
                }
            }

            if (names.size() == 0) {
                for (ObjectName war : (Set<ObjectName>) this.mbscnx.queryNames(new ObjectName("*:j2eeType=WebModule,*"), null)) {
                    if (url.equals(this.mbscnx.getAttribute(war, "warURL"))) {
                        names.add(war.getKeyProperty("name"));
                    }
                }
            }
        }

        if (names.size() == 0) {
            throw new IllegalArgumentException("No application for appName '" + appName + "'");
        }

        int totalActiveSessions = 0;
        for (String fullPath : names) {
            while (fullPath.startsWith("/")) {
                fullPath = fullPath.substring(1);
            }
            int slash = fullPath.indexOf('/');

            String host = fullPath.substring(0, slash);
            String path = fullPath.substring(slash);

            for (ObjectName manager : (Set<ObjectName>) this.mbscnx.queryNames(new ObjectName("*:type=Manager,path=" + path
                + ",host=" + host), null)) {
                totalActiveSessions += ((Integer) this.mbscnx.getAttribute(manager, "activeSessions")).intValue();
            }
        }
        return totalActiveSessions;
    }

    @Override
    @SuppressWarnings("unchecked")
    public int getActiveSessions() throws Exception {
        int totalActiveSessions = 0;

        if (this.mbscnx != null) {
            this.checkJMXConnection();

            for (ObjectName manager : (Set<ObjectName>) this.mbscnx.queryNames(new ObjectName("*:type=Manager,*"), null)) {
                totalActiveSessions += ((Integer) this.mbscnx.getAttribute(manager, "activeSessions")).intValue();
            }
        }

        return totalActiveSessions;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void start() throws Exception {
        ObjectName cdObjectName = null;
        JMXConnector managerConnector = null;
        MBeanServerConnection managerMbscnx = null;

        try {
            this.checkJMXConnection();
        } catch (IOException e) {
            if (this.managerConnector != null) {
                String url = this.managerConnector.getConnectorUrl();
                String username = this.managerConnector.getUsername();
                String password = this.managerConnector.getPassword();

                this.appendToLog("Connection to server's JMX server failed.");
                this.appendToLog("Trying the associated Cluster Daemon's JMX server on URL '" + url + "'");

                Map<String, Object> env = new HashMap<String, Object>();
                if (username != null && password != null) {
                    String[] creds = {username, password};
                    env.put(JMXConnector.CREDENTIALS, creds);
                }

                MBeanServerConnection oldMbscnx = this.mbscnx;
                JMXConnector oldConnector = this.connector;
                try {
                    this.establishJMXConnection(new JMXServiceURL(url), env);
                    managerConnector = this.connector;
                    managerMbscnx = this.mbscnx;
                } finally {
                    this.connector = oldConnector;
                    this.mbscnx = oldMbscnx;
                }
                cdObjectName = ((Set<ObjectName>) managerMbscnx.queryNames(new ObjectName("*:type=ClusterDaemon,*"), null))
                    .iterator().next();

                this.appendToLog("JMX server connection OK for Cluster Daemon for server '" + this.name
                    + "', ClusterDaemon is '" + cdObjectName + "'");
            } else {
                throw e;
            }
        }

        this.appendToLog("Starting server");

        if (managerMbscnx == null || cdObjectName == null) {
            if (this.hasState) {
                String state = this.mbscnx.getAttribute(this.j2eeServer, "state").toString();
                if (state.contains("stopped") && state.contains("failed")) {
                    this.mbscnx.invoke(this.j2eeServer, "start", null, null);
                }
            } else {
                this.mbscnx.invoke(this.j2eeServer, "start", null, null);
            }
        } else {
            try {
                Object[] opParams = new Object[] {this.name, null, null};
                String[] opSignature = new String[] {String.class.getName(), String.class.getName(), String.class.getName()};
                managerMbscnx.invoke(cdObjectName, "start", opParams, opSignature);
            } finally {
                managerConnector.close();
                managerConnector = null;
                managerMbscnx = null;
                System.gc();
            }
        }

        this.appendToLog("Server started");
    }

    @Override
    public void stop() throws Exception {
        this.checkJMXConnection();
        this.appendToLog("Calling the stop method " + this.stopMethod + " on server");

        this.mbscnx.invoke(this.j2eeServer, this.stopMethod, null, null);

        this.appendToLog("Server stopped");
    }

    @Override
    public boolean isStarted() {
        try {
            this.checkJMXConnection();
            if (this.hasState) {
                String state = this.mbscnx.getAttribute(this.j2eeServer, "state").toString();
                return state.contains("running");
            } else {
                return true;
            }
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean enableOrDisableApplications(final boolean enable) throws Exception {
        this.checkJMXConnection();
        this.appendToLog("Enabling or disabling all applications on this server");

        boolean failed = false;
        for (ObjectName war : (Set<ObjectName>) this.mbscnx.queryNames(new ObjectName("*:j2eeType=WebModule,*"), null)) {
            if (war.getKeyProperty("virtualContext") == null) {
                String path = (String) this.mbscnx.getAttribute(war, "path");
                if (path == null || path.length() < 1) {
                    path = "/";
                }

                // warURLArgument is null for Web services deployed by EJBs
                Object warURLArgument = this.mbscnx.getAttribute(war, "warURL");
                if (warURLArgument != null) {
                    String warURL = warURLArgument.toString();
                    if (warURL.endsWith("/jonasAdmin.war") || warURL.endsWith("jonas-ctxroot.war")
                        || warURL.endsWith("jonas-doc-en.war")) {
                        this.appendToLog("\tApplication on path '" + path
                            + "' is a JOnAS-specific non-third-user application. Skipping...");
                        continue;
                    }
                }

                ObjectName filter;
                try {
                    filter = ((Set<ObjectName>) this.mbscnx.queryNames(new ObjectName("*:type=J2EEFilter,path=" + path), null))
                        .iterator().next();
                } catch (NoSuchElementException e) {
                    this.appendToLog("\tApplication on path '" + path
                        + "' doesn't have the OnlyAllowUsersWithSessionFilter filter! Please "
                        + "import the filter JAR from the jadort-samples package and set it in the application's descriptor");
                    failed = true;
                    continue;
                }

                this.mbscnx.setAttribute(filter, new Attribute("active", Boolean.valueOf(!enable)));
                this.appendToLog("\tFilter for application on path '" + path + "' has been set as " + (enable ? "in" : "")
                    + "active, application is therefore " + (enable ? "enabled" : "disabled"));
            }
        }
        if (failed) {
            this.appendToLog("At least one application on this server couldn't be " + (enable ? "enabled" : "disabled"));
            return false;
        } else {
            this.appendToLog("All applications on this server are now " + (enable ? "enabled" : "disabled"));
            return true;
        }
    }
}
