/*
 * Copyright (c) 1998-2000, Fiorano Software, Inc.
 * All Rights Reserved
 *
 *  JMS-EJB Integration Sample
 *
 *  FileName   : Subscriber.java
 *
 *  Implements an asynchronous listener to listen
 *  for messages published on the queue - "primaryQueue".
 *
 *  Receives JMS Messages that were published on the Queue, 
 *  demarshalls the content of the JMS Message to lookup and 
 *  invoke appropriate methods on the EJB with appropriate 
 *  parameters using reflection.
 *
 *
 *  USAGE::
 *  java -Djava.naming.factory.initial=AppServer'sInitialContext Subscriber 
 * 
 *  Questions/comments/suggestions?
 *  Please visit: http://www.fiorano.com
 *  or e-mail: support@fiorano.com
 *
 *  @since     4.4, May 2000
 */

import javax.jms.*;
import javax.ejb.*;
import javax.naming.*;
import javax.rmi.*;

import java.lang.reflect.*;
import java.util.*;

import fiorano.jms.rtl.FioranoInitialContext;

/** 
 *  This class can be run as a stand-alone JMS Application
 *  The JVM should have a reference of hte appropriate Server
 *  interfaces (Home/Remote Interfaces of all EJB's that need to be
 *  invoked) in its CLASSPATH.
 *
 *  Alternatively, this class can also be made as a startup
 *  class, so that the AppServer is responsible for starting
 *  and stopping this class within the AppServer's JVM instance.
 *
 *  The main disadvantage of making this class as a startup class
 *  is the non-proprietary nature of the support for the startup 
 *  classes by the various AppServer vendors.
 *
 */
class Subscriber implements MessageListener,ExceptionListener
{
    // String identifying the message type
    private static final String FIORANO_EJB_MESSAGE = "FIORANO_EJB_MESSAGE";
    
    // AppServer's InitialContext
    private InitialContext m_appServerContext;
    
    public static void main (String args[])
    {
        Subscriber subscriber = new Subscriber ();
        
        try
        {
            //  1. Create the InitialContext Object used for looking up
            //     JMS administered objects on the FioranoMQ
            //     located on the default host.
            FioranoInitialContext ic = new FioranoInitialContext ();
            ic.bind ();

            // 1.1  Lookup Connection Factory and Queue names
            //
            QueueConnectionFactory qcf =
                        (QueueConnectionFactory) ic.lookup ("primaryQCF");
            Queue queue = (Queue)ic.lookup("primaryQueue");

            // 1.2  Dispose the InitialContext resources
            //
            ic.dispose();

            // 2. create and start a queue connection
            System.out.println("Creating queue connection");
            QueueConnection queueConnection = qcf.createQueueConnection();
            
	        // Register an Exception Listner
	        queueConnection.setExceptionListener (subscriber);
            queueConnection.start ();

            // 3. create queue session on the connection just created
            System.out.println("Creating queue session: not transacted, auto ack");
            QueueSession queueSession = queueConnection.createQueueSession(false,1);

            // 4. create subscriber
            System.out.println("Creating queue, subscriber");
            QueueReceiver queueReceiver = queueSession.createReceiver(queue);

            // 5. install an asynchronous listener/callback on the subscriber object
            //    just created
            System.out.println ("Ready to subscribe for messages :");
            queueReceiver.setMessageListener (new Subscriber ());
        }
        catch (Exception e)
        {
            e.printStackTrace ();
        }
    }

    /**
     *  Message listener which receives messages aysynchronously
     *  for the bound subscriber.
     *  
     *  Read the message properties, and lookup the appropriate
     *  EJB. Invoke appropriate method on the EJB.
     *
     *  @param Message, received JMS Message
     */
    public void onMessage (Message msg)
    {
        try 
        { 
            String msgType = msg.getJMSType(); 
            
            // Check whether the received messages is 
            // an EJB invocation message.
            if (msgType.equals(FIORANO_EJB_MESSAGE)) 
            { 
                // Get the contents of the message to invoke apppropriate EJB
                String beanName = msg.getStringProperty(JMSConstants.EJB_NAME); 
                String constName = msg.getStringProperty(JMSConstants.CONST); 
                int nConstArgs = msg.getIntProperty(JMSConstants.CONST_N_ARGS); 
                String methodName = msg.getStringProperty(JMSConstants.METHOD_NAME); 
                int nMethodArgs = msg.getIntProperty(JMSConstants.METHOD_N_ARGS); 
  
                // Get Appserver's InitialContext to lookup appropriate Bean references
                if (m_appServerContext == null) 
                {
                    // Please make sure to set appropriate System
                    // property.
                    // In case of WebLogic AppServer, set property
                    // java.naming.factory.initial=weblogic.jndi.T3InitialContextFactory
                    
                    //Properties prop = System.getProperties ();
                    //prop.put ("java.naming.factory.initial", "weblogic.jndi.T3InitialContextFactory");
                    
                    System.setProperties (prop);
                    
                    try
                    {
                        m_appServerContext = new InitialContext(); 
                    }
                    catch (NamingException err)
                    {
                        System.out.println ("Unable to initialize AppServer's InitialContext. Please ensure" +
                                             "to set System property java.naming.factory.initial");
                        err.printStackTrace ();
                    }
                }
  
                if (m_appServerContext != null) 
                { 
                   try 
                   { 
                        // Get the constructor types and args to create appropriate EJB
                        
                        Class[] constTypes = getClassTypesFromMessage(msg, nConstArgs, JMSConstants.CONST, JMSConstants.CONST_TYPE);
                        Object[] constArgs = getArgumentsFromMessage(msg, nConstArgs, JMSConstants.CONST,JMSConstants.CONST_TYPE );
  
                        // Get the class type and parameters for the EJBObject (stub) method to invoke... 
                        Class[] methodTypes = getClassTypesFromMessage(msg, nMethodArgs, JMSConstants.METHOD_NAME, JMSConstants.METHOD_TYPE); 
                        Object[] methodArgs = getArgumentsFromMessage(msg, nMethodArgs, JMSConstants.METHOD_NAME, JMSConstants.METHOD_TYPE); 
  
                        // Find our home class through JNDI... 
                        Object obj = m_appServerContext.lookup(beanName); 
                        Class beanClass = obj.getClass(); 
  
                        // Find the create method... 
                        Method methodCreate = beanClass.getMethod(constName, constTypes); 
  
                        // Invoke the create method... 
                        Object ejb =  methodCreate.invoke(obj, constArgs); 
                        System.out.println("Received Event. Invoking EJB::Method()" + methodName); 
                        
                        // Find the EJB method in the JMS Message... 
                        Method methodCall = ejb.getClass().getMethod(methodName, methodTypes); 
                        
                        // Invoke the EJB method... 
                        methodCall.invoke(ejb, methodArgs); 
                    } 
                    catch(InvocationTargetException e) 
                    { 
                        System.out.println("ReceiverStartup: InvocationTargetException was thrown"); 
                    } 
                    catch(NoSuchMethodException e) 
                    { 
                        System.out.println("ReceiverStartup: NoSuchMethodException was thrown"); 
                    } 
                    catch(IllegalAccessException e) 
                    { 
                        System.out.println("ReceiverStartup: IllegalAccessException was thrown"); 
                    } 
                    catch(Exception e) 
                    { 
                        System.out.println("ReceiverStartup: An exception was thrown"); 
                    } 
                } 
                else 
                    System.out.println("JMSSubscription:: Startup Failure"); 
            } 
        } 
        catch (JMSException e) 
        { 
            e.printStackTrace(); 
        } 
    } 
  
    /**
     * Get Class Type from Message
     *
     * @param message
     * @param no of args
     * @param argument Name
     * @param type name
     */
    private Class[] getClassTypesFromMessage (Message msg, int params, String argName, String typeName) 
                throws JMSException 
    { 
        Class[] types = new Class[0]; 
        
        if (params > 0) 
        { 
            types = new Class[params]; 
  
            for (int i = 0; i < params; i++) 
            { 
                String type = msg.getStringProperty(typeName + i); 
                if (type.equals("String")) 
                { 
                    types[i] = String.class; 
                } 
                else if (type.equals("int")) 
                { 
                    types[i] = int.class; 
                } 
                else if (type.equals("long")) 
                { 
                    types[i] = long.class; 
                } 
                else if (type.equals("float")) 
                { 
                    types[i] = float.class; 
                } 
                else if (type.equals("double")) 
                { 
                    types[i] = double.class; 
                } 
                else if (type.equals("short")) 
                { 
                    types[i] = short.class; 
                } 
                else if (type.equals("byte")) 
                { 
                    types[i] = byte.class; 
                } 
                else if (type.equals("object")) 
                { 
                    // Extract any type of object... 
                    Object obj = msg.getObjectProperty(argName + i); 
                    types[i] = obj.getClass(); 
                    
                    System.out.println("Object instance: " + obj.getClass().getName()); 
                } 
                else if (type.equals("Message")) 
                { 
                    types[i] = Message.class; 
                } 
                else 
                    System.out.println("Unknown parameter type"); 
            } 
        } 
        return types; 
    } 
    
    /**
     * Get Arguments Type from Message
     *
     * @param message
     * @param no of args
     * @param argument Name
     * @param type name
     * 
     * @exception JMSException if the operation fails
     */
    private Object[] getArgumentsFromMessage(Message msg, int params, String argName, String argType) 
            throws JMSException 
    { 
        Object[] args = new Object[0]; 
  
        if (params > 0) 
        { 
            args = new Object[params]; 
  
            for (int i = 0; i < params; i++) 
            { 
                String type = msg.getStringProperty(argType + i); 
                System.out.println ("Arg Type" + argType + " Value" + type);
                
                if (type.equals("String")) 
                { 
                    String temp = msg.getStringProperty(argName + i); 
                    args[i] = temp; 
                } 
                else if (type.equals("int")) 
                { 
                    int temp = msg.getIntProperty(argName + i); 
                    args[i] = new Integer(temp); 
                } 
                else if (type.equals("long")) 
                { 
                    long temp = msg.getLongProperty(argName + i); 
                    args[i] = new Long(temp); 
                } 
                else if (type.equals("float")) 
                { 
                    float temp = msg.getFloatProperty(argName + i); 
                    args[i] = new Float(temp); 
                } 
                else if (type.equals("double")) 
                { 
                    double temp = msg.getDoubleProperty(argName + i); 
                    args[i] = new Double(temp); 
                } 
                else if (type.equals("short")) 
                { 
                    short temp = msg.getShortProperty(argName + i); 
                    args[i] = new Short(temp); 
                } 
                else if (type.equals("byte")) 
                { 
                    byte temp = msg.getByteProperty(argName + i); 
                    args[i] = new Float(temp); 
                } 
                else if (type.equals("object")) 
                { 
                    Object obj = msg.getObjectProperty(argName + i); 
                    args[i] = obj; 
                } 
                else if (type.equals("Message")) 
                { 
                    args[i] = (javax.jms.Message)msg;
                } 
                // You could add other types here...Object,etc... 
                else 
                    System.out.println("Unknown parameter type"); 
            } 
        } 
        return args; 
    } 

    /** 
     * If a JMS provider detects a serious problem with a 
     * Connection this method is invoked passing it a JMSException 
     * describing the problem. 
     *
     * @param JMSException e
     */
    public void onException (JMSException e)
    {
	    //Report the Error and take necessary Error handling measures
        String error = e.getErrorCode ();
	    System.out.println (error);
    }
}