/*
* 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.ejb3.mdb;
import org.jboss.annotation.ejb.ResourceAdapter;
import org.jboss.aop.AspectManager;
import org.jboss.aop.MethodInfo;
import org.jboss.aop.advice.Interceptor;
import org.jboss.deployment.DeploymentException;
import org.jboss.ejb3.*;
import org.jboss.ejb3.mdb.inflow.JBossMessageEndpointFactory;
import org.jboss.ejb3.interceptor.InterceptorInfoRepository;
import org.jboss.ejb3.jms.JMSDestinationFactory;
import org.jboss.ejb3.timerservice.TimedObjectInvoker;
import org.jboss.ejb3.timerservice.TimerServiceFactory;
import org.jboss.jms.jndi.JMSProviderAdapter;
import org.jboss.logging.Logger;
import org.jboss.metadata.ActivationConfigPropertyMetaData;
import javax.ejb.*;
import javax.ejb.Timer;
import javax.jms.*;
import javax.jms.Queue;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
* @version <tt>$Revision: 61280 $</tt>
* @author <a href="mailto:bdecoste@jboss.com">William DeCoste</a>
*/
public abstract class MessagingContainer extends EJBContainer implements TimedObjectInvoker
{
private static final Logger log = Logger.getLogger(MessagingContainer.class);
protected TimerService timerService;
protected ActivationSpec activationSpec = new ActivationSpec();
protected JBossMessageEndpointFactory messageEndpointFactory;
/**
* Default destination type. Used when no message-driven-destination is given
* in ejb-jar, and a lookup of destinationJNDI from jboss.xml is not
* successfull. Default value: javax.jms.Topic.
*/
protected final static String DEFAULT_DESTINATION_TYPE = "javax.jms.Topic";
public MessagingContainer(String ejbName, AspectManager manager, ClassLoader cl, String beanClassName, Hashtable ctxProperties,
InterceptorInfoRepository interceptorRepository, Ejb3Deployment deployment)
{
super(Ejb3Module.BASE_EJB3_JMX_NAME + ",name=" + ejbName, manager, cl, beanClassName, ejbName, ctxProperties, interceptorRepository, deployment);
beanContextClass = MDBContext.class;
messageEndpointFactory = new JBossMessageEndpointFactory();
messageEndpointFactory.setContainer(this);
}
public abstract Class getMessagingType();
public abstract Map getActivationConfigProperties();
protected abstract void populateActivationSpec();
public abstract MethodInfo getMethodInfo(Method method);
public void setMessageEndpointFactory(JBossMessageEndpointFactory messageEndpointFactory)
{
this.messageEndpointFactory = messageEndpointFactory;
}
public String getResourceAdaptorName()
{
ResourceAdapter annotation = (ResourceAdapter) resolveAnnotation(ResourceAdapter.class);
if (annotation == null)
return JMS_ADAPTOR;
return annotation.value();
}
protected void addActivationSpecProperty(Map result, ActivationConfigProperty property)
{
if (!property.propertyName().equals("messagingType"))
{
ActivationConfigPropertyMetaData metaData = new ActivationConfigPropertyMetaData();
try
{
Field nameField = ActivationConfigPropertyMetaData.class.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(metaData, property.propertyName());
Field valueField = ActivationConfigPropertyMetaData.class.getDeclaredField("value");
valueField.setAccessible(true);
valueField.set(metaData, property.propertyValue());
}
catch (Exception e)
{
throw new RuntimeException(e);
}
/*
* Older versions don't have this
* TODO revert to this after we ditch 4.0.3SP1 as supported EJB3 platform
metaData.setName(property.propertyName());
metaData.setValue(property.propertyValue());
*/
result.put(property.propertyName(), metaData);
}
}
/**
* Initialize the container invoker. Sets up a connection, a server session
* pool and a connection consumer for the configured destination.
* <p/>
* Any JMSExceptions produced while initializing will be assumed to be
* caused due to JMS Provider failure.
*
* @throws Exception Failed to initalize.
*/
public void start() throws Exception
{
super.start();
populateActivationSpec();
innerStart();
timerService = TimerServiceFactory.getInstance().createTimerService(this.getObjectName(), this);
startProxies();
TimerServiceFactory.getInstance().restoreTimerService(timerService);
}
protected void innerStart() throws Exception
{
log.debug("Initializing");
if (getResourceAdaptorName().equals(JMS_ADAPTOR))
jmsCreate();
}
public ObjectName getJmxName()
{
ObjectName jmxName = null;
String jndiName = ProxyFactoryHelper.getLocalJndiName(this);
// The name must be escaped since the jndiName may be arbitrary
String name = org.jboss.ejb.Container.BASE_EJB_CONTAINER_NAME + ",jndiName=" + jndiName;
try
{
jmxName = org.jboss.mx.util.ObjectNameConverter.convert(name);
}
catch (MalformedObjectNameException e)
{
e.printStackTrace();
throw new RuntimeException("Failed to create ObjectName, msg=" + e.getMessage());
}
return jmxName;
}
protected void startProxies() throws Exception
{
messageEndpointFactory.start();
}
/**
* Parse the JNDI suffix from the given JNDI name.
*
* @param jndiname The JNDI name used to lookup the destination.
* @param defautSuffix Description of Parameter
* @return The parsed suffix or the defaultSuffix
*/
protected String parseJndiSuffix(final String jndiname,
final String defautSuffix)
{
// jndiSuffix is merely the name that the user has given the MDB.
// since the jndi name contains the message type I have to split
// at the "/" if there is no slash then I use the entire jndi name...
String jndiSuffix = "";
if (jndiname != null)
{
int indexOfSlash = jndiname.indexOf("/");
if (indexOfSlash != -1)
{
jndiSuffix = jndiname.substring(indexOfSlash + 1);
}
else
{
jndiSuffix = jndiname;
}
}
else
{
// if the jndi name from jboss.xml is null then lets use the ejbName
jndiSuffix = defautSuffix;
}
return jndiSuffix;
}
public Object localInvoke(Method method, Object[] args) throws Throwable
{
MethodInfo info = getMethodInfo(method);
if (info == null)
{
throw new RuntimeException("Could not resolve beanClass method from proxy call: " + method.toString());
}
return localInvoke(info, args);
}
public Object localInvoke(MethodInfo info, Object[] args) throws Throwable
{
ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
pushEnc();
try
{
EJBContainerInvocation nextInvocation = new EJBContainerInvocation(info);
nextInvocation.setAdvisor(this);
nextInvocation.setArguments(args);
return nextInvocation.invokeNext();
}
finally
{
Thread.currentThread().setContextClassLoader(oldLoader);
popEnc();
}
}
public TimerService getTimerService()
{
return timerService;
}
public TimerService getTimerService(Object pKey)
{
assert timerService != null : "Timer Service not yet initialized";
return timerService;
}
public void callTimeout(Timer timer) throws Exception
{
Method timeout = callbackHandler.getTimeoutCallback();
if (timeout == null) throw new EJBException("No method has been annotated with @Timeout");
Object[] args = {timer};
try
{
localInvoke(timeout, args);
}
catch (Throwable throwable)
{
if (throwable instanceof Exception) throw (Exception) throwable;
throw new RuntimeException(throwable);
}
}
public void stop() throws Exception
{
if (timerService != null)
{
TimerServiceFactory.getInstance().removeTimerService(timerService);
timerService = null;
}
stopProxies();
super.stop();
}
protected void stopProxies() throws Exception
{
messageEndpointFactory.stop();
}
// ********* JMS Specific
protected static final String JMS_ADAPTOR = "jms-ra.rar";
protected static final String DESTINATION = "destination";
protected static final String DESTINATION_TYPE = "destinationType";
protected static final String PROVIDER_ADAPTER_JNDI = "providerAdapterJNDI";
protected static final String MAX_SESSION = "maxSession";
protected void initializePool() throws Exception
{
super.initializePool();
String maxSession = getMaxSession();
if (maxSession != null)
{
pool.setMaxSize(Integer.parseInt(maxSession));
}
}
protected String getProviderAdapterJNDI()
{
ActivationConfigPropertyMetaData property = (ActivationConfigPropertyMetaData)getActivationConfigProperties().get(PROVIDER_ADAPTER_JNDI);
if (property != null)
return property.getValue();
return "java:/DefaultJMSProvider";
}
protected String getMaxSession()
{
ActivationConfigPropertyMetaData property = (ActivationConfigPropertyMetaData)getActivationConfigProperties().get(MAX_SESSION);
if (property != null)
return property.getValue();
return null;
}
protected String getDestination()
{
ActivationConfigPropertyMetaData property = (ActivationConfigPropertyMetaData)getActivationConfigProperties().get(DESTINATION);
if (property != null)
return property.getValue();
return null;
}
protected String getDestinationType()
{
ActivationConfigPropertyMetaData property = (ActivationConfigPropertyMetaData)getActivationConfigProperties().get(DESTINATION_TYPE);
if (property != null)
return property.getValue();
return null;
}
protected void jmsCreate() throws Exception
{
// Get the JMS provider
// todo get rid of server module dependency
JMSProviderAdapter adapter = getJMSProviderAdapter();
log.debug("Provider adapter: " + adapter);
// Connect to the JNDI server and get a reference to root context
Context context = adapter.getInitialContext();
log.debug("context: " + context);
// if we can't get the root context then exit with an exception
if (context == null)
{
throw new RuntimeException("Failed to get the root context");
}
// Unfortunately the destination is optional, so if we do not have one
// here we have to look it up if we have a destinationJNDI, else give it
// a default.
String destinationType = getDestinationType();
if (destinationType == null)
{
log.warn("No message-driven-destination given; using; guessing type");
destinationType = getDestinationType(context, getDestination());
}
if ("javax.jms.Topic".equals(destinationType))
{
innerCreateTopic(context);
}
else if ("javax.jms.Queue".equals(destinationType))
{
innerCreateQueue(context);
}
else
throw new DeploymentException("Unknown destination-type " + destinationType);
log.debug("Initialized with config " + toString());
context.close();
}
protected void innerCreateQueue(Context context)
throws Exception
{
log.debug("Got destination type Queue for " + ejbName);
// Get the JNDI suffix of the destination
String jndiSuffix = parseJndiSuffix(getDestination(), ejbName);
log.debug("jndiSuffix: " + jndiSuffix);
// lookup or create the destination queue
Queue queue = null;
try
{
// First we try the specified queue
if (getDestination() != null)
queue = (Queue) context.lookup(getDestination());
}
catch (NamingException e)
{
log.warn("Could not find the queue destination-jndi-name=" + getDestination());
}
catch (ClassCastException e)
{
throw new DeploymentException("Expected a Queue destination-jndi-name=" + getDestination());
}
if (queue == null)
queue = (Queue) createDestination(Queue.class,
context,
"queue/" + jndiSuffix,
jndiSuffix);
}
protected void innerCreateTopic(Context context)
throws Exception
{
log.debug("Got destination type Topic for " + ejbName);
// Get the JNDI suffix of the destination
String jndiSuffix = parseJndiSuffix(getDestination(), ejbName);
log.debug("jndiSuffix: " + jndiSuffix);
// lookup or create the destination topic
Topic topic = null;
try
{
// First we try the specified topic
if (getDestination() != null)
topic = (Topic) context.lookup(getDestination());
}
catch (NamingException e)
{
log.warn("Could not find the topic destination-jndi-name=" + getDestination());
}
catch (ClassCastException e)
{
throw new DeploymentException("Expected a Topic destination-jndi-name=" + getDestination());
}
if (topic == null)
topic = (Topic) createDestination(Topic.class,
context,
"topic/" + jndiSuffix,
jndiSuffix);
}
/**
* Create and or lookup a JMS destination.
*
* @param type Either javax.jms.Queue or javax.jms.Topic.
* @param ctx The naming context to lookup destinations from.
* @param jndiName The name to use when looking up destinations.
* @param jndiSuffix The name to use when creating destinations.
* @return The destination.
* @throws IllegalArgumentException Type is not Queue or Topic.
* @throws Exception Description of Exception
*/
private Destination createDestination(final Class<? extends Destination> type,
final Context ctx,
final String jndiName,
final String jndiSuffix)
throws Exception
{
try
{
// first try to look it up
return (Destination) ctx.lookup(jndiName);
}
catch (NamingException e)
{
// is JMS?
if (getDestination() == null)
{
return null;
}
else
{
// if the lookup failes, the try to create it
log.warn("destination not found: " + jndiName + " reason: " + e);
log.warn("creating a new temporary destination: " + jndiName);
createTemporaryDestination(type, jndiSuffix);
// try to look it up again
return (Destination) ctx.lookup(jndiName);
}
}
}
private void createTemporaryDestination(Class<? extends Destination> type, String jndiSuffix) throws Exception
{
//
// jason: we should do away with this...
//
// attempt to create the destination (note, this is very
// very, very unportable).
//
// MBeanServer server = org.jboss.mx.util.MBeanServerLocator.locateJBoss();
// String methodName;
// String destinationContext;
// if (type == Topic.class)
// {
// destinationContext = "topic";
// methodName = "createTopic";
// }
// else if (type == Queue.class)
// {
// destinationContext = "queue";
// methodName = "createQueue";
// }
// else
// {
// // type was not a Topic or Queue, bad user
// throw new IllegalArgumentException
// ("Expected javax.jms.Queue or javax.jms.Topic: " + type);
// }
/* No longer supported (AS 4.x)
ObjectName destinationManagerName = new ObjectName("jboss.mq:service=DestinationManager");
KernelAbstraction kernel = KernelAbstractionFactory.getInstance();
// invoke the server to create the destination
Object result = kernel.invoke(destinationManagerName,
methodName,
new Object[]{jndiSuffix},
new String[]{"java.lang.String"});
InitialContext jndiContext = InitialContextFactory.getInitialContext();
String binding = destinationContext + "/" + jndiSuffix;
try
{
jndiContext.lookup(binding);
}
catch (NamingException e)
{
jndiContext.rebind(binding, result);
}
*/
//throw new UnsupportedOperationException("Can't create destination " + destinationContext + "/" + jndiSuffix);
JMSDestinationFactory.getInstance().createDestination(type, jndiSuffix);
}
/**
* Return the JMSProviderAdapter that should be used.
*
* @return The JMSProviderAdapter to use.
*/
protected JMSProviderAdapter getJMSProviderAdapter()
throws NamingException
{
Context context = getInitialContext();
//todo make this pluggable
String providerAdapterJNDI = getProviderAdapterJNDI();
try
{
log.debug("Looking up provider adapter: " + providerAdapterJNDI);
return (JMSProviderAdapter) context.lookup(providerAdapterJNDI);
}
finally
{
context.close();
}
}
/**
* Try to get a destination type by looking up the destination JNDI, or
* provide a default if there is not destinationJNDI or if it is not possible
* to lookup.
*
* @param ctx The naming context to lookup destinations from.
* @param destinationJNDI The name to use when looking up destinations.
* @return The destination type, either derived from destinationJDNI or
* DEFAULT_DESTINATION_TYPE
*/
protected String getDestinationType(Context ctx, String destinationJNDI)
{
String destType = null;
if (destinationJNDI != null)
{
try
{
Destination dest = (Destination) ctx.lookup(destinationJNDI);
if (dest instanceof javax.jms.Topic)
{
destType = "javax.jms.Topic";
}
else if (dest instanceof javax.jms.Queue)
{
destType = "javax.jms.Queue";
}
}
catch (NamingException ex)
{
log.debug("Could not do heristic lookup of destination ", ex);
}
}
if (destType == null)
{
log.warn("Could not determine destination type, defaults to: " +
DEFAULT_DESTINATION_TYPE);
destType = DEFAULT_DESTINATION_TYPE;
}
return destType;
}
} |