/*
* 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.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.rmi.MarshalException;
import java.security.AccessController;
import java.security.Policy;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.ejb.EJBException;
import javax.ejb.EJBObject;
import javax.ejb.TimedObject;
import javax.ejb.Timer;
import javax.ejb.TimerService;
import javax.ejb.spi.HandleDelegate;
import javax.management.MBeanException;
import javax.management.MalformedObjectNameException;
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.transaction.TransactionManager;
import javax.xml.soap.SOAPMessage;
import org.jboss.deployers.vfs.spi.structure.VFSDeploymentUnit;
import org.jboss.deployment.DeploymentException;
import org.jboss.ejb.plugins.local.BaseLocalProxyFactory;
import org.jboss.ejb.txtimer.EJBTimerService;
import org.jboss.ejb.txtimer.EJBTimerServiceImpl;
import org.jboss.invocation.Invocation;
import org.jboss.invocation.InvocationKey;
import org.jboss.invocation.InvocationStatistics;
import org.jboss.invocation.InvocationType;
import org.jboss.invocation.JBossLazyUnmarshallingException;
import org.jboss.invocation.MarshalledInvocation;
import org.jboss.logging.Logger;
import org.jboss.metadata.ApplicationMetaData;
import org.jboss.metadata.BeanMetaData;
import org.jboss.metadata.EjbLocalRefMetaData;
import org.jboss.metadata.EjbRefMetaData;
import org.jboss.metadata.EnvEntryBinder;
import org.jboss.metadata.EnvEntryMetaData;
import org.jboss.metadata.MessageDestinationRefMetaData;
import org.jboss.metadata.ResourceEnvRefMetaData;
import org.jboss.metadata.ResourceRefMetaData;
import org.jboss.metadata.javaee.spec.MessageDestinationMetaData;
import org.jboss.metadata.javaee.spec.ServiceReferenceMetaData;
import org.jboss.metadata.serviceref.ServiceReferenceHandler;
import org.jboss.metadata.serviceref.VirtualFileAdaptor;
import org.jboss.mx.util.ObjectNameConverter;
import org.jboss.mx.util.ObjectNameFactory;
import org.jboss.naming.ENCFactory;
import org.jboss.naming.ENCThreadLocalKey;
import org.jboss.naming.NonSerializableFactory;
import org.jboss.naming.Util;
import org.jboss.security.AnybodyPrincipal;
import org.jboss.security.AuthenticationManager;
import org.jboss.security.ISecurityManagement;
import org.jboss.security.RealmMapping;
import org.jboss.security.SecurityConstants;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.authorization.PolicyRegistration;
import org.jboss.system.ServiceMBeanSupport;
import org.jboss.util.NestedError;
import org.jboss.util.NestedRuntimeException;
import org.jboss.wsf.spi.deployment.UnifiedVirtualFile;
import org.omg.CORBA.ORB;
/**
* This is the base class for all EJB-containers in JBoss. A Container
* functions as the central hub of all metadata and plugins. Through this
* the container plugins can get hold of the other plugins and any metadata
* they need.
*
* <p>The EJBDeployer creates instances of subclasses of this class
* and calls the appropriate initialization methods.
*
* <p>A Container does not perform any significant work, but instead delegates
* to the plugins to provide for all kinds of algorithmic functionality.
*
* @see EJBDeployer
*
* @author <a href="mailto:rickard.oberg@jboss.org">Rickard Oberg</a>
* @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
* @author <a href="mailto:Scott.Stark@jboss.org">Scott Stark</a>.
* @author <a href="bill@burkecentral.com">Bill Burke</a>
* @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
* @author <a href="mailto:christoph.jung@infor.de">Christoph G. Jung</a>
* @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
* @author adrian@jboss.org
* @author anil.saldhana@jboss.org
* @version $Revision: 98925 $
*
* @jmx.mbean extends="org.jboss.system.ServiceMBean"
*/
public abstract class Container extends ServiceMBeanSupport implements ContainerMBean, AllowedOperationsFlags
{
public final static String BASE_EJB_CONTAINER_NAME = "jboss.j2ee:service=EJB";
public final static ObjectName ORB_NAME = ObjectNameFactory.create("jboss:service=CorbaORB");
public final static ObjectName EJB_CONTAINER_QUERY_NAME = ObjectNameFactory.create(BASE_EJB_CONTAINER_NAME + ",*");
protected static final Method EJBOBJECT_REMOVE;
/** A reference to {@link javax.ejb.TimedObject#ejbTimeout}. */
protected static final Method EJB_TIMEOUT;
/** This is the application that this container is a part of */
protected EjbModule ejbModule;
/**
* This is the classloader of this container. All classes and resources that
* the bean uses will be loaded from here. By doing this we make the bean
* re-deployable
*/
protected ClassLoader classLoader;
/** The class loader for remote dynamic classloading */
protected ClassLoader webClassLoader;
/**
* Externally supplied configuration data
*/
private VFSDeploymentUnit unit;
/**
* This is the new metadata. it includes information from both ejb-jar and
* jboss.xml the metadata for the application can be accessed trough
* metaData.getApplicationMetaData()
*/
protected BeanMetaData metaData;
/** This is the EnterpriseBean class */
protected Class beanClass;
/** This is the Home interface class */
protected Class homeInterface;
/** This is the Remote interface class */
protected Class remoteInterface;
/** The local home interface class */
protected Class localHomeInterface;
/** The local inteface class */
protected Class localInterface;
/** This is the TransactionManager */
protected TransactionManager tm;
/** The Security Context FQN */
protected String securityContextClassName;
/** Security Domain to fall back on **/
protected String defaultSecurityDomain;
/** SecurityManagement Instance - holder of all security managers */
protected ISecurityManagement securityManagement;
/** PolicyRegistration - Holds Authorization Policies */
protected PolicyRegistration policyRegistration;
/** This is the SecurityManager */
protected AuthenticationManager sm;
/** This is the realm mapping */
protected RealmMapping rm;
/** The custom security proxy used by the SecurityInterceptor */
protected Object securityProxy;
/** This is the bean lock manager that is to be used */
protected BeanLockManager lockManager;
/** ??? */
protected LocalProxyFactory localProxyFactory = new BaseLocalProxyFactory();
/** This is a cache for method permissions */
private HashMap<Method, Set<Principal>> methodPermissionsCache = new HashMap<Method, Set<Principal>>();
/** Maps for MarshalledInvocation mapping */
protected Map marshalledInvocationMapping = new HashMap();
/** ObjectName of Container */
private ObjectName jmxName;
/** HashMap<String, EJBProxyFactory> for the invoker bindings */
protected HashMap proxyFactories = new HashMap();
/** A priviledged actions for MBeanServer.invoke when running with sec mgr */
private MBeanServerAction serverAction = new MBeanServerAction();
/**
* The Proxy factory is set in the Invocation. This TL is used
* for methods that do not have access to the Invocation.
*/
protected ThreadLocal proxyFactoryTL = new ThreadLocal();
/** The number of create invocations that have been made */
protected long createCount;
/** The number of create invocations that have been made */
protected long removeCount;
/** Time statistics for the invoke(Invocation) methods */
protected InvocationStatistics invokeStats = new InvocationStatistics();
/** The JACC context id for the container */
protected String jaccContextID;
/**
* Flag to denote whether a JACC configuration has been fitted for authorization
*/
protected boolean isJaccEnabled = false;
protected EJBTimerService timerService;
static
{
try
{
EJBOBJECT_REMOVE = EJBObject.class.getMethod("remove", new Class[0]);
EJB_TIMEOUT = TimedObject.class.getMethod("ejbTimeout", new Class[] { Timer.class });
}
catch (Throwable t)
{
throw new NestedRuntimeException(t);
}
}
// Public --------------------------------------------------------
public Class getLocalClass()
{
return localInterface;
}
public Class getLocalHomeClass()
{
return localHomeInterface;
}
public Class getRemoteClass()
{
return remoteInterface;
}
/**
* this actually should be called remotehome, but for interface compliance purposes
* we keep it like that
*/
public Class getHomeClass()
{
return homeInterface;
}
/**
* Whether the bean is call by value
*
* @return true for call by value
*/
public boolean isCallByValue()
{
if (ejbModule.isCallByValue())
return true;
return metaData.isCallByValue();
}
/**
* Sets a transaction manager for this container.
*
* @see javax.transaction.TransactionManager
*
* @param tm
*/
public void setTransactionManager(final TransactionManager tm)
{
this.tm = tm;
}
/**
* Returns this container's transaction manager.
*
* @return A concrete instance of javax.transaction.TransactionManager
*/
public TransactionManager getTransactionManager()
{
return tm;
}
public void setSecurityManager(AuthenticationManager sm)
{
this.sm = sm;
}
public AuthenticationManager getSecurityManager()
{
return sm;
}
public ISecurityManagement getSecurityManagement()
{
return securityManagement;
}
public void setSecurityManagement(ISecurityManagement securityManagement)
{
this.securityManagement = securityManagement;
}
public PolicyRegistration getPolicyRegistration()
{
return policyRegistration;
}
public void setPolicyRegistration(PolicyRegistration policyRegistration)
{
this.policyRegistration = policyRegistration;
}
public String getDefaultSecurityDomain()
{
return defaultSecurityDomain;
}
public void setDefaultSecurityDomain(String defaultSecurityDomain)
{
this.defaultSecurityDomain = defaultSecurityDomain;
}
public String getSecurityContextClassName()
{
return securityContextClassName;
}
public void setSecurityContextClassName(String securityContextClassName)
{
this.securityContextClassName = securityContextClassName;
}
public BeanLockManager getLockManager()
{
return lockManager;
}
public void setLockManager(final BeanLockManager lockManager)
{
this.lockManager = lockManager;
lockManager.setContainer(this);
}
public void addProxyFactory(String invokerBinding, EJBProxyFactory factory)
{
proxyFactories.put(invokerBinding, factory);
}
public void setRealmMapping(final RealmMapping rm)
{
this.rm = rm;
}
public RealmMapping getRealmMapping()
{
return rm;
}
public void setSecurityProxy(Object proxy)
{
this.securityProxy = proxy;
}
public Object getSecurityProxy()
{
return securityProxy;
}
public EJBProxyFactory getProxyFactory()
{
EJBProxyFactory factory = (EJBProxyFactory)proxyFactoryTL.get();
// There's no factory thread local which means this is probably
// a local invocation. Just use the first (usually only)
// proxy factory.
// TODO: define a default factory in the meta data or
// even better, let the return over the original transport
// plugin the transport layer for the generated proxy
if (factory == null && remoteInterface != null)
{
Iterator i = proxyFactories.values().iterator();
if (i.hasNext())
factory = (EJBProxyFactory)i.next();
}
return factory;
}
public void setProxyFactory(Object factory)
{
proxyFactoryTL.set(factory);
}
public EJBProxyFactory lookupProxyFactory(String binding)
{
return (EJBProxyFactory)proxyFactories.get(binding);
}
public final VFSDeploymentUnit getDeploymentUnit()
{
return unit;
}
public final void setDeploymentUnit(VFSDeploymentUnit di)
{
this.unit = di;
}
/**
* Sets the application deployment unit for this container. All the bean
* containers within the same application unit share the same instance.
*
* @param app application for this container
*/
public void setEjbModule(EjbModule app)
{
ejbModule = app;
}
public String getJaccContextID()
{
return jaccContextID;
}
public void setJaccContextID(String id)
{
jaccContextID = id;
}
public EJBTimerService getTimerService()
{
return timerService;
}
public void setTimerService(EJBTimerService timerService)
{
this.timerService = timerService;
}
/**
* Get the flag whether JACC is enabled
* @return
*/
public boolean isJaccEnabled()
{
return isJaccEnabled;
}
/**
* Set the flag that JACC is enabled
*
* @param isJaccEnabled
*/
public void setJaccEnabled(boolean isJaccEnabled)
{
this.isJaccEnabled = isJaccEnabled;
}
/**
* Gets the application deployment unit for this container. All the bean
* containers within the same application unit share the same instance.
* @jmx.managed-attribute
*/
public EjbModule getEjbModule()
{
return ejbModule;
}
/**
* Gets the deployment unit name that contains this container.
*/
public String getDeploymentName()
{
return ejbModule.name;
}
/**
* Gets the number of create invocations that have been made
* @jmx.managed-attribute
*/
public long getCreateCount()
{
return createCount;
}
/**
* Gets the number of remove invocations that have been made
* @jmx.managed-attribute
*/
public long getRemoveCount()
{
return removeCount;
}
/** Gets the invocation statistics collection
* @jmx.managed-attribute
*/
public InvocationStatistics getInvokeStats()
{
return invokeStats;
}
/**
* Converts the method invocation stats into a detyped nested map structure.
* The format is:
*
* {methodName => {statisticTypeName => longValue}}
*
* @return A map indexed by method name with map values indexed by statistic type
*/
public Map<String, Map<String, Long>> getDetypedInvocationStatistics()
{
return invokeStats.toDetypedMap();
}
/**
* Return the current instance pool associate with this container if
* supported by the underlying container implementation.
*
* @return instance pool
* @throws UnsupportedOperationException if the container does not support an instance pool
*/
protected InstancePool getInstancePool()
{
throw new UnsupportedOperationException();
}
/**
* Get current pool size of the pool associated with this container,
* also known as the method ready count
*
* @throws UnsupportedOperationException if the container type does not support an instance pool
*/
public int getCurrentPoolSize()
{
return getInstancePool().getCurrentSize();
}
/**
* Get current pool size of the pool associated with this container.
*
* @throws UnsupportedOperationException if the container type does not support an instance pool
*/
public int getMaxPoolSize()
{
return getInstancePool().getMaxSize();
}
/**
* Resets the current invocation stats
*/
public void resetInvocationStats()
{
invokeStats.resetStats();
}
/**
* Sets the class loader for this container. All the classes and resources
* used by the bean in this container will use this classloader.
*
* @param cl
*/
public void setClassLoader(ClassLoader cl)
{
this.classLoader = cl;
}
/**
* Returns the classloader for this container.
*
* @return
*/
public ClassLoader getClassLoader()
{
return classLoader;
}
/** Get the class loader for dynamic class loading via http.
*/
public ClassLoader getWebClassLoader()
{
return webClassLoader;
}
/** Set the class loader for dynamic class loading via http.
*/
public void setWebClassLoader(final ClassLoader webClassLoader)
{
this.webClassLoader = webClassLoader;
}
/**
* Sets the meta data for this container. The meta data consists of the
* properties found in the XML descriptors.
*
* @param metaData
*/
public void setBeanMetaData(final BeanMetaData metaData)
{
this.metaData = metaData;
}
/**
* push the ENC onto the stack so that java:comp works
*
*/
public void pushENC()
{
ENCFactory.pushContextId(getJmxName());
}
public void popENC()
{
ENCFactory.popContextId();
}
/**
* cleanup ENC on shutdown
*
*/
public void cleanENC()
{
ENCFactory.getEncById().remove(getJmxName());
}
/** Get the components environment context
* @jmx.managed-attribute
* @return Environment Context
*/
public Context getEnvContext() throws NamingException
{
pushENC();
try
{
return (Context)new InitialContext().lookup("java:comp/env");
}
finally
{
popENC();
}
}
/**
* Returns the metadata of this container.
*
* @jmx.managed-attribute
* @return metaData;
*/
public BeanMetaData getBeanMetaData()
{
return metaData;
}
/**
* Returns the permissions for a method. (a set of roles)
*
* @return assemblyDescriptor;
*/
public Set<Principal> getMethodPermissions(Method m, InvocationType iface)
{
Set<Principal> permissions;
if (methodPermissionsCache.containsKey(m))
{
permissions = (Set)methodPermissionsCache.get(m);
}
else if (m.equals(EJB_TIMEOUT))
{
// No role is required to access the ejbTimeout as this is
permissions = new HashSet<Principal>();
permissions.add(AnybodyPrincipal.ANYBODY_PRINCIPAL);
methodPermissionsCache.put(m, permissions);
}
else
{
String name = m.getName();
Class[] sig = m.getParameterTypes();
Set<String> roles = getBeanMetaData().getMethodPermissions(name, sig, iface);
permissions = new HashSet<Principal>();
if (roles != null)
{
for (String role : roles)
permissions.add(new SimplePrincipal(role));
}
methodPermissionsCache.put(m, permissions);
}
return permissions;
}
/**
* Returns the bean class instance of this container.
*
* @return instance of the Enterprise bean class.
*/
public Class getBeanClass()
{
return beanClass;
}
/**
* Returns a new instance of the bean class or a subclass of the bean class.
* This factory style method is speciffically used by a container to supply
* an implementation of the abstract accessors in EJB2.0, but could be
* usefull in other situations. This method should ALWAYS be used instead
* of getBeanClass().newInstance();
*
* @return the new instance
*
* @see java.lang.Class#newInstance
*/
public Object createBeanClassInstance() throws Exception
{
return getBeanClass().newInstance();
}
/**
* Sets the codebase of this container.
*
* @param codebase a possibly empty, but non null String with
* a sequence of URLs separated by spaces
* /
public void setCodebase(final String codebase)
{
if (codebase != null)
this.codebase = codebase;
}
*/
/**
* Gets the codebase of this container.
*
* @return this container's codebase String, a sequence of URLs
* separated by spaces
* /
public String getCodebase()
{
return codebase;
}
*/
/** Build a JMX name using the pattern jboss.j2ee:service=EJB,jndiName=[jndiName]
where the [jndiName] is either the bean remote home JNDI binding, or
the local home JNDI binding if the bean has no remote interfaces.
*/
public ObjectName getJmxName()
{
if (jmxName == null)
{
BeanMetaData beanMetaData = getBeanMetaData();
if (beanMetaData == null)
{
throw new IllegalStateException("Container metaData is null");
}
String jndiName = beanMetaData.getContainerObjectNameJndiName();
if (jndiName == null)
{
throw new IllegalStateException("Container jndiName is null");
}
// The name must be escaped since the jndiName may be arbitrary
String name = BASE_EJB_CONTAINER_NAME + ",jndiName=" + jndiName;
try
{
jmxName = ObjectNameConverter.convert(name);
}
catch (MalformedObjectNameException e)
{
throw new RuntimeException("Failed to create ObjectName, msg=" + e.getMessage());
}
}
return jmxName;
}
/**
* Creates the single Timer Service for this container if not already created
*
* @param pKey Bean id
* @return Container Timer Service
* @throws IllegalStateException If the type of EJB is not allowed to use the
* timer service, or the bean class does not implement javax.ejb.TimedObject
*
* @see javax.ejb.EJBContext#getTimerService
*
* @jmx.managed-operation
**/
public TimerService getTimerService(Object pKey) throws IllegalStateException
{
if (this instanceof StatefulSessionContainer)
throw new IllegalStateException("Statefull Session Beans are not allowed to access the TimerService");
// Validate that the bean implements the TimedObject interface
if (TimedObject.class.isAssignableFrom(beanClass) == false)
{
// jbcts-381
return EJBTimerServiceImpl.FOR_NON_TIMED_OBJECT;
}
TimerService timerService = null;
try
{
timerService = this.timerService.createTimerService(getJmxName(), pKey, this);
}
catch (Exception e)
{
throw new EJBException("Could not create timer service", e);
}
return timerService;
}
/**
* Removes Timer Service for this container
*
* @param pKey Bean id
* @throws IllegalStateException If the type of EJB is not allowed to use the timer service
*
* @jmx.managed-operation
**/
public void removeTimerService(Object pKey) throws IllegalStateException
{
try
{
if (pKey != null)
{
// entity bean->remove()
timerService.removeTimerService(getJmxName(), pKey);
}
else
{
// container stop, we choose whether active timers
// should be persisted (default), or not (legacy)
timerService.removeTimerService(getJmxName(), getBeanMetaData().getTimerPersistence());
}
}
catch (Exception e)
{
log.error("Could not remove timer service", e);
}
}
/**
* Restore any timers previously persisted for this container
*/
protected void restoreTimers()
{
try
{
// TODO: this name needs to be externalized
// pass to the ejb timer service the container ObjectName
timerService.restoreTimers(getServiceName(), getClassLoader());
}
catch (Exception e)
{
log.warn("Could not restore ejb timers", e);
}
}
/**
* The EJBDeployer calls this method. The EJBDeployer has set
* all the plugins and interceptors that this bean requires and now proceeds
* to initialize the chain. The method looks for the standard classes in
* the URL, sets up the naming environment of the bean. The concrete
* container classes should override this method to introduce
* implementation specific initialization behaviour.
*
* @throws Exception if loading the bean class failed
* (ClassNotFoundException) or setting up "java:"
* naming environment failed (DeploymentException)
*/
protected void createService() throws Exception
{
// Acquire classes from CL
beanClass = classLoader.loadClass(metaData.getEjbClass());
if (metaData.getLocalHome() != null)
localHomeInterface = classLoader.loadClass(metaData.getLocalHome());
if (metaData.getLocal() != null)
localInterface = classLoader.loadClass(metaData.getLocal());
localProxyFactory.setContainer(this);
localProxyFactory.create();
if (localHomeInterface != null)
ejbModule.addLocalHome(this, localProxyFactory.getEJBLocalHome());
ejbModule.createMissingPermissions(this, metaData);
// Allow the policy to incorporate the policy configs
Policy.getPolicy().refresh();
}
/**
* A default implementation of starting the container service.
* The container registers it's dynamic MBean interface in the JMX base.
*
* The concrete container classes should override this method to introduce
* implementation specific start behaviour.
*
* todo implement the service lifecycle methods in an xmbean interceptor so
* non lifecycle managed ops are blocked when mbean is not started.
*
* @throws Exception An exception that occured during start
*/
protected void startService() throws Exception
{
// Setup "java:comp/env" namespace
setupEnvironment();
localProxyFactory.start();
}
/**
* A default implementation of stopping the container service (no-op). The
* concrete container classes should override this method to introduce
* implementation specific stop behaviour.
*/
protected void stopService() throws Exception
{
localProxyFactory.stop();
removeTimerService(null);
teardownEnvironment();
}
/**
* A default implementation of destroying the container service (no-op).
* The concrete container classes should override this method to introduce
* implementation specific destroy behaviour.
*/
protected void destroyService() throws Exception
{
cleanENC();
localProxyFactory.destroy();
ejbModule.removeLocalHome(this);
beanClass = null;
homeInterface = null;
remoteInterface = null;
localHomeInterface = null;
localInterface = null;
methodPermissionsCache.clear();
// InvocationStatistics holds refs to Methods from
// application classes, so to avoid a classloader
// leak, lets not just resetStats() but also replace
// the object
invokeStats.resetStats(); // in case someone else has a ref
invokeStats = new InvocationStatistics();
marshalledInvocationMapping.clear();
}
/**
* This method is called when a method call comes
* in on the Home object. The Container forwards this call to the
* interceptor chain for further processing.
*
* @param mi the object holding all info about this invocation
* @return the result of the home invocation
*
* @throws Exception
*/
public abstract Object internalInvokeHome(Invocation mi) throws Exception;
/**
* This method is called when a method call comes
* in on an EJBObject. The Container forwards this call to the interceptor
* chain for further processing.
*/
public abstract Object internalInvoke(Invocation mi) throws Exception;
abstract Interceptor createContainerInterceptor();
public abstract void addInterceptor(Interceptor in);
/** The detached invoker operation.
*
* @jmx.managed-operation
*
* @param mi - the method invocation context
* @return the value of the ejb invocation
* @throws Exception on error
*/
public Object invoke(Invocation mi) throws Exception
{
ClassLoader callerClassLoader = SecurityActions.getContextClassLoader();
long start = System.currentTimeMillis();
Method m = null;
Object type = null;
String contextID = getJaccContextID();
try
{
pushENC();
// JBAS-3732 - Remove classloader.equals optimization
SecurityActions.setContextClassLoader(this.classLoader);
// Set the JACC context id
mi.setValue(InvocationKey.JACC_CONTEXT_ID, contextID);
contextID = SecurityActions.setContextID(contextID);
// Set the standard JACC policy context handler data is not a SEI msg
if (mi.getType() != InvocationType.SERVICE_ENDPOINT)
{
EJBArgsPolicyContextHandler.setArgs(mi.getArguments());
}
else
{
SOAPMessage msg = (SOAPMessage)mi.getValue(InvocationKey.SOAP_MESSAGE);
SOAPMsgPolicyContextHandler.setMessage(msg);
}
// Set custom JACC policy handlers
BeanMetaDataPolicyContextHandler.setMetaData(this.getBeanMetaData());
// Check against home, remote, localHome, local, getHome,
// getRemote, getLocalHome, getLocal
type = mi.getType();
// stat gathering: concurrent calls
this.invokeStats.callIn();
if (type == InvocationType.REMOTE || type == InvocationType.LOCAL ||
// web service calls come in as "ordinary" application invocations
type == InvocationType.SERVICE_ENDPOINT)
{
if (mi instanceof MarshalledInvocation)
{
((MarshalledInvocation)mi).setMethodMap(marshalledInvocationMapping);
if (log.isTraceEnabled())
{
log.trace("METHOD REMOTE INVOKE " + mi.getObjectName() + "||" + mi.getMethod().getName() + "||");
}
}
m = mi.getMethod();
Object obj = internalInvoke(mi);
return obj;
}
else if (type == InvocationType.HOME || type == InvocationType.LOCALHOME)
{
if (mi instanceof MarshalledInvocation)
{
((MarshalledInvocation)mi).setMethodMap(marshalledInvocationMapping);
if (log.isTraceEnabled())
{
log.trace("METHOD HOME INVOKE " + mi.getObjectName() + "||" + mi.getMethod().getName() + "||" + mi.getArguments().toString());
}
}
m = mi.getMethod();
Object obj = internalInvokeHome(mi);
return obj;
}
else
{
throw new MBeanException(new IllegalArgumentException("Unknown invocation type: " + type));
}
}
/**
* Having to catch this exception here in case can not
* unmarshall arguments, values, etc. Then, convert to
* UnmarshalException as defined by spec (JBAS-2999)
*/
catch (JBossLazyUnmarshallingException e)
{
InvocationType calltype = mi.getType();
boolean isLocal = calltype == InvocationType.LOCAL || calltype == InvocationType.LOCALHOME;
// handle unmarshalling exception which should only come if problem unmarshalling
// invocation payload, arguments, or value on remote end.
if (isLocal)
{
throw new EJBException("UnmarshalException", e);
}
else
{
throw new MarshalException("MarshalException", e);
}
}
finally
{
if (m != null)
{
long end = System.currentTimeMillis();
long elapsed = end - start;
this.invokeStats.updateStats(m, elapsed);
}
// stat gathering: concurrent calls
this.invokeStats.callOut();
popENC();
// Restore the incoming class loader
SecurityActions.setContextClassLoader(callerClassLoader);
// Restore the incoming context id
contextID = SecurityActions.setContextID(contextID);
if (mi.getType() == InvocationType.SERVICE_ENDPOINT)
{
// Remove msg from ThreadLocal to prevent leakage into the thread pool
SOAPMsgPolicyContextHandler.setMessage(null);
}
else
{
// Remove args from ThreadLocal to prevent leakage into the thread pool
EJBArgsPolicyContextHandler.setArgs(null);
}
// Remove metadata from ThreadLocal to prevent leakage into the thread pool
BeanMetaDataPolicyContextHandler.setMetaData(null);
}
}
// Private -------------------------------------------------------
/**
* This method sets up the naming environment of the bean.
* We create the java:comp/env namespace with properties, EJB-References,
* and DataSource ressources.
*/
private void setupEnvironment() throws Exception
{
BeanMetaData beanMetaData = getBeanMetaData();
// debug
log.debug("Begin java:comp/env for EJB: " + beanMetaData.getEjbName());
ClassLoader tcl = SecurityActions.getContextClassLoader();
log.debug("TCL: " + tcl);
ORB orb = null;
HandleDelegate hd = null;
try
{
orb = (ORB)server.getAttribute(ORB_NAME, "ORB");
hd = (HandleDelegate)server.getAttribute(ORB_NAME, "HandleDelegate");
}
catch (Throwable t)
{
log.debug("Unable to retrieve orb" + t.toString());
}
// Since the BCL is already associated with this thread we can start
// using the java: namespace directly
Context ctx = (Context)new InitialContext().lookup("java:comp");
Object id = ENCFactory.getCurrentId();
log.debug("Using java:comp using id=" + id);
// Bind the orb
if (orb != null)
{
NonSerializableFactory.rebind(ctx, "ORB", orb);
log.debug("Bound java:comp/ORB for EJB: " + getBeanMetaData().getEjbName());
NonSerializableFactory.rebind(ctx, "HandleDelegate", hd);
log.debug("Bound java:comp/HandleDelegate for EJB: " + getBeanMetaData().getEjbName());
}
// JTA links
ctx.bind("TransactionSynchronizationRegistry", new LinkRef("java:TransactionSynchronizationRegistry"));
log.debug("Linked java:comp/TransactionSynchronizationRegistry to JNDI name: java:TransactionSynchronizationRegistry");
Context envCtx = ctx.createSubcontext("env");
// Bind environment properties
{
Iterator i = beanMetaData.getEnvironmentEntries();
while (i.hasNext())
{
EnvEntryMetaData entry = (EnvEntryMetaData)i.next();
log.debug("Binding env-entry: " + entry.getName() + " of type: " + entry.getType() + " to value:" + entry.getValue());
EnvEntryBinder.bindEnvEntry(envCtx, entry);
}
}
// Bind EJB references
{
Iterator i = beanMetaData.getEjbReferences();
while (i.hasNext())
{
EjbRefMetaData ref = (EjbRefMetaData)i.next();
log.debug("Binding an EJBReference " + ref.getName());
if (ref.getLink() != null)
{
// Internal link
String linkName = ref.getLink();
String jndiName = ref.getJndiName();
log.debug("Binding " + ref.getName() + " to ejb-link: " + linkName + " -> " + jndiName);
if (jndiName == null)
{
String msg = "Failed to resolve ejb-link: " + linkName + " from ejb-ref: " + ref.getName() + " in ejb: " + beanMetaData.getEjbName();
throw new DeploymentException(msg);
}
Util.bind(envCtx, ref.getName(), new LinkRef(jndiName));
}
else
{
// Get the invoker specific ejb-ref mappings
Iterator it = beanMetaData.getInvokerBindings();
Reference reference = null;
while (it.hasNext())
{
String invokerBinding = (String)it.next();
// Check for an invoker level jndi-name
String name = ref.getInvokerBinding(invokerBinding);
// Check for an global jndi-name
if (name == null)
name = ref.getJndiName();
if (name == null)
{
throw new DeploymentException("ejb-ref " + ref.getName() + ", expected either ejb-link in ejb-jar.xml or " + "jndi-name in jboss.xml");
}
StringRefAddr addr = new StringRefAddr(invokerBinding, name);
log.debug("adding " + invokerBinding + ":" + name + " to Reference");
if (reference == null)
{
reference = new Reference("javax.naming.LinkRef", ENCThreadLocalKey.class.getName(), null);
}
reference.add(addr);
}
// If there were invoker bindings create bind the reference
if (reference != null)
{
if (ref.getJndiName() != null)
{
// Add default for the bean level ejb-ref/jndi-name
StringRefAddr addr = new StringRefAddr("default", ref.getJndiName());
reference.add(addr);
}
if (reference.size() == 1 && reference.get("default") == null)
{
/* There is only one invoker binding and its not default so
create a default binding to allow the link to have a value
when accessed without an invoker active.
*/
StringRefAddr addr = (StringRefAddr)reference.get(0);
String target = (String)addr.getContent();
StringRefAddr addr1 = new StringRefAddr("default", target);
reference.add(addr1);
}
Util.bind(envCtx, ref.getName(), reference);
}
else
{
// Bind the bean level ejb-ref/jndi-name
if (ref.getJndiName() == null)
{
throw new DeploymentException("ejb-ref " + ref.getName() + ", expected either ejb-link in ejb-jar.xml " + "or jndi-name in jboss.xml");
}
Util.bind(envCtx, ref.getName(), new LinkRef(ref.getJndiName()));
}
}
}
}
// Bind Local EJB references
{
Iterator i = beanMetaData.getEjbLocalReferences();
while (i.hasNext())
{
EjbLocalRefMetaData ref = (EjbLocalRefMetaData)i.next();
String refName = ref.getName();
log.debug("Binding an EJBLocalReference " + ref.getName());
if (ref.getLink() != null)
{
// Internal link
log.debug("Binding " + refName + " to bean source: " + ref.getLink());
String jndiName = ref.getJndiName();
Util.bind(envCtx, ref.getName(), new LinkRef(jndiName));
}
else
{
// Bind the bean level ejb-local-ref/local-jndi-name
if (ref.getJndiName() == null)
{
throw new DeploymentException("ejb-local-ref " + ref.getName() + ", expected either ejb-link in ejb-jar.xml " + "or local-jndi-name in jboss.xml");
}
Util.bind(envCtx, ref.getName(), new LinkRef(ref.getJndiName()));
}
}
}
// Bind service references
{
ClassLoader loader = unit.getClassLoader();
UnifiedVirtualFile vfsRoot = new VirtualFileAdaptor(unit.getRoot());
Iterator<ServiceReferenceMetaData> serviceReferences = beanMetaData.getServiceReferences();
if (serviceReferences != null)
{
while (serviceReferences.hasNext())
{
ServiceReferenceMetaData sref = serviceReferences.next();
String refName = sref.getServiceRefName();
new ServiceReferenceHandler().bindServiceRef(envCtx, refName, vfsRoot, loader, sref);
}
}
}
// Bind resource references
{
Iterator i = beanMetaData.getResourceReferences();
// let's play guess the cast game ;) New metadata should fix this.
ApplicationMetaData application = beanMetaData.getApplicationMetaData();
while (i.hasNext())
{
ResourceRefMetaData ref = (ResourceRefMetaData)i.next();
String resourceName = ref.getResourceName();
String finalName = application.getResourceByName(resourceName);
String resType = ref.getType();
// If there was no resource-manager specified then an immeadiate
// jndi-name or res-url name should have been given
if (finalName == null)
finalName = ref.getJndiName();
if (finalName == null && resType.equals("java.net.URL") == false)
{
// the application assembler did not provide a resource manager
// if the type is javax.sql.Datasoure use the default one
if (ref.getType().equals("javax.sql.DataSource"))
{
// Go through JNDI and look for DataSource - use the first one
Context dsCtx = new InitialContext();
try
{
// Check if it is available in JNDI
dsCtx.lookup("java:/DefaultDS");
finalName = "java:/DefaultDS";
}
catch (Exception e)
{
log.debug("failed to lookup DefaultDS; ignoring", e);
}
finally
{
dsCtx.close();
}
}
// Default failed? Warn user and move on
// POTENTIALLY DANGEROUS: should this be a critical error?
if (finalName == null)
{
log.warn("No resource manager found for " + ref.getResourceName());
continue;
}
}
if (resType.equals("java.net.URL"))
{
// URL bindings
if (ref.getResURL() != null)
{
// The URL string was given by the res-url
log.debug("Binding URL: " + ref.getRefName() + " to JDNI ENC as: " + ref.getResURL());
URL resURL = new URL(ref.getResURL());
Util.bind(envCtx, ref.getRefName(), resURL);
}
else
{
log.debug("Binding URL: " + ref.getRefName() + " to: " + finalName);
Object bind = null;
if (ref.getJndiName() != null)
{
// Was the url given as a jndi-name reference to link to it
bind = new LinkRef(finalName);
}
else
{
// The url string was given via a resource-name mapping
bind = new URL(finalName);
}
Util.bind(envCtx, ref.getRefName(), bind);
}
}
else
{
// Resource Manager bindings, should validate the type...
log.debug("Binding resource manager: " + ref.getRefName() + " to JDNI ENC as: " + finalName);
Util.bind(envCtx, ref.getRefName(), new LinkRef(finalName));
}
}
}
// Bind resource env references
{
Iterator i = beanMetaData.getResourceEnvReferences();
while (i.hasNext())
{
ResourceEnvRefMetaData resRef = (ResourceEnvRefMetaData)i.next();
String encName = resRef.getRefName();
String jndiName = resRef.getJndiName();
// Should validate the type...
log.debug("Binding env resource: " + encName + " to JDNI ENC as: " + jndiName);
Util.bind(envCtx, encName, new LinkRef(jndiName));
}
}
// Bind message destination references
{
Iterator i = beanMetaData.getMessageDestinationReferences();
while (i.hasNext())
{
MessageDestinationRefMetaData ref = (MessageDestinationRefMetaData)i.next();
String refName = ref.getRefName();
String jndiName = ref.getJNDIName();
String link = ref.getLink();
if (link != null)
{
if (jndiName == null)
{
MessageDestinationMetaData messageDestination = getMessageDestination(link);
if (messageDestination == null)
throw new DeploymentException("message-destination-ref '" + refName + "' message-destination-link '" + link
+ "' not found and no jndi-name in jboss.xml");
else
{
String linkJNDIName = messageDestination.getJndiName();
if (linkJNDIName == null)
log.warn("message-destination '" + link + "' has no jndi-name in jboss.xml");
else
jndiName = linkJNDIName;
}
}
else
log.warn("message-destination-ref '" + refName + "' ignoring message-destination-link '" + link + "' because it has a jndi-name in jboss.xml");
}
else if (jndiName == null)
throw new DeploymentException("message-destination-ref '" + refName + "' has no message-destination-link in ejb-jar.xml and no jndi-name in jboss.xml");
Util.bind(envCtx, refName, new LinkRef(jndiName));
}
}
// Create a java:comp/env/security/security-domain link to the container
// or application security-domain if one exists so that access to the
// security manager can be made without knowing the global jndi name.
String securityDomain = metaData.getContainerConfiguration().getSecurityDomain();
if (securityDomain == null)
securityDomain = metaData.getApplicationMetaData().getSecurityDomain();
if (securityDomain != null)
{
//JBAS-6060: Tolerate a Security Domain configuration without the java:/jaas prefix
if(securityDomain.startsWith(SecurityConstants.JAAS_CONTEXT_ROOT) == false)
securityDomain = SecurityConstants.JAAS_CONTEXT_ROOT + "/" + securityDomain;
log.debug("Binding securityDomain: " + securityDomain + " to JDNI ENC as: security/security-domain");
Util.bind(envCtx, "security/security-domain", new LinkRef(securityDomain));
Util.bind(envCtx, "security/subject", new LinkRef(securityDomain + "/subject"));
Util.bind(envCtx, "security/realmMapping", new LinkRef(securityDomain + "/realmMapping"));
Util.bind(envCtx, "security/authorizationMgr", new LinkRef(securityDomain + "/authorizationMgr"));
}
log.debug("End java:comp/env for EJB: " + beanMetaData.getEjbName());
}
public MessageDestinationMetaData getMessageDestination(String link)
{
return EjbUtil50.findMessageDestination(null, unit, link);
}
/**
*The <code>teardownEnvironment</code> method unbinds everything from
* the comp/env context. It would be better do destroy the env context
* but destroyContext is not currently implemented..
*
* @exception Exception if an error occurs
*/
private void teardownEnvironment() throws Exception
{
Context ctx = (Context)new InitialContext().lookup("java:comp");
ctx.unbind("env");
log.debug("Removed bindings from java:comp/env for EJB: " + getBeanMetaData().getEjbName());
ctx.unbind("TransactionSynchronizationRegistry");
log.debug("Unbound java:comp/TransactionSynchronizationRegistry for EJB: " + getBeanMetaData().getEjbName());
try
{
NonSerializableFactory.unbind("ORB");
log.debug("Unbound java:comp/ORB for EJB: " + getBeanMetaData().getEjbName());
NonSerializableFactory.unbind("HandleDelegate");
log.debug("Unbound java:comp/HandleDelegate for EJB: " + getBeanMetaData().getEjbName());
}
catch (NamingException ignored)
{
}
}
/**
* The base class for container interceptors.
*
* <p>
* All container interceptors perform the same basic functionality
* and only differ slightly.
*/
protected abstract class AbstractContainerInterceptor implements Interceptor
{
protected final Logger log = Logger.getLogger(this.getClass());
public void setContainer(Container con)
{
}
public void setNext(Interceptor interceptor)
{
}
public Interceptor getNext()
{
return null;
}
public void create()
{
}
public void start()
{
}
public void stop()
{
}
public void destroy()
{
}
protected void rethrow(Exception e) throws Exception
{
if (e instanceof IllegalAccessException)
{
// Throw this as a bean exception...(?)
throw new EJBException(e);
}
else if (e instanceof InvocationTargetException)
{
Throwable t = ((InvocationTargetException)e).getTargetException();
if (t instanceof EJBException)
{
throw (EJBException)t;
}
else if (t instanceof Exception)
{
throw (Exception)t;
}
else if (t instanceof Error)
{
throw (Error)t;
}
else
{
throw new NestedError("Unexpected Throwable", t);
}
}
throw e;
}
// Monitorable implementation ------------------------------------
public void sample(Object s)
{
// Just here to because Monitorable request it but will be removed soon
}
public Map retrieveStatistic()
{
return null;
}
public void resetStatistic()
{
}
}
/** Perform the MBeanServer.invoke op in a PrivilegedExceptionAction if
* running with a security manager.
*/
class MBeanServerAction implements PrivilegedExceptionAction
{
private ObjectName target;
String method;
Object[] args;
String[] sig;
MBeanServerAction()
{
}
MBeanServerAction(ObjectName target, String method, Object[] args, String[] sig)
{
this.target = target;
this.method = method;
this.args = args;
this.sig = sig;
}
public Object run() throws Exception
{
Object rtnValue = server.invoke(target, method, args, sig);
return rtnValue;
}
Object invoke(ObjectName target, String method, Object[] args, String[] sig) throws Exception
{
SecurityManager sm = System.getSecurityManager();
Object rtnValue = null;
if (sm == null)
{
// Direct invocation on MBeanServer
rtnValue = server.invoke(target, method, args, sig);
}
else
{
try
{
// Encapsulate the invocation in a PrivilegedExceptionAction
MBeanServerAction action = new MBeanServerAction(target, method, args, sig);
rtnValue = AccessController.doPrivileged(action);
}
catch (PrivilegedActionException e)
{
Exception ex = e.getException();
throw ex;
}
}
return rtnValue;
}
}
}