/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007-2009 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: J2EEServer.java 20644 2010-12-10 17:36:23Z alitokmen $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.lib.jmbeans;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

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 javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import javax.management.openmbean.TabularData;
import javax.management.openmbean.TabularDataSupport;
import javax.management.openmbean.TabularType;

import org.osgi.service.packageadmin.PackageAdmin;
import org.ow2.jonas.Version;
import org.ow2.jonas.configuration.ConfigurationManager;
import org.ow2.jonas.ejb3.IEasyBeansService;
import org.ow2.jonas.lib.jmbeans.monitoring.MemoryMonitoring;
import org.ow2.jonas.lib.management.javaee.J2EEDeployedObject;
import org.ow2.jonas.lib.management.javaee.J2EEManagedObject;
import org.ow2.jonas.lib.management.javaee.J2EEResource;
import org.ow2.jonas.lib.management.javaee.J2EEServerState;
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.management.J2EEServerService;
import org.ow2.jonas.management.ServiceManager;
import org.ow2.jonas.properties.ServerProperties;
import org.ow2.jonas.registry.RegistryService;
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.deployable.OSGiDeployable;
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;
import org.ow2.util.plan.bindings.repository.Repository;
import org.ow2.util.plan.repository.api.IRepositoryManager;
import org.ow2.util.url.URLUtils;


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

    /**
     * Logger.
     */
    private static final 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 J2EEServerState serverState = null;

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

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

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

    /**
     * Info on JONAS_BASE.
     */
    private String jonasBase = 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 operations.
     */
    private IDeployerManager deployerManager = null;

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

    /**
     * The repository manager.
     */
    private IRepositoryManager repositoryManager = 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;

    /**
     * Package admin service.
     */
    private PackageAdmin packageAdminService = null;

    /**
     * Registery service instance.
     */
    private RegistryService registryService = null;

    /**
     * EasyBeans service instance.
     */
    private IEasyBeansService easyBeansService;

    /**
     * Standby system property name.
     */
    private static final String JONAS_STANDBY = "jonas.standby";

    /**
     * Start date system property.
     */
    private static final String JONAS_START_DATE = "jonas.start.date";

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

    /**
     * MBean constructor.
     */
    public J2EEServer() {
        setStateManageable(true);
        setStatisticsProvider(false);
        setEventProvider(true);

        if (isEventProvider()) {
            eventTypes = new String[J2EEServerState.values().length];
            for (J2EEServerState state : J2EEServerState.values()) {
                eventTypes[state.ordinal()] = state.getName();
            }
        }

        this.startTime = System.currentTimeMillis();

        this.serverState = J2EEServerState.INITIAL;
        this.resources = new ArrayList<String>();
        this.deployedObjects = new ArrayList<String>();
        this.monitor = new MemoryMonitoring();
        this.javaVMs = new ArrayList<String>();
    }

    public void startJ2EEServer() throws Exception {
        // this.versions = props.getVersionsFile();
        this.versions = "Versions";
        this.serverVendor = Version.VENDOR;
        this.serverVersion = Version.getNumber();
        this.jonasBase = getJonasBase();

        // TODO get a reference on a TimerManager service.
        setTimerManager(TimerManager.getInstance());
        setActivated(true);

        createProtocols();
    }

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

    public long getStartTime() {
        return startTime;
    }

    /**
     * @return the uptime of the server (in ms)
     */
    public long getUptime() {
        return System.currentTimeMillis() - startTime;
    }

    public String getServerVendor() {
        return serverVendor;
    }

    public String getServerVersion() {
        return serverVersion;
    }

    public String getServerName() {
        return serverProperties.getServerName();
    }

    /**
     * @return jonas.base
     */
    public String getJonasBase() {
        return serverProperties.getValue("jonas.base");
    }

    /**
     * @return jonas.root
     */
    public String getJonasRoot() {
        return serverProperties.getValue("jonas.root");
    }

    public String getProtocols() {
        return protocols;
    }

    public String getVersions() {
        return 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 List<String> getResources() {
        return resources;
    }

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

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

    /**
     * @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 bindVersioningService(final VersioningService versioningService) {
        this.versioningService = versioningService;
    }

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

    /**
     * @param packageAdminService The packageAdmin service to set.
     */
    public void bindPackageAdminService(final PackageAdmin packageAdminService) {
        this.packageAdminService = packageAdminService;
    }

    /**
     * Sets the packageAdmin service to null.
     */
    public void unbindPackageAdminService() {
        this.packageAdminService = null;
    }


    /**
     * Start the server by starting all the non-mandatory services.
     * @throws Exception
     */
    public void start() throws Exception {
        start(false);
    }
    /**
     * Start the server by starting all the non-mandatory services.
     * @param standby <code>true </code> to activate standby mode.
     * @throws Exception any.
     */
    public void start(final boolean standby) throws Exception {
        if (serverState.equals(J2EEServerState.STOPPED) || serverState.equals(J2EEServerState.FAILED)) {
            // Start operation is allowed
            // Check if there are services to start
            if (serviceManager.getOptionalServices().size() > 0 && !standby) {
                setStarting();
                serviceManager.startServices();
            } else if (!standby) {
                setRunning();
            }
        } 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");
        }
    }

    /**
     * Start recursive. Check if standby mode is enabled.
     * @param standby <code>true </code> to activate standby mode.
     * @throws Exception any.
     */
    public void startRecursive(final boolean standby) throws Exception {
        try {
            start(standby);
        } catch (IllegalStateException e) {
            // Start recursive 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(J2EEServerState.RUNNING) || serverState.equals(J2EEServerState.STARTING)
                || serverState.equals(J2EEServerState.FAILED)) {
            // Stop operation is allowed
            // Check if there are services to stop
            if (serviceManager.getOptionalServices().size() > 0) {
                setStopping();
                serviceManager.stopServices();
            } else {
                setStopped();
            }
        } else {
            // Stop 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 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) {
            e.printStackTrace();
        }
        if (Boolean.getBoolean(JONAS_STANDBY)) {
            setStopped();
        } else {
            // Check if there are services to start
            if (serviceManager.getOptionalServices().size() > 0) {
                setStarting();
            } else {
                setRunning();
            }
        }
    }

    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;
        }
    }

    /**
     * Log state information.
     */
    private void info(final String message) {
        logger.info("JOnAS AS v{0} named ''{1}'' {2}", getServerVersion(), getServerName(), message);
    }

    public void setStarting() {
        serverState = J2EEServerState.STARTING;
        Notification notif = new Notification(serverState.getName(), getObjectName(), sequenceNumber++);
        sendNotification(notif);
        logger.info(jvmInfos());
        info("STARTING");
    }

    /**
     * @return information on JVM currently used
     */
    public static String jvmInfos() {
        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 setRunning() {
        // The server is RUNNING, no need to check the service states
        serviceManager.disableServiceStatesCheck();

        serverState = J2EEServerState.RUNNING;
        Notification notif = new Notification(serverState.getName(), getObjectName(), sequenceNumber++);
        sendNotification(notif);
        info("RUNNING");

        // Register the embedded service only when the server is running
        registerEasyBeansEmbeddedService();
    }

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

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

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

    public J2EEServerState getState() {
        return serverState;
    }

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

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

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

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

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

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

    /**
     * Set the repository manager used by this service.
     * @param repositoryManager the new repository manager.
     */
    public void setRepositoryManager(final IRepositoryManager repositoryManager) {
        this.repositoryManager = repositoryManager;
    }

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

    /**
     * @param serviceManager reference to the serviceManager
     */
    public void setServiceManager(final ServiceManager serviceManager) {
        this.serviceManager = serviceManager;
    }

    /**
     * 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);

        boolean isDeployed = false;
        try {
            isDeployed = deployerManager.isDeployed(deployable);
        } catch (Exception e) {
            // We can suppose that if the deployer is not started (supported), the application is not deployed
        }

        // First check if the archive is already deployed
        if (isDeployed) {
            throw new RuntimeException("Archive '" + deployable + "'  is already deployed.");
        }
        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());
        }
    }

    /**
     * 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);

        try {
            // First check if the archive is already deployed
            if (!deployerManager.isDeployed(deployable)) {
                throw new RuntimeException("Archive '" + deployable + "'  is not deployed.");
            }

            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());
        }
    }

    /**
     * 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 = null;
        try {
            file = new File(fileName).getCanonicalFile();
        } catch (Exception e) {
            // Cannot canonicalize the file, use the name as is ...
            file = new File(fileName);
        }

        if (file.exists()) {
            try {
                return isDeployedFile(URLUtils.fileToURL(file));
            } 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 setServerProperties(final ServerProperties serverProperties) {
        this.serverProperties = serverProperties;
    }

    public void setRegistryService(final RegistryService registryService) {
        this.registryService = registryService;
    }

    public void setEasyBeansService(final IEasyBeansService easyBeansService) {
        this.easyBeansService = easyBeansService;
    }

    // --------------- JonasAdmin support -----------------------//
    /**
     * file extension containing a deployment plan
     */
    public static final String DEPL_EXTENSION = "xml";

    /**
     * The ServiceManager reference
     */
    private ServiceManager serviceManager;

    /**
     * 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);
        }
        // and xmls
        al1 = getInstalledXmls();
        for (URL url : al1) {
            al.add(url);
        }
        return al;
    }

    private Collection<String> getRepositoryDirectories() throws MalformedURLException {
        Set<String> result = new HashSet<String>();
        result.add(this.getUploadDirectory());

        Iterator<Repository> repositories = this.repositoryManager.iterator();
        while (repositories.hasNext()) {
            Repository repository = repositories.next();
            String repositoryURLAsString = repository.getUrl();
            URL repositoryURL = new URL(repositoryURLAsString);
            if ("file".equals(repositoryURL.getProtocol())) {
                File repositoryFile = URLUtils.urlToFile(repositoryURL).getAbsoluteFile();
                result.add(repositoryFile.getPath());
            }
        }

        return result;
    }

    /**
     * Get the xml files in the known repositories. They are supposed to contain deployment plans.
     * @return the urls of the xml files within the known repositories
     * @throws MalformedURLException If the path cannot be parsed as a URL
     * @throws IOException problem with the construction of the canonical pathname
     */
    private ArrayList<URL> getInstalledXmls() throws MalformedURLException, IOException {
        ArrayList<URL> al = new ArrayList<URL>();
        ArrayList<URL> al1 = null;
        for (String dir : this.getRepositoryDirectories()) {
            al1 = JModule.getInstalledFileInDir(dir, DEPL_EXTENSION);
            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 : this.getRepositoryDirectories()) {
            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 : this.getRepositoryDirectories()) {
            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 : this.getRepositoryDirectories()) {
            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 : this.getRepositoryDirectories()) {
            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 {
        try {
            File file = URLUtils.urlToFile(fileUrl);
            if (!file.isFile()) {
                return false;
            }
            final IDeployable deployable = getDeployable(file.getPath());
            if (deployable instanceof OSGiDeployable) {
                // JONAS-292: OSGi bundles don't have any MBeans,
                // therefore don't get registered in the deployedObjects list
                boolean isDeployed = deployerManager.isDeployed(deployable);
                return isDeployed;
            }
        } catch (Throwable ignored) {
            // Ignored
        }

        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 isDeployedPlan(final URL fileUrl) {
        // This mechanic may probably works for all deployables that have been managed by the deployer manager
        try {
            return deployerManager.isDeployed(getDeployable(URLUtils.urlToFile(fileUrl).getPath()));
        } catch (Exception e) {
            // Something goes wrong
            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;
        }
        if (isDeployedPlan(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");
                    if (url != null) {
                        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));
            }
        }

        // JONAS-292: OSGi bundles don't have any MBeans,
        // therefore don't get registered in the deployedObjects list
        for (URL fileUrl : getInstalledJars()) {
            try {
                File file = URLUtils.urlToFile(fileUrl);
                if (!file.isFile()) {
                    continue;
                }
                final IDeployable deployable = getDeployable(file.getPath());
                if (deployable instanceof OSGiDeployable) {
                    boolean isDeployed = deployerManager.isDeployed(deployable);
                    if (isDeployed) {
                        result.add(extractPath(fileUrl));
                    }
                }
            } catch (Throwable ignored) {
                // Ignored
            }
        }

        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, final boolean replaceExisting) throws Exception {

        File directoryUploadedFile = null;
        FileOutputStream fos = null;
        try {
            // We save all files in the upload directory
            String dir = this.getUploadDirectory();

            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);
                String versionNumber = versioningService.getVersionNumber(tempFile);
                tempFile.delete();
                if (versionID != null && versionNumber != null) {
                    int extensionStarts = fileName.length() - 4;
                    String extension = fileName.substring(extensionStarts);
                    fileName = fileName.substring(0, extensionStarts);
                    if (!fileName.endsWith(versionID)) {
                        if (fileName.endsWith("-" + versionNumber)) {
                            fileName = fileName.substring(0, fileName.length() - versionNumber.length() - 1);
                        }
                        fileName += versionID;
                    }
                    fileName += extension;
                }
            }

            // 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 upload directory first
            dir = this.getUploadDirectory();
            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 the directory in which J2EEServer uploads files
     * @return directory in which J2EEServer uploads files
     */
    public String getUploadDirectory() {
        return jonasBase + File.separator + "deploy";
    }

    /**
     * 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;
    }

    /**
     * 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;
    }

    /**
     * @return Get the protocols list from the registry service
     */
    private void createProtocols() {
        String protocols = "";
        String prefix = "rmi/";
        List<String> protocolList = registryService.getActiveProtocolNames();
        Iterator<String> it = protocolList.iterator();
        while (it.hasNext()) {
            String protocolName = it.next();
            if (protocols == "") {
                protocols = prefix + protocolName;
            } else {
                protocols += "," + prefix + protocolName;
            }
        }
        this.protocols = protocols;
    }

    /**
     * Return the all the services names, whatever their state is.
     * @return list of services names.
     */
    public List<String> getServices() {
        return serviceManager.getAllServices();
    }

    /**
     * Get a service description.
     * @param service the service name.
     * @return the service description
     */
    public String getServiceDescription(final String service) {
        return serviceManager.getServiceDescription(service);
    }

    /**
     * Get a service state.
     * @param service the service name.
     * @return the service state.
     */
    public String getServiceState(final String service) {
        return serviceManager.getServiceState(service);
    }

    /**
     * Start a service.
     * @param service the service name.
     * @throws Exception If the startup of the service fails
     */
    public void startService(final String service) throws Exception {
        serviceManager.startService(service);
    }

    /**
     * Stop a service.
     * @param service the service name.
     * @throws Exception If the stop of the service fails
     */
    public void stopService(final String service) throws Exception {
        serviceManager.stopService(service);
    }

    /**
     * Register the EasyBeans embedded as an OSGi service.
     */
    private void registerEasyBeansEmbeddedService() {
        // Register the Easybeans Embedded as an OSGi service
        if (easyBeansService != null) {
            easyBeansService.registerEmbeddedService();
        }
    }

    /**
     * Return the server startup time.
     * @return The server startup time
     */
    private String getServerStartupTime() {
        String startDate = System.getProperty(JONAS_START_DATE);
        System.clearProperty(JONAS_START_DATE);
        if (startDate != null) {
            long startupTime = System.currentTimeMillis() - Long.parseLong(startDate);
            if (startupTime > 0) {
                // Create a format
                SimpleDateFormat sdf = new SimpleDateFormat("m'm'ss's'");
                return sdf.format(startupTime);
            }
        }
        return null;
    }

    /**
     * Return a JVM thread stack dump.
     * @return A String containing the thread stack dump
     */
    public String getThreadStackDump() {

        StringBuilder builder = new StringBuilder();
        Map<Thread, StackTraceElement[]> st = Thread.getAllStackTraces();
        for (Map.Entry<Thread, StackTraceElement[]> e : st.entrySet()) {
            StackTraceElement[] el = e.getValue();
            Thread t = e.getKey();
            builder.append("\"").append(t.getName()).append("\"");
            builder.append(" id=").append(t.getId());
            builder.append(", ");
            builder.append(" prio=").append(t.getPriority());
            builder.append(", ");
            if (t.isAlive()) {
                builder.append("alive, ");
            }
            if (t.isInterrupted()) {
                builder.append("interrupted, ");
            }
            builder.append(t.getState() + ", ");
            if (t.isDaemon()) {
                builder.append("daemon, ");
            }
            builder.append("\n");
            for (StackTraceElement line : el) {
                builder.append("\t").append(line).append("\n");
            }
            builder.append("\n");
        }
        return builder.toString();
    }

    /**
     * Print a JVM thread stack dump in a given file.
     * @param filename the file name
     */
    public void printThreadStackDump(final String filename) {
        File f = null;
        String stackDump = null;
        PrintWriter printWriter = null;

        try {
            // Create file
            f = new File(filename);

            try {
                // Create a FileWriter stream to the file
                FileWriter fileWriter = new FileWriter(f);

                // Put a buffered wrapper around it
                BufferedWriter bufWriter = new BufferedWriter(fileWriter);

                // Wrap the PrintWriter stream around this output stream and turn on
                // the autoflush.
                printWriter = new PrintWriter(bufWriter, true);

            } catch (IOException ioe) {
                logger.warn("Could not prepare file \"" + filename + "\" for writing", ioe);
            }

            // Get thread stack dump
            try {
                stackDump = getThreadStackDump();
            } catch (Exception estack) {
                logger.warn("Error getting thread stack dump", estack);
            }
            // Write thread stack dump in output stream and check errors
            printWriter.write(stackDump);
            if (printWriter.checkError()) {
                logger.warn("Could not write in file \"" + filename + "\"");
            }
            printWriter.close();

        } catch (Exception efile) {
            logger.warn("Could not create file \"" + filename + "\"", efile);
        }
    }

    /**
     * @return package admin service.
     */
    protected PackageAdmin getPackageAdminService() {
        return packageAdminService;
    }


    /**
     * Log a JVM thread stack dump in JOnAS log file (level : info).
     */
    public void logThreadStackDump() {

        try {
            String stackDump = getThreadStackDump();
            logger.info(stackDump);
        } catch (Exception estack) {
            logger.warn("Error getting thread stack dump", estack);
        }
    }

    /**
     * Return a TabularData containing a JVM thread stack dump.
     * @return A TabularData containing the thread stack dump
     */
    public TabularData getThreadStackDumpList() {

        String[] itemNames = {"id", "name", "priority", "is alive", "is interrupted", "state", "is daemon", "stackTrace"};
        String[] itemValues = new String[itemNames.length];
        String[] itemDescriptions = new String[itemNames.length];
        OpenType[] itemTypes = new OpenType[itemNames.length];

        CompositeType threadPropertiesType = null;
        CompositeDataSupport[] compositeDataSupportTab = null;
        TabularType tabularType = null;
        TabularDataSupport tabularDataSupport = null;

        for (int i = 0; i < itemNames.length; i++) {
            itemDescriptions[i] = "Thread" + itemNames[i];
            itemTypes[i] = SimpleType.STRING;
        }

        Map<Thread, StackTraceElement[]> st = Thread.getAllStackTraces();
        compositeDataSupportTab = new CompositeDataSupport[st.size()];

        int indice = 0;
        String stackTrace = "";
        for (Map.Entry<Thread, StackTraceElement[]> e : st.entrySet()) {
            StackTraceElement[] el = e.getValue();
            Thread t = e.getKey();

            // Fill itemValues for threadPropertiesType
            itemValues[0] = Long.toString(t.getId());
            itemValues[1] = t.getName();
            itemValues[2] = Integer.toString(t.getPriority());
            if (t.isAlive()) {
                itemValues[3] = "true";
            } else {
                itemValues[3] = "false";
            }
            if (t.isInterrupted()) {
                itemValues[4] = "true";
            } else {
                itemValues[4] = "false";
            }
            itemValues[5] = t.getState().toString();
            if (t.isDaemon()) {
                itemValues[6] = "true";
            } else {
                itemValues[6] = "false";
            }

            for (StackTraceElement line : el) {
                stackTrace += (line + "\n");
            }
            itemValues[7] = stackTrace;
            stackTrace = "";

            // Build threadPropertiesType
            try {
                threadPropertiesType = new CompositeType("threadProperty", "a thread property", itemNames, itemDescriptions,
                        itemTypes);

                compositeDataSupportTab[indice] = new CompositeDataSupport(threadPropertiesType, itemNames, itemValues);

                indice++;

            } catch (OpenDataException ex) {
                logger.warn("Could not create CompositeDataSupport using CompositeType \"threadPropertiesType\"", ex);
                return null;
            }

        }

        try {
            tabularType = new TabularType("threadProperties", "List of Thread properties", threadPropertiesType, itemNames);

            tabularDataSupport = new TabularDataSupport(tabularType, itemNames.length, 0.75f);
            tabularDataSupport.putAll(compositeDataSupportTab);
            return tabularDataSupport;
        } catch (OpenDataException ex2) {
            logger.warn("Could not create TabularDataSupport using TabularType \"threadProperties\"", ex2);
            return null;
        }

    }
}
