/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.openejb.resource.activemq; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.RedeliveryPolicy; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.ra.ActiveMQConnectionRequestInfo; import org.apache.activemq.ra.ActiveMQEndpointActivationKey; import org.apache.activemq.ra.ActiveMQEndpointWorker; import org.apache.activemq.ra.ActiveMQManagedConnection; import org.apache.activemq.ra.MessageActivationSpec; 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; import org.apache.openejb.spi.ContainerSystem; import org.apache.openejb.util.Duration; import org.apache.openejb.util.LogCategory; import org.apache.openejb.util.Logger; import org.apache.openejb.util.URISupport; import org.apache.openejb.util.URLs; import org.apache.openejb.util.reflection.Reflections; 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; import java.net.URISyntaxException; import java.util.Collection; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Properties; 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 { private String dataSource; private String useDatabaseLock; private String startupTimeout = "60000"; private BootstrapContext bootstrapContext; private final Map<BeanContext, ObjectName> mbeanNames = new ConcurrentHashMap<>(); public String getDataSource() { return dataSource; } public void setDataSource(final String dataSource) { this.dataSource = dataSource; } public void setUseDatabaseLock(final String useDatabaseLock) { this.useDatabaseLock = useDatabaseLock; } public int getStartupTimeout() { return Integer.parseInt(this.startupTimeout); } public void setStartupTimeout(final Duration startupTimeout) { if (startupTimeout.getUnit() == null) { startupTimeout.setUnit(TimeUnit.MILLISECONDS); } this.startupTimeout = String.valueOf(TimeUnit.MILLISECONDS.convert(startupTimeout.getTime(), startupTimeout.getUnit())); } @Override public void setServerUrl(final String url) { super.setServerUrl(url); } @Override public void start(final BootstrapContext bootstrapContext) throws ResourceAdapterInternalException { this.bootstrapContext = bootstrapContext; final String brokerXmlConfig = getBrokerXmlConfig(); super.setBrokerXmlConfig(null); super.start(bootstrapContext); final Properties properties = new Properties(); if (null != this.dataSource) { properties.put("DataSource", this.dataSource); } if (null != this.useDatabaseLock) { properties.put("UseDatabaseLock", this.useDatabaseLock); } if (null != this.startupTimeout) { properties.put("StartupTimeout", this.startupTimeout); } // prefix server uri with 'broker:' so our broker factory is used if (brokerXmlConfig != null && !brokerXmlConfig.trim().isEmpty()) { try { if (brokerXmlConfig.startsWith("broker:")) { final URISupport.CompositeData compositeData = URISupport.parseComposite(URLs.uri(brokerXmlConfig)); if (!compositeData.getParameters().containsKey("persistent")) { //Override default - Which is 'true' //noinspection unchecked compositeData.getParameters().put("persistent", "false"); } if ("false".equalsIgnoreCase(compositeData.getParameters().get("persistent").toString())) { properties.remove("DataSource"); // no need } setBrokerXmlConfig(ActiveMQFactory.getBrokerMetaFile() + compositeData.toURI()); } else if (brokerXmlConfig.toLowerCase(Locale.ENGLISH).startsWith("xbean:")) { setBrokerXmlConfig(ActiveMQFactory.getBrokerMetaFile() + brokerXmlConfig); } } catch (final URISyntaxException e) { throw new ResourceAdapterInternalException("Invalid BrokerXmlConfig", e); } createInternalBroker(brokerXmlConfig, properties); } } private void createInternalBroker(final String brokerXmlConfig, final Properties properties) { ActiveMQFactory.setThreadProperties(properties); try { //The returned broker should be started, but calling start is harmless. //We do not need to track the instance as the factory takes care of this. ActiveMQFactory.createBroker(URLs.uri(getBrokerXmlConfig())).start(); } catch (final Exception e) { Logger.getInstance(LogCategory.OPENEJB_STARTUP, ActiveMQResourceAdapter.class).getChildLogger("service").fatal("Failed to start ActiveMQ", e); } finally { ActiveMQFactory.setThreadProperties(null); // reset brokerXmlConfig if (brokerXmlConfig != null) { setBrokerXmlConfig(brokerXmlConfig); } } } @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")); for (final Map.Entry<ActiveMQEndpointActivationKey, ActiveMQEndpointWorker> entry : workers.entrySet()) { if (entry.getKey().getMessageEndpointFactory() == beanContext.getContainerData()) { return entry.getValue(); } } throw new IllegalStateException("No worker for " + beanContext.getDeploymentID()); } @Override public BootstrapContext getBootstrapContext() { return this.bootstrapContext; } @Override public void stop() { Logger.getInstance(LogCategory.OPENEJB_STARTUP, ActiveMQResourceAdapter.class).getChildLogger("service").info("Stopping ActiveMQ"); final Thread stopThread = new Thread("ActiveMQResourceAdapter stop") { @Override public void run() { try { stopImpl(); } catch (final Throwable t) { Logger.getInstance(LogCategory.OPENEJB_STARTUP, ActiveMQResourceAdapter.class).getChildLogger("service").error("ActiveMQ shutdown failed", t); } } }; stopThread.setDaemon(true); stopThread.start(); int timeout = 60000; try { timeout = Integer.parseInt(this.startupTimeout); } catch (final Throwable e) { //Ignore } try { //Block for a maximum of timeout milliseconds waiting for this thread to die. stopThread.join(timeout); } catch (final InterruptedException ex) { Logger.getInstance(LogCategory.OPENEJB_STARTUP, ActiveMQResourceAdapter.class).getChildLogger("service").warning("Gave up on ActiveMQ shutdown after " + timeout + "ms", ex); } } @Override public ActiveMQConnection makeConnection(final MessageActivationSpec activationSpec) throws JMSException { if (TomEEMessageActivationSpec.class.isInstance(activationSpec)) { final TomEEMessageActivationSpec s = TomEEMessageActivationSpec.class.cast(activationSpec); if (s.getConnectionFactoryLookup() != null) { try { final Object lookup = SystemInstance.get().getComponent(ContainerSystem.class).getJNDIContext() .lookup("openejb:Resource/" + s.getConnectionFactoryLookup()); if (!ActiveMQConnectionFactory.class.isInstance(lookup)) { final org.apache.activemq.ra.ActiveMQConnectionFactory connectionFactory = org.apache.activemq.ra.ActiveMQConnectionFactory.class.cast(lookup); Connection connection = connectionFactory.createConnection(); if (Proxy.isProxyClass(connection.getClass())) { // not great, we should find a better want without bypassing ra layer final InvocationHandler invocationHandler = Proxy.getInvocationHandler(connection); if (AutoConnectionTracker.ConnectionInvocationHandler.class.isInstance(invocationHandler)) { final Object handle = Reflections.get(invocationHandler, "handle"); if (TomEEManagedConnectionProxy.class.isInstance(handle)) { final ActiveMQManagedConnection c = ActiveMQManagedConnection.class.cast(Reflections.get(handle, "connection")); final ActiveMQConnection physicalConnection = ActiveMQConnection.class.cast(Reflections.get(c, "physicalConnection")); final RedeliveryPolicy redeliveryPolicy = activationSpec.redeliveryPolicy(); if (redeliveryPolicy != null) { physicalConnection.setRedeliveryPolicy(redeliveryPolicy); } return physicalConnection; } } } /* final RedeliveryPolicy redeliveryPolicy = activationSpec.redeliveryPolicy(); if (redeliveryPolicy != null) { physicalConnection.setRedeliveryPolicy(redeliveryPolicy); } */ return null; } } catch (final ClassCastException cce) { throw new java.lang.IllegalStateException(cce); } catch (final NamingException e) { throw new IllegalArgumentException(e); } } } return super.makeConnection(activationSpec); } @Override protected ActiveMQConnectionFactory createConnectionFactory(final ActiveMQConnectionRequestInfo connectionRequestInfo, final MessageActivationSpec activationSpec) { if (TomEEMessageActivationSpec.class.isInstance(activationSpec)) { final TomEEMessageActivationSpec s = TomEEMessageActivationSpec.class.cast(activationSpec); if (s.getConnectionFactoryLookup() != null) { try { final Object lookup = SystemInstance.get().getComponent(ContainerSystem.class).getJNDIContext() .lookup("openejb:Resource/" + s.getConnectionFactoryLookup()); if (ActiveMQConnectionFactory.class.isInstance(lookup)) { return ActiveMQConnectionFactory.class.cast(lookup); } return ActiveMQConnectionFactory.class.cast(lookup); // already handled } catch (final NamingException e) { throw new IllegalArgumentException(""); } } } final ActiveMQConnectionFactory factory = new TomEEConnectionFactory(); connectionRequestInfo.configure(factory, activationSpec); return factory; } private void stopImpl() throws Exception { super.stop(); final Collection<BrokerService> brokers = ActiveMQFactory.getBrokers(); final Iterator<BrokerService> it = brokers.iterator(); while (it.hasNext()) { final BrokerService bs = it.next(); try { bs.stop(); bs.waitUntilStopped(); } catch (final Throwable t) { //Ignore } it.remove(); } stopScheduler(); Logger.getInstance(LogCategory.OPENEJB_STARTUP, ActiveMQResourceAdapter.class).getChildLogger("service").info("Stopped ActiveMQ broker"); } private static void stopScheduler() { try { final Class<?> clazz = Class.forName("org.apache.kahadb.util.Scheduler"); final Method method = clazz.getMethod("shutdown"); method.invoke(null); } catch (final Throwable e) { //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; } } }