package examples.xml.biztalk;

import java.io.*;
import java.util.*;
import javax.naming.*;
import weblogic.jndi.Environment;
import examples.xml.biztalk.ejb.BizBeanHome;
import weblogic.xml.dom.*;
import org.w3c.dom.*;


/**
 * The BizConfig class is used to access the config parameters for 
 * the BizTalk server.
 *
 * @author Copyright (c) 2000 by BEA Systems, Inc. All Rights Reserved.
 */
public class BizConfig {
  public final static String JNDI_FACTORY = 
      "weblogic.jndi.WLInitialContextFactory";
  public final static String JMS_FACTORY = "javax.jms.QueueConnectionFactory";
  public final static String IN_QUEUE1 = "biztalk.jms.";
  public final static String IN_QUEUE2 = "Incoming";
  public final static String ERROR_QUEUE1 = "biztalk.jms.";
  public final static String ERROR_QUEUE2 = "Error";
  public final static String BIZ_ADMIN = "Bizconfig";
  public final static String BIZ_STOP = "Bizstop";
  //
  // config info variables
  //
  private boolean verbose = true;
  private static Vector bizServers = new Vector(1,1);
  private boolean loaded = false;
  private String name;
  private String configFile;
  private String inQ;
  private String errorQ;
  private String URI;
  private Hashtable mappings;
  private Vector bizBeans;
  private String bizHome;
  String separator = System.getProperty("file.separator");
  private PrintWriter log;

  /**
   * Constructor
   */
  public BizConfig() {
    bizHome = System.getProperty("user.dir");
    log = new PrintWriter(System.out, true);
  }
  /**
   * Establish log file to be used by this object. 
   *
   * @param ps output class
   */
  public void setLog(PrintWriter ps) {
    log = ps;
  }

  /**
   * The BizConfig loader reads the config file for the named BizServer
   * and stores the info into this object. The list of loaded servers is 
   * maintained in the class and accessible via the <tt>getBizServers</tt>
   * method.
   * <P>
   * The home directory used by a Bizserver is the user's current directory.
   * From there the server is configured from <server-name>.xml
   * <P>
   * The queues are "biztalk.jms.<I>name</I>.Incoming" and 
   * "biztalk.jms.<I>name</I>.Error". Note that BizConfig only fully-qualifies the
   * queue names. Actual setup of queues is the caller's responsibility.
   * <P>
   * All other config info comes from "<I>home</I>/<I>name</I>.xml which has
   * the following 'schema':
   * <P>
   * <PRE>
   * &lt;?xml version=1.0&gt;
   * &lt;Bizconfig&gt;
   *   name = "a name"
   *   verbose = "true"|"false"
   *   uri="home address"
   *   &lt;Bean&gt; bean.name&lt;/Bean&gt;         (0..n)
   *   &lt;Mapping&gt;                       (0..n)
   *     doc="a doctype"
   *	 bean="a bean"
   *   &lt;/Mapping&gt;
   * &lt;/Bizconfig&gt;
   * </PRE>
   * <P>
   * loadConfig reconfigures from scratch. i.e. a complete XML config
   * file must be provided. Prior configuration is lost.
   *
   * @param bizName name of server to configure.
   * @return true if successful, false if failed.
   */
  
  public boolean loadConfig(String bizName) {
    loaded = false;
    if (verbose) log.println("loading config: "+bizName);

    setName(bizName);
    setConfigFile();
    setInQ();
    setErrorQ();
    mappings = new Hashtable();
    bizBeans = new Vector();

    //
    // parse the config file filling in the important stuff
    //
    try {
      InputStream is = new FileInputStream(getConfigFile());
      Reader r = new InputStreamReader(is);
      DOMParser parser = DOMParser.getDOMParser("com.sun.xml.parser.Parser");
      Document d = parser.getDocument(r);
      Node node = d.getDocumentElement();
      if (loadConfigDOM(node)) {
        bizServers.addElement(this); // so other objects can find config
        loaded = true;
      }
    }
    catch (IOException ioe) {
      log.println("Unable to read config file, "+getConfigFile());
    }
    catch (DOMParserException dome) {
      log.println("Unable to parse config file, "+getConfigFile());
      log.println(dome.getMessage());
    }
    return loaded;
  }
  /**
   * reloadConfig reconfigures from an already configured Bizserver.
   *
   * @return true if successful, false if failed.
   */
  
  public boolean reloadConfig() {
    loaded = false;
    if (verbose) log.println("reloading config: "+name);

    mappings = new Hashtable();
    bizBeans = new Vector();

    //
    // parse the config file filling in the important stuff
    //
    try {
      InputStream is = new FileInputStream(getConfigFile());
      Reader r = new InputStreamReader(is);
      DOMParser parser = DOMParser.getDOMParser("com.sun.xml.parser.Parser");
      Document d = parser.getDocument(r);
      Node node = d.getDocumentElement();
      if (loadConfigDOM(node)) {
        bizServers.addElement(this); // so other objects can find config
        loaded = true;
      }
    }
    catch (IOException ioe) {
      log.println("Unable to read config file, "+getConfigFile());
    }
    catch (DOMParserException dome) {
      log.println("Unable to parse config file, "+getConfigFile());
      log.println(dome.getMessage());
    }
    return loaded;
  }
  //
  // load config info from a DOM
  //
  private boolean loadConfigDOM(Node node) {
    //
    // walk the tree. This code knows what the tree should look
    // like and expects it to be valid.
    //
    if (node.getNodeName().equals(BIZ_ADMIN)) {
      log.println("Node: "+node.getNodeName());
      NamedNodeMap nm = node.getAttributes();
      if (nm != null) {
        int items = nm.getLength();
        for (int i=0; i<items; i++) {
	  String attrName = nm.item(i).getNodeName();
	  String attrVal = nm.item(i).getNodeValue();
	  log.println("Attr: "+attrName+"="+attrVal);
          if (attrName.equals("name")) {}
	  if (attrName.equals("verbose")) {
	    if (attrVal.equals("true")) {verbose = true;}
	    if (attrVal.equals("false")) {verbose = false;}
	  }
	  if (attrName.equals("uri")) {setURI(attrVal);}
	}
      }
      else { 
        return false; 
      }
    }
    else { 
      return false; 
    }
    //
    // now process mappings
    //
    NodeList nl = node.getChildNodes();
    int children = nl.getLength();
    for (int i=0; i<children; i++) {
      Node nd = nl.item(i);
      log.println("Node: "+nd.getNodeName());
      if (nd.getNodeName().equals("Bean")) {
	//
	// handle beans
	//
	Node nc = nd.getFirstChild();
	log.println("Bean name: "+nc.getNodeValue());
	addBizBean(nc.getNodeValue());
      }
      if (nd.getNodeName().equals("Mapping")) {
	//
	// handle mappings
	//
        NamedNodeMap nm = nd.getAttributes();
        if (nm != null) {
          int items = nm.getLength();
	  String doc = "";
	  String bean = "";
	  boolean gotdoc = false;
	  boolean gotbean = false;
          for (int j=0; j<items; j++) {
	    String attrName = nm.item(j).getNodeName();
	    String attrVal = nm.item(j).getNodeValue();
	    log.println("Attr: "+attrName+"="+attrVal);
            if (attrName.equals("doc")) {doc = attrVal; gotdoc = true;}
	    if (attrName.equals("bean")) {bean = attrVal; gotbean = true;}
	    if (gotdoc && gotbean) {
	      addMapping(doc, bean);
	      gotdoc = false;
	      gotbean = false;
	    }
	  }
	  if (gotdoc ^ gotbean) {
	    return false;
	  }
        }
      }
    }
    return true;
  }

  /**
   * Returns the home directory for this server
   *
   * @return String containing home directory
   */
  public String getBizHome() {
    return bizHome;
  }

  /**
   * Returns the names of loaded Biztalk Servers
   *
   * @return Vector containing the names of nstantiated Biztalk Servers.
   */
  public Vector getBizServers() {
    return bizServers;
  }

  /**
   * Returns a BizConfig object.
   *
   * @param bizName Name of the Biztalk Server
   *
   * @return BizConfig object representing a particular Biztalk Server. 
   *         Returns null if the named server is not loaded.
   */
   
  public static BizConfig getConfig(String bizName) {
    for (int i=0; i<bizServers.size(); i++) {
      BizConfig bc = (BizConfig) bizServers.elementAt(i);
      if (bizName.equals(bc.getName())) return bc;
    }
    return null;
  }

  /**
   * prints the info in BizConfig object to an output stream
   */
  public void printConfig() {
    log.println("Name: "+ getName());
    log.println("Home: "+ bizHome);
    log.println("XML: "+ getConfigFile());
    log.println("In Queue: "+ getInQ());
    log.println("Error Queue: "+ getErrorQ());
    if (!loaded) { // skip the rest if load failed
      return;
    }
    log.println("URI:"+getURI());
    log.println("Beans:");
    Vector bn = getBizBeans();
    for (int i=0;i<bn.size();i++) {
      log.println("   "+bn.elementAt(i));
    }
    log.println("Doc Type Mappings:");
    for (Enumeration e = mappings.keys() ; e.hasMoreElements() ;) {
      String dt = (String)e.nextElement();
      Vector v = getBizBeansForDoc(dt);
      log.println("   Doc Type: "+dt);
      for (int i=0;i<v.size();i++) {
	log.println("      Bean: "+bn.elementAt(i).toString());
      }
    }
  }

  //
  // accessors
  //
  private void setName(String nm) {
    name = new String(nm);
  }
  private void setConfigFile() {
    configFile = new String (bizHome + separator + name + ".xml");
  }
  private void setInQ() {
    inQ = new String(IN_QUEUE1+getName()+IN_QUEUE2);
  }
  private void setErrorQ () {
    errorQ = new String(ERROR_QUEUE1+getName()+ERROR_QUEUE2);
  }
  private void setURI(String uri) {
    URI = uri;
  }
  private void addMapping(String docType, String beanName) {
    //
    // ensure the name maps to an actual bean
    //
    try {
      Context context = (new Environment()).getInitialContext();

      BizBeanHome bean = (BizBeanHome) context.lookup(beanName);

      //
      // check if docType is already a key and if so add the
      // bean to its entry.
      //
      Vector beans = (Vector)mappings.get(docType);
      if (beans == null) {
        beans = new Vector(1,1);
      }
      beans.addElement(bean);
      mappings.put(docType,beans);
    }
    catch (NamingException e) {
      log.println(beanName+" not registered. Will not be mapped to "+docType+ " documents.");
    }
  }

  private void addBizBean(String bean) {
    bizBeans.addElement(bean);
  }

  /**
   * Returns a Vector of registered EJB names (String objects). 
   * These are the beans that can be mapped to specific Biztalk document types. 
   *
   * @return Vector of String objects naming registered EJBs.
   *         Returns null if BizConfig object has not been loaded.
   * 
   */
  public Vector getBizBeans() {
    if (loaded) return bizBeans;
    return null;
  }

  /**
   * Returns name of Biztalk server represented in this BizConfig object.
   *
   * @return Name of Biztalk server.
   *         Returns null if BizConfig object has not been loaded.
   * 
   */
  public String getName() {
    return name;
  }
  /**
   * Returns debug level
   *
   * @return True if debugging.
   * 
   */
  public boolean getVerbose() {
    return verbose;
  }
  /**
   * Returns name of Biztalk server config file used to configure this BizConfig object.
   *
   * @return Name of Biztalk server config file.
   *         Returns null if BizConfig object has not been loaded.
   * 
   */
  public String getConfigFile() {
    return configFile;
  }
  /**
   * Returns name of JMS incoming queue name used by this Biztalk server.
   *
   * @return Name of Biztalk server incoming queue.
   *         Returns null if BizConfig object has not been loaded.
   * 
   */
  public String getInQ() {
    if (loaded) return inQ;
    return null;
  }
  /**
   * Returns name of JMS error queue name used by this Biztalk server. All
   * documents received by the Biztalk Server that can not be processed
   * are placed on this queue. Failures result from invalid document syntax
   * or internal errors.
   *
   * @return Name of Biztalk server error queue
   *         Returns null if BizConfig object has not been loaded.
   * 
   */
  public String getErrorQ() {
    if (loaded) return errorQ;
    return null;
  }
  /**
   * Returns  URI that this Biztalk server recognizes as its own. 
   * Documents received with <TT>To</TT> elements that do not match 
   * this URI will be routed to the error queue
   *
   * @return recognized URI.
   *         Returns null if BizConfig object has not been loaded.
   * 
   */
  public String getURI() {
    if (loaded) return URI;
    return null;
  }
  /**
   * Returns configured mapping between document types and EJBs. The
   * Hashtable returned uses Biztalk document types for the keys. The value
   * for each key is a Vector of EJB Class objects. The mapping is established
   * via the Biztalk server configuration file.
   *
   * @return Hashtable representing mapping between doc type and EJBs.
   *         Returns null if BizConfig object has not been loaded.
   * 
   */
  public Hashtable getMappings() {
    if (loaded) return mappings;
    return null;
  }
  /**
   * Returns list of EJB Class objects for the named document type.
   *
   * @param docType String identifying a Biztalk document type. This
   *                document type is identified in the first element of a Biztalk 
   *                document following the <TT>\&lt;Body/&gt;</TT> element.
   *
   * @return Vector containing EJB Class objects.
   *         Returns null if BizConfig object has not been loaded.
   */
  public Vector getBizBeansForDoc(String docType) {
    if (loaded) return (Vector)mappings.get(docType);
    return null;
  }
}
