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

package org.ow2.jonas.deployment.web;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.ow2.jonas.deployment.api.IEJBLocalRefDesc;
import org.ow2.jonas.deployment.api.IEJBRefDesc;
import org.ow2.jonas.deployment.api.IEnvEntryDesc;
import org.ow2.jonas.deployment.api.IMessageDestinationRefDesc;
import org.ow2.jonas.deployment.api.IResourceEnvRefDesc;
import org.ow2.jonas.deployment.api.IResourceRefDesc;
import org.ow2.jonas.deployment.api.IServiceRefDesc;
import org.ow2.jonas.deployment.common.DeploymentDescException;
import org.ow2.jonas.deployment.common.JndiEnvRefsGroupDesc;
import org.ow2.jonas.deployment.common.xml.JonasSecurity;
import org.ow2.jonas.deployment.common.xml.RunAs;
import org.ow2.jonas.deployment.common.xml.SecurityRole;
import org.ow2.jonas.deployment.common.xml.SecurityRoleMapping;
import org.ow2.jonas.deployment.web.xml.JonasServlet;
import org.ow2.jonas.deployment.web.xml.JonasWebApp;
import org.ow2.jonas.deployment.web.xml.Servlet;
import org.ow2.jonas.deployment.web.xml.ServletMapping;
import org.ow2.jonas.deployment.web.xml.WebApp;
import org.ow2.util.ee.metadata.war.api.IWarClassMetadata;
import org.ow2.util.ee.metadata.war.api.IWarDeployableMetadata;


/**
 * This class do the parsing of the web.xml file and jonas-web.xml files and
 * contruct a data structure associated to these two files.
 * 03/03 : Can read web.xml and jonas-web.xml if the url of the war is a directory.
 * @author Ludovic Bert
 * @author Florent Benoit
 * @author Philippe Coq
 */
public class WebContainerDeploymentDesc extends JndiEnvRefsGroupDesc {

    /**
     * The host on which the web application must be depoyed (can be null in
     * ear case).
     */
    private String host = null;

    /**
     * The context root of the web application (can be null in ear case).
     */
    private String contextRoot = null;

    /**
     * The port number of the prefered connector to access the web application
     * (can be null if defaults values can be found). Used only for WebServices.
     */
    private String port = null;

    /**
     * The tenant identifier of the application.
     */
    private String tenantId = null;

    /**
     * The delegation model used by the class loader context follows the java 2 delegation model ?
     */
    private boolean java2DelegationModel = true;

    /**
     * List of mapping between servlets names and servlets classes
     */
    private Map<String, ServletDesc> servlets = new HashMap<String, ServletDesc>();

    /**
     * List of mapping between servlets names and servlets url-mapping
     */
    private Map<String, List<String>> servletsUrlMapping = new Hashtable<String, List<String>>();

    /**
     * Mapping between servlet name and principal name
     */
    private Map<String, String> servletsPrincipalName = new Hashtable<String, String>();

    /**
     * Xml content of the web.xml file
     */
    private String xmlContent = "";

    /**
     * Xml content of the jonas-web.xml file
     */
    private String jonasXmlContent = "";

    /**
     * list of URLs
     */
    private List<SecurityRoleDesc> securityRoleList;

    /**
     * URLPattern Constraints
     */
    private SecurityConstraintListDesc securityConstraintListDesc;

    /**
     * Class level Metadatas of clazz annotated with @WebService.
     */
    private Map<String, IWarClassMetadata> webServicesMetadatas;

    /**
     * User to role mapping
     */
    private Map<String, List<String>> userToRoleMapping = null;



    /**
     * Construct an instance of a WebContainerDeploymentDesc.<BR>
     * Constructor is private, call one of the static getInstance
     * method instead.
     * @param fileName the name of the warFile
     * @param classLoader the classloader for the classes.
     * @param webApp the data structure of the web-app (web.xml)
     * @param jonasWebApp the data structure of the jonas-web-app
     * (jonas-web.xml)
     * @param metadata the annotation metadata of the jonas-web-app
     * @throws DeploymentDescException if the deployment
     * descriptors are corrupted.
     */
    public WebContainerDeploymentDesc(final String fileName,
                                      final ClassLoader classLoader,
                                      final WebApp webApp,
                                      final JonasWebApp jonasWebApp,
                                      final IWarDeployableMetadata metadata)
        throws DeploymentDescException {

        super(classLoader, webApp, jonasWebApp, fileName);


        // host
        if (jonasWebApp.getHost() != null) {
            host =  jonasWebApp.getHost();
        } else {
            host = null;
        }

        // context-root
        if (jonasWebApp.getContextRoot() != null) {
        contextRoot = jonasWebApp.getContextRoot();
        } else {
            contextRoot =  null;
        }

        // port
        if (jonasWebApp.getPort() != null) {
            port = jonasWebApp.getPort();
        } else {
            port = null;
        }

        // tenantId
        tenantId = jonasWebApp.getTenantId();

        // Class loader delegation model
        String delegationModel = null;
        if (jonasWebApp.getJava2DelegationModel() != null) {
            delegationModel = jonasWebApp.getJava2DelegationModel();
        } else {
            delegationModel = "true";
        }

        if (delegationModel.equalsIgnoreCase("false")) {
            java2DelegationModel = false;
        } else if (delegationModel.equalsIgnoreCase("true")) {
            java2DelegationModel = true;
        } else {
            throw new WebContainerDeploymentDescException("The java2 delegation model could be 'true' or 'false', not '" + delegationModel + "'.");
        }

        // Security Role List
        SecurityRole securityRole = null;
        securityRoleList = new ArrayList<SecurityRoleDesc>();
        for (Iterator itSecurityRole = webApp.getSecurityRoleList().iterator(); itSecurityRole.hasNext();) {
            securityRole = (SecurityRole) itSecurityRole.next();
            securityRoleList.add(new SecurityRoleDesc(securityRole));
        }


        // servlet classes + names + runas
        List servletList = webApp.getServletList();
        for (Iterator i = servletList.iterator(); i.hasNext();) {
            Servlet servlet = (Servlet) i.next();

            // Process @RunAs annotation if the run-as role has not already been set in XML DD
            if (servlet.getRunAs() == null) {
                String runAsAnnotationValue = runAsAnnotationValue(servlet, metadata);
                // Verify that the @RunAs annotation has been found in the servlet class
                if (runAsAnnotationValue != null) {
                    RunAs runAs = new RunAs();
                    runAs.setRoleName(runAsAnnotationValue);
                    servlet.setRunAs(runAs);
                }
            }

            if (servlet.getServletName() != null) {
                ServletDesc servletDesc = new ServletDesc(servlet);
                servlets.put(servlet.getServletName(), servletDesc);
            }
        }


        // Construct servletsUrlMapping
        List urlMappings = webApp.getServletMappingList();
        for (Iterator i = servletList.iterator(); i.hasNext();) {
            Servlet servlet = (Servlet) i.next();
            String name = servlet.getServletName().trim();
            List<String> mappings = new ArrayList<String>();
            for (Iterator m = urlMappings.iterator(); m.hasNext();) {
                ServletMapping sm = (ServletMapping) m.next();
                if (sm.getServletName().trim().equals(name)) {
                    String pattern = sm.getUrlPattern().trim();
                    if (pattern.indexOf('\n') != -1) {
                        throw new WebContainerDeploymentDescException("There is a '\\n' character inside the url pattern for servlet named '" + sm.getServletName() + "' in the file '" + fileName + "'.");
                    }
                    mappings.add(pattern);
                }
            }
            servletsUrlMapping.put(name, mappings);
        }

        // Build SecurityConstraintListDesc
        securityConstraintListDesc = new SecurityConstraintListDesc(webApp);

        List<JonasServlet> jonasServlets = jonasWebApp.getServletList();
        for (JonasServlet jonasServlet : jonasServlets) {
            servletsPrincipalName.put(jonasServlet.getServletName(), jonasServlet.getPrincipalName());
        }

         // user to role mapping
        JonasSecurity jonasSecurity = jonasWebApp.getJonasSecurity();
        if (jonasSecurity != null) {
            userToRoleMapping = new HashMap<String, List<String>>();
            for (Object o : jonasSecurity.getSecurityRoleMappingList()) {
                SecurityRoleMapping securityRoleMapping = (SecurityRoleMapping) o;
                if (securityRoleMapping != null) {
                    // get role name
                    String roleName = securityRoleMapping.getRoleName();
                    // get principals
                    List principals = securityRoleMapping.getPrincipalNamesList();

                    // Iterator on principals
                    for (Object principal : principals) {
                        // get principal name
                        String principalName = (String) principal;
                        // Existing mapping ?
                        List<String> currentMapping = userToRoleMapping.get(principalName);
                        if (currentMapping == null) {
                            currentMapping = new ArrayList<String>();
                            userToRoleMapping.put(principalName, currentMapping);
                        }
                        // add mapping
                        currentMapping.add(roleName);
                    }
                }
            }
        }

        /*
         * See SRV 13.2 of servlet spec 2.4 <br>
         * The sub elements under web-app can be in an arbitrary order in this
         * version of the specification. Because of the restriction of XML
         * Schema, The multiplicity of the elements distributable,
         * session-config, welcome-file-list, jspconfig, login-config, and
         * locale-encoding-mapping-list was changed from optional to 0 or more .
         * The containers must inform the developer with a descriptive error
         * message when the deployment descriptor contains more than one element
         * of session-config, jsp-config, and login-config.
         */
        if (webApp.getJspConfigNumber() > 1) {
            throw new WebContainerDeploymentDescException("The web-app element must contain only one element jsp-config in file '" + fileName + "'");
        }

        if (webApp.getLoginConfigNumber() > 1) {
            throw new WebContainerDeploymentDescException("The web-app element must contain only one element login-config in file '" + fileName + "'");
        }

        if (webApp.getSessionConfigNumber() > 1) {
            throw new WebContainerDeploymentDescException("The web-app element must contain only one element session-config in file '" + fileName + "'");
        }



    }

    /**
     * Returns the value of the @RunAs annotation if present, null otherwise
     * @param servlet The given servlet
     * @param metadata The webapp metadata
     * @return The value of the @RunAs annotation if present, null otherwise
     */
    private String runAsAnnotationValue(final Servlet servlet, final IWarDeployableMetadata metadata) {
        Collection<IWarClassMetadata> classes = metadata.getWarClassMetadataCollection();

        for (IWarClassMetadata clazz : classes) {
            String className = clazz.getJClass().getName();
            className = className.replace("/", ".");

            // Found a @RunAs annotation declaration into the servlet class
            if (className.equals(servlet.getServletClass()) && clazz.getRunAs() != null) {
                return clazz.getRunAs();
            }
        }

        // No @RunAs annotation found
        return null;
    }

    /**
     * Return the content of the web.xml file
     * @return the content of the web.xml file
     */
    public String getXmlContent() {
        return xmlContent;
    }

    /**
     * Return the content of the jonas-web.xml file
     * @return the content of the jonas-web.xml file
     */
    public String getJOnASXmlContent() {
        return jonasXmlContent;
    }

    /**
     * Set the content of the web.xml file
     * @param xml the content of the file
     */
    public void setXmlContent(final String xml) {
        xmlContent = xml;
    }

    /**
     * Set the content of the jonas-web.xml file
     * @param jXml the content of the file
     */
    public void setJOnASXmlContent(final String jXml) {
        jonasXmlContent = jXml;
    }


    /**
     * Get the context root of this web application.
     * @return the context root of this web application.
     */
    public String getContextRoot() {
        return contextRoot;
    }

    /**
     * Context classloader must follow the java2 delegation model ?
     * @return true if the context's classloader must follow the java 2 delegation model.
     */
    public boolean getJava2DelegationModel() {
        return java2DelegationModel;
    }

    /**
     * Get the host on which the web application must be deployed.
     * @return the host on which the web application must be deployed.
     */
    public String getHost() {
        return host;
    }

    /**
     * Get the prefered port of the connector used to access the web application.
     * @return the prefered port of the connector used to access the web application.
     */
    public String getPort() {
        return port;
    }

    /**
     * Get the tenant identifier
     * @return the identifier of the tenant
     */
    public String getTenantId() {
        return tenantId;
    }

    /**
     * Return a String representation of the WebContainerDeploymentDesc.
     * @return a String representation of the WebContainerDeploymentDesc.
     */
    @Override
    public String toString() {
        StringBuffer ret = new StringBuffer();

        // Return the displayName
        ret.append("\ngetDisplayName()=" + getDisplayName());

        // Return the resource-env-ref
        IResourceEnvRefDesc[] rer = getResourceEnvRefDesc();
        for (int i = 0; i < rer.length; i++) {
            ret.append("\ngetResourceEnvRefDesc(" + i + ")=" + rer[i].getClass().getName());
            ret.append(rer[i].toString());
        }

        // Return the resource-ref
        IResourceRefDesc[] resourceRefDesc = getResourceRefDesc();
        for (int i = 0; i < resourceRefDesc.length; i++) {
            ret.append("\ngetResourceRefDesc(" + i + ")=" + resourceRefDesc[i].getClass().getName());
            ret.append(resourceRefDesc[i].toString());
        }

        // Return the env-entry
        IEnvEntryDesc[] envEntries = getEnvEntryDesc();
        for (int i = 0; i < envEntries.length; i++) {
            ret.append("\ngetEnvEntryDesc(" + i + ")=" + envEntries[i].getClass().getName());
            ret.append(envEntries[i].toString());
        }

        // Return the ejb-ref
        IEJBRefDesc[] ejbRefDesc = getEjbRefDesc();
        for (int i = 0; i < ejbRefDesc.length; i++) {
            ret.append("\ngetEjbRefDesc(" + i + ")=" + ejbRefDesc[i].getClass().getName());
            ret.append(ejbRefDesc[i].toString());
        }

        // Return the ejb-local-ref
        IEJBLocalRefDesc[] ejbLocalRefDesc = getEjbLocalRefDesc();
        for (int i = 0; i < ejbLocalRefDesc.length; i++) {
            ret.append("\ngetEjbLocalRefDesc(" + i + ")=" + ejbLocalRefDesc[i].getClass().getName());
            ret.append(ejbLocalRefDesc[i].toString());
        }

        // Return the service-ref-name
        IServiceRefDesc[] svcRef = getServiceRefDesc();
        for (int i = 0; i < svcRef.length; i++) {
            ret.append("\ngetServiceRefDesc(" + i + ")=" + svcRef[i].getClass().getName());
            ret.append(svcRef[i].toString());
        }

        // Return the message-destination-ref
        IMessageDestinationRefDesc[] mdRefDesc = getMessageDestinationRefDesc();
        for (int i = 0; i < mdRefDesc.length; i++) {
            ret.append("\ngetMessageDestinationRefDesc(" + i + ")=" + mdRefDesc[i].getClass().getName());
            ret.append(mdRefDesc[i].toString());
        }

        // Return the host
        ret.append("\ngetHost()=" + getHost());

        // Return the context-root
        ret.append("\ngetContextRoot()=" + getContextRoot());

        return ret.toString();
    }

    /**
     * Gets the list of Servlets
     * @return list of Servlets
     */
    public Collection<ServletDesc> getServletDescList() {
        return servlets.values();
    }

    /**
     * Return a list of all servlets name available
     * @return a list of all servlets name available
     */
    public String[] getServletsName() {
        String[] st = new String[servlets.size()];
        return servlets.keySet().toArray(st);
    }

    /**
     * Return the classname of the given servlet
     * @param servName name of the given servlet
     * @return the classname of the given servlet
     */
    public String getServletClassname(final String servName) {
        return servlets.get(servName).getServletClass();
    }

    /**
     * Gets the constraint list
     * @return the constraint list
     */
    public SecurityConstraintListDesc getSecurityConstraintListDesc() {
        return securityConstraintListDesc;
    }

    /**
     * Gets the list of security roles
     * @return the list of security roles
     */
    public List<SecurityRoleDesc> getSecurityRoleList() {
        return securityRoleList;
    }

    /**
     * Return the list of urlMapping of the given servlet
     * @param servName name of the given servlet
     * @return the list of urlMapping of the given servlet
     */
    public List<String> getServletMappings(final String servName) {
        return servletsUrlMapping.get(servName);
    }

    /**
     * Return the principal name of the given servlet
     * @param servName name of the given servlet
     * @return the principal name of the given servlet
     */
    public String getServletPrincipalName(final String servName) {
        return servletsPrincipalName.get(servName);
    }

    /**
     * Set the list of POJO endpoints
     * @param services POJOs metadatas
     */
    public void setWebServices(final Map<String, IWarClassMetadata> services) {
        this.webServicesMetadatas = services;
    }

    /**
     * @return the list of classes annotated with @WebService from this webapp
     */
    public Map<String, IWarClassMetadata> getWebServices() {
        return webServicesMetadatas;
    }

    public void mergeServletsRunAS(final IWarDeployableMetadata warDeployableMetadata) {
        // TODO Auto-generated method stub

    }

    public Map<String, List<String>> getUserToRoleMapping() {
        return userToRoleMapping;
    }
}
