Spring Roo 入門: 第 5 回 Spring Roo のアドバンスド・アドオンおよびラッパー・アドオンを作成する

Spring Roo の能力を増強する

Spring Roo のアドバンスド・アドオンは、Java コードをアプリケーションに追加するためのメカニズムとなります (例えば、ドメイン・オブジェクトの equals メソッドと hashcode メソッドを作成可能なアドオンを作成するなど)。アドバンスド・アドオンのテンプレートは、addon create コマンドで作成することができます。テンプレートがあれば、開発者のさまざまな要件を満たすために、そのテンプレートを拡張することができます。この記事では、アドバンスド・アドオンを作成する手順を具体的に説明します。

Shekhar Gulati, Senior Consultant, Xebia

Photo of Shekar GulatiShekhar Gulati は、Xebia India の Java コンサルタントです。これまで 6 年以上、Enterprise Java の経験があり、Spring、Spring-WS、Spring Roo など、一連の Spring プロジェクトに関する幅広い経験を持っています。彼が関心を持っている分野には、Spring、NoSQL データベース、Hadoop、Spring Roo のような RAD フレームワーク、クラウド・コンピューティング (主に Google App Engine、CloudFoundry、OpenShift などの PaaS サービス) などがあります。彼は執筆活動も活発に行っていて、JavaLobby、Developer.com、IBM developerWorks、そして彼自身のブログ (http://whyjava.wordpress.com) などに寄稿しています。Twitter で彼をフォローするには、http://twitter.com/#!/shekhargulati にアクセスしてください。



2012年 5月 17日

連載「Spring Roo 入門」の第 3 回で、Spring Roo のアドオン・アーキテクチャーと、addon create コマンドを使って国際化対応アドオンとシンプル・アドオンを作成する方法を説明しました。今回の記事では、Spring Roo でサポートされている残りの 2 つのタイプのアドオン、つまりアドバンスド・アドオンとラッパー・アドオンを取り上げます。この記事を読む前に、第 3 回の記事を読むことをお勧めします。

アドバンスド・アドオンの概要

アドバンスド・アドオンを使用することで、Spring Roo はシンプル・アドオンで対処できるすべての処理を行える (Maven POM ファイルを依存関係やプラグインで更新するなど) だけでなく、構成ファイルを更新または追加することに加え、既存の Java 型を拡張したり、AspectJ ITD を使って新しい Java 型を導入したりすることもできるようになります。ソース・コードを追加できることが、アドバンスド・アドオンを他のアドオンよりも強力なものにしています。Spring Roo のアドバンスド・アドオンの作成作業に入る前に、まずは、Spring Roo が提供している既存のアドバンスド・アドオンについて検討します。


アドバンスド・アドオンの動作

既存のアドバンスド・アドオンの 1 つに、永続化関連の処理を行う JPA があります。つまり、データベースのサポートを追加して、新しいエンティティーを作成するアドオンです。このアドオンの動作を確かめるには、Roo シェルを開いて、リスト 1 に記載するコマンドを実行してください。この記事では、Spring Roo のバージョン 1.2.0.M1 を使用します。

リスト 1. JPA の例
project --topLevelPackage com.dw.demo --projectName entity-demo 
jpa setup --database FIREBIRD --provider HIBERNATE 
entity --class ~.domain.Book

jpa setup コマンドと entity コマンドは、いずれも org.springframework.roo.addon.jpa という名前のアドバンスド・アドオンに相当します。Roo シェルでの jpa setup コマンドと entity コマンドの出力を見れば、シンプル・アドオンとアドバンスド・アドオンとの違いは明らかに見分けられるはずです。リスト 2 に、jpa setup コマンドの出力を記載します。

リスト 2. jpa setup コマンドの出力
Created SRC_MAIN_RESOURCES/META-INF/spring/database.properties 
Updated ROOT/pom.xml [added dependencies ...] 
Updated SRC_MAIN_RESOURCES/META-INF/spring/applicationContext.xml 
Created SRC_MAIN_RESOURCES/META-INF/persistence.xml

jpa setup コマンドの出力には、このコマンドが pom.xml に依存関係を追加し、Spring の applicationContext.xml を更新し、永続化専用の persistence.xml を作成するなど、構成の役割を行っていることが示されています。jpa setup コマンドは Java ソース・コードを作成または更新することはないため、シンプル・アドオンに相当すると考えられます。上記のようなセットアップのシナリオには、シンプル・アドオンを使用してください。

続いて、entity コマンドの出力をリスト 3 に記載します。

リスト 3. entity コマンドの出力
Created SRC_MAIN_JAVA/com/dw/demo/domain 
Created SRC_MAIN_JAVA/com/dw/demo/domain/Book.java 
Created SRC_MAIN_JAVA/com/dw/demo/domain/Book_Roo_Configurable.aj 
Created SRC_MAIN_JAVA/com/dw/demo/domain/Book_Roo_Jpa_Entity.aj 
Created SRC_MAIN_JAVA/com/dw/demo/domain/Book_Roo_Entity.aj 
Created SRC_MAIN_JAVA/com/dw/demo/domain/Book_Roo_ToString.aj

上記の出力から、このコマンドによって Book.java という 1 つの Java ファイルと、4 つの *.aj ファイルが作成されることがわかります。アドバンスド・アドオンを識別するための黄金律は、Java ファイルまたは *.aj ファイル、あるいはこの entity コマンドの例のように、その両方が生成されているかどうかです。これらの *Roo_*.aj ファイルは、型間宣言 (ITD、Inter-type Declaration) と呼ばれます。ITD では、ある型 (側面) を使って別の型を宣言することができます。つまり、メソッドやフィールドを追加したり、型の階層を変更したりすることによって、任意の型の静的構造を変えられるということです。Roo は ITD をコード生成の成果物として使用し、その存続期間全体にわたって管理します。Roo は ITD を使用して個々のコンパイル単位でコードを生成することができますが、各 ITD は同じ 1 つのコンパイル済みクラスに合成されます。

entity コマンドの出力を目にしたところで、これらの成果物 (.java ファイルと .aj ファイル) が Spring Roo によって生成される仕組みを考えてください。リスト 4 に一例として、Book.java ファイルを記載します。

リスト 4. Book.java ファイル
package com.dw.demo.domain; 

import org.springframework.roo.addon.entity.RooEntity; 
import org.springframework.roo.addon.javabean.RooJavaBean; 
import org.springframework.roo.addon.tostring.RooToString; 

@RooJavaBean 
@RooToString 
@RooEntity 
public class Book { 
}

これは通常の Java ファイルのように見えますが、クラスのアノテーションは例外です。アノテーションの名前と .aj ファイルを見比べると、これらのアノテーションのいくつかは、.aj ファイルによって追加された機能に対応しているのは明らかです。例えば、RooToString は Book_Roo_ToString.aj ファイルおよび toString() メソッドに対応します。RooEntity は、Book_Roo_Entity.aj ファイル、Book_Roo_Jpa_Entity.aj ファイル、および永続化関連のメソッドに対応します。残りのアノテーションについては、とりあえず無視して構いません。アノテーションによって ITD が生成される仕組みを理解するには、Spring Roo がアドバンスド・アドオン機能を提供する方法を探る必要があります。

  1. Spring Roo は Roo シェルの起動時に、すべてのクラスをスキャンして、CommandMarker インターフェースを実装するすべてのクラスを登録します。CommandMarker インターフェースは Roo に対し、登録されたこれらのクラスが、このアドオンで実行可能なコマンドを定義することを伝えます。
  2. すべてのアドバンスド・アドオンはそれぞれのサービスを、Spring Roo が提供する OSGi ランタイムに登録します。アドバンスド・アドオンのサービスが指定するのは、コードの生成をトリガーする条件です。すべてのアドバンスド・アドオンのトリガー・ポイントとなるのは、アノテーションの存在です。例えば、toString() メソッドを生成するアドバンスド・アドオンは、Java 型に RooToString アノテーションが設定されている場合にのみトリガーされます。これは、他のアノテーションの場合にも当てはまります。
  3. このアドオンは、entity --class ~.domain.Book を使用する時点で、アノテーションが含まれる Book.java という Java ファイルを作成します。他のアドオンは、Java 型にそれぞれに対応するアノテーションが設定されている場合にトリガーされ、アノテーションに対応する .aj ファイルを作成します。

以上の説明は、実際に独自のアドバンスド・アドオンを作成してみると、より明白に理解できるようになるはずです。

org.springframework.roo.addon.jpa アドオンは、Spring Roo が提供するアドバンスド・アドオンの一例に過ぎません。この他にも、GWT、コントローラー、JSON など、さまざまなアドバンスド・アドオンがあります。Spring Roo の 1.2.0 リリースには、さらに 2 つのアドバンスド・アドオンとして、addon-equals と addon-jsf が追加されています。addon-equals はエンティティーの equals メソッドおよび hashcode メソッドの実装を提供するアドオンで、addon-jsf は Spring Roo アプリケーションに JSF サポートを提供するアドオンです。最新の Spring Roo スナップショットを使用するには、Spring Roo コードをビルドするか、Spring Roo リポジトリーからナイトリー・スナップショットをダウンロードしてください。


エンティティー・クラスに compareTo() メソッドを組み込む

java.lang.Comparable インターフェースを実装して compareTo() メソッドの実装を提供するには、通常、値オブジェクトまたはエンティティーが必要です。Comparable インターフェースは、その実装クラスの全オブジェクトに順序を付けます。Comparable を実装することで、以下の操作が可能になります。

  1. java.util.Collections.sort および java.util.Collections.binarySearch の呼び出し
  2. java.util.Arrays.sort および java.util.Arrays.binarySearch の呼び出し
  3. java.util.TreeMap 内のキーとしてのオブジェクトの使用
  4. java.util.TreeSet 内の要素としてのオブジェクトの使用

この記事では、アプリケーションで作成されるエンティティーに compareTo() の実装を提供するアドバンスド・アドオンを作成します。アプリケーションに Java コードを追加する場合には、アドバンスド・アドオンを作成する必要があります。


プロジェクトのセットアップ

Google コードにプロジェクトと Maven リポジトリーをセットアップする方法は Spring Roo のドキュメントで詳細に説明しているので、ここでその説明を繰り返すことはしません。この記事では、プロジェクトを「spring-dw-roo-compareto-addon」と名付けます。

Spring Roo の最新バージョンである 1.2.0.RC1 を使用していない場合は、Spring Roo プロジェクトの Web サイトからダウンロードしてください。ダウンロードしたファイルは、連載第 1 回の説明に従って、解凍してインストールしてください。

Spring Roo の以前のバージョンで使用されていたクラスのなかには、非推奨になったもの、または削除されたものがあります。


アドバンスド・アドオンを作成する

プロジェクトのセットアップが完了すると、spring-dw-roo-compareto-addon という名前のディレクトリーが作成されていることがわかります。この時点でこのディレクトリーに含まれているのは、.svn フォルダーだけです。コマンドラインで spring-dw-roo-compareto-addon ディレクトリーに移動して、Roo シェルを起動してください。そして、以下のコマンドを実行します。

addon create advanced --topLevelPackage org.xebia.roo.addon.compareto --projectName spring-dw-roo-compareto-addon

たったこれだけで、アドバンスド・アドオンを作成することができます。

次に、Roo シェルで perform package コマンドを実行してアドオン jar を作成します。リスト 5 には、addon create advanced コマンドによって作成されたファイルが示されています。

リスト 5. addon create advanced コマンドによって生成されたファイル
Created ROOT/pom.xml 
Created SRC_MAIN_JAVA 
Created SRC_MAIN_RESOURCES 
Created SRC_TEST_JAVA 
Created SRC_TEST_RESOURCES 
Created SPRING_CONFIG_ROOT 
Created ROOT/readme.txt 
Created ROOT/legal 
Created ROOT/legal/LICENSE.TXT 
Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto 
Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/ComparetoCommands.java 
Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/ComparetoOperations.java 
Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/ComparetoOperationsImpl.java 
Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/ComparetoMetadata.java 
Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/ComparetoMetadataProvider.java 
Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/RooCompareto.java 
Created ROOT/src/main/assembly 
Created ROOT/src/main/assembly/assembly.xml 
Created SRC_MAIN_RESOURCES/org/xebia/roo/addon/compareto 
Created SRC_MAIN_RESOURCES/org/xebia/roo/addon/compareto/configuration.xml

上記に示されているファイルのうち、pom.xml、readme.txt、license.txt などのファイルについては第 3 回で説明したので、改めて紹介する必要はありません。今回の記事に関係するのは、以下の成果物です。

  • ComparetoCommands.java
  • ComparetoOperations.java
  • ComparetoOperationsImpl.java
  • ComparetoMetadata.java
  • ComparetoMetadataProvider.java
  • RooCompareto.java

上記の成果物のそれぞれについて説明します。

  • ComparetoCommands.java: このクラスは CommandMarker インターフェースを実装して、2 種類のメソッドを公開します。そのうち一方は CliAvailablityIndicator アノテーションが付けられたメソッド、もう一方は CliCommand アノテーションが付けられたメソッドです。CliAvailablityIndicator アノテーションは、Spring Roo にこのコマンドを可視にする条件を指示します。例えば、entity コマンドは、ユーザーが永続化の設定を Roo シェルで定義するか、またはプロジェクトに直接定義するまで使用可能になりません。一方、@CliCommand アノテーションが付けられたメソッドは、コマンドを Roo シェルに登録します。@CliCommand アノテーションには 2 つの属性があります。1 つはコマンド名を定義する value、もう 1 つは、help コマンドが入力されたときに表示するヘルプ・メッセージを定義する help です。*Commands クラスについての詳しい説明は、第 3 回を参照してください。
  • ComparetoOperationsImpl.java: ComparetoCommands クラスは、処理のすべてを ComparetoOperationsImpl クラスに委任します。このクラスでは、以下の 4 つのメソッドが生成されます。
    • isCommandAvailable(): このメソッドは、ComparetoCommands クラスの CliAvailabilityIndicator ComparetoCommands アノテーションが付いたメソッドによって呼び出され、該当するコマンドを可視にするかどうかをチェックします。その目的は、コマンドがコンテキストを認識することを確実にするためです。このメソッドはさまざまな検証を行います。例えば、プロジェクトが作成された後にコマンドを可視にするか、あるいは永続化のセットアップが行われた後にコマンドを可視にするかなどの検証です。コマンドを可視にするための条件の設定は必須ではありません。単純に true を返すことで、コマンドを常に可視にすることができます。
    • setup(): このメソッドを呼び出す ComparetoCommands クラスの setup() メソッドには、@CliCommandアノテーションが付けられます。このコードは、セットアップ関連のタスクをこのクラスが行うことを明確にします。セットアップ関連のタスクとは、第 3 回の Jamon Roo アドオンで行っているような、Maven 依存関係の追加、Maven リポジトリーの追加、Spring コンテキスト・ファイルの作成または更新などです。
    • annotateType(): このメソッドと次に説明する annotateAll() は、シンプル・アドオンにはない新しいメソッドです。このメソッドは、指定の Java 型にアノテーション (RooCompareto) を追加する役目を果たします。Spring Roo が提供するサービスを使用して、特定の Java 型に関する Class 詳細を取得し、RooJCompareto アノテーションを追加します。
    • annotateAll(): このメソッドは、RooJavaBean アノテーションが付けられたすべての型を検出し、それらの型のすべてで annotateType() メソッドを呼び出します。すべてのエントリーに RooCompareto アノテーションを付ける場合には、このメソッドを使用します。
  • RooCompareto.java: このアノテーションの存在によって、アドオンがトリガーされ、コードが生成されます。
  • ComparetoMetadataProvider.java: このクラスは、Spring Roo のサービスです。Roo はこのクラスを呼び出して、アドオンのメタデータを取得します。このクラスは、メタデータを追加および削除するためのトリガーを登録します。このクラス内での変更は必要ありませんが、このクラスには getMetadata() というメソッドが 1 つあり、Spring Roo は RooCompareto アノテーションが付けられた Java 型がある場合には常にこのメソッドを呼び出すことを覚えておいてください。
  • ComparetoMetadata.java: このクラスの役目は、アドオンに対応する ITD を生成することです。生成されたコードで、ItdTypeDetailsBuilder という名前のクラスを使用して、フィールドとメソッドを持つ ITD を作成します。この後、デフォルトで生成されたコードを、compareTo メソッドを追加して Comparable インターフェースを実装するための要件を満たすように変更します。

要件を満たすようにアドオンを変更する

compareTo メソッドをエンティティー・クラスに追加するアドオンを作成するには、以下の手順に従います。

  • ターゲット・プロジェクトに、commons-lang バージョン 3.1 の Maven 依存関係を追加します。compareTo メソッドを作成するために使用する CompareToBuilder ビルダー・クラスは、commons-lang が提供するため、この作業が必要になります。
  • エンティティー・クラスに Comparable インターフェースを実装させます。
  • compareTo メソッドの ITD を作成します。

Maven 依存関係を追加する

上記の要件を満たすためには、ComparetoOperationsImpl クラスと ComparetoMetadata クラスに変更を加える必要があります。以下のように、必要な変更を順に行います。

  1. まず、ターゲット・プロジェクトに Maven commons-lang 依存関係を追加します。configuration.xml ファイルを更新して、デフォルトで指定されている Spring Batchの依存関係の代わりに commons-lang 依存関係を指定します (リスト 6 を参照)。
    リスト 6. configuration.xml ファイルを更新する
    <?xml version="1.0" encoding="UTF-8" standalone="no"?> 
    <configuration>
       <dependencies>
          <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
             <version>3.1</version>
          </dependency>
       </dependencies>
    </configuration>
  2. 次に、ComparetoOperationsImpl クラスの setup() メソッドの実装を変更して、Spring Batch の Maven 依存関係ではなく、commons-lang の Maven 依存関係を読み込むようにします (リスト 7 を参照)。annotateType() メソッドと annotateAll() メソッドは変更する必要がないため、以下のリストには記載していません。
    リスト 7. setup() メソッドの実装を変更する
    @Component
    @Service
    public class ComparetoOperationsImpl implements ComparetoOperations {
    
        @Reference
        private ProjectOperations projectOperations;
    
        @Reference
        private TypeLocationService typeLocationService;
    
        @Reference
        private TypeManagementService typeManagementService;
    
        /** {@inheritDoc} */
            public void setup() {
         // Install the add-on Google code repository needed to get the annotation
            projectOperations.addRepository("",
               new Repository("Compareto Roo add-on repository",
               "Compareto Roo add-on repository",
               "https://spring-dw-roo-compareto-addon.googlecode.com/svn/repo"));
            List<Dependency> dependencies = new ArrayList<Dependency>();
         // Install the dependency on the add-on jar (
            dependencies.add(new Dependency("org.xebia.roo.addon.compareto",
               "org.xebia.roo.addon.compareto", "0.1.0.BUILD-SNAPSHOT",
               DependencyType.JAR, DependencyScope.PROVIDED));
            Element configuration = XmlUtils.getConfiguration(getClass());
            for (Element dependencyElement : XmlUtils.findElements(
                    "/configuration/dependencies/dependency",
                    configuration)) {
                       dependencies.add(new Dependency(dependencyElement));
                    }
            projectOperations.addDependencies("", dependencies);
        }
    }

以上で行った変更は、第 3 回で Jamon シンプル・アドオンを作成するために行った変更と同様です。

エンティティー・クラスに Comparable インターフェースを実装させる

コードを変更して Maven 依存関係を追加した後は、エンティティー・クラスが java.lang.Comparable インターフェースを実装することを確実にする必要があります。そのためには、ComparetoMetadata クラスによって生成された ITD を変更します。これらのメタデータ・クラスが ITD を生成するために使用する ItdTypeDetailsBuilder クラスは、メソッド、フィールド、アノテーション、インターフェースなど、さまざまなものを ITD に追加する各種の追加メソッドを提供します。Java 型でインターフェースを実装するには、ItdTypeDetailsBuilder クラスの addImplementsType メソッドを使用します (リスト 8 を参照)。ITD の作成は ComparetoMetadata コンストラクター内で行われるので、以下のリストには、このコンストラクターだけを記載します。

リスト 8. java.lang.Comparable インターフェースを実装する
public ComparetoMetadata(String identifier, JavaType aspect Name,
    PhysicalTypeMetadata  governorPhysicalTypeMetadata) {
        super(identifier, aspect Name, governorPhysicalTypeMetadata);
        Assert.isTrue(isValid(identifier), "Metadata identification string '" + 
            identifier + "' does not appear to be a valid");
        JavaType comparableInterface = new JavaType("java.lang.Comparable");
        builder.addImplementsType(comparableInterface);
        itdTypeDetails = builder.build();
    }

compareTo メソッドの ITD を作成する

Java 型で Comparable インターフェースを実装した後、今度は compareTo メソッドの実装を提供する必要があります。compareTo メソッドを作成するための「流れるようなインターフェース」を提供するのは、CompareToBuilder クラスです。Spring Roo の equals アドオンは、EqualsBuilder と HashcodeBuilder を使って equals メソッドと hashcode メソッドを実装します。ここでは、CompareToBuilder が compareTo メソッドの作成を支援する方法を明確にするために Book というエンティティーを例として取り上げます。この Book エンティティーの compareTo メソッドを、CompareToBuilder を使って実装します。リスト 9 に、Book クラスと compareTo メソッドを記載します。

リスト 9. Book クラスと compareTo メソッド
import org.apache.commons.lang3.builder.CompareToBuilder;

public class Book implements Comparable {
    private String title;
    private String author;
    private double price;
    public Book(String title, String author, double price) {
        this.title = title;
        this.author = author;
        this.price = price;
    }

    // getters and setters

    public int compareTo(Book o) {
	if(!(o instanceof Book)){
	  return -1;
	}
	Book book = (Book)o
        return new CompareToBuilder().append(this.title, book.title).append(this.author, 
            book.author).append(this.price, book.price).toComparison();
    }

    @Override
    public String toString() {
        return "Book [title=" + title + ", author=" + author + ", price=" + price + "]";
    }

}

リスト 9 の compareTo メソッドは、以下の操作を行います。

  • o が instanceOf Book でなければ、-1 を返します。
  • o が instanceOf Book である場合、型を Book にキャストします。
  • CompareToBuilder クラスのオブジェクトを作成した後、フィールドで append メソッドを呼び出します。

以下の手順に従って、compareTo を段階的に作成します。

  1. o が instanceOf Book でなければ、-1 を返します。

    instanceOf のチェックを追加する前に、compareTo メソッドを作成します。リスト 10 を参照してください。

    リスト 10. compareTo メソッドを作成する
    public ComparetoMetadata(String identifier, JavaType aspect Name, 
        PhysicalTypeMetadata governorPhysicalTypeMetadata) { 
            super(identifier, aspect Name, governorPhysicalTypeMetadata); 
            Assert.isTrue(isValid(identifier), 
                "Metadata identification string '" + identifier + 
                "' does not appear to be a valid"); 
    
        JavaType comparableInterface = new JavaType("java.lang.Comparable"); 
        builder.addImplementsType(comparableInterface); 
        builder.addMethod(getCompareToMethod()); 
    
        itdTypeDetails = builder.build(); 
    } 
    
    private MethodMetadata getCompareToMethod() { 
        final JavaType parameterType = JavaType.OBJECT; 
        final List<JavaSymbolName> parameterNames = 
            Arrays.asList(new JavaSymbolName("obj")); 
        final InvocableMemberBodyBuilder bodyBuilder = 
            new InvocableMemberBodyBuilder(); 
        bodyBuilder.appendFormalLine("return -1;"); 
        final MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(getId(), 
            Modifier.PUBLIC, new JavaSymbolName("compareTo"), 
            JavaType.INT_PRIMITIVE, 
                AnnotatedJavaType.convertFromJavaTypes(parameterType), 
            parameterNames, bodyBuilder); 
            return methodBuilder.build(); 
    }

    getCompareToMethod() は、MethodMetadataBuilder クラスを使用して compareTo メソッドのメタデータを生成します。MethodMetadataBuilder は、Spring Roo がメソッドのメタデータを作成するために提供している Builder クラスです。メソッドのメタデータを作成するには、最初に MethodMetadataBuilder オブジェクトを作成します。その際、アクセス修飾子、メソッド名、戻り型、パラメーター・リストなどの引数を渡すか、リスト 11 のように、compareTo メソッドのメタデータを作成するためのメソッド本体のビルダーを渡します。

    リスト 11. instanceOf のチェック
    private MethodMetadata getCompareToMethod() { 
            final JavaType parameterType = JavaType.OBJECT; 
            String parameterName = "obj"; 
            final List<JavaSymbolName> parameterNames = 
                Arrays.asList(new JavaSymbolName(parameterName)); 
            final InvocableMemberBodyBuilder bodyBuilder = 
                new InvocableMemberBodyBuilder(); 
            final String typeName = destination.getSimpleTypeName(); 
            bodyBuilder.appendFormalLine("if (!(" + parameterName + 
                " instanceof " + typeName + ")) {"); 
            bodyBuilder.indent(); 
            bodyBuilder.appendFormalLine("return -1;"); 
            bodyBuilder.indentRemove(); 
            bodyBuilder.appendFormalLine("}"); 
    
            bodyBuilder.appendFormalLine("return -1;"); 
            final MethodMetadataBuilder methodBuilder = 
                new MethodMetadataBuilder(getId(), 
                Modifier.PUBLIC, new JavaSymbolName("compareTo"), 
                JavaType.INT_PRIMITIVE, 
                AnnotatedJavaType.convertFromJavaTypes(parameterType), 
                parameterNames, bodyBuilder); 
            return methodBuilder.build(); 
        }
  2. o が instanceOf Book である場合、o を Book 型にキャストします。

    次のステップは、compareTo メソッドを作成できるようにするためのキャストです。それには、instanceOf チェックの後に以下の行を追加します。

    bodyBuilder.appendFormalLine(typeName + " rhs = (" + typeName + ") " + 
        OBJECT_NAME + ";");
  3. CompareToBuilder クラスのオブジェクトを作成した後、フィールドで append メソッドを呼び出します。

    compareTo メソッドを作成するためには、クラスのすべてのフィールドにアクセスする必要があります。ComparetoMetadata クラスには型に関する情報が何も含まれていないため、クラスのフィールドを取得することができません。この情報は、リスト 12 の ComparetoMetadataProvider によって提供することができます。

    リスト12. ComparetoMetadataProvider
    protected ItdTypeDetailsProvidingMetadataItem getMetadata(String metadataId,
        JavaType aspect Name, PhysicalTypeMetadata governorPhysicalTypeMetadata, 
        String itdFilename) { 
    
            final String[] excludeFields = {};
    
            final MemberDetails memberDetails = 
                getMemberDetails(governorPhysicalTypeMetadata); 
            if (memberDetails == null) { 
                return null; 
            } 
    
            final JavaType javaType = 
                governorPhysicalTypeMetadata.getMemberHoldingTypeDetails().getName();
    
            final List<FieldMetadata> compareToFields = 
                locateFields(javaType, excludeFields, memberDetails, metadataId); 
    
            return new ComparetoMetadata(metadataId, aspect Name,
            governorPhysicalTypeMetadata, compareToFields);
        } 
    
    private List<FieldMetadata> locateFields(final JavaType javaType, final String[]
            excludeFields, final MemberDetails memberDetails, final String
            metadataIdentificationString) { 
    
    	final SortedSet<FieldMetadata> locatedFields = new TreeSet<FieldMetadata>(new
        	  Comparator<FieldMetadata>() { 
                public int compare(final FieldMetadata l, final FieldMetadata r) { 
                    return l.getFieldName().compareTo(r.getFieldName()); 
                } 
            }); 
    
            final List<?> excludeFieldsList = 
                CollectionUtils.arrayToList(excludeFields); 
            final FieldMetadata versionField = 
                persistenceMemberLocator.getVersionField(javaType); 
    
            for (final FieldMetadata field : memberDetails.getFields()) { 
                if (excludeFieldsList.contains(field.getFieldName().getSymbolName())) { 
                    continue; 
                } 
                if (Modifier.isStatic(field.getModifier()) ||
            Modifier.isTransient(field.getModifier()) ||
          		field.getFieldType().isCommonCollectionType() 
                        || field.getFieldType().isArray()) { 
                    continue; 
                } 
                if (versionField != null && 
                    field.getFieldName().equals(versionField.getFieldName())) { 
                        continue; 
                    } 
    
                locatedFields.add(field); 
    
                metadataDependencyRegistry.registerDependency(
                    field.getDeclaredByMetadataId(), 
                    metadataIdentificationString
                ); 
            } 
    
            return new ArrayList<FieldMetadata>(locatedFields); 
        }

    フィールドを取得したら、これらのフィールドを ComparetoMetadata クラスに渡します。これで、このクラスが compareTo メソッドを作成できるようになります (リスト 13 を参照)。

    リスト 13. メタデータを渡して compareTo メソッドを作成する
    private List<FieldMetadata> compareToFields; 
    
    public ComparetoMetadata(String identifier, JavaType aspectName, 
        PhysicalTypeMetadata governorPhysicalTypeMetadata, 
        List<FieldMetadata> compareToFields) { 
    
            super(identifier, aspectName, governorPhysicalTypeMetadata); 
            Assert.isTrue(isValid(identifier), 
                "Metadata identification string '" + identifier + 
                    "' does not appear to be a valid"); 
    
            this.compareToFields = compareToFields; 
            if (!CollectionUtils.isEmpty(compareToFields)) { 
                JavaType comparableInterface = new JavaType("java.lang.Comparable"); 
                builder.addImplementsType(comparableInterface); 
                builder.addMethod(getCompareToMethod()); 
            } 
            itdTypeDetails = builder.build(); 
    
        } 
    
        private MethodMetadata getCompareToMethod() { 
            final JavaType parameterType = JavaType.OBJECT; 
            String parameterName = "obj"; 
            final List<JavaSymbolName> parameterNames = 
                Arrays.asList(new JavaSymbolName(parameterName)); 
            final InvocableMemberBodyBuilder bodyBuilder = 
                new InvocableMemberBodyBuilder();
            final ImportRegistrationResolver imports = 
                builder.getImportRegistrationResolver(); 
                imports.addImport(
                    newJavaType("org.apache.commons.lang3.builder.CompareToBuilder")
                );
    
            final String typeName = destination.getSimpleTypeName(); 
            bodyBuilder.appendFormalLine("if (!(" + parameterName + " instanceof " + 
                typeName + ")) {"); 
            bodyBuilder.indent(); 
            bodyBuilder.appendFormalLine("return -1;"); 
            bodyBuilder.indentRemove(); 
            bodyBuilder.appendFormalLine("}"); 
    
            bodyBuilder.appendFormalLine(typeName + " rhs = (" + typeName + ") " + 
                parameterName + ";"); 
            final StringBuilder builder = new StringBuilder(); 
            builder.append("return new CompareToBuilder()"); 
    
            for (final FieldMetadata field : compareToFields) { 
                builder.append(".append(" + field.getFieldName() + ", rhs." + 
                    field.getFieldName() + ")"); 
            } 
            builder.append(".toComparison();"); 
    
            bodyBuilder.appendFormalLine(builder.toString()); 
    
            final MethodMetadataBuilder methodBuilder = 
                    new MethodMetadataBuilder(getId(), 
                        Modifier.PUBLIC, new JavaSymbolName("compareTo"), 
                        JavaType.INT_PRIMITIVE, 
                        AnnotatedJavaType.convertFromJavaTypes(parameterType), 
                        parameterNames, bodyBuilder); 
            return methodBuilder.build(); 
        }

テスト

以上で、compareTo アドオンの実装は完了しました。このアドオンの完全なソース・コードは、Google Code リポジトリーからダウンロードすることができます。早速、作成したばかりの compareTo をテストしてみましょう。

  1. Roo シェルを終了して、mvn clean install コマンドを実行します。ビルド・プロセスの実行中に、GPG パスフレーズの入力を求められます。
  2. Roo アドオンのビルドが完了したら、新しいコマンドラインを開き、bookshop という名前のディレクトリーを作成します。
  3. bookshop ディレクトリーに移動して、roo コマンドを実行し、Roo シェルを開きます。
  4. Roo シェルで、リスト 14 のコマンドを実行します。
    リスト 14. アドオンを作成する
    project --topLevelPackage com.xebia.roo.bookshop --projectName bookshop  
    jpa setup --database HYPERSONIC_IN_MEMORY --provider HIBERNATE  
    entity jpa --class ~.domain.Book  
    field string --fieldName title --notNull  
    field string --fieldName author --notNull  
    field number --fieldName price --type double --notNull
  5. アドオンをインストールして起動するには、Roo シェルに以下のコードを入力します。
    osgi start --url file://<location to compareTo addon jar>

    上記のコードによって、compareTo アドオンがインストールされてアクティブになります。アドオンのステータスは、OSGi ps コマンドを使用して確認することができます。

  6. compareto」と入力して Tab キーを押すと、3 つの compareto アドオン・コマンドが表示されます (リスト 15 を参照)。
    リスト 15. compareto アドオン・コマンドを表示する
    roo> compareto
    
    compareto add      compareto all      compareto setup
  7. これから、リスト 15 で表示したコマンドを使って、compareto アドオンが正しくインストールされていることを確認します。まずは、セットアップ・コマンドを実行して必要な依存関係を構成するところから始めます。リスト 16 を参照してください。
    リスト 16. セットアップ・コマンドを実行する
    roo> compareto setup 
    
    Updated ROOT/pom.xml [added repository 
        https://spring-dw-roo-compareto-addon.googlecode.com/svn/repo; 
        added dependencies org.xebia.roo.addon.compareto:org.xebia.roo.addon.compareto:
            0.1.0.BUILD,
        org.apache.commons:commons-lang3:3.1;
        removed dependency org.apache.commons:commons-lang3:3.0.1]
  8. compareto setup コマンドを実行した後のステップは、当然、compareTo メソッドをエンティティー・クラスに追加することです。追加するには、compareTo メソッドを 1 つのエンティティー・クラスにだけ生成するか、またはすべてのエンティティー・クラスに生成するかに応じて、compareto add または compareto all を使用します。ここでは、サンプル bookshop アプリケーション (「ダウンロード」を参照) のすべてのエンティティー・クラスに compareTo メソッドを追加します。リスト 17 を参照してください。
    リスト 17. compareTo メソッドをすべてのエンティティー・クラスに追加する
    roo> compareto all 
    Updated SRC_MAIN_JAVA/com/xebia/roo/bookshop/domain/Book.java 
    Created SRC_MAIN_JAVA/com/xebia/roo/bookshop/domain/Book_Roo_Compareto.aj

    上記の compareto all コマンドの出力を見るとわかるように、Book_Roo_Compareto.aj という名前の ITD が生成されました。このファイルに、compareTo メソッドが含まれます。リスト 18 に Book_Roo_Compareto.aj を記載します。

    リスト 18. Book_Roo_Compareto.aj
    import org.apache.commons.lang.builder.CompareToBuilder; 
    
    privileged aspect Book_Roo_Compareto { 
    
        declare parents: Book implements java.lang.Comparable; 
    
        public int Book.compareTo(java.lang.Object obj) { 
            if (!(obj instanceof Book)) { 
                return -1; 
            } 
            Book rhs = (Book) obj; 
            return new CompareToBuilder().append(author, 
                rhs.author).append(id, rhs.id).append(price, rhs.price).append(title, 
                rhs.title).toComparison(); 
        } 
        
    }
  9. Roo シェルで perform package コマンドを実行して、アドオンを追加した後にすべてのものが正しくコンパイルされたことを確かめてください。意外なことに、ビルドは失敗するはずです。その原因は、Maven が Spring Roo のバンドル依存関係を解決できないことにあります。これらのバンドル依存関係は、compareTo アドオンから提供されます。compareTo アドオンに対する依存関係が必要となる理由は、エンティティーには Compareto アノテーションを付けなければならないためです。このアドオンから提供する必要があるのは、このアノテーションのみです。私が最善と考えるアドオンの依存関係の解決方法は、別の Maven モジュールを作成し、そこにすべてのアドオンの依存関係を持たせることです。これは、Spring Roo で行う方法と似ています。Spring Roo では、使用されているすべてのアドオンが依存関係を持つのではなく、Spring Roo で共通のアノテーションをまとめた jar が 1 つあり、そこにすべての依存関係が含まれるのです。ここでもそれと同じようなやり方で、xebia-spring-roo-addon-annotation というプロジェクトを作成し、このモジュールに Compareto アノテーションを含めました。その上で、この jar をアドオンの jar の代わりにクライアント・プロジェクトに追加するように configuration.xml を更新しました。リスト 19 に configuration.xml を記載します。
    リスト 19. configuration.xml
    <?xml version="1.0" encoding="UTF-8" standalone="no"?> 
    <configuration> 
      <dependencies> 
        <dependency> 
          <groupId>org.apache.commons</groupId> 
          <artifactId>commons-lang3</artifactId> 
          <version>3.1</version> 
        </dependency> 
        <dependency> 
          <groupId>org.xebia.roo.addon</groupId> 
          <artifactId>xebia-spring-roo-addon-annotations</artifactId> 
          <version>0.0.1</version> 
        </dependency> 
      </dependencies> 
    
      <repositories> 
        <repository> 
          <id>spring-roo-addon-annoations</id> 
          <name>Spring Roo Addon Annotation</name> 
          <url>https://xebia-spring-roo-addon-annotations.googlecode.com/svn/repo</url> 
        </repository> 
      </repositories>  
    </configuration>

    ComparetoOperationsImpl クラスの setup() メソッドを更新して、更新後の configuration.xml ファイルに指定された新しい依存関係とリポジトリーを読み取るようにします。リスト 20 を参照してください。

    リスト 20. ComparetoOperationsImpl クラスの setup() メソッドを更新する
    public void setup() { bu
    
            List<Dependency> dependencies = new ArrayList<Dependency>(); 
    
            Element configuration = XmlUtils.getConfiguration(getClass()); 
            for (Element dependencyElement : 
                XmlUtils.findElements("/configuration/dependencies/dependency", 
                    configuration)) { 
    
    	    dependencies.add(new Dependency(dependencyElement)); 
            
    	} 
    
            projectOperations.addDependencies("", dependencies); 
    
            List<Element> repositories = XmlUtils.findElements( 
                    "/configuration/repositories/repository", configuration); 
            for (Element repositoryElement : repositories) { 
                Repository repository = new Repository(repositoryElement); 
                projectOperations.addRepository(projectOperations.getFocusedModuleName(),
                    repository); 
            } 
    }

    後は、以下のステップに従います。

  10. mvn clean install を実行して、アドオンを再ビルドします。
  11. ステップ 4 と同じ方法で、クライアントを再生成します。
  12. 古いアドオンを削除するために、Roo シェルで以下のコマンドを実行します。
    addon remove --bundleSymbolicName
    org.xebia.roo.addon.compareto
  13. osgi install コマンドを実行して、アドオンを再インストールします。
  14. アドオンのインストールが完了したら、compareto setup コマンドと compareto all コマンドを実行します。

すると、compareTo ITD が表示されます。perform package コマンドを実行すると、今度は上手く行くはずです。

開発環境でアドオンが正常に機能していることをテストした後は、前に作成した Google Code プロジェクトにこのアドオンを送信してください。アドオンを外部に公開するには、第 3 回で i18n アドオンを公開したときと同じ手順に従います。同様に、アドオンを RooBot に登録するには、Spring Roo のドキュメントに従います。


ラッパー・アドオンを使用して非 OSGi JDBC ドライバーを OSGi 対応にする

ラッパー・アドオンの一般的な用途は、非 OSGi JDBC ドライバーを OSGi 対応のバンドルに変換することです。JDBC ドライバーをラップする必要がある場合としては、例えば Spring Roo を使用して Oracle データベースのリバース・エンジニアリングを行わなければならない場合が挙げられます。著作権の問題により、Spring Roo では OSGi Oracle JDBC ドライバーを提供していません。Oracle データベースのリバース・エンジニアリングを行う前に、ドライバーを OSGi 対応に変換する必要があります。Oracle JDBC ドライバーのラッパー・アドオンを作成する方法は、以下のとおりです。

  1. 以下のコマンドを実行して、ローカル・マシンの Maven リポジトリーに Oracle JDBC jar をインストールします。
    mvn install:install-file -Dfile=ojdbc5.jar -DgroupId=com.oracle 
      -DartifactId=ojdbc5 -Dversion=11.2.0.2 -Dpackaging=jar
  2. oracle-wrapper-addon という名前のディレクトリーを作成し、コマンドラインからその新規ディレクトリーをカレント・ディレクトリーに変更します。
  3. Roo シェルを開いて、次のラッパー・アドオン・コマンドを実行します: addon create wrapper --topLevelPackage com.oracle.wrapper.jdbc --groupId com.oracle --artifactId ojdbc5 --version 11.2.0.2 --vendorName Oracle --licenseUrl oracle.com

    このコマンドが生成するのは、pom.xml ファイルだけです。このファイルを使用して、非 OSGi Oracle JDBC ドライバーを OSGi ドライバーに変換します。

  4. Roo シェル内で、次のコマンドを実行して OSGi バンドルを作成します: perform command --mavenCommand bundle:bundle

以上で、非 OSGi jar の OSGi バンドルが作成できました。


まとめ

今回の記事では、Spring Roo のアドバンスド・アドオンとラッパー・アドオンについての概要と、この 2 つのアドオンを作成する方法を学びました。Spring Roo の極めて重要な機能である、アドオンの作成についての調査はこれで完了しました。Spring Roo の機能を拡張する必要があるときには、必ずアドオンを作成することを検討してください。

連載「Spring Roo 入門」の次回の記事では、Spring Roo を使用して GWT アプリケーションを作成する方法を説明します。


ダウンロード

内容ファイル名サイズ
Sample codebookshop.zip14KB
Sample codespring-dw-roo-compareto-addon.zip18KB

参考文献

学ぶために

製品や技術を入手するために

  • Spring Roo のナイトリー・ビルドのスナップショットをダウンロードするには、Spring Roo リポジトリーにアクセスしてください。
  • Spring Roo プロジェクトの Web サイトから、Spring Roo の最新バージョン 1.2.0.RC1 をダウンロードしてください。
  • Google Code リポジトリーから、compareTo アドオンの完全なソース・コードをダウンロードしてください。
  • IBM 試用版ソフトウェアを使用して、開発者専用のソフトウェアを使って次のオープンソース開発プロジェクトを革新してください。IBM 試用版ソフトウェアは、ダウンロードまたは DVD で入手できます。

議論するために

  • developerWorks コミュニティーに参加してください。ここでは他の developerWorks ユーザーとのつながりを持てる他、開発者によるブログ、フォーラム、グループ、ウィキを調べることができます。developerWorks コミュニティーで、Real world open source グループの構築を手伝ってください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source, Java technology
ArticleID=814618
ArticleTitle=Spring Roo 入門: 第 5 回 Spring Roo のアドバンスド・アドオンおよびラッパー・アドオンを作成する
publish-date=05172012