package org.apache.maven.j2ee;

/* ====================================================================
 *   Copyright 2001-2004 The Apache Software Foundation.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 * ====================================================================
 */

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.maven.j2ee.war.FormLoginConfig;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

/**
 * Represents a J2EE War File
 *
 * @author <a href="mailto:dion@multitask.com.au">dIon Gillard</a>
 * @version $Id$
 */
public class WarFile extends JarFile
{

    /** web.xml entry in the war */
    public static final String WEB_XML = "WEB-INF/web.xml";
    /** lib entry in the war for jar files */
    public static final String LIB = "WEB-INF/lib/";
    
    /** 
     * Creates a new instance of WarFile
     * 
     * @param name the {@link File file} name of a war file
     * @throws IOException when an I/O error occurs
     */
    public WarFile(String name) throws IOException
    {
        super(name);
    }
    
    /** 
     * Creates a new instance of WarFile
     * 
     * @param name the {@link File file} name of a war file
     * @param verify whether or not to verify the war file if it is signed
     * @throws IOException when an I/O error occurs
     */
    public WarFile(String name, boolean verify) throws IOException
    {
        super(name, verify);
    }
    
    /** 
     * Creates a new instance of WarFile
     * 
     * @param warFile a J2EE .war {@link File file}
     * @throws IOException when an I/O error occurs
     */
    public WarFile(File warFile) throws IOException
    {
        super(warFile);
    }

    /** 
     * Creates a new instance of WarFile
     * 
     * @param warFile a J2EE .war {@link File file}
     * @param verify whether or not to verify the war file if it is signed
     * @throws IOException when an I/O error occurs
     */
    public WarFile(File warFile, boolean verify) throws IOException
    {
        super(warFile, verify);
    }

    /** 
     * Creates a new instance of WarFile
     * 
     * @param warFile a J2EE .war {@link File file}
     * @param verify whether or not to verify the war file if it is signed
     * @param mode the mode in which the file is to be opened
     * @throws IOException when an I/O error occurs
     */
    public WarFile(File warFile, boolean verify, int mode) throws IOException
    {
        super(warFile, verify, mode);
    }

    /**
     * Retrieves the WEB-INF/web.xml entry if it exists.
     * 
     * @return a {@link JarEntry} for web.xml
     */
    public JarEntry getWebXmlEntry()
    {
        return getJarEntry(WarFile.WEB_XML);
    }
    
    /**
     * Get a map of servlet name -> servlet class. The map has a size of zero
     * if there are no servlets or no web.xml in the war.
     * 
     * @return a map of servlets held in the war.
     * @throws IOException if there are problems reading from the war
     */
    public Map getServlets() throws IOException
    {
        Map servlets = new HashMap();
        if (getWebXmlEntry() != null)
        {
            Document webXml = getWebXml();
            List servletNodes = webXml.selectNodes("//servlet");
            Node node = null;
            for (Iterator nodes = servletNodes.iterator(); nodes.hasNext();)
            {
                node = (Node) nodes.next();
                String servletName = node.selectSingleNode("./servlet-name")
                    .getText();
                Node servletClass = node.selectSingleNode("./servlet-class");
                if (servletClass != null)
                {
                    servlets.put(servletName, servletClass.getText());
                }
            }
        }
        return servlets;
    }

    /**
     * Get a map of servlet name -> jsp file. The map has a size of zero
     * if there are no jsp files defined or no web.xml in the war.
     * 
     * @return a map of jsps defined using the <code>&lt;servlet&gt;</code> tag
     *      held in the war.
     * @throws IOException if there are problems reading from the war
     */
    public Map getJSPs() throws IOException
    {
        Map jsps = new HashMap();
        if (getWebXmlEntry() != null)
        {
            Document webXml = getWebXml();
            List servletNodes = webXml.selectNodes("//servlet");
            Node node = null;
            for (Iterator nodes = servletNodes.iterator(); nodes.hasNext();)
            {
                node = (Node) nodes.next();
                String servletName = node.selectSingleNode("./servlet-name")
                    .getText();
                Node jspFile = node.selectSingleNode("./jsp-file");
                if (jspFile != null)
                {
                    jsps.put(servletName, jspFile.getText());
                }
            }
        }

        return jsps;
    }
    
    /** 
     * Get a map of taglib-uri -> taglib-location. The map has zero size
     * if there are no taglibs defined or no web.xml in the war
     * 
     * @return a map of uri to location
     * @throws IOException when an I/O error occurs reading the war
     */
    public Map getTaglibs() throws IOException
    {
        Map taglibs = new HashMap();
        if (getWebXmlEntry() != null)
        {
            Document webXml = getWebXml();
            List taglibNodes = webXml.selectNodes("//taglib");
            Node node = null;
            for (Iterator nodes = taglibNodes.iterator(); nodes.hasNext();)
            {
                node = (Node) nodes.next();
                String taglibUri = node.selectSingleNode("./taglib-uri")
                    .getText();
                String taglibLocation = node.selectSingleNode(
                    "./taglib-location").getText();
                taglibs.put(taglibUri, taglibLocation);
            }
        }
        return taglibs;
    }
    
    /**
     * Get the web.xml back as a dom4j Document, for easier processing
     * 
     * @return a {@link Document} representing the web.xml
     * @throws IOException if there are any issues reading the web.xml
     * or producing the xml document
     */
    protected Document getWebXml() throws IOException
    {
        if (getWebXmlEntry() == null)
        {
            throw new IOException("Attempted to get non-existent web.xml");
        }
        try 
        {
            SAXReader xmlReader = new SAXReader(false);
            xmlReader.setEntityResolver(new J2EEEntityResolver());
            InputStream webXmlStream = getInputStream(getWebXmlEntry());
            Document webXml = xmlReader.read(webXmlStream);
            return webXml;
        }
        catch (DocumentException de)
        {
            de.printStackTrace();
            throw new IOException(de.getMessage());
        }
    }
    
    /**
     * Provide a set of jar files as found in WEB-INF/lib
     * 
     * @return a set of jar files as {@link JarEntry entries} from WEB-INF/lib
     */
    public Set getLibEntries()
    {
        Set libs = new HashSet();
        Enumeration entries = entries();
        JarEntry entry = null;
        while (entries.hasMoreElements())
        {
            entry = (JarEntry) entries.nextElement();
            if (entry.getName().startsWith("WEB-INF/lib/")
                && entry.getName().toLowerCase().endsWith(".jar"))
            { 
                libs.add(entry);
            }
        }
        return libs;
    }
    
    /**
     * Extract the given {@link JarEntry entry} to a temporary file
     * 
     * @param entry a previously retrieved entry from the jar file
     * @return the {@link File} created 
     * @throws IOException when an I/O error occurs reading the war
     */
    public File extract(JarEntry entry) throws IOException
    {
        // expand to temp dir and add to list
        File tempFile = File.createTempFile("maven", null);
        // read from jar and write to the tempJar file
        BufferedInputStream inStream = new BufferedInputStream(getInputStream(
            entry));
        BufferedOutputStream outStream = new BufferedOutputStream(
            new FileOutputStream(tempFile));
        int status = -1;
        while ((status = inStream.read()) != -1)
        {
            outStream.write(status);
        }
        outStream.close();
        inStream.close();
        
        return tempFile;
    }
    
    /**
     * Tests whether a 'file' exists in the war. A file in this case is
     * a jar entry prefixed with a '/'. e.g. the file /WEB-INF/web.xml  is
     * the same as the jar entry WEB-INF/web.xml
     * 
     * @param fileName an entry in the war to be searched for
     * @return whether the entry exists
     */
    public boolean hasFile(String fileName)
    {
        if (fileName == null)
        {
            throw new NullPointerException("fileName parameter can't be null");
        }
        
        String entryName = null;
        if (fileName.startsWith("/"))
        {
            entryName = fileName.substring(1);
            return getJarEntry(entryName) != null;
        }
        else
        {
            return false;
        }
    }
    
    /**
     * Get a map of error pages to error locations. The key of the map is
     * either the <code>&lt;error-code&gt;</code> or <code>&lt;
     * exception-type&gt;</code>, the value is <code>&lt;location&gt;</code>.
     * 
     * @return a map of error page to error location
     * @throws IOException if there are problems reading from the war
     */
    public Map getErrorPages() throws IOException
    {
        Map errorPages = new HashMap();
        if (getWebXmlEntry() != null)
        {
            Document webXml = getWebXml();
            List errorNodes = webXml.selectNodes("//error-page");
            Node node = null;
            for (Iterator nodes = errorNodes.iterator(); nodes.hasNext();)
            {
                node = (Node) nodes.next();
                Node errorCodeNode = node.selectSingleNode("./error-code");
                Node exceptionTypeNode = node.selectSingleNode(
                    "./exception-type");
                String location = node.selectSingleNode("./location").getText();
                String key = null;
                if (errorCodeNode == null)
                {
                    key = exceptionTypeNode.getText();
                }
                else
                {
                    key = errorCodeNode.getText();
                }
                errorPages.put(key, location);
            }
        }
        return errorPages;
    }
    
    /**
     * Get the <code>&lt;form-login-config&gt</code> details specified in the
     * war file, or null if the element is not present
     * 
     * @return a {@link FormLoginConfig} with the login and error pages
     * @throws IOException if there are problems reading from the war
     */
    public FormLoginConfig getFormLoginConfig() throws IOException 
    {
        FormLoginConfig config = null;
        if (getWebXmlEntry() != null)
        {
            Document webXml = getWebXml();
            Node formLoginConfigNode = webXml.selectSingleNode(
                "//form-login-config");
            if (formLoginConfigNode != null) 
            {
                String login = formLoginConfigNode.selectSingleNode(
                    "./form-login-page").getText();
                String error = formLoginConfigNode.selectSingleNode(
                    "./form-error-page").getText();
                config = new FormLoginConfig(login, error);
            }
        }
        return config;
    }
    
    /** 
     * Get a map of servlet name -> url pattern for all defined servlets
     *
     * @return a map as specified
     * @throws IOException if there are problems reading from the war
     */
    public Map getServletMappings() throws IOException
    {
        Map mappings = new HashMap();
        
        if (getWebXmlEntry() != null)
        {
            Document webXml = getWebXml();
            List nodes = webXml.selectNodes("/web-app/servlet-mapping");
            Node node = null;
            for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++)
            {
                node = (Node) nodes.get(nodeIndex);
                String servletName = node.valueOf("./servlet-name");
                String urlPattern = node.valueOf("./url-pattern");
                mappings.put(servletName, urlPattern);
            }
        }
        return mappings;
    }
}
