FileDocCategorySizeDatePackage
MessagingContainer.javaAPI DocJBoss 4.2.120382Fri Jul 13 20:53:56 BST 2007org.jboss.ejb3.mdb

MessagingContainer.java

/*
 * 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;
   }
}