/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2009 Bull S.A.
 * 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
 *
 * Initial developer(s): Florent BENOIT
 * --------------------------------------------------------------------------
 * $Id: ClientDeploymentDescManager.java 17993 2009-07-21 21:30:03Z loris $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.deployment.client.lib;

//import java

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.rmi.RemoteException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.StringTokenizer;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import javax.annotation.Resource;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.easybeans.resolver.api.EZBJNDIBeanData;
import org.ow2.easybeans.resolver.api.EZBJNDIData;
import org.ow2.easybeans.resolver.api.EZBRemoteJNDIResolver;
import org.ow2.jonas.deployment.api.IServiceRefDesc;
import org.ow2.jonas.deployment.client.AppClientDTDs;
import org.ow2.jonas.deployment.client.AppClientSchemas;
import org.ow2.jonas.deployment.client.ClientContainerDeploymentDesc;
import org.ow2.jonas.deployment.client.ClientContainerDeploymentDescException;
import org.ow2.jonas.deployment.client.JonasAppClientDTDs;
import org.ow2.jonas.deployment.client.JonasAppClientSchemas;
import org.ow2.jonas.deployment.client.rules.ApplicationClientRuleSet;
import org.ow2.jonas.deployment.client.rules.JonasClientRuleSet;
import org.ow2.jonas.deployment.client.xml.ApplicationClient;
import org.ow2.jonas.deployment.client.xml.JonasClient;
import org.ow2.jonas.deployment.common.DeploymentDescException;
import org.ow2.jonas.deployment.common.EjbRefDesc;
import org.ow2.jonas.deployment.common.MessageDestinationRefDesc;
import org.ow2.jonas.deployment.common.digester.JDigester;
import org.ow2.jonas.deployment.common.lib.AbsDeploymentDescManager;
import org.ow2.jonas.deployment.common.xml.EjbRef;
import org.ow2.jonas.deployment.common.xml.JonasEjbRef;
import org.ow2.jonas.deployment.common.xml.JonasMessageDestination;
import org.ow2.jonas.deployment.common.xml.JonasResource;
import org.ow2.jonas.deployment.common.xml.JonasServiceRef;
import org.ow2.jonas.deployment.common.xml.MessageDestinationRef;
import org.ow2.jonas.deployment.common.xml.PersistenceUnitRef;
import org.ow2.jonas.deployment.common.xml.ResourceRef;
import org.ow2.jonas.deployment.ejb.DeploymentDesc;
import org.ow2.jonas.deployment.ejb.lib.EjbDeploymentDescManager;
import org.ow2.jonas.deployment.ws.PortComponentDesc;
import org.ow2.jonas.deployment.ws.PortComponentRefDesc;
import org.ow2.jonas.deployment.ws.ServiceDesc;
import org.ow2.jonas.deployment.ws.WSDeploymentDesc;
import org.ow2.jonas.deployment.ws.lib.WSDeploymentDescManager;
import org.ow2.jonas.lib.loader.WebappClassLoader;
import org.ow2.jonas.lib.util.Log;
import org.ow2.util.archive.api.IArchive;
import org.ow2.util.archive.impl.ArchiveManager;
import org.ow2.util.ee.deploy.api.deployable.CARDeployable;
import org.ow2.util.ee.deploy.impl.helper.DeployableHelper;
import org.ow2.util.ee.deploy.impl.helper.UnpackDeployableHelper;
import org.ow2.util.ee.metadata.car.api.ICarDeployableMetadata;
import org.ow2.util.ee.metadata.car.impl.CarDeployableMetadataFactory;
import org.ow2.util.ee.metadata.car.impl.enc.CarENCBindingBuilder;
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.IJAnnotationResource;
import org.ow2.util.ee.metadata.common.api.struct.IJEjbEJB;
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.common.impl.enc.ENCBindingException;
import org.ow2.util.url.URLUtils;


/**
 * This class provide a way for managing the ClientContainerDeploymentDesc.
 * @author Florent Benoit
 */
public class ClientDeploymentDescManager extends AbsDeploymentDescManager {

    /**
     * The unique instance of the ClientDeploymentDescManager.
     */
    private static ClientDeploymentDescManager unique;

    /**
     * Reference on the EjbDeploymentDescManager.
     */
    private EjbDeploymentDescManager ejbDDManager = null;

    /**
     * Associate a ear classLoader to an hashtable which contains association
     * between Urls of wars and their optional alt-dd
     */
    private Hashtable earCLAltDDBindings = null;

    /**
     * The path to the application-client.xml file.
     */
    public static final String CLIENT_FILE_NAME = "META-INF/application-client.xml";

    /**
     * The path to the jonas-client.xml file.
     */
    public static final String JONAS_CLIENT_FILE_NAME = "META-INF/jonas-client.xml";

    /**
     * Digester used to parse application-client.xml
     */
    private static JDigester appClientDigester = null;

    /**
     * Digester use to parse jonas-client.xml
     */
    private static JDigester jonasAppClientDigester = null;

    /**
     * Flag for parser validation
     */
    private static boolean parsingWithValidation = true;

    /**
     * Rules to parse the application-client.xml
     */
    private static ApplicationClientRuleSet appClientRuleSet = new ApplicationClientRuleSet();

    /**
     * Rules to parse the jonas-client.xml
     */
    private static JonasClientRuleSet jonasAppClientRuleSet = new JonasClientRuleSet();

    /**
     * logger.
     */
    private static Logger logger = Log.getLogger(ClientDeploymentDescManager.class.getName());

    /**
     * Contructs a unique ClientDeploymentDescManager
     */
    private ClientDeploymentDescManager() {
        ejbDDManager = EjbDeploymentDescManager.getInstance();
        earCLAltDDBindings = new Hashtable();
    }

    /**
     * Get an instance of the ClientDeploymentDescManager.
     * @return the instance of the ClientDeploymentDescManager.
     */
    public static ClientDeploymentDescManager getInstance() {
        if (unique == null) {
            unique = new ClientDeploymentDescManager();
        }
        return unique;
    }

    /**
     * Get the specified client deployment descriptor.
     * @param url the url where to load xml deployment descriptors.
     * @param loaderForCls classloader used to load client classes.
     * @param earLoader the ear classloader.
     * @return ClientContainerDeploymentDesc the client deployment descriptor.
     * @throws DeploymentDescException when
     *         ClientContainerDeploymentDesc cannot be created with the given
     *         files.
     */
    public ClientContainerDeploymentDesc getDeploymentDesc(final URL url, final ClassLoader loaderForCls, final ClassLoader earLoader)
            throws DeploymentDescException {

        // Check if the jar exists ...
        if (!URLUtils.urlToFile(url).exists()) {
            String err = "Cannot get the deployment descriptor for ";
            err = err + "'" + url.getFile() + "'. The file doesn't exist.";
            throw new ClientContainerDeploymentDescException(err);
        }

        //url used to load an alternate DDesc in the EAR case
        URL altDDUrl = null;

        //check if it's an Ear case or not
        Hashtable urlAltddBindings = null;
        if (earLoader != null) {
            //Mapping ?
            urlAltddBindings = (Hashtable) earCLAltDDBindings.get(earLoader);
            if (urlAltddBindings == null) {
                //If there is no mapping, the setAltDD function was badly
                // called
                String err = "Cannot find if there is alt-dd for '" + url.getFile()
                        + "', the setAltDD function was badly called";
                throw new ClientContainerDeploymentDescException(err);
            }
            //Now we can get the optional alt-dd url file
            altDDUrl = (URL) urlAltddBindings.get(url);
        }

        // ... and get the instance of the ClientContainerDeploymentDesc.
        // If there is an alternate url for the application-client.xml, call the
        // method with this param.
        ClientContainerDeploymentDesc clientDD = null;
        try {
            if (altDDUrl != null) {
                clientDD = getInstance(URLUtils.urlToFile(url).getPath(), loaderForCls, altDDUrl.getFile());
            } else {
                clientDD = getInstance(URLUtils.urlToFile(url).getPath(), loaderForCls);
            }
        } catch (org.ow2.jonas.deployment.common.DeploymentDescException dde) {
            throw new ClientContainerDeploymentDescException(dde);
        }

        // Resolve the ejb-link for ejb-ref
        EjbRefDesc[] ejbRef = clientDD.getEjbRefDesc();
        for (int i = 0; i < ejbRef.length; i++) {
            if (ejbRef[i].getJndiName() == null) {
                String ejbLink = ejbRef[i].getEjbLink();
                String ejbRefType = ejbRef[i].getEjbRefType();
                String home = ejbRef[i].getHome();
                String remote = ejbRef[i].getRemote();
                if (ejbLink != null) {
                    if (earLoader == null) {
                        throw new ClientContainerDeploymentDescException(
                                "Ejb-link is not authorized from a single client jar. The client jar must be in an ear.");
                    } else {
                        String jndiName = null;
                        // First, try to get JNDI name through the manager before trying with the remote JNDI resolver.
                        try {
                            jndiName = getJndiName(url, ejbLink, earLoader, ejbRefType);
                        } catch (DeploymentDescException e) {

                            // log first exception
                            if (logger.isLoggable(BasicLevel.DEBUG)) {
                                logger.log(BasicLevel.DEBUG, "Unable to get JNDI name with the EJB DD manager", e);
                            }

                            // Was not able to get a JNDI name, try with the remote object.
                            EZBRemoteJNDIResolver jndiResolver = null;

                             // Get object
                            Object o = null;
                            try {
                                o = new InitialContext().lookup("EZB_Remote_JNDIResolver");
                            } catch (NamingException ne) {
                                // No Remote JNDI resolver, so throw first exception
                                logger.log(BasicLevel.DEBUG, "No Remote EJB3 JNDI Resolver found");
                                throw e;
                            }

                            // Object is here, so we can cast it
                            jndiResolver = (EZBRemoteJNDIResolver) PortableRemoteObject.narrow(o, EZBRemoteJNDIResolver.class);

                            // Ask the Resolver with interface name and bean name
                            String interfaceName = remote;
                            if (home != null && !"".equals(home)) {
                                interfaceName = home;
                            }
                            String beanName = ejbLink;
                            List<EZBJNDIBeanData> jndiDataList = null;
                            try {
                                jndiDataList = jndiResolver.getEJBJNDINames(interfaceName, beanName);
                            } catch (RemoteException re) {
                                // No Remote JNDI resolver, so throw first exception
                                throw new DeploymentDescException("Unable to get EJB-LINK for interface'" + interfaceName + "' and beanName'" + beanName + "'", re);
                            }

                            // Data is here, check if it is empty or not
                            if (jndiDataList.size() == 0) {
                                throw new DeploymentDescException("Unable to get EJB-LINK for interface'" + interfaceName + "' and beanName'" + beanName + "', no data was found on the remote side.");
                            } else if (jndiDataList.size() > 1) {
                                // too many entries
                                throw new DeploymentDescException("Unable to get EJB-LINK for interface'" + interfaceName + "' and beanName'" + beanName + "', too many answers : '" + jndiDataList + "'.");
                            }

                            // Only one item found, so get JNDI Name from this object
                            EZBJNDIBeanData jndiData = jndiDataList.get(0);
                            // Update JNDI name
                            jndiName = jndiData.getName();
                            if (logger.isLoggable(BasicLevel.DEBUG)) {
                                logger.log(BasicLevel.DEBUG, "Found JNDI Name '" + jndiName + "' for interface'" + interfaceName + "' and beanName'" + beanName + "', too many answers : '" + jndiDataList + "'.");
                            }
                        }
                        ejbRef[i].setJndiName(jndiName);
                    }
                }
            }
        }
        // Resolve the port-component-link for service-ref
        IServiceRefDesc[] serviceRef = clientDD.getServiceRefDesc();

        for (int i = 0; i < serviceRef.length; i++) {

            List pcRefs = serviceRef[i].getPortComponentRefs();
            for (int j = 0; j < pcRefs.size(); j++) {
                // for each service portComponents : resolve links
                PortComponentRefDesc pcr = (PortComponentRefDesc) pcRefs.get(j);
                String pclink = pcr.getPortComponentLink();
                if (pclink != null) {
                    // a pc link is defined, we resolve it
                    PortComponentDesc pcDesc = getPCDesc(url, pclink, earLoader);
                    pcr.setPortComponentDesc(pcDesc);
                }
            }
        }


        // Resolve the message-destination-link for message-destination-ref
        MessageDestinationRefDesc[] mdRef = clientDD.getMessageDestinationRefDesc();
        for (int i = 0; i < mdRef.length; i++) {
            if (mdRef[i].getJndiName() == null) {
                String jndiName = mdRef[i].getJndiName();
                String mdLink = mdRef[i].getMessageDestinationLink();
                String mdType = mdRef[i].getMessageDestinationType();
                String mdUsage = mdRef[i].getMessageDestinationUsage();
                if (mdLink != null) {
                    if (earLoader == null) {
                        throw new ClientContainerDeploymentDescException(
                                "Message-destination-link is not authorized from a single client jar. The client jar must be in an ear.");
                    } else {
                        jndiName = getMDJndiName(url, mdLink, mdType, mdUsage, earLoader);
                        //                        String jndiName = getMdJndiName(url, mdLink,
                        // earLoader, mdType);
                        mdRef[i].setJndiName(jndiName);
                    }
                }
            }
        }
        return clientDD;
    }
    /**
     * Return the port component desc from the pcLink string. pcLink format :
     * filename.[jar or war]#portComponentName in the same Ear File
     * @param warURL the url of the war being parsed. This is needed because
     *        pcLink is relative. With the url and the pcLink, we can know where
     *        the file is locate.
     * @param pcLink the pcLink tag of an port-component-ref.
     * @param earLoader the classloader of the ear.
     * @return the pcLink portComponent.
     * @throws DeploymentDescException when it failed
     */
    private PortComponentDesc getPCDesc(final URL warURL, final String pcLink, final ClassLoader earLoader)
            throws DeploymentDescException {

        // Extract from the pc link
        //   - the name of the file
        //   - the name of the bean
        String moduleLink = null;
        String pcNameLink = null;

        // Check the format of the pc-link. It must contains .jar# or .war#
        if ((pcLink.toLowerCase().indexOf(".war" + LINK_SEPARATOR) == -1)
                && (pcLink.toLowerCase().indexOf(".jar" + LINK_SEPARATOR) == -1)) {
            // the pc link is not in war or jar file
            String err = "PC-link " + pcLink
                    + " has a bad format. Correct format :  filename.(jar|war)#portComponentName";
            throw new DeploymentDescException(err);
        }
        StringTokenizer st = new StringTokenizer(pcLink, LINK_SEPARATOR);

        // We must have only two elements after this step, one for the fileName
        //   before the # and the name of the bean after the # char
        if (st.countTokens() != 2 || pcLink.startsWith(LINK_SEPARATOR) || pcLink.endsWith(LINK_SEPARATOR)) {
            String err = "PC-link " + pcLink
                    + " has a bad format. Correct format :  filename.[jar or war]#portComponentName";
            throw new DeploymentDescException(err);
        }

        //Get the token
        moduleLink = st.nextToken();
        pcNameLink = st.nextToken();

        // Now construct the URL from the absolute path from the url module and
        // the relative path from moduleJarLink
        URL moduleLinkUrl = null;
        try {
            moduleLinkUrl = new File(URLUtils.urlToFile(warURL).getParent() + File.separator + moduleLink)
                    .getCanonicalFile().toURL();
        } catch (MalformedURLException mue) {
            String err = "Error when creating an url for the module filename. Error :" + mue.getMessage();
            throw new DeploymentDescException(err);
        } catch (IOException ioe) {
            String err = "Error when creating/accessing a file. Error :" + ioe.getMessage();
            throw new DeploymentDescException(err);
        }

        // Check if the jar exist.
        if (!new File(moduleLinkUrl.getFile()).exists()) {
            String err = "Cannot get the deployment descriptor for '" + moduleLinkUrl.getFile()
                    + "'. The file doesn't exist.";
            throw new DeploymentDescException(err);
        }

        // We've got the url
        //   Now, We can ask the Deployment Descriptor of this url
        ClassLoader loaderForCls = null;
        ClassLoader current = Thread.currentThread().getContextClassLoader();
        if (moduleLink.toLowerCase().endsWith(".war")) {
            try {
                // webservice in webapp
                loaderForCls = new WebappClassLoader(moduleLinkUrl, current);
            } catch (IOException ioe) {
                throw new DeploymentDescException("Unable to create Web ClassLoader", ioe);
            }
        } else {
            // webservice in ejbjar
            loaderForCls = current;
        }

        WSDeploymentDesc wsDD = null;

        try {
            wsDD = WSDeploymentDescManager.getInstance().getDeploymentDesc(moduleLinkUrl, loaderForCls, earLoader);
        } catch (DeploymentDescException e) {
            String err = "Cannot get the deployment descriptor for '" + moduleLinkUrl.getFile() + "'.";
            throw new DeploymentDescException(err, e);
        }
        if (wsDD == null) {
            // the module doesn't contain port components.
            String err = "Port component link " + pcNameLink + " not found in " + moduleLinkUrl.getFile();
            throw new DeploymentDescException(err);
        }

        //get port component desc //pcNameLink
        List sdl = wsDD.getServiceDescs();
        boolean isFound = false;
        PortComponentDesc pcd = null;
        for (int i = 0; (i < sdl.size()) && !isFound; i++) {
            if (sdl.get(i) != null) {
                pcd = ((ServiceDesc) sdl.get(i)).getPortComponent(pcNameLink);
                isFound = (pcd != null);
                // we stop when we have found the good portComponent
            }
        }
        if (!isFound) {
            // the module doesn't contain port components.
            String err = "the port component link " + pcNameLink + " doesn't exist in " + moduleLinkUrl.getFile();
            throw new DeploymentDescException(err);
        }

        return pcd;
    }


    /**
     * Get an instance of a Client deployment descriptor by parsing the
     * application-client.xml and jonas-client.xml deployment descriptors.
     * @param clientFileName the fileName of the client file for the deployment
     *        descriptors.
     * @param classLoaderForCls the classloader for the classes.
     * @return a Client deployment descriptor by parsing the
     *         application-client.xml and jonas-client.xml deployment
     *         descriptors.
     * @throws DeploymentDescException if the deployment descriptors are
     *         corrupted.
     */
    public static ClientContainerDeploymentDesc getInstance(final String clientFileName, final ClassLoader classLoaderForCls)
            throws DeploymentDescException {

        return getInstance(clientFileName, classLoaderForCls, null);
    }

    /**
     * Get an instance of a Client deployment descriptor by parsing the
     * application-client.xml and jonas-client.xml deployment descriptors.
     * @param clientFileName the fileName of the client file for the deployment
     *        descriptors.
     * @param classLoaderForCls the classloader for the classes.
     * @param altClientXmlFilename the fileName to the application-client.xml
     *        for the alt-dd tag in the Ear Case. This is used for specify an
     *        alternate DDesc file.
     * @return a Client deployment descriptor by parsing the
     *         application-client.xml and jonas-client.xml deployment
     *         descriptors.
     * @throws DeploymentDescException if the deployment descriptors are
     *         corrupted.
     */
    public static ClientContainerDeploymentDesc getInstance(final String clientFileName, final ClassLoader classLoaderForCls,
            final String altClientXmlFilename)

    throws DeploymentDescException {

        // clientjar file
        JarFile clientFile = null;

        // Input streams
        InputStream applicationClientInputStream = null;
        InputStream jonasClientInputStream = null;

        // ZipEntry
        ZipEntry applicationClientZipEntry = null;
        ZipEntry jonasClientZipEntry = null;

        // Clients
        ApplicationClient applicationClient;
        JonasClient jonasClient;

        // Init xml contents values;
        String xmlContent = "";
        String jonasXmlContent = "";

        // Build the file
        File fClient = new File(clientFileName);

        //Check if the file exists.
        if (!(fClient.exists())) {
            String err = "' " + clientFileName + "' was not found.";
            throw new ClientContainerDeploymentDescException(err);
        }

        //Check if the Alt deploymentDesc file exists.
        //But only if it's a non null value because it's optionnal.
        if ((altClientXmlFilename != null) && (!new File(altClientXmlFilename).exists())) {
            String err = "The file for the altdd tag for the EAR case '" + altClientXmlFilename + "' was not found.";
            throw new ClientContainerDeploymentDescException(err);
        }

        // load the application-client deployment descriptor data
        // (META-INF/application-client.xml
        // and META-INF/jonas-client.xml)
        //No alt-dd case
        if (altClientXmlFilename == null) {
            try {
                clientFile = new JarFile(clientFileName);

                //Lookup in the JAR
                //Check the client entry
                applicationClientZipEntry = clientFile.getEntry(CLIENT_FILE_NAME);
                if (applicationClientZipEntry != null) {
                    //Get the stream
                    applicationClientInputStream = clientFile.getInputStream(applicationClientZipEntry);
                    xmlContent = xmlContent(applicationClientInputStream);
                    // necessary to have a not empty InputStream !!!
                    applicationClientInputStream = clientFile.getInputStream(applicationClientZipEntry);
                }
            } catch (Exception e) {
                if (clientFile != null) {
                    try {
                        clientFile.close();
                    } catch (IOException ioe) {
                        //We can't close the file
                    }
                }
                throw new ClientContainerDeploymentDescException(
                        "Can not read the XML deployment descriptors of the client jar file '" + clientFileName + "'.",
                        e);
            }
        } else {
            try {
                applicationClientInputStream = new FileInputStream(altClientXmlFilename);
                xmlContent = xmlContent(applicationClientInputStream);
                // necessary to have a not empty InputStream !!!
                applicationClientInputStream = new FileInputStream(altClientXmlFilename);
            } catch (FileNotFoundException ioe) {
                throw new ClientContainerDeploymentDescException("The altDD file '" + altClientXmlFilename
                        + "' was not found.");
            } catch (Exception e) {
                if (applicationClientInputStream != null) {
                    try {
                        applicationClientInputStream.close();
                    } catch (IOException ioe) {
                        // Can't close the file
                    }
                }
                throw new ClientContainerDeploymentDescException("Cannot read the XML deployment descriptors of the client jar file '"
                        + clientFileName + "'.", e);
            }
        }

        if (applicationClientInputStream != null) {
            applicationClient = loadApplicationClient(new InputStreamReader(applicationClientInputStream), CLIENT_FILE_NAME);
        } else {
            applicationClient = new ApplicationClient();
        }

        try {
            clientFile = new JarFile(clientFileName);

            // Lookup in the JAR
            // Check the client entry
            jonasClientZipEntry = clientFile.getEntry(JONAS_CLIENT_FILE_NAME);
            if (jonasClientZipEntry != null) {
                //Get the stream
                jonasClientInputStream = clientFile.getInputStream(jonasClientZipEntry);
                jonasXmlContent = xmlContent(jonasClientInputStream);
                // necessary to have a not empty InputStream !!!
                jonasClientInputStream = clientFile.getInputStream(jonasClientZipEntry);
            }
        } catch (Exception e) {
            if (clientFile != null) {
                try {
                    clientFile.close();
                } catch (IOException ioe) {
                    //We can't close the file
                }
            }
            throw new ClientContainerDeploymentDescException(
                    "Can not read the XML deployment descriptors of the client jar file '" + clientFileName + "'.", e);
        }

        // load jonas-client deployment descriptor data
        // (META-INF/jonas-client.xml)
        if (jonasClientInputStream != null) {
            jonasClient = loadJonasClient(new InputStreamReader(jonasClientInputStream), JONAS_CLIENT_FILE_NAME);
            try {
                jonasClientInputStream.close();
            } catch (IOException e) {
                // Nothing to do
            }
        } else {
            jonasClient = new JonasClient();
        }

        // Compute Metadatas
        ICarDeployableMetadata carDeployableMetadata = getCarMetadata(clientFileName, classLoaderForCls);


        // Get Holder and complete metadata
        IENCBindingHolder holder = completeClient(carDeployableMetadata,
                                                  applicationClient,
                                                  jonasClient,
                                                  classLoaderForCls);

        // instantiate client deployment descriptor
        ClientContainerDeploymentDesc clientDD = new ClientContainerDeploymentDesc(classLoaderForCls, applicationClient, jonasClient);
        clientDD.setXmlContent(xmlContent);
        clientDD.setJOnASXmlContent(jonasXmlContent);
        clientDD.setENCBindingHolder(holder);

        clientDD.setCarMetadata(carDeployableMetadata);

        return clientDD;
    }

    /**
     * Extract all the metadatas from the application client.
     * @param clientFileName the client
     * @param classLoader the classloader used for the client
     * @return the metadata structure associated to this client
     * @throws ClientContainerDeploymentDescException if an error occured during annotation/XML parsing
     */
    public static ICarDeployableMetadata getCarMetadata(final String clientFileName, final ClassLoader classLoader) throws ClientContainerDeploymentDescException {
        // Analyze metadata of the client

        File clientFile = new File(clientFileName);

        // First, get an archive
        IArchive archive = ArchiveManager.getInstance().getArchive(clientFile);

        ICarDeployableMetadata carDeployableMetadata;
        CARDeployable carDeployable;
        try {
            // Workaround for http://bugs.sun.com/view_bug.do?bug_id=6548436
            carDeployable = CARDeployable.class.cast(DeployableHelper.getDeployable(archive));
            // check if client is uncompressed
            if (!clientFile.isDirectory()) {
                carDeployable = UnpackDeployableHelper.unpack(carDeployable);
                logger.log(BasicLevel.DEBUG, "Unpack an application-client to create metadata");
            }
            carDeployableMetadata = new CarDeployableMetadataFactory().createDeployableMetadata(carDeployable, classLoader);
        } catch (Exception e) {
            throw new ClientContainerDeploymentDescException(e);
        }
        return carDeployableMetadata;
    }

    /**
     * Complete the given applicationClient object and jonasClient by reading annotations.
     * @param carDeployableMetadata given war file to analyze
     * @param applicationClient the web app struct
     * @param jonasClient the jonas webapp struct
     * @param classLoader the classloader used to get the classes
     * @return the binding holder
     * @throws ClientContainerDeploymentDescException if complete is not done
     */
    public static IENCBindingHolder completeClient(final ICarDeployableMetadata carDeployableMetadata,
                                                   final ApplicationClient applicationClient,
                                                   final JonasClient jonasClient,
                                                   final ClassLoader classLoader) throws ClientContainerDeploymentDescException {

        // Complete the standard application-client object with the annotations found
        IENCBindingHolder encBindingHolder = null;
        try {
            encBindingHolder = CarENCBindingBuilder.analyze(carDeployableMetadata);
        } catch (ENCBindingException e) {
            logger.log(BasicLevel.ERROR, "Unable to analyze metadata of '" + carDeployableMetadata + "'", e);
        }

        // Get @PersistenceUnit annotations bindings
        List<IENCBinding<IJavaxPersistenceUnit>> persistenceUnitBindings = encBindingHolder.getPersistenceUnitBindings();
        // Adds each binding as a persistence unit object
        for (IENCBinding<IJavaxPersistenceUnit> persistenceUnitBinding : persistenceUnitBindings) {
            // get object
            IJavaxPersistenceUnit annotationPersistenceUnit = persistenceUnitBinding.getValue();
            PersistenceUnitRef persistenceUnitRef = new PersistenceUnitRef();
            applicationClient.addPersistenceUnitRef(persistenceUnitRef);
            // set the ref name
            persistenceUnitRef.setPersistenceUnitRefName(annotationPersistenceUnit.getName());
            // set the unit name
            persistenceUnitRef.setPersistenceUnitName(annotationPersistenceUnit.getUnitName());
        }

        // Get @Resource annotations bindings
        List<IENCBinding<IJAnnotationResource>> resourcesBindings = encBindingHolder.getResourceBindings();
        // Adds each binding as a resource object
        for (IENCBinding<IJAnnotationResource> resourceBinding : resourcesBindings) {

            // get object
            IJAnnotationResource annotationResource = resourceBinding.getValue();

            // Build a new object and add it  (if not a session context)
            if ("javax.ejb.SessionContext".equals(annotationResource.getType())) {
                continue;
            } else if ("org.omg.CORBA.ORB".equals(annotationResource.getType())) {
                continue;
            } else if ("javax.transaction.UserTransaction".equals(annotationResource.getType())) {
                continue;
            }

            // Already present ?
            String resourceName = resourceBinding.getName();
            if (containsResource(resourceName, applicationClient)) {
                continue;
            }

            String messageDestinationLink = annotationResource.getMessageDestinationLink();

            // Resource ref
            if (messageDestinationLink == null) {
                ResourceRef resourceRef = new ResourceRef();
                applicationClient.addResourceRef(resourceRef);

                // Sets the name
                resourceRef.setResRefName(resourceBinding.getName());

                // Set auth
                Resource.AuthenticationType authType = annotationResource.getAuthenticationType();
                if (authType.equals(Resource.AuthenticationType.CONTAINER)) {
                    resourceRef.setResAuth("Container");
                } else {
                    resourceRef.setResAuth("Application");
                }

                // Sets type
                resourceRef.setResType(annotationResource.getType());

                // if there is a mapped name, add a jonas-resource element
                String mappedName = annotationResource.getMappedName();
                if (mappedName != null) {
                    JonasResource jonasResource = new JonasResource();
                    jonasResource.setResRefName(annotationResource.getName());
                    jonasResource.setJndiName(mappedName);
                    jonasClient.addJonasResource(jonasResource);
                }
            }  else {
                // Message destination ref
                MessageDestinationRef messageDestinationRef = new MessageDestinationRef();
                applicationClient.addMessageDestinationRef(messageDestinationRef);

                // Sets the name
                messageDestinationRef.setMessageDestinationRefName(resourceBinding.getName());

                // Set auth
                messageDestinationRef.setMessageDestinationLink(messageDestinationLink);

                // Sets type
                messageDestinationRef.setMessageDestinationType(annotationResource.getType());

                // if there is a mapped name, add a jonas-resource element
                String mappedName = annotationResource.getMappedName();
                if (mappedName != null) {
                    JonasMessageDestination jonasMessageDestination = new JonasMessageDestination();
                    jonasMessageDestination.setMessageDestinationName(annotationResource.getName());
                    jonasMessageDestination.setJndiName(mappedName);
                    jonasClient.addJonasMessageDestination(jonasMessageDestination);
                }

            }

        }

        // Get @EJB annotations bindings
        List<IENCBinding<IJEjbEJB>> ejbsBindings = encBindingHolder.getEJBBindings();

        // Adds each binding as a resource object
        for (IENCBinding<IJEjbEJB> ejbBinding : ejbsBindings) {
            // Build a new object and add it
            EjbRef ejbRef = new EjbRef();
            applicationClient.addEjbRef(ejbRef);

            // Sets the name
            ejbRef.setEjbRefName(ejbBinding.getName());

            // get object
            IJEjbEJB jEJB = ejbBinding.getValue();

            // if there is a mapped name, add a jonas-resource element
            String mappedName = jEJB.getMappedName();
            if (mappedName != null) {
                JonasEjbRef jonasEjbRef = new JonasEjbRef();
                jonasEjbRef.setEjbRefName(ejbBinding.getName());
                jonasEjbRef.setJndiName(mappedName);
                jonasClient.addJonasEjbRef(jonasEjbRef);
            }
        }

        // Iterates on jonas-service-ref elements to see if there are some values to override
        List<IENCBinding<IJaxwsWebServiceRef>> webServicesBindings = encBindingHolder.getWebServicesBindings();
        for (IENCBinding<IJaxwsWebServiceRef> binding : webServicesBindings) {
            IJaxwsWebServiceRef wsr = binding.getValue();
            JonasServiceRef jsr = findJonasServiceRef(jonasClient.getJonasServiceRefList(), wsr.getName());

             // Found a matching jonas-service-ref
            if (jsr != null) {
                mergeWebServiceRef(jsr, wsr);
            }
        }

        return encBindingHolder;
    }

    /**
     * Load the application-client.xml file.
     * @param reader the reader of the XML file.
     * @param fileName the name of the file (application-client.xml).
     * @return a structure containing the result of the application-client.xml
     *         parsing.
     * @throws DeploymentDescException if the deployment descriptor is
     *         corrupted.
     */
    public static ApplicationClient loadApplicationClient(final Reader reader, final String fileName)
            throws DeploymentDescException {

        ApplicationClient appc = new ApplicationClient();

        // No reader, return now the object
        if (reader == null) {
            return appc;
        }

        // Create if null
        if (appClientDigester == null) {
            // Create and initialize the digester
            appClientDigester = new JDigester(appClientRuleSet, getParsingWithValidation(), true, new AppClientDTDs(),
                    new AppClientSchemas());
        }

        try {
            appClientDigester.parse(reader, fileName, appc);
        } catch (DeploymentDescException e) {
            throw e;
        } finally {
            appClientDigester.push(null);
        }
        return appc;
    }

    /**
     * Load the jonas-client.xml file.
     * @param reader the stream of the XML file.
     * @param fileName the name of the file (jonas-client.xml).
     * @return a structure containing the result of the jonas-client.xml
     *         parsing.
     * @throws DeploymentDescException if the deployment descriptor is
     *         corrupted.
     */
    public static JonasClient loadJonasClient(final Reader reader, final String fileName) throws DeploymentDescException {

        JonasClient jc = new JonasClient();

        // Create if null
        if (jonasAppClientDigester == null) {
            jonasAppClientDigester = new JDigester(jonasAppClientRuleSet, getParsingWithValidation(), true,
                    new JonasAppClientDTDs(), new JonasAppClientSchemas());
        }

        try {
            jonasAppClientDigester.parse(reader, fileName, jc);

        } catch (DeploymentDescException e) {
            throw e;
        } finally {
            jonasAppClientDigester.push(null);
        }
        return jc;
    }

    /**
     * Return the JNDI name from the ejbLink string. ejbLink format :
     * filename.jar#beanName in the same Ear File
     * @param clientURL the url of the jar being parsed. This is needed because
     *        ejbLink is relative. With the url and the ejbLink, we can know
     *        where the file is locate.
     * @param ejbLink the ejbLink tag of an ejb-ref.
     * @param earLoader the classloader of the ear.
     * @param ejbType the type of the referenced ejb in the ejb-ref tag.
     * @return the JNDI name if found, null otherwise
     * @throws DeploymentDescException when it failed
     */
    private String getJndiName(final URL clientURL, final String ejbLink, final ClassLoader earLoader, final String ejbType)
            throws DeploymentDescException {
        // Now ask EJB deployment Desc manager
        // Last arg is always true as it is always ejb ref and not local ref from a client
        return ejbDDManager.getJndiName(clientURL, ejbLink, earLoader, ejbType, null, true);
    }

    /**
     * Make a cleanup of the cache of deployment descriptor. This method must
     * Return the JNDI name from the mdLink string. mdLink format :
     * filename.jar#mdName in the same Ear File
     * @param clientURL the url of the jar being parsed. This is needed because
     *        mdLink is relative. With the url and the mdLink, we can know where
     *        the file is locate.
     * @param mdLink the mdLink tag of a message-destination-ref
     * @param mdType the type of the referenced mdb in the
     *        message-destination-ref tag.
     * @param mdUsage the usage of the referenced mdb in the
     *        message-destination-ref tag.
     * @param earLoader the classloader of the ear.
     * @return the JNDI name if found, null otherwise
     * @throws ClientContainerDeploymentDescException when it failed
     */
    private String getMDJndiName(final URL clientURL, final String mdLink, final String mdType, final String mdUsage, final ClassLoader earLoader)
            throws ClientContainerDeploymentDescException {

        // Extract from the mdb link
        //   - the name of the file
        //   - the name of the destination
        String ejbJarLink = null;
        String destNameLink = null;
        DeploymentDesc dd = null;

        JonasMessageDestination md = null;
        // contains ejb-jar name
        if (mdLink.indexOf("#") > 0) {
            ejbJarLink = mdLink.split("#")[0];
            destNameLink = mdLink.split("#")[1];
        } else {
            // ask for any jar
            destNameLink = mdLink;
        }
        if (ejbJarLink != null) {
            //Check if ejbJarLink is a jar or not
            if (!ejbJarLink.endsWith(".jar")) {
                String err = "Ejbjar filename " + ejbJarLink + " from the message-destination-link " + mdLink
                        + " has a bad format. Correct format :  filename.jar";
                throw new ClientContainerDeploymentDescException(err);
            }
            // Now construct the URL from the absolute path from the url clientURL
            // and
            // the relative path from ejbJarLink
            URL ejbJarLinkUrl = null;
            try {
                ejbJarLinkUrl = new File(new File(clientURL.getFile()).getParent() + File.separator + ejbJarLink)
                        .getCanonicalFile().toURL();
            } catch (MalformedURLException mue) {
                String err = "Error when creating an url for the ejb jar filename. Error :" + mue.getMessage();
                throw new ClientContainerDeploymentDescException(err);
            } catch (IOException ioe) {
                String err = "Error when creating/accessing a file. Error :" + ioe.getMessage();
                throw new ClientContainerDeploymentDescException(err);
            }

            // Check if the jar exist.
            if (!new File(ejbJarLinkUrl.getFile()).exists()) {
                String err = "Cannot get the deployment descriptor for '" + ejbJarLinkUrl.getFile()
                        + "'. The file doesn't exist.";
                throw new ClientContainerDeploymentDescException(err);
            }

            // We've got the url
            //   Now, We can ask the Deployment Descriptor of this url
            URL[] ddURL = new URL[1];
            ddURL[0] = ejbJarLinkUrl;
            URLClassLoader loaderForClsEjb = new URLClassLoader(ddURL, earLoader);
            try {
                dd = ejbDDManager.getDeploymentDesc(ejbJarLinkUrl, loaderForClsEjb, earLoader);
            } catch (DeploymentDescException e) {
                String err = "Cannot get the deployment descriptor for '" + ejbJarLinkUrl.getFile() + "'.";
                throw new ClientContainerDeploymentDescException(err, e);
            }

            md = dd.getJonasMessageDestination(mdLink);
        }
        if (md == null) {

            String err = "No message-destination-link was found for '" + mdLink + "' in the file "
                    + clientURL.getFile() + " specified.";
            ClientContainerDeploymentDescException e = new ClientContainerDeploymentDescException(err);

            // Not found, try with the remote resolver

            // Was not able to get a JNDI name, try with the remote object.
            EZBRemoteJNDIResolver jndiResolver = null;

            // Get object
            Object o = null;
            try {
                o = new InitialContext().lookup("EZB_Remote_JNDIResolver");
            } catch (NamingException ne) {
                // No Remote JNDI resolver, so throw first exception
                logger.log(BasicLevel.DEBUG, "No Remote EJB3 JNDI Resolver found");
                throw e;
            }

            // Object is here, so we can cast it
            jndiResolver = (EZBRemoteJNDIResolver) PortableRemoteObject.narrow(o, EZBRemoteJNDIResolver.class);

            // Ask the Resolver
            List<EZBJNDIData> jndiDataList = null;
            try {
                jndiDataList = jndiResolver.getMessageDestinationJNDINames(destNameLink);
            } catch (RemoteException re) {
                // No Remote JNDI resolver, so throw first exception
                throw new ClientContainerDeploymentDescException("Unable to get EJB-LINK for destination '" + destNameLink + "'", re);
            }

            // Data is here, check if it is empty or not
            if (jndiDataList.size() == 0) {
                throw new ClientContainerDeploymentDescException("Unable to get EJB-LINK for destination '" + destNameLink + "'");
            } else if (jndiDataList.size() > 1) {
                // too many entries
                throw new ClientContainerDeploymentDescException("Unable to get EJB-LINK for destination '" + destNameLink + "', too many answers : '" + jndiDataList + "'.");
            }

            // Only one item found, so get JNDI Name from this object
            EZBJNDIData jndiData = jndiDataList.get(0);
            // Update JNDI name
            return jndiData.getName();

        }

        //Check if the type & usage of the message-destination-ref is correct.
        //For now checkTypeUsage(clientURL, mdType, mdUsage, dd);

        return md.getJndiName();
    }

    /**
     * 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) {
        //Remove the altdd mapping
        earCLAltDDBindings.remove(earClassLoader);

        //Then remove the cache of the ejb dd manager
        ejbDDManager.removeCache(earClassLoader);
    }

    /**
     * Set the alt deployment desc which are used instead of the web.xml file
     * which is in the war file. The alt-dd tag is in the application.xml file
     * of the ear files and is used ony in the EAR case. ie : deployment of wars
     * packaged into EAR applications. alt-dd tag is optionnal
     * @param earClassLoader the ear classloader which is used for mapped the
     *        URLs of the wars to the Alt dd.
     * @param urls the urls of the wars
     * @param altDDs the alt-dd name for the specified war URLs
     */
    public void setAltDD(final ClassLoader earClassLoader, final URL[] urls, final URL[] altDDs) {

        //Associate an url to a altDD url
        Hashtable urlAltddBindings = new Hashtable();

        //Fill the hashtable for each url
        for (int i = 0; i < urls.length; i++) {
            if (altDDs[i] != null) {
                urlAltddBindings.put(urls[i], altDDs[i]);
            }
        }

        //Bind the hashtable
        earCLAltDDBindings.put(earClassLoader, urlAltddBindings);

    }

    /**
     * Get the size of the cache (number of entries in the cache). This method
     * is used only for the tests.
     * @return the size of the cache (number of entries in the cache).
     */
    public int getCacheSize() {
        int bufferSize = 0;

        Enumeration keys = earCLAltDDBindings.keys();
        while (keys.hasMoreElements()) {
            ClassLoader loader = (ClassLoader) keys.nextElement();
            Hashtable hashtab = (Hashtable) earCLAltDDBindings.get(loader);
            bufferSize = bufferSize + hashtab.size();
        }

        return bufferSize;
    }

    /**
     * Controls whether the parser is reporting all validity errors.
     * @return if true, all external entities will be read.
     */
    public static boolean getParsingWithValidation() {
        return parsingWithValidation;
    }

    /**
     * Controls whether the parser is reporting all validity errors.
     * @param validation if true, all external entities will be read.
     */
    public static void setParsingWithValidation(final boolean validation) {
        ClientDeploymentDescManager.parsingWithValidation = validation;
    }


}
