Go to main content

Developing Data Services

Exit Print View

Updated: August 2018
 
 

CrnpClient.java Application

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

Contents of CrnpClient.java

/*
* CrnpClient.java
* ================
*
* Note regarding XML parsing:
*
* This program uses the Java Architecture for XML Processing (JAXP) API.
* See https://docs.oracle.com/javase/9/
* 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 registration 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 object, 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 registration 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;
}