/* * Node.java * * Created on 2004/06/19, 13:57 */ package net.sourceforge.shingetsu.node; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.ProtocolException; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.util.Enumeration; import java.util.Vector; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sourceforge.shingetsu.util.Config; /** * 新月匿名掲示板システムのネットワークのひとつのノードを表すクラスです。ノードは * 他の隣接ノードとコミュニケーションを交わしたり、スレッド、リストのやりとりを行 * います。 * @author Fuktommy * @author stokesia */ public class Node { //tweak by d55d278e //Config config = Config.getInstance(); private int port; //private int port = config.getPort(); private URL nodeURL; private String userAgent; //private String userAgent = config.getUserAgent(); //add private String encode; /** * 他ノードに通知する自ノードのポート番号 * @return ポート番号 */ protected int getPort() { return port; } /** * 他ノードに通知する自身のUA名 * @return UA名 */ protected String getUserAgent() { return userAgent; } /** * 自身が使用しているエンコード * @return エンコード名 */ protected String getEncoding() { return encode; } /** * ノードURLを設定 * @return URL */ protected void setURL(URL url) { nodeURL = url; } /* *add constructor */ protected Node() { Config config = Config.getInstance(); port = config.getPort(); userAgent = config.getUserAgent(); encode = config.getGeneralEncoding(); } /** * 指定された初期ノードでNodeクラスのインスタンスを生成します。 * @param url スキーム名を省略したserver.cgiへのURL * (例 www.example.com:8000/server.cgi) * @throws MalformedURLException 文字列に指定されたプロトコルが未知である場合 */ public Node(String url) throws MalformedURLException { this(); //add nodeURL = new URL("http://" + url); } /** * このオブジェクトが表すノードに新月プロトコルのコマンドを発行します。 * @param command ノードに発行するプロトコルコマンド * @return レスポンスのString型配列 * @throws MalformedURLException サーバントのノードアドレスが不正な場合 * @throws IOException 通信中に入出力エラーが発生した場合 */ public String[] talk(String command) throws MalformedURLException, IOException { Vector responseRecords = new Vector(); URL commandURL = null; URLConnection commandConnection = null; commandURL = new URL(nodeURL.toString() + command); commandConnection = commandURL.openConnection(); //tweak //commandConnection.setRequestProperty("User-Agent", userAgent); commandConnection.setRequestProperty("User-Agent", getUserAgent()); commandConnection.setRequestProperty("Accept", "text/plain"); commandConnection.connect(); //tweak //CharsetDecoder decoder = Charset.forName(config.getGeneralEncoding()).newDecoder(); CharsetDecoder decoder = Charset.forName(getEncoding()).newDecoder(); BufferedReader reader = new BufferedReader( new InputStreamReader( commandConnection.getInputStream(), decoder)); String line = null; while ((line = reader.readLine()) != null) { responseRecords.add(line); } reader.close(); String[] responses = new String[responseRecords.size()]; Enumeration enumeration = responseRecords.elements(); for (int i = 0; enumeration.hasMoreElements(); i++) { responses[i] = (String) enumeration.nextElement(); } return responses; } /** * このオブジェクトが表すノードへ/pingコマンドを発行し、ノードが有効かどうか調べま * す。 * @return ノードが有効であればtrue * @throws MalformedURLException サーバントのノードアドレスが不正な場合 * @throws IOException 通信中に入出力エラーが発生した場合 */ public boolean ping() throws MalformedURLException, IOException { boolean result = false; String[] responses = talk("/ping"); /* String nodeIPAddress = null; try { nodeIPAddress = InetAddress.getByName(nodeURL.getHost()).getHostAddress(); } catch(UnknownHostException e) { e.printStackTrace(); } */ if(responses.length == 2) { //result = (responses[0].equals("PONG")) && (responses[1].equals(nodeIPAddress)); result = responses[0].equals("PONG") && (responses[1].length() > 0); } return result; } /** * このオブジェクトが表すサーバントに/pingコマンドを発行し、ホスト名、ポート番号、 * パスが有効であることを確認し、サーバントに接続をします。レスポンスメッセージに * 他のノード情報が含まれていた場合はそのノードのオブジェクトを、含まれていなけれ * ば自分自身を戻り値として返します。 * @return 他のノードのオブジェクト、または自分自身 * @throws MalformedURLException サーバントのノードアドレスが不正な場合 * @throws ProtocolException サーバントのレスポンスが不正な場合 * @throws IOException 通信中に入出力エラーが発生した場合 */ public Node join() throws MalformedURLException, ProtocolException, IOException { String[] responses = talk("/ping"); String nodeAddress = null; if((responses.length >= 2) && responses[0].equals("PONG") && (responses[1].length() > 0)) { //nodeAddress = responses[1] + ":" + port + "/server.cgi"; //tweak //nodeAddress = ":" + port + "/server.cgi"; nodeAddress = ":" + getPort() + "/server.cgi"; responses = talk("/join/" + escape(nodeAddress)); } else { throw (new ProtocolException("通信相手のノードが有効ではありませんでした。")); } Node node = null; if(responses.length > 1) { if(responses[0].trim().equals("WELCOME")){ node = new Node(responses[1].trim()); } else { throw (new ProtocolException("通信相手のサーバントのレスポンスメッセージが不正でした。")); } } else if(responses.length > 0) { node = this; } else { throw (new ProtocolException("通信相手のサーバントのレスポンスメッセージが不正でした。")); } return node; } /** * このオブジェクトが表すサーバントに/pingコマンドを発行し、 ホスト名、ポート番号、 * パスが有効であることを確認し、接続を解除します。レスポンスメッセージとその処理 * は新月プロトコルに従います。 * @throws ProtocolException レスポンスメッセージが不正か、接続解除に失敗した場合 * @throws MalformedURLException サーバントのノードアドレスが不正な場合 * @throws IOException 通信中に入出力エラーが発生した場合 */ public void bye() throws ProtocolException, MalformedURLException, IOException { String[] responses = talk("/ping"); String nodeAddress = null; if((responses.length >= 2) && responses[0].equals("PONG") && (responses[1].length() > 0)) { //tweak //nodeAddress = responses[1] + ":" + port + "/server.cgi"; nodeAddress = responses[1] + ":" + getPort() + "/server.cgi"; responses = talk("/bye/" + escape(nodeAddress)); } else { throw (new ProtocolException("通信相手のノードが有効ではありませんでした。")); } if(((responses.length > 0) && responses[0].trim().equals("BYEBYE")) == false) { throw (new ProtocolException("通信相手のサーバントのレスポンスメッセージが不正でした。")); } } /** * このオブジェクトが表すサーバントに/pingコマンドでノードが有効であることを確認 * し、サーバントが接続してるノードを取得します。 * @return サーバントから得たノード * @throws MalformedURLException サーバントのノードアドレスが不正な場合 * @throws ProtocolException サーバントのレスポンスが不正な場合 * @throws IOException 通信中に入出力エラーが発生した場合 */ public Node node() throws MalformedURLException, ProtocolException, IOException { String[] responses = null; if(ping()) { responses = talk("/node"); } else { throw (new ProtocolException("通信相手のノードが有効ではありませんでした。")); } Node node = null; if(responses.length > 0) { node = new Node(responses[0].trim()); } else { throw (new ProtocolException("通信相手のサーバントのレスポンスメッセージが不正でした。")); } return node; } /** * このオブジェクトが表すサーバントに引数で指定したファイルをもっているかどうか問 * い合わせます。 * @param filename basenameが16進数で表されたファイル名(ただし、拡張子は不要) * @return もっていればtrue、もっていなければfalse * @throws MalformedURLException サーバントのノードアドレスが不正な場合 * @throws ProtocolException サーバントのレスポンスが不正な場合 * @throws IOException 通信中に入出力エラーが発生した場合 */ public boolean have(String filename) throws MalformedURLException, ProtocolException, IOException { String[] responses = null; if(ping()) { responses = talk("/have/" + filename); } else { throw (new ProtocolException("通信相手のノードが有効ではありませんでした。")); } boolean result = false; if(responses.length > 0) { String message = responses[0].trim(); if(message.equals("YES")) { result = true; } else if(!message.equals("NO")) { throw (new ProtocolException("通信相手のサーバントのレスポンスメッセージが不正でした。")); } } else { throw (new ProtocolException("通信相手のサーバントのレスポンスメッセージが不正でした。")); } return result; } /** * このオブジェクトが表すサーバントから引数で指定したファイルと時刻引数に一致す * る行を取得します。 * @param filename basenameが16進数で表されたファイル名(ただし、拡張子は不要) * @param timeArgument 新月プロトコルに従った時刻引数のString値 * @return 引数で指定した条件に一致するファイルの行 * @throws MalformedURLException サーバントのノードアドレスが不正な場合 * @throws ProtocolException サーバントのレスポンスが不正な場合 * @throws IOException 通信中に入出力エラーが発生した場合 */ public String[] get(String filename, String timeArgument) throws MalformedURLException, ProtocolException, IOException { String[] responses = null; if(have(filename)) { responses = talk("/get/" + filename + "/" + timeArgument); } else { throw (new ProtocolException("通信相手のノードがファイルを持っていませんでした。")); } /* if(!(responses.length > 0)) { throw (new ProtocolException("通信相手のサーバントのレスポンスメッセージが不正でした。")); } */ return responses; } /** * このオブジェクトが表すサーバントから引数で指定したファイルと時刻引数に一致す * る行を取得します。ただし、各行の時刻と識別子のみを返します。 * @param filename basenameが16進数で表されたファイル名(ただし、拡張子は不要) * @param timeArgument 新月プロトコルに従った時刻引数のString値 * @return 引数で指定した条件に一致するファイルの行 * @throws MalformedURLException サーバントのノードアドレスが不正な場合 * @throws ProtocolException サーバントのレスポンスが不正な場合 * @throws IOException 通信中に入出力エラーが発生した場合 */ public String[] head(String filename, String timeArgument) throws MalformedURLException, ProtocolException, IOException { String[] responses = null; if(have(filename)) { responses = talk("/head/" + filename + "/" + timeArgument); } else { throw (new ProtocolException("通信相手のノードがファイルを持っていませんでした。")); } /* if(!(responses.length > 0)) { throw (new ProtocolException("通信相手のサーバントのレスポンスメッセージが不正でした。")); } */ return responses; } /** * このオブジェクトが表すサーバントに引数で指定したファイルの更新を知らせます。 * @param filename basenameが16進数で表されたファイル名(ただし、拡張子は不要) * @param timeArgument 新月プロトコルに従った時刻引数のString値 * @param md5 本文のMD5値 * @param node 更新されたファイルがあるノード * @throws MalformedURLException サーバントのノードアドレスが不正な場合 * @throws ProtocolException サーバントのレスポンスが不正な場合 * @throws IOException 通信中に入出力エラーが発生した場合 */ public void update(String filename, String timeArgument, String md5, Node node) throws MalformedURLException, ProtocolException, IOException { String escapedAddress = escape(node.toString()); String message = "/update/" + filename + "/" + timeArgument + "/" + md5 + "/" + escapedAddress; if(ping()) { talk(message); } else { throw (new ProtocolException("通信相手のノードが有効ではありませんでした。")); } } /** * ノード固有のメッセージをかえします。 * @return ノード固有のメッセージ */ public String root() { return toString(); } /** * ノードの文字列表現を返します。 * @return ノードの文字列表現 */ public String toString() { return nodeURL.getHost() + ":" + nodeURL.getPort() + nodeURL.getFile(); } /** * このオブジェクト(ノード)と等しいかどうか比較します。 * @param node 比較対象のオブジェクト(ノード) * @return 等しければtrue */ public boolean equals(Object node) { return (this.toString().equals(node.toString())); } /** * 新月プロトコルのパス仕様に従って[/]を[+]に置き換えます。 * @param string 置換対象の文字列 * @return 置換された文字列 */ public String escape(String string) { Pattern pattern = Pattern.compile("/"); Matcher matcher = pattern.matcher(string); return (matcher.replaceAll("+")); } }