This appendix shows the complete CrnpClient.java application that is discussed in more detail in Chapter 12, Cluster Reconfiguration Notification Protocol.
/* * 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; }