/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2009 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: JContainer.java 19500 2010-03-23 09:01:28Z alitokmen $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.lib.ejb21;

import java.io.File;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;

import javax.ejb.AccessLocalException;
import javax.ejb.EJBException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.LinkRef;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.work.WorkManager;
import javax.security.jacc.PolicyContext;
import javax.transaction.Transaction;

import org.objectweb.util.monolog.api.BasicLevel;
import org.ow2.jonas.cmi.CmiService;
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.ejb.BeanDesc;
import org.ow2.jonas.deployment.ejb.DeploymentDesc;
import org.ow2.jonas.deployment.ejb.DeploymentDescEjb2;
import org.ow2.jonas.deployment.ejb.EntityBmpDesc;
import org.ow2.jonas.deployment.ejb.EntityCmpDesc;
import org.ow2.jonas.deployment.ejb.EntityDesc;
import org.ow2.jonas.deployment.ejb.EntityJdbcCmp1Desc;
import org.ow2.jonas.deployment.ejb.EntityJdbcCmp2Desc;
import org.ow2.jonas.deployment.ejb.MessageDrivenDesc;
import org.ow2.jonas.deployment.ejb.SessionStatefulDesc;
import org.ow2.jonas.deployment.ejb.SessionStatelessDesc;
import org.ow2.jonas.ha.HaService;
import org.ow2.jonas.jms.JmsManager;
import org.ow2.jonas.lib.bootstrap.JProp;
import org.ow2.jonas.lib.ejb21.jorm.RdbFactory;
import org.ow2.jonas.lib.ejb21.jorm.RdbMappingBuilder;
import org.ow2.jonas.lib.naming.URLFactory;
import org.ow2.jonas.lib.security.PermissionManagerException;
import org.ow2.jonas.naming.JComponentContextFactory;
import org.ow2.jonas.naming.JNamingManager;
import org.ow2.jonas.resource.ResourceService;
import org.ow2.jonas.tm.TransactionManager;
import org.ow2.jonas.ws.jaxrpc.IJAXRPCService;
import org.ow2.util.event.api.IEventDispatcher;

/**
 * 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.
 * @author Philippe Coq
 * @author Jeff Mesnil (Security)
 * @author Christophe Ney (Making easier Enhydra integration)
 * @author Philippe Durieux (New architecture for local interfaces)
 * @author Florent Benoit (Ear service, ejb-link, JACC security)
 * @author Ludovic Bert (Ear service, ejb-link)
 * @author Benjamin Bonnet (max size for thread pool)
 * @author eyindanga (add life cycle dispatcher)
 */

public class JContainer implements Container {

    /**
     * This class is the default factory class name used for the Entity bean.
     */
    public static final String DEFAULT_FACTORY_CLASS_NAME = "org.ow2.jonas.lib.ejb21.JEntityFactory";

    /**
     * Work Manager, used as a pool of threads.
     */
    private static WorkManager workManager = null;

    /**
     * List of beans (key=EJBName / Value=BeanFactory)
     */
    private HashMap beanList = new HashMap();

    /**
     *  Name of the ear application containing this container.
     */
    private String earFileName = null;

    /**
     * Container file name x.jar or x.xml
     */
    private String fileName;

    /**
     * External file name
     */
    private String externalFileName;

    /**
     * Jms manager
     */
    private JmsManager jms = null;

    /**
     * Classloader used by this container
     */
    private ClassLoader loader = null;

    /**
     * Container name
     */
    private String myname;

    /**
     * Naming object used to do lookup/bind in environment
     */
    private JNamingManager naming = null;

    /**
     * Permission manager of this container. JACC
     */
    private PermissionManager permissionManager = null;

    /**
     * Factory used to make Principal objects
     */
    private PrincipalFactory principalFactory = null;

    /**
     * Enable / disable security
     */
    private boolean securityFlag = true;

    /**
     * Swapper object
     */
    private Swapper swapper;

    /**
     * Transaction manager.
     */
    private TransactionManager tm = null;

    /**
     * Name of the Java EE Application of this EJB (if any)
     */
    private String javaEEApplicationName = null;

    private String tmpDirName = null;

    /**
     * The {@link JComponentContextFactory} instance.
     */
    private JComponentContextFactory componentContextFactory = null;

    /**
     * Container lifecycle dispatcher.
     */
    private IEventDispatcher lifeCycleDispatcher;

    /**
     * This is the default name for JORAM that will be used when trying to configure an MDB
     * when the JMS service is not running.
     */
    private static final String DEFAULT_ACTIVATION_SPEC_NAME = "joramActivationSpec";

    private DeploymentDesc ddesc;

    private RdbMappingBuilder jormMapping = null;

    private MBeanServer mbeanServer = null;

    private IJAXRPCService jaxrpcService = null;

    /**
     * The reference on CMI service.
     */
    private CmiService cmiService;

    /**
     * The reference on HA service.
     */
    private HaService haService;

    /**
     * The reference on Resource service.
     */
    private ResourceService rserv;

    /**
     * constructor
     * @param name name of the container.
     * @param extFileName external file name
     * @param file file name (.jar or .xml)
     * @param ld the class loader to be used
     * @param cmiService the reference on CMI service (can be null)
     * @param haService the reference on HA service (can be null)
     */
    public JContainer(
            final String name,
            final String extFileName,
            final String file,
            final ClassLoader ld,
            final DeploymentDesc dd,
            final CmiService cmiService,
            final HaService haService,
            final IJAXRPCService jaxrpService,
            final MBeanServer jmxserver,
            final ResourceService resService,
            final String workDirectory) {
        this.myname = name;
        this.externalFileName = extFileName;
        this.fileName = file;
        this.loader = ld;
        this.ddesc = dd;
        this.swapper = new Swapper(this);
        this.swapper.start();
        if (TraceEjb.isDebugIc()) {
            TraceEjb.interp.log(BasicLevel.DEBUG, "New Container extFN= " + externalFileName + " filename=" + fileName);
        }
        this.tmpDirName = workDirectory + File.separator + "beans";
        File d = new File(this.tmpDirName);
        d.mkdirs();
        this.mbeanServer = jmxserver;
        this.cmiService = cmiService;
        this.jaxrpcService = jaxrpService;
        this.haService = haService;
        this.rserv = resService;
    }

    public String getTmpDirName() {
        return tmpDirName;
    }

    public RdbMappingBuilder getJormMapping() {
        return jormMapping;
    }

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

    /**
     * Adds beans in container. This method is not part of JContainer interface,
     * although it is used by JOnAS Server, because we don't want to get all
     * BeanDesc classes (jonas_ejb.deployment.api) LATER: Replace this by
     * setDeploymentDesc ?
     * @param dd The Bean Deployment Descriptor
     * @return The bean factory created for this bean.
     * @throws EJBException
     */
    public synchronized BeanFactory addBean(final BeanDesc dd) {
        BeanFactory bf = null;
        String beanName = dd.getEjbName();
        if (dd instanceof SessionStatefulDesc) {
            if (TraceEjb.isDebugIc()) {
                TraceEjb.interp.log(BasicLevel.DEBUG, "add SessionStatefulBean " + beanName);
            }
            bf = new JStatefulFactory((SessionStatefulDesc) dd, this);
        } else if (dd instanceof SessionStatelessDesc) {
            if (TraceEjb.isDebugIc()) {
                TraceEjb.interp.log(BasicLevel.DEBUG, "add SessionStatelessBean " + beanName);
            }
            bf = new JStatelessFactory((SessionStatelessDesc) dd, this);
        } else if (dd instanceof MessageDrivenDesc) {
            if (TraceEjb.isDebugIc()) {
                TraceEjb.interp.log(BasicLevel.DEBUG, "add MessageDrivenBean " + beanName);
            }
            Object obj = null;
            InitialContext ictx = null;
            String destJName = ((MessageDrivenDesc) dd).getDestinationJndiName();
            try {
                ictx = naming.getInitialContext();
                obj = getActivationSpecFromContext(ictx, destJName);
            } catch (NamingException ex) {
                if (TraceEjb.isDebugIc()) {
                    TraceEjb.interp.log(BasicLevel.DEBUG, "Cannot find destination JNDI name " + destJName, ex);
                }
            }
            if (obj != null && obj instanceof ActivationSpec) {
                bf = new JMdbEndpointFactory((MessageDrivenDesc) dd, this, (ActivationSpec) obj, rserv);
            } else if (getJmsManager() == null) {
                // This is the case where we need to attach the MDB to an RAR, but it is either configured
                // as an EJB 2.0 MDB or incorrectly for a 2.1 MDB.
                if (((MessageDrivenDesc)  dd).getDestination() != null) {
                    if (TraceEjb.isDebugIc()) {
                        TraceEjb.interp.log(BasicLevel.DEBUG, "JMS service not started and specified ActivationSpec(" + destJName + ") not deployed");
                    }
                    throw new EJBException("JMS service not started and specified ActivationSpec(" + destJName + ") not deployed");
                }

                // Get the default ActivationSpec name and attempt to configure it
                String dest = DEFAULT_ACTIVATION_SPEC_NAME;  //default name

                try {
                    obj = getActivationSpecFromContext(ictx, dest);
                } catch (Exception ex) {
                    if (TraceEjb.isDebugIc()) {
                        TraceEjb.interp.log(BasicLevel.DEBUG, "JMS service not started and default ActivationSpec(" + dest + ") not deployed");
                    }
                    throw new EJBException("JMS service not started and default ActivationSpec(" + dest + ") not deployed", ex);
                }
                if (obj != null && obj instanceof ActivationSpec) {
                    bf = new JMdbEndpointFactory((MessageDrivenDesc) dd, dest, this, (ActivationSpec) obj, rserv);
                } else {
                    if (TraceEjb.isDebugIc()) {
                        TraceEjb.interp.log(BasicLevel.DEBUG, "Invalid destination: No ActivationSpec deployed matching " + dest);
                    }
                    throw new EJBException("Invalid destination: No ActivationSpec deployed matching " + dest);
                }
            } else {
                // This is the case where a 2.1 MDB is trying to connect to an RAR that isn't deployed
                if (((MessageDrivenDesc)  dd).getDestination() != null) {
                    if (TraceEjb.isDebugIc()) {
                        TraceEjb.interp.log(BasicLevel.DEBUG, "JMS service started and specified ActivationSpec(" + destJName + ") not deployed");
                    }
                    throw new EJBException("JMS service started and specified ActivationSpec(" + destJName + ") not deployed");
                }
                bf = new JMdbFactory((MessageDrivenDesc) dd, this);
            }
        } else if (dd instanceof EntityJdbcCmp2Desc) {
            EntityJdbcCmp2Desc ecd = (EntityJdbcCmp2Desc) dd;
            if (TraceEjb.isDebugIc()) {
                TraceEjb.interp.log(BasicLevel.DEBUG, "add CMP2 EntityBean " + beanName);
            }

            // Build the Jorm Mapping MetaInfo
            if (jormMapping == null) {
                try {
                    jormMapping = new RdbMappingBuilder((DeploymentDescEjb2) ddesc);
                } catch (DeploymentDescException e) {
                    TraceEjb.interp.log(BasicLevel.DEBUG, "Cannot build Jorm MetaInfo", e);
                    throw new EJBException(e);
                }
            }

            // Instanciate the Factory depending on the mapper
            String cn = null;
            try {
                String dsn = ecd.getDatasourceJndiName();
                // Pb: We need the mapper to instanciate the Factory.
                // The mapper is in the Datasource. No way to get it in a
                // standard way
                // since Datasource interface is very simple (getConnection()
                // only)
                //
                // This will now try to call a getMapperName method for the
                // datasource,
                // if it doesn't exist then get the mapper.properties file and
                // look for the
                // jndiname. This allows flexibility for additional data source
                // support,
                // but still is not perfect.

                InitialContext ictx = naming.getInitialContext();
                String mapperName = null;
                // Attempt to call getMapperName method and if it doesn't exist
                // then
                // read the mapper.properties file

                // Must use the loader of the container, because we should
                // be able to get the DataSourceImpl class registered in JNDI.
                ClassLoader old = Thread.currentThread().getContextClassLoader();
                try {
                    Thread.currentThread().setContextClassLoader(loader);
                    Object cls = ictx.lookup(dsn);
                    Method meth = cls.getClass().getMethod("getMapperName", (Class[]) null);
                    mapperName = (String) meth.invoke(cls, (Object[]) null);
                } catch (Exception e1) {
                    TraceEjb.interp.log(BasicLevel.WARN, "Cannot get mapper name from datasource: ", e1);
                } finally {
                    Thread.currentThread().setContextClassLoader(old);
                }
                if (mapperName == null || mapperName.trim().length() == 0) {
                    // Method doesn't exist so go read mapper.properties and
                    // look for jndiname mapping
                    try {
                        JProp dsProps = JProp.getInstance("mapper");
                        mapperName = dsProps.getValue(dsn, "");
                    } catch (Exception e2) {
                        throw new EJBException("Unable to retrieve mapperName for " + dsn, e2);
                    }
                    if (mapperName == null) {
                        throw new EJBException("Unable to retrieve mapperName for " + dsn + ". mappername is null.");
                    }
                }

                cn = ecd.getFactoryClassName();
                bf = (JEntityFactory) loader.loadClass(cn).newInstance();
                ((RdbFactory) bf).init(ecd, this, mapperName);
                // set minimum timeout for the swapper
                setSwapTime(((EntityDesc) ecd).getPassivationTimeout());
                TraceEjb.interp.log(BasicLevel.INFO, beanName + " is loaded and using " + mapperName);
            } catch (Exception e) {
                TraceEjb.interp.log(BasicLevel.ERROR, "Impossible to instanciate the entity factory: " + cn, e);
                throw new EJBException("Impossible to instanciate the entity factory: " + cn, e);
            }
        } else if (dd instanceof EntityDesc) {
            if (TraceEjb.isDebugIc()) {
                TraceEjb.interp.log(BasicLevel.DEBUG, "add EntityBean " + beanName);
            }
            // Instanciate the default factory
            String cn = null;
            try {
                cn = DEFAULT_FACTORY_CLASS_NAME;
                bf = (JEntityFactory) loader.loadClass(cn).newInstance();
            } catch (Exception e) {
                throw new EJBException("Impossible to instanciate the specified entity factory: " + cn, e);
            }
            ((JEntityFactory) bf).init((EntityDesc) dd, this);
            // set minimum timeout for the swapper
            setSwapTime(((EntityDesc) dd).getPassivationTimeout());
        } else {
            throw new EJBException("Bad Descriptor Type for " + beanName);
        }
        beanList.put(beanName, bf);
        TraceEjb.interp.log(BasicLevel.INFO, beanName + " available");

        try {
            Object[] params = {fileName};
            String[] signature = {"java.lang.String"};
            ObjectName areaServiceName = new ObjectName("AreaService", "name", "Service");

            // only use the area service if it was previously registered in the MBeanServer
            if (mbeanServer.isRegistered(areaServiceName)) {
                mbeanServer.invoke(areaServiceName, "addPackageInEJBArea", params, signature);
            }
        } catch (Exception e) {
            TraceEjb.interp.log(BasicLevel.ERROR, "Area service unreachable : " + e.getMessage(), e);
        }

        return bf;
    }

    /**
     * Lookup the ActivationSpec from the Bean InitialContext.
     * @param context JNDI Context of the Bean.
     * @param jndiName JNDI Name of the resource to lookup.
     * @return Returns the instance found if any.
     * @throws NamingException If JNDI lookup fails.
     */
    private Object getActivationSpecFromContext(final InitialContext context, final String jndiName) throws NamingException {
        // save the old TCCL
        ClassLoader old = Thread.currentThread().getContextClassLoader();
        Object obj = null;
        try {
            // set TCCL befaore lookup
            Thread.currentThread().setContextClassLoader(loader);
            // lookup
            obj = context.lookup(jndiName);
        } finally {
            // reset TCCL
            Thread.currentThread().setContextClassLoader(old);
        }
        return obj;
    }


    /**
     * Used by the above getXXXDependence() methods.
     * @param beanFactories the bean factories managed by this container.
     * @param rName jndi name of a resource (datasource, JMS destination or
     *        connection factory, mail factory).
     * @param isResRef true for resource-ref, false for resource-env-ref.
     * @return set of Properties, where each Properties provides the bean name,
     *         the container's file-name and the type of the bean which uses the
     *         resource.
     */
    private Set<Properties> beansDependence(final Enumeration<BeanFactory> beanFactories, final String rName, final boolean isResRef) {
        HashSet<Properties> result = new HashSet<Properties>();
        BeanFactory bf = null;
        while (beanFactories.hasMoreElements()) {
            // for each bean,
            bf = beanFactories.nextElement();
            BeanDesc ejbDesc = bf.getDeploymentDescriptor();

            boolean isDependent = false; // suppose its not dependent

            // get its type from the DeploymentDescriptor
            String ejbType = null;
            if (bf instanceof JEntityFactory) {
                if (ejbDesc instanceof EntityBmpDesc) {
                    ejbType = "ejbbmp";
                }
                if (ejbDesc instanceof EntityCmpDesc) {
                    ejbType = "ejbcmp";
                }
            } else if (bf instanceof JStatefulFactory) {
                ejbType = "ejbsbf";
            } else if (bf instanceof JStatelessFactory) {
                ejbType = "ejbsbl";
            } else if (bf instanceof JMdbFactory || bf instanceof JMdbEndpointFactory) {
                ejbType = "ejbmdb";
            }

            if (isResRef) {
                // if this is a cmp bean, directly compare rName with the data
                // source JNDI name
                // from its deployment descriptor
                if (ejbType.equals("ejbcmp")) {
                    String jndiName = null;
                    if (ejbDesc instanceof EntityJdbcCmp1Desc) {
                        jndiName = ((EntityJdbcCmp1Desc) ejbDesc).getDatasourceJndiName();
                    } else if (ejbDesc instanceof EntityJdbcCmp2Desc) {
                        jndiName = ((EntityJdbcCmp2Desc) ejbDesc).getDatasourceJndiName();
                    }
                    isDependent = rName.equals(jndiName);
                }
                // get the description of the bean's resource-refs
                IResourceRefDesc[] rrDesc = ejbDesc.getResourceRefDesc();
                for (int i = 0; i < rrDesc.length; i++) {
                    // for each resource-ref compare its name with rName
                    if (rrDesc[i].getJndiName().equals(rName)) {
                        isDependent = true;
                        break;
                    }
                }
            } else {
                if (ejbType.equals("ejbmdb")) {
                    if (rName.equals(((MessageDrivenDesc) ejbDesc).getDestinationJndiName())) {
                        isDependent = true;
                    }
                }
                // get the description of the bean's resource-env-refs
                IResourceEnvRefDesc[] rerDesc = ejbDesc.getResourceEnvRefDesc();
                for (int i = 0; i < rerDesc.length; i++) {
                    // for each resource-env-ref compare its name with rName
                    if (rerDesc[i].getJndiName().equals(rName)) {
                        isDependent = true;
                        break;
                    }
                }
            }

            if (isDependent) {
                Properties toAdd = new Properties();
                toAdd.setProperty("type", ejbType);
                toAdd.setProperty("fname", getFileName());
                toAdd.setProperty("name", ejbDesc.getEjbName());
                toAdd.setProperty("cname", getName());
                String earFileName = getEarFileName();
                if (earFileName != null) {
                    toAdd.setProperty("earFileName", earFileName);
                }
                result.add(toAdd);
            }

        }
        return result;
    }

    /**
     * Check Security. No control for Message Driven Beans
     * @param ejbName name of the EJB of which do control
     * @param ejbInv object containing security signature of the method, args of
     *        method, etc
     * @param inRunAs bean calling this method is running in run-as mode or not ?
     */
    protected void checkSecurity(final String ejbName, final EJBInvocation ejbInv, final boolean inRunAs) {
        String oldContextId = PolicyContext.getContextID();

        boolean accessIsOk = false;
        try {
            // Set contextID to PermissionManager contextID (EJB ContextID)
            if (permissionManager != null) {
                accessIsOk = permissionManager.checkSecurity(ejbName, ejbInv, inRunAs);
            }
        } catch (Exception e) {
            TraceEjb.security.log(BasicLevel.ERROR, "Error while checking security", e);
        } finally {
            PolicyContext.setContextID(oldContextId);
        }
        if (!accessIsOk) {
            StringBuffer errMsg = new StringBuffer("Access Denied on bean '");
            errMsg.append(ejbName);
            errMsg.append("' with run-as = '");
            errMsg.append(inRunAs);
            errMsg.append("'. ");
            if (ejbInv != null && ejbInv.methodPermissionSignature != null) {
                errMsg.append(" Method signature = '");
                errMsg.append(ejbInv.methodPermissionSignature);
                errMsg.append("'.");
            }
            throw new AccessLocalException(errMsg.toString());
        }

    }

    /**
     * Get the bean factory for the given bean.
     * @param ejbName the name of the bean
     * @return the bean factory for this bean
     */
    public BeanFactory getBeanFactory(final String ejbName) {
        return (BeanFactory) beanList.get(ejbName);
    }

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

    /**
     * @return the classloader used for this Container
     */
    public ClassLoader getClassLoader() {
        if (loader == null) {
            TraceEjb.logger.log(BasicLevel.ERROR, "container has been removed");
            //return Thread.currentThread().getContextClassLoader();
            return null;
        } else {
            return loader;
        }
    }

    /**
     * used internally by all the EJB Container classes.
     * @return the ContainerNaming object
     */
    public JNamingManager getContainerNaming() {
        return naming;
    }

    /**
     * Management method used by the EJBServiceImpl MBean. Determine which are
     * the ejbs using a given data source.
     * @param dsName JNDI name of the data source
     * @return a set of Properties describing the beans that use the data source
     */
    public Set<Properties> getDataSourceDependence(final String dsName) {
        // the true param corresponds to a resource-ref
        return beansDependence(Collections.enumeration(beanList.values()), dsName, true);
    }

    /**
     * get the name of the ear application containing this container.
     * @return the name of the ear application containing this container.
     */
    public String getEarFileName() {
        return earFileName;
    }

    /**
     * @return int Number of BMP type currently in this container
     */
    public int getEntityBMPNb() {
        BeanFactory bf;
        int total = 0;
        Iterator it = beanList.values().iterator();
        while (it.hasNext()) {
            bf = (BeanFactory) it.next();
            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;
        Iterator it = beanList.values().iterator();
        while (it.hasNext()) {
            bf = (BeanFactory) it.next();
            if (bf.getDeploymentDescriptor() instanceof EntityCmpDesc) {
                total++;
            }
        }
        return total;
    }

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

    /**
     * @return the external file name of the container
     */
    public String getExternalFileName() {
        return externalFileName;
    }

    /**
     * Management method used by the EJBServiceImpl MBean. Determine which are
     * the ejbs using a JMS Connection Factory.
     * @param cfName JNDI name of a JMS Connection Factory.
     * @return a set of Properties describing the beans that use the JMS
     *         Connection Factory.
     */
    public Set<Properties> getJmsConnectionFactoryDependence(final String cfName) {
        // the true param corresponds to a resource-ref
        return beansDependence(Collections.enumeration(beanList.values()), cfName, true);
    }

    /**
     * Management method used by the EJBServiceImpl MBean. Determine which are
     * the beans using a JMS destination.
     * @param destName JNDI name of a JMS destination
     * @return a set of Properties describing the ejbs that use the JMS
     *         destination.
     */
    public Set<Properties> getJmsDestinationDependence(final String destName) {
        // the true param corresponds to a resource-ref
        return beansDependence(Collections.enumeration(beanList.values()), destName, false);
    }

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

    /**
     * Management method used by the EJBServiceImpl MBean. Determine which are
     * the ejbs using a Mail Factory.
     * @param mfName JNDI name of a Mail Factory.
     * @return a set of Properties describing the beans that use the given Mail
     *         Factory.
     */
    public Set<Properties> getMailFactoryDependence(final String mfName) {
        // the true param corresponds to a resource-ref
        return beansDependence(Collections.enumeration(beanList.values()), mfName, true);
    }

    /**
     * @return int Number of MDB type currently in this container
     */
    public int getMessageDrivenNb() {
        BeanFactory bf;
        int total = 0;
        Iterator it = beanList.values().iterator();
        while (it.hasNext()) {
            bf = (BeanFactory) it.next();
            if (bf.getDeploymentDescriptor() instanceof MessageDrivenDesc) {
                total++;
            }
        }
        return total;
    }

    /**
     * @return name of this Container
     */
    public String getName() {
        return myname;
    }

    /**
     * Gets the permission manager
     * @return the permission manager
     */
    public PermissionManager getPermissionManager() {
        return permissionManager;
    }

    /**
     * @return the PrincipalFactory of the Container
     */
    public PrincipalFactory getPrincipalFactory() {
        return principalFactory;
    }

    /**
     * @return int Number of SBF type currently in this container
     */
    public int getStatefulSessionNb() {
        BeanFactory bf;
        int total = 0;
        Iterator it = beanList.values().iterator();
        while (it.hasNext()) {
            bf = (BeanFactory) it.next();
            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;
        Iterator it = beanList.values().iterator();
        while (it.hasNext()) {
            bf = (BeanFactory) it.next();
            if (bf.getDeploymentDescriptor() instanceof SessionStatelessDesc) {
                total++;
            }
        }
        return total;
    }

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

    /**
     * Return true if only if this ejbjar is in an ear file.
     * @return true if only if this ejbjar is in an ear file.
     */
    public boolean isInEarCase() {
        return (earFileName != null);
    }

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

    /**
     * @return List of beans hosted in this Container
     */
    public String[] listBeanNames() {
        return (String[]) beanList.keySet().toArray(new String[0]);
    }

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

    /**
     * register a BeanFactory
     * @param bf The Bean Factory to be registered
     */
    public void registerBF(final BeanFactory bf) {
        swapper.addBeanFactory(bf);
    }

    /**
     * register a BeanFactory for Sync
     * @param bf The Bean Factory to be registered
     */
    public void registerBFS(final BeanFactory bf) {
        swapper.addBeanFactorySync(bf);
    }

    /**
     * Remove the JOnAS container and unregister all beans.
     */
    public synchronized void remove() {
        if (TraceEjb.isDebugIc()) {
            TraceEjb.interp.log(BasicLevel.DEBUG, myname);
        }

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

        // synchronize all entity beans before removing them.
        // must be done here, not in the swapper.
        // This is necessary for CS policy only.
        syncAll(true, false);

        // remove all factories
        for (Iterator it = beanList.values().iterator(); it.hasNext(); ) {
            BeanFactory bf = (BeanFactory) it.next();
            try {
                bf.stop();
                TraceEjb.interp.log(BasicLevel.INFO, myname + ": " + bf.getEJBName() + " no longer available");
                Object[] params = {fileName};
                String[] signature = {"java.lang.String"};
                ObjectName areaServiceName = new ObjectName("AreaService", "name", "Service");
                try {
                    if (mbeanServer.isRegistered(areaServiceName)) {
                        mbeanServer.invoke(areaServiceName, "removePackageFromEJBArea", params, signature);
                    }
                } catch (Exception exc) {
                    TraceEjb.interp.log(BasicLevel.ERROR, "Area service unreachable : " + exc.getMessage(), exc);
                }
            } catch (Exception e) {
                TraceEjb.logger.log(BasicLevel.ERROR, myname, e);
            }
        }
        beanList.clear();

        // to unload all classes.
        loader = null;

        // Remove permission manager
        try {
            if (permissionManager != null) {
                permissionManager.delete();
            }
        } catch (PermissionManagerException pme) {
            TraceEjb.logger.log(BasicLevel.ERROR, myname, pme);
        }
        permissionManager = null;

        // Run the garbage collector
        Runtime.getRuntime().gc();
    }

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

        // Set bean context
        // Build comp/env
        Context ctxold = naming.setComponentContext(ctx);
        Context envCtx = ctx.createSubcontext("comp/env");

        // Bean Environment
        IEnvEntryDesc[] 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.interp.log(BasicLevel.DEBUG, myname + ": Binding object " + name + " -> " + obj);
            }
            envCtx.rebind(name, obj);
        }

        // Resource References
        IResourceRefDesc[] 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();
            String type = resref[i].getTypeName();
            // build the LinkRef that will be registered:
            // FactoryClassName = null, size = 1, refAddr = resname.

            // register object in JNDI
            if (TraceEjb.isDebugIc()) {
                TraceEjb.interp.log(BasicLevel.DEBUG, myname + ": Linking resource " + name + " -> " + resname);
            }

            if (type.equalsIgnoreCase("java.net.URL")) {
                // Specify the factory to use with the right URL
                Reference ref = new Reference("java.net.URL", URLFactory.class.getName(), null);
                StringRefAddr refAddr = new StringRefAddr("url", resname);
                ref.add(refAddr);
                envCtx.rebind(name, ref);
            } else {
                LinkRef lref = new LinkRef(resname);
                envCtx.rebind(name, lref);
            }
        }

        // Resource Environment References
        IResourceEnvRefDesc[] 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 (TraceEjb.isDebugIc()) {
                TraceEjb.interp.log(BasicLevel.DEBUG, myname + ": Linking resource environment " + name + " -> "
                        + resname);
            }
            envCtx.rebind(name, lref);
        }

        // EJB References
        IEJBRefDesc[] ejbref = dd.getEjbRefDesc();
        for (int i = 0; i < ejbref.length; i++) {
            // get information in descriptor
            String name = ejbref[i].getEjbRefName();
            String ejbname = null;
            ejbname = ejbref[i].getJndiName();
            LinkRef lref = new LinkRef(ejbname);

            if (TraceEjb.isDebugIc()) {
                TraceEjb.interp.log(BasicLevel.DEBUG, myname + ": Linking ejb " + name + " -> " + ejbname);
            }
            envCtx.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.
        IEJBLocalRefDesc[] ejblocalref = dd.getEjbLocalRefDesc();
        for (int i = 0; i < ejblocalref.length; i++) {
            String name = ejblocalref[i].getEjbRefName();
            String ejbname = ejblocalref[i].getJndiLocalName();
            LinkRef lref = new LinkRef(ejbname);
            if (TraceEjb.isDebugIc()) {
                TraceEjb.interp.log(BasicLevel.DEBUG, myname + ": Linking ejb " + name + " -> " + ejbname);
            }
            envCtx.rebind(name, lref);
        }

        // ServiceRef
        IServiceRefDesc[] serviceRefs = dd.getServiceRefDesc();
        for (int i = 0; i < serviceRefs.length; i++) {

            // We need the JAX-RPC service started.
            if (jaxrpcService == null) {
                // not started
                TraceEjb.logger.log(BasicLevel.WARN, "JAX-RPC service not started yet");
                break;
            }

            IServiceRefDesc srefdesc = serviceRefs[i];
            Reference ref = jaxrpcService.buildServiceRef(srefdesc, loader);
            String refname = srefdesc.getServiceRefName();
            if (TraceEjb.isDebugIc()) {
                TraceEjb.interp.log(BasicLevel.DEBUG, "Adding service-ref 'java:comp/env/" + refname + "'");
            }
            envCtx.rebind(refname, ref);
        }

        // MessageDestination References
        IMessageDestinationRefDesc[] mdref = dd.getMessageDestinationRefDesc();
        for (int i = 0; i < mdref.length; i++) {
            // get information in descriptor
            String name = mdref[i].getMessageDestinationRefName();
            String mdname = null;
            mdname = mdref[i].getJndiName();
            LinkRef lref = new LinkRef(mdname);

            if (TraceEjb.isDebugIc()) {
                TraceEjb.interp.log(BasicLevel.DEBUG, myname + ": Linking message-destination " + name + " -> " + mdname);
            }
            envCtx.rebind(name, lref);
        }

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

    /**
     * set the ContainerNaming object Called by the EJB Server when starting the
     * service.
     * @param naming the ContainerNaming object
     */
    public void setContainerNaming(final JNamingManager naming) {
        this.naming = naming;
    }

    /**
     * set the name of the ear application containing this container.
     * @param fileName the name of the ear application containing this
     *        container.
     */
    public void setEarFileName(final String fileName) {
        earFileName = fileName;
    }

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


    /**
     * Set the permission manager object
     * @param permissionManager permission manager object
     */
    public void setPermissionManager(final PermissionManager permissionManager) {
        this.permissionManager = permissionManager;
    }

    /**
     * Set the PrincipalFactory. This factory can be JOnAS Server dependant. The
     * Container makes no assumption on how to get the Principal.
     * @param pf the PrincipalFactory
     */
    public void setPrincipalFactory(final PrincipalFactory pf) {
        principalFactory = pf;
    }

    /**
     * Set the security flag to enable or disable security
     * @param b true or false to enable/disable security
     */
    public void setSecurity(final boolean b) {
        securityFlag = b;
    }

    /**
     * Take into account the swapping time for the bean.
     * @param t time in seconds (t = 0 no time out)
     */
    public void setSwapTime(final int t) {
        if (t > 0) {
            if (TraceEjb.isDebugSwapper()) {
                TraceEjb.swapper.log(BasicLevel.DEBUG, myname + " sec=" + t);
            }
            swapper.setSwapperTimeout(t);
        }
    }

    /**
     * set the Work Manager. Called by the EJB Server when starting the
     * service.
     * @param wm the Work Manager.
     */
    public void setWorkManager(final WorkManager wm) {
        workManager = wm;
    }

    /**
     * @return the WorkManager unique instance
     */
    public WorkManager getWorkManager() {
        return workManager;
    }

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

    /**
     * Try to passivate all entity bean instances
     * @param  store True if store even if passivationTimeout not reached
     * @param passivate true if bean instances will be released after having
     *        been written on storage.
     */
    public synchronized void syncAll(final boolean store, final boolean passivate) {
        if (TraceEjb.isDebugIc()) {
            TraceEjb.interp.log(BasicLevel.DEBUG, myname);
        }
        BeanFactory bf = null;
        Iterator it = beanList.values().iterator();
        while (it.hasNext()) {
            bf = (BeanFactory) it.next();
            if (passivate) {
                bf.reduceCache();
            } else {
                bf.syncDirty(store);
            }
        }
    }

    /**
     * Gets the context ID of this container (for jacc)
     * @return contextID used for JACC
     */
    public String getContextId() {
        return externalFileName;
    }

    /**
     * Try to store  all entity bean instances modified in the transaction
     * this method has been introduced to fix bug #305711
     * it's, may be, bad for performance
     * @param  tx
     */
    public synchronized void storeAll(final Transaction tx) {
        if (TraceEjb.isDebugIc()) {
            TraceEjb.interp.log(BasicLevel.DEBUG, myname);
        }
        BeanFactory bf = null;
        Iterator it = beanList.values().iterator();
        while (it.hasNext()) {
            bf = (BeanFactory) it.next();
            bf.storeInstances(tx);
        }
    }

    /**
     * {@inheritDoc}
     */
    public JComponentContextFactory getComponentContextFactory() {
        return componentContextFactory;
    }

    /**
     * {@inheritDoc}
     */
    public void setComponentContextFactory(final JComponentContextFactory factory) {
        this.componentContextFactory = factory;
    }

    /**
     * @return the reference on CMI service
     */
    public HaService getHaService() {
        return haService;
    }

    /**
     * @return the reference on HA service
     */
    public CmiService getCmiService() {
        return cmiService;
    }

    /**
     * Gets the Java EE Application Name of this container.
     * @return the Java EE Application name if there is one
     */
    public String getJavaEEApplicationName() {
        return javaEEApplicationName;
    }

    /**
     * Sets the Java EE Application Name of this container.
     * @param javaEEApplicationName the Java EE Application name
     */
    public void setJavaEEApplicationName(final String javaEEApplicationName) {
        this.javaEEApplicationName = javaEEApplicationName;
    }

    /**
     * @return the lifeCycleDispatcher
     */
    public IEventDispatcher getLifeCycleDispatcher() {
        return lifeCycleDispatcher;
    }

    /**
     * @param lifeCycleDispatcher the lifeCycleDispatcher to set
     */
    public void setLifeCycleDispatcher(final IEventDispatcher lifeCycleDispatcher) {
        this.lifeCycleDispatcher = lifeCycleDispatcher;
    }
}

