/*
* JBoss, Home of Professional Open Source.
* Copyright 2006, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ejb.plugins.inflow;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.ejb.EJBMetaData;
import javax.management.ObjectName;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.UnavailableException;
import javax.resource.spi.endpoint.MessageEndpoint;
import javax.resource.spi.endpoint.MessageEndpointFactory;
import javax.transaction.xa.XAResource;
import org.jboss.deployment.DeploymentException;
import org.jboss.ejb.Container;
import org.jboss.ejb.EJBProxyFactory;
import org.jboss.ejb.MessageDrivenContainer;
import org.jboss.invocation.Invocation;
import org.jboss.invocation.InvocationType;
import org.jboss.invocation.InvokerInterceptor;
import org.jboss.metadata.ActivationConfigPropertyMetaData;
import org.jboss.metadata.InvokerProxyBindingMetaData;
import org.jboss.metadata.javaee.spec.MessageDestinationMetaData;
import org.jboss.metadata.MessageDrivenMetaData;
import org.jboss.metadata.MetaData;
import org.jboss.mx.util.JMXExceptionDecoder;
import org.jboss.proxy.GenericProxyFactory;
import org.jboss.system.ServiceMBeanSupport;
import org.jboss.util.Strings;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* EJBProxyFactory for inflow message driven beans
*
* @author <a href="mailto:adrian@jboss.com">Adrian Brock</a> .
* @version <tt>$Revision: 66932 $</tt>
*/
public class JBossMessageEndpointFactory extends ServiceMBeanSupport
implements EJBProxyFactory, MessageEndpointFactory, JBossMessageEndpointFactoryMBean
{
/** Whether trace is enabled */
protected boolean trace = log.isTraceEnabled();
/** Our container */
protected MessageDrivenContainer container;
/** Our meta data */
protected MessageDrivenMetaData metaData;
/** The invoker binding */
protected String invokerBinding;
/** The invoker meta data */
protected InvokerProxyBindingMetaData invokerMetaData;
/** The activation properties */
protected HashMap<String, ActivationConfigPropertyMetaData> properties = new HashMap<String, ActivationConfigPropertyMetaData>();
/** The proxy factory */
protected GenericProxyFactory proxyFactory = new GenericProxyFactory();
/** The messaging type class */
protected Class<?> messagingTypeClass;
/** The resource adapter name */
protected String resourceAdapterName;
/** The resource adapter object name */
protected ObjectName resourceAdapterObjectName;
/** The activation spec */
protected ActivationSpec activationSpec;
/** The interceptors */
protected ArrayList<Class<?>> interceptors;
/** The interfaces */
protected Class<?>[] interfaces;
/** The next proxy id */
protected AtomicInteger nextProxyId = new AtomicInteger(0);
/** Whether delivery is active */
protected AtomicBoolean deliveryActive = new AtomicBoolean(true);
/** The signature for createActivationSpec */
protected String[] createActivationSpecSig = new String[]
{
Class.class.getName(),
Collection.class.getName()
};
/** The signature for activate/deactivateEndpint */
protected String[] activationSig = new String[]
{
MessageEndpointFactory.class.getName(),
ActivationSpec.class.getName()
};
/**
* Get the message driven container
*
* @return the container
*/
public MessageDrivenContainer getContainer()
{
return container;
}
public String getConfig()
{
return toString();
}
public MessageEndpoint createEndpoint(XAResource resource) throws UnavailableException
{
trace = log.isTraceEnabled();
if (getState() != STARTED && getState() != STARTING)
throw new UnavailableException("The container is not started");
HashMap context = new HashMap();
context.put(MessageEndpointInterceptor.MESSAGE_ENDPOINT_FACTORY, this);
context.put(MessageEndpointInterceptor.MESSAGE_ENDPOINT_XARESOURCE, resource);
String ejbName = container.getBeanMetaData().getContainerObjectNameJndiName();
if (trace)
log.trace("createEndpoint " + this + " xaResource=" + resource);
MessageEndpoint endpoint = (MessageEndpoint) proxyFactory.createProxy
(
ejbName + "@" + nextProxyId.incrementAndGet(),
container.getServiceName(),
InvokerInterceptor.getLocal(),
null,
null,
interceptors,
container.getClassLoader(),
interfaces,
context
);
if (trace)
log.trace("Created endpoint " + endpoint + " from " + this);
return endpoint;
}
public boolean isDeliveryTransacted(Method method) throws NoSuchMethodException
{
boolean result = false;
int transType = metaData.getMethodTransactionType(method.getName(), method.getParameterTypes(), InvocationType.LOCAL);
if (transType == MetaData.TX_REQUIRED)
result = true;
if (trace)
log.trace("isDeliveryTransacted " + container.getBeanMetaData().getContainerObjectNameJndiName() + " method=" + method + " result=" + result);
return result;
}
protected void startService() throws Exception
{
// Lets take a reference to our metadata
metaData = (MessageDrivenMetaData) container.getBeanMetaData();
// Resolve the message listener
resolveMessageListener();
// Resolve the resource adapter
resolveResourceAdapter();
// Create the activation config
createActivationSpec();
// Set up proxy parameters
setupProxyParameters();
// Activate
activate();
}
protected void stopService() throws Exception
{
// Deactivate
deactivate();
}
public boolean isIdentical(Container container, Invocation mi)
{
throw new Error("Not valid for MessageDriven beans");
}
public Object getEJBHome()
{
throw new Error("Not valid for MessageDriven beans");
}
public EJBMetaData getEJBMetaData()
{
throw new Error("Not valid for MessageDriven beans");
}
public Collection getEntityCollection(Collection collection)
{
throw new Error("Not valid for MessageDriven beans");
}
public Object getEntityEJBObject(Object id)
{
throw new Error("Not valid for MessageDriven beans");
}
public Object getStatefulSessionEJBObject(Object id)
{
throw new Error("Not valid for MessageDriven beans");
}
public Object getStatelessSessionEJBObject()
{
throw new Error("Not valid for MessageDriven beans");
}
public void setInvokerBinding(String binding)
{
this.invokerBinding = binding;
}
public void setInvokerMetaData(InvokerProxyBindingMetaData imd)
{
this.invokerMetaData = imd;
}
/**
* Set the container for which this is an invoker to.
*
* @param container The container for which this is an invoker to.
*/
public void setContainer(final Container container)
{
this.container = (MessageDrivenContainer) container;
}
/**
* Return a string representation of the current config state.
*/
public String toString()
{
StringBuffer buffer = new StringBuffer(100);
buffer.append(super.toString());
buffer.append("{ resourceAdapter=").append(resourceAdapterObjectName);
buffer.append(", messagingType=").append(messagingTypeClass.getName());
buffer.append(", ejbName=").append(container.getBeanMetaData().getContainerObjectNameJndiName());
buffer.append(", activationConfig=").append(properties.values());
buffer.append(", activationSpec=").append(activationSpec);
buffer.append("}");
return buffer.toString();
}
/**
* Resolve message listener class
*
* @throws DeploymentException for any error
*/
protected void resolveMessageListener() throws DeploymentException
{
String messagingType = metaData.getMessagingType();
try
{
messagingTypeClass = GetTCLAction.getContextClassLoader().loadClass(messagingType);
}
catch (Exception e)
{
DeploymentException.rethrowAsDeploymentException("Could not load messaging-type class " + messagingType, e);
}
}
/**
* Resolve the resource adapter name
*
* @return the resource adapter name
* @throws DeploymentException for any error
*/
protected String resolveResourceAdapterName() throws DeploymentException
{
return metaData.getResourceAdapterName();
}
/**
* Resolve the resource adapter
*
* @throws DeploymentException for any error
*/
protected void resolveResourceAdapter() throws DeploymentException
{
resourceAdapterName = resolveResourceAdapterName();
try
{
resourceAdapterObjectName = new ObjectName("jboss.jca:service=RARDeployment,name='" + resourceAdapterName + "'");
int state = ((Integer) server.getAttribute(resourceAdapterObjectName, "State")).intValue();
if (state != STARTED)
throw new DeploymentException("The resource adapter is not started " + resourceAdapterName);
}
catch (Exception e)
{
DeploymentException.rethrowAsDeploymentException("Cannot locate resource adapter deployment " + resourceAdapterName, e);
}
}
/**
* Set up the proxy parametrs
*
* @throws DeploymentException
*/
protected void setupProxyParameters() throws DeploymentException
{
// Set the interfaces
interfaces = new Class[] { MessageEndpoint.class, messagingTypeClass };
// Set the interceptors
interceptors = new ArrayList<Class<?>>();
Element proxyConfig = invokerMetaData.getProxyFactoryConfig();
Element endpointInterceptors = MetaData.getOptionalChild(proxyConfig, "endpoint-interceptors", null);
if (endpointInterceptors == null)
throw new DeploymentException("No endpoint interceptors found");
else
{
NodeList children = endpointInterceptors.getElementsByTagName("interceptor");
for (int i = 0; i < children.getLength(); ++i)
{
Node currentChild = children.item(i);
if (currentChild.getNodeType() == Node.ELEMENT_NODE)
{
Element interceptor = (Element) children.item(i);
String className = MetaData.getElementContent(interceptor);
try
{
Class<?> clazz = container.getClassLoader().loadClass(className);
interceptors.add(clazz);
}
catch (Throwable t)
{
DeploymentException.rethrowAsDeploymentException("Error loading interceptor class " + className, t);
}
}
}
}
}
/**
* Add activation config properties
*
* @throws DeploymentException for any error
*/
protected void augmentActivationConfigProperties() throws DeploymentException
{
// Allow activation config properties from invoker proxy binding
Element proxyConfig = invokerMetaData.getProxyFactoryConfig();
Element activationConfig = MetaData.getOptionalChild(proxyConfig, "activation-config");
if (activationConfig != null)
{
Iterator<Element> iterator = MetaData.getChildrenByTagName(activationConfig, "activation-config-property");
while (iterator.hasNext())
{
Element xml = iterator.next();
org.jboss.metadata.ejb.spec.ActivationConfigPropertyMetaData md = new org.jboss.metadata.ejb.spec.ActivationConfigPropertyMetaData();
ActivationConfigPropertyMetaData metaData = new ActivationConfigPropertyMetaData(md);
String name = MetaData.getElementContent(MetaData.getUniqueChild(xml, "activation-config-property-name"));
String value = MetaData.getElementContent(MetaData.getUniqueChild(xml, "activation-config-property-value"));
if (name == null || name.trim().length() == 0)
throw new DeploymentException("activation-config-property doesn't have a name");
if (Strings.isValidJavaIdentifier(name) == false)
throw new DeploymentException("activation-config-property '" + name + "' is not a valid java identifier");
md.setName(name);
md.setValue(value);
if (properties.containsKey(metaData.getName()) == false)
properties.put(metaData.getName(), metaData);
}
}
// Message Destination Link
String link = metaData.getDestinationLink();
if (link != null)
{
link = link.trim();
if (link.length() > 0)
{
if (properties.containsKey("destination"))
log.warn("Ignoring message-destination-link '" + link + "' when the destination " +
"is already in the activation-config.");
else
{
MessageDestinationMetaData destinationMetaData = container.getMessageDestination(link);
if (destinationMetaData == null)
throw new DeploymentException("Unresolved message-destination-link '" + link + "' no message-destination in ejb-jar.xml");
String jndiName = destinationMetaData.getJndiName();
if (jndiName == null)
throw new DeploymentException("The message-destination '" + link + "' has no jndi-name in jboss.xml");
org.jboss.metadata.ejb.spec.ActivationConfigPropertyMetaData acpmd = new
org.jboss.metadata.ejb.spec.ActivationConfigPropertyMetaData();
acpmd.setActivationConfigPropertyName("destination");
acpmd.setValue(jndiName);
ActivationConfigPropertyMetaData wrapper = new ActivationConfigPropertyMetaData(acpmd);
properties.put("destination", wrapper);
}
}
}
}
/**
* Create the activation spec
*
* @throws DeploymentException for any error
*/
protected void createActivationSpec() throws DeploymentException
{
properties = new HashMap(metaData.getActivationConfigProperties());
augmentActivationConfigProperties();
Object[] params = new Object[]
{
messagingTypeClass,
properties.values()
};
try
{
activationSpec = (ActivationSpec) server.invoke(resourceAdapterObjectName, "createActivationSpec", params, createActivationSpecSig);
}
catch (Throwable t)
{
t = JMXExceptionDecoder.decode(t);
DeploymentException.rethrowAsDeploymentException("Unable to create activation spec ra=" + resourceAdapterObjectName +
" messaging-type=" + messagingTypeClass.getName() + " properties=" + metaData.getActivationConfigProperties(), t);
}
}
public void startDelivery() throws Exception
{
if (getState() != STARTED)
throw new IllegalStateException("The MDB is not started");
if (deliveryActive.getAndSet(true))
return;
activate();
}
public void stopDelivery() throws Exception
{
stopDelivery(false);
}
public void stopDelivery(boolean asynch) throws Exception
{
if (getState() != STARTED)
throw new IllegalStateException("The MDB is not started");
if (deliveryActive.getAndSet(false) == false)
return;
if (asynch)
{
new Thread("StopDelivery: " + getServiceName())
{
public void run()
{
deactivate();
}
}.start();
}
else
{
deactivate();
}
}
public boolean getDeliveryActive()
{
return deliveryActive.get();
}
public void setDeliveryActive(boolean active)
{
deliveryActive.set(active);
}
/**
* Activate
*
* @throws DeploymentException for any error
*/
protected void activate() throws DeploymentException
{
if (deliveryActive.get() == false)
{
log.info("Delivery is disabled: " + getServiceName());
return;
}
Object[] params = new Object[] { this, activationSpec };
try
{
server.invoke(resourceAdapterObjectName, "endpointActivation", params, activationSig);
}
catch (Throwable t)
{
t = JMXExceptionDecoder.decode(t);
DeploymentException.rethrowAsDeploymentException("Endpoint activation failed ra=" + resourceAdapterObjectName +
" activationSpec=" + activationSpec, t);
}
}
/**
* Deactivate
*/
protected void deactivate()
{
Object[] params = new Object[] { this, activationSpec };
try
{
server.invoke(resourceAdapterObjectName, "endpointDeactivation", params, activationSig);
}
catch (Throwable t)
{
t = JMXExceptionDecoder.decode(t);
log.warn("Endpoint activation failed ra=" + resourceAdapterObjectName +
" activationSpec=" + activationSpec, t);
}
}
}