/**
 * JOnAS : Java(TM) OpenSource Application Server
 * Copyright (C) 1999-2007 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.generators.genbase.archive;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.jar.Attributes;

import javax.xml.parsers.ParserConfigurationException;



import org.objectweb.util.monolog.api.BasicLevel;

import org.ow2.jonas.deployment.api.IEJBRefDesc;
import org.ow2.jonas.deployment.api.IServiceRefDesc;
import org.ow2.jonas.deployment.client.ClientContainerDeploymentDesc;
import org.ow2.jonas.deployment.client.lib.ClientDeploymentDescManager;
import org.ow2.jonas.deployment.common.DeploymentDescException;
import org.ow2.jonas.generators.genbase.GenBaseException;
import org.ow2.jonas.generators.genbase.utils.XMLUtils;
import org.ow2.jonas.lib.loader.ClientClassLoader;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

/**
 * Client represnets a J2EE Client Archive.
 *
 * @author Guillaume Sauthier
 */
public class Client extends J2EEArchive implements EjbRefModule, WsClient {

    /** Container application (can be null) */
    private Application app = null;

    /** Client Deployment Desc */
    private ClientContainerDeploymentDesc clientDD = null;

    /** service-ref list */
    private List sRefs;

    /**
     * ejb-ref list
     */
    private List ejbRefs;

    /** jonas client descriptors */
    private Map descriptors;

    /**
     * jonas-client.xml
     */
    private Document jclientDoc;

    /**
     * Create a new Client not contained in Application
     *
     * @param archive the archive file
     *
     * @throws GenBaseException When Init fails.
     */
    public Client(Archive archive) throws GenBaseException {
        super(archive);
        if (getLogger().isLoggable(BasicLevel.DEBUG)) {
            getLogger().log(BasicLevel.DEBUG, "Wrapping '" + archive.getName() + "' in Client");
        }
        init();
    }

    /**
     * Create a new Client contained in Application
     *
     * @param archive the archive file
     * @param app the container Application
     *
     * @throws GenBaseException When Init fails
     */
    public Client(Archive archive, Application app) throws GenBaseException {
        super(archive);
        setApplication(app);
        if (getLogger().isLoggable(BasicLevel.DEBUG)) {
            getLogger().log(BasicLevel.DEBUG, "Wrapping '" + archive.getName() + "' in Client");
        }
        init();
    }

    /**
     * Initialize the Client module.
     *
     * @throws GenBaseException When client classloader cannot be created or when
     *         client deployment desc cannot be parsed.
     */
    private void init() throws GenBaseException {
        loadDescriptors();
    }

    /**
     * Load Deployment Descriptor of a Client.
     *
     * @throws GenBaseException when jonas-client.xml cannot be parsed
     */
    private void loadDescriptors() throws GenBaseException {

        descriptors = new Hashtable();
        InputStream jcis = null;
        try {
            jcis = getJonasClientInputStream();
            if (jcis != null) {
                jclientDoc = XMLUtils.newDocument(jcis, "META-INF/jonas-client.xml", isDTDsAllowed());
                descriptors.put("META-INF/jonas-client.xml", jclientDoc);
            }
        } catch (SAXException saxe) {
            String err = getI18n().getMessage("Client.loadDescriptors.parseError");
            throw new GenBaseException(err, saxe);
        } catch (ParserConfigurationException pce) {
            String err = getI18n().getMessage("Client.loadDescriptors.prepare");
            throw new GenBaseException(err, pce);
        } catch (IOException ioe) {
            String err = getI18n().getMessage("Client.loadDescriptors.parseError");
            throw new GenBaseException(err, ioe);
        }
    }

    /**
     * Set the container application.
     *
     * @param app the container application.
     */
    public void setApplication(Application app) {
        this.app = app;
    }

    /**
     * Returns the container application (can be null).
     *
     * @return the container application (can be null).
     */
    public Application getApplication() {
        return app;
    }

    /**
     * Returns the list of service-ref elements contained by a module.
     *
     * @return the list of service-ref elements contained by a module.
     */
    public List getServiceRefDescs() {
        return sRefs;
    }

    /**
     * add *.class from directory in the archive.
     *
     * @param classes directory with classes.
     */
    public void addClasses(File classes) {
        addDirectory(classes);
    }

    /**
     * Returns a Map of name to Document for each modified Descriptor of the
     * archive.
     *
     * @return a Map of name to Document
     */
    public Map getDescriptors() {
        return descriptors;
    }

    /**
     * Returns true if filename must be omitted in the archive.
     *
     * @param name filename to be tested
     *
     * @return true if filename must be omitted.
     */
    public boolean omit(String name) {
        return (name.equals("META-INF/jonas-client.xml") || name.equals("META-INF\\jonas-client.xml"));
    }

    /**
     * Returns the Document of the jonas-client.xml file.
     *
     * @return the Document of the jonas-client.xml file.
     */
    public Document getJonasClientDoc() {
        return jclientDoc;
    }

    /**
     * Returns the InputStream of the jonas-client.xml file.
     *
     * @return the InputStream of the jonas-client.xml file.
     *
     * @throws IOException When InputStream of jonas-client.xml cannot be
     *         returned.
     */
    public InputStream getJonasClientInputStream() throws IOException {
        InputStream is = null;

        if (isPacked()) {
            is = getInputStream("META-INF/jonas-client.xml");
        } else {
            is = getInputStream("META-INF" + File.separator + "jonas-client.xml");
        }

        return is;
    }

    /**
     * Initialize the Archive.
     * @throws GenBaseException When initialization fails.
     */
    public void initialize() throws GenBaseException {
        // if client in application, clientClassLoader includes ejbClassLoader too

        try {
            if (app == null) {
                // simple client case
                setModuleClassloader(new ClientClassLoader(getArchive().getRootFile().toURL(), Thread.currentThread()
                        .getContextClassLoader()));
            } else {
                // embedded client case
                setModuleClassloader(createClientClassLoaderWithinApplication(getApplication().getEJBClassLoader()));
            }
        } catch (IOException ioe) {
            String err = getI18n().getMessage("Client.init.loader", getArchive().getRootFile());
            throw new GenBaseException(err, ioe);
        }

        try {
            clientDD = ClientDeploymentDescManager.getInstance(getRootFile().getAbsolutePath(), getModuleClassloader());
        } catch (DeploymentDescException dde) {
            throw new GenBaseException(dde);
        }

        // we want a List of service-ref
        sRefs = new Vector();
        IServiceRefDesc[] refs = clientDD.getServiceRefDesc();
        for (int i = 0; i < refs.length; i++) {
            sRefs.add(refs[i]);
        }

        // List of ejb-refs
        ejbRefs = new Vector();
        IEJBRefDesc[] refDesc = clientDD.getEjbRefDesc();

        for (int i = 0; i < refDesc.length; i++) {
            ejbRefs.add(refDesc[i]);
        }

    }

    private ClientClassLoader createClientClassLoaderWithinApplication(ClassLoader parent) throws GenBaseException {

        // get Manifest Attributes if any
        String classpath = getManifest().getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
        // Minimal array siez: 1 item, the client jar file itself
        URL[] urls = new URL[1];
        if (classpath != null) {
            // Lookup specified files.
            String[] paths = classpath.split(" ");
            urls = new URL[paths.length + 1];
            for (int i = 0; i < paths.length; i++) {
                try {
                    URL path = new File(app.getRootFile(), paths[i]).toURL();
                    urls[i] = path;
                } catch (IOException ioe) {
                    String err = "Cannot transform '" + paths[i] + "' as a URL";
                    throw new GenBaseException(err, ioe);
                }
            }
        }


        ClientClassLoader ccl = null;
        try {
            urls[urls.length - 1] = getArchive().getRootFile().toURL();
            ccl = new ClientClassLoader(urls, parent);
        } catch (IOException e) {
            String err = "Cannot create Client ClassLoader for " + getArchive().getName();
            throw new GenBaseException(err, e);
        }

        return ccl;
    }

    /**
     * Returns the list of ejb-ref elements contained by a module.
     * @return the list of ejb-ref elements contained by a module.
     */
    public List getEjbRefDescs() {
        return ejbRefs;
    }

    /**
     * Close this archive
     */
    public void close() {
        super.close();
        ejbRefs = null;
        clientDD = null;
        app = null;
        descriptors = null;
        jclientDoc = null;
    }
}