2012-03-22

Java: スーパークラスのフィールドの書き換え

スーパークラスのフィールドをサブクラスでオーバーライドしようとしてもフィールドはオーバーライドではなく隠蔽という形になるので、サブクラスをスーパークラスのインスタンスとして扱うときにはオーバーライドしたつもりのフィールドの値がスーパークラスのフィールドの値になってしまいます(例のためfieldをpublicにしています)。

public class A {
	public String field = "A";
}
public class SubA extends A {
	public String field = "SubA";
}
public class Main {
	public static void main(String[] args) {
		SubA subA = new SubA();
		A a = subA;

		System.out.println(subA.field); // SubA
		System.out.println(a.field); // A ← SubAになってほしい
	}
}

スーパークラスとして扱っていてもサブクラスのフィールドの値を参照させたかったので、どうすればできるか試しました。

1. スーパークラスのメソッドを経由する → ×

スーパークラスにメソッドを定義してメソッド経由でフィールドにアクセスさせてもスーパークラスのメソッドがアクセスするのはスーパークラスのフィールドなので、そのメソッドを継承してきたサブクラスでもメソッド内ではスーパークラスのフィールドにアクセスしてしまいます。

public class A {
	public String field = "A";
	public String getField() {
		return field;
	}
}
public class SubA extends A {
	public String field = "SubA";
}
public class Main {
	public static void main(String[] args) {
		SubA subA = new SubA();
		A a = subA;

		System.out.println(subA.getField()); // A ← SubAになってほしい
		System.out.println(a.getField()); // A ← SubAになってほしい
	}
}

2. サブクラスで同名のフィールドを定義せず、コンストラクタ等でフィールドの値を書き換える → ○

これだとフィールドの隠蔽がされないのでサブクラスでは常にスーパークラスのfieldにアクセスするようになります。

public class A {
	public String field = "A";
	public String getField() {
		return field;
	}
}
public class SubA extends A {
	public SubA() {
		field = "SubA";
	}
}
public class Main {
	public static void main(String[] args) {
		SubA subA = new SubA();
		A a = subA;

		System.out.println(subA.getField()); // SubA
		System.out.println(a.getField()); // SubA
	}
}

サブクラスで同名フィールドを定義してしまうと×です。

3. コンストラクタ等で明示的にスーパークラスのフィールドの値を書き換える → ○

サブクラスで同名フィールドを定義していても、superキーワード経由でフィールドにアクセスすればスーパークラスのフィールドを書き換えられます。

public class A {
	public String field = "A";
	public String getField() {
		return field;
	}
}
public class SubA extends A {
	public String field = "SubA";
	public SubA() {
		super.field = this.field;
	}
}
public class Main {
	public static void main(String[] args) {
		SubA subA = new SubA();
		A a = subA;

		System.out.println(subA.getField()); // SubA
		System.out.println(a.getField()); // SubA
	}
}

4. オーバーライドしたメソッドを経由する → ○

サブクラスでスーパークラスと同じメソッドを定義してオーバーライドします。オーバーライドしたサブクラスのメソッドはサブクラスのフィールドにアクセスしてくれます(スーパークラスのフィールドを書き換えたわけではないですが)。ただスーパークラスとサブクラスで同じメソッドを書いているので無駄な感じがします…。

public class A {
	public String field = "A";
	public String getField() {
		return field;
	}
}
public class SubA extends A {
	public String field = "SubA";
	@Override
	public String getField() {
		return field;
	}
}
public class Main {
	public static void main(String[] args) {
		SubA subA = new SubA();
		A a = subA;

		System.out.println(subA.getField()); // SubA
		System.out.println(a.getField()); // SubA
	}
}

静的フィールドの場合

静的フィールドもサブクラスで同名フィールドを定義するとオーバーライドではなく隠蔽になります。またインスタンスメソッドと違い静的メソッドも隠蔽になるので上記4の方法は使えません。2だと問題ありません。3の場合…

public class A {
	public static String FIELD = "A";
	public static String getField() {
		return FIELD;
	}
}
public class SubA extends A {
	public static String FIELD = "SubA";
	static {
		A.FIELD = FIELD;
	}
}
public class Main {
	public static void main(String[] args) {
		System.out.println(SubA.getField()); // SubA
		System.out.println(A.getField()); // SubA
	}
}

static初期化子等の中でsuperが使えないのでクラス名を直に書かないといけません。

ただスーパークラスの静的フィールドを書き換えてしまうと影響が大きいのでやめたほうがいいです…。

2012-03-17

XAMPP(1.7.4): Apacheが起動しない

XAMPP(今使っているのはEclipse 3.7 Indigo Pleiades All in Oneに同梱されている1.7.4です)のコントロールパネルでApacheを起動しようとしても「Busy…」と言われて起動しない時があります。Apacheの標準設定ではポート80(HTTP)と443(SSL)をlistenするので、Apacheを起動させる前に他のプロセスに80や443を使われていると起動できません。

80、443を使うプロセス

遭遇したことのあるものではSkype、TeamViewerが80と443を使っていました。SkypeとTeamViewerは80と443を使わないように設定できます。

image
Skype (80, 443を使わないように設定した状態)

image
TeamViewer (80, 443を使わないように設定した状態)

80を使うプロセス

遭遇したことのあるものではGladinet Cloud Desktopが80を使っていました。設定変更は見つかりませんでした…誰か知っている人いたら教えて下さい…。

443を使うプロセス

遭遇したことのあるものではサービスのRouting and Remote Accessが443を使っていました。

image
Routing and Remote Access (無効にした状態)

何のプロセスが先にポートを使っているかわからないので、Apacheのconf/httpd.confとconf/extra/httpd-ssl.confに設定されているListen 80、Listen 443を変えたほうがイライラしなくてすむかもしれません(個人的に基本はVMware + CentOS + Apache、突発のものはXAMPPという形で作業しているので、自分はListen先を変えました)。

2012-03-14

EC-CUBE(2.11.5): パスワードのハッシュ化処理

Webシステムではユーザのパスワードはsha1等のハッシュアルゴリズムを使ってハッシュ化(暗号化)してDBに保存するのが普通です。今日はEC-CUBEの管理者のパスワードがわからなかったのでどのようにパスワードをハッシュ化しているか調べていました。

EC-CUBE(2.11系)では基本的には下記の要領でパスワードハッシュを生成しているようです。

hash_hmac(PASSWORD_HASH_ALGOS, "生パスワード:AUTH_MAGIC", salt);
PASSWORD_HASH_ALGOS data/config/config.php内のPASSWORD_HASH_ALGOSの値(“sha256”等)
生パスワード 入力するパスワード
AUTH_MAGIC data/config/config.php内のAUTH_MAGICの値
salt dtb_member(管理者)またはdtb_customer(会員)テーブルのsaltの値

ハッシュ化の処理はSC_Utils(data/class/util/SC_Utils.php)のsfGetHashString()が行なっていました。

/**
     * パスワードのハッシュ化
     *
     * @param string $str 暗号化したい文言
     * @param string $salt salt
     * @return string ハッシュ暗号化された文字列
     */
    function sfGetHashString($str, $salt) {
        $res = '';
        if ($salt == '') {
            $salt = AUTH_MAGIC;
        }
        if ( AUTH_TYPE == 'PLAIN') {
            $res = $str;
        } else {
            $res = hash_hmac(PASSWORD_HASH_ALGOS, $str . ":" . AUTH_MAGIC, $salt);
        }
        return $res;
    }

2012-03-11

Fiddler(2.3.9.3): サーバ上のファイルを一時的に置き換える

サーバ上にあるファイル(CSS、JS、画像ファイル等)を修正したりデバッグしたいとき、修正ファイルをサーバにアップロードせずにテストできると便利です(アップロード→確認 を何回も繰り返す必要がなくなるため)。

Fiddlerを使うと、下のような手順でサーバ上のファイルを置き換えることができます(例としてサンプルのJavaScriptを書き換えてみます。Fiddlerの説明については「実はFiddlerがすごすぎたので、機能まとめ紹介 :  blog.loadlimit - digital matter -」が詳しいです)。

  1. サーバにある書き換えたいファイルをローカルに保存しておく
    今回の例では http://dl.dropbox.com/u/5447955/test/20120312/js/script.js
  2. Fiddlerを起動する
  3. サーバにある書き換えたいファイルをブラウザで開く(すでに開いていれば再読み込み)
  4. Fiddler左側にログがたまっていくので、そこから該当するファイルの項目を見つけておく
    image
  5. Fiddler右側のAutoResponderタブを表示する
    image
  6. 「Enable automatic responses」「Unmatched requests passthrough」にチェックを入れる
    image
  7. 4.の項目を右側にドラッグする
    image
  8. Rule Editorの下側を「find a file…」にして1.のファイルを指定する
    image
  9. 1.のファイルを修正する
  10. 3.で開いておいたファイルを再読み込みする

再読み込みするとファイルの内容がローカルのものになっているのがわかります。

2012-03-09

Windows: telnet FTP接続 + NLSTの確認

FTPサーバでNLSTが動いているのか確認したいという話になったのでWindowsでの確認方法を簡単にまとめました(確認したクライアントはWindows 7 Home Premium + telnet、サーバはCentOS 5.6 + ProFTPD 1.3.1です)。

まずコマンドプロンプトをひとつ立ちあげて下記のように入力します(強調されている箇所。返ってくるメッセージはサーバによって変わります)。

C:\Users\user>telnet ホスト名 ポート番号(だいたい21)

220 ProFTPD 1.3.1 Server (ProFTPD) [xxx.xx.xxx.xx]
USER ユーザー名
331 Password required for xxxxx
PASS パスワード
230 User xxxxx logged in
PASV
227 Entering Passive Mode (xxx,xx,xxx,xx,a,b).
NLST

PASVの応答メッセージで数字(上記のxxx,xx,xxx,xx,a,bの箇所。xxx,xx,xxx,xxはサーバのIPアドレス。a,bはランダムな数字)が出てきたらNLSTの結果の受信用にコマンドプロンプトを立ち上げ、ホスト名と共にa * 256 + bの値をポート番号として入力して接続します(xxx,xx,xxx,xx,212,11 なら 212 * 256 + 11 = 54283)。

C:\Users\user>telnet ホスト名 ポート番号

接続すると新しく立ち上げた画面にはファイルとディレクトリの一覧が表示され、元の画面には例えば下記のように表示されます。

150 Opening BINARY mode data connection for file list
226-Transfer complete
226 Quotas off

(余談)

Windowsのftpコマンド(ftp.exe)でも確認は可能でした(コマンド送信用にftp.exe, 受信用にtelnetになるだけですが…)。ftpコマンドの場合PASVやNLSTなどはそのまま送信することはできませんが、quoteかliteralを使うと送信できました(quote PASVやquote NLST)。また ftp -d ホスト名 として起動するとlsやcdなどを実行した時の実際に送信されている生コマンドを表示してくれるので諸々の確認がしやすくなります。