Sun Cluster 3.1 10/03: Guía del desarrollador de los servicios de datos

Apéndice G Aplicación CrnpClient.java

Este apéndice muestra la aplicación CrnpClient.java completa, que se explica con más detalle en el Capítulo 12.

Contenido de CrnpClient.java

/*
 * CrnpClient.java
 * ================
 *
 * Nota sobre el análisis de XML:
 *
 * Este programa usa la arquitectura Java de Sun para las API de procesos de XML (JAXP).
 * Consulte http://java.sun.com/xml/jaxp/index.html para ver documentación sobre API y
 * obtener información sobre la disponibilidad.
 *
 * Este programa se ha escrito para Java 1.3.1 o superior.
 *
 * Información general del programa:
 *
 * El subproceso principal del programa crea un objeto CrnpClient, espera que el
 * usuario termine la demostración e invoca el cierre en el objeto CrnpClient
 * y sale del programa.
 *
 * El constructor de CrnpClient crea un objeto EventReceptionThread, abre una
 * conexión con el servidor CRNP (con el sistema y puerto especificados en la líne
 * a de comandos), construye un mensaje de registro (basado en las especificaciones
 * de la línea de comandos), envía el mensaje de registro y lee y analiza la respuesta.
 *
 * EventReceptionThread crea un zócalo de recepción vinculado al nombre de sistema
 * de la máquina en la que se ejecuta el programa y el puerto especificado en la línea
 * de comandos. Espera una rellamada de evento entrante y, cuando la recibe, construye
 * un documento XML desde el canal del zócalo de entrada, que se devuelve al objeto
 * CrnpClient para ser procesado.
 *
 * El método de apagado de CrnpClient se limita a enviar un mensaje de cancelación
 * de registro (REMOVE_CLIENT) SC_CALLBACK_REG al servidor crnp.
 *
 * Nota sobre el manejo de errores: en pro de la brevedad, este programa suele salir
 * cuando se producen la mayoría de los errores. Obviamente, una aplicación real
 * intentaría manejar algunos errores de formas diversas, por ejemplo, mediante
  * reintentos cuando corresponda.
 */

// 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.*;

/*
 * clase CrnpClient
 * -----------------
 * Consulte los comentarios de cabecera de archivos anteriores.
 */
class CrnpClient
{
	/*
	 * main
	 * ----
	 * El punto de entrada de la ejecución main, sólo verifica
	 * el número de argumentos de línea de comandos y construye
	 * una instancia de CrnpClient para que realice todo el trabajo.
	 */
	public static void main(String []args)
	{
		InetAddress regIp = null;
		int regPort = 0, localPort = 0;

		/* Verificar el número de argumentos de línea de comandos */
		if (args.length < 4) {
			System.out.println(
			    "Sintaxis: java CrnpClient sistemacrnp puertocrnp "
			    + "puertoLocal (-ac | -ae | -re) "
			    + "[(M | A | RG=nombre | R=nombre) [...]]");
			System.exit(1);
		}


		/*
		 * Se espera que la línea de comandos contenga la IP/el puerto del
		 * servidor crnp, el puerto local en el que se debe recibir y
		 * argumentos que especifiquen el tipo de registro.
		 */
		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);
		}

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

		// Esperar hasta que el usuario desee terminar el programa
		System.out.println("Pulsar Retorno para terminar la demostración...");

		// la lectura se bloquea hasta que el usuario escriba algo
		try {
			System.in.read();
		} catch (IOException e) {
			System.out.println(e.toString());
		}

		// apagar el cliente
		client.shutdown();
		System.exit(0);
	}

	/*
	 * ======================
	 * métodos públicos
	 * ======================
	 */

	/*
	 * Constructor CrnpClient
	 * -----------------------
	 * Analiza los argumentos de línea de comandos para saber cómo contactar con
	 * el servidor crnp, crea el subproceso de recepción de eventos y lo pone
	 * en ejecución, crea el objeto XML DocumentBuilderFactory y, finalmente, se
	 * registra para recibir rellamadas en el servidor crnp.
	 */
	public CrnpClient(InetAddress regIpIn, int regPortIn, int localPortIn,
	    String []clArgs)
	{
		try {

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

			/*
			 * Configurar el creador de documentos para
			 * el proceso de xml.
			 */
			setupXmlProcessing();

			/*
			 * Crear EventReceptionThread, que crea
			 * ServerSocket y lo vincula a un puerto e ip local.
			 */
			createEvtRecepThr();

			/*
			 * Registrar en el servidor crnp.
			 */
			registerCallbacks();

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

	/*
	 * processEvent
	 * ---------------
	 * Rellamada a CrnpClient, utilizada por EventReceptionThread
	 * cuando recibe rellamadas de eventos.
	 */
	public void processEvent(Event event)
	{
		/*
		 * Con fines demostrativos, imprima el evento en System.out.
		 * Por supuesto, una aplicación real utilizaría el evento
		 * de algún modo.
		 */
		event.print(System.out);
	}

	/*
	 * apagado
	 * -------------
	 * Cancelar el registro del servidor CRNP.
	 */
	public void shutdown()
	{
		try {
			/* Enviar un mensaje de cancelación de registro al servidor */
			unregister();
		} catch (Exception e) {
			System.out.println(e);
			System.exit(1);
		}
	}


	/*
	 * ======================
	 * métodos de ayuda privados
	 * ======================
	 */

	/*
	 * setupXmlProcessing
	 * --------------------
	 * Crear el generador de documentos para
	 * analizar los eventos y respuestas de xml.
	 */
	private void setupXmlProcessing() throws Exception
	{
		dbf = DocumentBuilderFactory.newInstance();

		// No es necesario validar
		dbf.setValidating(false);
		dbf.setExpandEntityReferences(false);

		// Se desea ignorar comentarios y espacios en blanco
		dbf.setIgnoringComments(true);
		dbf.setIgnoringElementContentWhitespace(true);

		// Fusionar secciones CDATA en nodos de TEXT.
		dbf.setCoalescing(true);
	}

	/*
	 * createEvtRecepThr
	 * -------------------
	 * Crea un objeto EventReceptionThread nuevo, guarda el ip
	 * y puerto al que está vinculado el zócalo receptor y
	 * pone el subproceso en ejecución.
	 */
	private void createEvtRecepThr() throws Exception
	{
		/* crear el objeto de subproceso */
		evtThr = new EventReceptionThread(this);

		/*
		 * Ahora, iniciar la ejecución del subproceso para empezar a
		 * recibir rellamadas de entrega de eventos.
		 */
		evtThr.start();
	}


	/*
	 * registerCallbacks
	 * ------------------
	 * Crea una conexión de zócalo al servidor crnp y envía
	 * un mensaje de registro de evento.
	 */
	private void registerCallbacks() throws Exception
	{
		System.out.println("Acerca del registro");

		/*
		 * Crear un zócalo conectado al ip/puerto de registro
		 * del servidor crnp y enviar la información de registro.
		 */
		Socket sock = new Socket(regIp, regPort);
		String xmlStr = createRegistrationString();
		PrintStream ps = new PrintStream(sock.getOutputStream());
		ps.print(xmlStr);

		/*
		 * Leer la respuesta
		 */
		readRegistrationReply(sock.getInputStream());

		/*
		 * Cerrar la conexión de zócalo.
		 */
		sock.close();
	}

	/*
	 * unregister
	 * ----------
	 * Al igual que en registerCallbacks, crear una conexión de zócalo al
	 * servidor crnp, enviar un mensaje de cancelación de registro, esperar
	 * la respuesta del servidor y cerrar el zócalo.
	 */
	private void unregister() throws Exception
	{
		System.out.println("Acerca de la cancelación del registro");

		/*
		 * Crear un zócalo conectado al ip/puerto de registro del
		 * servidor crnp y enviar información de cancelación de registro.
		 */
		Socket sock = new Socket(regIp, regPort);
		String xmlStr = createUnregistrationString();
		PrintStream ps = new PrintStream(sock.getOutputStream());
		ps.print(xmlStr);

		/*
		 * Leer la respuesta
		 */
		readRegistrationReply(sock.getInputStream());

		/*
		 * Cerrar la conexión de zócalo.
		 */
		sock.close();
	}

	/*
	 * createRegistrationString
	 * ------------------
	 * Construye un objeto CallbackReg basado en los argumentos de línea de
	 * comandos de este programa; después, recupera la cadena XML del objeto
	 * CallbackReg.
	 */
	private String createRegistrationString() throws Exception
	{
		/*
		 * Crear la clase real de CallbackReg y establecer el puerto.
		 */
		CallbackReg cbReg = new CallbackReg();
		cbReg.setPort("" + localPort);

		// Establecer el tipo de registro
		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("Tipo de registro no válido: " + regs[3]);
			System.exit(1);
		}

		// Agregar los eventos
		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
	 * ----------------
	 * Crea un evento de registro de XML con clase EC_Cluster, y sin
	 * subclase.
	 */
	private Event createAllEvent()
	{
		Event allEvent = new Event();
		allEvent.setClass("EC_Cluster");
		return (allEvent);
	}

	/*
	 * createMembershipEvent
	 * ----------------------
	 * Crea un evento de registro de XML con EC_Cluster, subclase
	 * ESC_cluster_membership.
	 */
	private Event createMembershipEvent()
	{
		Event membershipEvent = new Event();
		membershipEvent.setClass("EC_Cluster");
		membershipEvent.setSubclass("ESC_cluster_membership");
		return (membershipEvent);
	}

	/*
	 * createRgEvent
	 * ----------------
	 * Crea un evento de registro de XML con clase EC_Cluster,
	 * subclase ESC_cluster_rg_state y un nvpair "rg_name" (basado
	 * en parámetro de entrada).
	 */
	private Event createRgEvent(String rgname)
	{
		/*
		 * Crea un evento de cambio de estado de grupo de recursos para el
		 * grupo de recursos nombre_registro. Observar que se da
		 * un par nombre-valor (nvpair) para este tipo de evento para
		 * especificar el grupo de recursos que interesa.
		 */
		/*
		 * Construir el objeto de evento y fijar la clase y subclase.
		 */
		Event rgStateEvent = new Event();
		rgStateEvent.setClass("EC_Cluster");
		rgStateEvent.setSubclass("ESC_cluster_rg_state");

		/*
		 * Crear el objeto nvpair y agregarlo al evento.
		 */
		NVPair rgNvpair = new NVPair();
		rgNvpair.setName("rg_name");
		rgNvpair.setValue(rgname);
		rgStateEvent.addNvpair(rgNvpair);

		return (rgStateEvent);
	}

	/*
	 * createREvent
	 * ----------------
	 * Crea un evento de registro de XML con clase EC_Cluster,
	 * subclase ESC_cluster_r_state y un nvpair "r_name" (basado
	 * en parámetro de entrada).
	 */
	private Event createREvent(String rname)
	{
		/*
		 * Crea un evento de cambio de estado de recurso para el
		 * recurso nombre_registro. Observar que se indica un par
		 * nombre-valor (nvpair) para este tipo de evento, para
		 * especificar el grupo de recursos que interesa.
		 */
		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
	 * ------------------
	 * Construye un objeto REMOVE_CLIENT CallbackReg, después recupera
	 * la cadena XML del objeto CallbackReg.
	 */
	private String createUnregistrationString() throws Exception
	{
		/*
		 * Crear el objeto CallbackReg.
		 */
		CallbackReg cbReg = new CallbackReg();
		cbReg.setPort("" + localPort);
		cbReg.setRegType(CallbackReg.REMOVE_CLIENT);

		/*
		 * se dirige el registro hacia OutputStream
		 */
		String xmlStr = cbReg.convertToXml();

		// Imprimir la cadena para la depuración
		System.out.println(xmlStr);
		return (xmlStr);
	}


	/*
	 * readRegistrationReply
	 * ------------------------
	 * Analizar el xml en un documento, construir un objeto RegReply
	 * a partir del documento e imprimir el objeto RegReply.  Observar
	 * que una aplicación real actuaría según el status_code
	 * del objeto RegReply.
	 */
	private void readRegistrationReply(InputStream stream)
	    throws Exception
	{
		// Crear el constructor de documento
		DocumentBuilder db = dbf.newDocumentBuilder();

		//
		// Establecer ErrorHandler antes del análisis
		// Utilizar el descriptor predeterminado.
		//
		db.setErrorHandler(new DefaultHandler());

		// Analizar el archivo de entrada
		Document doc = db.parse(stream);

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

	/* variables de miembro privado */
	private InetAddress regIp;
	private int regPort;
	private EventReceptionThread evtThr;
	private String regs[];

	/* variables de miembro público */
	public int localPort;
	public DocumentBuilderFactory dbf;
}

/*
 * clase EventReceptionThread
 * ----------------------------
 * Consultar los comentarios de cabecera de archivo de antes.
 */
class EventReceptionThread extends Thread
{
	/*
	 * EventReceptionThread constructor
	 * ----------------------------------
	 * Crea un ServerSocket nuevo, vinculado al nombre de sistema local y
	 * un puerto comodín.
	 */
	public EventReceptionThread(CrnpClient clientIn) throws IOException
	{
		/*
		 * mantener una referencia al cliente para que se pueda rellamar
		 * al recibir un evento.
		 */
		client = clientIn;

		/*
		 * Especificar la IP al que se debería establecer el vínculo.
		 * Es sencillamente el ip del sistema local. Si hay más de una
		 * interfaz pública configurada en esta máquina, se irá a
		 * cualquiera que indique InetAddress.getLocalHost.
		 *
		 */
		listeningSock = new ServerSocket(client.localPort, 50,
		    InetAddress.getLocalHost());
    		System.out.println(listeningSock);
	}

	/*
	 * run
	 * ---
	 * Invocado por el método Thread.Start.
	 *
	 * Realiza un bucle infinito, a la espera de conexiones de entrada
	 * de ServerSocket.
	 *
	 * A medida que se acepta cada conexión entrante, se crea un objeto de
	 * de evento del canal de xml, que luego se pasa al objeto
	 * CrnpClient para su proceso.
	 */
	public void run()
	{
		/*
		 * Bucle infinito.
		 */
		try {
			//
			// Crea el generador de documento con el
			// generador de documentos de CrnpClient.
			//
			DocumentBuilder db = client.dbf.newDocumentBuilder();

			//
			// Establecer un ErrorHandler antes de analizar
			// Utilizar el descriptor predeterminado.
			//
			db.setErrorHandler(new DefaultHandler());

			while(true) {
				/* Esperar rellamada del servidor */
				Socket sock = listeningSock.accept();

				// Analizar el archivo de entrada
				Document doc = db.parse(sock.getInputStream());

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

				/* Cerrar el zócalo */
				sock.close();
			}
			// INACCESIBLE

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

	/* variables de miembro privado */
	private ServerSocket listeningSock;
	private CrnpClient client;
}

/*
 * clase NVPair
 * -----------
 * Esta clase guarda un par de nombre-valor (ambas cadenas). Sabe como crear
 * un mensaje XML NVPAIR desde sus miembros y cómo analizar un elemento
 * XML NVPAIR en sus miembros.
 *
 * Observar que la especificación de formato de un NVPAIR permite varios
 * valores. Se ha realizado la simplificación a un solo valor.
 */
class NVPair
{
	/*
	 * Dos constructores: el primero crea un NVPair vacío; el segundo
	 * crea un NVPair a partir de un elemento XML NVPAIR.
	 */
	public NVPair()
	{
		name = value = null;
	}

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

	/*
	 * Configuradores públicos.
	 */
	public void setName(String nameIn)
	{
		name = nameIn;
	}

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

	/*
	 * Imprime el nombre y valor en una sola línea.
	 */
	public void print(PrintStream out)
	{
		out.println("NAME=" + name + " VALUE=" + value);
	}

	/*
	 * createXmlElement
	 * ------------------
	 * Construye un elemento XML NVPAIR a partir de las variables
	 * de miembro. Toma el documento como parámetro para que pueda
	 * crear el elemento.
	 */
	public Element createXmlElement(Document doc)
	{
		// Crear el elemento.
		Element nvpair = (Element)
		    doc.createElement("NVPAIR");
		//
		// Agregar el nombre. Observar que el nombre real es
		// una sección CDATA separada.
		//
		Element eName = doc.createElement("NAME");
		Node nameData = doc.createCDATASection(name);
		eName.appendChild(nameData);
		nvpair.appendChild(eName);
		//
		// Agregar el valor. Observar que el valor real es
		// una sección CDATA separada.
		//
		Element eValue = doc.createElement("VALUE");
		Node valueData = doc.createCDATASection(value);
		eValue.appendChild(valueData);
		nvpair.appendChild(eValue);

		return (nvpair);
	}

	/*
	 * retrieveValues
	 * ----------------
	 * Analizar el elemento XML para recuperar el nombre y valor.
	 */
	private void retrieveValues(Element elem)
	{
		Node n;
		NodeList nl;

		//
		// Buscar el elemento NAME
		//
		nl = elem.getElementsByTagName("NAME");
		if (nl.getLength() != 1) {
			System.out.println("Error al analizar: no se puede encontrar "
			    + "nodo NAME.");
			return;
		}

		//
		// Obtener la sección TEXT
		//
		n = nl.item(0).getFirstChild();
		if (n == null || n.getNodeType() != Node.TEXT_NODE) {
			System.out.println("Error al analizar: no se puede encontrar "
			    + "sección TEXT.");
			return;
		}

		// Recuperar el valor
		name = n.getNodeValue();

		//
		// Ahora, obtener el elemento de valor
		//
		nl = elem.getElementsByTagName("VALUE");
		if (nl.getLength() != 1) {
			System.out.println("Error al analizar: no se puede encontrar "
			    + "nodo VALUE.");
			return;
		}

		//
		// Obtener la sección TEXT
		//
		n = nl.item(0).getFirstChild();
		if (n == null || n.getNodeType() != Node.TEXT_NODE) {
			System.out.println("Error al analizar: no se puede encontrar "
			    + "sección TEXT.");
			return;
		}

		// Recuperar el valor
		value = n.getNodeValue();
	}


	/*
	 * Accesores públicos
	 */
	public String getName()
	{
		return (name);
	}

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

	// Variables de miembro privado
	private String name, value;
}


/*
 * clase Event
 * -----------
 * Esta clase guarda un evento que consta de una clase, subclase, fabricante,
 * editor y lista de pares de nombre-valor. Sabe cómo construir un elemento
 * XML SC_EVENT_REG a partir de sus miembros y cómo analizar un elemento
 * XML SC_EVENT en sus miembros. Observar que existe una asimetría aquí: se
 * analizan los elementos SC_EVENT, pero se construyen elementos SC_EVENT_REG.
 * Esto se debe a que los elementos SC_EVENT_REG se utilizan en los mensajes
 * de registro (que hay que construir), en tanto que los elementos SC_EVENT se
 * utilizan en la entrega de eventos (que hay que analizar). La única diferencia
 * es que los elementos SC_EVENT_REG no tienen fabricante ni editor.
 */
class Event
{

	/*
	 * Dos constructores: el primero crea un evento vacío; el segundo
	 * crea un evento a partir de un documento XML SC_EVENT.
	 */
	public Event()
	{
		regClass = regSubclass = null;
		nvpairs = new Vector();
	}

	public Event(Document doc)
	{

		nvpairs = new Vector();

		//
		// Convertir el documento en una cadena para imprimir con fines
		// de depuración.
		//
		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());

		// Realizar el análisis real.
		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
	 * ------------------
	 * Construye un elemento XMl SC_EVENT_REG a partir de las variables de
	 * los miembros. Toma el documento como parámetro para que pueda crear
	 * el elemento. Depende de la capacidad del NVPair createXmlElement.
	 */
	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);
	}

	/*
	 * Imprime las variables de miembros en varias líneas.
	 */
	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
	 * ----------------
	 * Analizar el documento XML para recuperar la clase, subclase,
	 * fabricante, editor y nvpairs.
	 */
	private void retrieveValues(Document doc)
	{
		Node n;
		NodeList nl;

		//
		// Buscar el elemento SC_EVENT.
		//
		nl = doc.getElementsByTagName("SC_EVENT");
		if (nl.getLength() != 1) {
			System.out.println("Error al analizar: no se puede encontrar"
			    + "nodo SC_EVENT.");
			return;
		}

		n = nl.item(0);

		//
		// Recuperar los valores de los atributos CLASS, SUBCLASS,
		// VENDOR y PUBLISHER.
		//
		regClass = ((Element)n).getAttribute("CLASS");
		regSubclass = ((Element)n).getAttribute("SUBCLASS");
		publisher = ((Element)n).getAttribute("PUBLISHER");
		vendor = ((Element)n).getAttribute("VENDOR");

		//
		// Recuperar todos los pares n-v
		//
		for (Node child = n.getFirstChild(); child != null;
		     child = child.getNextSibling())
		{
			nvpairs.add(new NVPair((Element)child));
		}
	}

	/*
	 * Métodos de accesores públicos.
	 */
	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);
	}

	// Variables de miembro privado.
	private String regClass, regSubclass;
	private Vector nvpairs;
	private String vendor, publisher;
}


/*
 * clase CallbackReg
 * -----------
 * Esta clase guarda un puerto y regType (ambas cadenas) y una lista de
 * eventos. Sabe cómo construir un mensaje XML SC_CALLBACK_REG de sus
 * miembros.
 *
 * Observar que esta clase no necesita poder analizar mensajes
 * SC_CALLBACK_REG porque sólo el servidor CRNP debe analizar mensajes
 * SC_CALLBACK_REG
 *
 */
class CallbackReg
{
	// Definiciones útiles para el método setRegType
	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, regType no válido" +
			    regTypeIn);
			regType = "ADD_CLIENT";
			break;
		}
	}

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


	/*
	 * convertToXml
	 * ------------------
	 * Construye un documento XML SC_CALLBACK_REG a partir de variables
	 * de miembro. Depende de la capacidad del evento createXmlElement.
	 */
	public String convertToXml()
	{
		Document document = null;
		DocumentBuilderFactory factory =
		    DocumentBuilderFactory.newInstance();
		try {
			DocumentBuilder builder = factory.newDocumentBuilder();
			document = builder.newDocument();
		} catch (ParserConfigurationException pce) {
			// No se puede crear el analizador con las opciones especificadas
			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);

		//
		// Ahora, convertir el documento en cadena.
		//
		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());
	}

	// variables de miembro privado
	private String port;
	private String regType;
	private Vector regEvents;
}

/*
 * clase RegReply
 * -----------
 * Esta clase guarda status_code y status_msg (ambas cadenas).
 * Sabe cómo analizar un elemento XML SC_REPLY en sus miembros.
 */
class RegReply
{

	/*
	 * El constructor único toma un documento XML y lo analiza.
	 */
	public RegReply(Document doc)
	{
		//
		// Convertir el documento en cadena.
		//
		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);
	}

	/*
	 *  Accesores públicos
	 */
	public String getStatusCode()
	{
		return (statusCode);
	}

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

	/*
	 * Imprime la información en una sola línea.
	 */
	public void print(PrintStream out)
	{
		out.println(statusCode + ": " +
		    (statusMsg != null ? statusMsg : ""));
	}

 	/*
	 * retrieveValues
	 * ----------------
	 * Analiza el documento XML para recuperar statusCode y statusMsg.
	 */
	private void retrieveValues(Document doc)
	{
		Node n;
		NodeList nl;

		//
		// Buscar el elemento SC_REPLY.
		//
		nl = doc.getElementsByTagName("SC_REPLY");
		if (nl.getLength() != 1) {
			System.out.println("Error al analizar: no se puede encontrar "
			    + "nodo SC_REPLY.");
			return;
		}

		n = nl.item(0);

		// Recuperar el valor del atributo STATUS_CODE
		statusCode = ((Element)n).getAttribute("STATUS_CODE");

		//
		// Buscar el elemento SC_STATUS_MSG
		//
		nl = ((Element)n).getElementsByTagName("SC_STATUS_MSG");
		if (nl.getLength() != 1) {
			System.out.println("Error al analizar: no se puede encontrar"
			    + "nodo SC_STATUS_MSG.");
			return;
		}

		//
		// Obtener la sección TEXT, si la hubiera.
		//
		n = nl.item(0).getFirstChild();
		if (n == null || n.getNodeType() != Node.TEXT_NODE) {
			// Sin error, si no lo hay
			// se retorna discretamente.
			return;
		}

		// Recuperar el valor
		statusMsg = n.getNodeValue();
	}

	// variables de miembro privado
	private String statusCode;
	private String statusMsg;
}