/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-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 any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.ear.internal;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.management.ObjectName;

import org.ow2.jonas.deployment.ear.wrapper.EarManagerWrapper;
import org.ow2.jonas.ear.EarService;
import org.ow2.jonas.ejb2.EJBService;
import org.ow2.jonas.ejb3.IEasyBeansService;
import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.lib.bootstrap.LoaderManager;
import org.ow2.jonas.lib.service.AbsServiceImpl;
import org.ow2.jonas.lib.util.JonasObjectName;
import org.ow2.jonas.lib.work.DeployerLog;
import org.ow2.jonas.multitenant.MultitenantService;
import org.ow2.jonas.workcleaner.DeployerLogException;
import org.ow2.jonas.management.ServiceManager;
import org.ow2.jonas.resource.ResourceService;
import org.ow2.jonas.service.ServiceException;
import org.ow2.jonas.versioning.VersioningService;
import org.ow2.jonas.web.JWebContainerService;
import org.ow2.jonas.workcleaner.CleanTask;
import org.ow2.jonas.workcleaner.WorkCleanerService;
import org.ow2.jonas.ws.jaxrpc.IJAXRPCService;
import org.ow2.util.archive.api.ArchiveException;
import org.ow2.util.ee.deploy.api.deployable.EARDeployable;
import org.ow2.util.ee.deploy.api.deployer.IDeployerManager;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;
import org.ow2.util.url.URLUtils;

/**
 * JOnAS EAR Service Implementation class. This class provides an implementation
 * of the ear service.
 * @author Florent Benoit
 * @author Ludovic Bert
 * @author Francois Fornaciari
 *         Contributor(s):
 *              Adriana Danes: highlight configuration properties
 *              Eric Hardesty: added ability to include rar files in an ear
 *              Michel-Ange Anton : new JSR77 MBean
 *              S. Ali Tokmen (versioning)
 */
public class JOnASEARService extends AbsServiceImpl implements EarService, JOnASEARServiceMBean {

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

    /**
     * Reference to the JMX Service.
     */
    private JmxService jmxService = null;

    /**
     * EasyBeans service.
     */
    private IEasyBeansService easyBeansService = null;

    /**
     * External Classloader.
     */
    private ClassLoader extClassLoader;

    /**
     * Link to the Ear Deployer.
     */
    private EarDeployer earDeployer = null;

    /**
     * DeployerManager Service.
     */
    private IDeployerManager deployerManager;

    /**
     * Working directory for applications.
     */
    private File workAppsFile = null;

    /**
     * Enable call to GenClientStub. (disabled by default)
     */
    private boolean genStub = false;

    /**
     * Use a child classLoader for EJB3.
     */
    private boolean useEJB3ChildClassloader = false;


    /**
     * Initialize some Maps at instantiation time.
     */
    public JOnASEARService() {
        // Create the EAR deployer
        earDeployer = new EarDeployer();
    }

    /**
     * @param validate Use a validating parser ?
     */
    public void setParsingwithvalidation(final boolean validate) {
        EarManagerWrapper.setParsingWithValidation(validate);
        if (!validate) {
            logger.debug("EAR XML parsing without validation");
        } else {
            logger.debug("EAR XML parsing with validation");
        }
    }

    /**
     * @param isEnabled if generation of stubs need to be enabled ?
     */
    public void setGenstub(final boolean isEnabled) {
        this.genStub = isEnabled;
    }

    /**
     * Enable the use of a child classloader for EJB3s.
     * @param isEnabled if EJB3 neeeds a child class loader
     */
    public void useEJB3ChildClassloader(final boolean isEnabled) {
        this.useEJB3ChildClassloader = isEnabled;
    }


    /**
     * Stop the EAR service.
     * @throws ServiceException if the stop failed.
     */
    @Override
    protected void doStop() throws ServiceException {
        // Undeploy EARs
        earDeployer.stop();

        if (deployerManager != null) {
            // Unregister deployer
            deployerManager.unregister(earDeployer);
        }

        // Admin code
        if (jmxService != null) {
            unregisterEarServiceMBean(getDomainName());
        }

        // The service is stopped
        logger.info("EAR Service stopped");
    }

    /**
     * Start the EAR service.
     * @throws ServiceException if the startup failed.
     */
    @Override
    protected void doStart() throws ServiceException {
        initWorkingDirectory();

        // Reset embedded server linked to the ear deployer for the JOnAS/Traditional case.
        // In fact, this has been done previously during the service injection but with an EZB Service not started.
        if (easyBeansService != null) {
            earDeployer.setEmbedded(easyBeansService.getEasyBeansServer());
        }

        // get apps ClassLoader
        try {
            LoaderManager lm = LoaderManager.getInstance();
            extClassLoader = lm.getExternalLoader();
        } catch (Exception e) {
            logger.error("Cannot get the Applications ClassLoader from EAR Container Service: " + e);
            throw new ServiceException("Cannot get the Applications ClassLoader from EAR Container Service", e);
        }

        // TODO, When ear service will become a bundle, we could use
        // the meta-data located in META-INF/mbeans-descriptors.xml
        jmxService.loadDescriptors(getClass().getPackage().getName(), getClass().getClassLoader());

        // set properties
        earDeployer.setAppsClassLoader(extClassLoader);
        earDeployer.setJMXService(jmxService);
        earDeployer.setServerProperties(getServerProperties());
        earDeployer.setAppsWorkDirectory(getAppsWorkDirectory());
        earDeployer.setWebappsWorkDirectory(getWebappsWorkDirectory());

        if (genStub) {
            earDeployer.enableGenClientStub();
        }
        if (useEJB3ChildClassloader) {
            earDeployer.useEJB3ChildClassloader();
        }

        // Register it
        deployerManager.register(earDeployer);

        // Admin code
        registerEarServiceMBean(this, getDomainName());

        logger.info("EAR Service started");
    }

    /**
     * Create working directory for applications.
     */
    protected void initWorkingDirectory() {
        if (workAppsFile == null) {
            // Create $JONAS_BASE/work/apps directory file
            workAppsFile = new File(getAppsWorkDirectory() + File.separator + getServerProperties().getServerName());
            workAppsFile.mkdirs();
        }
    }

    /**
     * Method called when the workCleanerService is bound to the component.
     * @param workCleanerService the workCleanerService reference
     */
    protected void setWorkCleanerService(final WorkCleanerService workCleanerService) {
        initWorkingDirectory();

        File fileLog = new File(workAppsFile.getPath() + File.separator + getServerProperties().getServerName() + ".log");
        if (!fileLog.exists()) {
            try {
                // Create dirs
                fileLog.getParentFile().mkdirs();
                fileLog.createNewFile();
            } catch (IOException e) {
                logger.error("cannot create the log file" + fileLog, e);
            }
        }

        // Create the logger
        try {
            DeployerLog deployerLog = new DeployerLog(fileLog);
            earDeployer.setDeployerLog(deployerLog);

            CleanTask cleanTask = new EarCleanTask(this, deployerLog);

            workCleanerService.registerTask(cleanTask);
            workCleanerService.executeTasks();
        } catch (DeployerLogException e) {
            logger.error("Cannot register the clean task", e);
        }
    }

    /**
     * Return the list of all deployed applications.
     * @return The list of deployed applications
     */
    public List<String> getDeployedEars() {
        List<String> names = new ArrayList<String>();
        Map<URL, EARDeployable> ears = earDeployer.getEars();
        for (URL earURL : ears.keySet()) {
            names.add(earURL.getFile());
        }
        return names;
    }

    /**
     * @return current number of ears deployed in the JOnAS server
     */
    public Integer getDeployedEARsNumber() {
        return new Integer(earDeployer.getEars().size());
    }

    /**
     * Test if the specified filename is already deployed or not. This method
     * is a management method provided by the EarServerice MBean.
     * @param fileName the name of the ear file.
     * @return true if the ear is deployed, else false.
     */
    public boolean isEarLoaded(final String fileName) {

        URL url = null;
        boolean isLoaded = false;
        try {
            // Absolute filename
            try {
                url = new File(fileName).getCanonicalFile().toURL();
                // Check if the ear is already deployed or not
                if (earDeployer.getEars().get(url) != null) {
                    isLoaded = true;
                }
            } catch (Exception e) {
                url = null;
            }
        } catch (Exception e) {
            logger.error("Can not found if the ear is deployed or not");
            return false;
        }

        return isLoaded;
    }

    /**
     * Test if the specified unpack name is already deployed or not. This method is defined in the EarService interface.
     * @param unpackName the name of the ear file.
     * @return true if the ear is deployed, else false.
     */
    public boolean isEarDeployedByWorkName(final String unpackName) {
        Map<URL, EARDeployable> ears = earDeployer.getEars();
        for (EARDeployable earDeployable : ears.values()) {
            try {
                File unpackedFile= URLUtils.urlToFile(earDeployable.getArchive().getURL());
                if (unpackName.equals(unpackedFile.getName())) {
                    return true;
                }
            } catch (ArchiveException e) {
                logger.debug("Cannot retrieve the name of the unpacked ear {0}", unpackName);
            }

        }
        return false;
    }

    // Admin code implementation (JMX based)
    // -------------------------------------
    /**
     * Register EAR Service MBean.
     * @param service ear container service to manage
     * @param domainName domain name
     */
    private void registerEarServiceMBean(final Object service, final String domainName) {
        ObjectName on = JonasObjectName.earService(domainName);
        jmxService.registerMBean(service, on);
    }

    /**
     * Unregister EAR Container Service MBean.
     * @param domainName domain name
     */
    private void unregisterEarServiceMBean(final String domainName) {
        ObjectName on = JonasObjectName.earService(domainName);
        jmxService.unregisterMBean(on);
    }

    /**
     * @param jmxService the jmxService to set
     */
    public void setJmxService(final JmxService jmxService) {
        this.jmxService = jmxService;
    }

    /**
     * @param ejbService the ejbService to set
     */
    public void setEjbService(final EJBService ejbService) {
        earDeployer.setEjb21Service(ejbService);
    }

    /**
     * Unbind the {@link EJBService}.
     */
    public void unsetEjbService() {
        earDeployer.setEjb21Service(null);
    }

    /**
     * @param webContainerService the webContainerService to set
     */
    public void setWebContainerService(final JWebContainerService webContainerService) {
        earDeployer.setWebContainerService(webContainerService);
    }

    /**
     * Unbind the {@link JWebContainerService}.
     */
    public void unsetWebContainerService() {
        earDeployer.setWebContainerService(null);
    }

    /**
     * @param jaxrpcService the jaxrpcService to set
     */
    public void setJAXRPCService(final IJAXRPCService jaxrpcService) {
        earDeployer.setJAXRPCService(jaxrpcService);
    }

    /**
     * Unbind the {@link IJAXRPCService}.
     */
    public void unsetJAXRPCService() {
        earDeployer.setJAXRPCService(null);
    }

    /**
     * @param resourceService the resourceService to set
     */
    public void setResourceService(final ResourceService resourceService) {
        earDeployer.setResourceService(resourceService);
    }

    /**
     * Unbind the {@link ResourceService}.
     */
    public void unsetResourceService() {
        earDeployer.setResourceService(null);
    }

    /**
     * @param service the EJB3 Service to be injected.
     */
    public void setEasyBeansService(final IEasyBeansService service) {
        this.easyBeansService = service;
        earDeployer.setEasyBeansService(easyBeansService);
        earDeployer.setEmbedded(easyBeansService.getEasyBeansServer());
    }

    /**
     * Unbind the {@link IEasyBeansService}.
     */
    public void unsetEasyBeansService() {
        this.easyBeansService = null;
        earDeployer.setEasyBeansService(null);
        earDeployer.setEmbedded(null);
    }

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

    /**
     * Unbind the {@link IDeployerManager}.
     */
    public void unsetDeployerManager() {
        this.deployerManager = null;
    }

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

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

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

    /**
     * @param multitenantService  The multitenant service to set.
     */
    public void setMultitenantService(final MultitenantService multitenantService) {
        earDeployer.setMultitenantService(multitenantService);
    }

    /**
     * Sets the multitenant service to null.
     */
    public void unsetMultitenantService() {
        earDeployer.unsetMultitenantService();
    }

    /**
     * @return The multitenant service.
     */
    public MultitenantService getMultitenantService() {
        return earDeployer.getMultitenantService();
    }

    /**
     * Sets the service manager.
     * @param serviceManager the service manager
     */
    public void setServiceManager(final ServiceManager serviceManager) {
        earDeployer.setServiceManager(serviceManager);
    }

    /**
     * Sets the service manager to null.
     */
    public void unsetServiceManager() {
        earDeployer.unsetServiceManager();
    }

    /**
     * @return work directory for apps.
     */
    protected String getAppsWorkDirectory() {
        return getServerProperties().getWorkDirectory() + File.separator + "apps";
    }

    /**
     * @return work directory for webapps.
     */
    protected String getWebappsWorkDirectory() {
        return getServerProperties().getWorkDirectory() + File.separator + "webapps";
    }

}
