org.glassfish.ejb.mdb.MessageBeanContainer Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2013 Oracle and/or its affiliates. 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_1_1.html
* or packager/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 packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [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.
*/
// Portions Copyright [2017-2024] [Payara Foundation and/or its affiliates]
package org.glassfish.ejb.mdb;
import com.sun.appserv.connectors.internal.api.ConnectorRuntime;
import com.sun.appserv.connectors.internal.api.ResourceHandle;
import com.sun.appserv.connectors.internal.api.TransactedPoolManager;
import com.sun.ejb.ComponentContext;
import com.sun.ejb.EjbInvocation;
import com.sun.ejb.containers.*;
import com.sun.ejb.containers.EJBContextImpl.BeanState;
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.ejb.monitoring.stats.EjbMonitoringStatsProvider;
import com.sun.ejb.monitoring.stats.EjbPoolStatsProvider;
import com.sun.ejb.spi.container.OptionalLocalInterfaceProvider;
import com.sun.enterprise.admin.monitor.callflow.ComponentType;
import com.sun.enterprise.config.serverbeans.Config;
import com.sun.enterprise.deployment.EjbBundleDescriptor;
import com.sun.enterprise.deployment.LifecycleCallbackDescriptor.CallbackType;
import com.sun.enterprise.deployment.MethodDescriptor;
import com.sun.enterprise.deployment.runtime.BeanPoolDescriptor;
import com.sun.enterprise.security.SecurityManager;
import com.sun.enterprise.util.LocalStringManagerImpl;
import com.sun.enterprise.util.Utility;
import com.sun.logging.LogDomains;
import org.glassfish.api.admin.ServerEnvironment;
import org.glassfish.api.invocation.ComponentInvocation;
import org.glassfish.api.invocation.InvocationManager;
import org.glassfish.ejb.api.MessageBeanListener;
import org.glassfish.ejb.api.MessageBeanProtocolManager;
import org.glassfish.ejb.api.ResourcesExceededException;
import org.glassfish.ejb.config.MdbContainer;
import org.glassfish.ejb.deployment.descriptor.EjbDescriptor;
import org.glassfish.ejb.deployment.descriptor.EjbMessageBeanDescriptor;
import org.glassfish.ejb.mdb.monitoring.stats.MessageDrivenBeanStatsProvider;
import org.glassfish.ejb.spi.MessageBeanClient;
import org.glassfish.ejb.spi.MessageBeanClientFactory;
import jakarta.ejb.*;
import jakarta.resource.spi.endpoint.MessageEndpoint;
import jakarta.transaction.Status;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.sun.appserv.connectors.internal.api.ConnectorConstants.CONNECTOR_MESSAGE_BEAN_CLIENT_FACTORY;
import static com.sun.ejb.containers.EJBContextImpl.BeanState.POOLED;
import static com.sun.enterprise.deployment.LifecycleCallbackDescriptor.CallbackType.POST_CONSTRUCT;
import static com.sun.enterprise.util.Utility.setContextClassLoader;
import static com.sun.logging.LogDomains.MDB_LOGGER;
import static java.util.logging.Level.*;
import static javax.transaction.xa.XAResource.TMSUCCESS;
/**
* 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.
*
* 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 {
private static final Logger _logger = LogDomains.getLogger(MessageBeanContainer.class, MDB_LOGGER);
private static LocalStringManagerImpl localStrings = new LocalStringManagerImpl(MessageBeanContainer.class);
// 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 = CONNECTOR_MESSAGE_BEAN_CLIENT_FACTORY;
private static final int DEFAULT_RESIZE_QUANTITY = 8;
private static final int DEFAULT_STEADY_SIZE = 0;
private static final int DEFAULT_MAX_POOL_SIZE = 32;
private static final int DEFAULT_IDLE_TIMEOUT = 600;
// issue 4629. 0 means a bean can remain idle indefinitely.
private static final int MIN_IDLE_TIMEOUT = 0;
private final String appEJBName_;
private MessageBeanClient messageBeanClient_;
private AbstractPool messageBeanPool_;
private BeanPoolDescriptor beanPoolDesc_;
private int maxMessageBeanListeners_;
private int numMessageBeanListeners_;
private Class> messageBeanInterface_;
private Class> messageBeanSubClass_;
// TODO : remove
private final int statMessageCount = 0;
private TransactedPoolManager poolMgr;
private final Class> messageListenerType_;
MessageBeanContainer(EjbDescriptor desc, ClassLoader loader, SecurityManager sm) throws Exception {
super(ContainerType.MESSAGE_DRIVEN, desc, loader, sm);
isMessageDriven = true;
appEJBName_ = desc.getApplication().getRegistrationName() + ":" + desc.getName();
EjbMessageBeanDescriptor msgBeanDesc = (EjbMessageBeanDescriptor) desc;
ComponentInvocation componentInvocation = null;
try {
Class> beanClass = loader.loadClass(desc.getEjbClassName());
messageListenerType_ = loader.loadClass(msgBeanDesc.getMessageListenerType());
Class> messageListenerType_1 = messageListenerType_;
if (isNoMethodsListenerInterface(messageListenerType_1)) {
// Generate interface and subclass for EJB 3.2 5.4.3 - Message-Driven Bean with No-Methods Listener Interface
MessageBeanInterfaceGenerator generator = new MessageBeanInterfaceGenerator(loader);
messageBeanInterface_ = generator.generateMessageBeanInterface(beanClass);
messageBeanSubClass_ = generator.generateMessageBeanSubClass(beanClass, messageBeanInterface_);
}
// 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 (Method next : msgListenerMethods) {
addInvocationInfo(next, MethodDescriptor.EJB_BEAN, null);
}
poolMgr = ejbContainerUtilImpl.getServices().getService(TransactedPoolManager.class);
// 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);
MessageBeanClientFactory clientFactory = null;
if (factoryClassName != null) {
Class clientFactoryClass = loader.loadClass(factoryClassName);
clientFactory = (MessageBeanClientFactory) clientFactoryClass.newInstance();
} else {
clientFactory = ejbContainerUtilImpl.getServices().getService(MessageBeanClientFactory.class, DEFAULT_MESSAGE_BEAN_CLIENT_FACTORY);
}
_logger.log(Level.FINE, "Using " + clientFactory.getClass().getName() + " 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);
componentInvocation = createComponentInvocation();
componentInvocation.setContainer(this);
invocationManager.preInvoke(componentInvocation);
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;
} finally {
if (componentInvocation != null) {
invocationManager.postInvoke(componentInvocation);
}
}
}
protected void registerMonitorableComponents(Method[] msgListenerMethods) {
super.registerMonitorableComponents();
poolProbeListener = new EjbPoolStatsProvider(messageBeanPool_,
getContainerId(), containerInfo.appName, containerInfo.modName,
containerInfo.ejbName);
poolProbeListener.register();
// registryMediator.registerProvider(messageBeanPool_);
// super.setMonitorOn(mdbc.isMonitoringEnabled());
_logger.log(Level.FINE, "[MessageBeanContainer] registered monitorable");
}
@Override
protected EjbMonitoringStatsProvider getMonitoringStatsProvider(
String appName, String modName, String ejbName) {
return new MessageDrivenBeanStatsProvider(getContainerId(), appName, modName, ejbName);
}
@Override
public boolean scanForEjbCreateMethod() {
return true;
}
@Override
protected void initializeHome() throws Exception {
throw new UnsupportedOperationException("MessageDrivenBean needn't initialize home");
}
@Override
protected void addLocalRemoteInvocationInfo() throws Exception {
// Nothing to do for MDBs
}
@Override
protected boolean isCreateHomeFinder(Method method) {
return false;
}
private void createMessageBeanPool(EjbMessageBeanDescriptor descriptor) {
beanPoolDesc_ = descriptor.getIASEjbExtraDescriptors().getBeanPool();
if (beanPoolDesc_ == null) {
beanPoolDesc_ = new BeanPoolDescriptor();
}
MdbContainer mdbc = ejbContainerUtilImpl.getServices()
.getService(Config.class, ServerEnvironment.DEFAULT_INSTANCE_NAME).getExtensionByType(MdbContainer.class);
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);
//if ejb pool idle-timeout-in-seconds is not explicitly set in
//glassfish-ejb-jar.xml, returned value is -1
value = beanPoolDesc_.getPoolIdleTimeoutInSeconds();
if (value < MIN_IDLE_TIMEOUT) {
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();
String val = descriptor.getEjbBundleDescriptor().getEnterpriseBeansProperty(SINGLETON_BEAN_POOL_PROP);
messageBeanPool_ = new NonBlockingPool(getContainerId(), appEJBName_, objFactory,
beanPoolDesc_.getSteadyPoolSize(), beanPoolDesc_
.getPoolResizeQuantity(), beanPoolDesc_
.getMaxPoolSize(), beanPoolDesc_
.getPoolIdleTimeoutInSeconds(), loader,
Boolean.parseBoolean(val));
}
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, val, e.toString(),
"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, value, emsg, deft });
value = deft;
}
if ((highLimit >= 0) && (value > highLimit)) {
_logger.log(Level.WARNING, "containers.mdb.invalid_value",
new Object[] { appName, value, emsg, highLimit });
value = highLimit;
}
return value;
}
private boolean containerStartsTx(Method method) {
int txMode = getTxAttr(method, MethodDescriptor.EJB_BEAN);
return isEjbTimeoutMethod(method) ? ((txMode == TX_REQUIRES_NEW) || (txMode == TX_REQUIRED))
: (txMode == TX_REQUIRED);
}
public String getMonitorAttributeValues() {
StringBuilder sbuf = new StringBuilder();
sbuf.append("MESSAGEDRIVEN ");
sbuf.append(appEJBName_);
sbuf.append(messageBeanPool_.getAllAttrValues());
sbuf.append("]");
return sbuf.toString();
}
@Override
public boolean userTransactionMethodsAllowed(ComponentInvocation inv) {
boolean utMethodsAllowed = false;
if (isBeanManagedTran) {
if (inv instanceof EjbInvocation) {
EjbInvocation ejbInvocation = (EjbInvocation) inv;
MessageBeanContextImpl mdc = (MessageBeanContextImpl) ejbInvocation.context;
utMethodsAllowed = (mdc.operationsAllowed()) ? true: false;
}
}
return utMethodsAllowed;
}
public void setEJBHome(EJBHome ejbHome) throws Exception {
throw new Exception("Can't set EJB Home on Message-driven bean");
}
@Override
public EJBObjectImpl getEJBObjectImpl(byte[] instanceKey) {
throw new EJBException("No EJBObject for message-driven beans");
}
@Override
public EJBObjectImpl createEJBObjectImpl() throws CreateException {
throw new EJBException("No EJBObject for message-driven beans");
}
@Override
protected 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.
*/
@Override
protected boolean callEJBTimeout(RuntimeTimerState timerState,
EJBTimerService timerService) throws Exception {
boolean redeliver = false;
// There is no resource associated with the delivery of the timeout.
try {
Method method = getTimeoutMethod(timerState);
// Do pre-invoke logic for message bean with tx import = false
// and a null resource handle.
beforeMessageDelivery(method, MessageDeliveryType.Timer,
false, null);
prepareEjbTimeoutParams((EjbInvocation) invocationManager.getCurrentInvocation(),
timerState, timerService);
// Method arguments had been set already
deliverMessage(null);
} 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 = postEjbTimeout(timerState, timerService);
redeliver = !successfulPostEjbTimeout;
}
// afterMessageDelivery takes care of postInvoke and postInvokeTx
// processing. If any portion of that work fails, mark
// timer for redelivery.
boolean successfulAfterMessageDelivery = afterMessageDeliveryInternal(null);
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.
*/
@Override
protected void forceDestroyBean(EJBContextImpl sc) {
MessageBeanContextImpl mbc = (MessageBeanContextImpl)sc;
if (mbc.isInState(BeanState.DESTROYED)) {
return;
}
// mark context as destroyed
mbc.setState(BeanState.DESTROYED);
messageBeanPool_.destroyObject(sc);
}
// This particular preInvoke signature not used
@Override
public void preInvoke(EjbInvocation inv) {
throw new EJBException("preInvoke(Invocation) not supported");
}
private class MessageBeanContextFactory implements ObjectFactory {
@Override
public Object create(Object param) {
try {
return createMessageDrivenEJB();
} catch (CreateException ex) {
throw new EJBException(ex);
}
}
@Override
public void destroy(Object obj) {
MessageBeanContextImpl beanContext = (MessageBeanContextImpl) obj;
Object ejb = beanContext.getEJB();
if (!beanContext.isInState(BeanState.DESTROYED)) {
// Called from pool implementation to reduce the pool size.
// So we need to call ejb.ejbRemove() and
// mark context as destroyed.
EjbInvocation inv = null;
try {
// NOTE : Context class-loader is already set by Pool
inv = createEjbInvocation(ejb, beanContext);
inv.isMessageDriven = true;
invocationManager.preInvoke(inv);
beanContext.setInEjbRemove(true);
intercept(CallbackType.PRE_DESTROY, beanContext);
cleanupInstance(beanContext);
ejbProbeNotifier.ejbBeanDestroyedEvent(getContainerId(),
containerInfo.appName, containerInfo.modName,
containerInfo.ejbName);
} 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(BeanState.DESTROYED);
}
// tell the TM to release resources held by the bean
transactionManager.componentDestroyed(beanContext);
// Message-driven beans can't have transactions across
// invocations.
beanContext.setTransaction(null);
}
}
@Override
protected ComponentContext _getContext(EjbInvocation inv) {
MessageBeanContextImpl context = null;
try {
context = (MessageBeanContextImpl) messageBeanPool_.getObject(null);
context.setState(BeanState.INVOKING);
} catch (Exception e) {
throw new EJBException(e);
}
return context;
}
/**
* Return instance to a pooled state.
*/
@Override
public void releaseContext(EjbInvocation inv) {
MessageBeanContextImpl beanContext = (MessageBeanContextImpl) inv.context;
if (beanContext.isInState(BeanState.DESTROYED)) {
return;
}
beanContext.setState(BeanState.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);
}
/** TODO
public void appendStats(StringBuilder 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
@Override
public void postInvoke(EjbInvocation inv) {
throw new EJBException("postInvoke(Invocation) not supported "
+ "in message-driven bean container");
}
/****************************************************************
* The following are implementation for methods required by the *
* MessageBeanProtocalManager interface. *
****************************************************************/
/**
* {@inheritDoc}
*/
@Override
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);
}
@Override
public void destroyMessageBeanListener(MessageBeanListener listener) {
synchronized (this) {
numMessageBeanListeners_--;
}
}
/**
* @param method
* One of the methods used to deliver messages, e.g. onMessage
* method for jakarta.jms.MessageListener. Note that if the
* method
is not one of the methods for message
* delivery, the behavior of this method is not defined.
* @return
*/
@Override
public boolean isDeliveryTransacted(Method method) {
return containerStartsTx(method);
}
@Override
public BeanPoolDescriptor getPoolDescriptor() {
return beanPoolDesc_;
}
/**
* Generates the appropriate Proxy based on the message listener type.
*
* @param handler InvocationHandler responsible for calls on the proxy
* @return an object implementing MessageEndpoint and the appropriate MDB view
* @throws Exception
*/
@Override
public Object createMessageBeanProxy(InvocationHandler handler) throws Exception {
if (isNoMethodsListenerInterface(messageListenerType_)) {
// EJB 3.2 No-interface MDB View
Proxy proxy = (Proxy) Proxy.newProxyInstance(loader, new Class[]{messageBeanInterface_}, handler);
OptionalLocalInterfaceProvider provider = (OptionalLocalInterfaceProvider) messageBeanSubClass_.newInstance();
provider.setOptionalLocalIntfProxy(proxy);
return provider;
} else {
// EJB 3.1 - 2.0 Interface View
return Proxy.newProxyInstance(loader, new Class[]{messageListenerType_, MessageEndpoint.class}, handler);
}
}
/**
* Detects if the message-listener type indicates an EJB 3.2 MDB No-Methods Listener Interface
*
* In the future this method could potentially just return:
*
*
* return Annotation.class.isAssignableFrom(messageListenerType)
*
*
* @param messageListenerType
* @return true of the specified interface has no methods
*/
private static boolean isNoMethodsListenerInterface(Class> messageListenerType) {
// DMB: In the future, this can just return 'Annotation.class.isAssignableFrom(messageListenerType)'
return messageListenerType.getMethods().length == 0;
}
@Override
protected MessageBeanContextImpl _constructEJBContextImpl(Object instance) {
return new MessageBeanContextImpl(instance, this);
}
/**
* Instantiate and initialize a message-driven bean instance.
*/
private MessageBeanContextImpl createMessageDrivenEJB() throws CreateException {
EjbInvocation ejbInvocation = null;
MessageBeanContextImpl context = null;
ClassLoader originalClassLoader = null;
try {
// Set application class loader before invoking instance.
originalClassLoader = setContextClassLoader(getClassLoader());
context = (MessageBeanContextImpl) createEjbInstanceAndContext();
Object ejb = context.getEJB();
// java:comp/env lookups are allowed from here on...
ejbInvocation = createEjbInvocation(ejb, context);
ejbInvocation.isMessageDriven = true;
invocationManager.preInvoke(ejbInvocation);
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.
injectEjbInstance(context);
// 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.
intercept(POST_CONSTRUCT, context);
//sanitizing the null reference of ejbProbeNotifier and returning null
if(ejbProbeNotifier == null) {
_logger.severe("The reference for ejbProbeNotifier is not available, this is an un-sync state of the container");
return null;
}
ejbProbeNotifier.ejbBeanCreatedEvent(getContainerId(),
containerInfo.appName, containerInfo.modName,
containerInfo.ejbName);
// Set the state to POOLED after ejbCreate so that EJBContext methods not allowed will throw exceptions
context.setState(POOLED);
} catch (Throwable t) {
_logger.log(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(SEVERE, t.getClass().getName(), t);
CreateException ce = new CreateException("Could not create Message-Driven EJB");
ce.initCause(t);
throw ce;
} finally {
if (originalClassLoader != null) {
setContextClassLoader(originalClassLoader);
}
if (ejbInvocation != null) {
invocationManager.postInvoke(ejbInvocation);
}
}
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) {
poolMgr.registerResource(resourceHandle);
}
}
private void unregisterMessageBeanResource(ResourceHandle resourceHandle) {
// resource handle may be null if preInvokeTx error caused
// ResourceAllocator.destroyResource()
if (resourceHandle != null) {
poolMgr.unregisterResource(resourceHandle, TMSUCCESS);
}
}
@Override
protected void afterBegin(EJBContextImpl context) {
// Message-driven Beans cannot implement SessionSynchronization!!
}
@Override
protected void beforeCompletion(EJBContextImpl context) {
// Message-driven beans cannot implement SessionSynchronization!!
}
@Override
protected void afterCompletion(EJBContextImpl ctx, int status) {
// Message-driven Beans cannot implement SessionSynchronization!!
}
// default
@Override
public boolean passivateEJB(ComponentContext context) {
return false;
}
/**
* @deprecated not called and not used in Payara 5
*/
// default
@Deprecated
public void activateEJB(Object ctx, Object instanceKey) {
}
/**
* Called when the application containing this message-bean has
* successfully gotten through the initial load phase of each
* module. Now we can "turn on the spigot" and allow incoming
* requests, which could result in the creation of message-bean
* instances.
* @param deploy true if this method is called during application deploy
*/
@Override
public void startApplication(boolean deploy) {
super.startApplication(deploy);
// Start delivery of messages to message bean instances.
try {
messageBeanClient_.start();
} catch (Exception e) {
_logger.log(FINE, e.getClass().getName(), e);
throw new RuntimeException("MessageBeanContainer.start failure for app " +
appEJBName_, e);
}
}
private ComponentInvocation createComponentInvocation() {
EjbBundleDescriptor ejbBundleDesc = getEjbDescriptor().getEjbBundleDescriptor();
ComponentInvocation newInv = new ComponentInvocation(
getComponentId(),
ComponentInvocation.ComponentInvocationType.SERVLET_INVOCATION,
this,
ejbBundleDesc.getApplication().getAppName(),
ejbBundleDesc.getModuleName(),
ejbBundleDesc.getApplication().getRegistrationName()
);
//newInv.setJNDIEnvironment(getJNDIEnvironment()); ???
return newInv;
}
private void cleanupResources() {
ComponentInvocation componentInvocation = createComponentInvocation();
ASyncClientShutdownTask task = new ASyncClientShutdownTask(appEJBName_, messageBeanClient_, loader, messageBeanPool_, componentInvocation);
long timeout = 0;
try {
timeout = ejbContainerUtilImpl.getServices()
.getService(ConnectorRuntime.class)
.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 {
ejbContainerUtilImpl.addWork(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(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
+ " mili-seconds.");
long maxWaitTime = System.currentTimeMillis() + timeout;
// wait in loop to guard against spurious wake-up
do {
long timeTillTimeout = maxWaitTime - System.currentTimeMillis();
if (timeTillTimeout <= 0) {
break;
}
task.wait(timeTillTimeout);
} while (!task.isDone());
}
if (!task.isDone()) {
_logger.log(Level.WARNING,
"[MDBContainer] ASync task has not finished. "
+ "Giving up after " + timeout
+ " mili-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.FINE,
"[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 Runnable {
private boolean done = false;
String appName;
MessageBeanClient mdbClient;
ClassLoader clsLoader;
AbstractPool mdbPool;
ComponentInvocation componentInvocation;
ASyncClientShutdownTask(String appName, MessageBeanClient mdbClient,
ClassLoader loader, AbstractPool mdbPool, ComponentInvocation componentInvocation) {
this.appName = appName;
this.mdbClient = mdbClient;
this.clsLoader = loader;
this.mdbPool = mdbPool;
this.componentInvocation = componentInvocation;
}
@Override
public void run() {
ClassLoader previousClassLoader = null;
InvocationManager invocationManager = EjbContainerUtilImpl.getInstance().getInvocationManager();
try {
previousClassLoader = Utility.setContextClassLoader(clsLoader);
invocationManager.preInvoke(componentInvocation);
// Cleanup the message bean client resources.
mdbClient.close();
_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 {
synchronized (this) {
this.done = true;
this.notifyAll();
}
try {
mdbPool.close();
} catch (Exception ex) {
_logger.log(Level.FINE, "Exception while closing pool", ex);
}
invocationManager.postInvoke(componentInvocation);
if (previousClassLoader != null) {
Utility.setContextClassLoader(previousClassLoader);
}
}
}
public synchronized boolean isDone() {
return this.done;
}
}
/**
* Called by BaseContainer during container shutdown sequence
* @param appBeingUndeployed
*/
@Override
protected void doConcreteContainerShutdown(boolean appBeingUndeployed) {
_logger.log(Level.FINE, "containers.mdb.shutdown_cleanup_start",
appEJBName_);
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 EjbInvocation manager's thread-specific state to track the
* invocation across these three calls.
*
* @param method
* @param deliveryType
* @param txImported
* @param resourceHandle
*/
public void beforeMessageDelivery(Method method, MessageDeliveryType deliveryType,
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);
}
EjbInvocation invocation = createEjbInvocation();
try {
MessageBeanContextImpl context = (MessageBeanContextImpl) getContext(invocation);
if( deliveryType == MessageDeliveryType.Timer ) {
invocation.isTimerCallback = true;
}
// 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.setOriginalContextClassLoader(
Utility.setContextClassLoader(getClassLoader()));
invocation.isMessageDriven = true;
invocation.method = method;
context.setState(BeanState.INVOKING);
invocation.context = context;
invocation.instance = context.getEJB();
invocation.ejb = context.getEJB();
invocation.setContainer(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.setContainerStartsTx(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 {
EjbInvocation invocation = null;
boolean methodCalled = false; // for monitoring
Object result = null;
invocation = (EjbInvocation) 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 (isTimedObject()
&& isEjbTimeoutMethod(invocation.method)) {
invocation.beanMethod = invocation.method;
intercept(invocation);
} 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.methodParams = params;
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 jakarta.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 {
/*
* FIXME 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;
EjbInvocation invocation = null;
invocation = (EjbInvocation) invocationManager.getCurrentInvocation();
if (invocation == null) {
_logger.log(Level.SEVERE, "containers.mdb.no_invocation",
new Object[] { appEJBName_, "" });
} else {
try {
if (invocation.isContainerStartsTx()) {
// 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
ejbProbeNotifier.messageDeliveredEvent(getContainerId(),
containerInfo.appName, containerInfo.modName,
containerInfo.ejbName);
} 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.getOriginalContextClassLoader());
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.WARNING
: Level.FINE;
_logger.log(exLogLevel, "containers.mdb.invocation_exception",
new Object[] { appEJBName_,
invocation.exception.toString() });
_logger.log(exLogLevel, invocation.exception.getClass()
.getName(), invocation.exception);
}
}
return success;
}
// TODO : remove
public long getMessageCount() {
return statMessageCount;
}
public enum MessageDeliveryType { Message, Timer }
}