/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007-2008 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 (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.lib.jmbeans;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Properties;

import javax.management.AttributeChangeNotification;
import javax.management.AttributeChangeNotificationFilter;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.JMException;
import javax.management.MBeanException;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.MBeanServerNotification;
import javax.management.MalformedObjectNameException;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.ReflectionException;

import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.ow2.jonas.configuration.ConfigurationManager;
import org.ow2.jonas.depmonitor.MonitoringService;
import org.ow2.jonas.generators.wsgen.wrapper.WsGenWrapper;
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.jmbeans.monitoring.MemoryMonitoring;
import org.ow2.jonas.lib.management.javaee.J2EEState;
import org.ow2.jonas.lib.management.javaee.J2eeObjectName;
import org.ow2.jonas.lib.timer.TimerManager;
import org.ow2.jonas.lib.util.JModule;
import org.ow2.jonas.lib.util.JonasObjectName;
import org.ow2.jonas.properties.ServerProperties;
import org.ow2.jonas.versioning.VersioningService;
import org.ow2.util.archive.api.IArchive;
import org.ow2.util.archive.api.IArchiveManager;
import org.ow2.util.ee.deploy.api.deployable.IDeployable;
import org.ow2.util.ee.deploy.api.deployer.IDeployerManager;
import org.ow2.util.ee.deploy.impl.helper.DeployableHelper;
import org.ow2.util.ee.deploy.impl.helper.DeployableHelperException;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;

/**
 * The JSR77 J2EEServer MBean implementation.
 * Implement the StateManagement and Events specified in the JSR77.
 * @author Adriana.Danes@bull.net
 */
public class J2EEServer extends J2EEManagedObject implements NotificationListener, MBeanRegistration {

    /**
     * Logger.
     */
    private Log logger = LogFactory.getLog(J2EEServer.class);

    /**
     * Sequence number needed to send JMX notifications.
     */
    private long sequenceNumber = 0;

    /**
     * MBean server.
     */
    private MBeanServer mbeanServer = null;

    /**
     * Current J2EE Server state.
     */
    private J2EEState serverState;

    /**
     * Server start time.
     */
    private long startTime;

    // == Static properties of the server == //
    /**
     * Server vendor.
     */
    private String serverVendor = null;

    /**
     * Server version.
     */
    private String serverVersion = null;

    /**
     * Server name.
     */
    private String serverName = null;

    /**
     * Server domain name.
     */
    private String domainName = null;

    /**
     * Info on JONAS_BASE.
     */
    private String jonasBase = null;

    /**
     * Info on JONAS_ROOT.
     */
    private String jonasRoot = null;

    /**
     * Protocols defined for the server.
     */
    private String protocols = null;

    /**
     * Info on JONAS_BASE.
     */
    private String versions = null;

    /**
     * Server properties.
     */
    private ServerProperties serverProperties = null;

    /**
     * Memory monitoring.
     */
    private MemoryMonitoring monitor = null;

    /**
     * deployerManager instance needed for deploy/undeploy operation.
     */
    private IDeployerManager deployerManager = null;

    /**
     * archiveManager instance needed for deploy/undeploy operation.
     */
    private IArchiveManager archiveManager = null;

    /**
     * Configuration manager.
     */
    private ConfigurationManager configManager = null;

    // JSR77 attributes
    /**
     * The list of MBean names corresponding to the JVMs on which this server has running threads.
     */
    private ArrayList<String> javaVMs = null;

    /**
     * List of resources given by the corresponding OBJECT_NAMES.
     */
    private ArrayList<String> resources = null;

    /**
     * List of deployed modules and applications given by the corresponding OBJECT_NAMES.
     */
    private ArrayList<String> deployedObjects = null;

    /**
     * Versioning service.
     */
    private VersioningService versioningService;

    /**
     * Bootstrap system property name.
     */
    private static final String JONAS_BOOTSTRAP = "jonas.bootstrap";

    /**
     * OSGi BundleContext.
     */
    private BundleContext bc = null;

    /**
     * Event type list.
     */
    private String[] eventTypes;

    /**
     * MBean constructor.
     * @param objectName object name of the managed object
     */
    public J2EEServer(final String objectName) {
        super(objectName);
        javaVMs = new ArrayList<String>();
    }

    /**
     * MBean constructor.
     * @param objectName object name of the managed object
     * @param stateManageable if true, this managed object implements J2EE State Management Model
     * @param statisticsProvider if true, this managed object implements the J2EE StatisticProvide Model
     * @param eventProvider if true, this managed object implements the J2EE EventProvider Model
     */
    public J2EEServer(final String objectName, final boolean stateManageable, final boolean statisticsProvider,
            final boolean eventProvider, final BundleContext bc) {
        super(objectName, stateManageable, statisticsProvider, eventProvider);
        javaVMs = new ArrayList<String>();
        services = new ArrayList<String>();
        if (eventProvider) {
            eventTypes = new String[J2EEState.values().length];
            for (J2EEState state : J2EEState.values()) {
                eventTypes[state.ordinal()] = state.getName();
            }
        }
        if (stateManageable) {
            Date d = new Date();
            startTime = d.getTime();
        }
        resources = new ArrayList<String>();
        deployedObjects = new ArrayList<String>();
        monitor = new MemoryMonitoring();
        this.bc = bc;
    }

    public String[] getEventTypes() {
        return eventTypes;
    }

    public J2EEState getState() {
        return serverState;
    }

    public boolean isStarting() {
        if (serverState.equals(J2EEState.STARTING)) {
            return true;
        }
        return false;
    }

    public boolean isRunning() {
        if (serverState.equals(J2EEState.RUNNING)) {
            return true;
        }
        return false;
    }

    public boolean isFailed() {
        if (serverState.equals(J2EEState.FAILED)) {
            return true;
        }
        return false;
    }

    public boolean isStopping() {
        if (serverState.equals(J2EEState.STOPPING)) {
            return true;
        }
        return false;
    }

    public boolean isStopped() {
        if (serverState.equals(J2EEState.STOPPED)) {
            return true;
        }
        return false;
    }

    public long getStartTime() {
        return startTime;
    }

    public String getServerVendor() {
        return serverVendor;
    }

    public void setServerVendor(final String serverVendor) {
        this.serverVendor = serverVendor;
    }

    public String getServerVersion() {
        return serverVersion;
    }

    public void setServerVersion(final String serverVersion) {
        this.serverVersion = serverVersion;
    }

    public String getServerName() {
        return serverName;
    }

    public void setServerName(final String serverName) {
        this.serverName = serverName;
    }

    /**
     * @return jonas.base
     */
    public String getJonasBase() {
        return jonasBase;
    }

    /**
     * Used by the J2EEServer creator (MBeansRegistration).
     * @param jonasBase JONAS_BASE to be set
     */
    public void setJonasBase(final String jonasBase) {
        this.jonasBase = jonasBase;
    }

    /**
     * @return jonas.base
     */
    public String getJonasRoot() {
        return jonasRoot;
    }

    /**
     * Used by the J2EEServer creator (MBeansRegistration).
     * @param jonasRoot JONAS_ROOT to be set
     */
    public void setJonasRoot(final String jonasRoot) {
        this.jonasRoot = jonasRoot;
    }

    public String getProtocols() {
        return protocols;
    }

    public void setProtocols(final String protocols) {
        this.protocols = protocols;
    }

    public String getVersions() {
        return versions;
    }

    public void setVersions(final String versions) {
        this.versions = versions;
    }

    public boolean isActivated() {
        return monitor.getActivated();
    }

    /**
     * Set memory monitoring activation.
     * @param activated
     */
    public void setActivated(final boolean activated) {
        monitor.setActivated(activated);
    }

    public long getCurrentUsedMemory() {
        return monitor.usedMemory();
    }

    public long getCurrentTotalMemory() {
        return monitor.totalMemory();
    }

    public int getRange() {
        return monitor.getRange();
    }

    public void setRange(final int range) {
        monitor.setRange(range);
    }

    public int getSizeTableMeasures() {
        return monitor.getSizeTableMeasures();
    }

    public void setSizeTableMeasures(final int sizeTableMeasures) {
        monitor.setSizeTableMeasures(sizeTableMeasures);
    }

    public Long[] getTableMeasures() {
        return monitor.getTableMeasures();
    }

    /**
     * @return The resources list.
     */
    public ArrayList<String> getResources() {
        return resources;
    }

    /**
     * @return The deployed list.
     */
    public ArrayList<String> getDeployedObjects() {
        return deployedObjects;
    }

    /**
     * @return The current domain name.
     */
    public String getDomainName() {
        return domainName;
    }

    /**
     * Set the domain name.
     * @param domainName The current domain name
     */
    public void setDomainName(final String domainName) {
        this.domainName = domainName;
    }

    /**
     * @return the list of MBean names corresponding to the JVMs on which this server has running threads
     */
    public String[] getJavaVMs() {
        String[] result = new String[javaVMs.size()];
        int i = 0;
        for (String name : javaVMs) {
            result[i++] = name;
        }
        return result;
    }

    /**
     * Add an object name to the <code>javaVMs</code> list.
     * @param objectName Object name corresponding to a JVM MBean
     */
    public void addJavaVM(final String objectName) {
        javaVMs.add(objectName);
    }

    /**
     * @param versioningService  The versioning service to set.
     */
    public void setVersioningService(final VersioningService versioningService) {
        this.versioningService = versioningService;
    }

    /**
     * Sets the versioning service to null.
     */
    public void unsetVersioningService() {
        this.versioningService = null;
    }

    /**
     * @return The versioning service.
     */
    public VersioningService getVersioningService() {
        return this.versioningService;
    }

    /**
     * Start the server by starting all the non-mandatory services.
     * @throws Exception
     */
    public void start() throws Exception {
        if (serverState.equals(J2EEState.STOPPED) || serverState.equals(J2EEState.FAILED)) {
            // start operation is allowed
            setStarting();
            configManager.updateServiceConfigurations();
        } else {
            // start operation is not allowed
            throw new IllegalStateException(
                    "The start() operation can be invoked only when the server is in STOPPED or FAILED state");
        }
    }

    public void startRecursive() throws Exception {
        if (serverState.equals(J2EEState.STOPPED) || serverState.equals(J2EEState.FAILED)) {
            setStarting();
            configManager.updateServiceConfigurations();
        } else {
            // start operation is not allowed
            throw new IllegalStateException(
                    "The startRecursive() operation can be invoked only when the server is in STOPPED or FAILED state");
        }
    }

    /**
     * Stop the server by stopping all the non-mandatory services.
     */
    public void stop() throws Exception {
        if (serverState.equals(J2EEState.RUNNING) || serverState.equals(J2EEState.STARTING) || serverState.equals(J2EEState.FAILED)) {
            // stop operation is allowed
            setStopping();
            configManager.deleteServiceConfigurations();
        } else {
            // start operation is not allowed
            throw new IllegalStateException(
                    "The stop() operation can be invoked only when the server is in RUNNING, STARTING or FAILED state");
        }
    }

    /**
     * Halt the server.
     */
    public void halt() throws Exception {
        configManager.haltServer();
    }

    // Other JMX methods
    /**
     * Treat JMX notifications.
     * @param notification received notification
     * @param handback hand back object
     */
    public void handleNotification(final Notification notification, final Object handback) {
        // Treat notifications emitted by the jmx server
        if (notification instanceof MBeanServerNotification) {
            // ObjectName of the MBean that caused the notification
            ObjectName causeOn = ((MBeanServerNotification) notification).getMBeanName();
            // Check for some j2eeType MBEa,s
            String type = causeOn.getKeyProperty("j2eeType");
            if (J2EEResource.isJ2eeResourceType(type)) {
                handleResourceNotification(causeOn, notification.getType());
            }
            if (J2EEDeployedObject.isJ2EEDeployedObjectType(type)) {
                handleDeployedNotification(causeOn, notification.getType());
            }
        }
        // Treat other notifications
        if (notification instanceof AttributeChangeNotification) {
            String attName = ((AttributeChangeNotification) notification).getAttributeName();
            logger.debug("Received AttributeChangeNotification about attribute " + attName);
            // TODO make a static for the attribute name
            if ("readyToRunning".equals(attName)) {
                setRunning();
            }
        }
    }

    /**
     * Treat J2EE Resources registration/unregistration notifications.
     * @param notification received notification type
     * @param handback hand back object
     */
    private void handleResourceNotification(final ObjectName resourceOn, final String notificationType) {
        if (notificationType.equals(MBeanServerNotification.REGISTRATION_NOTIFICATION)) {
            addResource(resourceOn.toString());
        } else if (notificationType.equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
            removeResource(resourceOn.toString());
        }
    }

    /**
     * Treat J2EE Deployable registration/unregistration notifications.
     * @param notification received notification type
     * @param handback hand back object
     */
    private void handleDeployedNotification(final ObjectName resourceOn, final String notificationType) {
        if (notificationType.equals(MBeanServerNotification.REGISTRATION_NOTIFICATION)) {
            addDeployedObject(resourceOn.toString());
        } else if (notificationType.equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
            removeDeployedObject(resourceOn.toString());
        }
    }

    /**
     * Add a resource name to the <code>resources</code> list.
     * @param name OBJECT_NAME corresponding to a J2EEResource MBean
     */
    private void addResource(final String name) {
        synchronized (resources) {
            if (resources.contains(name)) {
                // TODO log message
            } else {
                resources.add(name);
                // TODO Send AttributeAddNotification or other notification (?)
            }
        }
    }

    /**
     * Add a deployed name to the <code>deployedObjects</code> list.
     * @param name OBJECT_NAME corresponding to a J2EE deployed object MBean
     */
    private void addDeployedObject(final String name) {
        synchronized (deployedObjects) {
            if (deployedObjects.contains(name)) {
                // TODO log message
            } else {
                deployedObjects.add(name);
                // TODO Send AttributeAddNotification or other notification (?)
            }
        }
    }

    /**
     * Remove a resource name from the resources list.
     * @param name resource name to remove
     */
    private void removeResource(final String name) {
        synchronized (resources) {
            int index = resources.indexOf(name);
            if (index > -1) {
                resources.remove(index);
                // TODO Send AttributeAddNotification or other notification (?)
            }
        }
    }

    /**
     * Remove a deployed object name from the deployedObjects list.
     * @param name deployed object name to remove
     */
    private void removeDeployedObject(final String name) {
        synchronized (deployedObjects) {
            int index = deployedObjects.indexOf(name);
            if (index > -1) {
                deployedObjects.remove(index);
                // TODO Send AttributeAddNotification or other notification (?)
            }
        }
    }

    public void postDeregister() {
    }

    /**
     * After registering, add myself as listener to notifications emitted by: - the MBeanServerDelegate (to receive JMX
     * registration/un-registration notifications).
     */
    public void postRegister(final Boolean registrationDone) {
        if (!registrationDone) {
            return;
        }
        try {
            ObjectName on = ObjectName.getInstance("JMImplementation:type=MBeanServerDelegate");
            mbeanServer.addNotificationListener(on, this, null, null);
        } catch (JMException me) {
            me.printStackTrace();
        } catch (NullPointerException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        if (Boolean.getBoolean(JONAS_BOOTSTRAP)) {
            setStopped();
        } else {
            setStarting();
        }
    }

    public void preDeregister() throws Exception {
    }

    public ObjectName preRegister(final MBeanServer server, final ObjectName name) throws Exception {
        this.mbeanServer = server;
        if (name == null) {
            return ObjectName.getInstance(getObjectName());
        } else {
            return name;
        }
    }

    protected void setServicesRunning() {
        // Get the service reference for Deployment Monitoring service
        ServiceReference reference = bc.getServiceReference(MonitoringService.class.getName());
        if (reference == null) {
            // No deployment Monitoring service,
            setRunning();
        } else {
            setDepmonitoringRunning(reference);
        }
    }

    protected void setDepmonitoringRunning(final ServiceReference reference) {
        ServiceReference depMonitoringReference = reference;
        if (depMonitoringReference == null) {
            depMonitoringReference = bc.getServiceReference(MonitoringService.class.getName());
        }
        // Add a notification listener on the 'readyToRunning' attribute of the monitoring service
        AttributeChangeNotificationFilter filter = new AttributeChangeNotificationFilter();
        filter.enableAttribute("readyToRunning");
        try {
            mbeanServer.addNotificationListener(JonasObjectName.deployableMonitorService(domainName), this, filter, null);
        } catch (InstanceNotFoundException e) {
            e.printStackTrace();
        }

        // Start the monitoring of deployables
        MonitoringService monitoringService = (MonitoringService) bc.getService(depMonitoringReference);
        monitoringService.startMonitoring();
    }

    /**
     * Log state information.
     */
    private void info(final String state) {
        logger.info("JOnAS server ''{0}'' {1}", serverName, state);
    }

    // Protected methods for state management
    protected void setStarting() {
        serverState = J2EEState.STARTING;
        Notification notif = new Notification(serverState.getName(), getObjectName(), sequenceNumber++);
        sendNotification(notif);
        info("STARTING");
    }

    protected void setRunning() {
        serverState = J2EEState.RUNNING;
        Notification notif = new Notification(serverState.getName(), getObjectName(), sequenceNumber++);
        sendNotification(notif);
        info("RUNNING");
    }

    protected void setStopping() {
        serverState = J2EEState.STOPPING;
        Notification notif = new Notification(serverState.getName(), getObjectName(), sequenceNumber++);
        sendNotification(notif);
        info("STOPPING");
    }

    protected void setStopped() {
        serverState = J2EEState.STOPPED;
        Notification notif = new Notification(serverState.getName(), getObjectName(), sequenceNumber++);
        sendNotification(notif);
        info("STOPPED");
    }

    protected void setFailed() {
        serverState = J2EEState.FAILED;
        Notification notif = new Notification(serverState.getName(), getObjectName(), sequenceNumber++);
        sendNotification(notif);
        info("FAILED");
    }

    /**
     * @param deployerManager reference to the deployerManager
     */
    public void setDeployerManager(final IDeployerManager deployerManager) {
        this.deployerManager = deployerManager;
    }

    /**
     * @param archiveManager reference to the archiveManager
     */
    public void setArchiveManager(final IArchiveManager archiveManager) {
        this.archiveManager = archiveManager;
    }

    /**
     * Deploy a file to a local deployer.
     * @param fileName the name of the file to deploy
     */
    public void deploy(final String fileName) {
        logger.debug("Deploying ", fileName);

        // check if the given entry is a valid file
        File f = new File(fileName);
        if (!f.isAbsolute()) {
            throw new RuntimeException("The given filename '" + fileName + "' is not an absolute file.");
        }

        final IDeployable deployable = getDeployable(fileName);

        // Deploy the file in an execution block
        IExecution<Void> exec = new IExecution<Void>() {
            public Void execute() throws RuntimeException {
                try {
                    deployerManager.deploy(deployable);
                } catch (Exception e) {
                    logger.error("Cannot deploy the deployable ", deployable, e);
                    throw new RuntimeException("Cannot deploy the deployable '" + deployable + "' : " + e.getMessage());
                }
                return null;
            }
        };

        // Execute
        ExecutionResult<Void> result = RunnableHelper.execute(getClass().getClassLoader(), exec);

        // Throw an RuntimeException if needed
        if (result.hasException()) {
            throw new RuntimeException(result.getException());
        }
    }

    /**
     * Undeploy a file from a local deployer.
     * @param fileName the name of the file to undeploy
     */
    public void undeploy(final String fileName) {
        logger.debug("Undeploying ", fileName);

        final IDeployable deployable = getDeployable(fileName);

        // Undeploy the file in an execution block
        IExecution<Void> exec = new IExecution<Void>() {
            public Void execute() throws RuntimeException {
                try {
                    deployerManager.undeploy(deployable);
                    runGC();
                } catch (Exception e) {
                    logger.error("Cannot undeploy the deployable ", deployable, e);
                    throw new RuntimeException("Cannot undeploy the deployable '" + deployable + "' : " + e.getMessage());
                }
                return null;
            }
        };

        // Execute
        ExecutionResult<Void> result = RunnableHelper.execute(getClass().getClassLoader(), exec);

        // Throw an RuntimeException if needed
        if (result.hasException()) {
            throw new RuntimeException(result.getException());
        }
    }

    /**
     * Convert a ready to deploy file to a deployable object.
     * @param fileName the name of the file
     * @return the deployable object
     */
    protected IDeployable getDeployable(final String fileName) {
        IDeployable deployable = null;
        // Get File
        File file = new File(fileName);

        // check file
        if (!file.exists()) {
            throw new RuntimeException("The file '" + fileName + "' is not present on the filesystem.");
        }

        IArchive archive = archiveManager.getArchive(file);
        if (archive == null) {
            logger.error("No archive found for the invalid file ", file);
            throw new RuntimeException("No archive found for the invalid file '" + file + "'.");
        }
        try {
            deployable = DeployableHelper.getDeployable(archive);
        } catch (DeployableHelperException e) {
            logger.error("Cannot get a deployable for the archive ", archive, e);
            throw new RuntimeException("Cannot get a deployable for the archive '" + archive + "' : " + e.getMessage());
        }

        return deployable;
    }

    public boolean isDeployed(final String fileName) throws Exception {
        File file = new File(fileName);
        if (file.exists()) {
            try {
                return isDeployedFile(file.toURL());
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                throw e;
            }
        }
        return false;
    }

    public String getJvmInfos() {
        StringBuffer sb = new StringBuffer();
        sb.append("JVM used is ");
        sb.append(System.getProperty("java.version"));
        sb.append(" version of ");
        sb.append(System.getProperty("java.vm.name"));
        sb.append("-");
        sb.append(System.getProperty("java.vm.version"));
        sb.append("/");
        sb.append(System.getProperty("java.vendor"));
        sb.append(" vendor on ");
        sb.append(System.getProperty("os.name"));
        sb.append(" ");
        sb.append(System.getProperty("os.version"));
        sb.append("/");
        sb.append(System.getProperty("os.arch"));
        sb.append(" OS.");
        return sb.toString();
    }

    public void runGC() throws RemoteException {
        Runtime.getRuntime().gc();
    }

    public Properties getConfigFileEnv() {
        return serverProperties.getConfigFileEnv();
    }

    public void setConfigManager(final ConfigurationManager configManager) {
        this.configManager = configManager;
    }

    public TimerManager getTimerManager() {
        return monitor.getTimerManager();
    }

    public void setTimerManager(final TimerManager timerManager) {
        monitor.setTimerManager(timerManager);
    }

    public void setAllProperties(final ServerProperties allProperties) {
        this.serverProperties = allProperties;
    }

    // --------------- JonasAdmin support -----------------------//

    /**
     * This variable contains the list of directory where to lookup for JAR, WAR, RAR and EAR files for deployment.
     */
    private ArrayList<String> repositoryDirs = null;

    /**
     * The repositoryDirs must contain at least the 'deploy' directory. TODO replace deployDir with value from
     * DeployableMonitor.DEFAULT_DIRECTORY
     */
    public void initRepositoryDirs() {
        repositoryDirs = new ArrayList<String>();
        String deployDir = jonasBase + File.separator + "deploy";
        repositoryDirs.add(deployDir);
    }

    public void setDirectories(final String dirs) {
        ArrayList<String> additionalDirectories = convertToList(dirs);
        for (String dir : additionalDirectories) {
            String dirName = dir;
            File tmpFile = new File(dir);

            // Not an absolute file, should be relative to JONAS_BASE.
            if (!tmpFile.isAbsolute()) {
                dirName = jonasBase + File.separator + dir;
                tmpFile = new File(dirName);
            }

            // Directory exists ?
            if (tmpFile.exists()) {
                repositoryDirs.add(dirName);
            }
        }
    }

    /**
     * TODO This method is copied from AbsServiceImpl. Utility method to convert a given String of comma-separated elements to a
     * List.
     * @param value String value
     * @return List constructed from the given String
     */
    private ArrayList<String> convertToList(final String value) {
        // only handle String list
        // separator is the ','
        String[] values = value.split(",");
        ArrayList<String> injection = new ArrayList<String>();

        // We should have at least 1 real value
        if (!((values.length == 1) && ("".equals(values[0])))) {
            for (int i = 0; i < values.length; i++) {
                String part = values[i];
                injection.add(part.trim());
            }
        }
        return injection;
    }

    private ArrayList<URL> getInstalledFiles() throws MalformedURLException, IOException {
        ArrayList<URL> al = getInstalledWars();
        // next type jars
        ArrayList<URL> al1 = getInstalledJars();
        for (URL url : al1) {
            al.add(url);
        }
        // next rars
        al1 = getInstalledRars();
        for (URL url : al1) {
            al.add(url);
        }
        // and ears
        al1 = getInstalledEars();
        for (URL url : al1) {
            al.add(url);
        }
        return al;
    }

    private ArrayList<URL> getInstalledJars() throws MalformedURLException, IOException {
        ArrayList<URL> al = new ArrayList<URL>();
        ArrayList<URL> al1 = null;
        for (String dir : repositoryDirs) {
            al1 = JModule.getInstalledFileInDir(dir, JModule.EJBJAR_EXTENSION);
            for (URL url : al1) {
                al.add(url);
            }
        }
        return al;
    }

    private ArrayList<URL> getInstalledWars() throws MalformedURLException, IOException {
        ArrayList<URL> al = new ArrayList<URL>();
        String extension = JModule.WAR_EXTENSION;
        ArrayList<URL> al1 = null;
        for (String dir : repositoryDirs) {
            al1 = JModule.getInstalledFileInDir(dir, extension);
            for (URL url : al1) {
                al.add(url);
            }
        }
        return al;
    }

    private ArrayList<URL> getInstalledRars() throws MalformedURLException, IOException {
        ArrayList<URL> al = new ArrayList<URL>();
        String extension = JModule.RAR_EXTENSION;
        ArrayList<URL> al1 = null;
        for (String dir : repositoryDirs) {
            al1 = JModule.getInstalledFileInDir(dir, extension);
            for (URL url : al1) {
                al.add(url);
            }
        }
        return al;
    }

    private ArrayList<URL> getInstalledEars() throws MalformedURLException, IOException {
        ArrayList<URL> al = new ArrayList<URL>();
        String extension = JModule.EAR_EXTENSION;
        ArrayList<URL> al1 = null;
        for (String dir : repositoryDirs) {
            al1 = JModule.getInstalledFileInDir(dir, extension);
            for (URL url : al1) {
                al.add(url);
            }
        }
        return al;
    }

    private ArrayList<URL> getDeplyableFiles() throws MalformedURLException, IOException, MalformedObjectNameException,
            NullPointerException, AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
        ArrayList<URL> installedUrls = getInstalledFiles();
        ArrayList<URL> deployableUrls = new ArrayList<URL>();
        for (URL installed : installedUrls) {
            if (!isDeployedFile(installed)) {
                deployableUrls.add(installed);
            }
        }
        return deployableUrls;
    }

    private ArrayList<URL> getDeplyableJars() throws MalformedURLException, IOException, MalformedObjectNameException,
            NullPointerException, AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
        ArrayList<URL> installedUrls = getInstalledJars();
        ArrayList<URL> deployableUrls = new ArrayList<URL>();
        for (URL installed : installedUrls) {
            if (!isDeployedFile(installed)) {
                deployableUrls.add(installed);
            }
        }
        return deployableUrls;
    }

    private ArrayList<URL> getDeplyableWars() throws MalformedURLException, IOException, MalformedObjectNameException,
            NullPointerException, AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
        ArrayList<URL> installedUrls = getInstalledWars();
        ArrayList<URL> deployableUrls = new ArrayList<URL>();
        for (URL installed : installedUrls) {
            if (!isDeployedFile(installed)) {
                deployableUrls.add(installed);
            }
        }
        return deployableUrls;
    }

    private ArrayList<URL> getDeplyableEars() throws MalformedURLException, IOException, MalformedObjectNameException,
            NullPointerException, AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
        ArrayList<URL> installedUrls = getInstalledEars();
        ArrayList<URL> deployableUrls = new ArrayList<URL>();
        for (URL installed : installedUrls) {
            if (!isDeployedFile(installed)) {
                deployableUrls.add(installed);
            }
        }
        return deployableUrls;
    }

    private ArrayList<URL> getDeplyableRars() throws MalformedURLException, IOException, MalformedObjectNameException,
            NullPointerException, AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
        ArrayList<URL> installedUrls = getInstalledRars();
        ArrayList<URL> deployableUrls = new ArrayList<URL>();
        for (URL installed : installedUrls) {
            if (!isDeployedFile(installed)) {
                deployableUrls.add(installed);
            }
        }
        return deployableUrls;
    }

    private boolean isDeployedWar(final URL fileUrl) throws NullPointerException, MalformedObjectNameException,
            AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
        for (String deployed : deployedObjects) {
            ObjectName on = ObjectName.getInstance(deployed);
            String j2eeType = on.getKeyProperty("j2eeType");
            if (j2eeType.equals("WebModule")) {
                try {
                    URL url = (URL) mbeanServer.getAttribute(on, "warURL");
                    if (fileUrl.equals(url)) {
                        return true;
                    }
                } catch(AttributeNotFoundException e) {
                    // This means the WebModule is a virtual context, ignore
                }
            }
        }
        return false;
    }

    private boolean isDeployedJar(final URL fileUrl) throws NullPointerException, MalformedObjectNameException,
            AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
        for (String deployed : deployedObjects) {
            ObjectName on = ObjectName.getInstance(deployed);
            String j2eeType = on.getKeyProperty("j2eeType");
            if (j2eeType.equals("EJBModule")) {
                URL url = (URL) mbeanServer.getAttribute(on, "url");
                if (fileUrl.equals(url)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean isDeployedRar(final URL fileUrl) throws NullPointerException, MalformedObjectNameException,
            AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
        for (String deployed : deployedObjects) {
            ObjectName on = ObjectName.getInstance(deployed);
            String j2eeType = on.getKeyProperty("j2eeType");
            if (j2eeType.equals("ResourceAdapterModule")) {
                URL url = (URL) mbeanServer.getAttribute(on, "rarURL");
                if (fileUrl.equals(url)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean isDeployedEar(final URL fileUrl) throws NullPointerException, MalformedObjectNameException,
            AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
        for (String deployed : deployedObjects) {
            ObjectName on = ObjectName.getInstance(deployed);
            String j2eeType = on.getKeyProperty("j2eeType");
            if (j2eeType.equals("J2EEApplication")) {
                URL url = (URL) mbeanServer.getAttribute(on, "earUrl");
                if (fileUrl.equals(url)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean isDeployedFile(final URL fileUrl) throws NullPointerException, MalformedObjectNameException, AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
        if(isDeployedJar(fileUrl)) {
            return true;
        }
        if (isDeployedWar(fileUrl)) {
            return true;
        }
        if (isDeployedRar(fileUrl)) {
            return true;
        }
        if (isDeployedEar(fileUrl)) {
            return true;
        }
        return false;
    }

    public ArrayList<String> getDeployableFiles() {
        ArrayList<String> result = new ArrayList<String>();
        ArrayList<URL> deployableUrls;
        try {
            deployableUrls = getDeplyableFiles();
            for (URL url : deployableUrls) {
                result.add(extractPath(url));
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return result;
    }

    public ArrayList<String> getDeployableJars() {
        ArrayList<String> result = new ArrayList<String>();
        ArrayList<URL> deployableUrls;
        try {
            deployableUrls = getDeplyableJars();
            for (URL url : deployableUrls) {
                result.add(extractPath(url));
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return result;
    }

    public ArrayList<String> getDeployableEars() {
        ArrayList<String> result = new ArrayList<String>();
        ArrayList<URL> deployableUrls;
        try {
            deployableUrls = getDeplyableEars();
            for (URL url : deployableUrls) {
                result.add(extractPath(url));
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return result;
    }

    public ArrayList<String> getDeployableWars() {
        ArrayList<String> result = new ArrayList<String>();
        ArrayList<URL> deployableUrls;
        try {
            deployableUrls = getDeplyableWars();
            for (URL url : deployableUrls) {
                result.add(extractPath(url));
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return result;
    }

    public ArrayList<String> getDeployableRars() {
        ArrayList<String> result = new ArrayList<String>();
        ArrayList<URL> deployableUrls;
        try {
            deployableUrls = getDeplyableRars();
            for (URL url : deployableUrls) {
                result.add(extractPath(url));
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return result;
    }

    /**
     * @return List of deployed WAR files.
     * @throws NullPointerException
     * @throws MalformedObjectNameException
     * @throws ReflectionException
     * @throws MBeanException
     * @throws InstanceNotFoundException
     * @throws AttributeNotFoundException
     * @throws Exception
     */
    public ArrayList<String> getDeployedWars() throws Exception {
        ArrayList<String> result = new ArrayList<String>();
        for (String deployed : deployedObjects) {
            ObjectName on = ObjectName.getInstance(deployed);
            String j2eeType = on.getKeyProperty("j2eeType");
            String j2eeApplication = on.getKeyProperty("J2EEApplication");
            boolean inEar = true;
            if (J2eeObjectName.NONE.equals(j2eeApplication)) {
                inEar = false;
            }
            if (j2eeType.equals("WebModule") && !inEar) {
                try {
                    URL url = (URL) mbeanServer.getAttribute(on, "warURL");
                    result.add(extractPath(url));
                } catch(AttributeNotFoundException e) {
                    // This means the WebModule is a virtual context, ignore
                }
            }
        }
        return result;
    }

    /**
     * @return List of deployed JAR files.
     * @throws Exception
     */
    public ArrayList<String> getDeployedJars() throws Exception {
        ArrayList<String> result = new ArrayList<String>();
        for (String deployed : deployedObjects) {
            ObjectName on = ObjectName.getInstance(deployed);
            String j2eeType = on.getKeyProperty("j2eeType");
            String j2eeApplication = on.getKeyProperty("J2EEApplication");
            boolean inEar = true;
            if (J2eeObjectName.NONE.equals(j2eeApplication)) {
                inEar = false;
            }
            if (j2eeType.equals("EJBModule") && !inEar) {
                URL url = (URL) mbeanServer.getAttribute(on, "url");
                result.add(extractPath(url));
            }
        }
        return result;
    }

    /**
     * @return List of deployed RAR files.
     * @throws Exception
     */
    public ArrayList<String> getDeployedRars() throws Exception {
        ArrayList<String> result = new ArrayList<String>();
        for (String deployed : deployedObjects) {
            ObjectName on = ObjectName.getInstance(deployed);
            String j2eeType = on.getKeyProperty("j2eeType");
            String j2eeApplication = on.getKeyProperty("J2EEApplication");
            boolean inEar = true;
            if (J2eeObjectName.NONE.equals(j2eeApplication)) {
                inEar = false;
            }
            if (j2eeType.equals("ResourceAdapterModule") && !inEar) {
                URL url = (URL) mbeanServer.getAttribute(on, "rarURL");
                result.add(extractPath(url));
            }
        }
        return result;
    }

    /**
     * @return List of deployed EAR files.
     * @throws Exception
     */
    public ArrayList<String> getDeployedEars() throws Exception {
        ArrayList<String> result = new ArrayList<String>();
        for (String deployed : deployedObjects) {
            ObjectName on = ObjectName.getInstance(deployed);
            String j2eeType = on.getKeyProperty("j2eeType");
            if (j2eeType.equals("J2EEApplication")) {
                URL url = (URL) mbeanServer.getAttribute(on, "earUrl");
                result.add(extractPath(url));
            }
        }
        return result;
    }

    public ArrayList<String> getDeployedFiles() throws Exception {
        ArrayList<String> result = getDeployedEars();
        ArrayList<String> resultNext = getDeployedWars();
        for (String file : resultNext) {
            result.add(file);
        }
        resultNext = getDeployedJars();
        for (String file : resultNext) {
            result.add(file);
        }
        resultNext = getDeployedRars();
        for (String file : resultNext) {
            result.add(file);
        }
        return result;
    }

    public String sendFile(final byte[] fileContent, String fileName, boolean replaceExisting) throws Exception {

        File directoryUploadedFile = null;
        FileOutputStream fos = null;
        try {
            // We save all files in the "deploy" directory
            String dir = jonasBase + File.separator + "deploy";

            if (versioningService != null && versioningService.isVersioningEnabled()) {
                // If file is versioned, make sure file is renamed before
                // uploading in order to ensure filename uniqueness
                File tempFile = File.createTempFile("jonas_renameUpload", ".tmp");
                tempFile.deleteOnExit();
                FileOutputStream tempFos = new FileOutputStream(tempFile);
                tempFos.write(fileContent);
                tempFos.close();
                String versionID = versioningService.getVersionID(tempFile);
                tempFile.delete();
                if (versionID != null && !fileName.contains(versionID)) {
                    int extensionStarts = fileName.length() - 4;
                    fileName = fileName.substring(0, extensionStarts) + versionID + fileName.substring(extensionStarts);
                }
            }

            // set the dest file
            directoryUploadedFile = new File(dir, fileName);

            // check, by default we can't overwrite an existing file.
            if (directoryUploadedFile.exists() && !replaceExisting) {
                throw new Exception("File '" + directoryUploadedFile + "' already exists on the server.");
            }

            // write the bytes to the given file
            fos = new FileOutputStream(directoryUploadedFile);
            fos.write(fileContent);
        } finally {
            if (fos != null) {
                try {
                    // close the output stream
                    fos.close();
                } catch (IOException ioe) {
                    logger.debug("Cannot close the output stream", ioe);
                }
            }

        }
        if (directoryUploadedFile != null) {
            logger.info("sendFile return directoryUploadedFile= " + directoryUploadedFile.getPath());
            return directoryUploadedFile.getPath();
        } else {
            return "error, no uploaded file";
        }
    }

    /**
     * Dump the given bytes to a local file and then return the path to this file.
     * @param fileName the name of the file to distribute
     * @param fileContent the content of the given file
     * @return the path of the distributed file
     */
    public String distribute(final String fileName, final byte[] fileContent) throws Exception {
        logger.info("Distribute file to the local filesystem with the name = ''{0}''.", fileName);

        String path = sendFile(fileContent, fileName, true);
        return path;
    }

    /**
     * Remove a specified J2EE module
     * @param fileName Name of file to remove
     * @return true if file has been removed
     * @throws Exception if remove fails
     */
    public boolean removeModuleFile(final String fileName) throws Exception {
        boolean existFile = false;
        // File could exists (absolute file)
        File searchedFile = new File(fileName);
        // Directory where to search given file
        String dir = null;
        if (!searchedFile.exists()) {
            // search in deploy directory first
            dir = jonasBase + File.separator + "deploy";
            searchedFile = new File(dir, fileName);
        }
        if (searchedFile.exists()) {
            existFile = true;
        } else {
            // search in a specific directory
            dir = getFolderDir(fileName);
            searchedFile = new File(dir, fileName);
            if (searchedFile.exists()) {
                existFile = true;
            }
        }
        if (existFile) {
            return searchedFile.delete();
        } else {
            throw new Exception("File '" + searchedFile + "' was not found on the JOnAS server. Cannot remove it");
        }

    }

    /**
     * Get directory based on a filename
     * @param fileName name of the file
     * @return folder of type JONAS_BASE/XXXX/
     * @throws Exception if file is not a J2EE archive
     */
    private String getFolderDir(final String fileName) throws Exception {
        // based on extension
        String dir = null;
        // EJB
        if (fileName.toLowerCase().endsWith(".jar")) {
            dir = jonasBase + File.separator + "ejbjars";
        } else if (fileName.toLowerCase().endsWith(".war")) {
            // War file
            dir = jonasBase + File.separator + "webapps";
        } else if (fileName.toLowerCase().endsWith(".ear")) {
            // ear file
            dir = jonasBase + File.separator + "apps";
        } else if (fileName.toLowerCase().endsWith(".rar")) {
            // rar file
            dir = jonasBase + File.separator + "rars";
        } else {
            // invalid type
            throw new Exception("Invalid extension for the file '" + fileName + "'. Valid are .jar, .war, .ear, .rar");
        }
        return dir;
    }

    /**
     * Apply WsGen on a local file and generate webservices artifacts
     * @param pathname : local path name to the component
     * @param unpacked true if we want to have an unpacked directory as result
     * @return Returns the path to the modified archive
     */
    public String wsgenLocalFile(final String pathname, final Boolean unpacked) {
        try {
            WsGenWrapper wsgen = new WsGenWrapper();
            String outputFilename = wsgen.callWsGenExecute(pathname, unpacked);
            boolean modified = wsgen.callWsGenIsInputModifed();
            if (modified) {
                return outputFilename;
            } else {
                return null;
            }
        } catch (Exception e) {
            logger.warn("Cannot generate web services for this component {0}", pathname, e);
            return null;
        }
    }

    /**
     * Extracts a file path from a URL (no matter whether it has been
     * URI-escaped previously or not).
     *
     * @param url  URL to extract file path from.
     *
     * @return  File path of that URL.
     */
    private String extractPath(final URL url) {
        String path;
        try {
            // Escape the path if necessary
            File file = new File(url.toURI());
            path = file.toURL().getFile();
        } catch (Exception e) {
            path = url.getPath();
        }
        return path;
    }

    // Service management
    private ServiceManager serviceManager = null;

    private ArrayList<String> services = null;

    public void setServiceManager(final ServiceManager serviceManager) {
        this.serviceManager = serviceManager;
    }

    public String[] getServices() {
        services = serviceManager.getServices();
        String[] result = new String[services.size()];
        int i = 0;
        for (String service : services) {
            result[i++] = service;
        }
        return result;
    }

    public String getServiceDescription(final String service) {
        return serviceManager.getServiceDescription(service);
    }

    public String getServiceState(final String service) {
        ServiceState state =  serviceManager.getServiceState(service);
        return state.toString();
    }

}
