Red Hatの佐藤匡剛です。
APIコンポーネントフレームワーク紹介記事の第2回目です。前回はこのフレームワークが何なのかを説明したので、今回は実際にその使い方を説明します。
プロジェクトの生成
APIコンポーネントフレームワークを使ってカスタムコンポーネントを開発するには、まずMavenアーキタイプcamel-archetype-api-component
でプロジェクトの雛形を生成します。
$ mvn archetype:generate \ -DarchetypeGroupId=org.apache.camel.archetypes \ -DarchetypeArtifactId=camel-archetype-api-component \ -DarchetypeVersion=2.23.1 \ -DgroupId=com.redhat.samples \ -DartifactId=camel-hello \ -Dname=Hello \ -Dscheme=hello \ -Dversion=1.0-SNAPSHOT \ -DinteractiveMode=false
以下のようなMavenマルチプロジェクトが生成されます。name
パラメータの文字列がxxxxxComponent
、xxxxxConfiguration
、xxxxxConsumer
、xxxxxEndpoint
、xxxxxProducer
といったクラス名のプレフィックスになります。scheme
パラメータの文字列が生成されるコンポーネントのエンドポイントURIのスキーム名("hello://..."
)になります。
camel-hello/ ├── camel-hello-api/ │ ├── pom.xml │ └── src/main/java/com/redhat/sample/camel/hello/api/ │ ├── HelloFileHello.java │ └── HelloJavadocHello.java ├── camel-hello-component/ │ ├── pom.xml │ ├── signatures/ │ │ └── file-sig-api.txt │ └── src/ │ ├── main/ │ │ ├── java/com/redhat/sample/camel/hello/ │ │ │ ├── HelloComponent.java │ │ │ ├── HelloConfiguration.java │ │ │ ├── HelloConsumer.java │ │ │ ├── HelloEndpoint.java │ │ │ ├── HelloProducer.java │ │ │ └── internal/ │ │ │ ├── HelloConstants.java │ │ │ └── HelloPropertiesHelper.java │ │ └── resources/META-INF/services/org/apache/camel/component/ │ │ └── hello │ └── test/ │ ├── java/com/redhat/sample/camel/hello/ │ │ └── AbstractHelloTestSupport.java │ └── resources/ │ ├── log4j2.properties │ └── test-options.properties └── pom.xml
プロジェクトは2つのサブプロジェクトで構成されています。
xxxxx-api
― (オプション)3rdパーティのJavaライブラリを直接使えない場合に、カスタムでAPIを用意するためのプロジェクト。REST APIの場合も生成したJavaクライアントをここに置く。必要無い場合は、ルートプロジェクトと一緒に削除してxxxxx-component
プロジェクトだけを使う。xxxxx-component
― 開発するコンポーネントの本体。
camel-hello-component/pom.xml
を見ると、camel-hello-api
がdependencyに定義されています。3rdパーティのライブラリを使う場合は、このAPIプロジェクトを削除して代わりにライブラリのdependencyに置き換えます。
<dependency> <groupId>com.redhat.sample.camel.hello</groupId> <artifactId>camel-hello-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> [...] <!-- Component API javadoc in provided scope to read API signatures --> <dependency> <groupId>com.redhat.sample.camel.hello</groupId> <artifactId>camel-hello-api</artifactId> <version>1.0-SNAPSHOT</version> <classifier>javadoc</classifier> <scope>provided</scope> </dependency>
例えば、camel-twilioでは以下のようになっています。
https://github.com/apache/camel/blob/camel-2.23.1/components/camel-twilio/pom.xml
<!-- Twilio Java SDK --> <dependency> <groupId>com.twilio.sdk</groupId> <artifactId>twilio</artifactId> <version>${twilio-version}</version> </dependency> [...] <!-- Component API javadoc in provided scope to read API signatures --> <dependency> <groupId>com.twilio.sdk</groupId> <artifactId>twilio</artifactId> <version>${twilio-version}</version> <type>javadoc</type> <scope>provided</scope> </dependency>
ここでjavadoc
タイプのdependencyも登場していますが、なぜ必要かはこれから説明します。
ひとまずはcamel-hello-api
を使ってcamel-helloコンポーネントを完成させます。
APIとエンドポイントURIのマッピング
次にやることは、APIとCamelエンドポイントとのマッピングを設定することです。第1回に説明したように、APIコンポーネントのエンドポイントURIは基本的に以下の形式になります。
scheme://apiName/methodName?option1=value1&...&optionN=valueN
自動生成されたcamel-hello-api
のAPIクラスHelloFileHello
を見ると、以下の通りです。
public class HelloFileHello { public String sayHi() { return "Hello!"; } public String greetMe(String name) { return "Hello " + name; } public String greetUs(String name1, String name2) { return "Hello " + name1 + ", " + name2; } }
これを、以下のようなエンドポイントURIにマッピングします。
hello://hello-file/sayHi hello://hello-file/greetMe hello://hello-file/greetUs
メソッド引数のマッピング
APIメソッドの引数は、次のようにURIのオプションにハードコードして渡すこともできます。
hello://hello-file/greetUs?name1=Llama&name2=Alpaca
しかし、たいていの場合は動的に値を渡したいと思うので、Camelメッセージのヘッダで渡します。
from("direct:sayHello") .setHeader("CamelHello.name1", constant("Llama")) .setHeader("CamelHello.name2", constant("Alpaca")) .to("hello://hello-file/greetUs");
ヘッダ名はCamelXxxxx.
+ <引数名>
です。Xxxxx
には、プロジェクト生成時にname
パラメータで指定した文字列が入ります。
メッセージボディを直接引数に渡したい場合は、特別なURIオプションinBody
を使います。inBody
の値には、ボディをどの引数に渡すかを引数名で指定します。
.to("hello://hello-file/greetMe?inBody=name");
これでgreetMe
メソッドの引数name
にメッセージボディが直接渡されます。
javadoc
dependencyが必要な理由
第1回の記事で、APIコンポーネントフレームワークの要点はコード生成とリフレクションだと説明しました。このAPIメソッドからエンドポイントURIへのマッピングでリフレクションが使われるので、開発者がメソッド毎にいちいちエンドポイントの処理を手書きする必要がありません。しかし、メソッドの引数名は、Javaバイトコードの仕様上リフレクションで取得することができません。Javaの.classファイルには引数名の情報が残っていないからです。
そこで、dependencyのJARファイルとは別にAPIのシグネチャを取得する方法が必要です。APIコンポーネントフレームワークは2つの方法をサポートしています。
- Javadocから取得する
- テキストファイルに書き出したシグネチャを読み込む
そのため、1の方法を用いる場合にjavadoc
のdependencyが必要になるのです。
APIコンポーネントのマッピング設定
camel-hello-component/pom.xml
を開いて、Mavenプラグインにマッピング設定を記述します。すでに様々なオプションがコメントアウトされた状態で出力されているので、これを眺めるだけでもどんなことができるのか、ある程度理解できると思います。
<!-- generate Component source and test source --> <plugin> <groupId>org.apache.camel</groupId> <artifactId>camel-api-component-maven-plugin</artifactId> <executions> <execution> <id>generate-test-component-classes</id> <goals> <goal>fromApis</goal> </goals> <configuration> <apis> <api> <apiName>hello-file</apiName> <proxyClass>com.redhat.sample.camel.hello.api.HelloFileHello</proxyClass> <fromSignatureFile>signatures/file-sig-api.txt</fromSignatureFile> <!-- Use substitutions to manipulate parameter names and avoid name clashes <substitutions> <substitution> <method>^(.+)$</method> <argName>^(.+)$</argName> <argType>java.lang.String</argType> <replacement>$1Param</replacement> <replaceWithType>false</replaceWithType> </substitution> </substitutions> --> <!-- Exclude automatically generated endpoint options by name <excludeConfigNames>name-pattern<excludeConfigNames> --> <!-- Exclude automatically generated endpoint options by type <excludeConfigTypes>type-pattern<excludeConfigTypes> --> <!-- Add custom endpoint options to generated EndpointConfiguration class for this API <extraOptions> <extraOption> <type>java.util.List<String></type> <name>customOption</name> </extraOption> </extraOptions> --> <!-- Use method aliases in endpoint URIs, e.g. support 'widget' as alias for getWidget or setWidget <aliases> <alias> <methodPattern>[gs]et(.+)</methodPattern> <methodAlias>$1</methodAlias> </alias> </aliases> --> <!-- for some methods, null can be a valid input <nullableOptions> <nullableOption>option-name</nullableOption> </nullableOptions> --> </api> <api> <apiName>hello-javadoc</apiName> <proxyClass>com.redhat.sample.camel.hello.api.HelloJavadocHello</proxyClass> <fromJavadoc> <!-- Use exclude patterns to limit what gets exposed in component endpoint <excludePackages>package-name-patterns</excludePackages> <excludeClasses>class-name-patterns</excludeClasses> <includeMethods>method-name-patterns</includeMethods> <excludeMethods>method-name-patterns</excludeMethods> <includeStaticMethods>use 'true' to include static methods, false by default<includeStaticMethods> --> </fromJavadoc> </api> </apis> <!-- Specify global values for all APIs here, these are overridden at API level <substitutions/> <excludeConfigNames/> <excludeConfigTypes/> <extraOptions/> <fromJavadoc/> <aliases/> <nullableOptions/> --> </configuration> </execution> </executions> </plugin>
ひとまずポイントはここです。
<api> <apiName>hello-file</apiName> <proxyClass>com.redhat.sample.camel.hello.api.HelloFileHello</proxyClass> <fromSignatureFile>signatures/file-sig-api.txt</fromSignatureFile> [...] </api>
<apiName>
でエンドポイントURIのapiName
に当たる部分を定義します。<proxyClass>
でそのエンドポイントの下敷きとなるAPIクラスを指定します。先ほどAPIのシグネチャを読み込む方法が2通りあると言いましたが、<fromSignatureFile>
はファイルベースの方法です。
signatures/file-sig-api.txt
を見るとこのようにシグネチャが定義されています。
public String sayHi(); public String greetMe(String name); public String greetUs(String name1, String name2);
シグネチャファイルでなくJavadocを使う場合は<fromJavadoc>
を指定します。この場合、わざわざ自分でシグネチャを書く必要がないので、通常はこちらの方法がお勧めです。3rdパーティライブラリにJavadocがない、など特殊な場合にのみシグネチャファイルを使います。
<api> <apiName>hello-javadoc</apiName> <proxyClass>com.redhat.sample.camel.hello.api.HelloJavadocHello</proxyClass> <fromJavadoc> [...] </fromJavadoc> </api>
とりあえず設定はこのままでも動くので、このまま進めます。
ConfigurationとEndpointの修正
ソースコードで最低限修正が必要なのは2箇所です。まずはcamel-hello-component/src/main/java/com/redhat/sample/camel/hello/HelloConfiguration.java
を開きます。
@UriParams public class HelloConfiguration { // TODO add component configuration properties }
ここに、コンポーネントの設定に必要なパラメータを追加します。Webサービスであれば、最低でも接続に認証情報が必要になるはずです。そういった、ユーザがこのコンポーネントを使う際に設定すべきパラメータをここに追加します。とりあえず、今回はダミーのHello APIを呼び出すだけなのでこのままにします。
次に、camel-hello-component/src/main/java/com/redhat/sample/camel/hello/HelloEndpoint.java
を開きます。
@UriEndpoint(firstVersion = "1.0-SNAPSHOT", scheme = "hello", title = "Hello", syntax="hello:name", consumerClass = HelloConsumer.class, label = "custom") public class HelloEndpoint extends AbstractApiEndpoint<HelloApiName, HelloConfiguration> { [...] // TODO create and manage API proxy private Object apiProxy; [...] @Override protected void afterConfigureProperties() { // TODO create API proxy, set connection properties, etc. switch (apiName) { case HELLO_FILE: apiProxy = new HelloFileHello(); break; case HELLO_JAVADOC: apiProxy = new HelloJavadocHello(); break; default: throw new IllegalArgumentException("Invalid API name " + apiName); } }
重要なのはafterConfigureProperties()
メソッドです。ここで、エンドポイント毎にAPIオブジェクトのインスタンスを生成し、apiProxy
フィールドに保持します。APIオブジェクトを生成する際にWebサービスへの接続が必要な場合は、ここで先ほど修正したConfigurationクラスから必要なパラメータを受け取って接続を行います。ここも、今回のサンプルではこのままにします。
ビルドと実行
プロジェクトが完成したら、以下を実行してビルド&インストールをします。
$ mvn clean install
ビルドが成功すると、camel-hello-component/target/
にソースコードが生成されているのが見つかります。
camel-hello-component/target/ ├── generated-sources/camel-component/ │ └── com/redhat/sample/camel/hello/ │ ├── HelloFileHelloEndpointConfiguration.java │ ├── HelloJavadocHelloEndpointConfiguration.java │ └── internal/ │ ├── HelloApiCollection.java │ ├── HelloApiName.java │ ├── HelloFileHelloApiMethod.java │ └── HelloJavadocHelloApiMethod.java └── generated-test-sources/camel-component/ └── com/redhat/sample/camel/hello/ ├── HelloFileHelloIntegrationTest.java └── HelloJavadocHelloIntegrationTest.java
generated-sources
の方は、Mavenの設定でビルド時に一緒にコンパイルされるようになっています。毎回ビルド時に自動生成されるものなので、ソースコードリポジトリにコミットする必要はありません。
generated-test-sources
についても同様ですが、最終回のテストのところで詳しく説明します。
出来上がったコンポーネントは、標準のCamelコンポーネントと同じようにMavenのdependencyに追加して、Camelルートより実行してください。
<dependency> <groupId>com.redhat.sample.camel.hello</groupId> <artifactId>camel-hello</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
試しに次のような単体テストを実行してみましょう。
public class HelloTest extends CamelTestSupport { @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @Override public void configure() throws Exception { from("direct:sayHello") .setHeader("CamelHello.name1", constant("Llama")) .setHeader("CamelHello.name2", constant("Alpaca")) .to("hello://hello-file/greetUs") .log("${body}"); } }; } @Test public void sayHello() throws Exception { template.sendBody("direct:sayHello", null); } }
実行結果はこんな感じになります。
$ mvn test -Dtest=HelloTest ... [amel-1) thread #1 - CamelHello] route1 INFO Hello Llama, Alpaca
おわりに
とりあえず動作するコンポーネントが完成しました。しかし、野生のWebサービスのAPIは千差万別であり、それを上手くコンポーネントに飼いならすには高度なマッピングの設定が必要不可欠です。
次回は、高度なマッピング設定の方法を説明します。
- 次の記事 ― APIコンポーネントフレームワークの高度な設定