/* * JBoss, Home of Professional Open Source. * Copyright 2006, 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.rmi.RemoteException; import java.util.Map; import java.util.HashMap; import java.util.Iterator; import java.util.Collection; import java.util.Hashtable; import java.util.Enumeration; 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.ejb.TimedObject; import javax.ejb.Timer; import javax.transaction.Transaction; import javax.management.ObjectName; import org.jboss.invocation.Invocation; import org.jboss.invocation.InvocationType; import org.jboss.invocation.MarshalledInvocation; import org.jboss.monitor.StatisticsProvider; import org.jboss.metadata.EntityMetaData; import org.jboss.metadata.ConfigurationMetaData; 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: 57209 $ * * @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; /** * 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 */ 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 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 = SecurityActions.getContextClassLoader(); SecurityActions.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(); // Make some additional validity checks with regards to the container configuration checkCoherency (); // Map the bean methods setupBeanMapping(); // Map the home methods setupHomeMapping(); // Map the interfaces to Long setupMarshalledInvocationMapping(); // 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); } // Initialize pool instancePool.create(); for (Iterator it = proxyFactories.keySet().iterator(); it.hasNext(); ) { String invokerBinding = (String)it.next(); EJBProxyFactory ci = (EJBProxyFactory)proxyFactories.get(invokerBinding); ci.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 instance cache instanceCache.create(); // 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 SecurityActions.setContextClassLoader(oldCl); } } protected void startService() throws Exception { // Associate thread with classloader ClassLoader oldCl = SecurityActions.getContextClassLoader(); SecurityActions.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 the instance pool instancePool.start(); Interceptor i = interceptor; while(i != null) { i.start(); i = i.getNext(); } // Restore persisted ejb timers restoreTimers(); } finally { // Reset classloader SecurityActions.setContextClassLoader(oldCl); } } protected void stopService() throws Exception { // Associate thread with classloader ClassLoader oldCl = SecurityActions.getContextClassLoader(); SecurityActions.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 SecurityActions.setContextClassLoader(oldCl); } } protected void destroyService() throws Exception { // Associate thread with classloader ClassLoader oldCl = SecurityActions.getContextClassLoader(); SecurityActions.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(); } MarshalledInvocation.removeHashes(homeInterface); MarshalledInvocation.removeHashes(remoteInterface); // Call default destroy super.destroyService(); } finally { // Reset classloader SecurityActions.setContextClassLoader(oldCl); } } public Object internalInvokeHome(Invocation mi) throws Exception { Method method = mi.getMethod(); if (method != null && method.getName().equals("remove")) { // Map to EJBHome.remove(Object) to EJBObject.remove() InvocationType type = mi.getType(); if (type == InvocationType.HOME) mi.setType(InvocationType.REMOTE); else if (type == InvocationType.LOCALHOME) mi.setType(InvocationType.LOCAL); mi.setMethod(EJBOBJECT_REMOVE); // Handle or primary key? Object arg = mi.getArguments()[0]; if (arg instanceof Handle) { if (arg == null) throw new RemoteException("Null handle"); Handle handle = (Handle) arg; EJBObject ejbObject = handle.getEJBObject(); mi.setId(ejbObject.getPrimaryKey()); } else mi.setId(arg); mi.setArguments(new Object[0]); return getInterceptor().invoke(mi); } 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 Transaction tx = mi.getTransaction(); if (!getBeanMetaData().getContainerConfiguration().getSyncOnCommitOnly()) synchronizeEntitiesWithinTransaction(tx); // Get the persistence manager to do the dirty work EntityEnterpriseContext ctx = (EntityEnterpriseContext)mi.getEnterpriseContext(); getPersistenceManager().removeEntity(ctx); Object pk = ctx.getId(); removeTimerService(pk); // 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) ctx.setId(null); removeCount++; } /** * @throws Error Not yet implemented. */ public Handle getHandle(Invocation mi) throws RemoteException { // TODO throw new Error("Not yet implemented"); } public Object getPrimaryKey(Invocation mi) throws RemoteException { return mi.getId(); } /** * @throws IllegalStateException If container invoker is null. */ public EJBHome getEJBHome(Invocation mi) throws RemoteException { EJBProxyFactory ci = getProxyFactory(); if (ci == null) { String msg = "No ProxyFactory, check for ProxyFactoryFinderInterceptor"; throw new IllegalStateException(msg); } return (EJBHome) ci.getEJBHome(); } public boolean isIdentical(Invocation mi) throws RemoteException { EJBProxyFactory ci = getProxyFactory(); if (ci == null) { String msg = "No ProxyFactory, check for ProxyFactoryFinderInterceptor"; throw new IllegalStateException(msg); } return ci.isIdentical(this, mi); } /** * 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 final EntityEnterpriseContext ctx = (EntityEnterpriseContext)mi.getEnterpriseContext(); getPersistenceManager().createEntity(mi.getMethod(), mi.getArguments(), ctx); // The context implicitely carries the EJBObject createCount++; return localProxyFactory.getEntityEJBLocalObject(ctx.getId(), true); } /** * 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 { Method method = mi.getMethod(); Object[] args = mi.getArguments(); EntityEnterpriseContext instance = (EntityEnterpriseContext)mi.getEnterpriseContext(); boolean syncOnCommitOnly = metaData.getContainerConfiguration().getSyncOnCommitOnly(); Transaction tx = mi.getTransaction(); Class returnType = method.getReturnType(); if (Collection.class.isAssignableFrom(returnType) || returnType == Enumeration.class) { // as per the spec 9.6.4, entities must be synchronized with the datastore when an ejbFind is called. if (!syncOnCommitOnly) { synchronizeEntitiesWithinTransaction(tx); } // Iterator finder Collection c = getPersistenceManager().findEntities(method, args, instance, localProxyFactory); // BMP entity finder methods are allowed to return java.util.Enumeration. if (returnType == Enumeration.class) { return java.util.Collections.enumeration(c); } else { return c; } } else { return findSingleObject(tx, method, args, instance, localProxyFactory); } } // 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 { EJBProxyFactory ci = getProxyFactory(); if (ci == null) { String msg = "No ProxyFactory, check for ProxyFactoryFinderInterceptor"; throw new IllegalStateException(msg); } Method method = mi.getMethod(); Object[] args = mi.getArguments(); EntityEnterpriseContext instance = (EntityEnterpriseContext)mi.getEnterpriseContext(); boolean syncOnCommitOnly = metaData.getContainerConfiguration().getSyncOnCommitOnly(); Transaction tx = mi.getTransaction(); Class returnType = method.getReturnType(); if (Collection.class.isAssignableFrom(returnType) || returnType == Enumeration.class) { // as per the spec 9.6.4, entities must be synchronized with the datastore when an ejbFind is called. if (!syncOnCommitOnly) { synchronizeEntitiesWithinTransaction(tx); } // Iterator finder Collection c = getPersistenceManager().findEntities(method, args, instance, ci); // BMP entity finder methods are allowed to return java.util.Enumeration. // We need a serializable Enumeration, so we can't use Collections.enumeration() if (returnType == Enumeration.class) { return new SerializableEnumeration(c); } else { return c; } } else { return findSingleObject(tx, method, args, instance, ci); } } /** * Invokes ejbStore method on the instance * @param ctx the instance to invoke ejbStore on * @throws Exception */ public void invokeEjbStore(EntityEnterpriseContext ctx) throws Exception { if (ctx.getId() != null) { final EntityPersistenceManager pm = getPersistenceManager(); pm.invokeEjbStore(ctx); } } /** * For CMP actually stores the instance */ public void storeEntity(EntityEnterpriseContext ctx) throws Exception { if (ctx.getId() != null) { final EntityPersistenceManager pm = getPersistenceManager(); if(pm.isStoreRequired(ctx)) { pm.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) { String msg = "No ProxyFactory, check for ProxyFactoryFinderInterceptor"; throw new IllegalStateException(msg); } // 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) { String msg = "No ProxyFactory, check for ProxyFactoryFinderInterceptor"; throw new IllegalStateException(msg); } 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