Sun Cluster データサービス開発ガイド (Solaris OS 版)

付録  G CrnpClient.java アプリケーション

この付録では、CrnpClient.java アプリケーションの完全なコードを示します (詳細は、第 12 章「CRNP」を参照)。

CrnpClient.java のコンテンツ

/*
 * CrnpClient.java
 * ================
 *
 * 解析についての注意:
 *
 * このプログラムは、Sun Java Architecture for XML Processing (JAXP) API を
 * 使用しています。API ドキュメントや利用についての情報は、
 * http://java.sun.com/xml/jaxp/index.html を参照して下さい。
 *
 * このプログラムは、Java 1.3.1 以降を対象に作成されています。
 *
 * プログラムの概要:
 * 
 * このプログラムのメインスレッドは、CrnpClient オブジェクトを作成し、
 * ユーザーがデモを終了するのを待機し、CrnpClient オブジェクトで
 * shutdown を呼び出し、最後にプログラムを終了します。
 *
 * CrnpClient コンストラクタは、EventReceptionThread オブジェクトを作成し、
 * (コマンド行で指定されたホストとポートを使用して) CRNP サーバーに対して
 * 接続を開き、(コマンド行の指定にもとづいて) 登録メッセージを作成し、登録
 * メッセージを送信し、応答の読み取りと解析を行います。
 *
 * EventReceptionThread は、このプログラムが動作するマシンのホスト名と
 * コマンド行に指定されるポートにバインドされる待機ソケットを作成します。
 * EventReceptionThread は、イベントコールバックの着信を待機し、受信した
 * ソケットストリームから XML ドキュメントを構築し、これを CrnpClient* 
 * オブジェクトに返して処理を行わせます。
 *
 * CrnpClient 内の shutdown メソッドは、単に登録解除用の
 * (REMOVE_CLIENT) SC_CALLBACK_REG メッセージを crnp サーバーへ送信
 * するだけです。
 *
 * エラー処理についての注意: 説明を簡潔にするため、このプログラムはほとんどの
 * エラーに対して単に終了するだけですが、実際のアプリケーションではさまざまな
 * 方法でエラー処理がなされます (適宜再試行するなど)。
 */

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

// 標準パッケージ
import java.net.*;
import java.io.*;
import java.util.*;

/*
 * クラス CrnpClient
 * -----------------
 * 上記のファイルヘッダーコメントを参照。
 */
class CrnpClient
{
	/*
	 * main
	 * ----
	 * 実行のエントリポイント main は、コマンド行引数の数を
	 * 検証し、すべての作業を行う CrnpClient インスタンスを
	 * 作成する。
	 */
	public static void main(String []args)
	{
		InetAddress regIp = null;
		int regPort = 0, localPort = 0;

		/* コマンド行引数の数を検証する */
		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);
		}


		/*
		 * コマンド行には crnp サーバーの IP とポート、
		 * 待機するローカルポート、登録タイプを示す
		 * 引数が入る。
		 */
		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);
		}

		// CrnpClient を作成する。
		CrnpClient client = new CrnpClient(regIp, regPort, localPort,
		    args);

		// ユーザーがプログラムを終了したくなるまで待機する。
		System.out.println("Hit return to terminate demo...");
		
		// ユーザーが何か入力するまで読み取りはブロックする。
		try {
			System.in.read();
		} catch (IOException e) {
			System.out.println(e.toString());
		}

		// クライアントを停止する。
		client.shutdown();
		System.exit(0);
	}

	/*
	 * ======================
	 * public メソッド
	 * ======================
	 */

	/*
	 * CrnpClient コンストラクタ
	 * -----------------------
	 * crnp サーバーとの通信方法を知るためにコマンド行引数を解析
	 * し、イベント受信スレッドを作成し、このスレッドの実行を開始
	 * し、XML DocumentBuilderFactory オブジェクトを作成し、
	 * 最後に crnp サーバーにコールバックの登録を行う。
	 */
	public CrnpClient(InetAddress regIpIn, int regPortIn, int localPortIn,
	    String []clArgs)
	{
		try {

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

			/*
			 * xml 処理用のドキュメントビルダー
			 * ファクトリを設定する。
			 */
			setupXmlProcessing();

			/*
			 * ServerSocket を作成してこれをローカル IP と
			 *  ポートにバインドする EventReception を作成する。
			 */
			createEvtRecepThr();

			/*
			 * crnp サーバーに登録する。
			 */
			registerCallbacks();

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

	/*
	 * processEvent
	 * ---------------
	 * CrnpClient (イベントコールバックを受信する際に
	 * EventReceptionThread によって使用される) にコールバックする。
	 */
	public void processEvent(Event event)
	{
		/*
		 * ここでは、説明の都合上、単純にイベントを System.out
		 * に出力。実際のアプリケーションでは、通常、イベント
		 * をなんらかの方法で使用する。
		 */		
		event.print(System.out);
	}

	/*
	 * shutdown
	 * -------------
	 * CRNP サーバーに対する登録を解除する。
	 */
	public void shutdown()
	{
		try {
			/* サーバーに登録解除メッセージを送信する */
			unregister();
		} catch (Exception e) {
			System.out.println(e);
			System.exit(1);
		}
	}


	/*
	 * ======================
	 * private ヘルパーメソッド
	 * ======================
	 */

	/*
	 * setupXmlProcessing
	 * --------------------
	 * xml 応答と xml イベントを解析するためにドキュメント
	 * ビルダーファクトリを作成する。
	 */
	private void setupXmlProcessing() throws Exception
	{
		dbf = DocumentBuilderFactory.newInstance();

		// わざわざ検証する必要はない。
		dbf.setValidating(false);
		dbf.setExpandEntityReferences(false);

		// コメントと空白文字は無視したい。
		dbf.setIgnoringComments(true);
		dbf.setIgnoringElementContentWhitespace(true);

		// CDATA セクションを TEXT ノードに結合する。
		dbf.setCoalescing(true);
	}

	/*
	 * createEvtRecepThr
	 * -------------------
	 * 新しい EventReceptionThread オブジェクトを作成し、待機
	 * ソケットがバインドされる IP とポートを保存し、スレッドの
	 * 実行を開始する。
	 */
	private void createEvtRecepThr() throws Exception
	{
		/* スレッドオブジェクトを作成する */
		evtThr = new EventReceptionThread(this);

		/*
		 * イベント配信コールバックを待機し始めるために
		 * スレッドの実行を開始する。
		 */
		evtThr.start();
	}


	/*
	 * registerCallbacks
	 * ------------------
	 * crnp サーバーに対するソケット接続を作成し、
	 * イベント登録メッセージを送信する。
	 */
	private void registerCallbacks() throws Exception
	{
		System.out.println("About to register");

		/*
		 * crnp サーバーの登録 IP / ポートに接続されたソケット
		 * を作成し、登録情報を送信する。
		 */
		Socket sock = new Socket(regIp, regPort);
		String xmlStr = createRegistrationString();
		PrintStream ps = new PrintStream(sock.getOutputStream());
		ps.print(xmlStr);

		/*
		 * 応答を読み取る。
		 */
		readRegistrationReply(sock.getInputStream());
		
		/*
		 * ソケット接続を閉じる。
		 */
		sock.close();
	}

	/*
	 * unregister
	 * ----------
	 * registerCallbacks の場合と同様に、crnp サーバーに対する
	 * ソケット接続を作成し、登録解除メッセージを送信し、
	 * サーバーからの応答を待機し、ソケットを閉じる。
	 */
	private void unregister() throws Exception
	{
		System.out.println("About to unregister");

		/*
		 * crnp サーバーの登録 IP / ポートに接続された
		 * ソケットを作成し、登録解除情報を送信する。
		 */
		Socket sock = new Socket(regIp, regPort);
		String xmlStr = createUnregistrationString();
		PrintStream ps = new PrintStream(sock.getOutputStream());
		ps.print(xmlStr);

		/*
		 * 応答を読み取る。
		 */
		readRegistrationReply(sock.getInputStream());
		
		/*
		 * ソケット接続を閉じる。
		 */
		sock.close();
	}

	/*
	 * createRegistrationString
	 * ------------------
	 * このプログラムのコマンド行引数にもとづいて CallbackReg
	 * オブジェクトを作成し、CallbackReg オブジェクトから XML
	 * 文字列を取得する。
	 */
	private String createRegistrationString() throws Exception
	{
		/*
		 * 実際の CallbackReg クラスを作成し、ポートを設定する。
		 */
		CallbackReg cbReg = new CallbackReg();
		cbReg.setPort("" + localPort);

		// 登録タイプを設定する。
		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);
		}

		// イベントを追加する。
		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
	 * ----------------
	 * クラス EC_Cluster を使用して (サブクラスは使用しない)
	 * XML 登録イベントを作成する。
	 */
	private Event createAllEvent()
	{
		Event allEvent = new Event();
		allEvent.setClass("EC_Cluster");
		return (allEvent);
	}

	/*
	 * createMembershipEvent
	 * ----------------------
	 * クラス EC_Cluster、サブクラス ESC_cluster_memberhip を
	 * 使用して XML 登録イベントを作成する。
	 */
	private Event createMembershipEvent()
	{
		Event membershipEvent = new Event();
		membershipEvent.setClass("EC_Cluster");
		membershipEvent.setSubclass("ESC_cluster_membership");
		return (membershipEvent);
	}

	/*
	 * createRgEvent
	 * ----------------
	 * クラス EC_Cluster、サブクラス ESC_cluster_rg_state、
	 * および "rg_name" nvpair (入力パラメータにもとづく) を
	 * 1 つ使用して XML 登録イベントを作成する。
	 */
	private Event createRgEvent(String rgname)
	{
		/*
		 * rgname リソースグループ用の
		 *  リソースグループ状態変更イベントを作成する。
		 * このイベントタイプには、どのリソースグループに興味
		 * があるのかを示すため、名前/ 値ペア (nvpair) を指定
		 * する。
		 */
		/*
		 * イベントオブジェクトを作成し、クラスとサブクラスを設定する。
		 */
		Event rgStateEvent = new Event();
		rgStateEvent.setClass("EC_Cluster");
		rgStateEvent.setSubclass("ESC_cluster_rg_state");

		/*
		 * nvpair オブジェクトを作成し、これをこのイベントに追加する。
		 */
		NVPair rgNvpair = new NVPair();
		rgNvpair.setName("rg_name");
		rgNvpair.setValue(rgname);
		rgStateEvent.addNvpair(rgNvpair);

		return (rgStateEvent);
	}

	/*
	 * createREvent
	 * ----------------
	 * クラス EC_Cluster、サブクラス ESC_cluster_r_state、
	 * および "r_name" nvpair (入力パラメータにもとづく) を
	 * 1 つ使用して XML 登録イベントを作成する。
	 */
	private Event createREvent(String rname)
	{
		/*
		 * rgname リソース用のリソース
		 * 状態変更イベントを作成する。
		 * このイベントタイプには、どのリソースグループに興味
		 * があるかを示すため、名前/ 値ペア (nvpair) を指定
		 * する。
		 */
		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
	 * ------------------
	 * REMOVE_CLIENT CallbackReg オブジェクトを作成し、
	 * CallbackReg オブジェクトから XML 文字列を取得する。
	 */
	private String createUnregistrationString() throws Exception
	{
		/*
		 * CallbackReg オブジェクトを作成する。
		 */
		CallbackReg cbReg = new CallbackReg();
		cbReg.setPort("" + localPort);
		cbReg.setRegType(CallbackReg.REMOVE_CLIENT);

		/*
		 * 登録を OutputStream に整列化する。
		 */
		String xmlStr = cbReg.convertToXml();

		// デバッグのために文字列を出力する。
		System.out.println(xmlStr);
		return (xmlStr);
	}


	/*
	 * readRegistrationReply
	 * ------------------------
	 * xml を解析してドキュメントにし、このドキュメントから
	 * RegReply オブジェクトを構築し、RegReply オブジェクトを
	 * 出力する。実際のアプリケーションでは、通常、RegReply
	 * オブジェクトの status_code にもとづいて処理をする。
	 */
	private void readRegistrationReply(InputStream stream)
	    throws Exception
	{
		// ドキュメントビルダーを作成する。
		DocumentBuilder db = dbf.newDocumentBuilder();
	
		//
		// 解析前に ErrorHandler を設定する。
		// ここではデフォルトハンドラを使用。
		//
		db.setErrorHandler(new DefaultHandler());

		// 入力ファイルを解析する。
		Document doc = db.parse(stream);

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

	/* private 指定のメンバー変数 */
	private InetAddress regIp;
	private int regPort;
	private EventReceptionThread evtThr;
	private String regs[];

	/* public 指定のメンバー変数 */
	public int localPort;
	public DocumentBuilderFactory dbf;
}

/*
 * クラス EventReceptionThread
 * ----------------------------
 * 上記のファイルヘッダーコメントを参照。
 */
class EventReceptionThread extends Thread
{
	/*
	 * EventReceptionThread コンストラクタ
	 * ----------------------------------
	 * ローカルホスト名とワイルドカードポートにバインドされる
	 * 新しい ServerSocket を作成する。
	 */
	public EventReceptionThread(CrnpClient clientIn) throws IOException
	{
		/*
		 * イベントの取得時に再度呼び返すことができるように、
		 * クライアントに対する参照を保持する。
		 */
		client = clientIn;

		/*
		 * バインドする IP を指定する。これは、ローカル
		 * ホスト IP である。このマシンに複数のパブリック
		 * インタフェースが構成されている場合は、
		 * InetAddress.getLocalHost によって検出される
		 * ものをどれでも使用する。
		 *
		 */
		listeningSock = new ServerSocket(client.localPort, 50,
		    InetAddress.getLocalHost());
    		System.out.println(listeningSock);
	}

	/*
	 * run
	 * ---
	 * Thread.Start メソッドによって呼び出される。
	 *
	 * ServerSocket で着信接続を待機し、永続的にループする。
	 *
	 * 各着信接続が受け入れられる際に xml ストリームから
	 * Event オブジェクトが作成される。続いてこのオブジェクト
	 * が CrnpClient オブジェクトに返されて処理される。
	 */
	public void run()
	{
		/*
		 * 永続的にループする。
		 */
		try {
			//
			// CrnpClient 内のドキュメントビルダーファクトリを
			// 使用してドキュメントビルダーを作成する。
			//
			DocumentBuilder db = client.dbf.newDocumentBuilder();

			//
			// 解析前に ErrorHandler を設定する。
			// ここではデフォルトハンドラを使用。
			//
			db.setErrorHandler(new DefaultHandler());

			while(true) {
				/* サーバーからのコールバックを待機 */
				Socket sock = listeningSock.accept();

				// 入力ファイルを解析する。
				Document doc = db.parse(sock.getInputStream());

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

				/* ソケットを閉じる */
				sock.close();
			}
			// 到達不能

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

	/* private 指定のメンバー変数 */
	private ServerSocket listeningSock;
	private CrnpClient client;
}

/*
 * クラス NVPair
 * -----------
 * このクラスは名前/ 値ペア (両方とも文字列) を格納する。
 * このクラスは、そのメンバーから NVPAIR XML メッセージを構築し、
 * NVPAIR XML 要素を解析してそのメンバーにすることができる。
 *
 * NVPAIR の形式仕様では複数の値が許可されているが、ここでは
 * 単純に値は 1 つだけという前提を下す。
 */
class NVPair
{
	/*
	 * 2 つのコンストラクタ: 最初のコンストラクタは空の NVPair を
	 * 作成する。2 つ目は NVPAIR XML 要素から NVPair を作成する。
	 */
	public NVPair()
	{
		name = value = null;
	}

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

	/*
	 * Public 指定のセッター。
	 */
	public void setName(String nameIn)
	{
		name = nameIn;
	}

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

	/*
	 * 1 行で名前と値を出力する。
	 */
	public void print(PrintStream out)
	{
		out.println("NAME=" + name + " VALUE=" + value);
	}

	/*
	 * createXmlElement
	 * ------------------
	 * メンバー変数から NVPAIR XML 要素を作成する。
	 * この要素を作成できるように、ドキュメントをパラメータとして
	 * 受け付ける。
	 */
	public Element createXmlElement(Document doc)
	{
		// 要素を作成する。
		Element nvpair = (Element)
		    doc.createElement("NVPAIR");
		//
		// 名前を追加する。実際の名前は別の
		// CDATA セクションであることに注意。
		//
		Element eName = doc.createElement("NAME");
		Node nameData = doc.createCDATASection(name);
		eName.appendChild(nameData);
		nvpair.appendChild(eName);
		//
		// 値を追加する。実際の値は別の
		// CDATA セクションであることに注意。
		//
		Element eValue = doc.createElement("VALUE");
		Node valueData = doc.createCDATASection(value);
		eValue.appendChild(valueData);
		nvpair.appendChild(eValue);

		return (nvpair);
	}

	/*
	 * retrieveValues
	 * ----------------
	 * XML 要素を解析して名前と値を取得する。
	 */
	private void retrieveValues(Element elem)
	{
		Node n;
		NodeList nl;

		//
		// NAME 要素を検出する。
		//
		nl = elem.getElementsByTagName("NAME");
		if (nl.getLength() != 1) {
			System.out.println("Error in parsing: can't find "
			    + "NAME node.");
			return;
		}
		
		//
		// 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;
		}

		// 値を取得する。
		name = n.getNodeValue();

		//
		// ここで値要素を取得する。
		//
		nl = elem.getElementsByTagName("VALUE");
		if (nl.getLength() != 1) {
			System.out.println("Error in parsing: can't find "
			    + "VALUE node.");
			return;
		}
		
		//
		// 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;
		}

		// 値を取得する。
		value = n.getNodeValue();
	}


	/*
	 * Public 指定のアクセッサ
	 */
	public String getName()
	{
		return (name);
	}

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

	// Private 指定のメンバー変数
	private String name, valu;
}


/*
 * クラス Event
 * -----------
 * このクラスは、クラス、サブクラス、ベンダー、パブリッシャー、名前/
 * 値ペアのリストから成るイベントを格納する。このクラスでは、そのメンバー
 * から SC_EVENT_REG XML 要素を作成し、この要素を解析してそのメンバーに
 * することができる。次の非対称性に注意: SC_EVENT 要素を解析するが、
 * 作成するのは SC_EVENT_REG 要素である。これは、SC_EVENT_REG 要素が
 * 登録メッセージ (これは作成の必要がある) 内で使用され、SC_EVENT 要素
 * がイベント配信 (これは解析の必要がある) 内で使用されるためである。
 * 違いは、SC_EVENT_REG 要素にはベンダーとパブリッシャーがないことだけ
 * である。
 */
class Event
{

	/*
	 * 2 つのコンストラクタ: 最初のコンストラクタは空のイベントを
	 * 作成し、2 つ目は SC_EVENT XML ドキュメントからイベントを
	 * 作成する。
	 */
	public Event()
	{
		regClass = regSubclass = null;
		nvpairs = new Vector();
	}

	public Event(Document doc)
	{

		nvpairs = new Vector();

		//
		// デバッグで使用できるようにドキュメントを文字列に
		// 変換して出力する。
		//
		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 指定のセッター。
	 */
	public void setClass(String classIn)
	{
		regClass = classIn;
	}

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

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

	/*
	 * createXmlElement
	 * ------------------
	 * メンバー変数から SC_EVENT_REG XML 要素を作成する。
	 * この要素を作成できるように、ドキュメントをパラメータとして
	 * 受け付ける。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);
	}

	/*
	 * メンバー変数を複数行に出力する。
	 */
	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
	 * ----------------
	 * XML ドキュメントを解析し、クラス、サブクラス、ベンダー、
	 * パブリッシャー、および nvpair を取得する。
	 */
	private void retrieveValues(Document doc)
	{
		Node n;
		NodeList nl;

		//
		// 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);

		//
		// CLASS、SUBCLASS、VENDOR、および PUBLISHER
		// 属性の値を取得する。
		//
		regClass = ((Element)n).getAttribute("CLASS");
		regSubclass = ((Element)n).getAttribute("SUBCLASS");
		publisher = ((Element)n).getAttribute("PUBLISHER");
		vendor = ((Element)n).getAttribute("VENDOR");

		//
		// すべての nv ペアを取得する。
		//
		for (Node child = n.getFirstChild(); child != null;
		     child = child.getNextSibling())
		{
			nvpairs.add(new NVPair((Element)child));
		}
	}

	/*
	 * 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);
	}

	// Private 指定のメンバー変数
	private String regClass, regSubclass;
	private Vector nvpairs;
	private String vendor, publisher;
}


/*
 * クラス CallbackReg
 * -----------
 * このクラスは、ポートと登録タイプ (どちらも文字列)、およびイベントリストを
 * 格納する。このクラスは、そのメンバーから SC_CALLBACK_REG XML メッセージ
 * を作成できる。
 *
 * SC_CALLBACK_REG メッセージを解析する必要があるのは CRNP サーバー
 * だけであるため、このクラスで SC_CALLBACK_REG メッセージを解析でき
 * なくてもよい。
 */
class CallbackReg
{
	// 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 指定のセッター。
	 */
	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
	 * ------------------
	 * メンバー変数から SC_CALLBACK_REG XML ドキュメントを構築する。
	 * Event createXmlElement 機能を使用。
	 */
	public String convertToXml()
	{
		Document document = null;
		DocumentBuilderFactory factory =
		    DocumentBuilderFactory.newInstance();
		try {
			DocumentBuilder builder = factory.newDocumentBuilder();
			document = builder.newDocument(); 
		} catch (ParserConfigurationException pce) {
			// 指定したオプションを持つパーサーを構築できない。
			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);

		//
		// ここでドキュメントを文字列に変換する。
		//
		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 指定のメンバー変数
	private String port;
	private String regType;
	private Vector regEvents;
}

/*
 * クラス RegReply
 * -----------
 * このクラスは、status_code と status_msg (どちらも文字列) を格納する。
 * このクラスは、SC_REPLY XML 要素を解析し、そのメンバーにできる。
 */
class RegReply
{

	/*
	 * 1 つのコンストラクタが XML ドキュメントを受け入れて解析を行う。
	 */
	public RegReply(Document doc)
	{
		//
		// ここでドキュメントを文字列に変換する。
		//
		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 指定のアクセッサ
	 */
	public String getStatusCode()
	{
		return (statusCode);
	}

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

	/*
	 * 1 行で情報を出力する。
	 */
	public void print(PrintStream out)
	{
		out.println(statusCode + ": " +
		    (statusMsg != null ? statusMsg : ""));
	}

 	/*
	 * retrieveValues
	 * ----------------
	 * XML ドキュメントを解析し、statusCode と statusMsg を取得する。
	 */
	private void retrieveValues(Document doc)
	{
		Node n;
		NodeList nl;

		//
		// 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);

		// STATUS_CODE 属性の値を取得する。
		statusCode = ((Element)n).getAttribute("STATUS_CODE");

		//
		// 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;
		}
		
		//
		// TEXT セクションが存在する場合は、それを取得する。
		//
		n = nl.item(0).getFirstChild();
		if (n == null || n.getNodeType() != Node.TEXT_NODE) {
			// 存在しなくてもエラーではないため、
			// このまま戻る。
			return;
		}

		// 値を取得する。
		statusMsg = n.getNodeValue();
	}

	// private 指定のメンバー変数。
	private String statusCode;
	private String statusMsg;
}