/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2008 Bull S.A.S.
 * Contact: jonas-team@objectweb.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  021ejbJarDD11-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */


package org.objectweb.jonas_ejb.deployment.api;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.objectweb.jonas_ejb.deployment.xml.AssemblyDescriptor;
import org.objectweb.jonas_ejb.deployment.xml.EjbJar;
import org.objectweb.jonas_ejb.deployment.xml.Entity;
import org.objectweb.jonas_ejb.deployment.xml.JonasEjbJar;
import org.objectweb.jonas_ejb.deployment.xml.JonasEntity;
import org.objectweb.jonas_ejb.deployment.xml.JonasRunAsMapping;
import org.objectweb.jonas_ejb.deployment.xml.JonasSession;
import org.objectweb.jonas_ejb.deployment.xml.MethodPermission;
import org.objectweb.jonas_ejb.deployment.xml.Session;

import org.objectweb.jonas_lib.deployment.api.DeploymentDescException;
import org.objectweb.jonas_lib.deployment.api.DescriptionGroupDesc;
import org.objectweb.jonas_lib.deployment.xml.JLinkedList;
import org.objectweb.jonas_lib.deployment.xml.JonasMessageDestination;
import org.objectweb.jonas_lib.deployment.xml.MessageDestination;

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

/**
 * Class to hold meta-information related to the deployment of an ejb-jar
 * @author Christophe Ney [cney@batisseurs.com] : Initial developer
 * @author Helene Joanin
 * @author Philippe Durieux
 * @author Markus Karg (Novell port)
 * @author Philippe Coq
 * @author S. Ali Tokmen, Malek Chahine: EJB statistics
 */
public abstract class DeploymentDesc extends DescriptionGroupDesc {

    /**
     * Logger
     */
    protected Logger logger;


    /**
     * Ejb spec version
     */
    protected String  specVersion = null;

    /**
     * Set of enterprise beans deployment descriptors
     */
    protected HashMap beanDesc = new HashMap();

    /**
     * Assembly descriptor
     */
    protected AssemblyDescriptor asd = null;

    /**
     * Deployment desc's file name (jar or directory)
     */
    protected String fileName = null;

    /**
     * Deployment desc's file name (jar or directory)
     */
    protected String ejbClientJar = null;

    /**
     * List of MethodPermissions
     */
    private List methodPermissionsDescList = null;

    /**
     * ExcludeList in Assembly descriptor
     */
    private ExcludeListDesc excludeListDesc = null;

    /**
     * List of JonasMessageDestinations
     */
    protected JLinkedList jonasMDList = null;

    /**
     * Xml content of the standard deployment descriptor file
     */
    private String xmlContent = "";

    /**
     * Xml content of the JOnAS deployment descriptor file
     */
    private String jonasXmlContent = "";

    /**
     * Mapping for runAs principals
     * Principal name --> list of roles (array)
     */
    private Map runAsMapping = null;

    /**
     * true if EJB monitoring is active for this EJB-JAR, null if nothing set.
     */
    private Boolean monitoringEnabled;

    /**
     * Number of milliseconds after which methods will start warning for this
     * EJB-JAR, null if nothing set.
     */
    private Integer warningThreshold;

    /**
     * Build the Meta-Information from the XML data binding trees
     * containing the EJB and JOnAS deployment descriptors.
     * @param classLoader The Class Loader to be used
     * @param ejbJar The EjbJar information, from standard deployment descriptor.
     * @param jonasEjbJar The JonasEjbJar information, from JOnAS specific deployment descriptor.
     * @param l The logger to be used for tracing
     * @param fileName deployment desc's jar or directory name
     * @throws DeploymentDescException Cannot deploy bean
     */
    public DeploymentDesc(ClassLoader classLoader,
                                EjbJar ejbJar,
                                JonasEjbJar jonasEjbJar,
                                Logger l,
                                String fileName)
        throws DeploymentDescException {


        logger = l;

        // set jarFileName
        this.fileName = fileName;

        // test classloader
        if (classLoader == null) {
            throw new DeploymentDescException("DeploymentDesc: Classloader is null");
        }

        // test the validity of the ejbJar (EnterpriseBeans must be present)
        if (ejbJar.getEnterpriseBeans() == null) {
            throw new DeploymentDescException("invalid standard deployment descriptor (<enterprise-beans> element missing)");
        }
        // spec-version
        specVersion = ejbJar.getVersion();

        // ejb-client-jar
        ejbClientJar = ejbJar.getEjbClientJar();

        // assembly descriptor
        asd = ejbJar.getAssemblyDescriptor();

        // Run-as mapping
        runAsMapping = new HashMap();
        for (Iterator i = jonasEjbJar.getJonasRunAsMappingList().iterator(); i.hasNext();) {
            // Get Mapping
            JonasRunAsMapping jonasRunAsMapping = (JonasRunAsMapping) i.next();
            String principalName = jonasRunAsMapping.getPrincipalName();
            // Get existing roles if any
            String[] existingRunAsRoleMapping = (String[]) runAsMapping.get(principalName);
            String[] newMappingRoles = null;
            int r = 0;
            if (existingRunAsRoleMapping == null) {
                newMappingRoles = new String[jonasRunAsMapping.getRoleNamesList().size()];
            } else {
                newMappingRoles = new String[jonasRunAsMapping.getRoleNamesList().size() + existingRunAsRoleMapping.length];
                // Now add existing roles
                System.arraycopy(existingRunAsRoleMapping, 0, newMappingRoles, 0, existingRunAsRoleMapping.length);
                r =  existingRunAsRoleMapping.length;
            }
            Iterator itR = jonasRunAsMapping.getRoleNamesList().iterator();
            while (itR.hasNext()) {
                newMappingRoles[r] = (String) itR.next();
                r++;
            }
            runAsMapping.put(principalName, newMappingRoles);
        }

        // Use by PermissionManager for translating xml into EJBMethodPermission
        methodPermissionsDescList = new LinkedList();
        // Create EJBMEthodPermissions for each method-permission
        if (asd != null) {
            for (Iterator i = asd.getMethodPermissionList().iterator(); i.hasNext();) {
                MethodPermission methodPermission = (MethodPermission) i.next();
                methodPermissionsDescList.add(new MethodPermissionDesc(methodPermission));
            }
        }

        // Use by PermissionManager for translating xml into EJBMethodPermission
        if (asd != null && asd.getExcludeList() != null) {
            excludeListDesc = new ExcludeListDesc(asd.getExcludeList());
        }

        // jonas-message-destination
        jonasMDList = jonasEjbJar.getJonasMessageDestinationList();

        // HashMap of jonas-session
        HashMap jonasSession = new HashMap();
        for (Iterator i = jonasEjbJar.getJonasSessionList().iterator(); i.hasNext();) {
            JonasSession jSes = (JonasSession) i.next();
            jonasSession.put(jSes.getEjbName(), jSes);
        }

        // session beans
        for (Iterator i = ejbJar.getEnterpriseBeans().getSessionList().iterator(); i.hasNext();) {
            BeanDesc bd = null;
            Session ses = (Session) i.next();
            // find corresponding jonas session
            JonasSession jSes = (JonasSession) jonasSession.get(ses.getEjbName());
            if (jSes == null) {
                // Build a default jonas-session if not exist
                jSes = new JonasSession();
                jSes.setEjbName(ses.getEjbName());
            }
            if (ses.getSessionType().equals("Stateful")) {
                // stateful
                bd = new SessionStatefulDesc(classLoader, ses, asd, jSes, jonasMDList, fileName);
            } else if (ses.getSessionType().equals("Stateless")) {
                // stateless
                bd = new SessionStatelessDesc(classLoader, ses, asd, jSes, jonasMDList, fileName);
            } else {
                throw new DeploymentDescException("invalid session-type content for bean " + ses.getEjbName());
            }
            bd.setDeploymentDesc(this);
            bd.check();
            beanDesc.put(bd.getEjbName(), bd);
        }

        // HashMap of jonas-entity
        HashMap jonasEntity = new HashMap();
        for (Iterator i = jonasEjbJar.getJonasEntityList().iterator(); i.hasNext();) {
            JonasEntity jEnt = (JonasEntity) i.next();
            jonasEntity.put(jEnt.getEjbName(), jEnt);
        }
        // entity beans
        for (Iterator i = ejbJar.getEnterpriseBeans().getEntityList().iterator(); i.hasNext();) {
            BeanDesc bd = null;
            Entity ent = (Entity) i.next();
            // find corresponding jonas entity
            JonasEntity jEnt = (JonasEntity) jonasEntity.get(ent.getEjbName());
            if (jEnt == null) {
                throw new DeploymentDescException("jonas-entity missing for bean " + ent.getEjbName());
            }
            if (ent.getPersistenceType().equals("Bean")) {
                // bean managed
                bd = new EntityBmpDesc(classLoader, ent, asd, jEnt, jonasMDList, fileName);
            } else if (ent.getPersistenceType().equals("Container")) {
                // container managed (always jdbc)
                bd = newEntityBeanDesc(classLoader, ent, asd, jEnt, jonasMDList);
            } else {
                throw new DeploymentDescException("Invalid persistence-type content for bean " + ent.getEjbName());
            }
            bd.setDeploymentDesc(this);
            bd.check();
            beanDesc.put(bd.getEjbName(), bd);
        }

        // TODO read monitoringEnabled and warningThreshold,
        //      container will automatically set monitoringSettingsDefinedInDD
    }

    /**
     * Get an Iterator on the Bean Desc list
     * @return Iterator on BeanDesc
     */
    public Iterator getBeanDescIterator() {
        return beanDesc.values().iterator();
    }

    /**
     * Get descriptors for all beans contained in jar file
     * @return Array of bean's descriptors
     */
    public BeanDesc[] getBeanDesc() {
        BeanDesc[] ret = new BeanDesc[beanDesc.size()];
        int j = 0;
        for (Iterator i = beanDesc.values().iterator(); i.hasNext(); j++) {
            ret[j] = (BeanDesc) i.next();
        }
        return ret;
    }

    /**
     * Gets the Mapping for run-as principal
     * @param principalName name of the run-as principal
     * @return array of roles
     */
    public String[] getRolesForRunAsPrincipal(String principalName) {
        return (String[]) runAsMapping.get(principalName);
    }

    /**
     * Get bean descriptor given its name
     * @param ejbName the name of the bean in the Deployment Descriptor
     * @return bean descriptor given its name
     */
    public BeanDesc getBeanDesc(String ejbName) {
        return (BeanDesc) beanDesc.get(ejbName);
    }

    /**
     * Get bean descriptor given its abstract schema name
     * @param asn Abstract Schema Name
     * @return null if it doesn't exist.
     */
    public EntityCmp2Desc asn2BeanDesc(String asn) {
        for (Iterator i = beanDesc.values().iterator(); i.hasNext();) {
            BeanDesc bd = (BeanDesc) i.next();
            if (bd instanceof EntityCmp2Desc) {
                if (asn.equals(((EntityCmp2Desc) bd).getAbstractSchemaName())) {
                    return ((EntityCmp2Desc) bd);
                }
            }
        }
        return null;
    }


    /**
     * Get the list of the methodPermissionDesc objects which represent
     * method-permission elements in assembly-descriptor
     * @return the list of methodPermissionDesc objects
     */
    public List getMethodPermissionsDescList() {
        return methodPermissionsDescList;
    }

    /**
     * Get the exclude list of the assembly descriptor
     * @return the exclude list of the assembly descriptor
     */
    public ExcludeListDesc getExcludeListDesc() {
        return excludeListDesc;
    }


    /**
     * Get bean descriptor given its interface local name
     * @param itfLocalName local interface name
     * @return null if it doesn't exist.
     */
    public BeanDesc getBeanDescWithLocalInterface(String itfLocalName) {
        for (Iterator i = beanDesc.values().iterator(); i.hasNext();) {
            BeanDesc bd = (BeanDesc) i.next();
            if (bd.getLocalClass() != null) {
                if (itfLocalName.equals(bd.getLocalClass().getName())) {
                    return bd;
                }
            }
        }
        return null;
    }

    /**
     * Find the JOnAS message destination for the given name
     * @param mdLink the name of the message destination link in the Deployment Descriptor
     * @return boolean if link was found
     */
    public boolean getMessageDestination(String mdLink) {
        MessageDestination md = null;
        if (asd != null && asd.getMessageDestinationList() != null) {
            for (Iterator i = asd.getMessageDestinationList().iterator(); i.hasNext();) {
                md = (MessageDestination) i.next();
                if (md.getMessageDestinationName().equals(mdLink)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Get the JOnAS message destination for the given name
     * @param mdLink the name of the message destination link in the Deployment Descriptor
     * @return the jonas message destination given the name
     */
    public JonasMessageDestination getJonasMessageDestination(String mdLink) {
        JonasMessageDestination jmd = null;
        if (jonasMDList != null) {
            for (Iterator i = jonasMDList.iterator(); i.hasNext();) {
                jmd = (JonasMessageDestination) i.next();
                if (jmd.getMessageDestinationName().equals(mdLink)) {
                    return jmd;
                }
            }
        }
        return null;
    }

    /**
     * In case of beans with old CMP1 persistance, we need to instanciate the old class,
     * as if we were in an old Deployment Descriptor.
     * Default is CMP2.x for entity beans with a EJB2.0 DD.
     *
     * @param cl The ClassLoader to be used
     * @param ent Entity MetaInformation from XML files
     * @param asd AssemblyDescriptor MetaInformation from XML files
     * @param j JonasEntity MetaInformation from XML files
     * @param jMDRList MessageDrivenRef list
     *
     * @return The Entity Bean Descriptor, for the good CMP version.
     *
     * @throws DeploymentDescException Cannot build Entity Descriptor
     */
    protected abstract BeanDesc newEntityBeanDesc(ClassLoader cl, Entity ent,
                                                  AssemblyDescriptor asd, JonasEntity j, JLinkedList jMDRList)
        throws DeploymentDescException;

    /**
     * Get the display name
     * @return the Display name string, from the deployment descriptor.
     */
    public String getDisplayName() {
        return displayName;
    }

    /**
     * Get the ejb-client-jar name
     * @return the ejb-client-jar string, from the deployment descriptor (may be null).
     */
    public String getEjbClientJar() {
        return ejbClientJar;
    }

    /**
     * @return true if EJB monitoring is active for this EJB-JAR, null if
     *         nothing set.
     */
    public Boolean getMonitoringEnabled() {
        return this.monitoringEnabled;
    }

    /**
     * @return Number of milliseconds after which methods will start warning
     *         for this EJB-JAR, null if nothing set.
     */
    public Integer getWarningThreshold() {
        return this.warningThreshold;
    }

    /**
     * get the current logger
     * @return the Logger
     */
    public Logger getLogger() {
        return logger;
    }

    /**
     * set the current logger
     * @param logger the Logger
     */
    public void setLogger(Logger logger) {
        this.logger = logger;
    }

    /**
     * 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;
    }

    /**
     * String representation of the object for test purpose
     * @return String representation of this object
     */
    public String toString() {
        StringBuffer ret = new StringBuffer();
        ret.append("\ngetDisplayName()=" + getDisplayName());
        ret.append("\ngetEjbClientJar()=" + getEjbClientJar());
        BeanDesc[] b = getBeanDesc();
        for (int i = 0; i < b.length; i++) {
            ret.append("\ngetBeanDesc(" + i + ")=" + b[i].getClass().getName());
            ret.append(b[i].toString());
        }
        return ret.toString();
    }

    /**
     * @param xmlContent XML Content
     */
    public void setXmlContent(String xmlContent) {
        this.xmlContent = xmlContent;
    }

    /**
     * @param jonasXmlContent XML Content
     */
    public void setJOnASXmlContent(String jonasXmlContent) {
        this.jonasXmlContent = jonasXmlContent;
    }

}
