/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2008 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.client;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

import javax.naming.Context;
import javax.naming.LinkRef;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.persistence.EntityManagerFactory;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginContext;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.carol.util.configuration.CarolDefaultValues;
import org.ow2.carol.util.configuration.ConfigurationRepository;
import org.ow2.cmi.ha.interceptor.iiop.HAInterceptorInitializer;
import org.ow2.easybeans.injection.api.ArchiveInjectionException;
import org.ow2.easybeans.injection.impl.ArchiveInjection;
import org.ow2.easybeans.loader.EasyBeansClassLoader;
import org.ow2.easybeans.persistence.PersistenceUnitManager;
import org.ow2.easybeans.persistence.api.PersistenceXmlFileAnalyzerException;
import org.ow2.easybeans.persistence.xml.JPersistenceUnitInfo;
import org.ow2.easybeans.persistence.xml.PersistenceXmlFileAnalyzer;
import org.ow2.easybeans.server.Embedded;
import org.ow2.jonas.Version;
import org.ow2.jonas.client.naming.ClientInitialContextFactory;
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.client.ClientContainerDeploymentDesc;
import org.ow2.jonas.deployment.client.ClientContainerDeploymentDescException;
import org.ow2.jonas.deployment.client.lib.ClientDeploymentDescManager;
import org.ow2.jonas.deployment.common.PersistenceUnitRefDesc;
import org.ow2.jonas.deployment.ear.EarDeploymentDesc;
import org.ow2.jonas.deployment.ear.EarDeploymentDescException;
import org.ow2.jonas.deployment.ear.lib.EarDeploymentDescManager;
import org.ow2.jonas.deployment.ear.xml.Web;
import org.ow2.jonas.deployment.ejb.lib.EjbDeploymentDescManager;
import org.ow2.jonas.deployment.web.lib.WebDeploymentDescManager;
import org.ow2.jonas.generators.genbase.generator.Config;
import org.ow2.jonas.generators.genclientstub.ClientStubGen;
import org.ow2.jonas.generators.wsgen.WsGen;
import org.ow2.jonas.lib.cpmanager.EarClassPathManager;
import org.ow2.jonas.lib.cpmanager.EarClassPathManagerException;
import org.ow2.jonas.lib.cpmanager.JarList;
import org.ow2.jonas.lib.cpmanager.JarListException;
import org.ow2.jonas.lib.naming.SingletonComponentContextFactory;
import org.ow2.jonas.lib.naming.SingletonNamingManager;
import org.ow2.jonas.lib.security.mapping.JPolicyUserRoleMapping;
import org.ow2.jonas.lib.util.Log;
import org.ow2.jonas.naming.JComponentContextFactory;
import org.ow2.jonas.naming.JNamingManager;
import org.ow2.jonas.security.auth.callback.NoInputCallbackHandler;
import org.ow2.jonas.security.interceptors.iiop.SecurityInitializer;
import org.ow2.jonas.tm.jotm.ots.OTSORBInitializer;
import org.ow2.jonas.ws.jaxrpc.base.ClientJServiceFactoryFinder;
import org.ow2.jonas.ws.jaxrpc.factory.JServiceFactory;
import org.ow2.jonas.ws.jaxws.client.JAXWSWebServiceRefBuilder;
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.builder.webserviceref.WebServiceRefBuilder;
import org.ow2.util.ee.deploy.api.deployable.CARDeployable;
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.LibDeployable;
import org.ow2.util.ee.deploy.impl.helper.DeployableHelper;
import org.ow2.util.ee.deploy.api.helper.DeployableHelperException;
import org.ow2.util.ee.deploy.impl.helper.UnpackDeployableHelper;
import org.ow2.util.ee.metadata.car.api.ICarDeployableMetadata;
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.IJaxwsWebServiceRef;
import org.ow2.util.url.URLUtils;

/**
 * Defines the class use for the client container This class analyze the ear or
 * the jar client and launch the client.
 * @author Florent Benoit
 */
public class ClientContainer {

    /**
     * Name of the carol file.
     */
    private static final String CAROL_FILE = "carol.properties";

    /**
     * Main class to use to launch the application client.
     */
    private String mainClass = null;

    /**
     * Temporary directory.
     */
    private String tmpDir = null;

    /**
     * Jar client to use (if many).
     */
    private String jarClient = null;

    /**
     * Classpath for the application client.
     */
    private String classpath = null;

    /**
     * trace.properties file to use instead of the default file.
     */
    private String clientTraceFile = null;

    /**
     * Reference to the client jar to use
     */
    private File clientJarFile = null;

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

    /**
     * carol.properties file to use instead of the default file.
     */
    private String carolFile = null;

    /**
     * Arguments used by the client.
     */
    private String[] args = null;

    /**
     * Class loader of the EAR.
     */
    private URLClassLoader earClassLoader = null;

    /**
     * Extra Arguments.
     */
    private List<String> appArgs = null;

    /**
     * URLs resolved in the case of the extension mechanism in the Ear case.
     */
    private URL[] extensionsURLs = null;

    /**
     * Logger to use.
     */
    private Logger logger = null;

    /**
     * true if the user want to auto generate web services artifacts.
     */
    private boolean applyWsGen = true;

    /**
     * true if the user want to auto generate some stubs on client side.
     * Default is false.
     */
    private boolean applyGenClientStub = false;

    /**
     * Allows persistence management in client applications.
     */
    private PersistenceUnitManager persistenceUnitManager = null;

    private static final String IIOP_INTERCEPTOR = "org.omg.PortableInterceptor.ORBInitializerClass.";

    /**
     * Constructor for a Client container.
     * @param args the arguments of the instance of the client container
     */
    private ClientContainer(final String[] args) {
        this.args = args;

        appArgs = new ArrayList<String>();
    }

    /**
     * Call the Log class to instanciate the client container logger.
     */
    private void initLogger() {
        // Allow tracing ejb/jms code
        Log.configure(clientTraceFile);
        // init the logger
        this.logger = Log.getLogger("org.ow2.jonas.client");
    }

    /**
     * Main method of the Client container.
     * @param args the arguments of the client container
     */
    public static void main(final String[] args) {
        // Retrieve command line parameters
        ClientContainer cc = new ClientContainer(args);

        try {
            cc.start();
        } catch (InvocationTargetException ite) {
            Throwable t = ite.getTargetException();
            String message = t.getMessage();
            if (t instanceof Error) {
                System.err.println("There was the following error : " + message);
            } else if (t instanceof Exception) {
                System.err.println("There was the following exception : " + message);
            }
            t.printStackTrace(System.err);
        } catch (Exception e) {
            System.err.println("There was the following exception : " + e.getMessage());
            e.printStackTrace();
            System.exit(-1);
        }
    }

    /**
     * Start the client container.
     * @throws Exception if it fails
     */
    private void start() throws Exception {
        analyzeArgs();

        // Use the specified traceclient.properties
        if (clientTraceFile != null) {
            File tClient = new File(clientTraceFile);

            if (!tClient.exists()) {
                throw new ClientContainerException("The file '" + clientTraceFile + "' was not found.");
            }

            if (!tClient.isFile()) {
                throw new ClientContainerException("The file '" + clientTraceFile
                        + "' is not a valid file. Maybe a directory ?");
            }

            // Configure log
            System.setProperty("jonas.client.trace.file", clientTraceFile);
            Log.reset();
        } else {
            clientTraceFile = "traceclient";
        }

        initLogger();

        // Get the filename
        String userArg = null;
        String fileName = null;
        boolean fileMode = true;

        try {
            userArg = appArgs.get(0);
        } catch (IndexOutOfBoundsException ioobe) {
            usage();
            throw new ClientContainerException(
                    "You haven't specify a jar, an ear file or class name as argument. See the Usage.");
        }

        String className = null;
        // Test if this is an ear or a jar file else it must be a class name
        if (!(userArg.toLowerCase().endsWith(".jar") || userArg.toLowerCase().endsWith(".ear"))) {
            className = userArg;
            fileMode = false;
        } else {
            fileMode = true;
            fileName = userArg;
        }


        // Gets the deployable for the given archive
        IDeployable<?> deployable = null;
        if (fileMode) {
            File argFile = new File(fileName);

            if (!argFile.exists()) {
                throw new ClientContainerException("The file '" + argFile + "' does not exist !");
            }

            // build archive
            IArchive archive = ArchiveManager.getInstance().getArchive(argFile);

            try {
                deployable = DeployableHelper.getDeployable(archive);
            } catch (DeployableHelperException e) {
                throw new Exception("Cannot get a deployable for the archive '"
                        + archive + "'", e);
            }

            // Unpack and analyze EAR file if it is an ear
            if (EARDeployable.class.isAssignableFrom(deployable.getClass())) {
                // Unpack of EAR
                deployable = UnpackDeployableHelper.unpack(EARDeployable.class.cast(deployable), "client-deployer");
            }
        }

        // Working directory
        File workDir = new File(System.getProperty("java.io.tmpdir"));


        // WsGen
        if (applyWsGen && fileMode) {
            try {
                File argFile = new File(fileName);

                if (!argFile.exists()) {
                    throw new ClientContainerException("The specified file '"
                            + fileName + "' doesn't exists.");
                }
                Manifest manifest = new JarFile(argFile).getManifest();

                if (manifest == null) {
                    throw new ClientContainerException(
                            "No manifest was found inside the file" + argFile);
                }

                // Extract attributes
                Attributes attributes = manifest.getMainAttributes();

                if (attributes == null) {
                    throw new ClientContainerException(
                            "No attributes were found in the manifest of the file '"
                                    + argFile + "'.");
                }
                String archiveVersion = attributes.getValue("WsGen-JOnAS-Version");
                if (!Version.getNumber().equals(archiveVersion)) {
                    this.logger.log(BasicLevel.DEBUG, "Archive was not compiled with the latest WsGen. Regenerating ...");
                    // different versions, re-generate Stuff
                    WsGen wsGen = new WsGen();
                    Config config = new Config();
                    String deployablePath = URLUtils.urlToFile(deployable.getArchive().getURL()).getPath();
                    config.setInputname(deployablePath);
                    config.setOut(workDir);

                    String path = wsGen.execute(config, deployable);
                    // Check if a new archive has been created
                    if (!path.equals(deployablePath)) {
                        IArchive archive = ArchiveManager.getInstance().getArchive(new File(path));
                        // Build a new deployable with the new archive
                        EARDeployable newDeployable = EARDeployable.class.cast(DeployableHelper.getDeployable(archive));
                        deployable = UnpackDeployableHelper.unpack(newDeployable, workDir, newDeployable.getShortName(), false);
                    }
                }
            } catch (Exception e) {
                throw new ClientContainerException(
                        "Cannot apply WsGen on the client archive : " + userArg, e);
            }
        }

        // GenClient stub
        if (applyGenClientStub && fileMode) {
            try {
                File argFile = new File(fileName);

                if (!argFile.exists()) {
                    throw new ClientContainerException("The specified file '"
                            + fileName + "' doesn't exists.");
                }
                    // different versions, re-generate Stuff
                    ClientStubGen stubGen = new ClientStubGen();
                    Config config = new Config();
                    String deployablePath = URLUtils.urlToFile(deployable.getArchive().getURL()).getPath();
                    config.setInputname(deployablePath);
                    config.setOut(workDir);

                    String path = stubGen.execute(config, deployable);
                    // Check if a new archive has been created
                    if (!path.equals(deployablePath)) {
                        IArchive archive = ArchiveManager.getInstance().getArchive(new File(path));
                        // Build a new deployable with the new archive
                        IDeployable newDeployable = DeployableHelper.getDeployable(archive);
                        deployable = UnpackDeployableHelper.unpack(newDeployable, workDir, newDeployable.getShortName(), false);
                    }
            } catch (Exception e) {
                logger.log(BasicLevel.WARN, "Cannot apply GenClientStub on the client archive : " + userArg, e);
            }
        }

        clientJarFile = null;
        if (deployable != null) {
            if (EARDeployable.class.isAssignableFrom(deployable.getClass())) {
                clientJarFile = extractAndAnalyzeEar(EARDeployable.class.cast(deployable));
            } else {
                // Client jar is the given file
                clientJarFile = URLUtils.urlToFile(deployable.getArchive().getURL());
            }
        } else if (fileName != null) {
            clientJarFile = new File(fileName);
        }

        // SOAP/SAAJ initialization (property)
        System.setProperty("javax.xml.soap.SOAPConnectionFactory", "com.sun.xml.messaging.saaj.client.p2p.HttpSOAPConnectionFactory");
        System.setProperty("javax.xml.soap.SOAPFactory", "com.sun.xml.messaging.saaj.soap.ver1_1.SOAPFactory1_1Impl");
        System.setProperty("javax.xml.soap.MetaFactory", "com.sun.xml.messaging.saaj.soap.SAAJMetaFactoryImpl");
        System.setProperty("javax.xml.soap.MessageFactory", "com.sun.xml.messaging.saaj.soap.ver1_1.SOAPMessageFactory1_1Impl");

        // Carol initialization (property)
        System.setProperty("javax.rmi.CORBA.PortableRemoteObjectClass",
                           "org.ow2.carol.rmi.multi.MultiPRODelegate");
        System.setProperty("org.omg.CORBA.ORBClass", "org.jacorb.orb.ORB");
        System.setProperty("org.omg.CORBA.ORBSingletonClass", "org.jacorb.orb.ORBSingleton");
        System.setProperty("org.omg.PortableInterceptor.ORBInitializerClass.standard_init",
                           "org.jacorb.orb.standardInterceptors.IORInterceptorInitializer");
        System.setProperty("javax.rmi.CORBA.UtilClass", "org.ow2.carol.util.delegate.UtilDelegateImpl");

        // IIOP Interceptors must be declared via System Properties
        System.setProperty(IIOP_INTERCEPTOR + OTSORBInitializer.class.getName(), "");
        System.setProperty(IIOP_INTERCEPTOR + SecurityInitializer.class.getName(), "");
        System.setProperty(IIOP_INTERCEPTOR + HAInterceptorInitializer.class.getName(), "");

        // Build a classloader for the initialisation of carol
        // Priority
        //    1/ file as argument
        //    2/ file in the jar client
        //    3/ file in client.jar
        URL urlCarolFile = null;

        if (carolFile != null) {
            File fCarol = new File(carolFile);

            if (!fCarol.exists()) {
                throw new ClientContainerException("The file '" + carolFile + "' was not found.");
            }

            if (!fCarol.isFile()) {
                throw new ClientContainerException("The file '" + carolFile
                        + "' is not a valid file. Maybe a directory ?");
            }

            if (!fCarol.getName().equals(CAROL_FILE)) {
                throw new ClientContainerException("The file '" + carolFile + "' must be named '" + CAROL_FILE + "'.");
            }

            try {
                urlCarolFile = fCarol.toURL();
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Using carol.properties file specified by the user on command line");
                }
            } catch (MalformedURLException mue) {
                throw new ClientContainerException("Error when building an URL for the file '" + fCarol + "'.", mue);
            }
        }

        // carol.properties file in client jar, use it if this is the case ?
        if (urlCarolFile == null && fileMode) {
            URL tmpUrl = null;
            try {
                tmpUrl = clientJarFile.toURL();
            } catch (MalformedURLException mue) {
                throw new ClientContainerException("Error when building an URL for the file '" + clientJarFile + "'.", mue);
            }
            // Build classloader with null parent CL, to see if it is in the file.
            ClassLoader tmpCl = new URLClassLoader(new URL[] {tmpUrl}, null);

            urlCarolFile = tmpCl.getResource(CarolDefaultValues.CAROL_CONFIGURATION_FILE);
            if (urlCarolFile != null) {
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Using carol.properties file of the '" + clientJarFile + "' file.");
                }
            }
        }

        if (urlCarolFile != null) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Init carol with URL '" + urlCarolFile + "'.");
            }
            ConfigurationRepository.init(urlCarolFile);
        } else {
            ConfigurationRepository.init();
        }
        // Add Csiv2 Interceptors
        ConfigurationRepository.addInterceptors("iiop", "org.ow2.jonas.security.iiop.Csiv2Initializer");

        // Must be done AFTER carol init because carol set this property internally.
        System.setProperty(Context.INITIAL_CONTEXT_FACTORY, ClientInitialContextFactory.class.getName());

        // Carol configuration is done
        // Extract Main-Class to use in the jar from the manifest
        if (fileMode) {
            Manifest manifest = new JarFile(clientJarFile).getManifest();

            if (manifest == null) {
                throw new ClientContainerException("No manifest was found inside the file" + clientJarFile);
            }

            // Extract attributes
            Attributes attributes = manifest.getMainAttributes();

            if (attributes == null) {
                throw new ClientContainerException("No attributes were found in the manifest of the file '"
                        + clientJarFile + "'.");
            }
            mainClass = attributes.getValue(Attributes.Name.MAIN_CLASS);
        } else {
            mainClass = className;
        }


        // Invoke the client if there is no need of XML parsing
        if (!fileMode) {
            // Use TCCL as parent ClassLoader
            ClassLoader tccl = Thread.currentThread().getContextClassLoader();
            ClassLoader clientCL = new URLClassLoader(getUserClasspathUrls(), tccl);
            Thread.currentThread().setContextClassLoader(clientCL);
            // No metadatas to use -> null
            invokeClient(null);
            return;
        }

        if (mainClass == null || mainClass.length() == 0) {
            throw new ClientContainerException("No main class was found inside the Manifest  of the file '"
                    + clientJarFile + "'. This attribute is required to launch the application client.");
        }

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Using Main-Class :" + mainClass);
        }

        // Convert file to URL
        URL clientJarURL = URLUtils.fileToURL(clientJarFile);

        // Build the urls for the classloader
        URL[] urlsClient = null;

        // URLs for the classloader
        if (extensionsURLs != null) {
            // There were URLs with the extension mechanism in the EAR
            urlsClient = new URL[extensionsURLs.length + 1];

            for (int i = 0; i < extensionsURLs.length; i++) {
                urlsClient[i] = extensionsURLs[i];

                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Adding " + extensionsURLs[i] + " to the urls of the client");
                }
            }

            urlsClient[extensionsURLs.length] = clientJarURL;
        } else {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Only one url for urls of client");
            }

            // No extension or jar case.
            urlsClient = new URL[1];
            urlsClient[0] = clientJarURL;
        }

        // Build classloader
        URLClassLoader clientClassloader = new URLClassLoader(urlsClient, Thread.currentThread()
                .getContextClassLoader());
        Thread.currentThread().setContextClassLoader(clientClassloader);

        // Get the deployment descriptor from file
        ClientContainerDeploymentDesc clientDD = null;
        if (extensionsURLs != null) {
            EjbDeploymentDescManager.getInstance().addClassLoaderUrlMapping(clientClassloader, extensionsURLs);
        }

        try {
            clientDD = ClientDeploymentDescManager.getInstance()
                                                  .getDeploymentDesc(clientJarURL,
                                                                     clientClassloader,
                                                                     earClassLoader);
        } catch (ClientContainerDeploymentDescException e) {
            String err = "Cannot read the deployment descriptors '" + clientJarURL + "'";
            error(err);
            throw new ClientContainerException(err, e);
        }

        // Populate the java:comp/env (ENC) environment.
        try {
            setClientEnvironment(clientDD);
        } catch (Exception e) {
            //populating environment failed.
            String err = "Error when populating ";
            error(err);
            throw new ClientContainerException(err, e);
        }

        // JAAS
        String jaasFile = clientDD.getJaasFile();
        String jaasEntry = clientDD.getJaasEntry();
        String username = clientDD.getUsername();
        String password = clientDD.getPassword();

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Using jaas file = " + jaasFile);
        }

        String jaasConfigFile = null;

        if (jaasFile != null) {
            // Use JAAS
            jaasConfigFile = "jar:" + clientJarURL.toExternalForm() + "!/" + jaasFile;
            System.setProperty("java.security.auth.login.config", jaasConfigFile);
        }

        CallbackHandler ch = null;

        if ((username != null) && (password != null)) {
            ch = new NoInputCallbackHandler(username, password);
            info("Using the login/password specified in the jonas-client.xml file with a specific CallbackHandler");
        } else {
            // Is there a callbackHandler specified ?
            String ddCallbackHandler = clientDD.getCallbackHandler();

            if (ddCallbackHandler != null) {
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Using '" + ddCallbackHandler + "' class as CallbackHandler.");
                }

                Class<?> clazz = null;

                //try to invoke this class
                try {
                    clazz = clientClassloader.loadClass(ddCallbackHandler);
                } catch (Exception e) {
                    throw new ClientContainerException("There was an error while trying to instantiate the class '"
                            + ddCallbackHandler
                            + "' which is specified in the application.xml as CallbackHandler class", e);
                }

                try {
                    ch = (CallbackHandler) clazz.newInstance();
                } catch (Exception e) {
                    throw new ClientContainerException(
                            "Error while triyng to cast the class '" + ddCallbackHandler
                            + "' to CallbackHandler interface, maybe the specified class doesn't implement this interface.",
                            e);
                }
            }
        }

        // Use JAAS
        if (ch != null) {
            if (jaasFile == null) {
                // check if there is a system property
                String sysJaasConfig = System.getProperty("java.security.auth.login.config");
                if (sysJaasConfig == null) {
                    throw new ClientContainerException("You have defined that you want use a CallbackHandler but "
                                                   + "you haven't specify the jaas file to use for the JAAS configuration.");
                } else {
                    info("Using system defined JAAS config file" + sysJaasConfig);
                }
            }

            if (jaasEntry == null) {
                throw new ClientContainerException("You have defined that you want use a CallbackHandler but "
                                                   + "you haven't specify the jaas entry to use from the JAAS config file.");
            }

            info("Using JAAS loginContext '" + jaasEntry + "' from the file '" + jaasConfigFile + "'.");

            try {
                LoginContext lc = new LoginContext(jaasEntry, ch);
                lc.login();
            } catch (Exception e) {
                String err = "Can not use the JAAS authentication";
                error(err);
                throw new ClientContainerException(err, e);
            }
        }

        // Start client
        invokeClient(clientDD.getCarDeployableMetadata());

    }

    /**
     * Start the client on its main class with the thread class loader.
     * @throws ClassNotFoundException if class is not found
     * @throws NoSuchMethodException if method (main) is not found
     * @throws IllegalAccessException if access is illegal
     * @throws InvocationTargetException if invocation failed
     */
    private void invokeClient(final ICarDeployableMetadata carDeployableMetadata) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
            InvocationTargetException {
        ClassLoader clientClassloader = Thread.currentThread().getContextClassLoader();

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            if (clientClassloader instanceof URLClassLoader) {
                URLClassLoader urlClassLoader = (URLClassLoader) clientClassloader;
                URL[] urls = urlClassLoader.getURLs();
                logger.log(BasicLevel.DEBUG, "URLs of the classloader :");
                for (int u = 0; u < urls.length; u++) {
                    logger.log(BasicLevel.DEBUG, "URL[" + u + "] = " + urls[u]);
                }
            }
        }

        // Invoke client
        // Launch the "class_to_run" by using our classloader.
        Class<?> clazz = clientClassloader.loadClass(mainClass);

        // File mode, needs to analyze and perform injection in the client
        if (clientJarFile != null) {
            ArchiveInjection archiveInjection = new ArchiveInjection(carDeployableMetadata);
            try {
                archiveInjection.init(clazz);
            } catch (ArchiveInjectionException e) {
                logger.log(BasicLevel.ERROR, "Cannot make class initialization.", e);
            }

        }

        Class<?>[] argList = new Class[] {args.getClass()};
        Method meth = clazz.getMethod("main", argList);

        // Remove name of the file from arguments
        String[] newArgs = new String[appArgs.size() - 1];
        String txtArgs = "";

        for (int i = 0; i < newArgs.length; i++) {
            newArgs[i] = appArgs.get(i + 1);
            txtArgs += (newArgs[i] + " ");
        }

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Starting the application client with the arguments '" + txtArgs + "'.");
        }

        info("Starting client...");

        meth.invoke(null, new Object[] {newArgs});

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "End of main method");
        }

    }

    /**
     * Set the environment of this client.
     * @param clientDD deployment descriptor used for build the environment
     * @throws NamingException if the creation of the environment fails
     * @throws ClientContainerException if there was an error during NamingManager retrieving.
     */
    private void setClientEnvironment(final ClientContainerDeploymentDesc clientDD)
            throws NamingException, ClientContainerException {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "");
        }

        //Init the naming manager
        JComponentContextFactory ccf = null;
        try {
            naming = SingletonNamingManager.getInstance();
            ccf = SingletonComponentContextFactory.getInstance();
            naming.setJComponentContextFactory(ccf);
        } catch (NamingException e) {
            throw new ClientContainerException("Error when getting the reference to the Naming manager", e);
        }

        Context javaCtx = ccf.createComponentContext("ClientContainer");
        naming.setClientContainerComponentContext(javaCtx);

        Context envCtx = javaCtx.createSubcontext("comp/env");

        // Bean Environment
        IEnvEntryDesc[] envt = clientDD.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, "Binding object " + name + " -> " + obj);
            }

            envCtx.rebind(name, obj);
        }

        // Resource References
        IResourceRefDesc[] resref = clientDD.getResourceRefDesc();

        for (int i = 0; i < resref.length; i++) {
            // get information in descriptor
            String name = resref[i].getName();
            String resname = resref[i].getJndiName();
            String type = resref[i].getTypeName();

            // build the LinkRef that will be registered:
            // FactoryClassName = null, size = 1, refAddr = resname.
            // register object in JNDI
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "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 {
                LinkRef lref = new LinkRef(resname);
                envCtx.rebind(name, lref);
            }
        }

        // Resource Environment References
        IResourceEnvRefDesc[] resEnvref = clientDD.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, "Linking resource environment " + name + " -> " + resname);
            }

            envCtx.rebind(name, lref);
        }

        // EJB References
        IEJBRefDesc[] ejbref = clientDD.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, "Linking ejb " + name + " -> " + ejbname);
            }

            envCtx.rebind(name, lref);
        }

        // ServiceRef
        IServiceRefDesc[] serviceRefs = clientDD.getServiceRefDesc();
        if (serviceRefs.length != 0) {

            // get the current ClassLoader
            ClassLoader loader = Thread.currentThread().getContextClassLoader();

            // get the JServiceFactory
            JServiceFactory factory = ClientJServiceFactoryFinder.getJOnASServiceFactory();

            for (int i = 0; i < serviceRefs.length; i++) {
                // Create the Service from the ServiceRef description
                String refname = serviceRefs[i].getServiceRefName();

                Reference ref = factory.getServiceReference(serviceRefs[i], loader);
                envCtx.rebind(refname, ref);

                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Adding service-ref 'java:comp/env/" + refname + "'");
                }
            }
        }

        // JAX WS service Ref
        IENCBindingHolder encBindingHolder = clientDD.getENCBindingHolder();
        List<IENCBinding<IJaxwsWebServiceRef>> webServiceRefBindings = encBindingHolder.getWebServicesBindings();
        if (webServiceRefBindings != null) {
            for (IENCBinding<IJaxwsWebServiceRef> webServiceRefBinding : webServiceRefBindings) {
                // Get name / value
                String encName = webServiceRefBinding.getName();
                IJaxwsWebServiceRef jaxwsWebServiceRef = webServiceRefBinding.getValue();

                // Build reference
                WebServiceRefBuilder builder = new JAXWSWebServiceRefBuilder();

                Reference ref = null;
                try {
                    ref = builder.build(jaxwsWebServiceRef);
                } catch (NamingException e) {
                    throw new ClientContainerException("Cannot build the Reference for " + "@WebServiceRef(" + jaxwsWebServiceRef.getName()
                            + ")", e);
                }

                envCtx.rebind(encName, ref);

            }
        }



        // Persistence Unit Reference
        PersistenceUnitRefDesc[] puref = clientDD.getPersistenceUnitRefs();

        for (int i = 0; i < puref.length; i++) {
            String refName = puref[i].getRefName();
            String unitName =  puref[i].getName();

            if (persistenceUnitManager == null) {
                // This CAR doesn't contain persistence unit metadata
                break;
            }

            EntityManagerFactory emf = persistenceUnitManager.getEntityManagerFactory(unitName);

            envCtx.rebind(refName, emf);

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

        // MessageDestination References
        IMessageDestinationRefDesc[] mdref = clientDD.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, "Linking message-destination-ref " + name + " -> " + mdname);
            }

            envCtx.rebind(name, lref);
        }
    }

    /**
     * Analyze arguments and extract parameters for the client container.
     * @throws Exception if there is an error when analyzing arguments
     */
    private void analyzeArgs() throws Exception {
        for (int argn = 0; argn < args.length; argn++) {
            String arg = args[argn];

            try {
                if (arg.equals("-tmpDir")) {
                    tmpDir = args[++argn];

                    continue;
                }

                if (arg.equals("-jarClient")) {
                    jarClient = args[++argn];

                    continue;
                }

                if (arg.equals("-traceFile")) {
                    clientTraceFile = args[++argn];

                    continue;
                }

                if (arg.equals("-carolFile")) {
                    carolFile = args[++argn];

                    continue;
                }

                if (arg.equals("-cp")) {
                    classpath = args[++argn];
                    continue;
                }

                if (arg.equals("-nowsgen")) {
                    applyWsGen = false;
                    continue;
                }

                if (arg.equals("-genclientstub")) {
                    applyGenClientStub = true;
                    continue;
                }

                if (arg.equals("--help") || arg.equals("-help") || arg.equals("-h") || arg.equals("-?")) {
                    usage();
                    System.exit(1);
                }

                // Add argument to the application arguments
                appArgs.add(arg);
            } catch (ArrayIndexOutOfBoundsException aioobe) {
                // The next argument is not in the array
                throw new ClientContainerException("A required parameter was missing after the argument" + arg);
            }
        }
    }

    /**
     * Print the usage of this client.
     */
    private void usage() {
        System.out.println("Usage of this client :");
        System.out.println("-------------------------------------------------------------------");
        System.out.println("java -jar client.jar <client.jar|app.ear|className> [options]");
        System.out.println("-------------------------------------------------------------------");
        System.out.println(" -jarClient   : Specify the client jar to use of the ear if many.");
        System.out.println(" -traceFile   : Specify the configuration file to use for the traces\n"
                + "                of this client instead of the default file\n"
                + "                (traceclient.properties) present in client.jar.");
        System.out.println(" -carolFile   : Specify the carol.properties file to use instead of \n"
                + "                the default carol.properties file of the client.jar");
        System.out.println(" -tmpDir      : Specify the temp directory where unpack the ear.");
        System.out.println(" -cp          : Specify the classpath to use for the jar client.");
        System.out.println(" -nowsgen     : Specify if the Container shouldn't use Automated WsGen.");
        System.out.println(" -enable-jpa2 : Enable JPA 2.0 libraries on the client side.");
        System.out.println("-------------------------------------------------------------------");
        System.out.println("  --help  : Display this help.");
        System.out.println("  -help   : Display this help.");
        System.out.println("  -h      : Display this help.");
        System.out.println("  -?      : Display this help.");
        System.out.println("-------------------------------------------------------------------");
    }

    /**
     * Extract the client of an ear and analyze ear too.
     * @param earDeployable ear to be analyzed
     * @return the file of the client which was extracted
     * @throws Exception if the analyze and/or extract fails
     */
    private File extractAndAnalyzeEar(final EARDeployable earDeployable) throws Exception {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "");
        }

        URL earUrl = null;

        try {
            earUrl = earDeployable.getArchive().getURL();
        } catch (ArchiveException e) {
            throw new ClientContainerException("Cannot get url with deployable '" + earDeployable + "'.", e);
        }

        // Create classLoader
        URL[] arrURL = new URL[1];
        arrURL[0] = earUrl;

        // parent classloader is the current classloader
        ClassLoader currentLoader = Thread.currentThread().getContextClassLoader();
        URLClassLoader loaderCls = new URLClassLoader(arrURL, currentLoader);

        EarDeploymentDesc earDD = null;

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Getting the deployment descriptor of the file" + earDeployable);
        }

        try {
            earDD = EarDeploymentDescManager.getDeploymentDesc(earDeployable, loaderCls);
        } catch (EarDeploymentDescException e) {
            String err = "Error in the Deployment descriptor from deployable '" + earDeployable + "'";
            throw new ClientContainerException(err, e);
        }

        Map userToRoleMapping = earDD.getUserToRoleMapping();
        // Do user-to-role mapping
        if (userToRoleMapping != null) {
            for (Iterator itMapping = userToRoleMapping.keySet().iterator(); itMapping.hasNext();) {
                String principalName = (String) itMapping.next();
                List roles = (List) userToRoleMapping.get(principalName);
                String[] roleNames = (String[]) roles.toArray(new String[roles.size()]);
                JPolicyUserRoleMapping.addGlobalUserToRoleMapping(principalName, roleNames);
            }
        }


        // Get the tags from the Deployment descriptor
        String[] ejbTags = earDD.getEjbTags();
        Web[] webTags = earDD.getWebTags();

        String[] clientTags = earDD.getClientTags();
        String[] altDDEjbs = earDD.getAltDDEjbs();
        String[] altDDWebs = earDD.getAltDDWebs();
        String[] altDDClients = earDD.getAltDDClients();

        // Changing arrays into JarList
        JarList ejbsList = new JarList(ejbTags);
        JarList clientsList = new JarList(clientTags);

        String[] webURIs = new String[webTags.length];
        for (int i = 0; i < webURIs.length; i++) {
            webURIs[i] = webTags[i].getWebUri();
        }
        JarList websList = new JarList(webURIs);

        // Temporary directory
        String tempDir = null;

        if (tmpDir != null) {
            // use specific directory
            tempDir = tmpDir;
            info("Use your specified temp directory '" + tempDir + "'.");
        } else {
            // use default
            tempDir = System.getProperty("java.io.tmpdir");
        }

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Using temp directory '" + tempDir + "'.");
        }

        // Can we write to ?
        File tmpFileDir = new File(tempDir);

        if (!tmpFileDir.exists() || !tmpFileDir.isDirectory()) {
            throw new ClientContainerException("The temp directory '" + tempDir
                    + "' doesn't exist or is not a directory.");
        }

        if (!tmpFileDir.canWrite()) {
            throw new ClientContainerException("Can not write to the temporary directory '" + tempDir + "'.");
        }

        // Unpack the ear file and get the unpacked dir
        URL dirUnpackURL = null;

        if (earDeployable.getOriginalDeployable() == null) {
        try {
            EARDeployable unpackedDeployable = UnpackDeployableHelper.unpack(earDeployable, tmpFileDir, null);
            dirUnpackURL = unpackedDeployable.getArchive().getURL();
        } catch (Exception e) {
            String err = "Error while unpacking the file '" + earUrl + "'";
            throw new ClientContainerException(err, e);
        }
        } else {
            dirUnpackURL = earDeployable.getArchive().getURL();
        }

        // Ear is unpacked, now analyze manifest Class-path:
        EarClassPathManager earCPManager = null;

        try {
            earCPManager = new EarClassPathManager(clientsList, dirUnpackURL);
        } catch (EarClassPathManagerException e) {
            String err = "Error while creating the Ear class path manager of the ear : '" + earUrl + "'";
            error(err + " : " + e.getMessage());
            throw new ClientContainerException(err, e);
        }

        URL[] classpathURLs = null;

        //Get the urls of the ear class path manager
        try {
            classpathURLs = earCPManager.getResolvedClassPath();
        } catch (EarClassPathManagerException e) {
            String err = "Error while trying to resolve the classpath of the ejbjars and wars of the ear : '" + earUrl
                    + "'";
            error(err + " : " + e.getMessage());
            throw new ClientContainerException(err, e);
        }

        // Add libraries urls found with Java EE library folder
        List<LibDeployable> libDeployables = earDeployable.getLibDeployables();
        List<URL> updatedClasspathURLs = new ArrayList<URL>();
        if (classpathURLs != null) {
           for (URL classpathURL : classpathURLs) {
                updatedClasspathURLs.add(classpathURL);
           }

       }

        // Add all extra libraries found with new Java EE 5 feature
        for (LibDeployable libDeployable : libDeployables) {
            URL libDeployableURL = null;
            try {
                libDeployableURL = libDeployable.getArchive().getURL();
            } catch (ArchiveException e) {
                logger.log(BasicLevel.ERROR, "libs: unable to get URL from '" + libDeployableURL + "'.");
                continue;
            }
            if (!updatedClasspathURLs.contains(libDeployableURL)) {
                updatedClasspathURLs.add(libDeployableURL);
            }
        }
        classpathURLs = updatedClasspathURLs.toArray(new URL[updatedClasspathURLs.size()]);


        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "EAR : ejbs = " + ejbsList);
            logger.log(BasicLevel.DEBUG, "EAR : webs = " + websList);
            logger.log(BasicLevel.DEBUG, "EAR : clientUrls = " + clientsList);
        }

        currentLoader = Thread.currentThread().getContextClassLoader();
        earClassLoader = new URLClassLoader(new URL[0], currentLoader);

        //Extract the urls of the jarList
        URL[] jarUrls = null;
        URL[] warUrls = null;
        URL[] clientUrls = null;

        try {
            jarUrls = ejbsList.getURLs(dirUnpackURL.toExternalForm());
            warUrls = websList.getURLs(dirUnpackURL.toExternalForm());
            clientUrls = clientsList.getURLs(dirUnpackURL.toExternalForm());
        } catch (JarListException e) {
            String err = "Error while getting the URLs from jarlist of the ear : '" + earUrl + "'";
            throw new ClientContainerException(err, e);
        }

        // Get PersistenceUnitManagers from client jars
        List<CARDeployable> carDeployables = earDeployable.getCARDeployables();
        if (carDeployables != null) {

            // Construct EasyBeansClassLoader for PersistentContext object creation by EasyBeans
            ClassLoader ezbClassLoader = new EasyBeansClassLoader(clientUrls, currentLoader);

            // Configure some dialects.
            configureExtraDialects();

            for (CARDeployable carDeployable : carDeployables) {
                updatePersistenceUnitManager(getPersistenceUnitManager(carDeployable, ezbClassLoader));
            }
        }

        // TODO - also analyze PersistenceUnitManager info in EAR !

        //Fill Alt-DD for Ejbs and Clients
        String altdd = null;
        File fAltDD = null;

        //Transorm the array altDDWebs into an array with the absolute URL to
        // the file
        URL[] clientsAltDDs = new URL[altDDClients.length];

        for (int i = 0; i < altDDClients.length; i++) {
            if (altDDClients[i] != null) {
                altdd = altDDClients[i];

                if (altdd != null) {
                    try {
                        fAltDD = new File(new URL(dirUnpackURL.toExternalForm() + File.separator + altdd).getFile());
                        clientsAltDDs[i] = fAltDD.getCanonicalFile().toURL();
                    } catch (MalformedURLException e) {
                        String err = "Can't build URL for alt-dd '" + altdd;
                        error(err + "': " + e.getMessage());
                        throw new ClientContainerException(err, e);
                    } catch (IOException ioe) {
                        String err = "Can't get canonicalFile() for the file '" + fAltDD;
                        error(err + "': " + ioe.getMessage());
                        throw new ClientContainerException(err, ioe);
                    }
                }
            }
        }

        URL[] ejbsAltDDs = new URL[altDDEjbs.length];

        for (int i = 0; i < altDDEjbs.length; i++) {
            if (altDDEjbs[i] != null) {
                altdd = altDDEjbs[i];

                if (altdd != null) {
                    try {
                        fAltDD = new File(new URL(dirUnpackURL.toExternalForm() + File.separator + altdd).getFile());
                        ejbsAltDDs[i] = fAltDD.getCanonicalFile().toURL();
                    } catch (MalformedURLException e) {
                        String err = "Can't build URL for alt-dd '" + altdd;
                        error(err + "': " + e.getMessage());
                        throw new ClientContainerException(err, e);
                    } catch (IOException ioe) {
                        String err = "Can't get canonicalFile() for the file '" + fAltDD;
                        error(err + "': " + ioe.getMessage());
                        throw new ClientContainerException(err, ioe);
                    }
                }
            }
        }

        URL[] websAltDDs = new URL[altDDWebs.length];

        for (int i = 0; i < altDDWebs.length; i++) {
            if (altDDWebs[i] != null) {
                altdd = altDDWebs[i];

                if (altdd != null) {
                    try {
                        fAltDD = new File(new URL(dirUnpackURL.toExternalForm() + File.separator + altdd).getFile());
                        websAltDDs[i] = fAltDD.getCanonicalFile().toURL();
                    } catch (MalformedURLException e) {
                        String err = "Can't build URL for alt-dd '" + altdd;
                        error(err + "': " + e.getMessage());
                        throw new ClientContainerException(err, e);
                    } catch (IOException ioe) {
                        String err = "Can't get canonicalFile() for the file '" + fAltDD;
                        error(err + "': " + ioe.getMessage());
                        throw new ClientContainerException(err, ioe);
                    }
                }
            }
        }

        EjbDeploymentDescManager.getInstance().setAvailableEjbJarsAndAltDDs(earClassLoader, jarUrls, ejbsAltDDs);
        WebDeploymentDescManager.getInstance().setAltDD(earClassLoader, warUrls, websAltDDs);
        ClientDeploymentDescManager.getInstance().setAltDD(earClassLoader, clientUrls, clientsAltDDs);

        // Construct the ejb classloader for all the ejb-jars of the same
        // ear application. Because there is one ejb classloader for all
        // the ejb-jars of the same ear application.
        URL[] userURLs = getUserClasspathUrls();
        extensionsURLs = new URL[jarUrls.length + classpathURLs.length + userURLs.length];

        System.arraycopy(jarUrls, 0, extensionsURLs, 0, jarUrls.length);
        System.arraycopy(classpathURLs, 0, extensionsURLs, jarUrls.length, classpathURLs.length);
        System.arraycopy(userURLs, 0, extensionsURLs, jarUrls.length + classpathURLs.length, userURLs.length);

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Extensions urls :");

            for (int ii = 0; ii < extensionsURLs.length; ii++) {
                logger.log(BasicLevel.DEBUG, "url[" + ii + "] = " + extensionsURLs[ii]);
            }
        }

        // Choose the jar client
        if (clientUrls.length == 0) {
            throw new ClientContainerException("No java client was found in the application.xml file of the Ear '"
                    + earUrl + "'.");
        }

        File fClient = null;

        // User has specify an application client to execute
        // Search it
        if (jarClient != null) {
            int f = 0;
            File ff = null;
            boolean found = false;

            // Search the best matching file
            while (f < clientUrls.length && !found) {
                ff = URLUtils.urlToFile(clientUrls[f]);

                if (ff.getName().equals(jarClient)) {
                    found = true;
                    fClient = ff;

                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "Found a matching client with the name " + ff);
                    }
                }

                f++;
            }

            if (!found) {
                throw new ClientContainerException("No client with the name '" + jarClient
                        + "' was found in this Ear file");
            }
        } else {
            // Take first
            fClient = URLUtils.urlToFile(clientUrls[0]);

            // Warning if many
            if (clientUrls.length > 1) {
                warn("There are " + clientUrls.length + " clients in this ear, choosing the first one : "
                        + fClient.getName());
            }
        }

        info("Use the application client '" + fClient + "' of the Ear '" + earUrl + "'.");

        return fClient;
    }

    /**
     * Gets the URL of user classpath (can be empty).
     * @return URL of user classpath (-cp arg)
     */
    private URL[] getUserClasspathUrls() {
        if (classpath == null) {
            return new URL[0];
        }
        String sep = File.pathSeparator;
        List<URL> clUser = new ArrayList<URL>();
        StringTokenizer tokenizer = new StringTokenizer(classpath, sep);
        while (tokenizer.hasMoreTokens()) {
            File file = new File(tokenizer.nextToken());
            try {
                clUser.add(file.toURL());
            } catch (MalformedURLException mue) {
                logger.log(BasicLevel.WARN, "Cannot transform to URL the file : '" + file + "'", mue);
            }
        }
        return clUser.toArray(new URL[0]);
    }

    /**
     * Gets the persistence unit manager for a given deplyable and classloader.
     * @param deployable EAR or JAR deployable
     * @param classLoader EasyBeansClassLoader needed to create PersistenceContext objects.
     * @return
     */
    private PersistenceUnitManager getPersistenceUnitManager(final IDeployable<?> deployable,
            final ClassLoader classLoader) {
        PersistenceUnitManager persistenceUnitManager = null;
        try {
            JPersistenceUnitInfo[] persistenceUnitInfos = PersistenceXmlFileAnalyzer.analyzePersistenceXmlFile(deployable.getArchive());
            if (persistenceUnitInfos != null) {
                persistenceUnitManager = PersistenceXmlFileAnalyzer.loadPersistenceProvider(persistenceUnitInfos, classLoader);
            }
        } catch (PersistenceXmlFileAnalyzerException e) {
            throw new IllegalStateException("Failure when analyzing the persistence.xml file", e);
        }
        return persistenceUnitManager;
    }

    /**
     * Update persistenceUnitManager if already created, initialize it otherwise.
     * @param extraPersistenceUnitManager keeps persistence unit info from a given deployabale
     */
    private void updatePersistenceUnitManager(final PersistenceUnitManager extraPersistenceUnitManager) {
        // Existing manager
        if (persistenceUnitManager != null) {
            if (extraPersistenceUnitManager != null) {
                // Add the persistence unit infos to the existing
                // persistence unit manager
                persistenceUnitManager.addExtraPersistenceUnitInfos(extraPersistenceUnitManager.getPersistenceUnitInfos());
            }
        } else {
            // New persistence manager use the built manager
            persistenceUnitManager = extraPersistenceUnitManager;
        }
    }

    /**
     * Code from org.ow2.jonas.ejb.easybeans.EasyBeansService.
     * Configure some dialects that may be used with some databases used by JOnAS client.
     */
    @SuppressWarnings("unchecked")
    protected void configureExtraDialects() {
        // Add Hibernate dialect
        try {
            Class<?> hibernateDialectClass = Embedded.class.getClassLoader().loadClass("org.hibernate.dialect.DialectFactory");
            Field mapper = hibernateDialectClass.getDeclaredField("MAPPERS");
            mapper.setAccessible(true);
            Map<String, Object> map = null;
            map = (Map<String, Object>) mapper.get(null);
            Class<?> versionInsensitiveMapperClass = Embedded.class.getClassLoader().loadClass(
                    "org.hibernate.dialect.DialectFactory$VersionInsensitiveMapper");
            Constructor<?> c = versionInsensitiveMapperClass.getConstructor(String.class);
            Object oracle9idialect = c.newInstance("org.hibernate.dialect.Oracle9iDialect");
            map.put("Oracle9i Enterprise Edition", oracle9idialect);
            Object oracle10Dialect = c.newInstance("org.hibernate.dialect.OracleDialect");
            map.put("Oracle Database 10g Release 10.2.0.3.0 - 64bit Production", oracle10Dialect);
            mapper.setAccessible(false);
        } catch (Exception e) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Cannot configure some dialects used by Hibernate", e);
            }
        }
    }
    /**
     * Display an info message with the logger.
     * @param s the message
     */
    private void info(final String s) {
        logger.log(BasicLevel.INFO, s);
    }

    /**
     * Display an error message with the logger.
     * @param s the message
     */
    private void error(final String s) {
        logger.log(BasicLevel.ERROR, s);
    }

    /**
     * Display a warn message with the logger.
     * @param s the message
     */
    private void warn(final String s) {
        logger.log(BasicLevel.WARN, s);
    }
}
