package examples.xml.biztalk;

import java.io.*;
import java.rmi.RemoteException;
import java.util.Date;
import java.util.Hashtable;
import java.util.Vector;
import javax.ejb.CreateException;
import javax.jms.*;
import javax.jts.*;
import javax.naming.*;
import org.w3c.dom.*;
import examples.xml.biztalk.ejb.*;
import weblogic.jndi.Environment;
import weblogic.xml.dom.*;

/**
 * BizServer pulls messages off of the BizTalk queue, constructs
 * a DOMIncomingMessage 
 * and dispatches it to registered BizBeans. Within the 
 * <font face="Courier New" size = -1>waitForMessages</font> method, 
 * the QueueReceiver does an explicit receive. This means that 
 * this is a singled-threaded implementation, i.e. the thread running the server 
 * will pause until a message is received. To make this a mutli-threaded application, 
 * you would have to implement the JMS MessageListener interface. See 
 * WebLogic JMS documentation for more information.
 * <P>
 * A BizServer can be run using the examples.xml.biztalk.AdminCLU utility. 
 *
 * @author Copyright (c) 2000 by BEA Systems, Inc. All Rights Reserved.
 */

public class BizServer {
  public final static String JNDI_FACTORY= "weblogic.jndi.WLInitialContextFactory";
  public final static String JMS_FACTORY= "javax.jms.QueueConnectionFactory";
  private QueueConnectionFactory qconFactory;
  private QueueConnection qconSender;
  private QueueSession qsessionSender;
  private QueueSender qsender;
  private Queue outqueue=null;
  private TextMessage outmsg;
  private QueueConnection qconReceiver;
  private QueueSession qsessionReceiver;
  private QueueReceiver qreceiver;
  private Queue inqueue=null;
  private TextMessage inmsg;
  private Context context;
  
  private static BizBeanHome ejbHome;
  private PrintWriter log;
  private BizConfig config;
  private boolean verbose = true;
  boolean quit = false;
  private String logname;

  public static void main(String args[]) throws Exception {
    if (args.length != 1) {
      System.err.println("Usage: examples.xml.biztalk.BizServer <servername>");
      System.exit(0);
    }

    BizServer s = new BizServer(args[0]);
    while (!s.quit) {
      s.waitForMessages();
    }
    s.log.println("Exiting");
    if (s.inqueue != null) s.closeInQ();
    if (s.outqueue != null) s.closeOutQ();
    s.log.close();
  }

  public PrintWriter getLog() {return log;}

  /*
   * Constructs a BizServer object associated with the named configuration.
   * Config information for this server is located in the configuration
   * file, <I>name</I>.xml, where <I>name</I> is the constructor's parameter.
   *
   * @param name of this server. Used to locate configuration and log file.
   */
  public BizServer(String name) {
    try {
      context = (new Environment()).getInitialContext();
    }
    catch (NamingException namee) {
      System.err.println("Unable to establish JNDI context.");
      quit = true;
      return;
    }
    config = new BizConfig();
    verbose = config.getVerbose();
    //
    // open log
    //
    String separator = System.getProperty("file.separator");
    logname = config.getBizHome()+separator+name+".log";
    try {
      log = new PrintWriter( new FileOutputStream(logname, true), true);
      log.println("Starting Bizserver: "+name);
    }
    catch (IOException ioe) {
      System.err.println("Unable to open log file, "+logname+". Using System.err");
      log = new PrintWriter(System.err, true);
      logname = "stderr";
    }
    config.setLog(log);
    //
    // load config file
    //
    Date date = new Date();
    if (verbose) log.println(date.toString()+" Starting...");
    if (!config.loadConfig(name)) {
      log.println("Invalid config file, "+config.getConfigFile());
      quit = true;
      return;
    }
    //
    // create queues
    //
    try {
      initInQ(config.getInQ());
      initOutQ(config.getErrorQ());
    }
    catch (NamingException namee) {
      log.println("Unable to establish context.");
      quit = true;
      return;
    }
    catch (JMSException jmse) {
      log.println("Unable to create JMS connection.");
      quit = true;
      return;
    }
  }
  /**
   * Sets up the output queue.
   *
   * @param outQ Output queue name 
   *
   * @exception NamingException unable to establish context
   * @exception JMSException unable to setup JMS queue
   */

  private void initOutQ(String outQ) 
                 throws NamingException, JMSException {
    initSender(context, outQ);
  }
  /**
   * Sets up the input queue.
   *
   * @param inQ Input queue name 
   * @param outQ Output queue name 
   *
   * @exception NamingException unable to establish context
   * @exception JMSException unable to setup JMS queue
   */

  private void initInQ(String inQ) 
                 throws NamingException, JMSException {
    initReceiver(context, inQ);
  }



   private void initSender(Context ctx, String queueName)
                  throws NamingException, JMSException
  {
    // Look up the ConnectionFactory
    qconFactory = (QueueConnectionFactory)ctx.lookup(JMS_FACTORY);
    
    // Get a QueueConnection from the 
    // QueueConnectionFactory
    qconSender = qconFactory.createQueueConnection();
    
    // Get a QueueSession that is not transacted
    // and acknowledges automatically 
    qsessionSender = qconSender.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
    
    // Look up the Queue
    outqueue = (Queue) ctx.lookup(queueName);
    
    // Create a message producer (QueueSender)
    // for the Queue we looked up. 
    qsender = qsessionSender.createSender(outqueue);
    
    // Create a message object 
    outmsg = qsessionSender.createTextMessage();
    
    // Start the connection
    qconSender.start();
  }
  /**
   * Send a message to the queue. Use the QueueSender's default delivery mode, timeToLive and priority.
   *
   * @param the message to be sent
   * @exception JMSException if JMS fails to send the message due to some internal error.
   * @exception MessageFormatException if invalid message specified
   * @exception InvalidDestinationException if a client uses this method with a Queue sender with an invalid queue.
   */
  private void send(String message) throws JMSException
  {
    outmsg.setText(message);
    qsender.send(outmsg);
  }
  /**
   * Send a message to the queue. Use the QueueSender's default delivery mode, timeToLive and priority.
   *
   * @param the message to be sent
   * @exception JMSException if JMS fails to send the message due to some internal error.
   * @exception MessageFormatException if invalid message specified
   * @exception InvalidDestinationException if a client uses this method with a Queue sender with an invalid queue.
   */
  private void send(Message message) throws JMSException
  {
    if (verbose) log.println("sending");
    qsender.send(message);
  }

  /**
   * Close out queue connections
   *
   * @exception JMSException if JMS fails to close out due to some error.
   */
  private void closeOutQ()
       throws JMSException
  {
    try {
      qsender.close();
      qsessionSender.close();
      qconSender.close();
    }
    catch (JMSException jmse) {}
  }
  /**
   * Close out queue connections
   *
   * @exception JMSException if JMS fails to close out due to some error.
   */
  private void closeInQ()
       throws JMSException
  {
    try {
      qreceiver.close();
      qsessionReceiver.close();
      qconReceiver.close();
    }
    catch (JMSException jmse) {}
  }
 

  private void initReceiver(Context ctx, String queueName)
       throws NamingException, JMSException
  {
    // Look up the ConnectionFactory
    qconFactory = (QueueConnectionFactory)ctx.lookup(JMS_FACTORY);
    qconReceiver = qconFactory.createQueueConnection();
    qsessionReceiver = qconReceiver.createQueueSession(false,Session.AUTO_ACKNOWLEDGE);
    try {
      inqueue = (Queue) ctx.lookup(queueName);
    } catch (NamingException ne) {
      inqueue = qsessionReceiver.createQueue(queueName);
      ctx.bind(queueName, inqueue);
    }
    if (verbose) log.println("Connected to "+inqueue.toString());
    qreceiver = qsessionReceiver.createReceiver(inqueue);
    qconReceiver.start();
  }

  /**
   * Wait for a message to arrive. The message
   * is validated as a Biztalk document, parsed into a 
   * DOMIncomingMessage object and passed to registered beans. 
   *
   */
  public void waitForMessages() {
    String txt;
    boolean route = false;
    Message message;
    try {
      message = qreceiver.receive();
    }
    catch (JMSException jmse) {
      log.println(jmse.getMessage());
      return;
    }
    if (verbose) log.println("Message received.");
    try {
      if (message instanceof TextMessage) {
        txt = ((TextMessage)message).getText();
      }
      else {
        txt = message.toString();
      }
    }
    catch (JMSException jmse) {
      log.println("Unable to retrieve text from message.");
      routeIt(message);
      return;
    }
    if (verbose) log.println("JMS message text: "+txt);
    try {
      if (verbose) log.println ("Parsing message...");
      IncomingMessage im = new DOMIncomingMessage(txt);
      //
      // obtain beans assoc with this doctype and service each one.
      // Route to other server as nec.
      //
      String myUri = config.getURI();
      String toAddr = im.getToAddress();
      String docType = im.getType();
      if (verbose) log.println("Document type: "+docType);
      //
      // check for admin message first
      //
      if (docType.equals(BizConfig.BIZ_ADMIN)) {
        if (verbose) {
	  log.println("Config message received from admin."); 
          reconfig();
          return;
	}
      }
      if (docType.equals(BizConfig.BIZ_STOP)) {
        if (verbose) {
	  log.println("Shutdown message received from admin."); 
	  shutdown();
	  return;
	}
      }
      route = false; // true -> error handling message
      if (myUri != null) {
	if (toAddr.equals(myUri)) {
	  //
	  // This is the target Bizserver. Service all beans
	  //
	  Vector beans = config.getBizBeansForDoc(docType);
	  for (int j=0;j<beans.size();j++) {
	    // service this bean
            BizBeanHome beanHome = (BizBeanHome) beans.elementAt(j);
	    try {
              if (verbose) log.println("Invoking bean: "+
		                   (beanHome.getClass()).getName());
	      BizBean bean = beanHome.create();
	      bean.service(im);
	    }
	    catch (CreateException e) {
	      log.println("Unable to invoke bean: "+
		                   beanHome.getClass().getName());
	      route = true;
	    }
	  }
	}
	else {
	  log.println("to address ("+toAddr+") does not match this server's URI ("+myUri+")");
	  route = true; // wrong location
	}
      }
    } catch (BizException bize) {
      log.println("BizException: "+bize.getMessage());
      route = true;
    }
    catch (Exception e){
      e.printStackTrace(log);
    }

    finally {
      if (verbose) log.println("finally");
      if (route) {
        routeIt(message);
      }
    } 
  }

  private void routeIt(Message msg) {
    if (verbose) log.println("routing");
    try {
      send(msg);
    }
    catch (JMSException jmse) {
      log.println("Unable to queue to error queue, "+config.getErrorQ());
      jmse.printStackTrace(log);
    }
  }

  private void reconfig() {
    try {
      config.reloadConfig();
    }
    catch (Exception e) {
      e.printStackTrace(log);
    }
  }

  private void shutdown() {
    log.println("Shutting down.");
    quit = true;
  }
}

