/*
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * 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  02111-1307
 * USA
 *
 * Initial developer(s): Christophe Ney - Lutris Technologies Inc http://www.lutris.com
 * Contributor(s):
 * Markus Karg: Novell port.
 * Phlippe Durieux: EJB Local References
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */


package org.objectweb.jonas_ejb.deployment.api;

import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.Iterator;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.Reader;
import java.net.URL;

// SAX imports
import org.xml.sax.ErrorHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import org.objectweb.jonas_ejb.deployment.xml.*;


/**
 * Class to hold extended(*) deployment descriptor information <BR>
 * (*) extended deployment descriptor information includes Jonas specific
 * deployment descriptor file information
 * @author Christophe Ney
 */
public class DeploymentDesc {

    /** The error handler to use */
    private static ErrorHandler errorHandler = null;

    /** The entity resolver to use */
    private static EntityResolver entityResolver = null;

    /** Flag for parser validation */
    private static boolean parsingWithValidation = true;

    private Vector beanDescs = new Vector();
    private String displayName = null;

    public static final String EJB_JAR_FILE_NAME = "META-INF/ejb-jar.xml";
    public static final String JONAS_EJB_JAR_FILE_NAME ="META-INF/jonas-ejb-jar.xml";

    /**
     * constructor is private, call one of the static methods instead
     */
    public DeploymentDesc(ClassLoader classLoader, EjbJar ejbJar, JonasEjbJar jonasEjbJar)
	throws DeploymentDescException {

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

        // display name
        displayName = (ejbJar.getDisplayName() != null) ? ejbJar.getDisplayName().getValue() : null;

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

        // hashtable of jonas-session
        Hashtable jonasSession = new Hashtable();
        for (Iterator i = jonasEjbJar.getJonasSessionList().iterator(); i.hasNext(); ) {
            JonasSession jSes = (JonasSession)i.next();
            jonasSession.put(jSes.getEjbName().getValue(), 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().getValue());
            if (jSes == null) {
		// Build a default jonas-session if not exist
		jSes = new JonasSessionImpl();
		jSes.setEjbName(ses.getEjbName());
	    }
            if (ses.getSessionType().getValue().equals("Stateful")) {
                // stateful
                bd = new SessionStatefulDesc(classLoader, ses, asd, jSes);
            } else if (ses.getSessionType().getValue().equals("Stateless")) {
                // stateless
                bd = new SessionStatelessDesc(classLoader, ses, asd, jSes);
            } else {
                throw new DeploymentDescException("Invalid session-type content for bean "+ses.getEjbName().getValue());
            }
            bd.check();
            beanDescs.addElement(bd);
        }

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

        // hashtable of jonas-message-driven
        Hashtable jonasMessageDriven = new Hashtable();
        for (Iterator i = jonasEjbJar.getJonasMessageDrivenList().iterator(); i.hasNext(); ) {
            JonasMessageDriven jMd = (JonasMessageDriven)i.next();
                jonasMessageDriven.put(jMd.getEjbName().getValue(),jMd);
        }
        // message-driven beans
        for (Iterator i = ejbJar.getEnterpriseBeans().getMessageDrivenList().iterator(); i.hasNext(); ) {
            BeanDesc bd = null;
            MessageDriven md = (MessageDriven)i.next();
            /*
             * The Standard DTD allows a message-driven-bean without ejb-name
             * Problem to associate infos to this bean (trans-attribute, jonas-specific infos)
             * So the ejb-name must be specify and we check this
             */
            if (md.getEjbName()==null)
                throw new DeploymentDescException("ejb-name missing for a message driven bean");
            // find corresponding jonas message-driven
            JonasMessageDriven jMd = (JonasMessageDriven)jonasMessageDriven.get(md.getEjbName().getValue());
            if (jMd == null)
                throw new DeploymentDescException("jonas-message-driven-bean missing for bean "+md.getEjbName().getValue());
            bd = new MessageDrivenDesc(classLoader, md, asd, jMd);
            bd.check();
            beanDescs.addElement(bd);
        }
    }

    /**
     * Factory method with given classloaders
     * @param classLoaderForDD classloader used to load xml deployment descriptors
     * @param classLoaderForCls classloader used to load bean classes
     * @return instance of the corresponding DeploymentDesc
     * @exception DeploymentDescException when DeploymentDesc cannot be created with
     * given files.
     */
    public static DeploymentDesc getInstance(ClassLoader classLoaderForDD,
					     ClassLoader classLoaderForCls)
	throws DeploymentDescException {
        InputStream is;
        EjbJar ejbJar;
        JonasEjbJar jonasEjbJar;

        // load deployment descriptor data
        is = classLoaderForDD.getResourceAsStream(EJB_JAR_FILE_NAME);
        if (is == null) {
            throw new DeploymentDescException(EJB_JAR_FILE_NAME+" resource not found");
        }
        ejbJar = loadEjbJar(is, EJB_JAR_FILE_NAME);
        try {
            is.close();
        } catch(IOException e) {
        }

        // load jonas deployment descriptor data
        is = classLoaderForDD.getResourceAsStream(JONAS_EJB_JAR_FILE_NAME);
        if (is == null) {
            throw new DeploymentDescException(JONAS_EJB_JAR_FILE_NAME+" resource not found");
        }
        jonasEjbJar = loadJonasEjbJar(is, JONAS_EJB_JAR_FILE_NAME);
        try {
            is.close();
        } catch(IOException e) {
        }

        // instantiate deployment descriptor
        return new DeploymentDesc(classLoaderForCls, ejbJar, jonasEjbJar);
    }


    /**
     * Factory method using deployment descriptor and Jonas deployment descriptor
     * file names.
     * @return instance of the corresponding DeploymentDesc
     * @exception DeploymentDescException when DeploymentDesc cannot be created with
     * given files.
     * @deprecated use for integration only (jdk1.2 required)
     */
    public static DeploymentDesc getInstance(String ejbJarFileName, String jonasEjbJarFileName)
        throws DeploymentDescException {

        InputStream is;
        EjbJar ejbJar;
        JonasEjbJar jonasEjbJar;

        // load deployment descriptor data
        try {
            is = new FileInputStream(ejbJarFileName);
        } catch (FileNotFoundException e) {
            throw new DeploymentDescException(ejbJarFileName+" file not found");
        }
        ejbJar = loadEjbJar(is, ejbJarFileName);
        try {
            is.close();
        } catch(IOException e) {
        }

        // load jonas deployment descriptor data
        try {
            is = new FileInputStream(jonasEjbJarFileName);
        } catch (FileNotFoundException e) {
            throw new DeploymentDescException(jonasEjbJarFileName+" file not found");
        }
        jonasEjbJar = loadJonasEjbJar(is, jonasEjbJarFileName);
        try {
            is.close();
        } catch(IOException e) {
        }

        // instantiate deployment descriptor
        ClassLoader cl = DeploymentDesc.class.getClassLoader();
        if (cl == null) {
            cl = ClassLoader.getSystemClassLoader();
        }
        return new DeploymentDesc(cl, ejbJar, jonasEjbJar);
    }


    /**
     * Load the EjbJar file
     */
    private static EjbJar loadEjbJar(InputStream is, String name)
        throws DeploymentDescException {
        Reader reader = new InputStreamReader(is);
        SAXParserHelper helper = new SAXParserHelper(errorHandler,entityResolver);
        try {
            EjbJarUnmarshaller.setErrorHandler(helper.getWrappedErrorHandler());
            EjbJarUnmarshaller.setEntityResolver(helper.getWrappedEntityResolver());
            // Unmarshal the XML, and validate as it is unmarshalled
            return EjbJarUnmarshaller.unmarshal(reader, parsingWithValidation);
        } catch (IOException e) {
            if (helper.getSAXParseException()!=null) {
                String detailMsg=getSAXMsg(name,helper.getSAXParseException(), "Invalid XML");
                throw new DeploymentDescException(detailMsg, e);
            } else {
                throw new DeploymentDescException("Error reading XML document "+ name, e);
            }
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException ignored) { }
            }
        }
    }

    /**
     * Load the EjbJar file
     */
    private static JonasEjbJar loadJonasEjbJar(InputStream is, String name)
        throws DeploymentDescException {
        Reader reader = new InputStreamReader(is);
        SAXParserHelper helper = new SAXParserHelper(errorHandler,entityResolver);
        try {
            JonasEjbJarUnmarshaller.setErrorHandler(helper.getWrappedErrorHandler());
            JonasEjbJarUnmarshaller.setEntityResolver(helper.getWrappedEntityResolver());
            // Unmarshal the XML, and validate as it is unmarshalled
            return JonasEjbJarUnmarshaller.unmarshal(reader, parsingWithValidation);
        } catch (IOException e) {
            if (helper.getSAXParseException()!=null) {
                String detailMsg=getSAXMsg(name,helper.getSAXParseException(), "Invalid XML");
                throw new DeploymentDescException(detailMsg, e);
            } else {
                throw new DeploymentDescException("Error reading XML document "+ name, e);
            }
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException ignored) { }
            }
        }
    }

    /**
     * build a message from SAX Exception in a consistent style, consistent
     * with emacs compile mode (same as grep, cc, javac, etc).
     */
    private static String getSAXMsg(String fileName, SAXParseException exception, String msg) {
        String ret = fileName + ":" + exception.getLineNumber() + ":"+ exception.getColumnNumber() + ": ";
        ret += msg;
        return ret;
    }

    /**
     * Get descriptors for all beans contained in jar file
     * @return Array of bean's descriptors
     */
    public BeanDesc[] getBeanDesc() {
        BeanDesc ret[] = new BeanDesc[beanDescs.size()];
        beanDescs.copyInto(ret);
        return ret;
    }

    /**
     * Get bean descriptor given its name
     */
    public BeanDesc getBeanDesc(String ejbName) {
	BeanDesc ret = null;
	Iterator i = beanDescs.iterator();
	while (i.hasNext()) {
	    BeanDesc dd = (BeanDesc) i.next();
	    if (dd.getEjbName().equals(ejbName)) {
		ret = dd;
		break;
	    }
	}
	return ret;
    }

    /**
     * Get the display name
     */
    public String getDisplayName() {
        return displayName;
    }

    /**
     * String representation of the object for test purpose
     * @return String representation of this object
     */
    public String toString() {
        StringBuffer ret = new StringBuffer();
        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();
    }

    public static ErrorHandler getErrorHandler() {
        return errorHandler;
    }

    public static EntityResolver getEntityResolver() {
        return entityResolver;
    }

    public static boolean getParsingWithValidation() {
	return(parsingWithValidation);
    }

    public static void setErrorHandler(ErrorHandler errorHandler) {
        DeploymentDesc.errorHandler = errorHandler;
    }

    public static void setEntityResolver(EntityResolver entityResolver) {
        DeploymentDesc.entityResolver = entityResolver;
    }

    public static void setParsingWithValidation(boolean v) {
	parsingWithValidation = v;
    }

}

