/**
 * 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: BaseWebContainerService.java 20725 2011-01-17 09:33:20Z alitokmen $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.web.base;

import static org.ow2.jonas.ws.jaxws.IJAXWSService.KEY_WEB_SERVICES_METADATAS;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.URL;
import java.net.URLClassLoader;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.Vector;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.naming.Context;
import javax.naming.LinkRef;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContextType;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.http.HttpService;
import org.ow2.easybeans.deployment.api.EZBInjectionHolder;
import org.ow2.easybeans.persistence.api.EZBPersistenceUnitManager;
import org.ow2.easybeans.resolver.api.EZBJNDIResolver;
import org.ow2.easybeans.resolver.api.EZBJNDIResolverException;
import org.ow2.jonas.Version;
import org.ow2.jonas.audit.AuditService;
import org.ow2.jonas.deployment.api.IEJBLocalRefDesc;
import org.ow2.jonas.deployment.api.IEJBRefDesc;
import org.ow2.jonas.deployment.api.IEnvEntryDesc;
import org.ow2.jonas.deployment.api.IMessageDestinationRefDesc;
import org.ow2.jonas.deployment.api.IResourceEnvRefDesc;
import org.ow2.jonas.deployment.api.IResourceRefDesc;
import org.ow2.jonas.deployment.api.IServiceRefDesc;
import org.ow2.jonas.deployment.common.DeploymentDescException;
import org.ow2.jonas.deployment.web.WebContainerDeploymentDesc;
import org.ow2.jonas.deployment.web.lib.WarDeployableMetadataFactoryHolder;
import org.ow2.jonas.deployment.web.lib.WebDeploymentDescManager;
import org.ow2.jonas.ejb3.IEasyBeansService;
import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.lib.bootstrap.JProp;
import org.ow2.jonas.lib.bootstrap.LoaderManager;
import org.ow2.jonas.lib.loader.FilteringClassLoader;
import org.ow2.jonas.lib.loader.SimpleWebappClassLoader;
import org.ow2.jonas.lib.loader.WebappClassLoader;
import org.ow2.jonas.lib.naming.ComponentContext;
import org.ow2.jonas.lib.security.PermissionManagerException;
import org.ow2.jonas.lib.service.AbsServiceImpl;
import org.ow2.jonas.lib.util.JonasObjectName;
import org.ow2.jonas.lib.util.Log;
import org.ow2.jonas.lib.work.DeployerLog;
import org.ow2.jonas.lib.work.DeployerLogException;
import org.ow2.jonas.naming.JComponentContextFactory;
import org.ow2.jonas.naming.JNamingManager;
import org.ow2.jonas.service.ServiceException;
import org.ow2.jonas.versioning.VersioningService;
import org.ow2.jonas.web.JWebContainerService;
import org.ow2.jonas.web.JWebContainerServiceException;
import org.ow2.jonas.web.base.lib.PermissionManager;
import org.ow2.jonas.web.base.osgi.httpservice.HttpServiceFactory;
import org.ow2.jonas.web.base.osgi.httpservice.JOnASHttpService;
import org.ow2.jonas.web.base.proxy.HttpOnDemandProxy;
import org.ow2.jonas.web.base.proxy.HttpOnDemandProxyException;
import org.ow2.jonas.workcleaner.CleanTask;
import org.ow2.jonas.workcleaner.WorkCleanerService;
import org.ow2.jonas.ws.jaxrpc.IJAXRPCService;
import org.ow2.jonas.ws.jaxws.IJAXWSService;
import org.ow2.util.archive.api.ArchiveException;
import org.ow2.util.archive.api.IArchive;
import org.ow2.util.archive.impl.ArchiveManager;
import org.ow2.util.ee.deploy.api.deployable.EARDeployable;
import org.ow2.util.ee.deploy.api.deployable.IDeployable;
import org.ow2.util.ee.deploy.api.deployable.WARDeployable;
import org.ow2.util.ee.deploy.api.deployer.DeployerException;
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.UnpackDeployableHelper;
import org.ow2.util.ee.metadata.common.api.enc.IENCBinding;
import org.ow2.util.ee.metadata.common.api.enc.IENCBindingHolder;
import org.ow2.util.ee.metadata.common.api.struct.IJEjbEJB;
import org.ow2.util.ee.metadata.common.api.struct.IJavaxPersistenceContext;
import org.ow2.util.ee.metadata.common.api.struct.IJavaxPersistenceUnit;
import org.ow2.util.ee.metadata.common.api.struct.IJaxwsWebServiceRef;
import org.ow2.util.ee.metadata.war.api.IWarDeployableMetadataFactory;
import org.ow2.util.file.FileUtils;
import org.ow2.util.file.FileUtilsException;
import org.ow2.util.url.URLUtils;


/**
 * This abstract class provides an implementation for a dynamic JWebContainerService service.
 * @author Florent Benoit
 * @author Ludovic Bert (J2EE 1.3)
 * @author Nicolas Van Caneghem (exploded ear)
 * @author Michel-Ange Anton (contributor)
 * @author S. Ali Tokmen (versioning)
 */
public abstract class BaseWebContainerService extends AbsServiceImpl implements JWebContainerService,
        BaseWebContainerServiceMBean {

    /**
     * The name of the JONAS_BASE directory.
     */
    protected static final String JONAS_BASE = JProp.getJonasBase();

    /**
     * The name of the property used in work directory for single webapps (not ear case).
     */
    protected static final String SINGLE_WORK_WEBAPPS_DIR_SUFFIX = "single";

    /**
     * The name of the property used in work directory for EAR webapps (in ear case).
     */
    protected static final String INEAR_WORK_WEBAPPS_DIR_SUFFIX = "ear";

    /**
     * Length of the extension .war.
     */
    private static final int WAR_EXTENSION_LENGTH = ".war".length();

    /**
     * Logger for this service.
     */
    private static Logger logger = Log.getLogger(Log.JONAS_WEB_PREFIX);

    /**
     * Reference on the NamingManager.
     */
    private JNamingManager naming;

    /**
     * War deployer.
     */
    private WARDeployer warDeployer = null;

    /**
     * Reference on the ComponentContextFactory.
     */
    private JComponentContextFactory contextFactory;

    /**
     * Associates an URL of an unpacked WAR file to its classloader.
     */
    private Hashtable warLoaders = new Hashtable();

    /**
     * Associates an URL of a deployed WAR file to its classloader.
     */
    private Hashtable warBindings = new Hashtable();

    /**
     * JMX Service.
     */
    protected JmxService jmxService = null;

    /**
     * List of the war deployed by the Web container Service.
     */
    private List<War> warDeployed = new Vector<War>();

    /**
     * Map of the unpacked WARs.
     */
    private Map<URL, URL> unpackedWARs = new HashMap<URL, URL>();

    /**
     * Name of the server (Tomcat, Jetty, ...).
     */
    private String serverName = null;

    /**
     * Version of the web container.
     */
    private String serverVersion = null;

    /**
     * Reference to the JAX-RPC service.
     */
    private IJAXRPCService jaxrpcService = null;

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

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

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

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

    /**
     * Reference to the {@link DeployerLog} of the Web Container service.
     */
    private DeployerLog deployerLog;

    /**
     * Reference to the JAXWS Service.
     */
    private IJAXWSService jaxwsService;

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

    /**
     * Audit service.
     */
    private AuditService auditService;

    /**
     * On demand feature enabled ?
     */
    private boolean onDemandFeatureEnabled = false;

    /**
     * Link to the OnDemandProxy.
     */
    private HttpOnDemandProxy onDemandProxy = null;

    /**
     * Given port used for redirecting the requests (OnDemand proxy).
     * This port will be used to launch the internal web container (tomcat, jetty, etc)
     */
    private int onDemandRedirectPort = 0;

    /**
     * Service BundleContext.
     */
    private BundleContext bundleContext;

    /**
     * {@link HttpService} factory registration key.
     */
    private ServiceRegistration osgiHttpService;

    /**
     * Default constructor.
     * @param bundleContext the OSGi bundle context
     */
    public BaseWebContainerService(final BundleContext bundleContext) {
        this.bundleContext = bundleContext;
        this.warDeployer = new WARDeployer();
        this.onDemandProxy = new HttpOnDemandProxy();
    }

    /**
     * @param validate must we parse web.xml files with validation ?
     */
    public void setParsingwithvalidation(final boolean validate) {
        WebDeploymentDescManager.setParsingWithValidation(validate);
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            if (!validate) {
                logger.log(BasicLevel.DEBUG, "Web XML parsing without validation");
            } else {
                logger.log(BasicLevel.DEBUG, "Web XML parsing with validation");
            }
        }
    }

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

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

        if (onDemandFeatureEnabled && !getServerProperties().isDevelopment()) {
            logger.log(BasicLevel.INFO, "The OnDemand feature is disabled in production mode");
            onDemandFeatureEnabled = false;
        }

        // Start the proxy if with mode onDemand ?
        if (onDemandFeatureEnabled) {

            // Register proxy on the deployer
            warDeployer.setOnDemandProxy(onDemandProxy);

            // Register ourself on the proxy
            onDemandProxy.setWebContainerService(this);
            onDemandProxy.setHttpPortNumber(Integer.parseInt(getDefaultHttpPort()));
            // If 0, needs to send a random port
            if (onDemandRedirectPort == 0) {
                this.onDemandRedirectPort = getRandomPort();
            }
            onDemandProxy.setRedirectPortNumber(onDemandRedirectPort);

            // Start the proxy port
            try {
                onDemandProxy.enable();
            } catch (HttpOnDemandProxyException e) {
                logger.log(BasicLevel.ERROR, "Cannot start the OnDemand proxy for web container: ", e);
                throw new ServiceException("Cannot start the OnDemand proxy for web container", e);
            }

            logger.log(BasicLevel.INFO, "OnDemand Feature enabled: Listening on '" + onDemandProxy.getHttpPortNumber() + "' port (internal:" + onDemandProxy.getRedirectPortNumber() + ").");

        }

        // Register the OSGi HttpService
        Properties httpServiceProperties = new Properties();
        httpServiceProperties.setProperty(Constants.SERVICE_PID, JOnASHttpService.class.getName());

        String httpPort = getDefaultHttpPort();
        httpServiceProperties.setProperty("org.osgi.service.http.port", httpPort);

        try {
            String httpsPort = getDefaultHttpsPort();
            httpServiceProperties.setProperty("org.osgi.service.http.port.secure", httpsPort);
        } catch (Exception e) {
            // no https port
        }

        HttpServiceFactory<?> httpServiceFactory = createHttpServiceFactory();

        // If there is a factory, register it as the OSGi http service
        if (httpServiceFactory != null) {
            httpServiceFactory.setWorkDirectory(getServerProperties().getWorkDirectory());
            osgiHttpService = bundleContext.registerService(HttpService.class.getName(), httpServiceFactory,
                    httpServiceProperties);
        }

        // Init the deployer
        warDeployer.setWebContainerService(this);

        // Register the deployer
        deployerManager.register(warDeployer);

        // Register the MBean
        registerWebServiceMBean(this, getDomainName());
    }

    /**
     * Creates an instance of the http service factory.
     * @return an instance of the httpservice factory
     */
    protected abstract HttpServiceFactory<?> createHttpServiceFactory();


    /**
     * Stop the service.
     * @throws ServiceException if the stop failed.
     */
    @Override
    protected void doStop() throws ServiceException {

        // Unregister the HttpService
        if (osgiHttpService != null) {
            osgiHttpService.unregister();
        }

        // Undeploy standalone WARs
        warDeployer.stop();

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

        if (onDemandProxy != null) {
            try {
                onDemandProxy.disable();
            } catch (HttpOnDemandProxyException e) {
                logger.log(BasicLevel.WARN, "Cannot stop the OnDemand proxy", e);
            }
        }

        unregisterWebServiceMBean(getDomainName());

        logger.log(BasicLevel.DEBUG, "WebContainerService stopped");
    }

    /**
     * Create the environment and delegate the operation to the implementation of the web container.
     * @param ctx the context which contains the configuration in order to deploy a WAR.
     * @throws JWebContainerServiceException if the registration of the WAR failed.
     */
    protected abstract void doRegisterWar(Context ctx) throws JWebContainerServiceException;

    /**
     * Delegate the unregistration to the implementation of the web container.
     * @param ctx the context which contains the configuration in order to undeploy a WAR.
     * @throws JWebContainerServiceException if the unregistration failed.
     */
    protected abstract void doUnRegisterWar(Context ctx) throws JWebContainerServiceException;


    /**
     * Starts the specific code for the web container implementation.
     * This allows to start the internal container on demand (if there is an access on the http proxy port for example)
     * @throws JWebContainerServiceException if container is not started
     */
    public abstract void startInternalWebContainer() throws JWebContainerServiceException;

    /**
     * Checks if the internal web container has been started.
     * @return true if it is already started
     */
    public abstract boolean isInternalContainerStarted();


    /**
     * Return the URL where warURL has been unpacked.
     * @param warURL the URL of the war
     * @param earDeployable the EAR deployable (could be null)
     * @return the URL where warURL has been unpacked.
     * @throws JWebContainerServiceException when it is impossible to retrieve the unpacked URL.
     */
    protected URL getUnpackedURL(final URL warURL, final EARDeployable earDeployable) throws JWebContainerServiceException {

        String folder = getWebappsWorkDirectory() + File.separator + getJonasServerName() + File.separator;

        // Two cases :
        // war is alone --> unpack it in :
        // WEBAPPS_DIR/servername/single/war_filename_timestamp/
        // war come from an ear --> unpack it in :
        // WEBAPPS_DIR/servername/ear/ear_filename_timestamp/war_filename__timestamp/

        File warFile = URLUtils.urlToFile(warURL);

        // Build destination folder name
        if (earDeployable != null) {
            // The destination directory is built from the filename of the unpacked ear,
            // that is to say, the parent folder of the original war URL.
            String earFileName = warFile.getParentFile().getName();
            if (!getServerProperties().isDevelopment()) {
                try {
                    earFileName = URLUtils.urlToFile(earDeployable.getOriginalDeployable().getArchive().getURL()).getName();
                } catch (Exception e) {
                    logger.log(BasicLevel.ERROR, "Cannot retrieve the original deployable");
                }
            }
            folder += INEAR_WORK_WEBAPPS_DIR_SUFFIX + File.separator + earFileName;
        } else {
            folder += SINGLE_WORK_WEBAPPS_DIR_SUFFIX + File.separator;
        }

        // Build destination archive name
        String archiveName = warFile.getName();
        if (getServerProperties().isDevelopment() && earDeployable == null) {
            try {
                archiveName = FileUtils.lastModifiedFileName(warFile);
            } catch (FileUtilsException e) {
                throw new JWebContainerServiceException("Failed creating the destination name", e);
            }
        }

        try {
            IArchive archive = ArchiveManager.getInstance().getArchive(warFile);
            WARDeployable warDeployable = WARDeployable.class.cast(DeployableHelper.getDeployable(archive));
            UnpackDeployableHelper.unpack(warDeployable, new File(folder), archiveName, false);
            return warDeployable.getUnpackedDeployable().getArchive().getURL();
        } catch (Exception e) {
            throw new JWebContainerServiceException("Failed unpacking the war file " + warFile, e);
        }
    }

    /**
     * Return the class loader of the given warURL. Unpack the associated war and build the loader if it's not in the cache.
     * @param warURL the url of the war we want to get the loader
     * @param earDeployable If the WAR is in an EAR application
     * @param parentLoader the ejb class loader of the ear. May be null in non ear case.
     * @return the class loader of the given warURL.
     * @throws JWebContainerServiceException if the process failed.
     */
    public WebappClassLoader getClassLoader(final URL warURL, final EARDeployable earDeployable, final ClassLoader parentLoader)
            throws JWebContainerServiceException {

        WebappClassLoader loaderForCls = null;
        try {
            WebLoaderHolder holder = (WebLoaderHolder) warLoaders.get(warURL);
            if (holder != null) {
                loaderForCls = holder.getJonasWebLoader();
            }
        } catch (Exception e) {
            throw new JWebContainerServiceException("Error when getting '" + warURL + "' in cache", e);
        }

        if (loaderForCls == null) {
            // the war is not already used

            // Use the unpacked directory
            URL unpackedWarURL = getUnpackedURL(warURL, earDeployable);
            try {
                if (parentLoader != null) {
                    // ear case.
                    loaderForCls = new WebappClassLoader(unpackedWarURL, parentLoader);
                } else {
                    // Case of non-ear application : only a single war
                    loaderForCls = new WebappClassLoader(unpackedWarURL, extClassLoader);
                }
            } catch (IOException ioe) {
                throw new JWebContainerServiceException("Cannot create WebAppClassLoader from '" + unpackedWarURL + "'", ioe);
            }

            // add the class loader in cache.
            try {
                WebLoaderHolder holder = new WebLoaderHolder(loaderForCls, null);
                warLoaders.put(warURL, holder);
            } catch (Exception e) {
                throw new JWebContainerServiceException("Error when adding '" + warURL + "' in cache", e);
            }
        }

        return loaderForCls;
    }

    /**
     * @param warURL the URL of the webapp
     * @return Returns the ClassLoader used to link a JNDI environnment to a webapp
     */
    public ClassLoader getContextLinkedClassLoader(final URL warURL) {
        WebLoaderHolder holder = (WebLoaderHolder) warLoaders.get(warURL);
        if (holder != null) {
            return holder.getEnvWebLoader();
        }
        return null;
    }

    /**
     * Create the environment and delegate the operation to the implementation of the web container.
     * @param ctx the context which contains the configuration in order to deploy a WAR.
     * @throws JWebContainerServiceException if the registration of the WAR failed.
     */
    protected void registerWar(final Context ctx) throws JWebContainerServiceException {
        // There are 6 possible parameters :
        // - warURL is the URL of the war to deploy (required param).
        // - parentClassLoader is the parent classloader of
        // the web classloader (optional param).
        // - earClassLoader is the ear classloader (optional param).
        // - earURL is the URL of the ear (optional parameter :
        // if earURL is set it means that we are in the ear case, else it
        // means that we are in the non ear case).
        // - earDeployable the EARDeployable of the ear application file.
        // (optional parameter, same comment as earURL).
        // - altDD is the optional deployment descriptor (optional param).
        // - contextRoot is the context root for the Web application
        // (optional param).

        // Get the URL of the ear application file in the case of an
        // ear application, null otherwise.
        URL earURL = null;
        EARDeployable earDeployable = null;
        String contextRoot = null;
        String earAppName = null;
        String userURI = null;

        // Injection holder
        EZBInjectionHolder ezbInjectionHolder = null;

        try {
            earURL = (URL) ctx.lookup("earURL");
            earDeployable = (EARDeployable) ctx.lookup("earDeployable");
            contextRoot = (String) ctx.lookup("contextRoot");
        } catch (NamingException e) {
            if (earURL != null || earDeployable != null || contextRoot != null) {
                String err = "Error while getting parameter from context param :" + e.getMessage();
                logger.log(BasicLevel.ERROR, err);
                throw new JWebContainerServiceException(err, e);
            } // else nothing to do : non-ear case.
        }

        // Get EAR application name
        if (earDeployable != null) {
            earAppName = earDeployable.getModuleName();
        }

        // Get the URL of the war to deploy ...
        URL warURL = null;
        try {
            warURL = (URL) ctx.lookup("warURL");
        } catch (NamingException e) {
            String err = "Error while getting parameter from context param :" + e.getMessage();
            logger.log(BasicLevel.ERROR, err);
            throw new JWebContainerServiceException(err, e);
        }

        // WARDeployable is required for standalone webapp with CDI
        WARDeployable deployable = null;
        try {
            deployable = (WARDeployable) ctx.lookup("deployable");
        } catch (NamingException e) {
            // Ignore Ear case (for now)
            deployable = null;
        }

        // Get injection holder
        try {
            ezbInjectionHolder = (EZBInjectionHolder) ctx.lookup(EZBInjectionHolder.class.getName());
        } catch (NamingException e) {
            logger.log(BasicLevel.DEBUG, "No injection holder");
            // Create a new one if not yet existing
            if (ejb3Service != null) {
                ezbInjectionHolder = ejb3Service.buildInjectionHolder(null, null);
            }
        }

        // ... and check if the war to deploy exists.
        File warFile = URLUtils.urlToFile(warURL);
        if (!warFile.exists()) {
            String err = "registerWar: '" + warFile.getPath() + "' not found";
            logger.log(BasicLevel.ERROR, err);
            throw new JWebContainerServiceException(err);
        }

        // Check if the war to deploy is not already deployed.
        War war = getWar(warURL);
        if (war != null) {
            // The war is already deployed.
            String err = "Cannot deploy war '" + warURL.getFile() + "' is already deployed."
                    + " You must undeploy the war before a new deployment.";
            throw new JWebContainerServiceException(err);
        }

        URLClassLoader parentLoader = null;
        URLClassLoader earClassLoader = null;
        boolean isInEar = true;
        try {
            parentLoader = (URLClassLoader) ctx.lookup("parentClassLoader");
            earClassLoader = (URLClassLoader) ctx.lookup("earClassLoader");
        } catch (NamingException ne) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Not an ear case");
            }
            isInEar = false;
            // exception occurs when the earClassLoader is not found.
        }

        // Check WsGen for webapp outside of an Ear
        if (!isInEar) {
            try {
                applyWSGenIfNeeded(warFile.getCanonicalPath());
            } catch (Exception e) {
                throw new JWebContainerServiceException("Cannot apply WsGen on the file : " + warFile, e);
            }
        }

        // Get the war class loader.
        WebappClassLoader loaderForCls = getClassLoader(warURL, earDeployable, parentLoader);

        // Get the URL of the unpacked WAR
        URL unpackedWarURL = loaderForCls.getBaseURL();

        // The file is unpacked, so log it
        if (deployerLog != null && !isInEar && getServerProperties().isDevelopment()) {
            try {
                deployerLog.addEntry(warFile, URLUtils.urlToFile(unpackedWarURL));
            } catch (Exception e) {
                String err = "Error while adding the " + warFile + " entry in the log file";
                logger.log(BasicLevel.ERROR, err + " : " + e.getMessage());
                throw new JWebContainerServiceException(err, e);
            }
        }

        // Only if the JAX-RPC Service is started
        // And in non EAR case
        if (jaxrpcService != null && earClassLoader == null) {
            try {
                ComponentContext contctx = null;
                try {
                    contctx = new ComponentContext(unpackedWarURL.getFile());
                    File unpackedWarFile = URLUtils.urlToFile(unpackedWarURL);
                    contctx.rebind(IJAXRPCService.UNPACK_DIRECTORY_CTX_PARAM, unpackedWarFile);
                    contctx.rebind("jarUrls", new URL[0]);
                    contctx.rebind("warUrls", new URL[] {warURL});
                    if (parentLoader != null) {
                        contctx.rebind("ejbClassLoader", parentLoader);
                    }
                } catch (NamingException e) {
                    String err = "Can not bind params for the WebServices service, " + "Can't deploy Web Services Endpoint";
                    throw new JWebContainerServiceException(err, e);
                }
                jaxrpcService.deployWebServices(contctx);
            } catch (ServiceException se) {
                String err = "Error during the deployment of the WebServices of the War file '" + warURL + "'";
                logger.log(BasicLevel.ERROR, err + " : " + se.getMessage());
                throw new JWebContainerServiceException(err, se);
            }
        }

        // Get the deployment descriptor from file
        WebContainerDeploymentDesc webDD = null;
        try {
            WebDeploymentDescManager manager = WebDeploymentDescManager.getInstance();
            webDD = manager.getDeploymentDesc(unpackedWarURL, warURL, loaderForCls, earClassLoader);
        } catch (DeploymentDescException e) {
            String err = "Cannot read the deployment descriptors '" + warURL.getFile() + "'";
            logger.log(BasicLevel.ERROR, err + ": " + e);
            e.printStackTrace(System.err);
            throw new JWebContainerServiceException(err, e);
        }

        // Populate the java:comp/env (ENC) environment.
        ClassLoader webClassLoader = null;
        FilteringClassLoader filteringClassLoader = null;
        try {
            filteringClassLoader = createFilteringClassLoader(deployable, parentLoader, unpackedWarURL);
            webClassLoader = new SimpleWebappClassLoader(unpackedWarURL, filteringClassLoader);

        } catch (Exception e) {
            throw new JWebContainerServiceException("Unable to create classloader", e);
        }

        // Persistence unit entry present ?
        IArchive archive = ArchiveManager.getInstance().getArchive(URLUtils.urlToFile(unpackedWarURL));

        List<IArchive> persistenceArchives = new ArrayList<IArchive>();
        List<URL> urlsToAddToClassLoader = new ArrayList<URL>();
        // Add WEB-INF/classes for the classloader if there are persistence classes
        URL urlWebInfClasses = webClassLoader.getResource("WEB-INF/classes/");
        if (urlWebInfClasses != null) {
            urlsToAddToClassLoader.add(urlWebInfClasses);
        }

        // Check entry in the war
        URL persistenceXmlEntryInWarFile = webClassLoader.getResource("WEB-INF/classes/META-INF/persistence.xml");
        if (persistenceXmlEntryInWarFile != null) {
            // There is an entry, add the archive to be analyzed
            persistenceArchives.add(archive);

        }

        // Check entries in each library
        try {
            Iterator<URL> itURLEntries = archive.getResources();
            while (itURLEntries.hasNext()) {
                URL urlWarEntry = itURLEntries.next();
                // There are jar files
                if (urlWarEntry.toExternalForm().contains("/WEB-INF/lib/") && urlWarEntry.toExternalForm().endsWith(".jar")) {
                    IArchive archiveLib = ArchiveManager.getInstance().getArchive(URLUtils.urlToFile(urlWarEntry));
                    URL persistenceXmlInLib = archiveLib.getResource("META-INF/persistence.xml");
                    if (persistenceXmlInLib != null) {
                        persistenceArchives.add(archiveLib);
                    }
                    urlsToAddToClassLoader.add(urlWarEntry);
                }
            }
        } catch (ArchiveException e) {
            throw new JWebContainerServiceException("Cannot check for persistence", e);
        }

        // there are archives to analyze
        if (persistenceArchives.size() > 0) {
            if (ejb3Service != null) {

                // Add URLs to the classLoader
                for (URL urlToAdd : urlsToAddToClassLoader) {
                    ((SimpleWebappClassLoader) webClassLoader).addURL(urlToAdd);
                }
                // Create EasyBeans ClassLoader
                webClassLoader = ejb3Service.buildByteCodeEnhancementClassLoader(new URL[0], webClassLoader);

                // Analyze each persistence archive
                for (IArchive persistenceArchive : persistenceArchives) {
                    EZBPersistenceUnitManager persistenceUnitManager = null;
                    final ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
                    try {
                        Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
                        persistenceUnitManager = ejb3Service.getPersistenceUnitManager(persistenceArchive, webClassLoader);
                    } catch (org.ow2.easybeans.persistence.api.PersistenceXmlFileAnalyzerException e) {
                        throw new JWebContainerServiceException("Unable to analyze persistence.xml file", e);
                    } finally {
                        Thread.currentThread().setContextClassLoader(oldCL);
                    }

                    EZBPersistenceUnitManager existingPersistenceUnitManager = ezbInjectionHolder.getPersistenceUnitManager();
                    if (existingPersistenceUnitManager == null) {
                        ezbInjectionHolder = ejb3Service.buildInjectionHolder(persistenceUnitManager, ezbInjectionHolder
                                .getJNDIResolver());
                    } else {
                        // add unit infos
                        existingPersistenceUnitManager.merge(persistenceUnitManager);
                    }
                }
            } else {
                logger.log(BasicLevel.WARN, "Persistence archives + '" + persistenceArchives + "' found but no EJB3 service");
            }
        }
        try {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Populating environment of the file " + warURL.getFile());
            }
            Context ctxParam = new ComponentContext(unpackedWarURL.getFile());
            ctxParam.rebind("DeploymentDesc", webDD);
            ctxParam.rebind("warName", unpackedWarURL.getFile());

            if (earDeployable != null) {
                ctxParam.rebind("earDeployable", earDeployable);
            }
            ctxParam.rebind("parentCL", webClassLoader);

            // Do the populating of the java:comp/env (ENC) environment.
            setWebEnvironment(ctxParam, ezbInjectionHolder, webClassLoader);
        } catch (Exception e) {
            // populating environment failed.
            String err = "Error when populating ";
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            throw new JWebContainerServiceException(err, e);
        }

        // TODO maybe to be removed ?
        WebLoaderHolder holder = (WebLoaderHolder) warLoaders.get(warURL);
        holder.setEnvWebLoader(webClassLoader);

        contextRoot = getContextRoot(contextRoot, earClassLoader, warURL, webDD);
        String versionID = null;
        if (isVersioningEnabled()) {
            if (earURL != null) {
                versionID = versioningService.getVersionID(earDeployable);
            } else {
                versionID = versioningService.getVersionID(warURL);
            }
            // TODO: prefix for deploying MBeans inside this WAR (filters, ...)
        }
        if (versionID == null) {
            userURI = contextRoot;
        } else {
            if (!contextRoot.contains(versionID)) {
                userURI = contextRoot;
                contextRoot += versionID;
            } else {
                userURI = contextRoot.replace(versionID, "");
            }

            if (userURI.equals("/") && contextRoot.charAt(0) == '/') {
                // ensure context-root is valid (remove starting /)
                contextRoot = contextRoot.substring(1);
            }
        }

        // Set the name of the host where to deploy the war if it is
        // specified in the jonas-web.xml.
        String hostName = webDD.getHost();

        // Check if the context to deploy is not already deployed.
        List<War> deployedWars = getWar(contextRoot);
        for (War deployedWar : deployedWars) {
            String hostDeployed = deployedWar.getHostName();
            if ((hostDeployed == null && hostName == null) || (hostDeployed != null && hostDeployed.equals(hostName))) {
                // The war is already deployed.
                String err = "Cannot deploy war '" + warURL.getFile() + "' is already deployed with the context '"
                        + contextRoot + "'." + " You must undeploy the war before a new deployment.";
                throw new JWebContainerServiceException(err);
            }
        }

        // Context classloader must follow the java2 delegation model ?
        boolean java2DelegationModel = webDD.getJava2DelegationModel();

        if (!java2DelegationModel) {
            logger.log(BasicLevel.INFO, "The application " + warURL.getFile() + " has its java2-delegation-model parameter set "
                + "to false. JOnAS 5 offers a new possibility called the filtering class loader that can replace this parameter "
                + "and doesn't suffer from the many unexpected behaviours caused by class loader inversion. Please see to JOnAS "
                + "configuration guide for details.");
        }

        // Create War
        war = new War(warURL, earURL, hostName, contextRoot, java2DelegationModel, webDD.getXmlContent(), webDD
                .getJOnASXmlContent(), webDD.getServletsName());

        // Configure JACC
        PermissionManager permissionManager = null;
        try {
            boolean removePContext = true;
            // Policy context is already existing (created by ear service), no need to remove it
            if (earClassLoader != null) {
                removePContext = false;
            }
            permissionManager = new PermissionManager(webDD, war.getContextId(), removePContext);
            permissionManager.translateServletDeploymentDescriptor();
            // if not in ear case, commit the policy configuration, else it is done
            // by EAR service after linking all policy configuration objects
            if (earClassLoader == null) {
                permissionManager.commit();
            }
        } catch (Exception e) {
            e.printStackTrace();
            String err = "Cannot build permission manager object for the webapp '" + unpackedWarURL + "'";
            logger.log(BasicLevel.ERROR, err + ": " + e.getMessage());
            throw new JWebContainerServiceException(err, e);
        }

        // Configure the context to give to doRegisterWar ...
        Context ctxParam = null;
        try {
            ctxParam = new ComponentContext(unpackedWarURL.getFile());

            // Place the War object that provides a set of values
            // It will be filled with additional parameters
            ctxParam.rebind("war", war);

            // Required parameters
            // ----------------------------------
            ctxParam.rebind("unpackedWarURL", unpackedWarURL);
            ctxParam.rebind("parentCL", webClassLoader);
            ctxParam.rebind(FilteringClassLoader.class.getName(), filteringClassLoader);
            ctxParam.rebind("webDD", webDD);
            ctxParam.rebind("userURI", userURI);
            // Transmit the @WebServices classes too
            ctxParam.rebind(KEY_WEB_SERVICES_METADATAS, webDD.getWebServices());

            // Optional parameters
            // ----------------------------------
            if (earAppName != null) {
                ctxParam.rebind("earAppName", earAppName);
            }
            if (deployable != null) {
                ctxParam.rebind(WARDeployable.class.getName(), deployable);
            }
            // Injection holder
            if (ezbInjectionHolder != null) {
                ctxParam.rebind(EZBInjectionHolder.class.getName(), ezbInjectionHolder);
            }

        } catch (NamingException e) {
            String err = "Error when deploying the war  '" + unpackedWarURL.getFile() + "'";
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            throw new JWebContainerServiceException(err, e);
        }

        war.setPermissionManager(permissionManager);

        // register classloaders (ejb + ear)
        if (parentLoader != null) {
            try {
                ctxParam.bind("ejbClassLoader", parentLoader);
            } catch (NamingException e) {
                throw new JWebContainerServiceException("Unable to bind a parameter", e);
            }
        }
        if (earClassLoader != null) {
            try {
                ctxParam.bind("earClassLoader", earClassLoader);
            } catch (NamingException e) {
                throw new JWebContainerServiceException("Unable to bind a parameter", e);
            }
        }


        // ... and delegate the registration of the war to the wrapper.
        doRegisterWar(ctxParam);

        // Finaly indicates that the war is deployed.
        warDeployed.add(war);
        warBindings.put(warURL, webClassLoader);
        unpackedWARs.put(warURL, unpackedWarURL);

        // Admin code
        registerWarMBean(war, getDomainName(), warURL.getFile());

        // Complete the Deployment of the WebServices
        // Only if the JAX-RPC Service is started
        // And in non EAR case
        if (jaxrpcService != null && earClassLoader == null) {
            try {
                ComponentContext contctx = null;
                try {
                    contctx = new ComponentContext(unpackedWarURL.getFile());
                    contctx.rebind(IJAXRPCService.CLASSLOADER_CTX_PARAM, loaderForCls);
                    contctx.rebind(IJAXRPCService.PARENT_OBJECTNAME_CTX_PARAM, ctxParam.lookup("WebModule"));
                    contctx.rebind(IJAXRPCService.ISINEAR_CTX_PARAM, Boolean.FALSE);
                } catch (NamingException e) {
                    String err = "Can not bind params for the WebServices service, "
                            + "can't complete deployment of Web Services Endpoints";
                    throw new JWebContainerServiceException(err, e);
                }
                jaxrpcService.completeWSDeployment(contctx);
            } catch (ServiceException se) {
                String err = "Error during the deployment of the WebServices of the War file '" + warURL + "'";
                logger.log(BasicLevel.ERROR, err + " : " + se.getMessage());
                throw new JWebContainerServiceException(err, se);
            }
        }

        StringBuffer txtInfo = new StringBuffer("War " + URLUtils.urlToFile(warURL).getName() + " available at the context ");
        if (!contextRoot.startsWith("/")) {
            txtInfo.append("/");
        }
        txtInfo.append(contextRoot);

        if (hostName != null) {
            txtInfo.append(" on the host ");
            txtInfo.append(hostName);
        }
        txtInfo.append(".");
        logger.log(BasicLevel.INFO, txtInfo.toString());

        // Remove the DD cache (WebServices)
        if (jaxrpcService != null && earClassLoader == null) {
            jaxrpcService.removeCache(loaderForCls);
        }
    }

    /**
     * Creates a {@code FilteringClassLoader} for this WebApp.
     * @param deployable the WebApp {@code Deployable} File (may be null in EAR case)
     * @param parentLoader the Ear parent's ClassLoader (may be null)
     * @param unpackedWarURL The Url of the unpacked webapp (only used in non-EAR case, never null)
     * @return a Filtering ClassLoader configured from {@code WEB-INF/classloader-filtering.xml} (if any).
     */
    private FilteringClassLoader createFilteringClassLoader(final WARDeployable deployable,
                                                            final URLClassLoader parentLoader,
                                                            final URL unpackedWarURL) {

        // Find the parent loader
        ClassLoader parent;
        if (parentLoader == null) {
            // Standalone case, simply be a child of the common classloader
            parent = extClassLoader;
        } else {
            // In Ear case, the filter sits between the EjbJars + Libraries loader
            // and the Web Application ClassLoader (to be created as child of this loader).
            parent = parentLoader;
        }

        // Create the child filtering loader from the parent
        FilteringClassLoader filteringClassLoader = new FilteringClassLoader(parent);

        // Find the web archive
        IArchive archive = null;
        if (deployable != null) {
            // Standalone mode
            // Just get the archive from the War deployable
            archive = deployable.getArchive();
        } else {
            // In EAR mode
            // Construct the archive from the Url
            archive = ArchiveManager.getInstance().getArchive(unpackedWarURL);
        }

        // Seek the XML definition file in the archive (if any)
        try {
            String name = "WEB-INF/" + FilteringClassLoader.CLASSLOADER_FILTERING_FILE;
            URL definition = archive.getResource(name);
            if (definition != null) {
                filteringClassLoader.setDefinitionUrl(definition);
            }
        } catch (ArchiveException ae) {
            // Ignored
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                String message = "Cannot get classloader-filtering.xml file from the archive " + archive;
                logger.log(BasicLevel.DEBUG, message, ae);
            }
        } finally {
            archive.close();
        }

        filteringClassLoader.start();
        return filteringClassLoader;
    }

    /**
     * Apply WSGen on the given file if needed.
     * @param path the path to use
     * @return the modified file or the original file if WSGen has not been launched.
     * @throws DeployerException if WSGen cannot be applied.
     */
    private void applyWSGenIfNeeded(final String path) throws DeployerException {
        // JAX-RPC service started ?
        if (jaxrpcService == null) {
            logger.log(BasicLevel.DEBUG, "The JAX-RPC service is not present, no need to call WSGen");
            return;
        }

        // Auto WsGen enabled ?
        if (!jaxrpcService.isAutoWsGenEngaged()) {
            logger.log(BasicLevel.DEBUG, "Automatic WsGen is not enabled, no need to call WSGen");
            return;
        }

        IArchive archive = ArchiveManager.getInstance().getArchive(new File(path));

        // Check version in manifest
        String jonasVersionWsGen = archive.getMetadata().get("WsGen-JOnAS-Version");
        if (Version.getNumber().equals(jonasVersionWsGen)) {
            // no changes, just continue the normal deployment process
            logger.log(BasicLevel.DEBUG, "No change: no need to call WSGen");
            return;
        }

        try {
            IDeployable<?> deployable = DeployableHelper.getDeployable(archive);
            jaxrpcService.applyWSGen(deployable);
        } catch (Exception e) {
            throw new DeployerException(e);
        }
    }

    /**
     * Gets the context root for a WAR.
     * @param contextRoot Current context root, null if none.
     * @param earClassLoader EAR classloader for the WAR, null if none.
     * @param warURL War file's URL.
     * @param webDD Deployment descriptor of the WAR file.
     * @return Context root, checked (always begin with one slash).
     */
    protected String getContextRoot(String contextRoot, final URLClassLoader earClassLoader, final URL warURL,
            final WebContainerDeploymentDesc webDD) {
        // Set the right context root for the web application, the priority is
        // the following :
        // 1 - context-root of application.xml
        // 2 - context-root of jonas-web.xml
        // 3 - context-root is the name of the WAR file without extension.
        if (earClassLoader == null && contextRoot == null) {
            String cRoot = webDD.getContextRoot();
            if (cRoot == null) {
                String file = new File(warURL.getFile()).getName();
                if (file.toLowerCase().endsWith(".war")) {
                    contextRoot = file.substring(0, file.length() - WAR_EXTENSION_LENGTH);
                } else {
                    // It's a directory which is deployed
                    contextRoot = file.substring(0, file.length());
                }
            } else {
                contextRoot = cRoot;
            }
        }

        // ensure context-root is valid (remove starting /)
        if (contextRoot.startsWith("/") && !contextRoot.equals("/")) {
            logger.log(BasicLevel.WARN, "Context-Root '" + contextRoot
                    + "' contains invalid starting / in the name. Fixing it.");
            int c = 0;
            while (contextRoot.charAt(c) == '/') {
                c++;
            }
            contextRoot = contextRoot.substring(c);
        }
        return contextRoot;
    }

    /**
     * Register a WAR by delegating the operation to the registerWar() method. This is used for JMX management.
     * @param fileName the name of the war to deploy.
     * @throws RemoteException if rmi call failed.
     * @throws JWebContainerServiceException if the registration failed.
     */
    public void registerWar(final String fileName) throws RemoteException, JWebContainerServiceException {
        // convert the file name to the appropriate url and check if the file
        // exists.
        URL warURL = checkWarFile(fileName);

        // create the context to call the registerWar method.
        Context ctx = null;
        try {
            ctx = new ComponentContext(fileName);
            ctx.rebind("warURL", warURL);
        } catch (NamingException e) {
            String err = "Error when deploying the war  '" + fileName + "'";
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            throw new JWebContainerServiceException(err, e);
        }

        // call the registerWar method.
        registerWar(ctx);
    }

    /**
     * Register a WAR by delegating the operation to the registerWar(Context) method.
     *
     * @param deployable the webapp to be deployed
     * @throws org.ow2.jonas.web.JWebContainerServiceException
     *          if the registration failed.
     */
    public void registerWar(final WARDeployable deployable) throws JWebContainerServiceException {
        // create the context to call the registerWar method.
        Context ctx = null;
        try {
            ctx = new ComponentContext(deployable.getModuleName());
            ctx.rebind("warURL", deployable.getArchive().getURL());
            ctx.rebind("deployable", deployable);
        } catch (Exception e) {
            String err = "Error when deploying the war  '" + deployable.getModuleName() + "'";
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            throw new JWebContainerServiceException(err, e);
        }

        // call the registerWar method.
        registerWar(ctx);
    }

    /**
     * Set the environment of the web container inside the given context.
     * @param ctxParam the java:comp/env/ environment where is stored the values of the web container environment.
     * @param ezbInjectionHolder the easybeans injection handler.
     * @param webAppClassLoader the classloader of the web application.
     * @throws JWebContainerServiceException if the populating of the environment failed.
     */
    protected void setWebEnvironment(final Context ctxParam, final EZBInjectionHolder ezbInjectionHolder,
            final ClassLoader webAppClassLoader) throws JWebContainerServiceException {

        WebContainerDeploymentDesc dd = null;
        String warName = null;
        ClassLoader parentClassLoader = null;
        EARDeployable earDeployable = null;

        /*
         * get the parameters - Deployment desc of xml - Name of the war - Parent Class loader
         */
        try {
            dd = (WebContainerDeploymentDesc) ctxParam.lookup("DeploymentDesc");
            warName = (String) ctxParam.lookup("warName");
            earDeployable = (EARDeployable) ctxParam.lookup("earDeployable");
        } catch (NamingException e) {
            // Ignore if earDeployable not found, it's optional
            if (earDeployable != null) {
                String err = "Error while getting parameter from context param ";
                logger.log(BasicLevel.ERROR, err + e.getMessage());
                throw new JWebContainerServiceException(err, e);
            }
        }

        // Get the parentCL
        try {
            parentClassLoader = (ClassLoader) ctxParam.lookup("parentCL");
        } catch (NamingException e) {
            String err = "Error while getting parameter from context param ";
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            throw new JWebContainerServiceException(err, e);
        }

        // Create the java:comp/env entry and bind the entries
        try {
            // Create a JNDI context for this war java:comp/env
            Context javaCtx = contextFactory.createComponentContext(warName);
            naming.setComponentContext(javaCtx, parentClassLoader);
            Context envCtx = javaCtx.createSubcontext("comp/env");

            // War Environment entries
            IEnvEntryDesc[] envt = dd.getEnvEntryDesc();
            for (int i = 0; i < envt.length; i++) {
                // get information in descriptor
                String name = envt[i].getName();
                Object obj = envt[i].getValue();

                // register object in JNDI
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, warName + ": Binding object " + name + " -> " + obj);
                }
                envCtx.rebind(name, obj);
            }

            // Resource References
            IResourceRefDesc[] resref = dd.getResourceRefDesc();
            for (int i = 0; i < resref.length; i++) {
                // get information in descriptor
                String name = resref[i].getName();
                String type = resref[i].getTypeName();
                String resname = resref[i].getJndiName();
                // register object in JNDI
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, warName + ": Linking resource " + name + " -> " + resname);
                }

                if (type.equalsIgnoreCase("java.net.URL")) {
                    // Specify the factory to use with the right URL
                    Reference ref = new Reference("java.net.URL", "org.ow2.jonas.lib.naming.URLFactory", null);
                    StringRefAddr refAddr = new StringRefAddr("url", resname);
                    ref.add(refAddr);
                    envCtx.rebind(name, ref);
                } else {
                    // build the LinkRef that will be registered:
                    // FactoryClassName = null, size = 1, refAddr = resname.
                    LinkRef lref = new LinkRef(resname);
                    envCtx.rebind(name, lref);
                }
            }

            // Resource Environment References
            IResourceEnvRefDesc[] resEnvref = dd.getResourceEnvRefDesc();
            for (int i = 0; i < resEnvref.length; i++) {
                // get information in descriptor
                String name = resEnvref[i].getName();
                String resname = resEnvref[i].getJndiName();
                LinkRef lref = new LinkRef(resname);

                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, warName + ": Linking resource environment " + name + " -> " + resname);
                }
                envCtx.rebind(name, lref);
            }

            // EJB References
            IEJBRefDesc[] ejbref = dd.getEjbRefDesc();
            for (int i = 0; i < ejbref.length; i++) {
                // get information in descriptor
                String name = ejbref[i].getEjbRefName();
                String ejbname = null;
                ejbname = ejbref[i].getJndiName();

                LinkRef lref = new LinkRef(ejbname);

                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, warName + ": Linking ejb " + name + " -> " + ejbname);
                }
                envCtx.rebind(name, lref);
            }

            // EJB Local Refs
            // We use here ejb-link tag. This should be used also for
            // ejb-ref when we are able to manage references to
            // another jar file.
            IEJBLocalRefDesc[] ejblocalref = dd.getEjbLocalRefDesc();
            for (int i = 0; i < ejblocalref.length; i++) {
                String name = ejblocalref[i].getEjbRefName();
                String ejbname = ejblocalref[i].getJndiLocalName();
                LinkRef lref = new LinkRef(ejbname);
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, warName + ": Linking ejb " + name + " -> " + ejbname);
                }
                envCtx.rebind(name, lref);
            }

            // Message Destination References
            IMessageDestinationRefDesc[] mdref = dd.getMessageDestinationRefDesc();
            for (int i = 0; i < mdref.length; i++) {
                // get information in descriptor
                String name = mdref[i].getMessageDestinationRefName();
                String mdname = null;
                mdname = mdref[i].getJndiName();

                LinkRef lref = new LinkRef(mdname);

                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, warName + ": Linking message-destination " + name + " -> " + mdname);
                }
                envCtx.rebind(name, lref);
            }

            // Service Ref
            // We bind a Reference when full configuration is provided
            // Otherwise we bind only a LinkRef in JNDI
            // Only done when the JAX-RPC Service is active
            if (jaxrpcService != null) {
                IServiceRefDesc[] serviceRefs = dd.getServiceRefDesc();
                for (int i = 0; i < serviceRefs.length; i++) {

                    // Create the Service from the ServiceRef description
                    String name = serviceRefs[i].getServiceRefName();
                    // create a full Reference
                    Reference ref = jaxrpcService.buildServiceRef(serviceRefs[i], parentClassLoader);
                    envCtx.rebind(name, ref);
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "Adding service-ref 'java:comp/env/" + name + "'");
                    }
                }
            }

            // Gets the bindings
            IENCBindingHolder encBindingHolder = dd.getENCBindingHolder();

            // Persistence context
            if (ezbInjectionHolder != null) {
                EZBPersistenceUnitManager persistenceUnitManager = ezbInjectionHolder.getPersistenceUnitManager();
                if (persistenceUnitManager != null) {
                    for (IENCBinding<IJavaxPersistenceContext> binding : encBindingHolder.getPersistenceContextBindings()) {
                        String encName = binding.getName();
                        String unitName = binding.getValue().getUnitName();
                        PersistenceContextType type = binding.getValue().getType();
                        EntityManager em = persistenceUnitManager.getEntityManager(unitName, type);

                        envCtx.rebind(encName, em);
                        if (logger.isLoggable(BasicLevel.DEBUG)) {
                            logger.log(BasicLevel.DEBUG, "Adding persistence-context 'java:comp/env/" + encName + "'");
                        }
                    }
                }

                // Persistence unit
                for (IENCBinding<IJavaxPersistenceUnit> binding : encBindingHolder.getPersistenceUnitBindings()) {
                    if (persistenceUnitManager != null) {
                        String encName = binding.getName();
                        String unitName = binding.getValue().getUnitName();
                        EntityManagerFactory emf = persistenceUnitManager.getEntityManagerFactory(unitName);

                        envCtx.rebind(encName, emf);
                        if (logger.isLoggable(BasicLevel.DEBUG)) {
                            logger.log(BasicLevel.DEBUG, "Adding persistence-unit 'java:comp/env/" + encName + "'");
                        }
                    }
                }

                // EJBs
                EZBJNDIResolver jndiResolver = ezbInjectionHolder.getJNDIResolver();
                if (jndiResolver != null) {
                    for (IENCBinding<IJEjbEJB> binding : encBindingHolder.getEJBBindings()) {
                        String encName = binding.getName();
                        IJEjbEJB jEjbEJB = binding.getValue();
                        String interfaceName = jEjbEJB.getBeanInterface();
                        String beanName = jEjbEJB.getBeanName();

                        String jndiName;
                        try {
                            jndiName = jndiResolver.getEJBJNDIUniqueName(interfaceName, beanName);

                            // Bind a link to this JNDI name
                            envCtx.rebind(encName, new LinkRef(jndiName));
                            if (logger.isLoggable(BasicLevel.DEBUG)) {
                                logger.log(BasicLevel.DEBUG, "Adding ejb 'java:comp/env/" + encName + "' from JNDIName '"
                                        + jndiName + "'.");
                            }

                        } catch (EZBJNDIResolverException e) {
                            logger.log(BasicLevel.ERROR, "Cannot get JNDI name for enc name '" + encName + "'", e);

                        }

                    }
                }
            }

            // If JAXWS Service is present
            if (jaxwsService != null) {
                for (IENCBinding<IJaxwsWebServiceRef> binding : encBindingHolder.getWebServicesBindings()) {

                    // Ask the service for Reference creation
                    String name = binding.getName();
                    Reference ref = jaxwsService.createNamingReference(binding.getValue());

                    // Bind the reference into the JNDI Context
                    envCtx.rebind(name, ref);
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "Adding WebServiceRef 'java:comp/env/" + name + "'.");
                    }
                }
            }

            // JNDI lookup prefix
            if (earDeployable != null && isVersioningEnabled()) {
                String jndiPrefix = versioningService.getPrefix(earDeployable);
                if (jndiPrefix != null) {
                    envCtx.rebind("JNDILookupPrefix", jndiPrefix);
                }
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Adding JNDI-lookup-prefix 'java:comp/env/JNDILookupPrefix'");
                }
            }
        } catch (NamingException e) {
            String err = "Error while populating environment of the war file " + warName;
            logger.log(BasicLevel.ERROR, err + "  :" + e.getMessage());
            throw new JWebContainerServiceException(err, e);
        }
    }

    /**
     * Delegate the un-registration to the implementation of the web container and
     * delete the environment associated to the WAR file.
     * @param ctx the context which contains the configuration in order to un-deploy a WAR.
     * @throws JWebContainerServiceException if the un-registration failed.
     */
    protected void unRegisterWar(final Context ctx) throws JWebContainerServiceException {

        // Gets the 2 parameters :
        // warURL, isEarCase, hostName and contextRoot.
        URL warURL = null;
        boolean isEarCase = true;
        try {
            warURL = (URL) ctx.lookup("warURL");
            isEarCase = ((Boolean) ctx.lookup("isEarCase")).booleanValue();
        } catch (NamingException e) {
            String err = "Error while getting parameter from context param.";
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            throw new JWebContainerServiceException(err, e);
        }

        // Load the WARDeployable instance (if any)
        WARDeployable deployable = null;
        try {
            deployable = (WARDeployable) ctx.lookup("deployable");
        } catch (NamingException e) {
            // As I'm targeting standalone web app for now, it's not an
            // issue if the Deployable is not present (ear case)
            deployable = null;
        }

        // get the file name for displaying message.
        String fileName = warURL.getFile();

        // Check if the war is deployed.
        War war = null;
        war = getWar(warURL);
        if (war == null) {
            String err = "Cannot undeploy war: '" + fileName + "' is not deployed.";
            logger.log(BasicLevel.ERROR, err);
            throw new JWebContainerServiceException(err);
        }

        // Check if the war can be undeployed (2 case authorized) :
        // - case EAR and war is in an EAR.
        // - case non EAR and war isn't in an EAR.
        if (isEarCase != war.isInEarCase()) {
            String err = "Cannot undeploy war: '" + fileName
                    + "' it is in an ear application. You must undeploy the ear associated.";
            logger.log(BasicLevel.ERROR, err);
            throw new JWebContainerServiceException(err);
        }

        // Call the specific method of the implementation of the web container.
        try {
            String hostName = war.getHostName();
            if (hostName != null) {
                ctx.rebind("hostName", war.getHostName());
            }
            ctx.rebind("contextRoot", war.getContextRoot());
            ctx.rebind("webClassLoader", warBindings.get(warURL));
        } catch (NamingException e) {
            String err = "Error when undeploying the war  '" + fileName + "'";
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            throw new JWebContainerServiceException(err, e);
        }

        // Remove permission manager
        PermissionManager permissionManager = war.getPermissionManager();
        try {
            permissionManager.delete();
            permissionManager = null;
        } catch (PermissionManagerException pme) {
            logger.log(BasicLevel.ERROR, "Cannot remove permission manager for file '" + fileName + "'.", pme);
        }

        doUnRegisterWar(ctx);

        // undeploy webservices
        if (jaxrpcService != null) {
            jaxrpcService.undeployWebServices(ctx);
        }

        // Remove the context.
        URLClassLoader loader = (URLClassLoader) warBindings.remove(warURL);
        naming.unSetComponentContext(loader);

        // Remove classloader
        warLoaders.remove(warURL);

        // Indicates that the war is not in the deployed war.
        // (here we are sure that the war is not null and isn't in an ear).
        warDeployed.remove(war);
        unpackedWARs.remove(warURL);

        // Admin code
        unregisterWarMBean(getDomainName(), fileName);

        logger.log(BasicLevel.INFO, "War " + URLUtils.urlToFile(warURL).getName() + " no longer available");
    }

    /**
     * Unregister a WAR by delegating the operation to the unRegisterWar() method. This is used for JMX management.
     * @param fileName the name of the war to undeploy.
     * @throws RemoteException if rmi call failed.
     * @throws JWebContainerServiceException if the unregistration failed.
     */
    public void unRegisterWar(final String fileName) throws RemoteException, JWebContainerServiceException {

        // Convert the given file name to an url and check if the war is
        // deployed ...
        URL warURL = checkWarDeployed(fileName);

        // ... and do the undeployment.
        Context ctx = null;
        try {
            ctx = new ComponentContext(fileName);
            ctx.rebind("warURL", warURL);
            ctx.rebind("isEarCase", new Boolean(false));
        } catch (NamingException e) {
            String err = "Error when undeploying the war file '" + fileName + "'";
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            throw new JWebContainerServiceException(err, e);
        }
        unRegisterWar(ctx);
    }

    /**
     * Un-register a WAR by delegating the operation to the unRegisterWar(Context)
     * method.
     * @param deployable the webapp to be un-deployed.
     * @throws JWebContainerServiceException if the un-registration failed.
     */
    public void unRegisterWar(final WARDeployable deployable) throws JWebContainerServiceException {

        // Prepare the Context ...
        Context ctx = null;
        try {
            ctx = new ComponentContext(deployable.getModuleName());
            ctx.rebind("warURL", deployable.getArchive().getURL());
            ctx.rebind("isEarCase", Boolean.FALSE);
            ctx.rebind("deployable", deployable);
        } catch (Exception e) {
            String err = "Error when undeploying the war file '" + deployable.getModuleName() + "'";
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            throw new JWebContainerServiceException(err, e);
        }

        // ... and perform un-deployment
        unRegisterWar(ctx);
    }

    /**
     * Deploy the given wars of an ear file with the specified parent classloader (ejb classloader or ear classloader). (This
     * method is only used for the ear applications, not for the web applications).
     * @param ctx the context containing the configuration to deploy the wars. <BR>
     *        This context contains the following parameters :<BR>
     *        - urls the list of the urls of the wars to deploy. <BR>
     *        - earURL the URL of the ear application file. <BR>
     *        - parentClassLoader the parent classLoader of the wars. <BR>
     *        - earClassLoader the ear classLoader of the j2ee app. <BR>
     *        - altDDs the optional URI of deployment descriptor. <BR>
     *        - contextRoots the optional context root of the wars. <BR>
     * @throws JWebContainerServiceException if an error occurs during the deployment.
     */
    public void deployWars(final Context ctx) throws JWebContainerServiceException {
        // Gets the parameters from the context :
        // - urls the list of the urls of the wars to deploy.
        // - earURL the URL of the ear application file.
        // - earDeployable the EARDeployable of the ear application file.
        // - parentClassLoader the parent classLoader of the wars.
        // - earClassLoader the ear classLoader of the j2ee app.
        // - altDDs the optional URI of deployment descriptor.
        // - contextRoots the optional context root of the wars.
        URL[] urls = null;
        URL earURL = null;
        EARDeployable earDeployable = null;
        ClassLoader parentClassLoader = null;
        ClassLoader earClassLoader = null;
        URL[] altDDs = null;
        String[] contextRoots = null;
        EZBInjectionHolder ejbInjectionHolder = null;
        try {
            urls = (URL[]) ctx.lookup("urls");
            earURL = (URL) ctx.lookup("earURL");
            earDeployable = (EARDeployable) ctx.lookup("earDeployable");
            parentClassLoader = (ClassLoader) ctx.lookup("parentClassLoader");
            earClassLoader = (ClassLoader) ctx.lookup("earClassLoader");
            altDDs = (URL[]) ctx.lookup("altDDs");
            contextRoots = (String[]) ctx.lookup("contextRoots");
        } catch (NamingException e) {
            String err = "Error while getting parameter from context param ";
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            throw new JWebContainerServiceException(err, e);
        }

        // Get Injection holder
        try {
            ejbInjectionHolder = (EZBInjectionHolder) ctx.lookup(EZBInjectionHolder.class.getName());
        } catch (NamingException e) {
            logger.log(BasicLevel.DEBUG, "No persistence unit manager");
        }

        // webDDManager.setAltDD(earClassLoader, urls, altDDs);

        // Deploy all the wars of the ear application.
        for (int i = 0; i < urls.length; i++) {
            // Get the name of a war to deploy.
            String fileName = URLUtils.urlToFile(urls[i]).getPath();
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Deploy war '" + fileName + "' for the ear service");
            }

            // The context to give for the creation of the container
            // associated to the ejb-jar.
            Context contctx = null;
            try {
                contctx = new ComponentContext(fileName);
                contctx.rebind("warURL", urls[i]);
                contctx.rebind("parentClassLoader", parentClassLoader);
                contctx.rebind("earClassLoader", earClassLoader);
                contctx.rebind("earDeployable", earDeployable);
                contctx.rebind("earURL", earURL);
                if (altDDs[i] != null) {
                    contctx.rebind("altDD", altDDs[i]);
                }
                if (contextRoots[i] != null) {
                    contctx.rebind("contextRoot", contextRoots[i]);
                }
                // Injection holder
                if (ejbInjectionHolder != null) {
                    contctx.rebind(EZBInjectionHolder.class.getName(), ejbInjectionHolder);
                }
                registerWar(contctx);
            } catch (Exception e) {
                // A war is corrupted so undeploy all the deployed war
                // of the ear application.
                logger.log(BasicLevel.ERROR, "Error when deploying '" + fileName + "'");
                logger.log(BasicLevel.ERROR, e.getMessage());
                logger.log(BasicLevel.ERROR, "Undeploy war of the ear application");

                for (int j = 0; j < i; j++) {
                    String warFileName = urls[j].getFile();
                    try {
                        // Try to undeploy a war of the ear application.
                        ComponentContext context = new ComponentContext(warFileName);
                        context.rebind("warURL", urls[j]);
                        context.rebind("isEarCase", new Boolean(true));
                        unRegisterWar(context);
                    } catch (Exception ex) {
                        // Cannot undeploy a war of the ear application
                        // So there is an error message.
                        logger.log(BasicLevel.ERROR, "Error when undeploying '" + warFileName + "'");
                        logger.log(BasicLevel.ERROR, ex.getMessage());
                        logger.log(BasicLevel.ERROR, "Cannot undeploy war of the ear application");
                    }

                }
                throw new JWebContainerServiceException("Error during the deployment", e);
            }
        }
    }

    /**
     * Undeploy the given wars of an ear file with the specified parent classloader (ejb classloader or ear classloader). (This
     * method is only used for the ear applications, not for the war applications).
     * @param urls the list of the urls of the wars to undeploy.
     */
    public void unDeployWars(final URL[] urls) {
        for (int i = 0; i < urls.length; i++) {
            String warFileName = urls[i].getFile();
            try {
                // Try to undeploy a war of the ear application.
                ComponentContext context = new ComponentContext(warFileName);
                context.rebind("warURL", urls[i]);
                context.rebind("isEarCase", new Boolean(true));
                unRegisterWar(context);
            } catch (Exception ex) {
                // Cannot undeploy a war of the ear application
                // So there is an error message.
                logger.log(BasicLevel.ERROR, "Error when undeploying '" + warFileName + "'");
                logger.log(BasicLevel.ERROR, ex.getMessage());
                logger.log(BasicLevel.ERROR, "Cannot undeploy war of the ear application");
            }
        }
    }

    /**
     * Get the war identified by its URL (.war).
     * @param url the URL of the war to get.
     * @return the war indentified by its URL, or null if the war is not found.
     */
    public War getWar(final URL url) {
        for (War war : warDeployed) {
            if (war.getWarURL().equals(url)) {
                return war;
            }
        }
        return null;
    }

    /**
     * Get a list of wars identified by their Context.
     * @param pContext the context of the war to get.
     * @return the list of wars indentified by their Context, or an empty list if no wars are found.
     */
    protected List<War> getWar(final String pContext) {
        List<War> checkDeployed = new ArrayList<War>();
        for (War war : warDeployed) {
            if (war.getContextRoot().equals(pContext)) {
                checkDeployed.add(war);
            }
        }
        return checkDeployed;
    }

    /**
     * Make a cleanup of the cache of deployment descriptor. This method must be invoked after the ear deployment by the EAR
     * service.
     * @param earClassLoader the ClassLoader of the ear application to remove from the cache.
     */
    public void removeCache(final ClassLoader earClassLoader) {
        WebDeploymentDescManager.getInstance().removeCache(earClassLoader);
    }

    /**
     * Check if the specified file name correspond to a file which is located relatively to where the JOnAS server is launched
     * or in the $JONAS_BASE/web-apps.
     * @param fileName the file to check if it exists.
     * @return the URL of the file found (either relatively to the JOnAS server or to the $JONAS_BASE/web-apps).
     * @throws JWebContainerServiceException if the specified file does't exists.
     */
    protected URL checkWarFile(final String fileName) throws JWebContainerServiceException {

        File f = null;
        try {
            f = new File(fileName).getCanonicalFile();
            if (!f.exists()) {
                boolean found = false;
            }
        } catch (IOException e) {
            String err = "Invalid war file name '" + fileName;
            logger.log(BasicLevel.ERROR, err);
            throw new JWebContainerServiceException(err, e);
        }

        URL warURL = null;
        try {
            warURL = f.toURL();
        } catch (MalformedURLException e) {
            String err = "Invalid war file name '" + fileName + "'.";
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            throw new JWebContainerServiceException(err, e);
        }
        return warURL;
    }

    /**
     * Check if the specified file is already deployed in the JOnAS server and return the URL of this deployed war file.
     * @param fileName the name of the WAR file to check.
     * @return the URL of the deployed war file.
     * @throws JWebContainerServiceException if the file name doesn't correspond to a deployed war.
     */
    protected URL checkWarDeployed(final String fileName) throws JWebContainerServiceException {

        URL url = null;
        try {
            for (War war : warDeployed) {
                url = (new File(fileName).getCanonicalFile()).toURL();
                URL deployedWarURL = war.getWarURL();
                String deployedWarPath = deployedWarURL.toString();
                // Delete the char '/' at the end of the URL if present
                // FIXME: very strange to use File.separator on an URL as it should be always / ! ??
                if (deployedWarPath.endsWith(File.separator)) {
                    deployedWarPath = deployedWarPath.substring(0, deployedWarPath.length() - 1);
                }

                // For unpacked directory on Linux, the URL has a trailing / so check it also
                if (deployedWarPath.equals(url.toString()) || (deployedWarPath + "/").equals(url.toString())) {
                    return deployedWarURL;
                }
            }

            String err = "Cannot undeploy war: '" + fileName + "' is not deployed.";
            logger.log(BasicLevel.ERROR, err);
            throw new JWebContainerServiceException(err);

        } catch (MalformedURLException e) {
            String err = "Invalid war file name '" + fileName + "'.";
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            throw new JWebContainerServiceException(err, e);
        } catch (IOException e) {
            String err = "Invalid war file name '" + fileName;
            logger.log(BasicLevel.ERROR, err);
            throw new JWebContainerServiceException(err, e);
        }
    }

    /**
     * @return current number of wars deployed in the JOnAS server
     */
    public Integer getCurrentNumberOfWars() {
        return new Integer(warDeployed.size());
    }

    /**
     * Test if the specified filename is already deployed or not.
     * @param fileName the name of the war file.
     * @return true if the war is deployed, else false.
     */
    public boolean isWarLoaded(final String fileName) {

        URL url = null;
        boolean isLoaded = false;
        try {
            // Absolute filename
            try {
                url = new File(fileName).getCanonicalFile().toURL();
                // Check if the war is already deployed or not
                if (getWar(url) != null) {
                    isLoaded = true;
                } else {
                    // Not found force to test in relative Webapps directory
                    url = null;
                }
            } catch (Exception e) {
                url = null;
            }
        } catch (Exception e) {
            String err = "Can not found if the war is deployed or not";
            logger.log(BasicLevel.ERROR, err);
            return false;
        }

        return isLoaded;
    }

    /**
     * Return the list of all loaded web applications.
     * @return The list of deployed web applications
     */
    public List<String> getDeployedWars() {
        List<String> al = new ArrayList<String>();
        for (War war : warDeployed) {
            URL warURL = war.getWarURL();
            al.add(warURL.getFile());
        }
        return al;
    }

    /**
     * Gets the name of the server which is the web container.
     * @return the name of the server which is the web container
     */
    public String getServerName() {
        if (serverName == null) {
            updateServerInfos();
        }
        return serverName;
    }

    /**
     * Gets the version of the server which is the web container.
     * @return the version of the server which is the web container
     */
    public String getServerVersion() {
        if (serverVersion == null) {
            updateServerInfos();
        }
        return serverVersion;
    }

    /**
     * Test if the specified unpack name is already deployed or not. This method is defined in the {@link JWebContainerService}
     * interface.
     * @param unpackName the name of the ear file.
     * @return true if the ear is deployed, else false.
     */
    public boolean isWarDeployedByWorkName(final String unpackName) {
        for (URL unpackedURL : unpackedWARs.values()) {
            if (URLUtils.urlToFile(unpackedURL).getName().equals(unpackName)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Update info of the serverName and serverVersion.
     */
    protected abstract void updateServerInfos();

    /**
     * Return the Default host name of the web container.
     * @return the Default host name of the web container.
     * @throws JWebContainerServiceException when it is impossible to get the Default Host.
     */
    public abstract String getDefaultHost() throws JWebContainerServiceException;

    /**
     * Return the Default HTTP port number of the web container (can be null if multiple HTTP connector has been set).
     * @return the Default HTTP port number of the web container.
     * @throws JWebContainerServiceException when it is impossible to get the Default Http port.
     */
    public abstract String getDefaultHttpPort() throws JWebContainerServiceException;

    /**
     * Return the Default HTTPS port number of the web container (can be null if multiple HTTPS connector has been set).
     * @return the Default HTTPS port number of the web container.
     * @throws JWebContainerServiceException when it is impossible to get the Default Https port.
     */
    public abstract String getDefaultHttpsPort() throws JWebContainerServiceException;

    /**
     * @return Returns the logger.
     */
    protected static Logger getLogger() {
        return logger;
    }

    /**
     * @return Returns the naming.
     */
    protected JNamingManager getNaming() {
        return naming;
    }

    /**
     * @param serverName The serverName to set.
     */
    protected void setServerName(final String serverName) {
        this.serverName = serverName;
    }

    /**
     * @param serverVersion The serverVersion to set.
     */
    protected void setServerVersion(final String serverVersion) {
        this.serverVersion = serverVersion;
    }

    /**
     * Holds the ClassLoader used to retrieve the WebApp JNDI Context and the JOnAS Webapp ClasLoader.
     * @author Guillaume Sauthier
     */
    public class WebLoaderHolder {

        /**
         * Web ClassLoader used by JOnAS.
         */
        private WebappClassLoader jonasWebLoader;

        /**
         * Web ClassLoader used to retrieve the JNDI Context of the webapp.
         */
        private ClassLoader envWebLoader;

        /**
         * Constructs a WebLoaderHolder with jonas Loader and en Loader.
         * @param jonas JOnAS Webapp Loader
         * @param env Environnement ClassLoader
         */
        public WebLoaderHolder(final WebappClassLoader jonas, final ClassLoader env) {
            jonasWebLoader = jonas;
            envWebLoader = env;
        }

        /**
         * @return Returns the jonasWebLoader.
         */
        public WebappClassLoader getJonasWebLoader() {
            return jonasWebLoader;
        }

        /**
         * @return Returns the envWebLoader.
         */
        public ClassLoader getEnvWebLoader() {
            return envWebLoader;
        }

        /**
         * @param envWebLoader The envWebLoader to set.
         */
        public void setEnvWebLoader(final ClassLoader envWebLoader) {
            this.envWebLoader = envWebLoader;
        }

        /**
         * @param jonasWebLoader The jonasWebLoader to set.
         */
        public void setJonasWebLoader(final WebappClassLoader jonasWebLoader) {
            this.jonasWebLoader = jonasWebLoader;
        }
    }

    /**
     * @return the {@link JComponentContextFactory}.
     */
    protected JComponentContextFactory getContextFactory() {
        return contextFactory;
    }

    // Admin code (JMX based)
    // ---------------------
    /**
     * Register WEB Container Service MBean.
     * @param service web container service to manage
     * @param domainName domain name
     */
    protected void registerWebServiceMBean(final Object service, final String domainName) {
        ObjectName on = JonasObjectName.webContainerService(domainName);
        jmxService.registerMBean(service, on);
    }

    /**
     * Unregister WEB Container Service MBean.
     * @param domainName domain name
     */
    protected void unregisterWebServiceMBean(final String domainName) {
        ObjectName on = JonasObjectName.webContainerService(domainName);
        jmxService.unregisterMBean(on);
    }

    /**
     * Register War MBean.
     * @param war War instance to manage
     * @param domainName domain name
     * @param fileName the war file name
     */
    protected void registerWarMBean(final War war, final String domainName, final String fileName) {
        // ObjectName on = null;
        try {
            ObjectName on = JonasObjectName.war(domainName, fileName);
            jmxService.registerMBean(war, on);
        } catch (MalformedObjectNameException e) {
            logger.log(BasicLevel.WARN, "Could not register War MBean", e);
        }
    }

    /**
     * Unegister War MBean.
     * @param domainName domain name
     * @param fileName the war file name
     */
    protected void unregisterWarMBean(final String domainName, final String fileName) {
        // ObjectName on = null;
        try {
            ObjectName on = JonasObjectName.war(domainName, fileName);
            jmxService.unregisterMBean(on);
        } catch (MalformedObjectNameException e) {
            logger.log(BasicLevel.WARN, "Could not unregister War MBean", e);
        }
    }

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

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

    /**
     * @param naming the naming to set
     */
    public void setNaming(final JNamingManager naming) {
        this.naming = naming;
    }

    /**
     * @param contextFactory the contextFactory to set
     */
    public void setContextFactory(final JComponentContextFactory contextFactory) {
        this.contextFactory = contextFactory;
    }

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

    /**
     * Create working directory for applications.
     */
    protected void initWorkingDirectory() {
        if (workSingleWebAppsFile == null) {
            // Create $JONAS_BASE/work/webapps/single directory file
            workSingleWebAppsFile = new File(getWebappsWorkDirectory() + File.separator + getServerProperties().getServerName()
                    + File.separator + SINGLE_WORK_WEBAPPS_DIR_SUFFIX);
            workSingleWebAppsFile.mkdirs();
        }
    }

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

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

        // Create the logger
        try {
            deployerLog = new DeployerLog(fileLog);
            CleanTask cleanTask = new WarCleanTask(this, deployerLog);

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

    /**
     * @return Associates an URL of an unpacked WAR file to its classloader.
     */
    protected Hashtable getWarLoaders() {
        return warLoaders;
    }

    /**
     * @return Associates an URL of a deployed WAR file to its classloader.
     */
    protected Hashtable getWarBindings() {
        return warBindings;
    }

    /**
     * @return List of the war deployed by the Web container Service.
     */
    protected List<War> getWarDeployed() {
        return warDeployed;
    }

    /**
     * @return Reference to the JAX-RPC service.
     */
    protected IJAXRPCService getJAXRPCService() {
        return jaxrpcService;
    }

    /**
     * @return Application Classloader.
     */
    protected ClassLoader getAppsClassLoader() {
        return extClassLoader;
    }

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

    /**
     * @return Whether versioning is enabled.
     */
    public boolean isVersioningEnabled() {
        return (versioningService != null && versioningService.isVersioningEnabled());
    }

    /**
     * @param auditService The audit service to set.
     */
    public void setAuditService(final AuditService auditService) {
        this.auditService = auditService;
    }

    /**
     * Sets the audit service to null.
     */
    public void unsetAuditService() {
        this.auditService = null;
    }

    /**
     * @return The audit service.
     */
    public AuditService getAuditService() {
        return this.auditService;
    }

    /**
     * @param warDeployableMetadataFactory the deployable metadata factory service
     */
    public void setWarDeployableMetadataFactory(final IWarDeployableMetadataFactory warDeployableMetadataFactory) {
        WarDeployableMetadataFactoryHolder.setWarDeployableMetadataFactory(warDeployableMetadataFactory);
    }

    /**
     * Store the reference to the JAX-WS Service.
     * @param jaxwsService jaxws Service
     */
    public void bindJaxwsService(final IJAXWSService jaxwsService) {
        this.jaxwsService = jaxwsService;
    }

    /**
     * @return the jaxws service
     */
    protected IJAXWSService getJAXWSService() {
        return this.jaxwsService;
    }

    /**
     * Release the reference to the JAWS Service.
     * @param jaxwsService jaxws Service
     */
    public void unbindJaxwsService(final IJAXWSService jaxwsService) {
        this.jaxwsService = null;
    }

    /**
     * @param service the EJB3 Service to be injected.
     */
    public void setEjb3Service(final IEasyBeansService service) {
        this.ejb3Service = service;
    }

    /**
     * Unbind the {@link IEasyBeansService}.
     */
    public void unsetEjb3Service() {
        this.ejb3Service = null;
    }

    /**
     * Enable or not the on demand feature.
     * @param onDemandFeatureEnabled boolean true/false
     */
    public void setOnDemandFeature(final boolean onDemandFeatureEnabled) {
        this.onDemandFeatureEnabled = onDemandFeatureEnabled;
    }


    /**
     * Sets the redirect port number.
     * @param onDemandRedirectPort the given port used for OnDemand proxy
     */
    public void setOnDemandRedirectPort(final int onDemandRedirectPort) {
        if (isStarted()) {
            return;
        }
        this.onDemandRedirectPort = onDemandRedirectPort;
    }


    /**
     * @return true if the on demand feature is enabled and in development mode.
     */
    public boolean isOnDemandFeatureEnabled() {
        return onDemandFeatureEnabled && getServerProperties().isDevelopment();
    }

    /**
     * @return the on demande Http port.
     */
    public int getOnDemandRedirectPort() {
        return onDemandRedirectPort;
    }

    /**
     * @return a random and available port for the ondemand.
     */
    public int getRandomPort() {

        Random random = new Random();
        random.setSeed(System.currentTimeMillis());

        int portNumber = 0;
        int retry = 0;

        while (portNumber == 0 && retry < 50) {

            // try to use by starting at 8950 branch
            int randomPort = 8950 + random.nextInt(50);

            ServerSocket serverSocket = null;
            try {
                serverSocket = new ServerSocket(randomPort);
                portNumber = randomPort;
            } catch (IOException e) {
                // invalid port number, try a new one
                logger.log(BasicLevel.DEBUG, "unable to listen on the server socket", e);
            } finally {
                if (serverSocket != null) {
                    try {
                        serverSocket.close();
                    } catch (IOException e) {
                        logger.log(BasicLevel.DEBUG, "unable to close server socket", e);
                    }
                }
            }

            // new try
            retry++;
        }

        if (retry == 50) {
            throw new IllegalStateException("Unable to choose a random free port");
        }

        return portNumber;

    }

    /**
     * @return the bundle context of this service.
     */
    protected BundleContext getBundleContext() {
        return bundleContext;
    }

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

}
