Apache Camel - APIコンポーネントフレームワークの高度な設定

f:id:tasato-redhat:20190314152401p:plain

Red Hatの佐藤匡剛です。

APIコンポーネントフレームワーク紹介記事の第3回目です。前回はこのフレームワークを使って、簡単なコンポーネントを作成してみました。今回は、現実のWebサービスAPIをコンポーネントに取り込むための高度な設定について見ていきます。

高度なマッピング設定

前回眺めたcamel-hello-component/pom.xmlから始めます。

<!-- 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&lt;String&gt;</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>

<apiName> | <proxyClass> | <fromSignatureFile> | <fromJavadoc>

この箇所は前回説明した通りですが、マッピングの最も重要な設定です。

<apiName>hello-file</apiName>
<proxyClass>com.redhat.sample.camel.hello.api.HelloFileHello</proxyClass>
<fromSignatureFile>signatures/file-sig-api.txt</fromSignatureFile>
  • <apiName> ― エンドポイントURIscheme://apiName/methodName?option1=value1&...&optionN=valueNapiNameプレフィクス部分の定義。
  • <proxyClass> ― エンドポイントに対応するAPIクラス。
  • <fromSignatureFile> | <fromJavadoc> ― シグネチャの取得方法(シグネチャファイル or Javadoc)。

<substitution>

APIメソッドの引数は、エンドポイントのオプションにマッピングされます。しかし、エンドポイントのapiNameとAPIクラスとは1:1に対応するため、APIクラス内の複数メソッドで別々の型の引数に同じ名前が付いていた場合(ex. String dateDate date)にエンドポイントオプションの衝突が起きてしまいます。

<substitution>を使うと、正規表現を使って特定の引数を別の名前に置換して、名前の衝突を避けることができます。

<!-- 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>
  • <method> | <argName> ― 置換対象のメソッドと引数の名前。正規表現で指定する。
  • <argType> ― (オプション)特定の型の引数だけを対象にしたい場合に使う。正規表現で指定する。
  • <replacement> ― 置換後の引数名。正規表現のグループ($1$2、…)を参照できる。
  • <replaceWithType> ― デフォルト(=false)は<argName>の正規表現からグループを参照するが、trueにすると代わりに<argType>のグループを参照できる。

<excludeConfigNames> | <excludeConfigTypes>

エンドポイントオプションに含めたくない引数は、ここで正規表現を使って除外できます。

<!-- Exclude automatically generated endpoint options by name -->
<excludeConfigNames>name-pattern<excludeConfigNames>
<!-- Exclude automatically generated endpoint options by type -->
<excludeConfigTypes>type-pattern<excludeConfigTypes>
  • <excludeConfigNames> ― 除外する引数を引数名で指定。
  • <excludeConfigTypes> ― 除外する引数を型で指定。

<extraOption>

メソッド引数に登場しないオプションを追加できます。

<!-- Add custom endpoint options to generated EndpointConfiguration class for this API -->
<extraOptions>
  <extraOption>
    <type>java.util.List&lt;String&gt;</type>
    <name>customOption</name>
  </extraOption>
</extraOptions>

用途としては、例えば、そのままオプションとして使うには生成が複雑なオブジェクトの引数を上の<excludeConfigNames><excludeConfigTypes>で除外し、代わりにその生成に必要なパラメータをこのカスタムオプションとして定義する、などです。

追加したカスタムオプションは、プロジェクトのEndpoint、Consumer、Producer各クラスの中でinterceptProperties()メソッドをオーバーライドし、その中で実際にAPIメソッドに必要な引数に変換します。下はcamel-olingo4の例です。

https://github.com/apache/camel/blob/camel-2.23.1/components/camel-olingo4/camel-olingo4-component/src/main/java/org/apache/camel/component/olingo4/Olingo4Endpoint.java

    @Override
    public void interceptProperties(Map<String, Object> properties) {

        // read Edm if not set yet
        properties.put(EDM_PROPERTY, apiProxy.getEdm());

        // handle filterAlreadySeen property
        properties.put(FILTER_ALREADY_SEEN, configuration.getFilterAlreadySeen());

        // handle keyPredicate
        final String keyPredicate = (String)properties.get(KEY_PREDICATE_PROPERTY);
        if (keyPredicate != null) {

            // make sure a resource path is provided
            final String resourcePath = (String)properties.get(RESOURCE_PATH_PROPERTY);
            if (resourcePath == null) {
                throw new IllegalArgumentException("Resource path must be provided in endpoint URI, or URI parameter '" + RESOURCE_PATH_PROPERTY + "', or exchange header '"
                                                   + Olingo4Constants.PROPERTY_PREFIX + RESOURCE_PATH_PROPERTY + "'");
            }

            // append keyPredicate to dynamically create resource path
            properties.put(RESOURCE_PATH_PROPERTY, resourcePath + '(' + keyPredicate + ')');
        }

        // handle individual queryParams
        parseQueryParams(properties);
    }

<alias>

APIのメソッドにエイリアスを設定できます。エイリアスを設定すると、エンドポイントのmethodNameの部分に元々のメソッド名とエイリアスのどちらも使えるようになります。

<!-- 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>

エイリアスを設定すると、例えばgreetMe(...)greetUs(...)各メソッドに対して以下のようなエンドポイントを定義できる。

hello://hello-file/me
hello://hello-file/us

<nullableOption>

null値がOKなオプションを指定できます。

<!-- for some methods, null can be a valid input -->
<nullableOptions>
  <nullableOption>option-name</nullableOption>
</nullableOptions>

Javadoc固有の設定

最後に、Javadoc方式を採用した場合に固有の設定です。

<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>
  • <excludePackages> | <excludeClasses><proxyClass>で指定したAPIクラスからメソッドを読み込む際に、そのスーパークラスのメソッド読み込みを除外するための設定。パッケージまたはクラスを正規表現パターンで指定。<excludePackages>のデフォルトはjavax?\.lang.*
  • <includeMethods> ― APIの読み込みに明示的に含めるメソッドの正規表現パターン。
  • <excludeMethods> ― APIの読み込みから除外するメソッドの正規表現パターン。
  • <includeStaticMethods> ― staticメソッドを含めるかどうか。デフォルトは含めない(=false)。

グローバル設定

上記の設定パラメータは、<api>...</api>で囲まれたエンドポイント毎に設定することもできますし、<apis>の外に設定することでグローバルに適用することもできます。グローバルの設定は、エンドポイント毎の設定で上書きされます。

      <configuration>
        <apis>
          <api>
            [...]
            <!-- エンドポイント固有の設定 -->
          </api>
        </apis>
        <!-- グローバル設定 -->
        <substitutions>...</substitutions>
        <excludeConfigNames>...</excludeConfigNames>
        <excludeConfigTypes>...</excludeConfigTypes>
        <extraOptions>...</extraOptions>
        <fromJavadoc>...</fromJavadoc>
        <aliases>...</aliases>
        <nullableOptions>...</nullableOptions>
      </configuration>

エンドポイント毎のConfigurationクラス

ここまでのマッピング設定によってAPIから読み込まれた引数オプションは、コンポーネントプロジェクトのtarget/generated-sources/camel-componentの下にエンドポイント毎のConfigurationクラスとして自動生成されます。

前回作成したcamel-helloサンプルプロジェクトを見ると、以下のConfigurationクラスが生成されています。これらのクラスは、上記のマッピング設定から毎回自動生成されるものなので、その後手動で編集する必要はありません(してはいけません)。

camel-hello-component/target/
└── generated-sources/camel-component/
    └── com/redhat/sample/camel/hello/
        ├── HelloFileHelloEndpointConfiguration.java
        └── HelloJavadocHelloEndpointConfiguration.java

おわりに

APIコンポーネントフレームワークでは、この道具立てで既存のAPIをコンポーネントにマッピングして取り込むことになります。すでに様々なAPIコンポーネントがこのフレームワークで作られていることから分かるように、よほどエキセントリックなAPIでない限りこのマッピング設定でAPIを取り込むことができます。

それでも実際にAPIをマッピングしようとして迷ったときは、このフレームワークを使って作られているCamel標準コンポーネント(第1回参照)のソースコードを参照するといいでしょう。

次回、最終回はインテグレーションテストを追加することで、APIコンポーネントのプロジェクトを完成させます。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。