Guide des développeurs pour les services de données Sun Cluster 3.1 10/03

Annexe G Application CrnpClient.java

Cette annexe montre l'application CrnpClient.java dans son ensemble. Celle-ci est abordée en détails dans le Chapitre 12.

Contenu de CrnpClient.java

/*
 * CrnpClient.java
 * ================
 *
 * Remarque concernant l'analyse XML :
 *
 * Ce programme utilise l'API Sun Java Architecture for XML Processing (JAXP).
 * Reportez-vous à http://java.sun.com/xml/jaxp/index.html pour obtenir de la documentation
 * sur les API ainsi que des informations relatives à leur disponibilité
 * des informations relatives à leur disponibilité.
 *
 * Ce programme a été rédigé pour Java 1.3.1 ou supérieur.
 *
 * Présentation du programme :
 *
 * Le thread principal du programme crée un objet CrnpClient, attend que
 * l'utilisateur mette un terme à la démonstration, puis appelle la fermeture de 
 * l'objet CrnpClient et quitte le programme.
 *
 * Le constructeur de CrnpClient crée un objet EventReceptionThread,
 * ouvre une connexion au serveur CRNP (à l'aide de l'hôte et du port spécifiés
 * dans la ligne de commande), construit un message d'enregistrement (sur la base
 * des spécifications de la ligne de commande), envoie un message d'enregistrement, 
 * puis lit et analyse la réponse.
 *
 * L'objet EventReceptionThread crée un socket d'écoute lié au
 * nom d'hôte de la machine sur laquelle tourne le programme et au port
 * spécifié sur la ligne de commande. Il attend un rappel d'événement entrant,
 * moment auquel il construit un document XML sur la base du flux de socket entrant,
 * qu'il transmet à l'objet CrnpClient pour traitement.
 *
 * La méthode de fermeture de l'objet CrnpClient envoie simplement un message de
 * désenregistrement (REMOVE_CLIENT) SC_CALLBACK_REG au serveur crnp.
 *
 * Remarque concernant la gestion des erreurs : pour être bref, ce programme
 * se ferme purement et simplement dans la plupart des cas. Il est évident qu'une 
 * véritable application tenterait de gérer les erreurs de diverses manières, par exemple
 * en lançant de nouvelles tentatives, le cas échéant.
 */

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

// Packages standard
import java.net.*;
import java.io.*;
import java.util.*;

/*
 * class CrnpClient
 * -----------------
 * Voir les commentaires présentés ci-dessus dans l'en-tête du fichier.
 */
class CrnpClient
{
	/*
	 * main
	 * ----
	 * Point d'entrée de l'exécution, main vérifie surtout
	 * le nombre d'arguments de ligne de commande et construit une instance
	 * d'un CrnpClient chargé d'effectuer toutes les tâches.
	 */
	public static void main(String []args)
	{
		InetAddress regIp = null;
		int regPort = 0, localPort = 0;

		/* Vérifie le nombre d'arguments de ligne de commande */
		if (args.length < 4) {
			System.out.println(
			    "Usage: java CrnpClient Hôte_crnp Port_crnp "
			    + "Port_local (-ac | -ae | -re) "
			    + "[(M | A | RG=nom | R=nom) [...]]");
			System.exit(1);
		}


		/*
		 * Nous nous attendons à ce que la ligne de commande contienne l'adresse IP/le 
     * port du serveur crnp, le port local d'écoute ainsi que des arguments 
		 * indiquant le type d'enregistrement.
		 */
		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);
		}

		// Créez le CrnpClient
		CrnpClient client = new CrnpClient(regIp, regPort, localPort,
		    args);

		// Attendez maintenant que l'utilisateur souhaite terminer le programme
		System.out.println("Hit return to terminate demo...");

		// la lecture sera bloquée jusqu'à ce que l'utilisateur entre quelque chose
		try {
			System.in.read();
		} catch (IOException e) {
			System.out.println(e.toString());
		}

		// fermez le client
		client.shutdown();
		System.exit(0);
	}

	/*
	 * ======================
	 * Méthodes publiques
	 * ======================
	 */

	/*
	 * Constructeur de l'objet CrnpClient
	 * -----------------------
	 * Ce constructeur analyse les arguments de ligne de commande, ce qui nous permet 
	 * de savoir comment contacter le serveur crnp, crée le thread de réception des 
	 * événements et le démarre, crée l'objet XML DocumentBuilderFactory et enfin,
	 * s'enregistre auprès du serveur crnp pour les rappels.
	 */
	public CrnpClient(InetAddress regIpIn, int regPortIn, int localPortIn,
	    String []clArgs)
	{
		try {

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

			/*
			 * Configuration de l'usine du générateur de documents pour
			 * le traitement du format xml.
			 */
			setupXmlProcessing();

			/*
			 * Création de l'objet EventReceptionThread, qui crée un
			 * ServerSocket et le lie à une adresse IP et à un port locaux.
			 */
			createEvtRecepThr();

			/*
			 * Enregistrement auprès du serveur crnp.
			 */
			registerCallbacks();

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

	/*
	 * processEvent
	 * ---------------
	 * Rappel de l'objet CrnpClient, utilisé par EventReceptionThread
	 * quand il reçoit des rappels d'événements.
	 */
	public void processEvent(Event event)
	{
		/*
		 * Pour les besoins de la démonstration, il vous suffit d'imprimer l'événement
		 * sur System.out. Une véritable application utiliserait
		 * l'événement d'une manière ou d'une autre.
		 */
		event.print(System.out);
	}

	/*
	 * shutdown
	 * -------------
	 * Désenregistrement du serveur CRNP.
	 */
	public void shutdown()
	{
		try {
			/* envoi d'un message de désenregistrement au serveur */
			unregister();
		} catch (Exception e) {
			System.out.println(e);
			System.exit(1);
		}
	}


	/*
	 * ======================
	 * Méthodes des assistants privés
	 * ======================
	 */

	/*
	 * setupXmlProcessing
	 * --------------------
	 * Il crée l'usine du générateur de documents permettant
	 * l'analyse des événements et réponses xml.
	 */
	private void setupXmlProcessing() throws Exception
	{
		dbf = DocumentBuilderFactory.newInstance();

		// Il n'est pas nécessaire de valider
		dbf.setValidating(false);
		dbf.setExpandEntityReferences(false);

		// Nous souhaitons ignorer les commentaires et les blancs
		dbf.setIgnoringComments(true);
		dbf.setIgnoringElementContentWhitespace(true);

		// Combinez les sections CDATA et les noeuds TEXT.
		dbf.setCoalescing(true);
	}

	/*
	 * createEvtRecepThr
	 * -------------------
	 * Il crée un nouvel objet EventReceptionThread, enregistre l'adresse IP
	 * et le port auxquels est lié le socket d'écoute et
	 * démarre le thread.
	 */
	private void createEvtRecepThr() throws Exception
	{
		/* Création de l'objet du thread */
		evtThr = new EventReceptionThread(this);

		/*
		 * Démarrage du thread pour lancer l'écoute
		 * des rappels d'envoi d'événements.
		 */
		evtThr.start();
	}


	/*
	 * registerCallbacks
	 * ------------------
	 * Il établit une connexion de socket au serveur crnp et envoie
	 * un message d'enregistrement d'événement.
	 */
	private void registerCallbacks() throws Exception
	{
		System.out.println("About to register");

		/*
		 * Création d'un socket connecté à l'adresse IP/au port d'enregistrement
		 * du serveur crnp et envoi des informations d'enregistrement.
		 */
		Socket sock = new Socket(regIp, regPort);
		String xmlStr = createRegistrationString();
		PrintStream ps = new PrintStream(sock.getOutputStream());
		ps.print(xmlStr);

		/*
		 * Lecture de la réponse
		 */
		readRegistrationReply(sock.getInputStream());

		/*
		 * Fermeture de la connexion de socket
		 */
		sock.close();
	}

	/*
	 * unregister
	 * ----------
	 * Comme dans le cas de registerCallbacks, une connexion de socket est établie avec
	 * le serveur crnp, le message de désenregistrement est envoyé et le système
	 * attend la réponse du serveur, puis ferme le socket.
	 */
	private void unregister() throws Exception
	{
		System.out.println("About to unregister");

		/*
		 * Création d'un socket connecté à l'adresse IP/au port d'enregistrement
		 * du serveur crnp et envoi des informations de désenregistrement.
		 */
		Socket sock = new Socket(regIp, regPort);
		String xmlStr = createUnregistrationString();
		PrintStream ps = new PrintStream(sock.getOutputStream());
		ps.print(xmlStr);

		/*
		 * Lecture de la réponse
		 */
		readRegistrationReply(sock.getInputStream());

		/*
		 * Fermeture de la connexion de socket
		 */
		sock.close();
	}

	/*
	 * createRegistrationString
	 * ------------------
	 * Il construit un objet CallbackReg basé sur les arguments de ligne de commande
	 * de ce programme, puis récupère la chaîne XML depuis l'objet
	 * CallbackReg.
	 */
	private String createRegistrationString() throws Exception
	{
		/*
		 * Création de la classe CallbackReg à proprement parler et définition du port.
		 */
		CallbackReg cbReg = new CallbackReg();
		cbReg.setPort("" + localPort);

		// définissez le type d'enregistrement
		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);
		}

		// ajoutez les événements
		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
	 * ----------------
	 * Il crée un événement d'enregistrement XML avec une classe EC_Cluster et aucune
	 * sous-classe.
	 */
	private Event createAllEvent()
	{
		Event allEvent = new Event();
		allEvent.setClass("EC_Cluster");
		return (allEvent);
	}

	/*
	 * createMembershipEvent
	 * ----------------------
	 * Il crée un événement d'enregistrement XML avec la classe EC_Cluster, sous-classe
	 * ESC_cluster_memberhip.
	 */
	private Event createMembershipEvent()
	{
		Event membershipEvent = new Event();
		membershipEvent.setClass("EC_Cluster");
		membershipEvent.setSubclass("ESC_cluster_membership");
		return (membershipEvent);
	}

	/*
	 * createRgEvent
	 * ----------------
	 * Il crée un événement d'enregistrement XML avec la classe EC_Cluster,
	 * sous-classe ESC_cluster_rg_state, et aucun "nom_rg" nvpair (sur la base
	 * du paramètre d'entrée).
	 */
	private Event createRgEvent(String rgname)
	{
		/*
		 * Création d'un événement de changement d'état pour le
		 * groupe de ressources nom_rg. Remarquez que nous fournissons
		 * un couple nom/valeur (nvpair) pour ce type d'événement, pour
		 * spécifier le groupe de ressources qui nous intéresse.
		 */
		/*
		 * Construction de l'objet d'événement et paramétrage des classes et sous-classes.
		 */
		Event rgStateEvent = new Event();
		rgStateEvent.setClass("EC_Cluster");
		rgStateEvent.setSubclass("ESC_cluster_rg_state");

		/*
		 * Création de l'objet nvpair et ajout de celui-ci à l'événement
		 */
		NVPair rgNvpair = new NVPair();
		rgNvpair.setName("rg_name");
		rgNvpair.setValue(rgname);
		rgStateEvent.addNvpair(rgNvpair);

		return (rgStateEvent);
	}

	/*
	 * createREvent
	 * ----------------
	 * Il crée un événement d'enregistrement XML avec la classe EC_Cluster,
	 * sous-classe ESC_cluster_r_state, et un nvpair "nom-r" (sur la base
	 * du paramètre d'entrée).
	 */
	private Event createREvent(String rname)
	{
		/*
		 * Création d'un événement de changement d'état pour
		 * la ressource nom_rg. Remarquez que nous fournissons
		 * un couple nom/valeur (nvpair) pour ce type d'événement afin
		 * de spécifier le groupe de ressources qui nous intéresse.
		 */
		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
	 * ------------------
	 * Il construit un objet REMOVE_CLIENT CallbackReg, puis récupère
	 * la chaîne XML de l'objet CallbackReg.
	 */
	private String createUnregistrationString() throws Exception
	{
		/*
		 * Création de l'objet CallbackReg.
		 */
		CallbackReg cbReg = new CallbackReg();
		cbReg.setPort("" + localPort);
		cbReg.setRegType(CallbackReg.REMOVE_CLIENT);

		/*
		 * Classement de l'enregistrement dans l'OutputStream
		 */
		String xmlStr = cbReg.convertToXml();

		// Imprimez la chaîne dans un but de débogage
		System.out.println(xmlStr);
		return (xmlStr);
	}


	/*
	 * readRegistrationReply
	 * ------------------------
	 * Il analyse le langage xml dans un document, construit un objet RegReply
	 * à partir du document et imprime l'objet RegReply. Remarquez
	 * qu'une véritable application agirait sur la base du status_code
	 * de l'objet RegReply.
	 */
	private void readRegistrationReply(InputStream stream)
	    throws Exception
	{
		// Créez le constructeur de document
		DocumentBuilder db = dbf.newDocumentBuilder();

		//
		// Définissez un Gestionnaire d'erreur avant l'analyse
		// Utilisez le gestionnaire par défaut.
		//
		db.setErrorHandler(new DefaultHandler());

		// analysez le fichier d'entrée
		Document doc = db.parse(stream);

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

	/* variables des membres privés */
	private InetAddress regIp;
	private int regPort;
	private EventReceptionThread evtThr;
	private String regs[];

	/* variables des membres publics */
	public int localPort;
	public DocumentBuilderFactory dbf;
}

/*
 * class EventReceptionThread
 * ----------------------------
 * Reportez-vous aux commentaires ci-dessus dans l'en-tête du fichier.
 */
class EventReceptionThread extends Thread
{
	/*
	 * Constructeur d'EventReceptionThread
	 * ----------------------------------
	 * Il crée un nouveau ServerSocket, associé au nom d'hôte local et à
	 * un port générique.
	 */
	public EventReceptionThread(CrnpClient clientIn) throws IOException
	{
		/*
		 * Conservation d'une référence au client permettant de le rappeler
		 * à la réception d'un événement.
		 */
		client = clientIn;

		/*
		 * Spécification de l'adresse IP à laquelle s'associer.
		 * Il s'agit simplement de l'IP de l'hôte local. S'il existe plus
		 * d'une interface publique configurée sur cette
		 * machine, nous suivons celle proposée par
		 * InetAddress.getLocalHost.
		 *
		 */
		listeningSock = new ServerSocket(client.localPort, 50,
		    InetAddress.getLocalHost());
    		System.out.println(listeningSock);
	}

	/*
	 * run
	 * ---
	 * Appelé par la méthode Thread.Start.
	 *
	 * Boucle infinie, attendant des connexions entrantes sur le ServerSocket.
	 *
	 * Toutes les connexions entrantes étant acceptées, un objet Event
	 * est créé depuis le flux xml. Il est ensuite transmis à
	 * l'objet CrnpClient pour traitement.
	 */
	public void run()
	{
		/*
		 * Boucle infinie.
		 */
		try {
			//
			// Créez le constructeur de document à l'aide de l'usine
			// du constructeur dans le CrnpClient.
			//
			DocumentBuilder db = client.dbf.newDocumentBuilder();

			//
			// Définissez un Gestionnaire d'erreur avant l'analyse
			// Utilisez le gestionnaire par défaut.
			//
			db.setErrorHandler(new DefaultHandler());

			while(true) {
				/* Attente d'un rappel du serveur */
				Socket sock = listeningSock.accept();

				// analysez le fichier d'entrée
				Document doc = db.parse(sock.getInputStream());

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

				/* Fermeture du socket */
				sock.close();
			}
			// IMPOSSIBLE À ATTEINDRE

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

	/* Variables des membres privés */
	private ServerSocket listeningSock;
	private CrnpClient client;
}

/*
 * classe NVPair
 * -----------
 * Cette classe enregistre une paire nom/valeur (deux chaînes). Elle sait comment
 * construire un message NVPAIR XML depuis ses membres et comment décomposer
 * un élément NVPAIR XML en ses membres.
 *
 * Remarquez que la spécification formelle d'un couple NVPAIR autorise plusieurs valeurs.
 * Pour simplifier, nous nous basons ici sur une seule valeur.
 */
class NVPair
{
	/*
	 * Deux constructeurs : le premier crée un couple NVPair vide, le second
	 * un couple NVPair depuis un élément NVPAIR XML.
	 */
	public NVPair()
	{
		name = value = null;
	}

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

	/*
	 * Réglages publics.
	 */
	public void setName(String nameIn)
	{
		name = nameIn;
	}

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

	/*
	 * Impression du nom et de la valeur sur une seule ligne.
	 */
	public void print(PrintStream out)
	{
		out.println("NAME=" + name + " VALUE=" + value);
	}

	/*
	 * createXmlElement
	 * ------------------
	 * Il construit un élément NVPAIR XML à partir des variables des membres.
	 * Il considère le document comme un paramètre, de manière à pouvoir créer
	 * l'élément.
	 */
	public Element createXmlElement(Document doc)
	{
		// Créez l'élément.
		Element nvpair = (Element)
		    doc.createElement("NVPAIR");
		//
		// Ajoutez le nom. Remarquez que le nom actuel est
		// une section CDATA séparée.
		//
		Element eName = doc.createElement("NAME");
		Node nameData = doc.createCDATASection(name);
		eName.appendChild(nameData);
		nvpair.appendChild(eName);
		//
		// Ajoutez la valeur. Remarquez que la valeur actuelle est
		// une section CDATA séparée.
		//
		Element eValue = doc.createElement("VALUE");
		Node valueData = doc.createCDATASection(value);
		eValue.appendChild(valueData);
		nvpair.appendChild(eValue);

		return (nvpair);
	}

	/*
	 * retrieveValues
	 * ----------------
	 * Il analyse l'élément XML pour récupérer le nom et la valeur.
	 */
	private void retrieveValues(Element elem)
	{
		Node n;
		NodeList nl;

		//
		// Trouvez l'élément NOM
		//
		nl = elem.getElementsByTagName("NAME");
		if (nl.getLength() != 1) {
			System.out.println("Error in parsing: can't find "
			    + "NAME node.");
			return;
		}

		//
		// Obtenez la section TEXT
		//
		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;
		}

		// Récupérez la valeur
		name = n.getNodeValue();

		//
		// Définissez maintenant l'élément de valeur
		//
		nl = elem.getElementsByTagName("VALUE");
		if (nl.getLength() != 1) {
			System.out.println("Error in parsing: can't find "
			    + "VALUE node.");
			return;
		}

		//
		// Définissez la section TEXT
		//
		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;
		}

		// Récupérez la valeur
		value = n.getNodeValue();
	}


	/*
	 * Accès public
	 */
	public String getName()
	{
		return (name);
	}

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

	// Var membre privé
	private String name, value;
}


/*
 * class Event
 * -----------
 * Cette classe enregistre un événement se composant d'une classe, d'une sous-classe, 
 * d'un fournisseur, d'un éditeur et d'une liste de couples nom/valeur. Elle sait
 * comment construire un élément SC_EVENT_REG XML à partir de ses membres et comment 
 * décomposer un élément SC_EVENT XML en ses membres. Remarquez qu'il existe une 
 * asymétrie ici : nous analysons des éléments SC_EVENT, mais construisons des 
 * éléments SC_EVENT_REG. Ceci est dû au fait que les éléments SC_EVENT_REG sont 
 * (que nous devons construire), alors que les éléments SC_EVENT sont utilisés dans
 * utilisés dans des messages d'enregistrement les envois d'événements (que nous 
 * devons analyser). La seule différence réside dans le fait que les éléments SC_EVENT_REG
 * n'ont pas de fournisseurs ou d'éditeurs.
 */
class Event
{

	/*
	 * Deux constructeurs : le premier crée un événement vide. Le second
	 * crée un événement à partir d'un document SC_EVENT XML.
	 */
	public Event()
	{
		regClass = regSubclass = null;
		nvpairs = new Vector();
	}

	public Event(Document doc)
	{

		nvpairs = new Vector();

		//
		// Convertissez le document en une chaîne à imprimer dans un but de
		// débogage.
		//
		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());

		// Procédez à l'analyse actuelle.
		retrieveValues(doc);
	}

	/*
	 * Réglages publics.
	 */
	public void setClass(String classIn)
	{
		regClass = classIn;
	}

	public void setSubclass(String subclassIn)
	{
		regSubclass = subclassIn;
	}

	public void addNvpair(NVPair nvpair)
	{
		nvpairs.add(nvpair);
	}

	/*
	 * createXmlElement
	 * ------------------
	 * Il construit un élément SC_EVENT_REG XML à partir des variables des membres.
	 * Il considère le document comme un paramètre afin de pouvoir créer
	 * l'élément. Il se fonde sur la capacité 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);
	}

	/*
	 * Impression des variables des membres sur plusieurs lignes.
	 */
	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
	 * ----------------
	 * Analyse du document XML pour récupérer la classe, la sous-classe, le fournisseur,
	 * l'éditeur et les couples nvpair.
	 */
	private void retrieveValues(Document doc)
	{
		Node n;
		NodeList nl;

		//
		// Trouvez l'élément SC_EVENT.
		//
		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);

		//
		// Récupérez les valeurs des attributs de CLASS, SUBCLASS,
		// VENDOR et PUBLISHER.
		//
		regClass = ((Element)n).getAttribute("CLASS");
		regSubclass = ((Element)n).getAttribute("SUBCLASS");
		publisher = ((Element)n).getAttribute("PUBLISHER");
		vendor = ((Element)n).getAttribute("VENDOR");

		//
		// Récupérez toutes les paires nv
		//
		for (Node child = n.getFirstChild(); child != null;
		     child = child.getNextSibling())
		{
			nvpairs.add(new NVPair((Element)child));
		}
	}

	/*
	 * Méthodes d'accès public.
	 */
	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);
	}

	// Var membre privé.
	private String regClass, regSubclass;
	private Vector nvpairs;
	private String vendor, publisher;
}


/*
 * classe CallbackReg
 * -----------
 * Cette classe enregistre un port et un regType (deux chaînes) ainsi qu'une liste .
 * des événements. Elle sait comment construire un message SC_CALLBACK_REG XML à partir
 * de ses membres.
 *
 * Remarquez que cette classe ne doit pas être en mesure d'analyser les messages
 * SC_CALLBACK_REG parce que seul le serveur CRNP doit
 * le faire.
 */
class CallbackReg
{
	// Définition utile pour la méthode 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();
	}

	/*
	 * Réglages publics.
	 */
	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
	 * ------------------
	 * Il construit un document SC_CALLBACK_REG XML à partir des variables
	 * des membres. Il se fonde sur la capacité Event createXmlElement.
	 */
	public String convertToXml()
	{
		Document document = null;
		DocumentBuilderFactory factory =
		    DocumentBuilderFactory.newInstance();
		try {
			DocumentBuilder builder = factory.newDocumentBuilder();
			document = builder.newDocument();
		} catch (ParserConfigurationException pce) {
			// L'analyseur avec des options spécifiques ne peut être créé
			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);

		//
		// Convertissez maintenant le document en chaîne.
		//
		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());
	}

	// var membre privé
	private String port;
	private String regType;
	private Vector regEvents;
}

/*
 * class RegReply
 * -----------
 * Cette classe enregistre un code_état et un msg_état (deux chaînes).
 * Elle sait comment décomposer un élément SC_REPLY XML en ses membres.
 */
class RegReply
{

	/*
	 * L'unique constructeur prend un document XML et l'analyse.
	 */
	public RegReply(Document doc)
	{
		//
		// Convertissez maintenant le document en chaîne.
		//
		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);
	}

	/*
	 * Accès public
	 */
	public String getStatusCode()
	{
		return (statusCode);
	}

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

	/*
	 * Impression des informations sur une seule ligne.
	 */
	public void print(PrintStream out)
	{
		out.println(statusCode + ": " +
		    (statusMsg != null ? statusMsg : ""));
	}

 	/*
	 * retrieveValues
	 * ----------------
	 * Analyse des documents XML pour récupérer le Code_état et le Msg_état.
	 */
	private void retrieveValues(Document doc)
	{
		Node n;
		NodeList nl;

		//
		// Trouvez l'élément SC_REPLY.
		//
		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);

		// Récupérez la valeur du STATUS_CODE attribute
		statusCode = ((Element)n).getAttribute("STATUS_CODE");

		//
		// Trouvez l'élément SC_STATUS_MSG
		//
		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;
		}

		//
		// Obtenez la section TEXT, le cas échéant.
		//
		n = nl.item(0).getFirstChild();
		if (n == null || n.getNodeType() != Node.TEXT_NODE) {
			// Aucune erreur s'il n'y en a pas, par conséquent nous
			// revenons simplement sans message.
			return;
		}

		// Récupérez la valeur
		statusMsg = n.getNodeValue();
	}

	// var membre privé
	private String statusCode;
	private String statusMsg;
}