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

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

この付録では、CrnpClient.java アプリケーションの完全なコードを示します (詳細は 第 12 章「クラスタ再構成通知プロトコル」を参照)。

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_rg_state、
  * および "rg_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()
 {
  /*
   * Loop forever.
   */
  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, value;
}


/*
 * クラス 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) {
   // Not an error if there isn't one, so we
   // just silently return.
   return;
  }

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

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