/*
 * 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): Philippe Coq
 * Contributor(s): 
 *   Jeff Mesnil     : Security
 *   Christophe Ney  : for making easier Enhydra integration
 *   Philippe Durieux: New architecture for local interfaces
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas_ejb.container;

import java.security.Guard;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.Set;
import java.util.HashSet;
import java.util.Vector;
import java.util.Properties;
import javax.ejb.AccessLocalException;
import javax.ejb.EJBException;
import javax.naming.Context;
import javax.naming.LinkRef;
import javax.naming.StringRefAddr;
import javax.naming.NamingException;
import javax.transaction.NotSupportedException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.TransactionRequiredException;

import org.objectweb.common.Env;
import org.objectweb.jonas_ejb.deployment.api.BeanDesc;
import org.objectweb.jonas_ejb.deployment.api.DeploymentDesc;
import org.objectweb.jonas_ejb.deployment.api.EjbLocalRefDesc;
import org.objectweb.jonas_ejb.deployment.api.EjbRefDesc;
import org.objectweb.jonas_ejb.deployment.api.EnvEntryDesc;
import org.objectweb.jonas_ejb.deployment.api.MethodDesc;
import org.objectweb.jonas_ejb.deployment.api.ResourceEnvRefDesc;
import org.objectweb.jonas_ejb.deployment.api.ResourceRefDesc;
import org.objectweb.jonas_ejb.deployment.api.SessionDesc;
import org.objectweb.jonas_ejb.deployment.api.SessionStatefulDesc;
import org.objectweb.jonas_ejb.deployment.api.SessionStatelessDesc;
import org.objectweb.jonas_ejb.deployment.api.EntityDesc;
import org.objectweb.jonas_ejb.deployment.api.EntityBmpDesc;
import org.objectweb.jonas_ejb.deployment.api.EntityCmpDesc;
import org.objectweb.jonas_ejb.deployment.api.EntityJdbcCmpDesc;
import org.objectweb.jonas_ejb.deployment.api.MessageDrivenDesc;
import org.objectweb.jonas_ejb.lib.BeanNaming;
import org.objectweb.jonas_jms.api.JmsManager;
import org.enhydra.naming.ContainerNaming;

/**
 * This class represents an EJB container.
 *
 * A container is where an enterprise Bean object lives. 
 * All beans from a same ejb-jar file are installed in a single container.
 * For each EJB installed, the container provides a factory and
 * makes it available in the JNDI name space.
 * The JContainer basically manages a set of BeanFactory objects.
 */
public class JContainer implements Container {

    private Swapper swapper;
    private ClassLoader loader = null;
    private ContainerNaming naming = null;
    private TransactionManager tm = null;
    private JmsManager jms = null;
    private DeploymentDesc ejbdesc = null;

    public String myname; // Container name 
    public String fileName; // Container file name x.jar or x.xml
    private Vector beanList = new Vector(); // set of BeanFactory's
    private Guard methodGuard = null;
    private Guard roleGuard = null;
    private PrincipalFactory principalFactory = null;

    public JStatefulPool statefulSessionPool = null;

    // Pool of threads for Message Driven Beans
    // We use only 1 pool for all containers.
    private static final int DEFAULT_THREAD_POOL_SIZE = 10;
    int threadpoolsize = DEFAULT_THREAD_POOL_SIZE;
    static ThreadPool threadpool = null;

    /**
     * constructor
     */
    public JContainer(String name, String file, ClassLoader ld) {
	myname = name;
	fileName = file;
	loader = ld;
	swapper = new Swapper(this);
	swapper.start();
    }

    // ---------------------------------------------------------------
    // Container Implementation
    // ---------------------------------------------------------------

    /**
     * Return an array of bean names
     */
    public String [] listBeanNames() {

	String ret[] = new String[beanList.size()];
	Vector namelist = new Vector();
	BeanFactory bf = null;
	Enumeration enum = beanList.elements();
	while (enum.hasMoreElements()) {
	    bf = (BeanFactory) enum.nextElement();
	    namelist.addElement(bf.getEJBName());
	}
	namelist.copyInto(ret);
	return ret;
    }

    /**
     * Return the name of the container
     */
    public String getName() {
	return myname;
    }

    /**
     * Return the file name of the container (.xml or .jar)
     */
    public String getFileName() {
	return fileName;
    }

    /**
     * Set a MethodGuard
     */
    public void setMethodGuard(Guard methodGuard) {
	if (TraceEjb.isDebugSecurity())
	    TraceEjb.debugSecurity(myname+": setMethodGuard("+methodGuard+")");
	this.methodGuard = methodGuard;
    }
    
    /**
     * Get the MethodGuard
     */
    public Guard getMethodGuard() {
	return methodGuard;
    }

    /**
     * Set the RoleGuard
     */
    public void setRoleGuard (Guard roleGuard) {
	if (TraceEjb.isDebugSecurity())
	    TraceEjb.debugSecurity(myname+": setRoleGuard("+roleGuard+")");
	this.roleGuard = roleGuard;
    }

    /**
     * Get the RoleGuard
     */
    public Guard getRoleGuard() {
	return roleGuard;
    }

    /**
     * Returns the classloader used for this Container
     */
    public ClassLoader getClassLoader() {
	if (loader == null) {
	    return ClassLoader.getSystemClassLoader();
	} else {
	    return loader;
	}
    }

    /**
     * Set the PrincipalFactory
     */
    public void setPrincipalFactory(PrincipalFactory pf) {
	principalFactory = pf;
    }

    /**
     * Get the PrincipalFactory
     */
    public PrincipalFactory getPrincipalFactory() {
	return principalFactory;
    }

    /**
     * set the ContainerNaming object
     * Called by the EJB Server when starting the service.
     */
   public void setContainerNaming(ContainerNaming naming) {
	this.naming = naming;
   }
  
    /**
     * return the ContainerNaming object
     * used internally by all the EJB Container classes.
     */
    public ContainerNaming getContainerNaming() {
	return naming;
    }

    /**
     * set the Thread pool size.
     */
    public void setThreadPoolSize(int sz) {
	threadpoolsize = sz;
    }

    /**
     * Try to passivate all entity bean instances
     */
    public void syncAll() {
	TraceEjb.debugIc(myname, ": syncAll");
	BeanFactory bf = null;
	Enumeration enum = beanList.elements();
	while (enum.hasMoreElements()) {
	    bf = (BeanFactory) enum.nextElement();
	    bf.sync();
	}
    }

    /**
     * set the Transaction Manager.
     * Called by the EJB Server when starting the service.
     */
    public void setTransactionManager(TransactionManager tm) {
	this.tm = tm;
    }

    /**
     * return the Transaction Manager
     * used internally by all the EJB Container classes.
     */
    public TransactionManager getTransactionManager() {
	return tm;
    }

    /**
     * set the JmsManager object
     * Called by the EJB Server when starting ths service.
     */
   public void setJmsManager(JmsManager jms) {
	this.jms = jms;
   }

    /**
     * return the JmsManager object
     * used internally by all the EJB Container classes.
     */
    public JmsManager getJmsManager() {
	return jms;
    }

    /**
     * Remove the JOnAS container.
     */
    public void remove() {
	TraceEjb.debugIc(myname,": remove");

	// Stop the swapper.
	swapper.stopIt();

	// synchronize all entity beans before removing them.
	// must be done here, not in the swapper.
	syncAll();
	
	// remove all factories
	Enumeration enum = beanList.elements();
	while (enum.hasMoreElements()) {
	    BeanFactory bf = (BeanFactory) enum.nextElement();
	    try {
		bf.stop();
		System.out.println(myname+": "+ bf.getEJBName() + " no longer available");
	    } catch (Exception e) {
		TraceEjb.error(myname+": remove:", e);
	    }
	}
	beanList.removeAllElements();

	loader = null;
    }


    // -------------------------------------------------------------------
    // Methods used by EJBServer (but not part of JContainer interface)
    // -------------------------------------------------------------------

    /**
     * Set EjbDescriptor. Needed for ejb-link in ejb-ref and ejb-local-ref.
     * LATER: May be better to replace addBean by this.
     */
    public void setEjbDescriptor(DeploymentDesc ejbdesc) {
	this.ejbdesc = ejbdesc;
    }
    
    /**
     * Adds beans in container.
     * This method is not part of JContainer interface, although it
     * is used by EJBServer, because we don't want to get all BeanDesc
     * classes (jonas_ejb.deployment.api)
     * @param desc  The Bean Deployment Descriptor
     * @throw EJBException
     */
    public BeanFactory addBean(BeanDesc dd) {
	BeanFactory bf = null;
	String beanName = dd.getEjbName();
	if (dd instanceof SessionStatefulDesc) {
	    TraceEjb.debugIc("add SessionStatefulBean ", beanName);
	    if (statefulSessionPool == null) {
		statefulSessionPool = new JStatefulPool();
	    }
	    bf = new JStatefulFactory((SessionStatefulDesc)dd, this, statefulSessionPool);
	} else if (dd instanceof SessionStatelessDesc) {
	    TraceEjb.debugIc("add SessionStatelessBean ", beanName);
	    bf = new JStatelessFactory((SessionStatelessDesc)dd, this);
	} else if (dd instanceof MessageDrivenDesc) {
	    TraceEjb.debugIc("add MessageDrivenBean ", beanName);
	    if (threadpool == null) {
		threadpool = new ThreadPool(threadpoolsize);
	    }
	    bf = new JMdbFactory((MessageDrivenDesc)dd, this, threadpool);
	} else if (dd instanceof EntityDesc) {
	    TraceEjb.debugIc("add EntityBean ", beanName);
	    bf = new JEntityFactory();
	    ((JEntityFactory)bf).init((EntityDesc)dd, this);
	    // Set the passivation timeout
	    setSwapTime(((EntityDesc)dd).getPassivationTimeout());
	} else {
	    throw new EJBException("Bad Descriptor Type for "+beanName);
	}
	beanList.addElement(bf);
	System.out.println(beanName + " available");
	return bf;
    }

    /**
     * @return total Number of Beans
     */
    public int getBeanNb() {
	return beanList.size();
    }

    /**
     * @return int Number of BMP type currently in this container
     */
    public int getEntityBMPNb() {
	BeanFactory bf;
	int total = 0;
	Enumeration enum = beanList.elements();
	while (enum.hasMoreElements()) {
	    bf = (BeanFactory) enum.nextElement();
	    if (bf.getDeploymentDescriptor() instanceof EntityBmpDesc) total++;
	}
	return total;
    }

    /**
     * @return int Number of CMP type currently in this container
     */
    public int getEntityCMPNb() {
	BeanFactory bf;
	int total = 0;
	Enumeration enum = beanList.elements();
	while (enum.hasMoreElements()) {
	    bf = (BeanFactory) enum.nextElement();
	    if (bf.getDeploymentDescriptor() instanceof EntityCmpDesc) total++;
	}
	return total;
    }

    /**
     * @return int Number of SBF type currently in this container
     */
    public int getStatefulSessionNb() {
	BeanFactory bf;
	int total = 0;
	Enumeration enum = beanList.elements();
	while (enum.hasMoreElements()) {
	    bf = (BeanFactory) enum.nextElement();
	    if (bf.getDeploymentDescriptor() instanceof SessionStatefulDesc) total++;
	}
	return total;
    }

    /**
     * @return int Number of SBL type currently in this container
     */
    public int getStatelessSessionNb() {
	BeanFactory bf;
	int total = 0;
	Enumeration enum = beanList.elements();
	while (enum.hasMoreElements()) {
	    bf = (BeanFactory) enum.nextElement();
	    if (bf.getDeploymentDescriptor() instanceof SessionStatelessDesc) total++;
	}
	return total;
    }

    /**
     * @return int Number of MDB type currently in this container
     */
    public int getMessageDrivenNb() {
	BeanFactory bf;
	int total = 0;
	Enumeration enum = beanList.elements();
	while (enum.hasMoreElements()) {
	    bf = (BeanFactory) enum.nextElement();
	    if (bf.getDeploymentDescriptor() instanceof MessageDrivenDesc) total++;
	}
	return total;
    }

    /**
     * @return passivation time out for this container
     */
    public long getSwapTime() {
	return swapper.getTimeout();
    }

    // -------------------------------------------------------------------
    // Other public methods, used internally in this package.
    // -------------------------------------------------------------------

    /**
     * register an Entity that must be passivated
     */
    public void registerEJB(JEntitySwitch ejb) {
	TraceEjb.debugIc(myname, ": registerEJB");

	swapper.addEntity(ejb);
    }

    /**
     * register a BeanFactory
     */
    public void registerBF(BeanFactory bf) {
	TraceEjb.debugIc(myname, ": registerBF");

	swapper.addBeanFactory(bf);
    }

    /**
     * Take into account the swapping time for the bean.
     * @args time in seconds  (time = 0 no time out)
     */
    public void setSwapTime(int t) {
	TraceEjb.debugIc(myname, ": setSwapTime "+t);
	if (t > 0) {
	    swapper.setTimeout(1000*t);
	}
    }

    /**
     * Set the bean environment
     * args ctx Context for this bean
     * args dd Bean Deployment Descriptor
     * throws NamingException if could not rebind objects
     */
    public void setBeanEnvironment(Context ctx, BeanDesc dd) throws NamingException {

	// Set bean context
	Context ctxold = naming.setComponentContext(ctx);

	// Bean Environment
	EnvEntryDesc [] envt = dd.getEnvEntryDesc();
	for (int i = 0; i < envt.length; i++) {
	    // get information in descriptor
	    String name = envt[i].getName();
	    Object obj = envt[i].getValue();
	    // register object in JNDI
	    if (TraceEjb.isDebugIc()) 
		TraceEjb.debugIc(myname+": Binding object "+name+" -> "+obj);
	    ctx.rebind(name, obj);
	}

	// Resource References
	ResourceRefDesc [] resref = dd.getResourceRefDesc();
	for (int i = 0; i < resref.length; i++) {
	    // get information in descriptor
	    String name = resref[i].getName();
	    String resname = resref[i].getJndiName();
	    // build the LinkRef that will be registered:
	    // FactoryClassName = null, size = 1, refAddr = resname.
	    LinkRef lref = new LinkRef(resname);

	    // if we are using David add a parameter to the link to tell that the
	    // object must be found in rmiregistry (and not in CosNaming)
	    // this info is used by CompNamingContext.lookup()
	    if (Env.isDavid()) {
		lref.add(new StringRefAddr("TargetNaming", "RmiRegistry"));
	    }

	    // register object in JNDI
	    if (TraceEjb.isDebugIc()) 
		TraceEjb.debugIc(myname+": Linking resource "+name+" -> "+resname);
	    ctx.rebind(name, lref);
	}

	// Resource Environment References
	ResourceEnvRefDesc [] resEnvref = dd.getResourceEnvRefDesc();
	for (int i = 0; i < resEnvref.length; i++) {
	    // get information in descriptor
	    String name = resEnvref[i].getName();
	    String resname = resEnvref[i].getJndiName();
	    LinkRef lref = new LinkRef(resname);

	    // if we are using David add a parameter to the link to tell that the
	    // object must be found in rmiregistry (and not in CosNaming)
	    // this info is used by CompNamingContext.lookup()
	    if (Env.isDavid()) {
		lref.add(new StringRefAddr("TargetNaming", "RmiRegistry"));
	    }
    
	    if (TraceEjb.isDebugIc()) 
		TraceEjb.debugIc(myname+": Linking resource environment "+name
				     +" -> "+resname);
	    ctx.rebind(name, lref);
	}

	// EJB References
	EjbRefDesc [] ejbref = dd.getEjbRefDesc();
	for (int i = 0; i < ejbref.length; i++) {
	    // get information in descriptor
	    String name = ejbref[i].getName();
	    String ejbname = null;
	    String ejblink = ejbref[i].getEjbLink();
	    if ((ejblink != null) && (ejblink.indexOf(".jar#") == -1)) {
		// Ignore the ejb-link value when the ejblink is in a different ejb-jar file
		// "product.jar#ProductEJB" syntax not already taken into account	
		BeanDesc bd = ejbdesc.getBeanDesc(ejblink);
		if (bd == null) {
		    throw new NamingException("Incorrect ejb-link for ejb-local-ref '"+name
					      + "' of "+dd.getEjbName());
		}
		ejbname = bd.getJndiName();
	    } else {
		ejbname = ejbref[i].getJndiName();
		if (ejbname == null) {
		    throw new NamingException("jonas-ejb-ref missing for ejb-ref '"+name
					      + "' of "+dd.getEjbName());
		}
	    }
	    LinkRef lref = new LinkRef(ejbname);
	    
	    // if we are using David add a parameter to the link to tell that the
	    // object must be found in the CosNaming (and not in rmiregistry)
	    // this info is used by CompNamingContext.lookup()
	    if (Env.isDavid()) {
		lref.add(new StringRefAddr("TargetNaming", "CosNaming"));
	    }

	    if (TraceEjb.isDebugIc()) 
		TraceEjb.debugIc(myname+": Linking ejb "+name+" -> "+ejbname);
	    ctx.rebind(name, lref);
	}

	// EJB Local Refs
	// We use here ejb-link tag. This should be used also for ejb-ref when
	// we are able to manage references to another jar file.
	EjbLocalRefDesc [] ejblocalref = dd.getEjbLocalRefDesc();
	for (int i = 0; i < ejblocalref.length; i++) {
	    String name = ejblocalref[i].getName();
	    String ejblink = ejblocalref[i].getEjbLink();
	    BeanDesc bd = ejbdesc.getBeanDesc(ejblink);
	    if (bd == null) {
		throw new NamingException("Incorrect ejb-link for ejb-local-ref '"+name
					  + "' of "+dd.getEjbName());
	    }
	    String ejbname = bd.getJndiLocalName();
	    LinkRef lref = new LinkRef(ejbname);
	    if (TraceEjb.isDebugIc()) 
		TraceEjb.debugIc(myname+": Linking ejb "+name+" -> "+ejbname);
	    ctx.rebind(name, lref);
	}

	// Reset bean context
	naming.setComponentContext(ctxold);
    }

    /**
     * Check Security. No control for Message Driven Beans
     */
    protected void checkSecurity(String methodSignature) {
	// A "null" string means : no security control.
	if (methodSignature.equals("null")) {
	    return;
	}
	// methodGuard == null : no security service available
	if (methodGuard == null) {
	    TraceEjb.debugSecurity("JContainer.checkSecurity:  no security service available");
	    return;
	}
	TraceEjb.debugSecurity("JContainer.checkSecurity()");
	try {
	    methodGuard.checkGuard(methodSignature);
	} catch (SecurityException e) {
	    throw new AccessLocalException("Access Denied");
	}
    }

    // -------------------------------------------------------------------
    // Private methods
    // -------------------------------------------------------------------

}
