JavaScript is required to for searching.
Skip Navigation Links
Exit Print View
Oracle Solaris Cluster Data Services Developer's Guide     Oracle Solaris Cluster
search filter icon
search icon

Document Information

Preface

1.  Overview of Resource Management

2.  Developing a Data Service

3.  Resource Management API Reference

4.  Modifying a Resource Type

5.  Sample Data Service

6.  Data Service Development Library

7.  Designing Resource Types

8.  Sample DSDL Resource Type Implementation

9.  Solaris Cluster Agent Builder

10.  Generic Data Service

11.  DSDL API Functions

12.  Cluster Reconfiguration Notification Protocol

A.  Sample Data Service Code Listings

B.  DSDL Sample Resource Type Code Listings

C.  Requirements for Non-Cluster Aware Applications

D.  Document Type Definitions for the CRNP

E.  CrnpClient.java Application

Contents of CrnpClient.java

Index

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/webservices/jaxp/ 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;
}