/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 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
 *
 * Initial developer(s):  Philippe Coq
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.deployment.common;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.jonas.deployment.api.IEnvEntryDesc;
import org.ow2.jonas.deployment.api.IJNDIEnvRefsGroupDesc;
import org.ow2.jonas.deployment.api.IPersistenceUnitRefDesc;
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.xml.EjbLocalRef;
import org.ow2.jonas.deployment.common.xml.EjbRef;
import org.ow2.jonas.deployment.common.xml.EnvEntry;
import org.ow2.jonas.deployment.common.xml.JndiEnvRefsGroupXml;
import org.ow2.jonas.deployment.common.xml.JonasEjbRef;
import org.ow2.jonas.deployment.common.xml.JonasJndiEnvRefsGroupXml;
import org.ow2.jonas.deployment.common.xml.JonasMessageDestinationRef;
import org.ow2.jonas.deployment.common.xml.JonasResource;
import org.ow2.jonas.deployment.common.xml.JonasResourceEnv;
import org.ow2.jonas.deployment.common.xml.JonasServiceRef;
import org.ow2.jonas.deployment.common.xml.MessageDestinationRef;
import org.ow2.jonas.deployment.common.xml.PersistenceUnitRef;
import org.ow2.jonas.deployment.common.xml.ResourceEnvRef;
import org.ow2.jonas.deployment.common.xml.ResourceRef;
import org.ow2.jonas.deployment.common.xml.ServiceRef;
import org.ow2.jonas.deployment.ws.ServiceRefDesc;
import org.ow2.jonas.deployment.ws.WSDeploymentDescException;
import org.ow2.jonas.lib.util.Log;
import org.ow2.util.ee.metadata.common.api.enc.IENCBindingHolder;
/**
 * This class is used to keep the usage of containedJNDI environment
 * regerence elements consistent across J2EE deployment descriptors
 * Elements are "env-entry", "ejb-ref", "ejb-local-ref", "resource-ref",
 * "resource-env-ref, "service-ref", "message-destination-ref"
 * This class extends DescriptionGroupDesc  and give access to elements
 * "description", "display-name", "icon"
 * used by entity, session, message-driven-bean, web-app, clientApp
 *
 * @author Philippe Coq
 *
 */

public abstract class JndiEnvRefsGroupDesc extends DescriptionGroupDesc implements IJNDIEnvRefsGroupDesc {

    /**
     * List of resource env ref Descriptors.
     */
    private Vector resourceEnvRefs = new Vector();

    /**
     * List of resource ref Descriptors..
     */
    private Vector resourceRefs = new Vector();

    /**
     * List of env-entry Descriptors.
     */
    private Vector envEntries = new Vector();

    /**
     * List of service-ref Descriptors.
     */
    private Vector serviceRefs = new Vector();

    /**
     * List of EJB ref Descriptors..
     */
    private Vector ejbRefs = new Vector();

    /**
     * List of local EJB ref Descriptors..
     */
    private Vector ejbLocalRefs = new Vector();

    /**
     * List of message destination ref Descriptors..
     */
    private Vector messageDestinationRefs = new Vector();

    /**
     * List of persistence-unit-ref elements.
     */
    private Vector persistenceUnitRefs = new Vector();
    /**
     * Keeps some annotations processing data.
     */
    private IENCBindingHolder encBindingHolder = null;

    /**
     * logger
     */
    protected static Logger logger = Log.getLogger(Log.JONAS_DEPLOY_PREFIX);

    /**
     * Construct an instance of a JndiEnvRefsGroupDesc. <BR>
     *
     * @param classLoader the classloader for the deployed module.
     * @param app the data structure of the J2EE application
     * @param jonasApp the data structure of the jonas specific deployment
     *        descriptor
     * @param fileName the file name of the module (useful only for service-ref)
     * @throws DeploymentDescException if the deployment descriptors are
     *         corrupted.
     */
    protected JndiEnvRefsGroupDesc(final ClassLoader classLoader,
                                   final JndiEnvRefsGroupXml app,
                                   final JonasJndiEnvRefsGroupXml jonasApp,
                                   final String fileName)
      throws DeploymentDescException {

        // test classloader
        if (classLoader == null) {
            throw new DeploymentDescException("Classloader is null");
        }
        // display name
        displayName = null;
        if (app.getDisplayName() != null) {
            displayName = app.getDisplayName();
        }

        // resource-env-ref
        processResourceEnvRef(classLoader, app, jonasApp);

        // resource-ref
        processResourceRef(classLoader, app, jonasApp);

        // env-entry
        processEnvEntry(app);

        // ejb-ref
        processEjbRef(app, jonasApp);

        // ejb-local-ref
        processEjbLocalRef(app);

        // service-ref
        processServiceRef(classLoader, app, jonasApp, fileName);

        // message-destination-ref
        processMessageDestinationRef(app, jonasApp);

        // persistence-unit-ref
        processPersistenceUnitRef(app);

    }

    /**
     * @param app the data structure of the J2EE application
     * @throws DeploymentDescException if the deployment descriptors are
     *         corrupted.
     */
    private void processEnvEntry(final JndiEnvRefsGroupXml app) throws DeploymentDescException {
        // env-entry
        for (Iterator i = app.getEnvEntryList().iterator(); i.hasNext();) {
            IEnvEntryDesc envEntryDesc = new EnvEntryDesc((EnvEntry) i.next());
            envEntries.addElement(envEntryDesc);
        }
    }

    /**
     * @param app the data structure containing the value of Xml elements of a JndiEnvRefsGroup
     * @throws DeploymentDescException if the deployment descriptors are
     *         corrupted.
     */
    private void processPersistenceUnitRef(final JndiEnvRefsGroupXml app) throws DeploymentDescException {
        for (Iterator i = app.getPersistenceUnitRefList().iterator(); i.hasNext();) {
            IPersistenceUnitRefDesc persistenceUnitRefDesc = new PersistenceUnitRefDesc((PersistenceUnitRef) i.next());
            persistenceUnitRefs.addElement(persistenceUnitRefDesc);
        }
    }

    /**
     * @param app the data structure of the J2EE application
     * @param jonasApp the data structure of the jonas specific deployment
     *        descriptor
     * @throws DeploymentDescException if the deployment descriptors are
     *         corrupted.
     */
    private void processMessageDestinationRef(final JndiEnvRefsGroupXml app, final JonasJndiEnvRefsGroupXml jonasApp) throws DeploymentDescException {
        List messageDestinationRefList = app.getMessageDestinationRefList();
        List jonasMessageDestinationRefList = jonasApp.getJonasMessageDestinationRefList();


        HashMap messageDestinationRef = new HashMap();
        for (Iterator i = jonasMessageDestinationRefList.iterator(); i.hasNext();) {
            JonasMessageDestinationRef jonasMessageDestinationRef = (JonasMessageDestinationRef) i.next();
            String mdrName = jonasMessageDestinationRef.getMessageDestinationRefName();
            boolean findMDRef = false;
            for (Iterator j = messageDestinationRefList.iterator(); j.hasNext() && !findMDRef;) {
                MessageDestinationRef mdRef = (MessageDestinationRef) j.next();
                if (mdrName.equals(mdRef.getMessageDestinationRefName())) {
                    findMDRef = true;
                }
            }
            if (!findMDRef) {
                throw new DeploymentDescException("message-destination-ref missing for jonas-message-destination-ref " + mdrName);
            }
            messageDestinationRef.put(mdrName, jonasMessageDestinationRef);
        }
        for (Iterator i = messageDestinationRefList.iterator(); i.hasNext();) {
            MessageDestinationRef mdRef = (MessageDestinationRef) i.next();
            String mdrName = mdRef.getMessageDestinationRefName();
            JonasMessageDestinationRef jonasMessageDestinationRef = (JonasMessageDestinationRef) messageDestinationRef.get(mdrName);
            if (jonasMessageDestinationRef == null) {
                // The message-destination-link must be specified when there is no jonas-message-destination-ref
                if ((mdRef.getMessageDestinationLink() == null)) {
                    // Ignore the message-destination-link syntax "product.jar#ProductMD"
                    throw new DeploymentDescException("message-destination-link missing for message-destination-ref-name " + mdrName);
                }            }
            messageDestinationRefs.addElement(new MessageDestinationRefDesc(mdRef, jonasMessageDestinationRef));
        }
    }

    /**
     * @param classLoader the classloader for the deployed module.
     * @param app the data structure of the J2EE application
     * @param jonasApp the data structure of the jonas specific deployment
     *        descriptor
     * @param fileName the file name of the module (useful only for service-ref)
     * @throws WSDeploymentDescException if the deployment descriptors are
     *         corrupted.
     */
    private void processServiceRef(final ClassLoader classLoader, final JndiEnvRefsGroupXml app, final JonasJndiEnvRefsGroupXml jonasApp, final String fileName) throws WSDeploymentDescException {
        Map links = linksSR2JSR(app, jonasApp);
        List serviceRefList = app.getServiceRefList();
        for (Iterator i = serviceRefList.iterator(); i.hasNext();) {
            ServiceRef serviceRef = ((ServiceRef) i.next());
            JonasServiceRef jsr = (JonasServiceRef) links.get(serviceRef.getServiceRefName());
            serviceRefs.addElement(new ServiceRefDesc(classLoader, serviceRef, jsr, fileName));
        }
    }

    /**
     * @param app the data structure of the J2EE application
     * @throws DeploymentDescException if the deployment descriptors are
     *         corrupted.
     */
    private void processEjbLocalRef(final JndiEnvRefsGroupXml app) throws DeploymentDescException {
        List ejbLocalRefList = app.getEjbLocalRefList();
        for (Iterator i = ejbLocalRefList.iterator(); i.hasNext();) {
            EjbLocalRef ejbLocalRef = (EjbLocalRef) i.next();
            if ((ejbLocalRef.getEjbLink() == null)) {
                throw new DeploymentDescException("ejb-link missing for ejb-ref-name " + ejbLocalRef.getEjbRefName());
            }
            ejbLocalRefs.addElement(new EjbLocalRefDesc(ejbLocalRef));
        }
    }

    /**
     * @param app the data structure of the J2EE application
     * @param jonasApp the data structure of the jonas specific deployment
     *        descriptor
     * @throws DeploymentDescException if the deployment descriptors are
     *         corrupted.
     */
    private void processEjbRef(final JndiEnvRefsGroupXml app, final JonasJndiEnvRefsGroupXml jonasApp) throws DeploymentDescException {
        List ejbRefList = app.getEjbRefList();
        List jonasEjbRefList = jonasApp.getJonasEjbRefList();

        HashMap ejbRef = new HashMap();
        for (Iterator i = jonasEjbRefList.iterator(); i.hasNext();) {
            JonasEjbRef jonasEjbRef = (JonasEjbRef) i.next();
            String brName = jonasEjbRef.getEjbRefName();
            boolean findBRef = false;
            for (Iterator j = ejbRefList.iterator(); j.hasNext() && !findBRef;) {
                EjbRef eRef = (EjbRef) j.next();
                if (brName.equals(eRef.getEjbRefName())) {
                    findBRef = true;
                }
            }
            if (!findBRef) {
                throw new DeploymentDescException("ejb-ref missing for jonas-ejb-ref " + brName);
            }
            ejbRef.put(brName, jonasEjbRef);
        }
        for (Iterator i = ejbRefList.iterator(); i.hasNext();) {
            EjbRef eRef = (EjbRef) i.next();
            String brName = eRef.getEjbRefName();
            JonasEjbRef jonasEjbRef = (JonasEjbRef) ejbRef.get(brName);
            if (jonasEjbRef == null) {
                // The ejb-link must be specified when there is no jonas-ejb-ref
                if ((eRef.getEjbLink() == null)) {
                    // Ignore the ejb-link syntax "product.jar#ProductEJB"
                    // throw new DeploymentDescException("ejb-link missing for ejb-ref-name " + brName);
                    // FIXME gaellalire : get ejb3 compatibility
                    logger.log(BasicLevel.DEBUG, "Maybe the ejb-link is missing for ejb-ref-name " + brName);
                    continue;
                }
            }
            ejbRefs.addElement(new EjbRefDesc(eRef, jonasEjbRef));
        }
    }

    /**
     * @param classLoader the classloader for the deployed module.
     * @param app the data structure of the J2EE application
     * @param jonasApp the data structure of the jonas specific deployment
     *        descriptor
     * @throws DeploymentDescException if the deployment descriptors are
     *         corrupted.
     */
    private void processResourceRef(final ClassLoader classLoader, final JndiEnvRefsGroupXml app, final JonasJndiEnvRefsGroupXml jonasApp) throws DeploymentDescException {
        List resourceRefList = app.getResourceRefList();
        List jonasResourceList = jonasApp.getJonasResourceList();
        HashMap resource = new HashMap();
        for (Iterator i = jonasResourceList.iterator(); i.hasNext();) {
            JonasResource jonasResource = ((JonasResource) i.next());
            String rName = jonasResource.getResRefName();
            boolean findResRef = false;
            for (Iterator j = resourceRefList.iterator(); j.hasNext() && !findResRef;) {
                if (rName.equals(((ResourceRef) j.next()).getResRefName())) {
                    findResRef = true;
                }
            }

            if (!findResRef) {
                throw new DeploymentDescException("resource-ref missing for jonas-resource " + rName);
            }
            resource.put(rName, jonasResource);
        }

        for (Iterator i = resourceRefList.iterator(); i.hasNext();) {
            ResourceRef resourceRef = ((ResourceRef) i.next());
            String rName = resourceRef.getResRefName();

            // Handle some special cases
            String jndiName = null;
            if ("org.omg.CORBA.ORB".equals(resourceRef.getResType())) {
                jndiName = "java:comp/ORB";
            } else if ("javax.transaction.UserTransaction".equals(resourceRef.getResType())) {
                jndiName = "java:comp/UserTransaction";
            }
            if (jndiName != null) {
                JonasResource jonasResource = new JonasResource();
                jonasResource.setResRefName(rName);
                jonasResource.setJndiName(jndiName);
                jonasResourceList.add(jonasResource);
                resource.put(rName, jonasResource);
                resourceRef.setResAuth("Container");
            }

            if (!resource.containsKey(rName)) {
                logger.log(BasicLevel.DEBUG, "Unable to find the given resource, it may be resolved at runtime");
                continue;
            }
            resourceRefs.addElement(new ResourceRefDesc(classLoader, resourceRef, (JonasResource) resource.get(rName)));
        }
    }

    /**
     * @param classLoader the classloader for the deployed module.
     * @param app the data structure of the J2EE application
     * @param jonasApp the data structure of the jonas specific deployment
     *        descriptor
     * @throws DeploymentDescException if the deployment descriptors are
     *         corrupted.
     */
    private void processResourceEnvRef(final ClassLoader classLoader, final JndiEnvRefsGroupXml app, final JonasJndiEnvRefsGroupXml jonasApp) throws DeploymentDescException {
        List resourceEnvRefList = app.getResourceEnvRefList();
        List jonasResourceEnvList = jonasApp.getJonasResourceEnvList();
        HashMap resourceEnv = new HashMap();
        for (Iterator i = jonasResourceEnvList.iterator(); i.hasNext();) {
            JonasResourceEnv jonasResourceEnv = (JonasResourceEnv) i.next();
            String rName = jonasResourceEnv.getResourceEnvRefName();
            boolean findResRef = false;
            for (Iterator j = resourceEnvRefList.iterator(); j.hasNext() && !findResRef;) {
                ResourceEnvRef resourceEnvRef = (ResourceEnvRef) j.next();
                if (rName.equals(resourceEnvRef.getResourceEnvRefName())) {
                    findResRef = true;
                }
            }
            if (!findResRef) {
                throw new DeploymentDescException("resource-env-ref missing for jonas-resource-env " + rName);
            }
            resourceEnv.put(rName, jonasResourceEnv);
        }
        for (Iterator i = resourceEnvRefList.iterator(); i.hasNext();) {
            ResourceEnvRef resourceEnvRef = (ResourceEnvRef) i.next();
            String rName = resourceEnvRef.getResourceEnvRefName();
            if (!resourceEnv.containsKey(rName)) {
                throw new DeploymentDescException("jonas-resource-env missing for resource-env-ref-name " + rName);
            }
            resourceEnvRefs.addElement(new ResourceEnvRefDesc(classLoader, resourceEnvRef,
                    (JonasResourceEnv) resourceEnv.get(rName)));
        }
    }

    /**
     * @param app JndiEnvRefsGroupXml instance
     * @param jonasApp linked JonasJndiEnvRefsGroupXml instance
     * @return Returns a map associating the ServiceRef.name with the JonasServiceRef
     */
    private Map linksSR2JSR(final JndiEnvRefsGroupXml app, final JonasJndiEnvRefsGroupXml jonasApp) {
        Map res = new HashMap();
        // for each sr
        for (Iterator i = app.getServiceRefList().iterator(); i.hasNext();) {
            ServiceRef sr = (ServiceRef) i.next();
            res.put(sr.getServiceRefName(), null);
        }
        // jonas-<desc>.xml
        if (jonasApp != null) {

            // get all ServiceRef.name
            Set keys = res.keySet();

            // for each jonas service ref
            for (Iterator i = jonasApp.getJonasServiceRefList().iterator(); i.hasNext();) {
                JonasServiceRef jsr = (JonasServiceRef) i.next();
                String srName = jsr.getServiceRefName();

                if (keys.contains(srName)) {
                    // jonas-service-ref linked to service-ref
                    res.put(srName, jsr);
                } else {
                    String err = "jonas-service-ref '" + srName + "' is not linked to any service-ref. It will be ignored."; //getI18n().getMessage("WSDeploymentDesc.wsdlDeclareUnknownPort", wsdlf.getName());
                    logger.log(BasicLevel.WARN, err);
                }
            }
        }
        return res;
    }

    /**
     * Get resource environment references.
     *
     * @return array of resource environment reference descriptors
     */
    public IResourceEnvRefDesc[] getResourceEnvRefDesc() {
        IResourceEnvRefDesc[] ret = new IResourceEnvRefDesc[resourceEnvRefs.size()];
        resourceEnvRefs.copyInto(ret);
        return ret;
    }

    /**
     * Get resource manager connection factory references.
     *
     * @return array of resource reference descriptors
     */
    public IResourceRefDesc[] getResourceRefDesc() {
        IResourceRefDesc[] ret = new ResourceRefDesc[resourceRefs.size()];
        resourceRefs.copyInto(ret);
        return ret;
    }

    /**
     * Get environment entries.
     *
     * @return array of Env entries descriptors
     */
    public IEnvEntryDesc[] getEnvEntryDesc() {
        IEnvEntryDesc[] ret = new EnvEntryDesc[envEntries.size()];
        envEntries.copyInto(ret);
        return ret;
    }

    /**
     * Get EJB references.
     *
     * @return array of EJB reference descriptors
     */
    public EjbRefDesc[] getEjbRefDesc() {
        EjbRefDesc[] ret = new EjbRefDesc[ejbRefs.size()];
        ejbRefs.copyInto(ret);
        return ret;
    }

    /**
     * Get ejb local references.
     *
     * @return array of ejb local reference descriptors
     */
    public EjbLocalRefDesc[] getEjbLocalRefDesc() {
        EjbLocalRefDesc[] ret = new EjbLocalRefDesc[ejbLocalRefs.size()];
        ejbLocalRefs.copyInto(ret);
        return ret;
    }

    /**
     * Get service references.
     *
     * @return array of service references descriptors
     */
    public IServiceRefDesc[] getServiceRefDesc() {
        IServiceRefDesc[] ret = new ServiceRefDesc[serviceRefs.size()];
        serviceRefs.copyInto(ret);
        return ret;
    }

    /**
     * Get message-destination references.
     * @return array of message-destination references descriptors
     */
    public MessageDestinationRefDesc[] getMessageDestinationRefDesc() {
        MessageDestinationRefDesc[] ret = new MessageDestinationRefDesc[messageDestinationRefs.size()];
        messageDestinationRefs.copyInto(ret);
        return ret;
    }

    /**
     * @return the ENC Binding holder object.
     */
    public IENCBindingHolder getENCBindingHolder() {
        return encBindingHolder;
    }

    /**
     * Sets the given ENC binding holder.
     * @param encBindingHolder the given environment holder.
     */
    public void setENCBindingHolder(final IENCBindingHolder encBindingHolder) {
        this.encBindingHolder = encBindingHolder;
    }

    /**
     * Get and array of the persistence-unit-refs.
     * @return array of persistence unit refs
     */
    public PersistenceUnitRefDesc[] getPersistenceUnitRefs() {
        PersistenceUnitRefDesc[] ret = new PersistenceUnitRefDesc[persistenceUnitRefs.size()];
        persistenceUnitRefs.copyInto(ret);
        return ret;
    }
}
