+1 side note: take care to also capture the classloader and enforce the tccl when activating/deactivating it if you don't rely on workers
Romain Manni-Bucau @rmannibucau <https://twitter.com/rmannibucau> | Blog <https://blog-rmannibucau.rhcloud.com> | Old Blog <http://rmannibucau.wordpress.com> | Github <https://github.com/rmannibucau> | LinkedIn <https://www.linkedin.com/in/rmannibucau> | JavaEE Factory <https://javaeefactory-rmannibucau.rhcloud.com> 2017-06-26 12:55 GMT+02:00 Jonathan Gallimore <jonathan.gallim...@gmail.com> : > Forgot to mention, the JIRA for the original feature was TOMEE-2021: > https://issues.apache.org/jira/browse/TOMEE-2021 > > Thanks > > Jon > > On Mon, Jun 26, 2017 at 11:54 AM, Jonathan Gallimore < > jonathan.gallim...@gmail.com> wrote: > > > Hey folks > > > > We added a feature to TomEE a little while back, where we could control > > MDB listening states via JMX. This currently only works with ActiveMQ, so > > I'd like to propose a patch to make it work and MDB/RAR. In essence, the > > logic is exactly the same, but has shifted from ActiveMQResourceAdapter > to > > MdbContainer. Here's the diff for master showing what I had in mind. > > > > Any thoughts / objections? > > > > Thanks > > > > Jon > > > > diff --git > > a/container/openejb-core/src/main/java/org/apache/openejb/core/mdb/MdbContainer.java > b/container/openejb-core/src/main/java/org/apache/openejb/ > core/mdb/MdbContainer.java > > index a17f342bab..724effd72f 100644 > > --- a/container/openejb-core/src/main/java/org/apache/openejb/ > core/mdb/MdbContainer.java > > +++ b/container/openejb-core/src/main/java/org/apache/openejb/ > core/mdb/MdbContainer.java > > @@ -44,8 +44,22 @@ > > import org.apache.xbean.recipe.ObjectRecipe; > > import org.apache.xbean.recipe.Option; > > > > +import javax.management.Attribute; > > +import javax.management.AttributeList; > > +import javax.management.AttributeNotFoundException; > > +import javax.management.DynamicMBean; > > +import javax.management.InvalidAttributeValueException; > > +import javax.management.MBeanAttributeInfo; > > +import javax.management.MBeanConstructorInfo; > > +import javax.management.MBeanException; > > +import javax.management.MBeanInfo; > > +import javax.management.MBeanNotificationInfo; > > +import javax.management.MBeanOperationInfo; > > +import javax.management.MBeanParameterInfo; > > import javax.management.MBeanServer; > > +import javax.management.MalformedObjectNameException; > > import javax.management.ObjectName; > > +import javax.management.ReflectionException; > > import javax.naming.NamingException; > > import javax.resource.ResourceException; > > import javax.resource.spi.ActivationSpec; > > @@ -63,7 +77,9 @@ > > import java.util.TreeSet; > > import java.util.concurrent.ConcurrentHashMap; > > import java.util.concurrent.ConcurrentMap; > > +import java.util.concurrent.atomic.AtomicBoolean; > > > > +import static javax.management.MBeanOperationInfo.ACTION; > > import static org.apache.openejb.core.transaction.EjbTransactionUtil. > afterInvoke; > > import static org.apache.openejb.core.transaction.EjbTransactionUtil. > createTransactionPolicy; > > import static org.apache.openejb.core.transaction.EjbTransactionUtil. > handleApplicationException; > > @@ -73,9 +89,11 @@ > > private static final Logger logger = > > Logger.getInstance(LogCategory.OPENEJB, > "org.apache.openejb.util.resources"); > > > > private static final ThreadLocal<BeanContext> CURRENT = new > ThreadLocal<>(); > > - > > private static final Object[] NO_ARGS = new Object[0]; > > > > + private final Map<BeanContext, ObjectName> mbeanNames = new > ConcurrentHashMap<>(); > > + private final Map<BeanContext, MdbActivationContext> > activationContexts = new ConcurrentHashMap<>(); > > + > > private final Object containerID; > > private final SecurityService securityService; > > private final ResourceAdapter resourceAdapter; > > @@ -188,7 +206,18 @@ public void deploy(final BeanContext beanContext) > throws OpenEJBException { > > // activate the endpoint > > CURRENT.set(beanContext); > > try { > > - resourceAdapter.endpointActivation(endpointFactory, > activationSpec); > > + > > + final MdbActivationContext activationContext = new > MdbActivationContext(resourceAdapter, endpointFactory, activationSpec); > > + activationContexts.put(beanContext, activationContext); > > + > > + final boolean activeOnStartup = Boolean.parseBoolean( > beanContext.getProperties().getProperty("MdbActiveOnStartup", "true")); > > + if (activeOnStartup) { > > + activationContext.start(); > > + } > > + > > + final String jmxName = > > beanContext.getProperties().getProperty("MdbJMXControl", > "true"); > > + addJMxControl(beanContext, jmxName, activationContext); > > + > > } catch (final ResourceException e) { > > // activation failed... clean up > > beanContext.setContainer(null); > > @@ -201,6 +230,10 @@ public void deploy(final BeanContext beanContext) > throws OpenEJBException { > > } > > } > > > > + private static String getOrDefault(final Map<String, String> map, > final String key, final String defaultValue) { > > + return map.get(key) != null ? map.get(key) : defaultValue; > > + } > > + > > private ActivationSpec createActivationSpec(final BeanContext > beanContext) throws OpenEJBException { > > try { > > // initialize the object recipe > > @@ -253,7 +286,6 @@ private ActivationSpec createActivationSpec(final > BeanContext beanContext) throw > > logger.debug("No Validator bound to JNDI context"); > > } > > > > - > > // set the resource adapter into the activation spec > > activationSpec.setResourceAdapter(resourceAdapter); > > > > @@ -284,7 +316,17 @@ public void undeploy(final BeanContext beanContext) > throws OpenEJBException { > > if (endpointFactory != null) { > > CURRENT.set(beanContext); > > try { > > - resourceAdapter.endpointDeactivation(endpointFactory, > endpointFactory.getActivationSpec()); > > + > > + final ObjectName jmxBeanToRemove = > mbeanNames.remove(beanContext); > > + if (jmxBeanToRemove != null) { > > + LocalMBeanServer.unregisterSilently( > jmxBeanToRemove); > > + logger.info("Undeployed MDB control for " + > beanContext.getDeploymentID()); > > + } > > + > > + final MdbActivationContext activationContext = > activationContexts.remove(beanContext); > > + if (activationContext != null && > activationContext.isStarted()) { > > + > > resourceAdapter.endpointDeactivation(endpointFactory, > endpointFactory.getActivationSpec()); > > + } > > } finally { > > CURRENT.remove(); > > } > > @@ -492,6 +534,30 @@ public void release(final BeanContext deployInfo, > final Object instance) { > > } > > } > > > > + private void addJMxControl(final BeanContext current, final String > name, final MdbActivationContext activationContext) throws > ResourceException { > > + if (name == null || "false".equalsIgnoreCase(name)) { > > + return; > > + } > > + > > + final ObjectName jmxName; > > + try { > > + jmxName = "true".equalsIgnoreCase(name) ? new > ObjectNameBuilder() > > + .set("J2EEServer", "openejb") > > + .set("J2EEApplication", null) > > + .set("EJBModule", current.getModuleID()) > > + .set("StatelessSessionBean", current.getEjbName()) > > + .set("j2eeType", "control") > > + .set("name", current.getEjbName()) > > + .build() : new ObjectName(name); > > + } catch (final MalformedObjectNameException e) { > > + throw new IllegalArgumentException(e); > > + } > > + mbeanNames.put(current, jmxName); > > + > > + LocalMBeanServer.registerSilently(new > > MdbJmxControl(activationContext), > jmxName); > > + logger.info("Deployed MDB control for " + > current.getDeploymentID() + " on " + jmxName); > > + } > > + > > public static BeanContext current() { > > final BeanContext beanContext = CURRENT.get(); > > if (beanContext == null) { > > @@ -505,4 +571,115 @@ public static BeanContext current() { > > private TransactionPolicy txPolicy; > > private ThreadContext oldCallContext; > > } > > + > > + private static class MdbActivationContext { > > + private final ResourceAdapter resourceAdapter; > > + private final EndpointFactory endpointFactory; > > + private final ActivationSpec activationSpec; > > + > > + private AtomicBoolean started = new AtomicBoolean(false); > > + > > + public MdbActivationContext(final ResourceAdapter > resourceAdapter, final EndpointFactory endpointFactory, final > ActivationSpec activationSpec) { > > + this.resourceAdapter = resourceAdapter; > > + this.endpointFactory = endpointFactory; > > + this.activationSpec = activationSpec; > > + } > > + > > + public ResourceAdapter getResourceAdapter() { > > + return resourceAdapter; > > + } > > + > > + public EndpointFactory getEndpointFactory() { > > + return endpointFactory; > > + } > > + > > + public ActivationSpec getActivationSpec() { > > + return activationSpec; > > + } > > + > > + public boolean isStarted() { > > + return started.get(); > > + } > > + > > + public void start() throws ResourceException { > > + if (started.get()) { > > + return; > > + } > > + > > + resourceAdapter.endpointActivation(endpointFactory, > activationSpec); > > + started.set(true); > > + } > > + > > + public void stop() { > > + if (! started.get()) { > > + return; > > + } > > + > > + resourceAdapter.endpointDeactivation(endpointFactory, > activationSpec); > > + started.set(false); > > + } > > + } > > + > > + public static final class MdbJmxControl implements DynamicMBean { > > + private static final AttributeList ATTRIBUTE_LIST = new > AttributeList(); > > + private static final MBeanInfo INFO = new MBeanInfo( > > + "org.apache.openejb.resource.activemq. > ActiveMQResourceAdapter.MdbJmxControl", > > + "Allows to control a MDB (start/stop)", > > + new MBeanAttributeInfo[0], > > + new MBeanConstructorInfo[0], > > + new MBeanOperationInfo[]{ > > + new MBeanOperationInfo("start", "Ensure the > listener is active.", new MBeanParameterInfo[0], "void", ACTION), > > + new MBeanOperationInfo("stop", "Ensure the > listener is not active.", new MBeanParameterInfo[0], "void", ACTION) > > + }, > > + new MBeanNotificationInfo[0]); > > + > > + private final MdbActivationContext activationContext; > > + > > + private MdbJmxControl(final MdbActivationContext > activationContext) { > > + this.activationContext = activationContext; > > + } > > + > > + @Override > > + public Object invoke(final String actionName, final Object[] > params, final String[] signature) throws MBeanException, > ReflectionException { > > + if (actionName.equals("stop")) { > > + activationContext.stop(); > > + > > + } else if (actionName.equals("start")) { > > + try { > > + activationContext.start(); > > + } catch (ResourceException e) { > > + throw new MBeanException(new > IllegalStateException(e.getMessage())); > > + } > > + > > + } else { > > + throw new MBeanException(new > > IllegalStateException("unsupported > operation: " + actionName)); > > + } > > + return null; > > + } > > + > > + @Override > > + public MBeanInfo getMBeanInfo() { > > + return INFO; > > + } > > + > > + @Override > > + public Object getAttribute(final String attribute) throws > AttributeNotFoundException, MBeanException, ReflectionException { > > + throw new AttributeNotFoundException(); > > + } > > + > > + @Override > > + public void setAttribute(final Attribute attribute) throws > AttributeNotFoundException, InvalidAttributeValueException, > MBeanException, ReflectionException { > > + throw new AttributeNotFoundException(); > > + } > > + > > + @Override > > + public AttributeList getAttributes(final String[] attributes) { > > + return ATTRIBUTE_LIST; > > + } > > + > > + @Override > > + public AttributeList setAttributes(final AttributeList > attributes) { > > + return ATTRIBUTE_LIST; > > + } > > + } > > } > > diff --git a/container/openejb-core/src/main/java/org/apache/openejb/ > resource/activemq/ActiveMQResourceAdapter.java > b/container/openejb-core/src/main/java/org/apache/openejb/ > resource/activemq/ActiveMQResourceAdapter.java > > index cd44b79ba8..e5f201e3c5 100644 > > --- a/container/openejb-core/src/main/java/org/apache/openejb/ > resource/activemq/ActiveMQResourceAdapter.java > > +++ b/container/openejb-core/src/main/java/org/apache/openejb/ > resource/activemq/ActiveMQResourceAdapter.java > > @@ -29,8 +29,6 @@ > > import org.apache.openejb.BeanContext; > > import org.apache.openejb.core.mdb.MdbContainer; > > import org.apache.openejb.loader.SystemInstance; > > -import org.apache.openejb.monitoring.LocalMBeanServer; > > -import org.apache.openejb.monitoring.ObjectNameBuilder; > > import org.apache.openejb.resource.AutoConnectionTracker; > > import org.apache.openejb.resource.activemq.jms2. > TomEEConnectionFactory; > > import org.apache.openejb.resource.activemq.jms2. > TomEEManagedConnectionProxy; > > @@ -44,28 +42,11 @@ > > > > import javax.jms.Connection; > > import javax.jms.JMSException; > > -import javax.management.Attribute; > > -import javax.management.AttributeList; > > -import javax.management.AttributeNotFoundException; > > -import javax.management.DynamicMBean; > > -import javax.management.InvalidAttributeValueException; > > -import javax.management.MBeanAttributeInfo; > > -import javax.management.MBeanConstructorInfo; > > -import javax.management.MBeanException; > > -import javax.management.MBeanInfo; > > -import javax.management.MBeanNotificationInfo; > > -import javax.management.MBeanOperationInfo; > > -import javax.management.MBeanParameterInfo; > > -import javax.management.MalformedObjectNameException; > > import javax.management.ObjectName; > > -import javax.management.ReflectionException; > > import javax.naming.NamingException; > > -import javax.resource.NotSupportedException; > > import javax.resource.ResourceException; > > -import javax.resource.spi.ActivationSpec; > > import javax.resource.spi.BootstrapContext; > > import javax.resource.spi.ResourceAdapterInternalException; > > -import javax.resource.spi.endpoint.MessageEndpointFactory; > > import java.lang.reflect.InvocationHandler; > > import java.lang.reflect.Method; > > import java.lang.reflect.Proxy; > > @@ -78,8 +59,6 @@ > > import java.util.concurrent.ConcurrentHashMap; > > import java.util.concurrent.TimeUnit; > > > > -import static javax.management.MBeanOperationInfo.ACTION; > > - > > @SuppressWarnings("UnusedDeclaration") > > public class ActiveMQResourceAdapter extends > > org.apache.activemq.ra.ActiveMQResourceAdapter > { > > > > @@ -190,65 +169,6 @@ private void createInternalBroker(final String > brokerXmlConfig, final Properties > > } > > } > > > > - @Override > > - public void endpointActivation(final MessageEndpointFactory > endpointFactory, final ActivationSpec activationSpec) throws > ResourceException { > > - final BeanContext current = MdbContainer.current(); > > - if (current != null && "false".equalsIgnoreCase( > current.getProperties().getProperty("MdbActiveOnStartup"))) { > > - if (!equals(activationSpec.getResourceAdapter())) { > > - throw new ResourceException("Activation spec not > initialized with this ResourceAdapter instance (" + > activationSpec.getResourceAdapter() > + " != " + this + ")"); > > - } > > - if (!(activationSpec instanceof MessageActivationSpec)) { > > - throw new NotSupportedException("That type of > ActivationSpec not supported: " + activationSpec.getClass()); > > - } > > - > > - final ActiveMQEndpointActivationKey key = new > ActiveMQEndpointActivationKey(endpointFactory, > MessageActivationSpec.class.cast(activationSpec)); > > - Map.class.cast(Reflections.get(this, > "endpointWorkers")).put(key, new ActiveMQEndpointWorker(this, key) { > > - }); > > - // we dont want that worker.start(); > > - } else { > > - super.endpointActivation(endpointFactory, activationSpec); > > - } > > - > > - if (current != null) { > > - addJMxControl(current, current.getProperties(). > getProperty("MdbJMXControl")); > > - } > > - } > > - > > - private void addJMxControl(final BeanContext current, final String > name) throws ResourceException { > > - if (name == null || "false".equalsIgnoreCase(name)) { > > - return; > > - } > > - > > - final ActiveMQEndpointWorker worker = getWorker(current); > > - final ObjectName jmxName; > > - try { > > - jmxName = "true".equalsIgnoreCase(name) ? new > ObjectNameBuilder() > > - .set("J2EEServer", "openejb") > > - .set("J2EEApplication", null) > > - .set("EJBModule", current.getModuleID()) > > - .set("StatelessSessionBean", current.getEjbName()) > > - .set("j2eeType", "control") > > - .set("name", current.getEjbName()) > > - .build() : new ObjectName(name); > > - } catch (final MalformedObjectNameException e) { > > - throw new IllegalArgumentException(e); > > - } > > - mbeanNames.put(current, jmxName); > > - > > - LocalMBeanServer.registerSilently(new MdbJmxControl(worker), > jmxName); > > - log.info("Deployed MDB control for " + > current.getDeploymentID() + " on " + jmxName); > > - } > > - > > - @Override > > - public void endpointDeactivation(final MessageEndpointFactory > endpointFactory, final ActivationSpec activationSpec) { > > - final BeanContext current = MdbContainer.current(); > > - if (current != null && "true".equalsIgnoreCase( > current.getProperties().getProperty("MdbJMXControl"))) { > > - LocalMBeanServer.unregisterSilently(mbeanNames. > remove(current)); > > - log.info("Undeployed MDB control for " + > current.getDeploymentID()); > > - } > > - super.endpointDeactivation(endpointFactory, activationSpec); > > - } > > - > > private ActiveMQEndpointWorker getWorker(final BeanContext > beanContext) throws ResourceException { > > final Map<ActiveMQEndpointActivationKey, > ActiveMQEndpointWorker> workers = Map.class.cast(Reflections.get( > > > > MdbContainer.class.cast(beanContext.getContainer()).getResourceAdapter(), > "endpointWorkers")); > > @@ -396,72 +316,4 @@ private static void stopScheduler() { > > //Ignore > > } > > } > > - > > - public static final class MdbJmxControl implements DynamicMBean { > > - private static final AttributeList ATTRIBUTE_LIST = new > AttributeList(); > > - private static final MBeanInfo INFO = new MBeanInfo( > > - "org.apache.openejb.resource.activemq. > ActiveMQResourceAdapter.MdbJmxControl", > > - "Allows to control a MDB (start/stop)", > > - new MBeanAttributeInfo[0], > > - new MBeanConstructorInfo[0], > > - new MBeanOperationInfo[]{ > > - new MBeanOperationInfo("start", "Ensure the > listener is active.", new MBeanParameterInfo[0], "void", ACTION), > > - new MBeanOperationInfo("stop", "Ensure the > listener is not active.", new MBeanParameterInfo[0], "void", ACTION) > > - }, > > - new MBeanNotificationInfo[0]); > > - > > - private final ActiveMQEndpointWorker worker; > > - > > - private MdbJmxControl(final ActiveMQEndpointWorker worker) { > > - this.worker = worker; > > - } > > - > > - @Override > > - public Object invoke(final String actionName, final Object[] > params, final String[] signature) throws MBeanException, > ReflectionException { > > - switch (actionName) { > > - case "stop": > > - try { > > - worker.stop(); > > - } catch (final InterruptedException e) { > > - Thread.interrupted(); > > - } > > - break; > > - case "start": > > - try { > > - worker.start(); > > - } catch (ResourceException e) { > > - throw new MBeanException(new > IllegalStateException(e.getMessage())); > > - } > > - break; > > - default: > > - throw new MBeanException(new > > IllegalStateException("unsupported > operation: " + actionName)); > > - } > > - return null; > > - } > > - > > - @Override > > - public MBeanInfo getMBeanInfo() { > > - return INFO; > > - } > > - > > - @Override > > - public Object getAttribute(final String attribute) throws > AttributeNotFoundException, MBeanException, ReflectionException { > > - throw new AttributeNotFoundException(); > > - } > > - > > - @Override > > - public void setAttribute(final Attribute attribute) throws > AttributeNotFoundException, InvalidAttributeValueException, > MBeanException, ReflectionException { > > - throw new AttributeNotFoundException(); > > - } > > - > > - @Override > > - public AttributeList getAttributes(final String[] attributes) { > > - return ATTRIBUTE_LIST; > > - } > > - > > - @Override > > - public AttributeList setAttributes(final AttributeList > attributes) { > > - return ATTRIBUTE_LIST; > > - } > > - } > > } > > diff --git a/container/openejb-core/src/test/java/org/apache/openejb/ > resource/activemq/ActiveMQResourceAdapterControlTest.java > b/container/openejb-core/src/test/java/org/apache/openejb/core/mdb/ > ResourceAdapterControlTest.java > > similarity index 96% > > rename from container/openejb-core/src/test/java/org/apache/openejb/ > resource/activemq/ActiveMQResourceAdapterControlTest.java > > rename to container/openejb-core/src/test/java/org/apache/openejb/ > core/mdb/ResourceAdapterControlTest.java > > index 7940cd423d..3885dc51de 100644 > > --- a/container/openejb-core/src/test/java/org/apache/openejb/ > resource/activemq/ActiveMQResourceAdapterControlTest.java > > +++ b/container/openejb-core/src/test/java/org/apache/openejb/core/mdb/ > ResourceAdapterControlTest.java > > @@ -14,7 +14,7 @@ > > * See the License for the specific language governing permissions and > > * limitations under the License. > > */ > > -package org.apache.openejb.resource.activemq; > > +package org.apache.openejb.core.mdb; > > > > import org.apache.openejb.config.EjbModule; > > import org.apache.openejb.jee.EjbJar; > > @@ -45,6 +45,7 @@ > > import java.util.Properties; > > import java.util.concurrent.Semaphore; > > import java.util.concurrent.TimeUnit; > > +import java.util.logging.Logger; > > > > import static org.junit.Assert.assertEquals; > > import static org.junit.Assert.assertFalse; > > @@ -53,8 +54,9 @@ > > import static org.junit.Assert.fail; > > > > @RunWith(ApplicationComposer.class) > > -public class ActiveMQResourceAdapterControlTest { > > - @Resource(name = "ActiveMQResourceAdapterControlTest/test/ejb/Mdb") > > +public class ResourceAdapterControlTest { > > + private static final Logger logger = Logger.getLogger( > ResourceAdapterControlTest.class.getName()); > > + @Resource(name = "ResourceAdapterControlTest/test/ejb/Mdb") > > private Queue queue; > > > > @Resource > > > > > > > > >