田中公平先生の解説一覧

NHKの熱中夜話あるいは、熱中スタジアム田中公平先生が出ている回は見逃せないと思い、過去の番組のページを集めてみた。
BS熱中夜話ヒーローソングナイト第1夜誕生編
BS熱中夜話ヒーローソングナイト第2夜進化編

BS熱中夜話アニメソングナイト第1夜水木一郎&堀江美都子編
BS熱中夜話アニメソングナイト第2夜新世代作曲家編

熱中スタジアム特撮ソング第1夜ウルトラマン&仮面ライダーソング
熱中スタジアム特撮ソング第2夜戦隊&メタルヒーローソング

年に一回はこんな感じで出てたらしい。
八月にはhttp://www.nhk.or.jp/n-stadium/anison/index.htmlが放送されるらしいし、楽しみ。

junitで例外のテストをもっと簡単にしたい

最近のJunitでは例外のチェックが前よりは簡単になったけど、JDaveの匿名クラスを使った記述方法と比べていまいちだなと感じていた。

例外のチェック方法の比較

JUnit4での例その1:@Testのexceptionを使う場合。

JUnit Cookbook」より

@Test(expected= IndexOutOfBoundsException.class)
public void empty() { 
    new ArrayList<Object>().get(0); 
}
JUnit4での例その2:@Ruleを使う場合。

http://kentbeck.github.com/junit/javadoc/latest/org/junit/rules/ExpectedException.html」より。

// These tests all pass.
 public static class HasExpectedException {
        @Rule
        public ExpectedException thrown= new ExpectedException();
 
        @Test
        public void throwsNothing() {
    // no exception expected, none thrown: passes.
        }
 
        @Test
        public void throwsNullPointerException() {
                thrown.expect(NullPointerException.class);
                throw new NullPointerException();
        }
 
        @Test
        public void throwsNullPointerExceptionWithMessage() {
                thrown.expect(NullPointerException.class);
                thrown.expectMessage("happened?");
                thrown.expectMessage(startsWith("What"));
                throw new NullPointerException("What happened?");
        }
 }

あとは「JUnit4.7 の新機能 Rules とは〜その2 - A Memorandum」が参考になる。

JDaveでの例

JDave-examples」から必要な部分を抜粋。

import jdave.Block;
import jdave.Specification;
import jdave.junit4.JDaveRunner;

@RunWith(JDaveRunner.class)
public class StackSpec extends Specification<Stack<?>> {
    public class FullStack {
        private Stack<Integer> stack;

        public Stack<Integer> create() {
            stack = new Stack<Integer>(10);
            for (int i = 0; i < 10; i++) {
                stack.push(i);
            }
            return stack;
        }

        public void complainsOnPush() {
            specify(new Block() {
                public void run() throws Throwable {
                    stack.push(100);
                }
            }, should.raise(StackOverflowException.class));
        }       
    }
}

好みの問題かもしれないけど、

  • Blockを実装した匿名クラスを使う事で例外が起きるであろう箇所を指定できる。
  • そのすぐ近くに投げられるであろう例外と例外メッセージを記述できる。

という部分がとても気に入っている。

ただ、以下のような違いもあって、Junitから乗り換えるのが難しいと感じる部分もある。

  • JDaveRunnerでうごかさないといけない
  • Specificationクラス内にテストのサブクラスを記述しないといけい
  • createメソッドが必要

だったらJUnitのassertThatで同じような感じで記述できればいいかなと思って、以下のような感じで記述するためのカスタムMatcherを作ってみた。

assertThat( new Block(){
  public void run() throws Throwable {
                    stack.push(100);
                }
}, should.raise(StackOverflowException.class));

設計と実装

hamcrest の Matcher を独自拡張 - java.util.Date 版 closeTo - 倖せの迷う森」の独自のDate用Matcherを作成した例を参考にして、実装してみる。

TypeSafeMatcherについて

この例ではorg.hamcrest.TypeSafeMatcherというクラスを使っていて、これはhamcrest1.2以降で導入されている。
で、hamcrest-core1.2を使おうとしたら、mavenのセントラルレポジトリにないではないか。「Google Code Archive - Long-term storage for Google Code Project Hosting.」を見てみると、mavenレポジトリのアップロードに苦労しているっぽい。仕方がないので、ローカルにインストールして使う。

jdaveの例外記述バリエーション
  • 例外メッセージチェックの有無。
  • 例外クラスを厳密にチェックするか派生クラスを許可するか。

の組み合わせで、以下の4つが存在する。

  1. should.raise(Class expected)
  2. should.raise(Class expectedType, String expectedMessage)
  3. should.raiseExactly(Class expected)
  4. should.raiseExactly(Class expected,String expectedMessage)
assertThatメソッドのactualとBlockの相性

ひとまずJDaveのBlockインターフェースをそのまま使ってみたのだが、assertThatメソッドと第一引数として渡しているBlockの相性が悪い。

assertThatの第一引数actualに渡したオブジェクトは、toString()メソッドで実際の値をかえさなければいけないが、インターフェースを実装した匿名クラスだと、toStringで匿名クラス名しか表示できない。
かといって、toString()メソッドをそれぞれ実装するのは記述量がふえてしまって良くない。
ということでBlockを抽象クラスに変更して、Matcherでの評価時に発生した例外を保存出来るようにし、toString()メソッドでその例外の情報を返すようにした。

実際のコード

ひとまずbitbucketに置いてみた。
http://bitbucket.org/cnaos/hamcrest-exception-matcher/

主要なクラスの解説

Blockクラス

jdaveのBlockインターフェースとほぼ一緒。前述の理由によりassertThat()に適合するように修正している。

public abstract class Block {
    /** ブロックの実行時に発生した例外 */
    private String actualException="No Exception was thrown";

    /**
     * Evaluate this block.
     *
     * @throws Throwable if an exception is thrown during block evaluation.
     */
    public abstract void run() throws Throwable;

    /**
     * 発生した例外を設定します。
     * @param actualException
     */
    public void setActualException(String actualException) {
        this.actualException = actualException;
    }

    @Override
    public String toString() {
        return this.actualException;
    }
}
RaiseMatcherクラス

Block用のカスタムMatcher。
要となる部分は、matchesSafelyメソッドで、この部分で渡されたBlockを実行して、投げられた例外をチェックしている。

public class RaiseMatcher extends TypeSafeMatcher<Block> {

    /** 例外の期待値 */
    private final ExpectedException<? extends Throwable> expectation;

    /**
     * 
     * @param aExpectation 例外の期待値
     */
    public RaiseMatcher(ExpectedException aExpectation) {
        this.expectation = aExpectation;
    }

    @Factory
    public static <E extends Throwable> Matcher<Block> raise(final Class<E> expected) {
        return new RaiseMatcher(new ExpectedException<E>(expected));
    }

    @Factory
    public static <E extends Throwable> Matcher<Block> raise(final Class<E> expected, final String expectedMessage) {
        return new RaiseMatcher(new ExpectedExceptionWithMessage<E>(expected, expectedMessage));
    }

    @Factory
    public static <E extends Throwable> Matcher<Block> raiseExactly(final Class<E> expected) {
        return new RaiseMatcher(new ExactExpectedException<E>(expected));
    }

    @Factory
    public static <E extends Throwable> Matcher<Block> raiseExactly(final Class<E> expected, final String expectedMessage) {
        return new RaiseMatcher(new ExactExpectedExceptionWithMessage<E>(expected, expectedMessage));
    }

    /**
     *
     * @see jdave.Specification#specify(Block,ExpectedException)
     * @param block テストで実行するブロック
     * @return 指定したテスト条件を満たした場合はtrue
     */
    @Override
    protected boolean matchesSafely(final Block block) {
        try {
            block.run();
        } catch (final Throwable t) {
            block.setActualException(expectation.createDescription(t));
            if (!expectation.matches(t)) {
                return false;
            }
            return true;
        }
        return false;
    }

    /**
     * 投げられる事を期待している例外クラス名や例外メッセージを返す
     * @param description 期待する条件の説明を返す
     */
    @Override
    public void describeTo(Description description) {
        description.appendText("throw ");
        description.appendValue(expectation);
    }
}
ExpectedExceptionクラス

Blockから投げられる例外に関する期待条件クラス。
jdaveのExpectedExceptionをhamcrestMatcherに合うように改変した。

public class ExpectedException<T> {
    protected final Class<? extends T> expected;

    public ExpectedException(Class<? extends T> expected) {
        if(expected == null){
            throw new NullPointerException();
        }
        this.expected = expected;
    }

    public boolean matches(Throwable t) {
        return matchesType(t.getClass());
    }

    public String createDescription(Throwable t){
        return t.getClass().getName();
    }

    protected boolean matchesType(Class<? extends Throwable> actual) {
        return expected.isAssignableFrom(actual);
    }

    @Override
    public String toString() {
        return expected.getName();
    }
}

TwitterのOAuth認証を使ったサービスを開発する際の注意(その2)

前回のエントリで、対策が具体的でなかったのは、SHA-1などのハッシュ関数を使っても問題ないか自信が持てなかったためです。すみません。とりあえず自分で納得できる対策が調べられたと思うので書いておきます。

twitterIDをそのままSHA-1などの暗号学的ハッシュ関数にかけただけでは弱いので避けるということは知っていましたが、単なる固定文字列のsaltを付け加えるだけだと、常に同じ値が生成されるのでこれでいいのか自信が持てませんでした。

で、調べてみたら自動ログイン用のクッキーの値に必要な特性は、セッションIDの特性によくにていて、値の生成は「ユーザIDや時刻等の情報と擬似乱数とを混ぜ合わせた文字列に対してハッシュ関数を使用する」で問題なさそうだということが分かりました。
また、管理方法もに似ていて、「ログインごとに認証用のキーとなる値を新しい値に更新しなければいけない」ということも分かりました。

以下の情報を参考にしました

「解答:まちがった自動ログイン処理」で参考にした箇所

セキュリティのベストプラクティスとしてはクッキーにユーザに関連するいかなる情報(暗号化された情報を含む)も保存すべきではありません。クッキーにユーザに関連した情報(ユーザID、パスワード、メール、氏名、etc)を保存しなければならないシステムである場合、設計を見直し、ユーザ情報をクッキーに保存しなくても良い設計にしなければなりません。クッキーに保存しても良い値は、表示設定などのユーザ情報と関係の無い情報、セッションIDなどの予測不可能なランダムな値のみです。

ユーザIDくらいは大丈夫かと思ってたけど、そうだったのか。

【まとめ】

  • 自動ログイン機能は基本的にセキュリティ上のリスクを増加させるので安全性が重要なサービスでは実装しない。
  • 自動ログインの実装にはセッション管理と同様にランダムなクッキーの値を使用する。
  • 自動ログイン用のクッキーはログインの度に新しい値に更新する。(古い鍵の削除も忘れない)
  • ログイン時に自動ログインオプションが無効かつ自動ログイン用のクッキーが設定されている場合は自動ログインテーブルの該当レコードと自動ログインクッキーも削除する。
  • 自動ログイン用のクッキーは自動ログイン処理を行う場合にのみ必要なので自動ログイン専用のディレクトリを設定し、そのディレクトリのURLがリクエストされた場合にのみ送信する。(XSSリスクの低減)
  • ログアウトした場合、自動ログインクッキーが設定されている場合、自動ログインテーブルの該当レコードと自動ログインクッキーも削除する。
  • 実際の運用では古い自動ログイン用のレコードが溜まるので定期的に不要なレコードを削除する。

「安全なウェブサイトの作り方」で参考にした箇所

1.4 セッション管理の不備」が参考になった。
以下は項目名だけ抜粋

1) セッション ID を推測が困難なものにする
2) セッション ID を URL パラメータに格納しないようにする
3) HTTPS 通信で利用する Cookie には secure 属性を加える
4-1) ログイン成功後に、新しくセッションを開始するようにする
または
4-2) ログイン成功後に、既存のセッションIDとは別に秘密情報を発行し、ページの遷移毎にその値を確認する

IPA セキュア・プログラミング講座:Webアプリケーション編」で参考にした箇所

自動ログイン用のキーの特性として、セッションIDと同じ特性が必要らしいので、「第4章セッション対策セッション乗っ取り:#2 セッションID強度を高める」が参考になった。

(1) 公共に知られていそうなIDは使用しない
例えば、会員番号やユーザID等、公共に知られていそうな値では容易に推測が可能であるので、使用すべきではない。セッションIDは、人間が覚えておく必要は無いので、見た目が無意味な値であっても、まったく問題はない。

(2) ランダムなIDを使用する
00001、00002...等の規則性や連続性のある値は、推測が容易であるのでランダムなIDを使用する。例えば、ユーザIDや時刻等の情報と擬似乱数とを混ぜ合わせた文字列に対してハッシュ関数を使用することで、ランダムなIDが生成されるようになる。
ただし、擬似乱数生成関数やハッシュ関数を自前で作成することは避けるべきである。

(3) 桁数と文字種を多くする
桁数が短く、文字種の少ない場合、ブルートフォースアタックで破られてしまうおそれがある。これを防ぐためには、使用する文字種、桁数をなるべく多くし、総当りするパターンを増やすことである。例えば、英数小文字大文字で20桁の値を考えた場合、組み合わせとしては「(26+26+10)^20」通りとなる。

(4) ユニークなセッションIDを発行する
セッションIDは、どのような場合でも、常にユニークな値を発行するようにする。

  • Webアプリケーションを利用する他ユーザに対して発行する場合

同じセッションIDを発行してしまうと「なりすまし」のおそれがある。

  • 同一ユーザに対して発行する場合

セッションの度に同じIDを発行することがないようにする。これは一度セッションIDが盗まれてしまうと、Webアプリケーション内での「なりすまし」が永続的に可能となってしまうからである。

IPAの資料には、セッション設計のガイドラインはあるけど、自動ログインについての記述が無かったので、追加して欲しいと思った。

TwitterのOAuth認証を使ったサービスを開発する際の注意

OAuth を使ってソーシャル・ネットワーキング Web サイトにアクセスする: 第 2 回: OAuth 対応のWeb 版 Twitter クライアントを作成する」のサンプルをいじくっていて気づいたんですが、TwitterOAuth認証を使ったサービスを開発する場合、AccessTokenの管理方法に注意が必要です。
あんまり自信がないので、誤りがあればどんどん指摘してください。

以下の条件が成り立つ場合、サービス内でなりすましができてしまいます。

  1. サービス(Consumer)がtwitterIDとAccessTokenをひもづけて保管している。
  2. サービスがブラウザへ渡すcookieにtwitterIDをそのまま保存している
  3. ブラウザからサービスへtwitterIDを含むCookieが渡された場合、twitterIDをキーとしてサービス内に保存しているAccessTokenを検索し、存在していれば認証済みであるとみなす。
  4. なりすまし対象のアカウントがすでにOAuth認証済みで、サービス内になりすまし対象アカウントのAccessTokenが保存されている。

想定されるシナリオ

"サービス"はtwitterAPIを利用するWebアプリケーションです。
"Alice"(通常のユーザ)と"Carol"(なりすまし攻撃者)がいて、AliceがTwitterAPIを利用するサービスにOAuthで許可を与えたとします。

twitterからサービスへAlice用のAccessTokenが送信され、サービスはAliceのtwitterIDとひもづけて保存します。
twitterの認証画面からサービスの画面に戻った時に、サービスからAliceのブラウザにtwitterIDを含むcookieが送信されます。

次にAliceがサービスにアクセスした際に、サービスはcookieからtwitterIDを取得し、そのIDに対応するAccessTokenを取り出します。対応するAccessTokenが存在していれば、サービスはAliceがOAuthで許可を与えているものと見なします。

Carolはサービスにアクセスする際にAliceのスクリーンネームからtwitterIDを割り出し、AliceのtwitterIDを含むcookieを偽造することでサービス内でAliceになりすますことができます。

twitteIdはintの数値であり、これはTwitterAPIを使うことでスクリーンネームから容易に取得できます。ブラウザから確認したいのであれば、Twitter API Viewerのサービスを使ってみてください。

対策

サービス内でAccessTokenを保存する際のキーや、cookieに保存する値として適切なのは、以下の条件を満たすものです。

  • 容易に予測不可能
  • 一意で、ハッシュ値のように衝突しない

最後にひとこと

今後、このような事故がおこった場合、「OAuth認証によってパスワードによる認証が無くなってしまったため、これまで安全と思って送受信したり保存したりしていたIDがセキュリティ上の弱点となってしまった事例」とか言われるんだと思います。
これからOAuth対応でいろいろ手が入るサービスが出てくるでしょうが、気になる人はcookieにtwitteridが入っていないか注意してください。

mavenのレポートプラグインてんこ盛り設定

mavenレポートプラグインをてんこ盛りにしたpom.xmlの設定例です。

以下のレポートプラグインを使います。

Mavenレポジトリの設定

サードパーティのレポートプラグインを使うため必要です。

    <pluginRepositories>
        <pluginRepository>
            <id>Codehaus repository</id>
            <url>http://repository.codehaus.org/</url>
        </pluginRepository>
    </pluginRepositories>

プロジェクトの共通の設定

主に文字コードコンパイラバージョンを設定します。
これらのプロパティに対応しているプラグインでは設定パラメタの記述を省略し、対応していないプラグインはこのプロパティ設定を参照するようにしています。

    <properties>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <maven.compiler.target>1.6</maven.compiler.target>
        <maven.compiler.source>1.6</maven.compiler.source>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

ビルドプラグインの設定

タグ内に記述します。

cobertura-maven-pluginの設定

カバレッジ測定用の設定。

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>cobertura-maven-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <instrumentation>
                        <excludes>
                            <exclude>**/*Test.class</exclude>
                        </excludes>
                    </instrumentation>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>clean</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
maven-surefire-pluginの設定
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.5</version>
            </plugin>

レポートプラグインの設定

タグ内に記述します。

maven-site-pluginの設定

maven-site-plugin2.1を使うには、Maven 2.1.x 以降が必要です。
わざわざinputEncoding,outputEncodingを指定しているのは、指定しないとmavenプロパティの設定が使われず、ISO-8859-1が使われてしまうためです。

            <plugin>
                <artifactId>maven-site-plugin</artifactId>
                <version>2.1</version>
                <configuration>
                    <locales>ja</locales>
                    <inputEncoding>${project.build.sourceEncoding}</inputEncoding>
                    <outputEncoding>${project.reporting.outputEncoding}</outputEncoding>
                </configuration>
            </plugin>
maven-jxr-pluginの設定
            <plugin>
                <artifactId>maven-jxr-plugin</artifactId>
                <version>2.1</version>
                <configuration>
                    <aggregate>true</aggregate>
                    <inputEncoding>${project.build.sourceEncoding}</inputEncoding>
                    <outputEncoding>${project.reporting.outputEncoding}</outputEncoding>
                </configuration>
            </plugin>
maven-surefire-report-pluginの設定
            <plugin>
                <artifactId>maven-surefire-report-plugin</artifactId>
                <version>2.5</version>
                <configuration>
                    <showSuccess>false</showSuccess>
                </configuration>
            </plugin>
maven-checkstyle-pluginの設定
            <plugin>
                <artifactId>maven-checkstyle-plugin</artifactId>
                <version>2.5</version>
            </plugin>
maven-pmd-pluginの設定
            <plugin>
                <artifactId>maven-pmd-plugin</artifactId>
                <version>2.5</version>
                <configuration>
                    <targetJdk>${maven.compiler.target}</targetJdk>
                </configuration>
                <reportSets>
                    <reportSet>
                        <reports>
                            <report>pmd</report>
                            <report>cpd</report>
                        </reports>
                    </reportSet>
                </reportSets>
            </plugin>
findbugs-maven-pluginの設定

maven-site-pluginでlocaleをjaにしていると、文字化けを起こします。どうもwindows環境だとプラットフォームのデフォルトエンコーディングが優先されているみたいなのですが対処方法が分かりません。

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>findbugs-maven-plugin</artifactId>
                <version>2.3.1</version>
                <configuration>
                    <inputEncoding>${project.build.sourceEncoding}</inputEncoding>
                    <outputEncoding>${project.reporting.outputEncoding}</outputEncoding>
                </configuration>
            </plugin>
cobertura-maven-pluginの設定
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>cobertura-maven-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <formats>
                        <format>html</format>
                    </formats>
                </configuration>
            </plugin>
jdepend-maven-pluginの設定
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>jdepend-maven-plugin</artifactId>
                <version>2.0-beta-2</version>
            </plugin>
javancss-maven-pluginの設定
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>javancss-maven-plugin</artifactId>
                <version>2.0</version>
            </plugin>

まとめ

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>example</groupId>
    <artifactId>maven-report-example</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>maven-report-example</name>
    <properties>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <maven.compiler.target>1.6</maven.compiler.target>
        <maven.compiler.source>1.6</maven.compiler.source>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
    <pluginRepositories>
        <pluginRepository>
            <id>Codehaus repository</id>
            <url>http://repository.codehaus.org/</url>
        </pluginRepository>
    </pluginRepositories>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-library</artifactId>
            <version>1.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.1</version>
                <configuration>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>cobertura-maven-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <instrumentation>
                        <excludes>
                            <exclude>**/*Test.class</exclude>
                        </excludes>
                    </instrumentation>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>clean</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.5</version>
            </plugin>
        </plugins>
    </build>
    <reporting>
        <plugins>
            <plugin>
                <artifactId>maven-site-plugin</artifactId>
                <version>2.1</version>
                <configuration>
                    <locales>ja</locales>
                    <inputEncoding>${project.build.sourceEncoding}</inputEncoding>
                    <outputEncoding>${project.reporting.outputEncoding}</outputEncoding>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-jxr-plugin</artifactId>
                <version>2.1</version>
                <configuration>
                    <aggregate>true</aggregate>
                    <inputEncoding>${project.build.sourceEncoding}</inputEncoding>
                    <outputEncoding>${project.reporting.outputEncoding}</outputEncoding>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-surefire-report-plugin</artifactId>
                <version>2.5</version>
                <configuration>
                    <showSuccess>false</showSuccess>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-checkstyle-plugin</artifactId>
                <version>2.5</version>
            </plugin>
            <plugin>
                <artifactId>maven-pmd-plugin</artifactId>
                <version>2.5</version>
                <configuration>
                    <targetJdk>${maven.compiler.target}</targetJdk>
                </configuration>
                <reportSets>
                    <reportSet>
                        <reports>
                            <report>pmd</report>
                            <report>cpd</report>
                        </reports>
                    </reportSet>
                </reportSets>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>findbugs-maven-plugin</artifactId>
                <version>2.3.1</version>
                <configuration>
                    <inputEncoding>${project.build.sourceEncoding}</inputEncoding>
                    <outputEncoding>${project.reporting.outputEncoding}</outputEncoding>
                </configuration>

            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>cobertura-maven-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <formats>
                        <format>html</format>
                    </formats>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>jdepend-maven-plugin</artifactId>
                <version>2.0-beta-2</version>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>javancss-maven-plugin</artifactId>
                <version>2.0</version>
            </plugin>
        </plugins>

    </reporting>
</project>


NetBeansでlombokを使う

Java の冗長性を排除する手軽な方法」で紹介されていたlombokというライブラリが便利そうだったので、NetBeansで試してみた。
一言で言うと、lombokはJavaBeanのsetterやgetterを省略可能にして記述量を減らしてくれるライブラリ。JavaBeansのメソッドはIDE機械的に生成できるけど、このくらいの単純な仕様にして欲しかった。

ふつうのライブラリとちょっと違うのは、IDEコンパイラアノテーションを処理できるようにインストールが必要なこと。javacで扱う分にはjarがクラスパスに入ってればいいらしいけど。

lombokのインストール手順

  1. Project Lombokをブラウザで開く
  2. lombok.jarをダウンロードする
  3. lombpk.jarを実行する(lombokインストーラが起動する)
  4. lombokインストーラが起動し、インストールされているIDEを検索する
  5. lombokをインストールしたいIDEにチェックを入れる。
  6. インストールが終わると次のようなダイアログが表示される
    • IDE起動時の設定ファイルはインストーラが変更してくれる。ダイアログの説明だと、netbeansを起動する際にパラメタを追加する必要があるなんてことが書いてあるけど。
  7. NetBeansを再起動して、IDElombokアノテーションが処理されてsetterやgetterが生成されているのを確認すればインストール完了。

Mavenで使う場合

Use Lombok via Mavenに従えばよい。

実用上の懸念

たいして使い込んでないけど、実際に業務などで使う場合の懸念点を思いつきであげてみた。

jarを一本化するonejar-maven-pluginを使ってみた

javaプロジェクトを簡単に単一jarファイルで配布する - lime55の日記」から
One-JARでアプリケーションの配布を単純化」経由で
Google Code Archive - Long-term storage for Google Code Project Hosting.」を見つけたので試してみる。

One-Jarの仕組みをごくおおざっぱに説明すると、

  1. Jarの中にJarが入れ子で入っている。
  2. 外側のJarのMainクラス=外側のJarに含まれる特別なクラスローダが起動される
  3. 入れ子になっている内側のJarをロードする
  4. 入れ子のMainクラスを起動する

という感じ。

これまで、「Mavenで配布用zipファイルを作成する - Sacrificed & Exploited」でいろいろな配布パッケージを作る方法を試したけど、jar一個ですむならそっちの方が楽だし。

onejar-maven-pluginの使い方

onejar-maven-pluginの使い方はここにある。

パターンAとパターンBの2つの方法があるみたい。

  • パターンAは、maven-jar-pluginでmainクラスを指定する。
  • パターンBは、onejar-maven-pluginでmainクラスを指定する。

という違いくらい。


以下のpom.xmlはパターンBの例で、「example.onejarexample.SearchFrame」をmainclassに指定している。

<project>
    ....
    <build>
        <plugins>
         ....
            <plugin>
                <groupId>org.dstovall</groupId>
                <artifactId>onejar-maven-plugin</artifactId>
                <version>1.4.1</version>
                <executions>
                    <execution>
                        <configuration>
                            <mainClass>example.onejarexample.SearchFrame</mainClass>
                            <!-- Optional -->
                            <onejarVersion>0.96</onejarVersion>
                            <!-- Optional, default is false -->
                            <attachToBuild>true</attachToBuild>
                            <!-- Optional, default is "onejar" -->
                            <classifier>onejar</classifier>
                        </configuration>
                        <goals>
                            <goal>one-jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
         ....
        </plugins>
    </build>

    <pluginRepositories>
        <pluginRepository>
            <id>onejar-maven-plugin.googlecode.com</id>
            <url>http://onejar-maven-plugin.googlecode.com/svn/mavenrepo</url>
        </pluginRepository>
    </pluginRepositories>
    ....
</project>

設定ファイルが読み込めない場合がある

プロパティファイルやXMLファイルの読み込み方法によって、onejar化した場合に設定ファイルを読み込めない場合がある。
クラスローダ経由(ClassLoader.getResource())でクラスパス上の設定ファイルを読み込んでいる場合は、外部ファイルのファイルをクラスローダの検索範囲に入れることが出来ないため、設定ファイルを読み込むことができない。

ファイルとして設定ファイルを直接オープンしている分には問題ない。

内側のjarに記述したManifestファイルが読み込まれないので、そこにClass-Pathの設定を記述しても無視されてしまうためらしい。入れ子になっているjar内に配置するとクラスパスに入るけど、これじゃあ設定ファイルの意味ないし....

まとめ

個人的にはちょっと使いどころの難しいプラグインだと思う。
設定ファイルも含めて配布するような場合、maven-jar-pluginとmaven-assembly-pluginを組み合わせた方が無難かなと。

  • メリット
    • jar-with-dependenciesのようにjarを分解せずに一つに出来る。
    • 実行可能JARとして作成できる。
  • デメリット
    • jarをダブルクリックで起動した場合、起動段階でエラーになっても気づかない。
    • コンソールアプリの場合、バッチファイルが別途必要になる。
    • 設定ファイルの読み込み方によっては、onejar化に向かない場合がある。