Sun Cluster Data Services Developer's Guide for Solaris OS

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;
}