/*
* JBoss, Home of Professional Open Source.
* Copyright 2010, 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.ejb3.session;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import javax.ejb.EJBLocalObject;
import javax.ejb.EJBObject;
import javax.ejb.Handle;
import javax.ejb.RemoveException;
import org.jboss.aop.Domain;
import org.jboss.aop.MethodInfo;
import org.jboss.aop.util.MethodHashing;
import org.jboss.beans.metadata.api.annotations.Inject;
import org.jboss.ejb3.Ejb3Deployment;
import org.jboss.ejb3.async.impl.AsyncInvocationIdUUIDImpl;
import org.jboss.ejb3.async.impl.util.concurrent.ResultUnwrappingExecutorService;
import org.jboss.ejb3.async.spi.AsyncEndpoint;
import org.jboss.ejb3.async.spi.AsyncInvocationId;
import org.jboss.ejb3.async.spi.AsyncInvocationMap;
import org.jboss.ejb3.async.spi.AsyncInvocationTaskBase;
import org.jboss.ejb3.async.spi.CurrentAsyncInvocation;
import org.jboss.ejb3.common.lang.SerializableMethod;
import org.jboss.ejb3.common.registrar.spi.Ejb3RegistrarLocator;
import org.jboss.ejb3.core.businessobject.BusinessObjectFactory;
import org.jboss.ejb3.core.proxy.spi.CurrentRemoteProxyFactory;
import org.jboss.ejb3.core.proxy.spi.RemoteProxyFactory;
import org.jboss.ejb3.endpoint.Endpoint;
import org.jboss.ejb3.proxy.impl.factory.session.SessionProxyFactory;
import org.jboss.ejb3.proxy.impl.factory.session.SessionSpecProxyFactory;
import org.jboss.ejb3.proxy.impl.handler.session.SessionProxyInvocationHandler;
import org.jboss.ejb3.proxy.impl.remoting.SessionSpecRemotingMetadata;
import org.jboss.ejb3.proxy.spi.container.InvokableContext;
import org.jboss.ejb3.stateful.StatefulContainer;
import org.jboss.ejb3.stateful.StatefulContainerInvocation;
import org.jboss.logging.Logger;
import org.jboss.metadata.ejb.jboss.JBossSessionBeanMetaData;
import org.jboss.security.SecurityContext;
/**
* SessionSpecContainer
*
* A SessionContainer with support for Session Beans defined
* specifically by the EJB3 Specification
*
* @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
* @version $Revision: $
*/
public abstract class SessionSpecContainer extends SessionContainer implements InvokableContext, AsyncEndpoint
{
// ------------------------------------------------------------------------------||
// Class Members ----------------------------------------------------------------||
// ------------------------------------------------------------------------------||
private static final Logger log = Logger.getLogger(SessionSpecContainer.class);
/**
* Business interfaces for this EJB
*/
private Set<Class<?>> businessInterfaces;
/**
* {@link ExecutorService} used for EJB 3.1 Async invocations
*/
private final ExecutorService asynchronousExecutor;
private BusinessObjectFactory businessObjectFactory;
private AsyncInvocationMap currentAsyncInvocations;
// ------------------------------------------------------------------------------||
// Constructor ------------------------------------------------------------------||
// ------------------------------------------------------------------------------||
public SessionSpecContainer(ClassLoader cl, String beanClassName, String ejbName, Domain domain,
Hashtable ctxProperties, Ejb3Deployment deployment, JBossSessionBeanMetaData beanMetaData,
final ExecutorService asynchronousExecutor) throws ClassNotFoundException
{
super(cl, beanClassName, ejbName, domain, ctxProperties, deployment, beanMetaData);
if (asynchronousExecutor == null)
{
throw new IllegalArgumentException("Asynchronous Executor must be specified");
}
this.asynchronousExecutor = new ResultUnwrappingExecutorService(asynchronousExecutor, this);
}
public SessionSpecContainer(ClassLoader cl, String beanClassName, String ejbName, Domain domain,
Hashtable ctxProperties, JBossSessionBeanMetaData beanMetaData, final ExecutorService asynchronousExecutor)
throws ClassNotFoundException
{
super(cl, beanClassName, ejbName, domain, ctxProperties, beanMetaData);
if (asynchronousExecutor == null)
{
throw new IllegalArgumentException("Asynchronous Executor must be specified");
}
this.asynchronousExecutor = new ResultUnwrappingExecutorService(asynchronousExecutor, this);
}
/**
* Invokes the specified method upon the specified session, passing the specified
* arguments.
*
* This is required by the {@link Endpoint} interface and is the correct implementation
* of the ejb3-core containers looking forward.
*
* @param session
* @param invokedBusinessInterface
* @param method
* @param args
* @see org.jboss.ejb3.endpoint.Endpoint#invoke(java.io.Serializable, java.lang.Class, java.lang.reflect.Method, java.lang.Object[])
*/
public Object invoke(final Serializable session, final Class<?> invokedBusinessInterface, final Method method,
final Object[] args) throws Throwable
{
/*
* For now we'll just delegate to the legacy implementation
* defined by InvokableContext.invoke; in the future this method
* will be the real handler
*/
//TODO Move away from InvokableContext contract EJBTHREE-1782
// Create a SerializableMethod view
SerializableMethod sMethod = new SerializableMethod(method, invokedBusinessInterface);
// Handle in the transition method
return this.invoke(session, sMethod, args);
}
/**
* {@inheritDoc}
* @see org.jboss.ejb3.async.spi.AsyncEndpoint#invokeAsync(java.io.Serializable, java.lang.Class, java.lang.reflect.Method, java.lang.Object[])
*/
@Override
public Object invokeAsync(final Serializable session, final Class<?> invokedBusinessInterface, final Method method,
final Object[] args) throws Throwable
{
// Get the ES
final ExecutorService es = this.getAsynchronousExecutor();
// Get the Security Context
final SecurityContext sc = SecurityActions.getSecurityContext();
// Make a new ID for the invocation
final AsyncInvocationId id = new AsyncInvocationIdUUIDImpl();
// Submit as async
final Future<?> returnValue = es.submit(new AsyncInvocationTask<Object>(session,method,args, sc, id));
// Return
return returnValue;
}
/**
* Task used to dispatch a local invocation upon the server
* asynchronously
* @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
*/
private class AsyncInvocationTask<V> extends AsyncInvocationTaskBase<V>
{
private final Serializable session;
private final Method method;
private final Object[] args;
AsyncInvocationTask(final Serializable session, final Method method, final Object[] args,
final SecurityContext sc, final AsyncInvocationId id)
{
super(sc, id, AccessController.doPrivileged(new PrivilegedAction<ClassLoader>()
{
@Override
public ClassLoader run()
{
return Thread.currentThread().getContextClassLoader();
}
}));
this.session = session;
this.method = method;
this.args = args;
}
/**
* {@inheritDoc}
* @see org.jboss.ejb3.async.spi.AsyncInvocationTaskBase#before()
*/
@Override
protected void before() throws Exception
{
// Mark the current invocation on the executing Thread
CurrentAsyncInvocation.markCurrentInvocationOnThread(this.id);
}
/**
* {@inheritDoc}
* @see org.jboss.ejb3.async.spi.AsyncInvocationTaskBase#proceed()
*/
@Override
@SuppressWarnings("unchecked")
protected V proceed() throws Throwable
{
// Invoke on the container
return (V) SessionSpecContainer.this.invoke(session, null, method, args);
}
/**
* {@inheritDoc}
* @see org.jboss.ejb3.async.spi.AsyncInvocationTaskBase#after()
*/
@Override
protected void after() throws Exception
{
// Unmark the current invocation from the executing Thread
CurrentAsyncInvocation.unmarkCurrentInvocationFromThread();
}
}
/**
* A transition method in moving from InvokableContext.invoke to Endpoint.invoke.
*
* Invokes the specified method upon the specified session, passing the specified
* arguments
*
* @param session
* @param method
* @param args
* @return
* @throws Throwable
*/
@Deprecated
public Object invoke(final Serializable session, final SerializableMethod method, final Object[] args)
throws Throwable
{
/*
* Replace the TCL with the CL for this Container
*/
ClassLoader oldLoader = SecurityActions.getContextClassLoader();
SecurityActions.setContextClassLoader(this.getClassloader());
/*
* Obtain the target method (advised)
*/
Method actualMethod = method.toMethod(this.getClassloader());
long hash = MethodHashing.calculateHash(actualMethod);
MethodInfo info = getAdvisor().getMethodInfo(hash);
if (info == null)
{
throw new RuntimeException("Method invocation via Proxy could not be found handled for EJB "
+ this.getEjbName() + " : " + method.toString()
+ ", probable error in virtual method registration w/ Advisor for the Container");
}
Method unadvisedMethod = info.getUnadvisedMethod();
SerializableMethod unadvisedSerializableMethod = new SerializableMethod(unadvisedMethod);
// Mark the start time
long start = System.currentTimeMillis();
try
{
Class<?> invokedBusinessInterface = Class.forName(method.getActualClassName(), false, getClassloader());
// EJBTHREE-2076 if it's a EJB2.x view invocation, then invoked business interface should be null.
if (this.isEjb2xView(invokedBusinessInterface.getName()))
{
invokedBusinessInterface = null;
}
// Increment invocation statistics
invokeStats.callIn();
/*
* Invoke directly if this is an EJB2.x Method
*/
if (unadvisedMethod != null && isHomeMethod(unadvisedSerializableMethod))
{
return invokeHomeMethod(actualMethod, args);
}
else if (unadvisedMethod != null && this.isEjbObjectMethod(unadvisedSerializableMethod))
{
return invokeEJBObjectMethod(session, info, args);
}
// FIXME: Ahem, stateful container invocation works on all.... (violating contract though)
/*
* Build an invocation
*/
StatefulContainerInvocation nextInvocation = new StatefulContainerInvocation(info, session,
invokedBusinessInterface, this.getAsynchronousExecutor(), this);
nextInvocation.getMetaData().addMetaData(SessionSpecRemotingMetadata.TAG_SESSION_INVOCATION,
SessionSpecRemotingMetadata.KEY_INVOKED_METHOD, method);
nextInvocation.setArguments(args);
/*
* Invoke
*/
return nextInvocation.invokeNext();
}
finally
{
/*
* Update Invocation Statistics
*/
if (unadvisedMethod != null)
{
// Mark end time
long end = System.currentTimeMillis();
// Calculate elapsed time
long elapsed = end - start;
// Update statistics with elapsed time
invokeStats.updateStats(unadvisedMethod, elapsed);
}
// Complete call to increment statistics
invokeStats.callOut();
SecurityActions.setContextClassLoader(oldLoader);
}
}
/**
* Invokes the method described by the specified serializable method
* as called from the specified proxy, using the specified arguments
*
* @param proxy The proxy making the invocation
* @param method The method to be invoked
* @param args The arguments to the invocation
* @throws Throwable A possible exception thrown by the invocation
* @return
*/
public Object invoke(Object proxy, SerializableMethod method, Object[] args) throws Throwable
{
/*
* Obtain Session ID
*/
Serializable sessionId = null;
// If coming from ejb3-proxy-impl
if (Proxy.isProxyClass(proxy.getClass()))
{
InvocationHandler handler = Proxy.getInvocationHandler(proxy);
assert handler instanceof SessionProxyInvocationHandler : "Requires "
+ SessionProxyInvocationHandler.class.getName();
SessionProxyInvocationHandler sHandler = (SessionProxyInvocationHandler) handler;
sessionId = (Serializable) sHandler.getTarget();
}
//TODO Session ID if nointerface
// Send along to the transition method
return this.invoke(sessionId, method, args);
}
/**
* {@inheritDoc}
* @see org.jboss.ejb3.async.spi.AsyncCancellableContext#cancel(org.jboss.ejb3.async.spi.AsyncInvocationId)
*/
@Override
public boolean cancel(final AsyncInvocationId id) throws IllegalArgumentException
{
// Precondition checks
if (id == null)
{
throw new IllegalArgumentException("ID must be specified");
}
// Put a flag in the cancel map, will be cleared when the invocation passes through
final AsyncInvocationMap map = this.getCurrentAsyncInvocations();
return map.put(id, true) != null;
}
/**
* Provides implementation for this bean's EJB 2.1 Home.create() method
*
* @param factory
* @param unadvisedMethod
* @param args
* @return
* @throws Exception
*/
protected Object invokeHomeCreate(Method method, Object args[]) throws Exception
{
/*
* Initialize
*/
// Hold the JNDI Name
String jndiName = null;
// Flag for if we've found the interface
boolean foundInterface = false;
// Name of the EJB2.x Interface Class expected
String ejb2xInterface = method.getReturnType().getName();
// Get Metadata
JBossSessionBeanMetaData smd = this.getMetaData();
/*
* Determine if the expected type is found in metadata as a EJB2.x Interface
*/
// Is this a Remote Interface ?
boolean isLocal = false;
String ejb2xRemoteInterface = smd.getRemote();
if (ejb2xInterface.equals(ejb2xRemoteInterface))
{
// We've found it, it's false
foundInterface = true;
jndiName = smd.getJndiName();
}
// Is this a local interface?
if (!foundInterface)
{
String ejb2xLocalInterface = smd.getLocal();
if (ejb2xInterface.equals(ejb2xLocalInterface))
{
// Mark as found
foundInterface = true;
isLocal = true;
jndiName = smd.getLocalJndiName();
}
}
// If we haven't yet found the interface
if (!foundInterface)
{
throw new RuntimeException("Specified return value for " + method + " notes an EJB 2.x interface: "
+ ejb2xInterface + "; this could not be found as either a valid remote or local interface for EJB "
+ this.getEjbName());
}
// Allow override of the remote proxy
if (!isLocal)
{
RemoteProxyFactory remoteProxyFactory = CurrentRemoteProxyFactory.get();
if (remoteProxyFactory != null)
return remoteProxyFactory.create(null);
}
// Lookup
String proxyFactoryKey = this.getJndiRegistrar().getProxyFactoryRegistryKey(jndiName, smd, isLocal);
Object factory = Ejb3RegistrarLocator.locateRegistrar().lookup(proxyFactoryKey);
// Cast
assert factory instanceof SessionProxyFactory : "Specified factory " + factory.getClass().getName()
+ " is not of type " + SessionProxyFactory.class.getName() + " as required by "
+ StatefulContainer.class.getName() + ", but was instead " + factory;
SessionSpecProxyFactory sessionFactory = null;
sessionFactory = SessionSpecProxyFactory.class.cast(factory);
// Create Proxy
Object proxy = sessionFactory.createProxyEjb2x();
// Return
return proxy;
}
/**
* TODO: work in progress (refactor both invokeHomeMethod's, localHomeInvoke)
*/
//TODO
private Object invokeHomeMethod(Method method, Object args[]) throws Exception
{
if (method.getName().equals(Ejb2xMethodNames.METHOD_NAME_HOME_CREATE))
{
return this.invokeHomeCreate(method, args);
}
else if (method.getName().equals(Ejb2xMethodNames.METHOD_NAME_HOME_REMOVE))
{
if (args[0] instanceof Handle)
removeHandle((Handle) args[0]);
else
{
throw new RemoveException(
"EJB 3.0 Specification Violation 3.6.2.2: Session beans do not have a primary key");
}
return null;
}
else
{
throw new IllegalArgumentException("illegal home method " + method);
}
}
/**
* @deprecated Use isHomeMethod(SerializableMethod method) in SessionSpecContainer
*/
@Deprecated
protected boolean isHomeMethod(Method method)
{
if (javax.ejb.EJBHome.class.isAssignableFrom(method.getDeclaringClass()))
return true;
if (javax.ejb.EJBLocalHome.class.isAssignableFrom(method.getDeclaringClass()))
return true;
return false;
}
/**
* Determines whether the specified method is an EJB2.x Home Method
*
* @param method
* @return
*/
protected boolean isHomeMethod(SerializableMethod method)
{
// Get the Method
Method invokingMethod = method.toMethod(this.getClassloader());
// Use legacy
return this.isHomeMethod(invokingMethod);
}
/**
* @param method
* @return
* @deprecated Use isEjbObjectMethod(SerializableMethod method)
*/
@Deprecated
protected boolean isEJBObjectMethod(Method method)
{
/*
* Initialize
*/
// Get the declaring class
Class<?> declaringClass = method.getDeclaringClass();
/*
* Test if declared by EJBObject/EJBLocalObject
*/
if (declaringClass.getName().equals(EJBObject.class.getName()))
return true;
if (declaringClass.getName().equals(EJBLocalObject.class.getName()))
return true;
return false;
}
/**
* Determines whether the specified method is an EJB2.x Local
* or Remote Method
*
* @param method
* @return
*/
protected boolean isEjbObjectMethod(SerializableMethod method)
{
/*
* Initialize
*/
// Get the declaring class
Class<?> declaringClass = null;
String declaringClassName = method.getDeclaringClassName();
try
{
declaringClass = Class.forName(declaringClassName, false, this.getClassloader());
}
catch (ClassNotFoundException e)
{
throw new RuntimeException("Invoked Method specifies a declaring class that could not be loaded by the "
+ ClassLoader.class.getSimpleName() + " for EJB " + this.getEjbName());
}
/*
* Test if declared by EJBObject/EJBLocalObject
*/
if (declaringClass.getName().equals(EJBObject.class.getName()))
return true;
if (declaringClass.getName().equals(EJBLocalObject.class.getName()))
return true;
// If we've reached here, not EJBObject/EJBLocalObject
return false;
}
/**
*
* @param method
* @return
* @deprecated Use isHandleMethod(SerializableMethod method)
*/
@Deprecated
protected boolean isHandleMethod(Method method)
{
if (method.getDeclaringClass().getName().equals(Handle.class.getName()))
return true;
return false;
}
/**
* Determines if the specified Method is a Handle Method
* @param method
* @return
*/
protected boolean isHandleMethod(SerializableMethod method)
{
// Get the Method
Method invokingMethod = method.toMethod(this.getClassloader());
// Use legacy
return this.isHandleMethod(invokingMethod);
}
/**
* Returns the busines interfaces for this EJB
* @return
*/
protected Set<Class<?>> getBusinessInterfaces()
{
if (businessInterfaces == null)
{
throw new IllegalStateException("Business interfaces not yet initialized");
}
return businessInterfaces;
}
protected BusinessObjectFactory getBusinessObjectFactory()
{
return businessObjectFactory;
}
/**
* Returns true if the passed fully qualified class name, represents a
* EJB 2.x view of the EJB, corresponding to this container. Else
* returns false.
*
* @param intf Fully qualified class name
* @return
*/
private boolean isEjb2xView(String intf)
{
String remote = this.getMetaData().getRemote();
if (remote != null && remote.equals(intf))
{
return true;
}
String local = this.getMetaData().getLocal();
if (local != null && local.equals(intf))
{
return true;
}
return false;
}
// ------------------------------------------------------------------------------||
// Lifecycle Methods ------------------------------------------------------------||
// ------------------------------------------------------------------------------||
/**
* Lifecycle Start
*/
@Override
protected void lockedStart() throws Exception
{
log.info("Starting " + this);
// Cache the business interfaces
final Set<Class<?>> set = new HashSet<Class<?>>();
final Set<String> businessInterfaceNames = new HashSet<String>();
if (this.getMetaData().getBusinessLocals() != null)
{
businessInterfaceNames.addAll(this.getMetaData().getBusinessLocals());
}
if (this.getMetaData().getBusinessRemotes() != null)
{
businessInterfaceNames.addAll(this.getMetaData().getBusinessRemotes());
}
for (final String businessInterfaceName : businessInterfaceNames)
{
final Class<?> businessInterface;
try
{
businessInterface = Class.forName(businessInterfaceName, false, this.getClassloader());
}
catch (final ClassNotFoundException cnfe)
{
throw new RuntimeException("Could not find marked business interface in this EJB's ClassLoader", cnfe);
}
set.add(businessInterface);
}
businessInterfaces = Collections.unmodifiableSet(set);
verifyBusinessObjectFactory();
super.lockedStart();
}
/**
* Lifecycle Stop
*/
@Override
protected void lockedStop() throws Exception
{
log.info("Stopping " + this);
super.lockedStop();
}
/**
* Returns the {@link ExecutorService} used in processing Asynchronous invocations
*/
public ExecutorService getAsynchronousExecutor()
{
return this.asynchronousExecutor;
}
public void setBusinessObjectFactory(BusinessObjectFactory factory)
{
this.businessObjectFactory = factory;
}
protected void verifyBusinessObjectFactory()
{
log.warn("EJBTHREE-2126: container " + this + " does not verify the businessObjectFactory");
}
public AsyncInvocationMap getCurrentAsyncInvocations()
{
return this.currentAsyncInvocations;
}
@Inject(bean = "org.jboss.ejb3.async.AsyncInvocationsMap")
public void setCurrentAsyncInvocations(final AsyncInvocationMap currentAsyncInvocations)
{
this.currentAsyncInvocations = currentAsyncInvocations;
}
}