All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.ejb.mdb.MessageBeanContainer Maven / Gradle / Ivy

There is a newer version: 6.2024.6
Show newest version
/*
 * 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-2021] [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.container = 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.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.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 } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy