Sun Cluster Data Services Developer's Guide for Solaris OS

Appendix G CrnpClient.java Application

This appendix shows the complete CrnpClient.java application that is discussed in more detail in Chapter 12, Cluster Reconfiguration Notification Protocol.

Contents of CrnpClient.java

/*
 * CrnpClient.java
 * ================
 *
 * Note regarding XML parsing:
 *
 * This program uses the Sun Java Architecture for XML Processing (JAXP) API.
 * See http://java.sun.com/xml/jaxp/index.html for API documentation and
 * availability information.
 *
 * This program was written for Java 1.3.1 or higher.
 *
 * Program overview:
 * 
 * The main thread of the program creates a CrnpClient object, waits for the
 * user to terminate the demo, then calls shutdown on the CrnpClient object
 * and exits the program.
 *
 * The CrnpClient constructor creates an EventReceptionThread object,
 * opens a connection to the CRNP server (using the host and port specified
 * on the command line), constructs a registration message (based on the
 * command-line specifications), sends the registartion message, and reads
 * and parses the reply.
 *
 * The EventReceptionThread creates a listening socket bound to
 * the hostname of the machine on which this program runs, and the port
 * specified on the command line. It waits for an incoming event callback,
 * at which point it constructs an XML Document from the incoming socket
 * stream, which is then passed back to the CrnpClient object to process.
 *
 * The shutdown method in the CrnpClient just sends an unregistration
 * (REMOVE_CLIENT) SC_CALLBACK_REG message to the crnp server.
 *
 * Note regarding error handling: for the sake of brevity, this program just
 * exits on most errors.  Obviously, a real application would attempt to handle
 * some errors in various ways, such as retrying when appropriate.
 */

// JAXP packages
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import org.w3c.dom.*;

// standard packages
import java.net.*;
import java.io.*;
import java.util.*;

/*
 * class CrnpClient
 * -----------------
 * See file header comments above.
 */
class CrnpClient
{
 /*
  * main
  * ----
  * The entry point of the execution, main simply verifies the
  * number of command-line arguments, and constructs an instance
  * of a CrnpClient to do all the work.
  */
 public static void main(String []args)
 {
   InetAddress regIp = null;
   int regPort = 0, localPort = 0;

   /* Verify the number of command-line arguments */
   if (args.length < 4) {
      System.out.println(
         "Usage: java CrnpClient crnpHost crnpPort "
         + "localPort (-ac | -ae | -re) "
         + "[(M | A | RG=name | R=name) [...]]");
      System.exit(1);
   }


   /*
    * We expect the command line to contain the ip/port of the
    * crnp server, the local port on which we should listen, and
    * arguments specifying the type of registration.
    */
   try {
      regIp = InetAddress.getByName(args[0]);
      regPort = (new Integer(args[1])).intValue();
      localPort = (new Integer(args[2])).intValue();
   } catch (UnknownHostException e) {
      System.out.println(e);
      System.exit(1);
   }

   // Create the CrnpClient
   CrnpClient client = new CrnpClient(regIp, regPort, localPort,
      args);

   // Now wait until the user wants to end the program
   System.out.println("Hit return to terminate demo...");

   // read will block until the user enters something
   try {
      System.in.read();
   } catch (IOException e) {
      System.out.println(e.toString());
   }

   // shutdown the client
   client.shutdown();
   System.exit(0);
}

/*
 * ======================
 * public methods
 * ======================
 */

 /*
  * CrnpClient constructor
  * -----------------------
  * Parses the command line arguments so we know how to contact
  * the crnp server, creates the event reception thread, and starts it
  * running, creates the XML DocumentBuilderFactory obect, and, finally,
  * registers for callbacks with the crnp server.
  */
 public CrnpClient(InetAddress regIpIn, int regPortIn, int localPortIn,
      String []clArgs)
{
   try {

   regIp = regIpIn;
      regPort = regPortIn;
      localPort = localPortIn;
      regs = clArgs;

     /*
      * Setup the document builder factory for
      * xml processing.
      */
      setupXmlProcessing();

     /*
      * Create the EventReceptionThread, which creates a 
      * ServerSocket and binds it to a local ip and port.
      */
      createEvtRecepThr();

     /*
      * Register with the crnp server.
      */
      registerCallbacks();

    } catch (Exception e) {
      System.out.println(e.toString());
      System.exit(1);
    }
 }

/*
 * processEvent
 * ---------------
 * Callback into the CrnpClient, used by the EventReceptionThread
 * when it receives event callbacks.
 */
 public void processEvent(Event event)
 {
  /*
   * For demonstration purposes, simply print the event
   * to System.out.  A real application would obviously make
   * use of the event in some way.
   */
  event.print(System.out);
 }

 /*
  * shutdown
  * -------------
  * Unregister from the CRNP server.
  */
 public void shutdown()
 {
  try {
   /* send an unregistration message to the server */
   unregister();
  } catch (Exception e) {
   System.out.println(e);
   System.exit(1);
  }
 }


 /*
  * ======================
  * private helper methods
  * ======================
  */

 /*
  * setupXmlProcessing
  * --------------------
  * Create the document builder factory for
  * parsing the xml replies and events.
  */
 private void setupXmlProcessing() throws Exception
 {
  dbf = DocumentBuilderFactory.newInstance();

  // We don't need to bother validating
  dbf.setValidating(false);
  dbf.setExpandEntityReferences(false);

  // We want to ignore comments and whitespace
  dbf.setIgnoringComments(true);
  dbf.setIgnoringElementContentWhitespace(true);

  // Coalesce CDATA sections into TEXT nodes.
  dbf.setCoalescing(true);
 }

 /*
  * createEvtRecepThr
  * -------------------
  * Creates a new EventReceptionThread object, saves the ip
  * and port to which its listening socket is bound, and
  * starts the thread running.
  */
 private void createEvtRecepThr() throws Exception
 {
  /* create the thread object */
  evtThr = new EventReceptionThread(this);

  /*
   * Now start the thread running to begin listening
   * for event delivery callbacks.
   */
  evtThr.start();
 }


 /*
  * registerCallbacks
  * ------------------
  * Creates a socket connection to the crnp server and sends
  * an event registration message.
  */
 private void registerCallbacks() throws Exception
 {
  System.out.println("About to register");

  /*
   * Create a socket connected to the registration ip/port
   * of the crnp server and send the registration information.
   */
  Socket sock = new Socket(regIp, regPort);
  String xmlStr = createRegistrationString();
  PrintStream ps = new PrintStream(sock.getOutputStream());
  ps.print(xmlStr);

  /*
   * Read the reply
   */
  readRegistrationReply(sock.getInputStream());
  
  /*
   * Close the socket connection.
   */
  sock.close();
 }

 /*
  * unregister
  * ----------
  * As in registerCallbacks, we create a socket connection to
  * the crnp server, send the unregistration message, wait for
  * the reply from the server, then close the socket.
  */
 private void unregister() throws Exception
 {
  System.out.println("About to unregister");

  /*
   * Create a socket connected to the registration ip/port
   * of the crnp server and send the unregistration information.
   */
  Socket sock = new Socket(regIp, regPort);
  String xmlStr = createUnregistrationString();
  PrintStream ps = new PrintStream(sock.getOutputStream());
  ps.print(xmlStr);

  /*
   * Read the reply
   */
  readRegistrationReply(sock.getInputStream());
  
  /*
   * Close the socket connection.
   */
  sock.close();
 }

 /*
  * createRegistrationString
  * ------------------
  * Constructs a CallbackReg object based on the command line arguments
  * to this program, then retrieves the XML string from the CallbackReg
  * object.
  */
 private String createRegistrationString() throws Exception
 {
  /*
   * create the actual CallbackReg class and set the port.
   */
  CallbackReg cbReg = new CallbackReg();
  cbReg.setPort("" + localPort);

  // set the registration type
  if (regs[3].equals("-ac")) {
   cbReg.setRegType(CallbackReg.ADD_CLIENT);
  } else if (regs[3].equals("-ae")) {
   cbReg.setRegType(CallbackReg.ADD_EVENTS);
  } else if (regs[3].equals("-re")) {
   cbReg.setRegType(CallbackReg.REMOVE_EVENTS);
  } else {
   System.out.println("Invalid reg type: " + regs[3]);
   System.exit(1);
  }

  // add the events
  for (int i = 4; i < regs.length; i++) {
   if (regs[i].equals("M")) {
    cbReg.addRegEvent(createMembershipEvent());
   } else if (regs[i].equals("A")) {
    cbReg.addRegEvent(createAllEvent());
   } else if (regs[i].substring(0,2).equals("RG")) {
    cbReg.addRegEvent(createRgEvent(regs[i].substring(3)));
   } else if (regs[i].substring(0,1).equals("R")) {
    cbReg.addRegEvent(createREvent(regs[i].substring(2)));
   }
  }

  String xmlStr = cbReg.convertToXml();
  System.out.println(xmlStr);
  return (xmlStr);
 }

 /*
  * createAllEvent
  * ----------------
  * Creates an XML registartion event with class EC_Cluster, and no
  * subclass.
  */
 private Event createAllEvent()
 {
  Event allEvent = new Event();
  allEvent.setClass("EC_Cluster");
  return (allEvent);
 }

 /*
  * createMembershipEvent
  * ----------------------
  * Creates an XML registration event with class EC_Cluster, subclass
  * ESC_cluster_memberhip.
  */
 private Event createMembershipEvent()
 {
  Event membershipEvent = new Event();
  membershipEvent.setClass("EC_Cluster");
  membershipEvent.setSubclass("ESC_cluster_membership");
  return (membershipEvent);
 }

 /*
  * createRgEvent
  * ----------------
  * Creates an XML registration event with class EC_Cluster,
  * subclass ESC_cluster_rg_state, and one "rg_name" nvpair (based
  * on input parameter).
  */
 private Event createRgEvent(String rgname)
 {
  /*
   * Create a Resource Group state change event for the
   * rgname Resource Group.  Note that we supply
   * a name/value pair (nvpair) for this event type, to
   * specify in which Resource Group we are interested.
   */
  /*
   * Construct the event object and set the class and subclass.
   */
  Event rgStateEvent = new Event();
  rgStateEvent.setClass("EC_Cluster");
  rgStateEvent.setSubclass("ESC_cluster_rg_state");

  /*
   * Create the nvpair object and add it to the Event.
   */
  NVPair rgNvpair = new NVPair();
  rgNvpair.setName("rg_name");
  rgNvpair.setValue(rgname);
  rgStateEvent.addNvpair(rgNvpair);

  return (rgStateEvent);
 }

 /*
  * createREvent
  * ----------------
  * Creates an XML registration event with class EC_Cluster,
  * subclass ESC_cluster_r_state, and one "r_name" nvpair (based
  * on input parameter).
  */
 private Event createREvent(String rname)
 {
  /*
   * Create a Resource state change event for the
   * rgname Resource.  Note that we supply
   * a name/value pair (nvpair) for this event type, to
   * specify in which Resource Group we are interested.
   */
  Event rStateEvent = new Event();
  rStateEvent.setClass("EC_Cluster");
  rStateEvent.setSubclass("ESC_cluster_r_state");

  NVPair rNvpair = new NVPair();
  rNvpair.setName("r_name");
  rNvpair.setValue(rname);
  rStateEvent.addNvpair(rNvpair);

  return (rStateEvent);
 }

 /*
  * createUnregistrationString
  * ------------------
  * Constructs a REMOVE_CLIENT CallbackReg object, then retrieves
  * the XML string from the CallbackReg object.
  */
 private String createUnregistrationString() throws Exception
 {
  /*
   * Crate the CallbackReg object.
   */
  CallbackReg cbReg = new CallbackReg();
  cbReg.setPort("" + localPort);
  cbReg.setRegType(CallbackReg.REMOVE_CLIENT);

  /*
   * we marshall the registration to the OutputStream
   */
  String xmlStr = cbReg.convertToXml();

  // Print the string for debugging purposes
  System.out.println(xmlStr);
  return (xmlStr);
 }


 /*
  * readRegistrationReply
  * ------------------------
  * Parse the xml into a Document, construct a RegReply object
  * from the document, and print the RegReply object.  Note that
  * a real application would take action based on the status_code
  * of the RegReply object.
  */
 private void readRegistrationReply(InputStream stream)
     throws Exception
 {
  // Create the document builder
  DocumentBuilder db = dbf.newDocumentBuilder();
 
  //
  // Set an ErrorHandler before parsing
  // Use the default handler.
  //
  db.setErrorHandler(new DefaultHandler());

  //parse the input file
  Document doc = db.parse(stream);

  RegReply reply = new RegReply(doc);
  reply.print(System.out);
 }

 /* private member variables */
 private InetAddress regIp;
 private int regPort;
 private EventReceptionThread evtThr;
 private String regs[];

 /* public member variables */
 public int localPort;
 public DocumentBuilderFactory dbf;
}

/*
 * class EventReceptionThread
 * ----------------------------
 * See file header comments above.
 */
class EventReceptionThread extends Thread
{
 /*
  * EventReceptionThread constructor
  * ----------------------------------
  * Creates a new ServerSocket, bound to the local hostname and
  * a wildcard port.
  */
 public EventReceptionThread(CrnpClient clientIn) throws IOException
 {
  /*
   * keep a reference to the client so we can call it back
   * when we get an event.
   */
  client = clientIn;

  /*
   * Specify the IP to which we should bind.  It's
   * simply the local host ip.  If there is more
   * than one public interface configured on this
   * machine, we'll go with whichever one
   * InetAddress.getLocalHost comes up with.
   *
   */
  listeningSock = new ServerSocket(client.localPort, 50,
      InetAddress.getLocalHost());
      System.out.println(listeningSock);
 }

 /*
  * run
  * ---
  * Called by the Thread.Start method.
  *
  * Loops forever, waiting for incoming connections on the ServerSocket.
  *
  * As each incoming connection is accepted, an Event object
  * is created from the xml stream, which is then passed back to
  * the CrnpClient object for processing.
  */
 public void run()
 {
  /*
   * Loop forever.
   */
  try {
   //
   // Create the document builder using the document
   // builder factory in the CrnpClient.
   //
   DocumentBuilder db = client.dbf.newDocumentBuilder();

   //
   // Set an ErrorHandler before parsing
   // Use the default handler.
   //
   db.setErrorHandler(new DefaultHandler());

   while(true) {
    /* wait for a callback from the server */
    Socket sock = listeningSock.accept();

    // parse the input file
    Document doc = db.parse(sock.getInputStream());

    Event event = new Event(doc);
    client.processEvent(event);

    /* close the socket */
    sock.close();
   }
   // UNREACHABLE

  } catch (Exception e) {
   System.out.println(e);
   System.exit(1);
  }
 }

 /* private member variables */
 private ServerSocket listeningSock;
 private CrnpClient client;
}

/*
 * class NVPair
 * -----------
 * This class stores a name/value pair (both Strings).  It knows how to
 * construct an NVPAIR XML message from its members, and how to parse
 * an NVPAIR XML Element into its members.
 * 
 * Note that the formal specification of an NVPAIR allows for multiple values.
 * We make the simplifying assumption of only one value.
 */
class NVPair
{
 /*
  * Two constructors: the first creates an empty NVPair, the second
  * creates an NVPair from an NVPAIR XML Element.
  */
 public NVPair()
 {
  name = value = null;
 }

 public NVPair(Element elem)
 {
  retrieveValues(elem);
 }

 /*
  * Public setters.
  */
 public void setName(String nameIn)
 {
  name = nameIn;
 }

 public void setValue(String valueIn)
 {
  value = valueIn;
 }

 /*
  * Prints the name and value on a single line.
  */
 public void print(PrintStream out)
 {
  out.println("NAME=" + name + " VALUE=" + value);
 }

 /*
  * createXmlElement
  * ------------------
  * Constructs an NVPAIR XML Element from the member variables.
  * Takes the Document as a parameter so that it can create the
  * Element.
  */
 public Element createXmlElement(Document doc)
 {
  // Create the element.
  Element nvpair = (Element)
      doc.createElement("NVPAIR");
  //
  // Add the name.  Note that the actual name is
  // a separate CDATA section.
  //
  Element eName = doc.createElement("NAME");
  Node nameData = doc.createCDATASection(name);
  eName.appendChild(nameData);
  nvpair.appendChild(eName);
  //
  // Add the value.  Note that the actual value is
  // a separate CDATA section.
  //
  Element eValue = doc.createElement("VALUE");
  Node valueData = doc.createCDATASection(value);
  eValue.appendChild(valueData);
  nvpair.appendChild(eValue);

  return (nvpair);
 }

 /*
  * retrieveValues
  * ----------------
  * Parse the XML Element to retrieve the name and value.
  */
 private void retrieveValues(Element elem)
 {
  Node n;
  NodeList nl;

  //
  // Find the NAME element
  //
  nl = elem.getElementsByTagName("NAME");
  if (nl.getLength() != 1) {
   System.out.println("Error in parsing: can't find "
       + "NAME node.");
   return;
  }
  
  //
  // Get the TEXT section
  //
  n = nl.item(0).getFirstChild();
  if (n == null || n.getNodeType() != Node.TEXT_NODE) {
   System.out.println("Error in parsing: can't find "
       + "TEXT section.");
   return;
  }

  // Retrieve the value
  name = n.getNodeValue();

  //
  // Now get the value element
  //
  nl = elem.getElementsByTagName("VALUE");
  if (nl.getLength() != 1) {
   System.out.println("Error in parsing: can't find "
       + "VALUE node.");
   return;
  }
  
  //
  // Get the TEXT section
  //
  n = nl.item(0).getFirstChild();
  if (n == null || n.getNodeType() != Node.TEXT_NODE) {
   System.out.println("Error in parsing: can't find "
       + "TEXT section.");
   return;
  }

  // Retrieve the value
  value = n.getNodeValue();
 }


 /*
  * Public accessors
  */
 public String getName()
 {
  return (name);
 }

 public String getValue()
 {
  return (value);
 }

 // Private member vars
 private String name, value;
}


/*
 * class Event
 * -----------
 * This class stores an event, which consists of a class, subclass, vendor,
 * publisher, and list of name/value pairs.  It knows how to
 * construct an SC_EVENT_REG XML Element from its members, and how to parse
 * an SC_EVENT XML Element into its members.  Note that there is an assymetry
 * here: we parse SC_EVENT elements, but construct SC_EVENT_REG elements.
 * That is because SC_EVENT_REG elements are used in registration messages
 * (which we must construct), while SC_EVENT elements are used in event
 * deliveries (which we must parse).  The only difference is that SC_EVENT_REG
 * elements don't have a vendor or publisher.
 */
class Event
{

 /*
  * Two constructors: the first creates an empty Event; the second
  * creates an Event from an SC_EVENT XML Document.
  */
 public Event()
 {
  regClass = regSubclass = null;
  nvpairs = new Vector();
 }

 public Event(Document doc)
 {

  nvpairs = new Vector();

  //
  // Convert the document to a string to print for debugging
  // purposes.
  //
  DOMSource domSource = new DOMSource(doc);
  StringWriter strWrite = new StringWriter();
  StreamResult streamResult = new StreamResult(strWrite);
  TransformerFactory tf = TransformerFactory.newInstance();
  try {
   Transformer transformer = tf.newTransformer();
   transformer.transform(domSource, streamResult);
  } catch (TransformerException e) {
   System.out.println(e.toString());
   return;
  }
  System.out.println(strWrite.toString());

  // Do the actual parsing.
  retrieveValues(doc);
 }

 /*
  * Public setters.
  */
 public void setClass(String classIn)
 {
  regClass = classIn;
 }

 public void setSubclass(String subclassIn)
 {
  regSubclass = subclassIn;
 }

 public void addNvpair(NVPair nvpair)
 {
  nvpairs.add(nvpair);
 }

 /*
  * createXmlElement
  * ------------------
  * Constructs an SC_EVENT_REG XML Element from the member variables.
  * Takes the Document as a parameter so that it can create the
  * Element.  Relies on the NVPair createXmlElement ability.
  */
 public Element createXmlElement(Document doc)
 {
  Element event = (Element)
      doc.createElement("SC_EVENT_REG");
  event.setAttribute("CLASS", regClass);
  if (regSubclass != null) {
   event.setAttribute("SUBCLASS", regSubclass);
  }
  for (int i = 0; i < nvpairs.size(); i++) {
   NVPair tempNv = (NVPair)
       (nvpairs.elementAt(i));
   event.appendChild(tempNv.createXmlElement(doc));
  }
  return (event);
 }

 /*
  * Prints the member vars on multiple lines.
  */
 public void print(PrintStream out)
 {
  out.println("\tCLASS=" + regClass);
  out.println("\tSUBCLASS=" + regSubclass);
  out.println("\tVENDOR=" + vendor);
  out.println("\tPUBLISHER=" + publisher);
  for (int i = 0; i < nvpairs.size(); i++) {
   NVPair tempNv = (NVPair)
       (nvpairs.elementAt(i));
   out.print("\t\t");
   tempNv.print(out);
  }
 }

  /*
  * retrieveValues
  * ----------------
  * Parse the XML Document to retrieve the class, subclass, vendor,
  * publisher, and nvpairs.
  */
 private void retrieveValues(Document doc)
 {
  Node n;
  NodeList nl;

  //
  // Find the SC_EVENT element.
  //
  nl = doc.getElementsByTagName("SC_EVENT");
  if (nl.getLength() != 1) {
   System.out.println("Error in parsing: can't find "
       + "SC_EVENT node.");
   return;
  }

  n = nl.item(0);

  //
  // Retrieve the values of the CLASS, SUBCLASS,
  // VENDOR and PUBLISHER attributes.
  //
  regClass = ((Element)n).getAttribute("CLASS");
  regSubclass = ((Element)n).getAttribute("SUBCLASS");
  publisher = ((Element)n).getAttribute("PUBLISHER");
  vendor = ((Element)n).getAttribute("VENDOR");

  //
  // Retrieve all the nv pairs
  //
  for (Node child = n.getFirstChild(); child != null;
       child = child.getNextSibling())
  {
   nvpairs.add(new NVPair((Element)child));
  }
 }

 /*
  * Public accessor methods.
  */
 public String getRegClass()
 {
  return (regClass);
 }

 public String getSubclass()
 {
  return (regSubclass);
 }

 public String getVendor()
 {
  return (vendor);
 }

 public String getPublisher()
 {
  return (publisher);
 }

 public Vector getNvpairs()
 {
  return (nvpairs);
 }

 // Private member vars.
 private String regClass, regSubclass;
 private Vector nvpairs;
 private String vendor, publisher;
}


/*
 * class CallbackReg
 * -----------
 * This class stores a port and regType (both Strings), and a list of Events.
 * It knows how to construct an SC_CALLBACK_REG XML message from its members.
 * 
 * Note that this class does not need to be able to parse SC_CALLBACK_REG
 * messages, because only the CRNP server must parse SC_CALLBACK_REG
 * messages.
 */
class CallbackReg
{
 // Useful defines for the setRegType method
 public static final int ADD_CLIENT = 0;
 public static final int ADD_EVENTS = 1;
 public static final int REMOVE_EVENTS = 2;
 public static final int REMOVE_CLIENT = 3;

 public CallbackReg()
 {
  port = null;
  regType = null;
  regEvents = new Vector();
 }

 /*
  * Public setters.
  */
 public void setPort(String portIn)
 {
  port = portIn;
 }

 public void setRegType(int regTypeIn)
 {
  switch (regTypeIn) {
  case ADD_CLIENT:
   regType = "ADD_CLIENT";
   break;
  case ADD_EVENTS:
   regType = "ADD_EVENTS";
   break;
  case REMOVE_CLIENT:
   regType = "REMOVE_CLIENT";
   break;
  case REMOVE_EVENTS:
   regType = "REMOVE_EVENTS";
   break;
  default:
   System.out.println("Error, invalid regType " +
       regTypeIn);
   regType = "ADD_CLIENT";
   break;
  }
 }

 public void addRegEvent(Event regEvent)
 {
  regEvents.add(regEvent);
 } 


 /*
  * convertToXml
  * ------------------
  * Constructs an SC_CALLBACK_REG XML Document from the member
  * variables.  Relies on the Event createXmlElement ability.
  */
 public String convertToXml()
 {
  Document document = null;
  DocumentBuilderFactory factory =
      DocumentBuilderFactory.newInstance();
  try {
   DocumentBuilder builder = factory.newDocumentBuilder();
   document = builder.newDocument(); 
  } catch (ParserConfigurationException pce) {
   // Parser with specified options can't be built
   pce.printStackTrace();
   System.exit(1);
  }
  Element root = (Element) document.createElement("SC_CALLBACK_REG");
  root.setAttribute("VERSION", "1.0");
  root.setAttribute("PORT", port);
  root.setAttribute("REG_TYPE", regType);
  for (int i = 0; i < regEvents.size(); i++) {
   Event tempEvent = (Event)
       (regEvents.elementAt(i));
   root.appendChild(tempEvent.createXmlElement(document));
  }
  document.appendChild(root);

  //
  // Now convert the document to a string.
  //
  DOMSource domSource = new DOMSource(document);
  StringWriter strWrite = new StringWriter();
  StreamResult streamResult = new StreamResult(strWrite);
  TransformerFactory tf = TransformerFactory.newInstance();
  try {
   Transformer transformer = tf.newTransformer();
   transformer.transform(domSource, streamResult);
  } catch (TransformerException e) {
   System.out.println(e.toString());
   return ("");
  }
  return (strWrite.toString());
 }

 // private member vars
 private String port;
 private String regType;
 private Vector regEvents;
}

/*
 * class RegReply
 * -----------
 * This class stores a status_code and status_msg (both Strings).
 * It knows how to parse an SC_REPLY XML Element into its members.
 */
class RegReply
{

 /*
  * The only constructor takes an XML Document and parses it.
  */
 public RegReply(Document doc)
 {
  //
  // Now convert the document to a string.
  //
  DOMSource domSource = new DOMSource(doc);
  StringWriter strWrite = new StringWriter();
  StreamResult streamResult = new StreamResult(strWrite);
  TransformerFactory tf = TransformerFactory.newInstance();
  try {
   Transformer transformer = tf.newTransformer();
   transformer.transform(domSource, streamResult);
  } catch (TransformerException e) {
   System.out.println(e.toString());
   return;
  }
  System.out.println(strWrite.toString());

  retrieveValues(doc);
 }

 /*
  * Public accessors
  */
 public String getStatusCode()
 {
  return (statusCode);
 }

 public String getStatusMsg()
 {
  return (statusMsg);
 }

 /*
  * Prints the info on a single line.
  */
 public void print(PrintStream out)
 {
  out.println(statusCode + ": " +
      (statusMsg != null ? statusMsg : ""));
 }

  /*
  * retrieveValues
  * ----------------
  * Parse the XML Document to retrieve the statusCode and statusMsg.
  */
 private void retrieveValues(Document doc)
 {
  Node n;
  NodeList nl;

  //
  // Find the SC_REPLY element.
  //
  nl = doc.getElementsByTagName("SC_REPLY");
  if (nl.getLength() != 1) {
   System.out.println("Error in parsing: can't find "
       + "SC_REPLY node.");
   return;
  }

  n = nl.item(0);

  // Retrieve the value of the STATUS_CODE attribute
  statusCode = ((Element)n).getAttribute("STATUS_CODE");

  //
  // Find the SC_STATUS_MSG element
  //
  nl = ((Element)n).getElementsByTagName("SC_STATUS_MSG");
  if (nl.getLength() != 1) {
   System.out.println("Error in parsing: can't find "
       + "SC_STATUS_MSG node.");
   return;
  }
  
  //
  // Get the TEXT section, if there is one.
  //
  n = nl.item(0).getFirstChild();
  if (n == null || n.getNodeType() != Node.TEXT_NODE) {
   // Not an error if there isn't one, so we
   // just silently return.
   return;
  }

  // Retrieve the value
  statusMsg = n.getNodeValue();
 }

 // private member vars
 private String statusCode;
 private String statusMsg;
}