/* * JBoss, the OpenSource J2EE webOS * * Distributable under LGPL license. * See terms of license at gnu.org. */ package org.jboss.ejb; import java.lang.reflect.Method; import java.rmi.RemoteException; import java.util.Map; import java.util.HashMap; import java.util.Iterator; import java.util.Collection; import java.util.Hashtable; import javax.ejb.Handle; import javax.ejb.HomeHandle; import javax.ejb.EJBObject; import javax.ejb.EJBLocalObject; import javax.ejb.EJBHome; import javax.ejb.EJBLocalHome; import javax.ejb.EJBMetaData; import javax.ejb.RemoveException; import javax.ejb.EJBException; import javax.transaction.Transaction; import javax.management.ObjectName; import org.jboss.invocation.Invocation; import org.jboss.invocation.MarshalledInvocation; import org.jboss.monitor.StatisticsProvider; import org.jboss.metadata.EntityMetaData; import org.jboss.util.collection.SerializableEnumeration; /** * This is a Container for EntityBeans (both BMP and CMP). * * @see Container * @see EntityEnterpriseContext * * @author Rickard Öberg * @author Marc Fleury * @author Sebastien Alborini * @author Daniel OConnor * @author Bill Burke * @author Andreas Schaefer * @author Dain Sundstrom * @version $Revision: 1.80.2.9 $ * * @jmx:mbean extends="org.jboss.ejb.ContainerMBean" */ public class EntityContainer extends Container implements EJBProxyFactoryContainer, InstancePoolContainer, EntityContainerMBean { /** * These are the mappings between the home interface methods and the * container methods. */ protected Map homeMapping = new HashMap(); /** * These are the mappings between the remote/local interface methods and the * bean methods. */ protected Map beanMapping = new HashMap(); /** This is the persistence manager for this container */ protected EntityPersistenceManager persistenceManager; /** This is the instance cache for this container */ protected InstanceCache instanceCache; /** This is the instancepool that is to be used */ protected InstancePool instancePool; protected TxEntityMap txEntityMap = new TxEntityMap(); /** * This is the first interceptor in the chain. The last interceptor must * be provided by the container itself. */ protected Interceptor interceptor; /** * readOnly determines if state can be written to resource manager. */ protected boolean readOnly = false; /** * This provides a way to find the entities that are part of a given * transaction EntitySynchronizationInterceptor and InstanceSynchronization * manage this instance. */ protected static GlobalTxEntityMap globalTxEntityMap = new GlobalTxEntityMap(); public static GlobalTxEntityMap getGlobalTxEntityMap() { return globalTxEntityMap; } /** * Stores all of the entities associated with the specified transaction. * As per the spec 9.6.4, entities must be synchronized with the datastore * when an ejbFind is called. * Also, all entities within entire transaction should be synchronized before * a remove, otherwise there may be problems with 'cascade delete'. * * @param tx the transaction that associated entites will be stored * @throws Exception if an problem occures while storing the entities */ public static void synchronizeEntitiesWithinTransaction(Transaction tx) { // If there is no transaction, there is nothing to synchronize. if(tx != null) { getGlobalTxEntityMap().synchronizeEntities(tx); } } // Public -------------------------------------------------------- public boolean isReadOnly() { return readOnly; } public LocalProxyFactory getLocalProxyFactory() { return localProxyFactory; } public void setInstancePool(InstancePool ip) { if (ip == null) throw new IllegalArgumentException("Null pool"); this.instancePool = ip; ip.setContainer(this); } public InstancePool getInstancePool() { return instancePool; } public TxEntityMap getTxEntityMap() { return txEntityMap; } public void setInstanceCache(InstanceCache ic) { if (ic == null) throw new IllegalArgumentException("Null cache"); this.instanceCache = ic; ic.setContainer(this); } public InstanceCache getInstanceCache() { return instanceCache; } public EntityPersistenceManager getPersistenceManager() { return persistenceManager; } public void setPersistenceManager(EntityPersistenceManager pm) { if (pm == null) throw new IllegalArgumentException("Null persistence manager"); persistenceManager = pm; pm.setContainer(this); } public void addInterceptor(Interceptor in) { if (interceptor == null) { interceptor = in; } else { Interceptor current = interceptor; while (current.getNext() != null) { current = current.getNext(); } current.setNext(in); } } public Interceptor getInterceptor() { return interceptor; } public Class getHomeClass() { return homeInterface; } public Class getRemoteClass() { return remoteInterface; } /** * Returns a new instance of the bean class or a subclass of the bean class. * If this is 1.x cmp, simply return a new instance of the bean class. * If this is 2.x cmp, return a subclass that provides an implementation * of the abstract accessors. * * @see java.lang.Class#newInstance * * @return The new instance. */ public Object createBeanClassInstance() throws Exception { return persistenceManager.createBeanClassInstance(); } // Container implementation -------------------------------------- protected void createService() throws Exception { // Associate thread with classloader ClassLoader oldCl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(getClassLoader()); try { // Acquire classes from CL if (metaData.getHome() != null) homeInterface = classLoader.loadClass(metaData.getHome()); if (metaData.getRemote() != null) remoteInterface = classLoader.loadClass(metaData.getRemote()); // Call default init super.createService(); // Map the bean methods setupBeanMapping(); // Map the home methods setupHomeMapping(); // Map the interfaces to Long setupMarshalledInvocationMapping(); // Initialize pool instancePool.create(); // Try to register the instance pool as an MBean try { ObjectName containerName = super.getJmxName(); Hashtable props = containerName.getKeyPropertyList(); props.put("plugin", "pool"); ObjectName poolName = new ObjectName(containerName.getDomain(), props); server.registerMBean(instancePool, poolName); } catch(Throwable t) { log.debug("Failed to register cache as mbean", t); } for (Iterator it = proxyFactories.keySet().iterator(); it.hasNext(); ) { String invokerBinding = (String)it.next(); EJBProxyFactory ci = (EJBProxyFactory)proxyFactories.get(invokerBinding); ci.create(); } // Init instance cache instanceCache.create(); // Try to register the instance cache as an MBean try { ObjectName containerName = super.getJmxName(); Hashtable props = containerName.getKeyPropertyList(); props.put("plugin", "cache"); ObjectName cacheName = new ObjectName(containerName.getDomain(), props); server.registerMBean(instanceCache, cacheName); } catch(Throwable t) { log.debug("Failed to register cache as mbean", t); } // Init persistence persistenceManager.create(); // Initialize the interceptor by calling the chain Interceptor in = interceptor; while (in != null) { in.setContainer(this); in.create(); in = in.getNext(); } readOnly = ((EntityMetaData)metaData).isReadOnly(); } finally { // Reset classloader Thread.currentThread().setContextClassLoader(oldCl); } } protected void startService() throws Exception { // Associate thread with classloader ClassLoader oldCl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(getClassLoader()); try { // Call default start super.startService(); // Start container invokers for (Iterator it = proxyFactories.keySet().iterator(); it.hasNext(); ) { String invokerBinding = (String)it.next(); EJBProxyFactory ci = (EJBProxyFactory)proxyFactories.get(invokerBinding); ci.start(); } // Start instance cache instanceCache.start(); // Start persistence persistenceManager.start(); // Start the instance pool instancePool.start(); // Start all interceptors in the chain Interceptor in = interceptor; while (in != null) { in.start(); in = in.getNext(); } } finally { // Reset classloader Thread.currentThread().setContextClassLoader(oldCl); } } protected void stopService() throws Exception { // Associate thread with classloader ClassLoader oldCl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(getClassLoader()); try { //Stop items in reverse order from start //This assures that CachedConnectionInterceptor will get removed //from in between this and the pm before the pm is stopped. // Stop all interceptors in the chain Interceptor in = interceptor; while (in != null) { in.stop(); in = in.getNext(); } // Stop the instance pool instancePool.stop(); // Stop persistence persistenceManager.stop(); // Stop instance cache instanceCache.stop(); // Stop container invoker for (Iterator it = proxyFactories.keySet().iterator(); it.hasNext(); ) { String invokerBinding = (String)it.next(); EJBProxyFactory ci = (EJBProxyFactory)proxyFactories.get(invokerBinding); ci.stop(); } // Call default stop super.stopService(); } finally { // Reset classloader Thread.currentThread().setContextClassLoader(oldCl); } } protected void destroyService() throws Exception { // Associate thread with classloader ClassLoader oldCl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(getClassLoader()); try { // Destroy container invoker for (Iterator it = proxyFactories.keySet().iterator(); it.hasNext(); ) { String invokerBinding = (String)it.next(); EJBProxyFactory ci = (EJBProxyFactory)proxyFactories.get(invokerBinding); ci.destroy(); } // Destroy instance cache instanceCache.destroy(); instanceCache.setContainer(null); try { ObjectName containerName = super.getJmxName(); Hashtable props = containerName.getKeyPropertyList(); props.put("plugin", "cache"); ObjectName cacheName = new ObjectName(containerName.getDomain(), props); server.unregisterMBean(cacheName); } catch(Throwable ignore) { } // Destroy persistence persistenceManager.destroy(); persistenceManager.setContainer(null); // Destroy the pool instancePool.destroy(); instancePool.setContainer(null); try { ObjectName containerName = super.getJmxName(); Hashtable props = containerName.getKeyPropertyList(); props.put("plugin", "pool"); ObjectName poolName = new ObjectName(containerName.getDomain(), props); server.unregisterMBean(poolName); } catch(Throwable ignore) { } // Destroy all the interceptors in the chain Interceptor in = interceptor; while (in != null) { in.destroy(); in.setContainer(null); in = in.getNext(); } // Call default destroy super.destroyService(); } finally { // Reset classloader Thread.currentThread().setContextClassLoader(oldCl); } } public Object internalInvokeHome(Invocation mi) throws Exception { return getInterceptor().invokeHome(mi); } public Object internalInvoke(Invocation mi) throws Exception { // Invoke through interceptors return getInterceptor().invoke(mi); } // EJBObject implementation -------------------------------------- public void remove(Invocation mi) throws RemoteException, RemoveException { // synchronize entities with the datastore before the bean is removed // this will write queued updates so datastore will be consistent before removal if (!getBeanMetaData().getContainerConfiguration().getSyncOnCommitOnly()) synchronizeEntitiesWithinTransaction(mi.getTransaction()); // Get the persistence manager to do the dirty work getPersistenceManager().removeEntity((EntityEnterpriseContext)mi.getEnterpriseContext()); // We signify "removed" with a null id // There is no need to synchronize on the context since all the threads reaching here have // gone through the InstanceInterceptor so the instance is locked and we only have one thread // the case of reentrant threads is unclear (would you want to delete an instance in reentrancy) ((EnterpriseContext) mi.getEnterpriseContext()).setId(null); removeCount++; } /** * @throws Error Not yet implemented. */ public Handle getHandle(Invocation mi) throws RemoteException { // TODO throw new Error("Not yet implemented"); } /** * @throws Error Not yet implemented. */ public Object getPrimaryKey(Invocation mi) throws RemoteException { // TODO throw new Error("Not yet implemented"); } /** * @throws IllegalStateException If container invoker is null. */ public EJBHome getEJBHome(Invocation mi) throws RemoteException { EJBProxyFactory ci = getProxyFactory(); if (ci == null) { throw new IllegalStateException(); } return (EJBHome) ci.getEJBHome(); } public boolean isIdentical(Invocation mi) throws RemoteException { return ((EJBObject)mi.getArguments()[0]).getPrimaryKey().equals(((EnterpriseContext) mi.getEnterpriseContext()).getId()); // TODO - should also check type } /** * MF FIXME these are implemented on the client */ public EJBLocalHome getEJBLocalHome(Invocation mi) { return localProxyFactory.getEJBLocalHome(); } /** * @throws Error Not yet implemented. */ public void removeLocalHome(Invocation mi) throws RemoteException, RemoveException { throw new Error("Not Yet Implemented"); } /** * Local home interface implementation */ public EJBLocalObject createLocalHome(Invocation mi) throws Exception { // The persistence manager takes care of the wiring and creating the EJBLocalObject getPersistenceManager().createEntity(mi.getMethod(),mi.getArguments(), (EntityEnterpriseContext) mi.getEnterpriseContext()); // The context implicitely carries the EJBObject createCount++; return ((EntityEnterpriseContext)mi.getEnterpriseContext()).getEJBLocalObject(); } /** * Delegates to the persistence manager postCreateEntityMethod. */ public void postCreateLocalHome(Invocation mi) throws Exception { // The persistence manager takes care of the post create step getPersistenceManager().postCreateEntity(mi.getMethod(),mi.getArguments(), (EntityEnterpriseContext) mi.getEnterpriseContext()); } public Object findLocal(Invocation mi) throws Exception { /** * As per the spec 9.6.4, entities must be synchronized with the datastore * when an ejbFind is called. */ if (!getBeanMetaData().getContainerConfiguration().getSyncOnCommitOnly()) synchronizeEntitiesWithinTransaction(mi.getTransaction()); // Multi-finder? if (!mi.getMethod().getReturnType().equals(getLocalClass())) { // Iterator finder Collection c = getPersistenceManager().findEntities(mi.getMethod(), mi.getArguments(), (EntityEnterpriseContext)mi.getEnterpriseContext()); // Get the EJBObjects with that Collection ec = localProxyFactory.getEntityLocalCollection(c); // BMP entity finder methods are allowed to return java.util.Enumeration. try { if (mi.getMethod().getReturnType().equals(Class.forName("java.util.Enumeration"))) { return java.util.Collections.enumeration(ec); } else { return ec; } } catch (ClassNotFoundException e) { // shouldn't happen return ec; } } else { // Single entity finder Object id = getPersistenceManager().findEntity(mi.getMethod(), mi.getArguments(), (EntityEnterpriseContext)mi.getEnterpriseContext()); //create the EJBObject return localProxyFactory.getEntityEJBLocalObject(id); } } // Home interface implementation --------------------------------- /** * This methods finds the target instances by delegating to the persistence * manager It then manufactures EJBObject for all the involved instances * found. */ public Object find(Invocation mi) throws Exception { /** * As per the spec 9.6.4, entities must be synchronized with the datastore * when an ejbFind is called. */ if (!getBeanMetaData().getContainerConfiguration().getSyncOnCommitOnly()) synchronizeEntitiesWithinTransaction(mi.getTransaction()); EJBProxyFactory ci = getProxyFactory(); if (ci == null) { throw new IllegalStateException(); } // Multi-finder? if (!mi.getMethod().getReturnType().equals(getRemoteClass())) { // Iterator finder Collection c = getPersistenceManager().findEntities(mi.getMethod(), mi.getArguments(), (EntityEnterpriseContext)mi.getEnterpriseContext()); // Get the EJBObjects with that Collection ec = ci.getEntityCollection(c); // BMP entity finder methods are allowed to return java.util.Enumeration. // We need a serializable Enumeration, so we can't use Collections.enumeration() try { if (mi.getMethod().getReturnType().equals(Class.forName("java.util.Enumeration"))) { return new SerializableEnumeration(ec); } else { return ec; } } catch (ClassNotFoundException e) { // shouldn't happen return ec; } } else { // Single entity finder Object id = getPersistenceManager().findEntity(mi.getMethod(), mi.getArguments(), (EntityEnterpriseContext)mi.getEnterpriseContext()); //create the EJBObject return (EJBObject)ci.getEntityEJBObject(id); } } /** * store entity */ public void storeEntity(EntityEnterpriseContext ctx) throws Exception { if (ctx.getId() != null) { if(getPersistenceManager().isModified(ctx)) { getPersistenceManager().storeEntity(ctx); } } } /** * Delegates to the persistence manager postCreateEntityMethod. */ public void postCreateHome(Invocation mi) throws Exception { // The persistence manager takes care of the post create step getPersistenceManager().postCreateEntity(mi.getMethod(),mi.getArguments(), (EntityEnterpriseContext) mi.getEnterpriseContext()); } /** * This method takes care of the wiring of the "EJBObject" trio * (target, context, proxy). It delegates to the persistence manager. */ public EJBObject createHome(Invocation mi) throws Exception { // The persistence manager takes care of the wiring and creating the EJBObject getPersistenceManager().createEntity(mi.getMethod(),mi.getArguments(), (EntityEnterpriseContext) mi.getEnterpriseContext()); // The context implicitely carries the EJBObject createCount++; return ((EntityEnterpriseContext)mi.getEnterpriseContext()).getEJBObject(); } /** * A method for the getEJBObject from the handle */ public EJBObject getEJBObject(Invocation mi) throws RemoteException { EJBProxyFactory ci = getProxyFactory(); if (ci == null) { throw new IllegalStateException(); } // All we need is an EJBObject for this Id; return (EJBObject)ci.getEntityEJBObject(((EntityCache) instanceCache).createCacheKey(mi.getId())); } // EJBHome implementation ---------------------------------------- /** * @throws Error Not yet implemented. */ public void removeHome(Invocation mi) throws RemoteException, RemoveException { throw new Error("Not yet implemented"); } public EJBMetaData getEJBMetaDataHome(Invocation mi) throws RemoteException { EJBProxyFactory ci = getProxyFactory(); if (ci == null) { throw new IllegalStateException(); } return ci.getEJBMetaData(); } /** * @throws Error Not yet implemented. */ public HomeHandle getHomeHandleHome(Invocation mi) throws RemoteException { // TODO throw new Error("Not yet implemented"); } /** * @jmx:managed-attribute * @return the current cache size */ public long getCacheSize() { return instanceCache.getCacheSize(); } /** Flush the cache * @jmx:managed-operation */ public void flushCache() { instanceCache.flush(); } // StatisticsProvider implementation ------------------------------------ public Map retrieveStatistic() { // Loop through all Interceptors and add statistics Map lStatistics = new HashMap(); StatisticsProvider lProvider = (StatisticsProvider) getPersistenceManager(); lStatistics.putAll( lProvider.retrieveStatistic() ); lProvider = (StatisticsProvider) getInstancePool(); lStatistics.putAll( lProvider.retrieveStatistic() ); return lStatistics; } public void resetStatistic() { } // Private ------------------------------------------------------- private void setupHomeMappingImpl(Method[] m, String finderName, String append) throws Exception { // Adrian Brock: This should go away when we don't support EJB1x boolean isEJB1x = metaData.getApplicationMetaData().isEJB1x(); for (int i = 0; i < m.length; i++) { String methodName = m[i].getName(); try { try // Try home method { String ejbHomeMethodName = "ejbHome" + methodName.substring(0,1).toUpperCase() + methodName.substring(1); homeMapping.put(m[i], beanClass.getMethod(ejbHomeMethodName, m[i].getParameterTypes())); continue; } catch (NoSuchMethodException ignore) {} // just go on with other types of methods // Implemented by container (in both cases) if (methodName.startsWith("find")) { homeMapping.put(m[i], this.getClass().getMethod(finderName, new Class[] { Invocation.class })); } else if (methodName.equals("create") || (isEJB1x == false && methodName.startsWith("create"))) { homeMapping.put(m[i], this.getClass().getMethod("create"+append, new Class[] { Invocation.class })); beanMapping.put(m[i], this.getClass().getMethod("postCreate"+append, new Class[] { Invocation.class })); } else { homeMapping.put(m[i], this.getClass().getMethod(methodName+append, new Class[] { Invocation.class })); } } catch (NoSuchMethodException e) { throw new NoSuchMethodException("Could not find matching method for "+m[i]); } } } protected void setupHomeMapping() throws Exception { try { if (homeInterface != null) { Method[] m = homeInterface.getMethods(); setupHomeMappingImpl( m, "find", "Home" ); } if (localHomeInterface != null) { Method[] m = localHomeInterface.getMethods(); setupHomeMappingImpl( m, "findLocal", "LocalHome" ); } // Special methods // Get the One on Handle (getEJBObject), get the class Class handleClass = Class.forName("javax.ejb.Handle"); // Get the methods (there is only one) Method[] handleMethods = handleClass.getMethods(); //Just to make sure let's iterate for (int j=0; j