/**
 * JOnAS : Java(TM) OpenSource Application Server
 * Copyright (C) 1999-2004 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
 *
 * --------------------------------------------------------------------------
 * $Id:WebApp.java 10528 2007-06-05 08:26:23Z sauthieg $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.generators.genbase.archive;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.jar.JarFile;

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.common.DeploymentDescException;
import org.ow2.jonas.deployment.web.WebContainerDeploymentDesc;
import org.ow2.jonas.deployment.web.lib.WebDeploymentDescManager;
import org.ow2.jonas.deployment.ws.WSDeploymentDesc;
import org.ow2.jonas.deployment.ws.lib.WSDeploymentDescManager;
import org.ow2.jonas.generators.genbase.GenBaseException;
import org.ow2.jonas.generators.genbase.utils.TempRepository;
import org.ow2.jonas.generators.genbase.utils.XMLUtils;
import org.ow2.jonas.lib.loader.WebappClassLoader;
import org.ow2.util.file.FileUtils;
import org.ow2.util.file.FileUtilsException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

/**
 * A <code>WebApp</code> is a wrapper class around a Web Archive.
 *
 * @author Guillaume Sauthier
 */
public class WebApp extends J2EEArchive implements EjbRefModule, WsClient, WsEndpoint {

    /** Application containing the webapp */
    private Application app = null;

    /** webapp archive filename */
    private String webFilename;

    /** Web deployment descriptor */
    private WebContainerDeploymentDesc webDD;

    /** WebServices deployment descriptor */
    private WSDeploymentDesc wsDD = null;

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

    /** web-app */
    private Document webApp = null;

    /** jonas-web-app */
    private Document jonasWebApp = null;

    /** webservices */
    private Document webservices = null;

    /** jonas-webservices */
    private Document jonasWebservices = null;

    /** context */
    private Document context = null;

    /** webjetty */
    private Document webjetty = null;

    /** web descriptors */
    private Map descriptors;

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

    /**
     * Create an alone WebApp (not in an ear).
     *
     * @param archive file archive
     *
     * @throws GenBaseException When Init fails
     */
    public WebApp(final Archive archive) throws GenBaseException {
        super(archive);
        webFilename = archive.getName();
        if (getLogger().isLoggable(BasicLevel.DEBUG)) {
            getLogger().log(BasicLevel.DEBUG, "Wrapping '" + archive.getName() + "' in WebApp");
        }
        init();
    }

    /**
     * Create an embded WebApp.
     *
     * @param archive Web Archive
     * @param app container application
     *
     * @throws GenBaseException When init fails
     */
    public WebApp(final Archive archive, final Application app) throws GenBaseException {
        super(archive);
        webFilename = archive.getName();
        setApplication(app);
        if (getLogger().isLoggable(BasicLevel.DEBUG)) {
            getLogger().log(BasicLevel.DEBUG, "Wrapping '" + archive.getName() + "' in WebApp");
        }
        init();
    }

    /**
     * Initialize the WebApp module.
     *
     * @throws GenBaseException When classloader cannot be created or when
     *         decriptor loading fails.
     */
    private void init() throws GenBaseException {
        // load Deployment Descs
        loadDescriptors();
    }

    /**
     * Load Deployment Descriptor of a WebApp.
     *
     * @throws GenBaseException When descriptor cannot be parsed
     */
    private void loadDescriptors() throws GenBaseException {
        try {

            // Optional
            InputStream webIs = getWebInputStream();
            if (webIs != null) {
                webApp = XMLUtils.newDocument(webIs, "WEB-INF/web.xml", isDTDsAllowed());
            }

            // jonas-web.xml (optionnal)
            InputStream is = getJonasWebInputStream();

            if (is != null) {
                jonasWebApp = XMLUtils.newDocument(is, "WEB-INF/jonas-web.xml", isDTDsAllowed());
            }

            // webservices.xml (optionnal)
            InputStream isWS = getWebservicesInputStream();

            if (isWS != null) {
                webservices = XMLUtils.newDocument(isWS, "WEB-INF/webservices.xml", isDTDsAllowed());
            }

            // jonas-webservices.xml (optionnal)
            InputStream isJWS = getJonasWebservicesInputStream();

            if (isJWS != null) {
                jonasWebservices = XMLUtils.newDocument(isJWS, "WEB-INF/jonas-webservices.xml", isDTDsAllowed());
            }

            // context.xml (optional)
            InputStream isContext = getContextInputStream();
            if (isContext != null) {
                context = XMLUtils.newDocument(isContext, "META-INF/context.xml", isDTDsAllowed(), false);
            }

            //web-jetty.xml (optional)
            InputStream isWebJetty = getWebJettyInputStream();
            if (isWebJetty != null) {
                // Jetty needs web-jetty.xml to have a dtd
                webjetty = XMLUtils.newDocument(isWebJetty, "WEB-INF/web-jetty.xml", true, true);
            }

        } catch (SAXException saxe) {
            String err = getI18n().getMessage("WebApp.loadDescriptors.parseError");
            throw new GenBaseException(err, saxe);
        } catch (ParserConfigurationException pce) {
            String err = getI18n().getMessage("WebApp.loadDescriptors.prepare");
            throw new GenBaseException(err, pce);
        } catch (IOException ioe) {
            String err = getI18n().getMessage("WebApp.loadDescriptors.parseError");
            throw new GenBaseException(err, ioe);
        }

        descriptors = new Hashtable();
        if (webApp != null) {
            descriptors.put("WEB-INF/web.xml", webApp);
        }

        if (jonasWebApp != null) {
            descriptors.put("WEB-INF/jonas-web.xml", jonasWebApp);
        }

        if (webservices != null) {
            descriptors.put("WEB-INF/webservices.xml", webservices);
        }

        if (jonasWebservices != null) {
            descriptors.put("WEB-INF/jonas-webservices.xml", jonasWebservices);
        }

        if (context != null) {
            descriptors.put("META-INF/context.xml", context);
        }

        if (webjetty != null) {
            descriptors.put("WEB-INF/web-jetty.xml", webjetty);
        }
    }

    /**
     * Returns the name of the Archive. Overrides J2EEArchive.getName();
     *
     * @see org.ow2.jonas.generators.genbase.archive.J2EEArchive#getName()
     *
     * @return the name of the Archive.
     */
    @Override
    public String getName() {
        return webFilename;
    }

    /**
     * Set the container application.
     *
     * @param app the container application.
     */
    public void setApplication(final 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;
    }

    /**
     * Returns the list of webservice-description elements contained by a
     * module.
     *
     * @return the list of webservice-description elements contained by a
     *         module.
     */
    public List getServiceDescs() {
        if (wsDD != null) {
            return wsDD.getServiceDescs();
        } else {
            return new Vector();
        }
    }

    /**
     * Add Archive classes.
     *
     * @param classes root directory containing classes.
     */
    public void addClasses(final File classes) {
        addDirectoryIn("WEB-INF/classes", classes);
    }

    /**
     * Returns the Document of the web.xml file.
     *
     * @return the Document of the web.xml file.
     */
    public Document getWebAppDoc() {
        return webApp;
    }

    /**
     * Returns the Document of the jonas-web.xml file.
     *
     * @return the Document of the jonas-web.xml file.
     */
    public Document getJonasWebAppDoc() {
        return jonasWebApp;
    }

    /**
     * Returns the Document of the webservices.xml file.
     *
     * @return the Document of the webservices.xml file.
     */
    public Document getWebservicesDoc() {
        return webservices;
    }

    /**
     * Returns the Document of the jonas-webservices.xml file.
     *
     * @return the Document of the jonas-webservices.xml file.
     */
    public Document getJonasWebservicesDoc() {
        return jonasWebservices;
    }

    /**
     * Returns a new Document of the context.xml file
     *
     * @return the Document of the context.xml file.
     * @throws GenBaseException if context.xml Document cannot be produced.
     */
    public Document newContextDoc() throws GenBaseException {

        // Do not validate context.xml as there is no associated DTD or XML Schema
        context = loadXMLDescriptorAsResource("context.xml", "META-INF/context.xml", false);

        return context;
    }

    /**
     * Returns a new Document of the web-jetty.xml file
     *
     * @return the document of the web-jetty.xml file
     * @throws GenBaseException if context.xml Document cannot be produced.
     */
    public Document newWebJettyDoc() throws GenBaseException {

        webjetty = loadXMLDescriptorAsResource("web-jetty.xml", "WEB-INF/web-jetty.xml");

        return webjetty;
    }

    /**
     * Load and validate an XML {@link Document} as a resource.
     * @param resource resource's name (used to load from the classloader)
     * @param name name of the resource in the final web-app
     * @return Returns an XML {@link Document} representing the given resource.
     * @throws GenBaseException If unable to load the specified resource from the classloader.
     */
    private Document loadXMLDescriptorAsResource(final String resource, final String name) throws GenBaseException {

        return loadXMLDescriptorAsResource(resource, name, true);
    }

    /**
     * Load and validate (if required) an XML {@link Document} as a resource.
     * @param resource resource's name (used to load from the classloader)
     * @param name name of the resource in the final web-app
     * @param validate Indicate if the {@link Document} should be validated.
     * @return Returns an XML {@link Document} representing the given resource.
     * @throws GenBaseException If unable to load the specified resource from the classloader.
     */
    private Document loadXMLDescriptorAsResource(final String resource,
                                                 final String name,
                                                 final boolean validate) throws GenBaseException {
        Document doc = null;
        try {
            doc = XMLUtils.newDocument(this.getClass().getResourceAsStream(resource), name, validate);
        } catch (Exception e) {
            throw new GenBaseException("Cannot load '" + name + "'", e);
        }

        // save the descriptor in the descriptor Map
        descriptors.put(name, doc);

        return doc;
    }

    /**
     * Returns the Document of the context.xml file
     *
     * @return the Document of the context.xml file.
     */
    public Document getContextDoc() {
        return context;
    }

    /**
     * Returns the Document of the web-=jetty.xml file
     *
     * @return the document of the web-jetty.xml file
     */
    public Document getWebJettyDoc() {
        return webjetty;
    }

    /**
     * Returns the InputStream of the web.xml file.
     *
     * @return the InputStream of the web.xml file.
     *
     * @throws IOException When InputStream cannot be returned.
     */
    private InputStream getWebInputStream() throws IOException {
        InputStream is = null;

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

        return is;
    }

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

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

        return is;
    }

    /**
     * Returns the InputStream of the webservices.xml file.
     *
     * @return the InputStream of the webservices.xml file.
     *
     * @throws IOException When InputStream cannot be returned.
     */
    private InputStream getWebservicesInputStream() throws IOException {
        InputStream is = null;

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

        return is;
    }

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

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

        return is;
    }

    /**
     * Returns the InputStream of the context.xml file.
     *
     * @return the InputStream of the context.xml file.
     * @throws IOException When InputStream cannot be returned
     */
    private InputStream getContextInputStream() throws IOException {
        InputStream is = null;
        if (isPacked()) {
            is = getInputStream("META-INF/context.xml");
        } else {
            is = getInputStream("META-INF" + File.separator + "context.xml");
        }

        return is;
    }

    /**
     * Returns the InputStream of the web-jetty.xml file
     *
     * @return the InputStream of the web-jetty.xml file
     * @throws IOException When InputStream cannot be returned
     */
    private InputStream getWebJettyInputStream() throws IOException {
        InputStream is = null;
        if (isPacked()) {
            is = getInputStream("WEB-INF/web-jetty.xml");
        } else {
            is = getInputStream("WEB-INF" + File.separator + "web-jetty.xml");
        }

        return is;
    }

    /**
     * Returns a Map of name to Document for each modified Descriptor of the
     * archive.
     *
     * @return a Map of name to Document
     */
    @Override
    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.
     */
    @Override
    public boolean omit(final String name) {
        return (name.equals("WEB-INF/web.xml") || name.equals("WEB-INF\\web.xml")
                || name.equals("WEB-INF/jonas-web.xml") || name.equals("WEB-INF\\jonas-web.xml")
                || name.equals("WEB-INF/webservices.xml") || name.equals("WEB-INF\\webservices.xml")
                || name.equals("WEB-INF/jonas-webservices.xml") || name.equals("WEB-INF\\jonas-webservices.xml")
                || name.equals("META-INF/context.xml") || name.equals("META-INF\\context.xml")
                || name.equals("WEB-INF/web-jetty.xml") || name.equals("WEB-INF\\web-jetty.xml"));
    }

    /**
     * Initialize the Archive.
     * @throws GenBaseException When initialization fails.
     */
    @Override
    public void initialize() throws GenBaseException {
        File webappUnpackDir = getRootFile();
        try {

            if (getArchive().isPacked()) {
                JarFile jf = new JarFile(getRootFile());
                TempRepository tr = TempRepository.getInstance();
                webappUnpackDir = tr.createDir(".war");
                FileUtils.unpack(jf, webappUnpackDir);
                jf.close();
                setArchive(new FileArchive(webappUnpackDir));
            }

            if (app == null) {
                // simple webapp case
                setModuleClassloader(new WebappClassLoader(webappUnpackDir.toURL(), Thread.currentThread()
                        .getContextClassLoader()));
            } else {
                // embedded webapp case
                setModuleClassloader(new WebappClassLoader(webappUnpackDir.toURL(), app.getEJBClassLoader()));
            }
        } catch (IOException ioe) {
            String err = getI18n().getMessage("WebApp.init.loader", getArchive().getRootFile());
            throw new GenBaseException(err, ioe);
        } catch (FileUtilsException fue) {
            String err = getI18n().getMessage("WebApp.init.loader", getArchive().getRootFile());
            throw new GenBaseException(err, fue);
        }

        try {
            webDD = WebDeploymentDescManager.getDeploymentDesc(webappUnpackDir.getAbsolutePath(), getModuleClassloader());
        } catch (DeploymentDescException dde) {
            throw new GenBaseException(dde);
        }

        try {
            wsDD = WSDeploymentDescManager.getDeploymentDesc(webappUnpackDir.getAbsolutePath(), getModuleClassloader());
        } catch (DeploymentDescException dde) {
            throw new GenBaseException(dde);
        }

        // we want a List of service-ref
        sRefs = new Vector();

        IServiceRefDesc[] refs = webDD.getServiceRefDesc();

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

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

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

    }

    /**
     * @return Returns the context-root to use for this group of Services.
     */
    public String getContextRoot() {
        return this.wsDD.getContextRoot();
    }

    /**
     * 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
     */
    @Override
    public void close() {
        sRefs = null;
        ejbRefs = null;
        webDD = null;
        wsDD = null;
        webApp = null;
        app = null;
        descriptors = null;
        jonasWebApp = null;
        webFilename = null;
        webservices = null;
        jonasWebservices = null;
        context = null;
        webjetty = null;
    }
}