/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.ejb.containers;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.*;
import java.lang.reflect.InvocationTargetException;
import javax.ejb.*;
import javax.ejb.MessageDriven;
import javax.naming.InitialContext;
import javax.naming.Context;
import javax.transaction.*;
import javax.transaction.xa.*;
import com.sun.ejb.*;
import com.sun.ejb.containers.util.pool.Pool;
import com.sun.ejb.containers.util.pool.AbstractPool;
import com.sun.ejb.containers.util.pool.NonBlockingPool;
import com.sun.ejb.containers.util.pool.ObjectFactory;
import com.sun.enterprise.util.LocalStringManagerImpl;
import com.sun.enterprise.util.Utility;
import com.sun.enterprise.*;
import com.sun.enterprise.deployment.EjbDescriptor;
import com.sun.enterprise.deployment.MethodDescriptor;
import com.sun.enterprise.deployment.EjbMessageBeanDescriptor;
import static com.sun.enterprise.deployment.LifecycleCallbackDescriptor.CallbackType;
import com.sun.enterprise.deployment.runtime.BeanPoolDescriptor;
import com.sun.enterprise.server.ServerContext;
import com.sun.enterprise.server.ApplicationServer;
import com.sun.enterprise.config.serverbeans.ServerBeansFactory;
import com.sun.enterprise.config.serverbeans.MdbContainer;
import com.sun.enterprise.config.ConfigException;
import com.sun.enterprise.resource.ResourceHandle;
import com.sun.enterprise.log.Log;
import com.sun.enterprise.ServerConfiguration;
import com.sun.enterprise.admin.monitor.callflow.ComponentType;
import com.sun.enterprise.appverification.factory.AppVerification;
import com.sun.enterprise.admin.monitor.*;
import com.sun.ejb.containers.util.pool.AbstractPool;
import java.util.logging.*;
import com.sun.logging.*;
import com.sun.ejb.spi.stats.MessageDrivenBeanStatsProvider;
import com.sun.enterprise.connectors.util.ResourcesUtil;
import com.sun.ejb.containers.util.ContainerWorkPool;
/** This class provides container functionality specific to message-driven
* EJBs.
* At deployment time, one instance of the MessageDrivenBeanContainer is
* created for each message-driven bean in an application.
* <P>
* The 3 states of a Message-driven EJB (an EJB can be in only 1 state
* at a time):
* 1. POOLED : ready for invocations, no transaction in progress
* 2. INVOKING : processing an invocation
* 3. DESTROYED : does not exist
*
* A Message-driven Bean can hold open DB connections across invocations.
* It's assumed that the Resource Manager can handle
* multiple incomplete transactions on the same
* connection.
*
* @author Kenneth Saks
*/
public final class MessageBeanContainer extends BaseContainer
implements MessageBeanProtocolManager, MessageDrivenBeanStatsProvider
{
private static final Logger _logger =
LogDomains.getLogger(LogDomains.MDB_LOGGER);
private String appEJBName_;
private static LocalStringManagerImpl localStrings =
new LocalStringManagerImpl(MessageBeanContainer.class);
// Message-bean instance states
private static final int POOLED=1, INVOKING=2, DESTROYED=3;
private MessageBeanClient messageBeanClient_ = null;
private AbstractPool messageBeanPool_ = null;
private BeanPoolDescriptor beanPoolDesc_ = null;
private int maxMessageBeanListeners_;
private int numMessageBeanListeners_;
// Property used to bootstrap message bean client factory for inbound
// message delivery.
private static final String MESSAGE_BEAN_CLIENT_FACTORY_PROP =
"com.sun.enterprise.MessageBeanClientFactory";
private static final String DEFAULT_MESSAGE_BEAN_CLIENT_FACTORY =
"com.sun.enterprise.connectors.inflow.ConnectorMessageBeanClientFactory";
private static final int DEFAULT_RESIZE_QUANTITY = 1;
private static final int DEFAULT_STEADY_SIZE = 10;
private static final int DEFAULT_MAX_POOL_SIZE = 60;
private static final int DEFAULT_IDLE_TIMEOUT = 600;
private static final int MIN_IDLE_TIMEOUT = 1;
private int statMessageCount = 0;
MessageBeanContainer(EjbDescriptor desc, ClassLoader loader)
throws Exception
{
super(desc, loader);
appEJBName_ =
desc.getApplication().getRegistrationName() + ":" +desc.getName();
EjbMessageBeanDescriptor msgBeanDesc =
(EjbMessageBeanDescriptor) desc;
try {
// Register the tx attribute for each method on MessageListener
// interface. NOTE : These method objects MUST come from the
// MessageListener interface, NOT the bean class itself. This
// is because the message bean container clients do not have
// access to the message bean class.
Method[] msgListenerMethods =
msgBeanDesc.getMessageListenerInterfaceMethods(loader);
for(int i = 0; i < msgListenerMethods.length; i++) {
Method next = msgListenerMethods[i];
super.registerTxAttrForMethod(next, MethodDescriptor.EJB_BEAN);
}
// NOTE : No need to register tx attribute for ejbTimeout. It's
// done in BaseContainer intialization.
// Message-driven beans can be timed objects.
// Bootstrap message bean client factory. If the class name is
// specified as a system property, that value takes precedence.
// Otherwise use default client factory. The default is set to
// a client factory that uses the S1AS 7 style JMS connection
// consumer contracts. This will be changed once the Connector 1.5
// implementation is ready.
String factoryClassName = System.getProperty
(MESSAGE_BEAN_CLIENT_FACTORY_PROP,
DEFAULT_MESSAGE_BEAN_CLIENT_FACTORY);
Class clientFactoryClass = Class.forName(factoryClassName);
MessageBeanClientFactory clientFactory = (MessageBeanClientFactory)
clientFactoryClass.newInstance();
_logger.log(Level.FINE, "Using " + factoryClassName +
" for message bean client factory in " + appEJBName_);
// Create message bean pool before calling setup on
// Message-bean client, since pool properties can be retrieved
// through MessageBeanProtocolManager interface.
createMessageBeanPool(msgBeanDesc);
// Set resource limit for message bean listeners created through
// Protocol Manager. For now, just use max pool size. However,
// we might want to bump this up once the ejb timer service is
// integrated.
maxMessageBeanListeners_ = beanPoolDesc_.getMaxPoolSize();
numMessageBeanListeners_ = 0;
messageBeanClient_ =
clientFactory.createMessageBeanClient(msgBeanDesc);
messageBeanClient_.setup(this);
registerMonitorableComponents(msgListenerMethods);
createCallFlowAgent(ComponentType.MDB);
}
catch (Exception ex) {
if (messageBeanClient_ != null) {
messageBeanClient_.close();
}
_logger.log(Level.SEVERE,
"containers.mdb.create_container_exception",
new Object[]{desc.getName(), ex.toString()});
_logger.log(Level.SEVERE, ex.getClass().getName(), ex);
throw ex;
}
}
protected void registerMonitorableComponents(Method[] msgListenerMethods) {
registryMediator.registerProvider(this);
super.registerMonitorableComponents();
super.populateMethodMonitorMap(msgListenerMethods);
_logger.log(Level.FINE, "[Entity Container] registered monitorable");
}
private void createMessageBeanPool(EjbMessageBeanDescriptor descriptor)
throws ConfigException {
beanPoolDesc_ = descriptor.getIASEjbExtraDescriptors().getBeanPool();
if (beanPoolDesc_ == null) {
beanPoolDesc_ = new BeanPoolDescriptor();
}
ServerContext sc = ApplicationServer.getServerContext();
MdbContainer mdbc = ServerBeansFactory.
getConfigBean(sc.getConfigContext()).getMdbContainer();
int maxPoolSize = beanPoolDesc_.getMaxPoolSize();
if (maxPoolSize < 0) {
maxPoolSize =
stringToInt(mdbc.getMaxPoolSize(), appEJBName_, _logger);
}
maxPoolSize = validateValue(maxPoolSize, 1, -1, DEFAULT_MAX_POOL_SIZE,
"max-pool-size", appEJBName_, _logger);
beanPoolDesc_.setMaxPoolSize(maxPoolSize);
int value = beanPoolDesc_.getSteadyPoolSize();
if (value < 0) {
value = stringToInt
(mdbc.getSteadyPoolSize(), appEJBName_, _logger);
}
value = validateValue(value, 0, maxPoolSize, DEFAULT_STEADY_SIZE,
"steady-pool-size", appEJBName_, _logger);
beanPoolDesc_.setSteadyPoolSize(value);
value = beanPoolDesc_.getPoolResizeQuantity();
if (value < 0 ) {
value = stringToInt
(mdbc.getPoolResizeQuantity(), appEJBName_, _logger);
}
value = validateValue(value, 1, maxPoolSize, DEFAULT_RESIZE_QUANTITY,
"pool-resize-quantity", appEJBName_, _logger);
beanPoolDesc_.setPoolResizeQuantity(value);
value = beanPoolDesc_.getPoolIdleTimeoutInSeconds();
if (value <= 0) {
value = stringToInt(mdbc.getIdleTimeoutInSeconds(),
appEJBName_, _logger);
}
value = validateValue(value, MIN_IDLE_TIMEOUT, -1,
DEFAULT_IDLE_TIMEOUT, "idle-timeout-in-seconds",
appEJBName_, _logger);
beanPoolDesc_.setPoolIdleTimeoutInSeconds(value);
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, appEJBName_ +
": Setting message-driven bean pool max-pool-size=" +
beanPoolDesc_.getMaxPoolSize() +
", steady-pool-size=" + beanPoolDesc_.getSteadyPoolSize() +
", pool-resize-quantity=" +
beanPoolDesc_.getPoolResizeQuantity() +
", idle-timeout-in-seconds=" +
beanPoolDesc_.getPoolIdleTimeoutInSeconds());
}
// Create a non-blocking pool of message bean instances.
// The protocol manager implementation enforces a limit
// on message bean resources independent of the pool.
ObjectFactory objFactory = new MessageBeanContextFactory();
messageBeanPool_ = new NonBlockingPool
(appEJBName_,
objFactory,
beanPoolDesc_.getSteadyPoolSize(),
beanPoolDesc_.getPoolResizeQuantity(),
beanPoolDesc_.getMaxPoolSize(),
beanPoolDesc_.getPoolIdleTimeoutInSeconds(),
loader);
registryMediator.registerProvider(messageBeanPool_);
//super.setMonitorOn(mdbc.isMonitoringEnabled());
}
protected static int stringToInt(String val, String appName,
Logger logger) {
int value = -1;
try {
value = Integer.parseInt(val);
} catch (Exception e) {
_logger.log(Level.WARNING,"containers.mdb.invalid_value" ,
new Object[]{appName, new Integer(val) , e.toString(),
new Integer(0)} );
_logger.log(Level.WARNING, "", e);
}
return value;
}
//deft should always >= lowLimit
protected int validateValue(int value, int lowLimit, int highLimit,
int deft, String emsg, String appName, Logger logger) {
if (value < lowLimit ) {
_logger.log(Level.WARNING,"containers.mdb.invalid_value" ,
new Object[]{appName, new Integer(value) , emsg ,
new Integer(lowLimit)} );
value = deft;
}
if ((highLimit >= 0) && (value > highLimit)) {
_logger.log(Level.WARNING,"containers.mdb.invalid_value" ,
new Object[]{appName, new Integer(value) , emsg ,
new Integer(highLimit)} );
value = highLimit;
}
return value;
}
private boolean containerStartsTx(Method method) {
int txMode = getTxAttr(method, MethodDescriptor.EJB_BEAN);
return method.equals(ejbTimeoutMethod) ?
( (txMode == TX_REQUIRES_NEW) || (txMode == TX_REQUIRED) )
: (txMode == TX_REQUIRED);
}
public String getMonitorAttributeValues() {
StringBuffer sbuf = new StringBuffer();
sbuf.append("MESSAGEDRIVEN ");
sbuf.append(appEJBName_);
sbuf.append(messageBeanPool_.getAllAttrValues());
sbuf.append("]");
return sbuf.toString();
}
public boolean userTransactionMethodsAllowed(ComponentInvocation inv)
{
boolean utMethodsAllowed = false;
if( isBeanManagedTran ) {
if( inv instanceof Invocation ) {
Invocation i = (Invocation) inv;
EJBContextImpl mdc = (EJBContextImpl) i.context;
utMethodsAllowed = (mdc.isUnitialized() || mdc.isInEjbRemove())
? false : true;
}
}
return utMethodsAllowed;
}
public void setEJBHome(EJBHome ejbHome)
throws Exception
{
throw new Exception("Can't set EJB Home on Message-driven bean");
}
public EJBObjectImpl getEJBObjectImpl(byte[] instanceKey)
{
throw new EJBException("No EJBObject for message-driven beans");
}
public EJBObjectImpl createEJBObjectImpl()
throws CreateException
{
throw new EJBException("No EJBObject for message-driven beans");
}
void removeBean(EJBLocalRemoteObject ejbo, Method removeMethod,
boolean local)
throws RemoveException, EJBException
{
throw new EJBException("not used in message-driven beans");
}
/**
* Override callEJBTimeout from BaseContainer since delivery to
* message driven beans is a bit different from session/entity.
*/
boolean callEJBTimeout(RuntimeTimerState timerState,
EJBTimerService timerService) throws Exception {
boolean redeliver = false;
// There is no resource associated with the delivery of the timeout.
ResourceHandle nullResourceHandle = null;
try {
// Do pre-invoke logic for message bean with tx import = false
// and a null resource handle.
beforeMessageDelivery(ejbTimeoutMethod, false, nullResourceHandle);
// Application must be passed a TimerWrapper.
Object[] args = { new TimerWrapper(timerState.getTimerId(),
timerService) };
deliverMessage(args);
} catch(Throwable t) {
// A runtime exception thrown from ejbTimeout, independent of
// its transactional setting(CMT, BMT, etc.), should result in
// a redelivery attempt. The instance that threw the runtime
// exception will be destroyed, as per the EJB spec.
redeliver = true;
_logger.log(Level.FINE, "ejbTimeout threw Runtime exception", t);
} finally {
if( !isBeanManagedTran && (transactionManager.getStatus() ==
Status.STATUS_MARKED_ROLLBACK) ) {
redeliver = true;
_logger.log(Level.FINE, "ejbTimeout called setRollbackOnly");
}
// Only call postEjbTimeout if there are no errors so far.
if( !redeliver ) {
boolean successfulPostEjbTimeout =
timerService.postEjbTimeout(timerState.getTimerId());
redeliver = !successfulPostEjbTimeout;
}
// afterMessageDelivery takes care of postInvoke and postInvokeTx
// processing. If any portion of that work fails, mark
// timer for redelivery.
boolean successfulAfterMessageDelivery =
afterMessageDeliveryInternal(nullResourceHandle);
if( !redeliver && !successfulAfterMessageDelivery) {
redeliver = true;
}
}
return redeliver;
}
/**
* Force destroy the EJB. Called from postInvokeTx.
* Note: EJB2.0 section 18.3.1 says that discarding an EJB
* means that no methods other than finalize() should be invoked on it.
*/
void forceDestroyBean(EJBContextImpl sc)
{
if ( sc.getState() == DESTROYED )
return;
// mark context as destroyed
sc.setState(DESTROYED);
messageBeanPool_.destroyObject(sc);
}
// This particular preInvoke signature not used
public void preInvoke(Invocation inv) {
throw new EJBException("preInvoke(Invocation) not supported");
}
private class MessageBeanContextFactory implements ObjectFactory {
public Object create(Object param) {
try {
return createMessageDrivenEJB();
} catch (CreateException ex) {
throw new EJBException(ex);
}
}
public void destroy(Object obj) {
MessageBeanContextImpl beanContext =
(MessageBeanContextImpl) obj;
Object ejb = beanContext.getEJB();
if( beanContext.getState() != DESTROYED ) {
// Called from pool implementation to reduce the pool size.
// So we need to call ejb.ejbRemove() and
// mark context as destroyed.
Invocation inv = null;
try {
// NOTE : Context class-loader is already set by Pool
inv = new Invocation(ejb, MessageBeanContainer.this);
inv.context = beanContext;
inv.isMessageDriven = true;
invocationManager.preInvoke(inv);
beanContext.setInEjbRemove(true);
interceptorManager.intercept(
CallbackType.PRE_DESTROY, beanContext);
statRemoveCount++;
} catch ( Throwable t ) {
_logger.log(Level.SEVERE,
"containers.mdb_preinvoke_exception_indestroy",
new Object[]{appEJBName_, t.toString()});
_logger.log(Level.SEVERE, t.getClass().getName(), t);
} finally {
beanContext.setInEjbRemove(false);
if ( inv != null ) {
invocationManager.postInvoke(inv);
}
}
beanContext.setState(DESTROYED);
}
// tell the TM to release resources held by the bean
transactionManager.ejbDestroyed(beanContext);
// Message-driven beans can't have transactions across
// invocations.
beanContext.setTransaction(null);
}
}
protected ComponentContext _getContext(Invocation inv)
{
MessageBeanContextImpl context = null;
try {
context = (MessageBeanContextImpl) messageBeanPool_.getObject(null);
context.setState(INVOKING);
} catch(Exception e) {
throw new EJBException(e);
}
return context;
}
/**
* Return instance to a pooled state.
*/
public void releaseContext(Invocation inv)
{
MessageBeanContextImpl beanContext = (MessageBeanContextImpl)
inv.context;
if( beanContext.getState() == DESTROYED ) {
return;
}
beanContext.setState(POOLED);
// Message-driven beans can't have transactions across invocations.
beanContext.setTransaction(null);
// Update last access time so pool's time-based logic will work best
beanContext.touch();
messageBeanPool_.returnObject(beanContext);
}
public void appendStats(StringBuffer sbuf) {
sbuf.append("\nMessageBeanContainer: ")
.append("CreateCount=").append(statCreateCount).append("; ")
.append("RemoveCount=").append(statRemoveCount).append("; ")
.append("MsgCount=").append(statMessageCount).append("; ");
sbuf.append("]");
}
// This particular postInvoke signature not used
public void postInvoke(Invocation inv) {
throw new EJBException("postInvoke(Invocation) not supported " +
"in message-driven bean container");
}
/****************************************************************
* The following are implementation for methods required by the *
* MessageBeanProtocalManager interface. *
****************************************************************/
public MessageBeanListener createMessageBeanListener
(ResourceHandle resource) throws ResourcesExceededException {
boolean resourcesExceeded = false;
synchronized (this) {
if( numMessageBeanListeners_ < maxMessageBeanListeners_ ) {
numMessageBeanListeners_++;
} else {
resourcesExceeded = true;
}
}
if( resourcesExceeded ) {
ResourcesExceededException ree =
new ResourcesExceededException("Message Bean Resources " +
"exceeded for message bean " + appEJBName_);
_logger.log(Level.FINE, "exceeded max of " +
maxMessageBeanListeners_, ree);
throw ree;
}
//
// Message bean context/instance creation is decoupled from
// MessageBeanListener instance creation. This typically means
// the message bean instances are instantiated lazily upon actual
// message delivery. In addition, MessageBeanListener instances
// are not pooled since they are currently very small objects without
// much initialization overhead. This is the simplest approach since
// there is minimal state to track between invocations and upon
// error conditions such as message bean instance failure. However,
// it could be optimized in the following ways :
//
// 1. Implement MessageBeanListener within MessageBeanContextImpl.
// This reduces the number of objects created per thread of delivery.
//
// 2. Associate message bean context/instance with MessageBeanListener
// across invocations. This saves one pool retrieval and one
// pool replacement operation for each invocation.
//
//
return new MessageBeanListenerImpl(this, resource);
}
public void destroyMessageBeanListener(MessageBeanListener listener) {
synchronized (this) {
numMessageBeanListeners_--;
}
}
/**
* @param method One of the methods used to deliver messages, e.g.
* onMessage method for javax.jms.MessageListener.
* Note that if the <code>method</code> is not one
* of the methods for message delivery, the behavior
* of this method is not defined.
*/
public boolean isDeliveryTransacted (Method method) {
return containerStartsTx(method);
}
public BeanPoolDescriptor getPoolDescriptor() {
return beanPoolDesc_;
}
/**
* Instantiate and initialize a message-driven bean instance.
*/
private MessageBeanContextImpl createMessageDrivenEJB()
throws CreateException {
Invocation inv = null;
MessageBeanContextImpl context = null;
ClassLoader originalClassLoader = null;
boolean methodCalled = false;
boolean methodCallFailed = false;
try {
// Set application class loader before invoking instance.
originalClassLoader = Utility.setContextClassLoader(getClassLoader());
// create new message-driven ejb
Object ejb = ejbClass.newInstance();
// create MessageDrivenContext and set it in the EJB
context = new MessageBeanContextImpl(ejb, this);
context.setInterceptorInstances(
interceptorManager.createInterceptorInstances());
// java:comp/env lookups are allowed from here on...
inv = new Invocation(ejb, this);
inv.context = context;
inv.isMessageDriven = true;
invocationManager.preInvoke(inv);
if( ejb instanceof MessageDrivenBean ) {
// setMessageDrivenContext will be called without a Tx
// as required by the spec
((MessageDrivenBean)ejb).setMessageDrivenContext(context);
}
// Perform injection right after where setMessageDrivenContext
// would be called. This is important since injection methods
// have the same "operations allowed" permissions as
// setMessageDrivenContext.
injectionManager.injectInstance(ejb, ejbDescriptor, false);
for (Object interceptorInstance : context.getInterceptorInstances()) {
injectionManager.injectInstance(interceptorInstance,
ejbDescriptor, false);
}
// Set flag in context so UserTransaction can
// be used from ejbCreate. Didn't want to add
// a new state to lifecycle since that would
// require either changing lots of code in
// EJBContextImpl or re-implementing all the
// context methods within MessageBeanContextImpl.
context.setContextCalled();
// Call ejbCreate OR @PostConstruct on the bean.
interceptorManager.intercept(
CallbackType.POST_CONSTRUCT, context);
statCreateCount++;
// Set the state to POOLED after ejbCreate so that
// EJBContext methods not allowed will throw exceptions
context.setState(POOLED);
}
catch ( Throwable t ) {
_logger.log(Level.SEVERE,
"containers.mdb.ejb_creation_exception",
new Object[]{appEJBName_, t.toString()});
if (t instanceof InvocationTargetException) {
_logger.log(Level.SEVERE, t.getClass().getName(),
t.getCause());
}
_logger.log(Level.SEVERE, t.getClass().getName(), t);
CreateException ce =
new CreateException("Could not create Message-Driven EJB");
ce.initCause(t);
throw ce;
} finally {
if( originalClassLoader != null ) {
Utility.setContextClassLoader(originalClassLoader);
}
if ( inv != null ) {
invocationManager.postInvoke(inv);
}
}
return context;
}
/**
* Make the work performed by a message-bean instance's
* associated XA resource part of any global transaction
*/
private void registerMessageBeanResource(ResourceHandle resourceHandle)
throws Exception {
if( resourceHandle != null ) {
Switch theSwitch = Switch.getSwitch();
PoolManager poolMgr = theSwitch.getPoolManager();
poolMgr.registerResource(resourceHandle);
}
}
private void unregisterMessageBeanResource(ResourceHandle resourceHandle) {
// resource handle may be null if preInvokeTx error caused
// ResourceAllocator.destroyResource()
if (resourceHandle != null) {
Switch theSwitch = Switch.getSwitch();
PoolManager poolMgr = theSwitch.getPoolManager();
poolMgr.unregisterResource(resourceHandle, XAResource.TMSUCCESS);
}
}
void afterBegin(EJBContextImpl context)
{
// Message-driven Beans cannot implement SessionSynchronization!!
}
void beforeCompletion(EJBContextImpl context)
{
// Message-driven beans cannot implement SessionSynchronization!!
}
void afterCompletion(EJBContextImpl ctx, int status)
{
// Message-driven Beans cannot implement SessionSynchronization!!
}
// default
public boolean passivateEJB(ComponentContext context)
{
return false;
}
// default
public void activateEJB(Object ctx, Object instanceKey)
{
}
/**
* Called when the application containing this message-bean
* has successfully deployed.
*/
public void doAfterApplicationDeploy() {
super.doAfterApplicationDeploy();
// Start delivery of messages to message bean instances.
try {
messageBeanClient_.start();
}
catch(Exception e) {
_logger.log(Level.SEVERE,
"containers.mdb.start_message_delivery_exception",
new Object[]{appEJBName_, e.toString()});
_logger.log(Level.SEVERE, e.getClass().getName(), e);
}
}
private void cleanupResources() {
ASyncClientShutdownTask task = new ASyncClientShutdownTask(appEJBName_,
messageBeanClient_, loader, messageBeanPool_);
int timeout = 0;
try {
timeout = ResourcesUtil.createInstance().getShutdownTimeout();
} catch (Throwable th) {
_logger.log(Level.WARNING, "[MDBContainer] Got exception while trying "
+ " to get shutdown timeout", th);
}
try {
boolean addedAsyncTask = false;
if (timeout > 0) {
try {
ContainerWorkPool.addLast(task);
addedAsyncTask = true;
} catch (Throwable th) {
//Since we got an exception while trying to add the async task
// we will have to do the cleanup in the current thread itself.
addedAsyncTask = false;
_logger.log(Level.WARNING, "[MDBContainer] Got exception while trying "
+ "to add task to ContainerWorkPool. Will execute "
+ "cleanupResources on current thread", th);
}
}
if (addedAsyncTask) {
synchronized (task) {
if (! task.isDone()) {
_logger.log(Level.FINE, "[MDBContainer] "
+ "Going to wait for a maximum of " + timeout + " seconds.");
task.wait(timeout * 1000);
}
if (! task.isDone()) {
_logger.log(Level.WARNING, "[MDBContainer] ASync task has not finished. "
+ "Giving up after " + timeout + " seconds.");
} else {
_logger.log(Level.FINE, "[MDBContainer] ASync task has completed");
}
}
} else {
//Execute in the same thread
_logger.log(Level.FINE, "[MDBContainer] Attempting to do cleanup()in current thread...");
task.run();
_logger.log(Level.WARNING, "[MDBContainer] Current thread done cleanup()... ");
}
} catch (InterruptedException inEx) {
_logger.log(Level.SEVERE, "containers.mdb.cleanup_exception",
new Object[]{appEJBName_, inEx.toString()});
} catch (Exception ex) {
_logger.log(Level.SEVERE, "containers.mdb.cleanup_exception",
new Object[]{appEJBName_, ex.toString()});
}
}
private static class ASyncClientShutdownTask
implements com.sun.enterprise.util.threadpool.Servicable
{
private boolean done = false;
String appName;
MessageBeanClient mdbClient;
ClassLoader clsLoader;
AbstractPool mdbPool;
ASyncClientShutdownTask(String appName,
MessageBeanClient mdbClient, ClassLoader loader,
AbstractPool mdbPool) {
this.appName = appName;
this.mdbClient = mdbClient;
this.clsLoader = loader;
this.mdbPool = mdbPool;
}
public void prolog() { }
public void epilog() { }
public void service() { run(); }
public void run() {
ClassLoader previousClassLoader = null;
try {
previousClassLoader =
Utility.setContextClassLoader(clsLoader);
// Cleanup the message bean client resources.
mdbClient.close();
synchronized(this) {
this.done = true;
this.notify();
_logger.log(Level.FINE, "[MDBContainer] ASync thread done with mdbClient.close()");
}
} catch(Exception e) {
_logger.log(Level.SEVERE, "containers.mdb.cleanup_exception",
new Object[]{appName, e.toString()});
_logger.log(Level.SEVERE, e.getClass().getName(), e);
} finally {
try {
mdbPool.close();
} catch (Exception ex) {
_logger.log(Level.FINE, "Exception while closing pool", ex);
}
if (previousClassLoader != null) {
Utility.setContextClassLoader(previousClassLoader);
}
}
}
public synchronized boolean isDone() {
return this.done;
}
}
public void undeploy()
{
// This will cause all new invocations to be rejected.
super.setUndeployedState();
_logger.log(Level.FINE, "containers.mdb.undeploy", appEJBName_);
cleanupResources();
super.undeploy();
}
/**
* Called when server instance is shuting down
*/
public void onShutdown() {
_logger.log(Level.FINE,
"containers.mdb.shutdown_cleanup_start", appEJBName_);
setStoppedState();
monitorOn = false;
cleanupResources();
_logger.log(Level.FINE,
"containers.mdb.shutdown_cleanup_end", appEJBName_);
}
/**
* Actual message delivery happens in three steps :
*
* 1) beforeMessageDelivery(Message, MessageListener)
* This is our chance to make the message delivery
* itself part of the instance's global transaction.
*
* 2) onMessage(Message, MessageListener)
* This is where the container delegates to the
* actual ejb instance's onMessage method.
*
* 3) afterMessageDelivery(Message, MessageListener)
* Perform transaction cleanup and error handling.
*
* We use the Invocation manager's thread-specific state
* to track the invocation across these three calls.
*
*/
public void beforeMessageDelivery(Method method, boolean txImported,
ResourceHandle resourceHandle) {
if (containerState != CONTAINER_STARTED) { // i.e. no invocation
String errorMsg = localStrings.getLocalString
("containers.mdb.invocation_closed", appEJBName_ +
": Message-driven bean invocation closed by container",
new Object[] { appEJBName_ });
throw new EJBException(errorMsg);
}
Invocation invocation = new Invocation();
try {
MessageBeanContextImpl context =
(MessageBeanContextImpl) getContext(invocation);
// Set the context class loader here so that message producer will
// have access to application class loader during message processing.
// The previous context class loader will be restored in
// afterMessageDelivery.
invocation.originalContextClassLoader =
Utility.setContextClassLoader(getClassLoader());
invocation.isMessageDriven = true;
invocation.method = method;
context.setState(INVOKING);
invocation.context = context;
invocation.instance = context.getEJB();
invocation.ejb = context.getEJB();
invocation.container = this;
// Message Bean Container only starts a new transaction if
// there is no imported transaction and the message listener
// method has tx attribute TX_REQUIRED or the ejbTimeout has
// tx attribute TX_REQUIRES_NEW/TX_REQUIRED
boolean startTx = false;
if( !txImported ) {
startTx = containerStartsTx(method);
}
// keep track of whether tx was started for later.
invocation.containerStartsTx = startTx;
this.invocationManager.preInvoke(invocation);
if( startTx ) {
// Register the session associated with the message-driven
// bean's destination so the message delivery will be
// part of the container-managed transaction.
registerMessageBeanResource(resourceHandle);
}
preInvokeTx(invocation);
} catch(Throwable c) {
if (containerState != CONTAINER_STARTED ) {
_logger.log(Level.SEVERE,"containers.mdb.preinvoke_exception",
new Object[]{appEJBName_, c.toString()});
_logger.log(Level.SEVERE, c.getClass().getName(), c);
}
invocation.exception = c;
}
}
public Object deliverMessage(Object[] params) throws Throwable {
Invocation invocation = null;
boolean methodCalled = false; //for monitoring
Object result = null;
invocation = (Invocation) invocationManager.getCurrentInvocation();
if (invocation == null && _logger.isLoggable(Level.FINEST)) {
if (containerState != CONTAINER_STARTED) {
_logger.log(Level.FINEST, "No invocation in onMessage " +
" (container closing)");
}
else {
_logger.log(Level.FINEST, "No invocation in onMessage : ");
}
}
if( ( invocation != null ) && ( invocation.exception == null ) ) {
try {
// NOTE : Application classloader already set in
// beforeMessageDelivery
methodCalled = true;
if (ejbMethodStatsManager.isMethodMonitorOn()){
ejbMethodStatsManager.preInvoke(invocation.method);
}
invocation.methodParams = params;
if( isTimedObject() &&
invocation.method.equals(ejbTimeoutMethod) ) {
invocation.beanMethod = invocation.method;
invokeTargetBeanMethod(ejbTimeoutMethod, invocation,
invocation.ejb,
invocation.methodParams, null);
} else {
// invocation.beanMethod is the actual target method from
// the bean class. The bean class is not required to be
// a formal subtype of the message listener interface, so
// we need to be careful to invoke through the bean class
// method itself. This info is also returned from the
// interceptor context info.
invocation.beanMethod = invocation.ejb.getClass().getMethod
(invocation.method.getName(),
invocation.method.getParameterTypes());
result = super.intercept(invocation);
}
} catch(InvocationTargetException ite) {
//
// In EJB 2.1, message listener method signatures do not have
// any restrictions on what kind of exceptions can be thrown.
// This was not the case in J2EE 1.3, since JMS message driven
// beans could only implement
// void javax.jms.MessageListener.onMessage() , which does
// not declare any exceptions.
//
// In the J2EE 1.3 implementation, exceptions were only
// propagated when the message driven bean was not configured
// with CMT/Required transaction mode. This has been changed
// due to the Connector 1.5 integration. Now, all exceptions
// are propagated regardless of the tx mode. (18.2.2)
// Application exceptions are propagated as is, while system
// exceptions are wrapped in an EJBException.
//
// If an exception is thrown and there is a container-started
// transaction, the semantics are the same as for other ejb
// types whose business methods throw an exception.
// Specifically, if the exception thrown is an Application
// exception(defined in 18.2.1), it does not automatically
// result in a rollback of the container-started transaction.
//
Throwable cause = ite.getCause();
// set cause on invocation , rather than the propagated
// EJBException
invocation.exception = cause;
if( isSystemUncheckedException(cause) ) {
EJBException ejbEx = new EJBException
("message-driven bean method " + invocation.method +
" system exception");
ejbEx.initCause(cause);
cause = ejbEx;
}
throw cause;
} catch(Throwable t) {
EJBException ejbEx =
new EJBException("message-bean container dispatch error");
ejbEx.initCause(t);
invocation.exception = ejbEx;
throw ejbEx;
} finally {
if (methodCalled && (ejbMethodStatsManager.isMethodMonitorOn())) {
ejbMethodStatsManager.postInvoke(
invocation.method, invocation.exception);
}
if ( AppVerification.doInstrument() ) {
AppVerification.getInstrumentLogger().doInstrumentForEjb
(getEjbDescriptor(), invocation.method,
invocation.exception);
}
}
} // End if -- invoke instance's onMessage method
else {
if (invocation == null) {
String errorMsg = localStrings.getLocalString
("containers.mdb.invocation_closed",
appEJBName_ + ": Message-driven bean invocation " +
"closed by container", new Object[] { appEJBName_});
throw new EJBException(errorMsg);
}
else {
_logger.log(Level.SEVERE,
"containers.mdb.invocation_exception",
new Object[]{appEJBName_, invocation.exception.toString()});
_logger.log(Level.SEVERE, invocation.exception.getClass().
getName(), invocation.exception);
EJBException ejbEx = new EJBException();
ejbEx.initCause(invocation.exception);
throw ejbEx;
}
}
return result;
}
public void afterMessageDelivery(ResourceHandle resourceHandle) {
afterMessageDeliveryInternal(resourceHandle);
}
private boolean afterMessageDeliveryInternal(ResourceHandle resourceHandle)
{
// return value. assume failure until proven otherwise.
boolean success = false;
Invocation invocation = null;
invocation = (Invocation) invocationManager.getCurrentInvocation();
if (invocation == null) {
_logger.log(Level.SEVERE, "containers.mdb.no_invocation",
new Object[]{appEJBName_, ""});
} else {
MessageBeanContextImpl beanContext =
(MessageBeanContextImpl) invocation.context;
try {
if( invocation.containerStartsTx ) {
// Unregister the session associated with
// the message-driven bean's destination.
unregisterMessageBeanResource(resourceHandle);
}
// counterpart of invocationManager.preInvoke
invocationManager.postInvoke(invocation);
// Commit/Rollback container-managed transaction.
postInvokeTx(invocation);
// Consider successful delivery. Commit failure will be
// checked below.
success = true;
//TODO: Check if Tx existed / committed
statMessageCount++;
} catch(Throwable ce) {
_logger.log(Level.SEVERE,
"containers.mdb.postinvoke_exception",
new Object[]{appEJBName_, ce.toString()});
_logger.log(Level.SEVERE,ce.getClass().getName(), ce);
} finally {
releaseContext(invocation);
}
// Reset original class loader
Utility.setContextClassLoader(invocation.originalContextClassLoader);
if( invocation.exception != null ) {
if( isSystemUncheckedException(invocation.exception) ) {
success = false;
}
// Log system exceptions by default and application exceptions
// only when log level is FINE or higher.
Level exLogLevel =
isSystemUncheckedException(invocation.exception) ?
Level.INFO : Level.FINE;
//START OF IASRI 4660565
_logger.log(exLogLevel,
"containers.mdb.invocation_exception",
new Object[]{appEJBName_,
invocation.exception.toString()});
_logger.log(exLogLevel, invocation.exception.getClass().
getName(), invocation.exception);
}
}
return success;
}
public long getCreateCount() {
return statCreateCount;
}
public long getRemoveCount() {
return statRemoveCount;
}
public long getMessageCount() {
return statMessageCount;
}
}