/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This 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 (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.ejb;

import java.lang.reflect.Method;
import java.net.URL;
import java.security.Policy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Map;

import javax.ejb.EJBLocalHome;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.jacc.EJBMethodPermission;
import javax.security.jacc.PolicyConfiguration;
import javax.security.jacc.PolicyConfigurationFactory;
import javax.security.jacc.PolicyContextException;
import javax.transaction.TransactionManager;

import org.jboss.classloading.spi.RealClassLoader;
import org.jboss.deployers.structure.spi.DeploymentUnit;
import org.jboss.deployers.vfs.spi.structure.VFSDeploymentUnit;
import org.jboss.deployment.DeploymentException;
import org.jboss.ejb.plugins.SecurityProxyInterceptor;
import org.jboss.ejb.plugins.StatefulSessionInstancePool;
import org.jboss.ejb.txtimer.EJBTimerService;
import org.jboss.invocation.InvocationType;
import org.jboss.logging.Logger;
import org.jboss.metadata.ApplicationMetaData;
import org.jboss.metadata.BeanMetaData;
import org.jboss.metadata.ConfigurationMetaData;
import org.jboss.metadata.EntityMetaData;
import org.jboss.metadata.InvokerProxyBindingMetaData;
import org.jboss.metadata.MetaData;
import org.jboss.metadata.MethodMetaData;
import org.jboss.metadata.SessionMetaData;
import org.jboss.metadata.XmlLoadable;
import org.jboss.mx.util.MBeanProxyExt;
import org.jboss.mx.util.ObjectNameFactory;
import org.jboss.security.AuthenticationManager;
import org.jboss.security.ISecurityManagement;
import org.jboss.security.RealmMapping;
import org.jboss.security.SecurityConstants;
import org.jboss.security.SecurityUtil;
import org.jboss.security.authorization.PolicyRegistration;
import org.jboss.security.plugins.SecurityDomainContext;
import org.jboss.system.Registry;
import org.jboss.system.ServiceControllerMBean;
import org.jboss.system.ServiceMBeanSupport;
import org.jboss.tm.TransactionManagerFactory;
import org.jboss.util.loading.DelegatingClassLoader;
import org.jboss.web.WebClassLoader;
import org.jboss.web.WebClassLoaderFactory;
import org.jboss.web.WebServiceMBean;
import org.w3c.dom.Element;

/**
 * An EjbModule represents a collection of beans that are deployed as a unit.
 * 
 * <p>
 * The beans may use the EjbModule to access other beans within the same deployment unit.
 * 
 * @see Container
 * @see EJBDeployer
 * 
 * @author <a href="mailto:rickard.oberg@telkel.com">Rickard Oberg</a>
 * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
 * @author <a href="mailto:reverbel@ime.usp.br">Francisco Reverbel</a>
 * @author <a href="mailto:Adrian.Brock@HappeningTimes.com">Adrian.Brock</a>
 * @author <a href="mailto:Scott.Stark@jboss.org">Scott Stark</a>
 * @author <a href="mailto:Anil.Saldhana@jboss.org">Anil Saldhana</a>
 * @version $Revision: 104065 $
 */
@SuppressWarnings("deprecation")
public class EjbModule extends ServiceMBeanSupport implements EjbModuleMBean
{
   public static final String BASE_EJB_MODULE_NAME = "jboss.j2ee:service=EjbModule";

   public static final ObjectName EJB_MODULE_QUERY_NAME = ObjectNameFactory.create(BASE_EJB_MODULE_NAME + ",*");

   public static String DEFAULT_STATELESS_CONFIGURATION = "Default Stateless SessionBean";

   public static String DEFAULT_STATEFUL_CONFIGURATION = "Default Stateful SessionBean";

   public static String DEFAULT_ENTITY_BMP_CONFIGURATION = "Default BMP EntityBean";

   public static String DEFAULT_ENTITY_CMP_CONFIGURATION = "Default CMP EntityBean";

   public static String DEFAULT_MESSAGEDRIVEN_CONFIGURATION = "Default MesageDriven Bean";

   // Constants uses with container interceptor configurations
   public static final int BMT = 1;

   public static final int CMT = 2;

   public static final int ANY = 3;

   static final String BMT_VALUE = "Bean";

   static final String CMT_VALUE = "Container";

   static final String ANY_VALUE = "Both";

   /** The security management */
   private ISecurityManagement securityManagement;

   /** Class logger. */
   private static final Logger log = Logger.getLogger(EjbModule.class);

   // Constants -----------------------------------------------------

   // Attributes ----------------------------------------------------

   /** HashMap<ejbName, Container> the containers for this deployment unit. */
   HashMap containers = new HashMap();

   /** The containers in their ApplicationMetaData ordering */
   LinkedList containerOrdering = new LinkedList();

   /** HashMap<ejbName, EJBLocalHome> of local homes */
   HashMap localHomes = new HashMap();

   /** Class loader of this deployment unit. */
   ClassLoader classLoader = null;

   /** Name of this deployment unit, url it was deployed from */
   final String name;

   private VFSDeploymentUnit deploymentUnit;

   private ServiceControllerMBean serviceController;

   private final Map moduleData = Collections.synchronizedMap(new HashMap());

   private ObjectName webServiceName;

   private TransactionManagerFactory tmFactory;

   private EJBTimerService timerService;

   /** Whether we are call by value */
   private boolean callByValue;

   private ApplicationMetaData appMetaData;

   /**
    * Policy Registration Bean (Holder of Authorization Policies)
    */
   private PolicyRegistration policyRegistration = null;

   public EjbModule(final VFSDeploymentUnit unit, ApplicationMetaData metaData)
   {
      this.appMetaData = metaData;
      this.deploymentUnit = unit;
      String name = deploymentUnit.getName();
      if (name.endsWith("/"))
      {
         name = name.substring(0, name.length() - 1);
      }
      this.name = name;

      // FIXME all this deployment unit access should be replaced with deployers processing updating the metadata
      // Ask the ejb deployer whether we are call by value
      Boolean flag = unit.getAttachment("EJB.callByValue", Boolean.class);
      DeploymentUnit temp = unit;
      while (flag == null && temp != null)
      {
         // Ask the ear deployer whether we are call by value
         flag = temp.getAttachment("EAR.callByValue", Boolean.class);
         if (flag != null)
            break;
         temp = temp.getParent();
      }
      if (flag != null)
         callByValue = flag.booleanValue();

      // Set the unauthenticated identity on the metadata if absent
      if (metaData.getUnauthenticatedPrincipal() == null)
      {
         String unauthenticatedPrincipal = unit.getAttachment("EJB.unauthenticatedIdentity", String.class);
         if (unauthenticatedPrincipal == null)
            unauthenticatedPrincipal = unit.getAttachment("EAR.unauthenticatedIdentity", String.class);
         metaData.getJBossMetaData().setUnauthenticatedPrincipal(unauthenticatedPrincipal);
      }
      // Add the ApplicationMetaData for the jbossws ejb21 deployer to pickup later
      if (unit.getAttachment(ApplicationMetaData.class) == null)
         unit.addAttachment(ApplicationMetaData.class, metaData);
   }

   public void setTransactionManagerFactory(TransactionManagerFactory tm)
   {
      this.tmFactory = tm;
   }

   public void setSecurityManagement(ISecurityManagement sm)
   {
      this.securityManagement = sm;
   }

   public void setPolicyRegistration(PolicyRegistration policyRegistration)
   {
      this.policyRegistration = policyRegistration;
   }

   public EJBTimerService getTimerService()
   {
      return timerService;
   }

   public void setTimerService(EJBTimerService timerService)
   {
      this.timerService = timerService;
   }

   public ObjectName getWebServiceName()
   {
      return webServiceName;
   }

   public void setWebServiceName(ObjectName webServiceName)
   {
      this.webServiceName = webServiceName;
   }

   public Map getModuleDataMap()
   {
      return moduleData;
   }

   public Object getModuleData(Object key)
   {
      return moduleData.get(key);
   }

   public void putModuleData(Object key, Object value)
   {
      moduleData.put(key, value);
   }

   public void removeModuleData(Object key)
   {
      moduleData.remove(key);
   }

   /**
    * Add a container to this deployment unit.
    * 
    * @param con
    */
   private void addContainer(Container con) throws DeploymentException
   {
      String ejbName = con.getBeanMetaData().getEjbName();
      if (containers.containsKey(ejbName))
         throw new DeploymentException("Duplicate ejb-name. Container for " + ejbName + " already exists.");
      containers.put(ejbName, con);
      containerOrdering.add(con);
      con.setEjbModule(this);
   }

   /**
    * Remove a container from this deployment unit.
    * 
    * @param con
    */
   public void removeContainer(Container con)
   {
      containers.remove(con.getBeanMetaData().getEjbName());
      containerOrdering.remove(con);
   }

   public void addLocalHome(Container con, EJBLocalHome localHome)
   {
      localHomes.put(con.getBeanMetaData().getEjbName(), localHome);
   }

   public void removeLocalHome(Container con)
   {
      localHomes.remove(con.getBeanMetaData().getEjbName());
   }

   public EJBLocalHome getLocalHome(Container con)
   {
      return (EJBLocalHome) localHomes.get(con.getBeanMetaData().getEjbName());
   }

   /**
    * Whether the container is call by value
    * 
    * @return true for call by value
    */
   public boolean isCallByValue()
   {
      return callByValue;
   }

   /**
    * Get a container from this deployment unit that corresponds to a given name
    * 
    * @param name ejb-name name defined in ejb-jar.xml
    * 
    * @return container for the named bean, or null if the container was not found
    */
   public Container getContainer(String name)
   {
      return (Container) containers.get(name);
   }

   /**
    * Get all containers in this deployment unit.
    * 
    * @return a collection of containers for each enterprise bean in this deployment unit.
    * @jmx:managed-attribute
    */
   public Collection getContainers()
   {
      return containerOrdering;
   }

   /**
    * Get the class loader of this deployment unit.
    * 
    * @return
    */
   public ClassLoader getClassLoader()
   {
      return classLoader;
   }

   /**
    * Set the class loader of this deployment unit
    * 
    * @param cl
    */
   public void setClassLoader(ClassLoader cl)
   {
      this.classLoader = cl;
   }

   /**
    * Get the URL from which this deployment unit was deployed
    * 
    * @return The URL from which this Application was deployed.
    */
   public URL getURL()
   {
      return appMetaData.getUrl();
   }

   // Service implementation ----------------------------------------

   @Override
   protected void createService() throws Exception
   {
      serviceController = (ServiceControllerMBean) MBeanProxyExt.create(ServiceControllerMBean.class,
            ServiceControllerMBean.OBJECT_NAME, server);

      log.debug("createService, begin");

      // Set up the beans in this module.
      try
      {
         Iterator beans = appMetaData.getEnterpriseBeans();
         String contextID = appMetaData.getJaccContextID();
         if (contextID == null)
            contextID = deploymentUnit.getSimpleName();
         // appMetaData.gsetJaccContextID(contextID);
         /* PolicyConfiguration pc = null; */
         while (beans.hasNext())
         {
            BeanMetaData bean = (BeanMetaData) beans.next();
            log.info("Deploying " + bean.getEjbName());
            Container con = createContainer(bean, deploymentUnit);
            addContainer(con);
            // @todo support overriding the context id via metadata is needed
            con.setJaccContextID(contextID);
         }

         // only one iteration should be necessary, but we won't sweat it.
         // 2 iterations are needed by cmp...jdbc/bridge/JDBCCMRFieldBridge which
         // assumes persistence managers are all set up for every
         // bean in the relationship!
         ListIterator iter = containerOrdering.listIterator();
         while (iter.hasNext())
         {
            Container con = (Container) iter.next();
            ObjectName jmxName = con.getJmxName();
            /*
             * Add the container mbean to the deployment mbeans so the state of the deployment can be tracked.
             */
            server.registerMBean(con, jmxName);
            // deploymentUnit.mbeans.add(jmxName);
            BeanMetaData metaData = con.getBeanMetaData();
            Collection<ObjectName> depends = new ArrayList<ObjectName>();
            for (String dependsName : metaData.getDepends())
            {
               depends.add(ObjectName.getInstance(dependsName));
            }
            Iterator<String> invokerBindings = metaData.getInvokerBindings();
            while (invokerBindings != null && invokerBindings.hasNext())
            {
               String invokerBindingName = invokerBindings.next();
               InvokerProxyBindingMetaData ipbmd = appMetaData.getInvokerProxyBindingMetaDataByName(invokerBindingName);
               if (ipbmd != null)
               {
                  String invokerName = ipbmd.getInvokerMBean();
                  if (invokerName != null)
                  {
                     try
                     {
                        ObjectName invokerMBean = ObjectName.getInstance(invokerName);
                        if (depends.contains(invokerMBean) == false)
                           depends.add(invokerMBean);
                     }
                     catch (MalformedObjectNameException e)
                     {
                        log.trace("Ignored malformed invoker mbean '" + invokerName + "' " + e.toString());
                     }
                  }
               }
            }
            serviceController.create(jmxName, depends);
            // We keep the hashCode around for fast creation of proxies
            int jmxHash = jmxName.hashCode();
            Registry.bind(new Integer(jmxHash), jmxName);
            log.debug("Bound jmxName=" + jmxName + ", hash=" + jmxHash + "into Registry");
         }
      }
      catch (Exception e)
      {
         destroyService();
         throw e;
      } // end of try-catch

   }

   /**
    * The mbean Service interface <code>start</code> method calls the start method on each contatiner, then the init
    * method on each container. Conversion to a different registration system with one-phase startup is conceivable.
    * 
    * @exception Exception if an error occurs
    */
   @Override
   protected void startService() throws Exception
   {
      // before EntityContainer returns from the startService, its PM should be usable
      ListIterator iter = containerOrdering.listIterator();
      while (iter.hasNext())
      {
         Container con = (Container) iter.next();
         if (con.getBeanMetaData().isEntity())
         {
            ClassLoader oldCl = SecurityActions.getContextClassLoader();
            SecurityActions.setContextClassLoader(con.getClassLoader());
            con.pushENC();
            try
            {
               ((EntityContainer) con).getPersistenceManager().start();
            }
            finally
            {
               con.popENC();
               // Reset classloader
               SecurityActions.setContextClassLoader(oldCl);
            }
         }
      }

      iter = containerOrdering.listIterator();
      while (iter.hasNext())
      {
         Container con = (Container) iter.next();
         log.debug("startService, starting container: " + con.getBeanMetaData().getEjbName());
         serviceController.start(con.getJmxName());
      }
   }

   /**
    * Stops all the containers of this application.
    */
   @Override
   protected void stopService() throws Exception
   {
      ListIterator iter = containerOrdering.listIterator(containerOrdering.size());
      while (iter.hasPrevious())
      {
         Container con = (Container) iter.previous();
         try
         {
            ObjectName jmxName = con.getJmxName();
            // The container may already be destroyed so validate metaData
            BeanMetaData metaData = con.getBeanMetaData();
            String ejbName = metaData != null ? metaData.getEjbName() : "Unknown";
            log.debug("stopService, stopping container: " + ejbName);

            serviceController.stop(jmxName);
         }
         catch (Exception e)
         {
            log.error("unexpected exception stopping Container: " + con.getJmxName(), e);
         } // end of try-catch
      }
   }

   @Override
   protected void destroyService() throws Exception
   {
      WebServiceMBean webServer = null;
      if (webServiceName != null)
      {
         webServer = (WebServiceMBean) MBeanProxyExt.create(WebServiceMBean.class, webServiceName);
      }
      ListIterator iter = containerOrdering.listIterator(containerOrdering.size());
      while (iter.hasPrevious())
      {
         Container con = (Container) iter.previous();
         ObjectName jmxName = con.getJmxName();
         int conState = con.getState();
         boolean destroyContainer = true;
         log.debug("Looking to destroy container: " + jmxName + ", state: " + con.getStateString() + ", destroy: "
               + destroyContainer);

         // always unregister from Registry
         int jmxHash = jmxName.hashCode();
         Registry.unbind(new Integer(jmxHash));

         // Unregister the web classloader
         // Removing the wcl should probably be done in stop of the container,
         // but I don't want to look for errors today.
         if (webServer != null)
         {
            ClassLoader wcl = con.getWebClassLoader();
            if (wcl != null)
            {
               try
               {
                  webServer.removeClassLoader(wcl);
               }
               catch (Throwable e)
               {
                  log.warn("Failed to unregister webClassLoader", e);
               }
            }
         }

         // Only destroy containers that have been created or started
         if (destroyContainer)
         {
            try
            {
               serviceController.destroy(jmxName);
               serviceController.remove(jmxName);
               log.info("Undeployed " + con.getBeanMetaData().getEjbName());
               if (server.isRegistered(jmxName))
                  server.unregisterMBean(jmxName);
            }
            catch (Throwable e)
            {
               log.error("unexpected exception destroying Container: " + jmxName, e);
            } // end of try-catch
         }

         // Destroy proxy factories
         if (destroyContainer)
         {
            if (con.getBeanMetaData() != null && con.getBeanMetaData().getInvokerBindings() != null)
            {
               Iterator<String> invokerBindings = con.getBeanMetaData().getInvokerBindings();
               while (invokerBindings.hasNext())
               {
                  String invoker = invokerBindings.next();
                  EJBProxyFactory ci = con.lookupProxyFactory(invoker);
                  if (ci != null)
                  {
                     ci.setContainer(null);
                     ci.setInvokerBinding(null);
                     ci.setInvokerMetaData(null);
                  }
               }
            }
         }

         // cleanup container
         con.setBeanMetaData(null);
         con.setWebClassLoader(null);
         con.setClassLoader(null);
         con.setEjbModule(null);
         con.setTransactionManager(null);
         con.setSecurityManager(null);
         con.setRealmMapping(null);
         con.setSecurityProxy(null);
         con.setSecurityManagement(null);
         con.setPolicyRegistration(null);
         con.proxyFactories.clear();
      }

      this.containers.clear();
      this.localHomes.clear();
      this.containerOrdering.clear();
      this.moduleData.clear();
      this.serviceController = null;
   }

   // ******************
   // Container Creation
   // ******************

   private Container createContainer(BeanMetaData bean, VFSDeploymentUnit unit) throws Exception
   {
      Container container = null;
      // Added message driven deployment
      if (bean.isMessageDriven())
      {
         container = createMessageDrivenContainer(bean, unit);
      }
      else if (bean.isSession()) // Is session?
      {
         if (((SessionMetaData) bean).isStateless()) // Is stateless?
         {
            container = createStatelessSessionContainer((SessionMetaData) bean, unit);
         }
         else
         // Stateful
         {
            container = createStatefulSessionContainer((SessionMetaData) bean, unit);
         }
      }
      else
      // Entity
      {
         container = createEntityContainer(bean, unit);
      }

      container.setDeploymentUnit(unit);

      return container;
   }

   private MessageDrivenContainer createMessageDrivenContainer(BeanMetaData bean, DeploymentUnit unit) throws Exception
   {
      // get the container configuration for this bean
      // a default configuration is now always provided
      ConfigurationMetaData conf = bean.getContainerConfiguration();
      // Stolen from Stateless deploy
      // Create container
      MessageDrivenContainer container = new MessageDrivenContainer();
      int transType = bean.isContainerManagedTx() ? CMT : BMT;

      initializeContainer(container, conf, bean, transType, unit);
      createProxyFactories(bean, container);
      container.setInstancePool(createInstancePool(conf, unit.getClassLoader()));

      return container;
   }

   private StatelessSessionContainer createStatelessSessionContainer(SessionMetaData bean, DeploymentUnit unit)
         throws Exception
   {
      // get the container configuration for this bean
      // a default configuration is now always provided
      ConfigurationMetaData conf = bean.getContainerConfiguration();
      // Create container
      StatelessSessionContainer container = new StatelessSessionContainer();
      int transType = bean.isContainerManagedTx() ? CMT : BMT;
      initializeContainer(container, conf, bean, transType, unit);
      if (bean.getHome() != null || bean.getServiceEndpoint() != null)
      {
         createProxyFactories(bean, container);
      }
      container.setInstancePool(createInstancePool(conf, unit.getClassLoader()));

      return container;
   }

   private StatefulSessionContainer createStatefulSessionContainer(SessionMetaData bean, DeploymentUnit unit)
         throws Exception
   {
      // get the container configuration for this bean
      // a default configuration is now always provided
      ConfigurationMetaData conf = bean.getContainerConfiguration();
      // Create container
      StatefulSessionContainer container = new StatefulSessionContainer();
      int transType = bean.isContainerManagedTx() ? CMT : BMT;
      initializeContainer(container, conf, bean, transType, unit);
      if (bean.getHome() != null || bean.getServiceEndpoint() != null)
      {
         createProxyFactories(bean, container);
      }

      ClassLoader cl = unit.getClassLoader();
      container.setInstanceCache(createInstanceCache(conf, cl));
      // No real instance pool, use the shadow class
      StatefulSessionInstancePool ip = new StatefulSessionInstancePool();
      ip.importXml(conf.getContainerPoolConf());
      container.setInstancePool(ip);
      // Set persistence manager
      container.setPersistenceManager((StatefulSessionPersistenceManager) cl.loadClass(conf.getPersistenceManager())
            .newInstance());
      // Set the bean Lock Manager
      container.setLockManager(createBeanLockManager(container, false, conf.getLockClass(), cl));

      return container;
   }

   private EntityContainer createEntityContainer(BeanMetaData bean, DeploymentUnit unit) throws Exception
   {
      // get the container configuration for this bean
      // a default configuration is now always provided
      ConfigurationMetaData conf = bean.getContainerConfiguration();
      // Create container
      EntityContainer container = new EntityContainer();
      int transType = CMT;
      initializeContainer(container, conf, bean, transType, unit);
      if (bean.getHome() != null)
      {
         createProxyFactories(bean, container);
      }

      ClassLoader cl = unit.getClassLoader();
      container.setInstanceCache(createInstanceCache(conf, cl));
      container.setInstancePool(createInstancePool(conf, cl));
      // Set the bean Lock Manager
      boolean reentrant = ((EntityMetaData) bean).isReentrant();
      BeanLockManager lockMgr = createBeanLockManager(container, reentrant, conf.getLockClass(), cl);
      container.setLockManager(lockMgr);

      // Set persistence manager
      if (((EntityMetaData) bean).isBMP())
      {
         Class pmClass = cl.loadClass(conf.getPersistenceManager());
         EntityPersistenceManager pm = (EntityPersistenceManager) pmClass.newInstance();
         container.setPersistenceManager(pm);
      }
      else
      {
         // CMP takes a manager and a store
         org.jboss.ejb.plugins.CMPPersistenceManager persistenceManager = new org.jboss.ejb.plugins.CMPPersistenceManager();

         // Load the store from configuration
         Class pmClass = cl.loadClass(conf.getPersistenceManager());
         EntityPersistenceStore pm = (EntityPersistenceStore) pmClass.newInstance();
         persistenceManager.setPersistenceStore(pm);
         // Set the manager on the container
         container.setPersistenceManager(persistenceManager);
      }

      return container;
   }

   // **************
   // Helper Methods
   // **************

   /**
    * Perform the common steps to initializing a container.
    */
   private void initializeContainer(Container container, ConfigurationMetaData conf, BeanMetaData bean, int transType,
         DeploymentUnit unit) throws NamingException, DeploymentException
   {
      // Create local classloader for this container
      // For loading resources that must come from the local jar. Not for loading classes!
      // The VFS should be used for this
      // container.setLocalClassLoader(new URLClassLoader(new URL[0], localCl));
      // Set metadata (do it *before* creating the container's WebClassLoader)
      container.setEjbModule(this);
      container.setBeanMetaData(bean);

      ClassLoader unitCl = unit.getClassLoader();
      // Create the container's WebClassLoader
      // and register it with the web service.
      String webClassLoaderName = getWebClassLoader(conf, bean);
      log.debug("Creating WebClassLoader of class " + webClassLoaderName);
      WebClassLoader wcl = null;
      try
      {
         Class clazz = unitCl.loadClass(webClassLoaderName);
         wcl = WebClassLoaderFactory.createWebClassLoader(clazz, container.getJmxName(), (RealClassLoader) unitCl);
      }
      catch (Exception e)
      {
         throw new DeploymentException("Failed to create WebClassLoader of class " + webClassLoaderName + ": ", e);
      }

      if (webServiceName != null)
      {
         WebServiceMBean webServer = (WebServiceMBean) MBeanProxyExt.create(WebServiceMBean.class, webServiceName);
         URL[] codebase = {webServer.addClassLoader(wcl)};

         wcl.setWebURLs(codebase);
      } // end of if ()

      container.setWebClassLoader(wcl);
      // Create classloader for this container
      // Only used to unique the bean ENC and does not augment class loading
      container.setClassLoader(new DelegatingClassLoader(wcl));

      // Set transaction manager
      InitialContext iniCtx = new InitialContext();
      container.setTransactionManager(tmFactory.getTransactionManager());

      // Set
      container.setTimerService(timerService);

      // Set security domain manager
      String securityDomain = bean.getApplicationMetaData().getSecurityDomain();
      // JBAS-5960: Set default security domain if there is security metadata
      boolean hasSecurityMetaData = hasSecurityMetaData(bean);
      if (securityDomain == null && hasSecurityMetaData)
      {
         securityDomain = SecurityConstants.DEFAULT_EJB_APPLICATION_POLICY;
      }
      String confSecurityDomain = conf.getSecurityDomain();
      // Default the config security to the application security manager
      if (confSecurityDomain == null)
         confSecurityDomain = securityDomain;

      // Check for an empty confSecurityDomain which signifies to disable security
      if (confSecurityDomain != null && confSecurityDomain.length() == 0)
         confSecurityDomain = null;

      if (confSecurityDomain != null)
      { // Either the application has a security domain or the container has security setup
         try
         {
            String unprefixed = SecurityUtil.unprefixSecurityDomain(confSecurityDomain);
            log.debug("Setting security domain from: " + confSecurityDomain);
            String domainCtx = SecurityConstants.JAAS_CONTEXT_ROOT + "/" + unprefixed + "/domainContext";
            SecurityDomainContext sdc = (SecurityDomainContext) iniCtx.lookup(domainCtx);
            Object securityMgr = sdc.getSecurityManager();

            // Object securityMgr = iniCtx.lookup(confSecurityDomain);
            AuthenticationManager ejbS = (AuthenticationManager) securityMgr;
            RealmMapping rM = (RealmMapping) securityMgr;
            container.setSecurityManager(ejbS);
            container.setRealmMapping(rM);

            container.setSecurityManagement(securityManagement);
            container.setPolicyRegistration(policyRegistration);

            container.setDefaultSecurityDomain((String) unit.getAttachment("EJB.defaultSecurityDomain"));
            container.setSecurityContextClassName((String) unit.getAttachment("EJB.securityContextClassName"));
         }
         catch (NamingException e)
         {
            throw new DeploymentException("Could not find the security-domain, name=" + confSecurityDomain, e);
         }
         catch (Exception e)
         {
            throw new DeploymentException("Invalid security-domain specified, name=" + confSecurityDomain, e);
         }
      }
      else
      {
         if ("".equals(securityDomain) && hasSecurityMetaData)
            log.warn("EJB configured to bypass security. Please verify if this is intended. Bean=" + bean.getEjbName()
                  + " Deployment=" + unit.getName());
      }

      // Load the security proxy instance if one was configured
      String securityProxyClassName = bean.getSecurityProxy();
      if (securityProxyClassName != null)
      {
         try
         {
            Class proxyClass = unitCl.loadClass(securityProxyClassName);
            Object proxy = proxyClass.newInstance();
            container.setSecurityProxy(proxy);
            log.debug("setSecurityProxy, " + proxy);
         }
         catch (Exception e)
         {
            throw new DeploymentException("Failed to create SecurityProxy of type: " + securityProxyClassName, e);
         }
      }

      // Install the container interceptors based on the configuration
      addInterceptors(container, transType, conf.getContainerInterceptorsConf());
   }

   /**
    * Return the name of the WebClassLoader class for this ejb.
    */
   private static String getWebClassLoader(ConfigurationMetaData conf, BeanMetaData bmd) throws DeploymentException
   {
      String webClassLoader = null;
      Iterator it = bmd.getInvokerBindings();
      int count = 0;
      while (it.hasNext())
      {
         String invoker = (String) it.next();
         ApplicationMetaData amd = bmd.getApplicationMetaData();
         InvokerProxyBindingMetaData imd = amd.getInvokerProxyBindingMetaDataByName(invoker);
         if (imd == null)
         {
            String msg = "Failed to find InvokerProxyBindingMetaData for: '" + invoker
                  + "'. Check the invoker-proxy-binding-name to " + "invoker-proxy-binding/name mappings in jboss.xml";
            throw new DeploymentException(msg);
         }

         Element proxyFactoryConfig = imd.getProxyFactoryConfig();
         String webCL = MetaData.getOptionalChildContent(proxyFactoryConfig, "web-class-loader");
         if (webCL != null)
         {
            log.debug("Invoker " + invoker + " specified WebClassLoader class" + webCL);
            webClassLoader = webCL;
            count++;
         }
      }
      if (count > 1)
      {
         log.warn(count + " invokers have WebClassLoader specifications.");
         log.warn("Using the last specification seen (" + webClassLoader + ").");
      }
      else if (count == 0)
      {
         webClassLoader = conf.getWebClassLoader();
         if (webClassLoader == null)
            webClassLoader = "org.jboss.web.WebClassLoader";
      }
      return webClassLoader;
   }

   /**
    * Given a container-interceptors element of a container-configuration, add the indicated interceptors to the
    * container depending on the container transcation type.
    * 
    * @param container the container instance to setup.
    * @param transType one of the BMT, CMT or ANY constants.
    * @param element the container-interceptors element from the container-configuration.
    */
   private void addInterceptors(Container container, int transType, Element element) throws DeploymentException
   {
      // Get the interceptor stack(either jboss.xml or standardjboss.xml)
      Iterator interceptorElements = MetaData.getChildrenByTagName(element, "interceptor");
      String transTypeString = stringTransactionValue(transType);
      ClassLoader loader = container.getClassLoader();
      /*
       * First build the container interceptor stack from interceptorElements match transType values
       */
      ArrayList istack = new ArrayList();
      while (interceptorElements != null && interceptorElements.hasNext())
      {
         Element ielement = (Element) interceptorElements.next();
         /*
          * Check that the interceptor is configured for the transaction mode of the bean by comparing its 'transaction'
          * attribute to the string representation of transType
          */
         String transAttr = ielement.getAttribute("transaction");
         if (transAttr == null || transAttr.length() == 0)
            transAttr = ANY_VALUE;
         if (transAttr.equalsIgnoreCase(ANY_VALUE) || transAttr.equalsIgnoreCase(transTypeString))
         { // The transaction type matches the container bean trans type

            String className = null;
            try
            {
               className = MetaData.getFirstElementContent(ielement, null);
               Class clazz = loader.loadClass(className);
               Interceptor interceptor = (Interceptor) clazz.newInstance();
               if (interceptor instanceof XmlLoadable)
               {
                  ((XmlLoadable) interceptor).importXml(ielement);
               }
               istack.add(interceptor);
            }
            catch (ClassNotFoundException e)
            {
               log.warn("Could not load the " + className + " interceptor", e);
            }
            catch (Exception e)
            {
               log.warn("Could not load the " + className + " interceptor for this container", e);
            }
         }
      }

      if (istack.size() == 0)
         log.warn("There are no interceptors configured. Check the standardjboss.xml file");

      // Now add the interceptors to the container
      for (int i = 0; i < istack.size(); i++)
      {
         Interceptor interceptor = (Interceptor) istack.get(i);
         container.addInterceptor(interceptor);
      }

      /*
       * If there is a security proxy associated with the container add its interceptor just before the container
       * interceptor
       */
      if (container.getSecurityProxy() != null)
         container.addInterceptor(new SecurityProxyInterceptor());

      // Finally we add the last interceptor from the container
      container.addInterceptor(container.createContainerInterceptor());
   }

   /**
    * Create any JACC permissions for the ejb methods that were not explicitly assigned method-permission or
    * exclude-list mappings.
    * 
    * @param con - the ejb container
    * @param bean - the bean metadata
    * @throws ClassNotFoundException
    * @throws PolicyContextException
    */
   void createMissingPermissions(Container con, BeanMetaData bean) throws ClassNotFoundException,
         PolicyContextException
   {
      String contextID = con.getJaccContextID();
      PolicyConfigurationFactory pcFactory = PolicyConfigurationFactory.getPolicyConfigurationFactory();
      PolicyConfiguration pc = pcFactory.getPolicyConfiguration(contextID, false);
      Class clazz = con.getHomeClass();
      // If there is no security domain mark all methods as unchecked
      boolean hasSecurityDomain = con.getSecurityManager() != null;
      boolean exclude = hasSecurityDomain ? bean.isExcludeMissingMethods() : false;

      if (clazz != null)
      {
         addMissingMethodPermissions(bean, exclude, clazz, InvocationType.HOME, pc);
      }
      clazz = con.getLocalHomeClass();
      if (clazz != null)
      {
         addMissingMethodPermissions(bean, exclude, clazz, InvocationType.LOCALHOME, pc);
      }
      clazz = con.getLocalClass();
      if (clazz != null)
      {
         addMissingMethodPermissions(bean, exclude, clazz, InvocationType.LOCAL, pc);
      }
      clazz = con.getRemoteClass();
      if (clazz != null)
      {
         addMissingMethodPermissions(bean, exclude, clazz, InvocationType.REMOTE, pc);
      }

      if (pc.inService() == false)
         pc.commit();
      // Allow the policy to incorporate the policy configs
      Policy.getPolicy().refresh();
   }

   private void getInterfaces(Class iface, HashSet tmp)
   {
      tmp.add(iface);
      Class[] ifaces = iface.getInterfaces();
      for (int n = 0; n < ifaces.length; n++)
      {
         Class iface2 = ifaces[n];
         tmp.add(iface2);
         getInterfaces(iface2, tmp);
      }
   }

   private void addMissingMethodPermissions(BeanMetaData bean, boolean exclude, Class iface, InvocationType type,
         PolicyConfiguration pc) throws PolicyContextException
   {
      String ejbName = bean.getEjbName();
      HashSet tmp = new HashSet();
      getInterfaces(iface, tmp);
      Class[] ifaces = new Class[tmp.size()];
      tmp.toArray(ifaces);
      for (int n = 0; n < ifaces.length; n++)
      {
         Class c = ifaces[n];
         Method[] methods = c.getDeclaredMethods();
         for (int m = 0; m < methods.length; m++)
         {
            String methodName = methods[m].getName();
            Class[] params = methods[m].getParameterTypes();
            // See if there is a method-permission
            if (bean.hasMethodPermission(methodName, params, type))
               continue;
            // Create a permission for the missing method-permission
            EJBMethodPermission p = new EJBMethodPermission(ejbName, type.toInterfaceString(), methods[m]);
            if (exclude)
               pc.addToExcludedPolicy(p);
            else
               pc.addToUncheckedPolicy(p);
         }
      }
   }

   private static String stringTransactionValue(int transType)
   {
      String transaction = ANY_VALUE;
      switch (transType)
      {
         case BMT :
            transaction = BMT_VALUE;
            break;
         case CMT :
            transaction = CMT_VALUE;
            break;
      }
      return transaction;
   }

   /**
    * Create all proxy factories for this ejb
    */
   private static void createProxyFactories(BeanMetaData conf, Container container) throws Exception
   {
      ClassLoader cl = container.getClassLoader();
      Iterator it = conf.getInvokerBindings();
      boolean foundOne = false;
      while (it.hasNext())
      {
         String invoker = (String) it.next();
         String jndiBinding = conf.getInvokerBinding(invoker);
         log.debug("creating binding for " + jndiBinding + ":" + invoker);
         InvokerProxyBindingMetaData imd = conf.getApplicationMetaData().getInvokerProxyBindingMetaDataByName(invoker);
         EJBProxyFactory ci = null;

         // create a ProxyFactory instance
         try
         {
            ci = (EJBProxyFactory) cl.loadClass(imd.getProxyFactory()).newInstance();
            ci.setContainer(container);
            ci.setInvokerMetaData(imd);
            ci.setInvokerBinding(jndiBinding);
            if (ci instanceof XmlLoadable)
            {
               // the container invoker can load its configuration from the jboss.xml element
               ((XmlLoadable) ci).importXml(imd.getProxyFactoryConfig());
            }
            container.addProxyFactory(invoker, ci);
            foundOne = true;
         }
         catch (Exception e)
         {
            log.warn("The Container Invoker " + invoker
                  + " (in jboss.xml or standardjboss.xml) could not be created because of " + e
                  + " We will ignore this error, but you may miss a transport for this bean.");
         }
      }
      if (!foundOne)
      {
         throw new DeploymentException("Missing or invalid Container Invokers (in jboss.xml or standardjboss.xml).");
      }
   }

   private static BeanLockManager createBeanLockManager(Container container, boolean reentrant, String beanLock,
         ClassLoader cl) throws Exception
   {
      // The bean lock manager
      BeanLockManager lockManager = new BeanLockManager(container);

      Class lockClass = null;
      try
      {
         if (beanLock == null)
            beanLock = "org.jboss.ejb.plugins.lock.QueuedPessimisticEJBLock";
         lockClass = cl.loadClass(beanLock);
      }
      catch (Exception e)
      {
         throw new DeploymentException("Missing or invalid lock class (in jboss.xml or standardjboss.xml): " + beanLock
               + " - " + e);
      }

      lockManager.setLockCLass(lockClass);
      lockManager.setReentrant(reentrant);

      return lockManager;
   }

   private static InstancePool createInstancePool(ConfigurationMetaData conf, ClassLoader cl) throws Exception
   {
      // Set instance pool
      InstancePool ip = null;

      try
      {
         ip = (InstancePool) cl.loadClass(conf.getInstancePool()).newInstance();
      }
      catch (Exception e)
      {
         throw new DeploymentException("Missing or invalid Instance Pool (in jboss.xml or standardjboss.xml)", e);
      }

      if (ip instanceof XmlLoadable)
         ((XmlLoadable) ip).importXml(conf.getContainerPoolConf());

      return ip;
   }

   private static InstanceCache createInstanceCache(ConfigurationMetaData conf, ClassLoader cl) throws Exception
   {
      // Set instance cache
      InstanceCache ic = null;

      try
      {
         ic = (InstanceCache) cl.loadClass(conf.getInstanceCache()).newInstance();
      }
      catch (Exception e)
      {
         throw new DeploymentException("Missing or invalid Instance Cache (in jboss.xml or standardjboss.xml)", e);
      }

      if (ic instanceof XmlLoadable)
         ((XmlLoadable) ic).importXml(conf.getContainerCacheConf());

      return ic;
   }

   private boolean hasSecurityMetaData(BeanMetaData bean)
   {
      boolean hasSecMetaData = false;
      Iterator<MethodMetaData> iter = bean.getPermissionMethods();
      while (iter.hasNext())
      {
         MethodMetaData method = iter.next();
         if (!method.isUnchecked())
         {
            hasSecMetaData = true;
            break;
         }
      }

      return hasSecMetaData;
   }
}
/*
 * vim:ts=3:sw=3:et
 */
