16 プラグインによるORDS機能の拡張

この章では、ORDSプラグイン・フレームワークの使用について説明し、例を示します。

ORDSには、お客様独自のカスタム機能をORDS Webアプリケーションに追加できるプラグイン・フレームワークがあります。プラグインは、jarファイルをlib/extディレクトリに配置することでORDSランタイムに追加できます。ORDSディストリビューションには、サンプル・プラグインのソースが含まれています。それらのプラグイン・サンプルは、ビルド・プロセスの自動化に使用するソフトウェア・ツールであるApache antを使用してビルドできます。

16.1 プラグイン・プログラミング・モデル

この項では、開発者がORDS機能を拡張できるように、どのようにORDSプラグイン・フレームワークが設計されているかについて説明します。

開発者は、プラグイン・フレームワークを使用して、特定のビジネス・ニーズおよび要件を満たすようにORDSをカスタマイズできます。ORDSプラグイン・フレームワークを活用するには、開発者はJava開発の経験があり、Java SEおよびJava EE APIをよく理解している必要があります。GraalVMでサポートされているポリグロット言語の1つを埋め込む場合は、必要な言語コンポーネントをインストールして構成する必要があります。この項では、JavaScriptプラグインの例について簡単に説明します。

16.1.1 プラグインAPIの目的

この項では、ORDSプラグイン・フレームワークの目的を示します。

ORDSプラグイン・フレームワークは、次の目的を満たすように設計されています:

  • 最小限の学習曲線: 既存のJava SE、Java EE APIおよびプログラミング・モデルに基づいて構築されています
  • 少ない結合: 外部コードの依存関係を最小限に抑えて、次の依存関係のみを必要としています:
    • Java SE
    • Java EEサーブレットとjavax.inject
    • 最小限のORDS glueコード

ORDSプラグイン・フレームワークは、HttpServletsをプラグインとしてシームレスに統合できるJava EEサーブレットAPI、JSR-330依存性注入フレームワーク、および限定されたカスタム注釈セットを利用することで、これらの原則に従います。これらの機能により、開発者はweb.xmlデプロイメント・ディスクリプタを編集することなくORDSを拡張でき、柔軟性が増してカスタマイズのオプションが提供されます。

16.1.2 拡張ポイント

この項では、プラグインに追加のロジックを実装する方法について説明します。

プラグインは、次の拡張ポイント・オブジェクトを介してアプリケーション機能を拡張できます:

  • HttpServletクラスを拡張して受信HTTPリクエストを処理します
  • サーブレットのFilterインタフェースを実装し、リクエストまたはレスポンスをインターセプトして変更します。
  • Lifecycleインタフェースを実装して、アプリケーションの起動イベントおよび停止イベントに応答します。
  • ConfigurationSettingをインスタンス化して、プラグインの動作を変更する新しい設定を導入します
  • CommandProviderインタフェースを実装して、コマンドラインからアクセス可能なカスタム・コマンドを追加します。

これらの拡張ポイントをサービスと呼びます。サービスは、単に別のタイプが依存するすべてのタイプです。ExtensionPoints型は、使用可能な拡張ポイントをリストします。

16.1.3 プラグイン・プロバイダ

この項では、プラグインで新しい拡張ポイントを宣言および提供する方法について説明します。

プロバイダは、@Provides注釈で注釈が付けられたJava型を参照します。プロバイダは1つ以上のサービスを提供します。プロバイダが提供するサービスの詳細は、@Providesのドキュメントを参照してください。

16.1.4 プロバイダのライフサイクル

この項では、プロバイダのライフサイクルについて説明します。

プロバイダは、次の2つのライフサイクルのいずれかを持つことができます:
  • @RequestScoped: HTTPリクエストの開始時にインスタンス化され、リクエストの処理後に破棄されます。
  • @ApplicationScoped: アプリケーション・サーバーが起動されてORDSが起動されるときにインスタンス化され、サーバーがORDSを停止または再起動してORDSが停止または再起動したときに終了します。

デフォルトでは、@Providesで注釈が付けられたすべてのJava型に、@RequestScopedライフサイクルが割り当てられます。

リクエスト・タイプ @RequestScopedサービス @ApplicationScopedサービス
スレッド処理 単一スレッドに存在 複数のスレッドからアクセスでき、完全にスレッド・セーフである必要があります。
インスタンス化 頻繁にインスタンス化および破棄されるため、インスタンス化と破棄は効率的である必要があります。 インスタンス化され、時折破棄されます。ORDSアプリケーションのライフサイクルの間存続します
依存可能 その他の@RequestScopedサービスおよび@ApplicationScopedサービス その他の@ApplicationScopedサービス。@RequestScopedサービスには依存できません
その他の考慮事項 メンバー変数でリクエストごとの状態を安全に保持できます。 ライフサイクル・プロバイダは、常に@ApplicationScopedライフサイクルを持ちます。
16.1.4.1 ベスト・プラクティス

この項では、ベスト・プラクティスを示します。

ベスト・プラクティスは次のとおりです:

@ApplicationScopedサービスは、複数のリクエスト間で状態を共有することが絶対に必要でないかぎり使用を避けてください。

@ApplicationScopedサービスに適したいくつかのシナリオを次に示します:
  • 複数のリクエスト間で共有する必要があるデータのキャッシュ。キャッシュによって、特定のトランザクションおよび操作のコストを削減でき、場合によっては、スレッド同期の複雑さと無効なキャッシュ状態の脅威を上回るパフォーマンス上の利点があります。
  • 処理されたリクエストの合計数や処理されたバイトの合計数などのグローバル・メトリックを追跡します。
@ApplicationScopedサービスに適さないいくつかのユースケースを次に示します:
  • 単に再計算が高価であると考えているためのデータのキャッシュ。
  • リクエストごとに1つのサービスを作成することは非効率的または無駄であるため、単一インスタンスのサービスを優先します。

スレッド間で状態を共有するのではなく、信頼できる同時アクセスを提供するデータベースなどのバックエンド・ストレージ・システムにグローバル状態を格納します。このアプローチにより、スレッド間の共有状態に関連するリスクを回避できます。

16.1.5 サービス・プロバイダの優先順位付け

この項では、サービス・プロバイダの優先順位について説明します。

サービスに複数のプロバイダがある場合は、最も適切なプロバイダの選択や、プロバイダの起動順序の決定に役立つように、@Priority注釈が用意されています。

16.1.6 依存性注入

ランタイムは、標準の依存性注入パターンを利用して、プロバイダが依存するサービスを指定します。これには、JSR-330互換の依存性注入フレームワークが含まれます。プロバイダは、その依存関係をコンストラクタ内で宣言します。このコンストラクタには、@Injectの注釈を付ける必要があります。

例16-1 依存性注入の例

@Provides /* Advertise the Foo provider to the D.I. framework */
class Foo {
 @Inject Foo(Bar bar) { /* Express the dependency on the Bar service */
  this.bar = bar;
 }
 
 public void someMethod() {
  bar.use(); /* use the dependency */
 }
 private final Bar bar;
}

関連項目:

16.1.7 AvailableDependencies

プラグインで使用可能な依存関係のセットは、AvailableDependencies型によって列挙されます。

16.2 サーブレットの拡張機能

この項では、HTTPリクエストを処理するための拡張機能を作成する方法について説明します。

HTTPリクエストを処理する拡張機能を作成するには、HttpServletを拡張するクラスを作成する必要があります。

次の項では、サーブレット拡張機能の構築の詳細、およびJava注釈を使用してサーブレットのメタデータを定義する方法について説明します。

メタデータの主な部分は、特定のHTTPメソッド、特定のURIパターンまたはサーブレット全体に固有です。特定のメタデータを定義するには、対応するJavaメソッドに注釈を付けるか、@PathTemplate注釈のプロパティを設定します。

16.2.1 サーブレットのライフサイクル

この項では、ORDSでのサーブレットのライフサイクルについて説明します。

ORDSのサーブレットは、標準のJEEアプリケーション・サーバーのサーブレット・ライフサイクルとは異なり、HTTPリクエストの期間中のみ存在します。その結果、各サーブレット・インスタンスは1つのスレッドによって使用されるため、スレッドの問題を管理する必要はありません。

HTTPリクエストごとに、リクエストのターゲットとして識別されると個別のインスタンスが作成され、リクエストが処理されるとそのインスタンスは破棄されます。

16.2.1.1 @Dispatches注釈について

この項では、@Dispatches注釈について説明します。

@Dispatches注釈は、サーブレットが処理するURLパターンをORDSに通知します。これらのパターンはルート・パターンと呼ばれ、特定の構文を持ちます。

すべてのサーブレットには、1つの@Dispatches注釈が必要です。@Dispatches注釈には、少なくとも1つの@PathTemplate注釈が必要です。また、各@PathTemplate注釈には、サーブレットが処理するURLパターンが記述されます。

Javadocでは、注釈の使用方法が詳細に説明されており、いくつかの例が示されています。

16.2.1.2 @PathTemplate注釈について

この項では、@PathTemplate注釈について説明します。

[@PathTemplate][path-template]注釈は、サーブレットがサービスする単一のルート・パターンを記述します。また、@PathTemplate注釈には、指定したルート・パターンに固有のメタデータを提供するために使用できる多数のプロパティがあります。

16.2.1.3 PathTemplateMatchについて

この項では、PathTemplateMatchオブジェクトについて説明します。

一致した@PathTemplateを識別するために、サーブレットはPathTemplates.matchedTemplate(HttpServletRequest)メソッドをコールして、対応するPathTemplateMatchを取得する必要があります。

サーブレットの例
@Provides
@Dispatches({
 @PathTemplate(name="collection", value="/examples/collection/"),
 @PathTemplate(name="item", value="/examples/collection/:id")})
class ExampleServlet {

 protected void doGet(HttpServletRequest req,HttpServletResponse resp) {
  PathTemplateMatch matched = this.pathTemplates.matchedTemplate(req);
  switch(matched.name()) {
   case "collection":
    // process collection pattern
    break;
   case "item::
    String id = matched.parameters().get("id");
    // process item pattern
    break;
  }
 }
}

PathTemplateMatchインスタンスを取得すると、通常、サーブレットは、PathTemplateMatch#name()メソッドを調べることによって、リクエストを処理するための正しいロジックに分岐できます。ルート・パターンにパラメータが含まれている場合、パラメータにバインドされた値はPathTemplateMatch#parameters()メソッドを使用して取得できます。

/examples/collection/:idというルートがあるとします

次のいずれかの方法でリクエストを行うことで、アクセスしてID値101を指定できます:
  • /examples/collection/101
  • /examples/collection?id=101

16.3 プラグインの例

この項では、plugin-demoプラグインのビルドおよびデプロイについて説明します。

plugin-demoプラグインは、データベースに問い合せて現在のデータベース・ユーザーを判別し、その情報をレスポンスでエコー表示します。JavaおよびJavascriptを使用した例が提供されているのは、プラグインの作成で現在サポートされているのはこの2つの言語のみであるためです。

前提条件

  • JDK 1.8以降
  • Apache Ant 1.8.2以降
  • インストールされ、REST対応データベース・スキーマが構成されたORDS

16.3.1 Javaプラグインのデモンストレーション

この項では、Javaプラグイン・デモンストレーションの例の詳細について説明します。

プラグイン・デモンストレーションの例は、examples/plugins/plugin-demoにあり、これには、HttpServlet (実行時にデータベース接続が挿入される)のソースが含まれています。このサーブレットでは、そのJDBCデータベース接続を使用してデータベースで問合せが実行され、実行時にレスポンスが返されます。参照されるすべてのファイルは、examples/pluginsフォルダにある製品ディストリビューションに含まれています:

${ORDS_HOME}
|-- examples
     |-- plugins
          |-- lib
          |-- plugin-demo
              |-- src

ここで、${ORDS_HOME}は、製品ディストリビューションが解凍された場所です。

16.3.1.1 plugin-demoのフォルダ構造について

この項では、plugin-demoファイルのフォルダ構造について説明します。

plugin-demoファイルは、${ORDS_HOME}フォルダの次の場所にあります:
${ORDS_HOME}/examples/plugins/plugin-demo

${ORDS_HOME}/examples/plugins/plugin-demoフォルダには、次のものが含まれています:

  • srcフォルダには次のものが含まれています:
    • PluginDemo.java: プラグインのJavaソース・コード
  • build.xml: ソース・コードをコンパイルおよびパッケージ化するANTビルド・スクリプト
  • built: パッケージ・プラグイン(plugin-demo.jar)を含むbuild.xmlから生成されたフォルダ
  • ${ORDS_HOME}/examples/plugins/libフォルダには、プラグインのコンパイルに必要な.jarファイルが含まれています。
16.3.1.1.1 必要なライブラリ

この項では、必要なライブラリについて説明します。

必要なjarファイルは、${ORDS_HOME}/examples/plugins/lib製品配布フォルダに含まれています。

プラグインをコンパイルするには、次のライブラリがクラスパスに存在する必要があります:

  • plugin-api.jar
  • plugin-apt.jar
  • javax.inject.jar
  • servlet-api-3.1.0.jar
  • ojdbc11.jar

plugin-api.jarについて

このライブラリは、プラグインをランタイムにウィービングするための@Dispatchesなどのglueコードを提供します。

plugin-apt.jarについて

このライブラリは、@Providesで注釈付きのクラスを検出可能にする注釈プロセッサを提供します。

javax.inject.jarについて

このライブラリは、JSR-330 APIタイプ(@Injectなど)を提供します。

servlet-api-3.1.0.jarについて

このライブラリは、Javaサーブレット3.1.0 APIタイプ(HttpServletなど)を提供します。

ojdbc11.jarについて

このライブラリはオプションであり、プラグインがOracleConnectionなどのOracle JDBC Extension APIにアクセスする必要がある場合にのみ必要です。

16.3.1.2 PluginDemo.javaについて

この項では、コード・スニペットの例のPluginDemo.javaプラグインとその説明を示します。

PluginDemo.javaプラグインのコード・スニペットの例を次に示します:
package example;

import java.io.IOException;
import java.sql.*;
import jakarta.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import oracle.dbtools.plugin.api.di.annotations.Provides;
import oracle.dbtools.plugin.api.http.annotations.*;
import oracle.dbtools.plugin.api.routes.*;

/**
 * This example plugin {@link HttpServlet} demonstrates:
 * <ul>
 * <li>Using the injected {@link Connection} to query the database.</li>
 * <li>Using the injected {@link PathTemplates} service to decode the parameters
 * of the servlet's {@link PathTemplateMatch}.</li>
 * </ul>
 *
 * <h4>Testing the Servlet</h4> Invoke the servlet with the following URL:
 *
 * <pre>
 *  http://<i>server</i>/ords/<i>schema</i>/demos/plugin?who=<i>somebody</i>
 * </pre>
 *
 * where:
 * <ul>
 * <li><i>server</i> is the hostname and port of the server.</li>
 * <li><i>schema</i> is the name of the REST enabled database schema.</li>
 * <li><i>somebody</i> is any value you wish, e.g. a person's name.</li>
 * <ul>
 * For example:
 *
 * <pre>
 *  http://localhost:8080/ords/test_schema/demos/plugin?who=Scott
 * </pre>
 *
 * @author cdivilly
 *
 */
@Provides
@Dispatches(@PathTemplate("/demos/plugin"))
class PluginDemo extends HttpServlet {
  @Inject
  PluginDemo(Connection conn, PathTemplates pathTemplates) {
    this.conn = conn;
    this.pathTemplates = pathTemplates;
  }

  public void doGet(HttpServletRequest request,
                    HttpServletResponse response)
      throws ServletException, IOException {

    PathTemplateMatch match = pathTemplates
                                .matchedTemplate(request);
    try {
      /* retrieve 'who' query parameter */
      String who = match.parameters().get("who");
      who = null == who ? "anonymous" : who;

      /* execute database query */
      PreparedStatement ps = conn
       .prepareStatement("select sys_context('USERENV','CURRENT_USER') from dual");

      ResultSet rs = ps.executeQuery();
      rs.next();

      /* determine the database user */
      String user = rs.getString(1);

      /* Print the greeting */
      response.getWriter().println(user + " says hello to: " + who);
      rs.close();
      ps.close();
    } catch (SQLException e) {
      throw new ServletException(e);
    }
  }

  private final Connection    conn;
  private final PathTemplates pathTemplates;
}

前述のコード・スニペットの説明を次に示します:

  • PluginDemoクラスには、次の注釈が付いています:
    • @Provides注釈。このシナリオでは、HttpServletサービスを提供することを示します。

    • @Dispatchesおよび@PathTemplate注釈。サーブレットが応答するURLパターンを定義します。

  • PluginDemoコンストラクタには@Injectの注釈が付けられ、このコンストラクタをクラスのインスタンスの作成に使用する必要がある依存性注入フレームワークで使用する必要があることを示します。

  • コンストラクタのパラメータは、次の依存関係を指定します:
    • 最初のパラメータは、データベース接続が必要であることを示し、ORDSはリクエストURLのマッピング・ルールに従って、この接続を特定のデータベース・スキーマに割り当てます。
    • 2番目のパラメータはPathTemplatesサービスで、サーブレットは現在のリクエストに関連付けられている特定のPathTemplateを取得できます。
  • PluginDemo:
    • doGet()メソッドをオーバーライドして、GET HTTPメソッドをサポートすることを示します。

    • PathTemplatesサービスを使用して、リクエストにバインドされたPathTemplateMatchを判別します。
    • 次に、テンプレート・パラメータをデコードし、whoパラメータの値を抽出します。
    • 接続インスタンスを問い合せて、現在のデータベース・ユーザーのユーザー名を判別します。

      レスポンスでは、データベース・ユーザーおよびwhoパラメータの値がレスポンスに出力されることを示すメッセージが表示されます。

16.3.1.3 プラグインのビルド

このセクションでは、プラグインをビルドする方法について説明します。

plugin-demoフォルダで次のコマンドを入力します:
$ ant
ソース・コードがコンパイルされ、built/plugin-demo.jarという名前のアーカイブにパッケージ化されます。
16.3.1.4 プラグインのパッケージ化

この項では、プラグインをパッケージ化する方法について説明します。

プラグインをパッケージ化するには、plugin-demo.jarを拡張ライブラリにコピーします:
$  cp built/plugin-demo.jar ${ORDS_HOME}/lib/ext/
16.3.1.5 プラグインのテスト

この項では、プラグインをテストする方法について説明します。

プラグインをテストするには:

ORDSをスタンドアロン・モードで起動します:
.$ cd ${ORDS_HOME}/bin
.$ cd ${ORDS_HOME}/bin
$ ords --config config_dir serve

16.3.2 リクエストURLの分析

この項では、ORDSがリクエストURLを分析し、データベース・プールおよびスキーマにマップする方法について説明します。

開発されたプラグインが動作するには、データベース接続が必要です。そのため、ORDSは接続先の正しいデータベースおよびスキーマを判別する必要があります。ORDSは、リクエストURLを分析し、それを対応するデータベース・プールおよびスキーマにマップすることで、これを実現します。

ORDSがマッピングを判別できない場合は、リクエストURLに対して404 Not Foundステータスが返されます。

16.3.3 リクエストURLの試行

リクエストURLを試行するには:

ORDSは、localhost上のスタンドアロン・モードで実行されている必要があります。次のURLを試してください:
http://localhost:8080/ords/hr/demos/plugin?who=Scott
ブラウザに次のテキストが表示されます:
hr says hello to: Scott
  • リクエストURLの/hrの部分は、リクエストをhrデータベース・スキーマにマップします。
  • hrスキーマに接続された接続インスタンスは、PluginDemoインスタンスに注入されます。
  • PluginDemoは、接続を問い合せて現在のユーザーを判別し、リクエストURLにバインドされたwhoパラメータをデコードし、この情報を使用して表示されるメッセージを作成します。

16.3.4 Javaの例

次の項では、Javaの例をいくつか示します。

16.3.4.1 Hello Worldの例

この項では、Hello Worldの例を示します。

次のHello Worldの例は、リクエスト・ハンドラ・プラグインの作成の基本を示しています:

HelloWorld.java
@Dispatches(@PathTemplate("/hello"))
@Provides                            
public class HelloWorld extends HttpServlet {
 public doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
 	response.setContentType("text/plain");
 	response.getWriter().println("Hello World");
 }
}
前述のHello Worldの例の説明を次に示します:
  • HttpServletをサブクラス化するクラスを作成します。
  • @Provides注釈を使用して、クラスを依存性注入フレームワークに宣言します。
  • @Dispatches注釈を使用して、クラスをリクエスト・ディスパッチ・フレームワークに宣言します。
  • @PathTemplate注釈を使用して、クラスが応答するリクエスト・パスを宣言します。
  • HttpServlet doGet ()メソッドをオーバーライドして、ハンドラのロジックを提供します。
16.3.4.2 依存性の注入

この項では、プラグインで外部APIに対する依存関係を指定する方法を示すコード・スニペットの例について説明します。

プラグインは、そのコンストラクタの@Inject注釈を使用して、外部APIに対する依存関係を指定できます。

UsesLogging.java
@Dispatches(@PathTemplate("/uses-logging"))
@Provides                            
public class UsesLogging extends HttpServlet {

 @Inject
 public UsesLogging(Logger log) {
 	this.log = log;
 }   
 public doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
    log.fine("received request:\n" + request.toString());
 	response.setContentType("text/plain");
 	response.getWriter().println("Hello World");
 	log.fine("processed request");
 }

 private final Logger log;
前述のコード・スニペットの説明を次に示します:
  • UsesLogging型をインスタンス化する前に、DIフレームワークは@Injectの注釈が付いたコンストラクタを検索します。次に、コンストラクタの引数を調べ、指定された型の実装の解決を試みます。すべての依存関係が解決されると、DIフレームワークは必要な引数を使用してコンストラクタを呼び出します。
  • この場合、サーブレットはLoggerサービスを使用してデバッグ情報を記録します。
  • 注入できるサービスのセットは、AvailableDependencies列挙に記述されています。

16.3.5 Javascriptプラグインのデモンストレーション

この項では、Javascriptを使用してプラグインを作成する方法について説明します。

16.3.5.1 プラグインJavascript

ORDSでは、リクエスト時にORDSインスタンスで実行可能なJavaScriptをお客様が定義できるようにするためのサービス・フレームワークとして、JavaScriptが提供されています。これは、アプリケーションの開発に使用される従来のRESTfulサービスの概念に似ています。このフレームワークは、モジュール、テンプレートおよびハンドラというアーキテクチャに基づいています。「Oracle REST Data Servicesアプリケーションの開発」を参照してください。データベースでモジュール、テンプレートおよびハンドラを定義するのではなく、それらをプラグインとしてXML表現において指定し、そのXML表現がlib/ext/ディレクトリから読み取られます。

ORDSのexamplesディレクトリにはplugin-javascriptサンプルが含まれており、そのソースはexamples/plugins/plugin-javascriptディレクトリにあります。この項では、そのプラグインの主要要素について説明します。

ノート:

ORDSのJavaScriptプラグイン機能が動作するには、JSコンポーネント付属GraalVMが必要です。
このORDS機能が動作するには、JSコンポーネント付属GraalVMが必要です。詳細は、GraalVMの構成を参照してください。

このサンプルには、JavaScriptソースのインライン定義と外部定義が多数含まれています。外部JavaScriptソースへの参照は、クラスパスにあるファイルに対するものです。

ファイル 説明
build.xml antビルド・プロジェクト。
src/js/example.js 外部JavaScriptファイルのサンプル。ここでの外部とは、XMLリソース・モジュール・ファイル内で定義されているのではなく参照されているということです。
src/META-INF/manifest.json XMLリソース・モジュールを登録するためにORDSで起動時に読み取る、プラグイン構成メタデータ・ファイル。
src/META-ING/modules/javascript.xml テンプレートとハンドラを多数含むサンプル・モジュールを定義する、XMLリソース・モジュール・ファイル。
次の手順を実行してサンプルをビルドし使用します。
  1. ディレクトリをexamples/plugins/plugin-javascriptに変更します。
  2. antを実行してexamples/plugins/plugin-javascript/built/plugin-javascript.jarファイルをビルドします。
  3. plugin-javascript.jarファイルをORDSディストリビューションのlib/extディレクトリにコピーし、サポートされているJSコンポーネント付属GraalVMを使用してORDSインスタンスを起動します。
  4. 次のURLパターンを使用して定義済ハンドラを起動します: http://server/ords/javascript-examples/{template pattern}
    1. たとえば、http://localhost:8080/ords/javascript-examples/nowです。この場合は、現在の時間が返されます。

      ノート:

      ORDS RESTサービスとは異なり、JavaScript as a Service実装ではデータベース接続が不要であり使用されません。
16.3.5.1.1 サンプル・サービスの用途と使用

この項では、サンプル・サービスの用途と使用について説明します。

用途 リクエスト アクション レスポンス
現在のUTC時間をapplication/jsonとして返すインラインJavascriptのサンプル。 /ords/javascript-examples/now GET { "now":"2023-08-31T16:08:55.471Z" }
パラメータを受け入れるインラインJavascriptのサンプル。 /ords/javascript-examples/future?days=7 GET { "now":"2023-08-31T16:08:55.471Z", "future":"2023-09-07T16:08:55.471Z", "days":7 }
様々なソースからの各種パラメータを受け入れるインラインJavascriptのサンプル。
/ords/javascript-examples/hello?name=Ted

curl --location 'ords/javascript-examples/hello' \
--header 'Agent: Test'
GET
Hello Ted
Hello Test
パラメータを受け入れる外部Javascriptファイルのサンプル。 /ords/javascript-examples/fibonacci?length=50 GET {fib: 12586269025}
暗黙的パラメータcontent_typeおよびbody_textを使用してリクエスト値を取得し、ords_responseを使用してHttpServletResponseでのsetStatusおよびsetContentTypeを呼び出すインラインJavascriptのサンプル。
curl --location '/ords/hr/javascript-examples/countwords' \
--header 'Content-Type: application/json' \
--data '{"text": "How many words are here?"}'
POST
{"text": "How many words are here?","count": 5}
16.3.5.1.2 Graal JavaScriptコンポーネントの埋込み

JDKバージョン21のGraalVMで実行されているORDSでJavaScriptをゲスト言語として実行できるようにするには、JavaScriptコンポーネントをプラグインとして埋め込む必要があります。

JavaScriptの埋込みに必要なアーティファクトは次のとおりです。
  • GraalVMポリグロットAPI
  • JavaScript言語
次に、必要な依存関係の取得に役立つMaven依存性の設定を示すサンプル・コード・スニペットを示します。
<dependency>
    <groupId>org.graalvm.polyglot</groupId>
    <artifactId>polyglot</artifactId>
    <version>${graalvm.version}</version>
</dependency>
<dependency>
    <groupId>org.graalvm.polyglot</groupId>
    <!-- Language: js -->
    <artifactId>js</artifactId>
    <version>${graalvm.version}</version>
    <type>pom</type>
</dependency>

埋込み言語への依存性設定の詳細は、GraalVMリファレンス・マニュアルの言語の埋込みの項を参照してください。必要なアーティファクトをダウロードしたら、実行時にクラスパスに含められるようにlib/ext/ディレクトリに配置します。

関連項目:

言語の埋込み

16.4 ルート・パターン

ルート・パターンは、特定のHTTPリクエスト・パスを照合するために使用される形式を定義します。このパターンは、リクエストURIのパス・コンポーネントと照合されます。

16.4.1

この項では、ルート・パターンの例を示します。

/objects/:object/:id?
このルート・パターンは、次のパスと一致します:
  • /objects/emp/101: empリソースのIDが101の項目に対してリクエストが一致します。
  • /objects/emp/: empリソースとリクエストが一致します。これは、:idパラメータにはidパラメータがオプションであることを示す?修飾子の注釈が付けられているためです。

16.4.2 ルート・パターンの目的

この項では、ルート・パターン構文の目的を明確に説明します。これは、標準ではなく推奨ガイドラインとして機能します。

ルート・パターンの構文は、次のような多数のWebフレームワークで見られるパターン・ルーティング構文から発想を得ており、類似しています:

  • Angularのルート
  • Ruby on Railsのルーティング

ルート・パターンは、これらのフレームワークおよび類似フレームワークによって普及したアドホック・パターン構文の正式な定義を作成する必要性に対処しています。

ルート・パターンの目的は、曖昧な一連のルート・パターンを定義できなくすることです。特定のリクエスト・パスでは、パスとの照合に1つまたは0個のルート・パターンのみを選択できます。その結果、ルート・パターン構文は、フレームワークで使用されるアド・ホック構文よりも柔軟性が低いか、表現性が低い場合があります。

これは意識的なデザインのトレードオフです。アドホック構文では、パターンが宣言される順序によって曖昧さが解決され、最初に宣言されたパターンが最初にテストされ、2番目の宣言されたパターンが2番目にテストされます(以下同様)。開発者は、パターン宣言を順序付けて、より限定的なパターンがより限定的ではないパターンの前にテストされるようにできます。これには一元的なコードの場所が必要であり、そこでルートを宣言して、エラーを回避するためにパターンの慎重な順序付けが必要となります。これらの要件は、多くの開発者がルート・パターンを定義していてルート・パターンの競合や重複を十分に認識できていない大規模なアプリケーション、およびルート・パターンを様々な場所(プラガブル・アーキテクチャなど)で定義する必要があるアプリケーションには拡張できません。

ルート・パターン構文はURIテンプレート構文とも若干似ていますが、URIテンプレートとルート・パターンの用途は異なります。URIテンプレートではテンプレートから実際のURIを形成することに焦点が置かれ、ルート・パターンではURIのパス部分をコンポーネント・パーツに分解することに焦点が置かれます。

16.5 ルート・パターン構文のルール

この項では、ルート・パターン構文のルールについて説明します。

ルート・パターンは、0個以上の埋込み変数式を含む出力可能なUnicode文字の文字列です。式には、先頭のコロン(:)と末尾のスラッシュ(/)または文字列の末尾で区切られた名前付きパラメータを指定するか、ワイルドカード文字(*)で示されたglobパラメータを指定できます。1つ以上の名前付きパラメータを含むパターンは、名前付きパターンと呼ばれます。globパラメータを含むパターンは、globパターンと呼ばれます。1つのパターンに名前付きパターンとglob式を混在させることはできません。変数式がないパターンは、リテラル・パターンと呼ばれます。

16.5.1 パス・セパレータ

この項では、パス・セパレータについて説明します。

スラッシュ(/)文字は、パターンをパス・セグメントに区切ります。パス・セパレータの後に別のパス・セパレータを続けることはできません。ルート・パターンの先頭のパス・セパレータは暗黙的に指定され、省略できます。

  • パターンa/b/a/bは等価です
  • パターン*/*は等価です
  • パターンa/ba/b/は等価ではありません。末尾のパス・セパレータは重要であり、無視できません

16.5.2 予約文字

この項では、予約文字について説明します。

予約文字のセットは、RFC 3986のセクション2.2で定義されています。
  • reserved = gen-delims / sub-delims
  • gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
  • sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="

16.5.3 リテラル値

ルート・パターンの式およびパス・セパレータ以外の文字は、リテラル値と呼ばれます。

リテラル値には、予約文字を除く任意の出力可能なUnicode文字を含めることができます。

16.5.4 名前付きパラメータ

この項では、名前付きパラメータについて説明します。

名前付きパラメータの始まりは、コロン文字(「:」)で示されます。名前付きパラメータの末尾は、パス・セパレータまたは文字列の終わりで示されます。名前付きパターンは、末尾に修飾子を付けることができます。指定されたパラメータ名は、各ルート・パターンで1回のみ出現する必要があります。ルート・パターンには、0個以上の名前付きパターンを指定できます。

:

named-expression-pattern = *(literal / path-separator / named-expression )

valid-name = [a-zA-Z0-9] / '-' / '_' 
char = [a-zA-Z]
name = char valid-name*
param-decl = name ('*' / '?' )

named-expression = ':' param-decl path-separator /
                   ':' param-decl <eos>
16.5.4.1 修飾子

この項では、修飾子について説明します。

修飾子は、名前付きパラメータの照合動作を変更します。修飾子を含めることができるのはルート・パターン内の1つの名前付きパラメータのみであり、パターン内の最後の名前付きパラメータである必要があります。修飾子は、名前付きパラメータ式の末尾に付けられます。

16.5.4.1.1 イーガー修飾子

この項では、イーガー修飾子について説明します。

イーガー修飾子はアスタリスク文字(「*」)で示され、パス・セパレータ文字を含む名前付きパターンに一致するすべての文字を文字列の末尾まで可能なかぎり照合するように照合ソフトウェアに指示します。

/foo/:all-children*

このパターンは次のパスに一致します:

  • /foo/bar: all-childrenはbarに関連付けられます
  • /foo/bar/: all-childrenはbarに関連付けられ、イーガー修飾子はパス・セパレータを含むすべての文字と一致します
  • /foo/bar/baz: all-childrenはbar/bazに関連付けられ、イーガー修飾子は文字列の最後まですべての文字と照合されます。

イーガー修飾子は少なくとも1つの文字と一致する必要があるため、前のパターンは次のパスと一致しません:

/foo/: このパスと一致するには、all-childrenを空の文字列に関連付ける必要がありますが、それは許可されません。

16.5.4.1.2 オプション修飾子

この項では、オプション修飾子について説明します。

オプション修飾子は疑問符(?)で示され、文字列の末尾に達するまで、名前付きパターンを0個以上の文字と照合することをパターン照合ソフトウェアに指示します。

/foo/:item?

このパターンは次のパスに一致します:

  • /foo/bar: アイテムはbarに関連付けられています
  • /foo/: 項目が空の文字列に関連付けられ、オプション修飾子により、名前付きパラメータがゼロの長さの文字列と一致します
16.5.4.1.3 複合名前付きパラメータ

この項では、複合名前付きパラメータについて説明します。

複合名前付きパラメータは、リクエスト・パス内の一致するテキストが名前付きコンポーネントに分解される名前付きパラメータです。各コンポーネントは、カンマ(,)で区切られます。複合名前付きパラメータには、オプション修飾子を付けることができますが、イーガー修飾子を付けることはできません。

:

/line-items/:order_id,item_id/detail
16.5.4.1.4 Globパラメータ

この項では、globパラメータについて説明します。

globパラメータは、ワイルドカード修飾子(「*」文字)で示されます。ワイルドカード修飾子は、パターンの末尾に指定し、その前にパス・セパレータを付ける必要があります。1つのパターンでは、単一のglobパラメータのみが許可されます。globパラメータは、名前付きパラメータと同じパターンでは指定できません。
glob-pattern = *(literal / path-separator /  ) / path-separator '*'
globパラメータは、文字列の最後までの0文字以上の文字と照合されます。
:
  • /*: すべてのパスに一致します
  • /foo/*: /foo/パスを含む/foo/で始まるすべてのパスに一致します。

16.6 パターンの照合ルール

この項では、ルート・パターンの照合ルールについて説明します。

ルート・パターンは、次のトークンで構成されます:
  • パス・セパレータ
  • リテラル値
  • 名前付きパラメータ
  • 名前付き複合パラメータ
  • Globパラメータ

ルート・パターンは、リクエスト・パスのURLエンコード形式と照合され、各トークンがリクエスト・パスの対応するセグメントと照合されます。

トークンは、左から右の順に照合され、最初のトークンがリクエスト・パスの左端のセグメントと照合され、2番目のトークンが次の左端のセグメントと照合されます(以下同様)。各トークン・タイプを照合するためのルールは、次の項で定義します。

16.6.1 パス・セパレータの照合

この項では、パス・セパレータの照合ルールについて説明します。

各パス・セパレータ・トークンは、リクエスト・パス内の1つの「/」文字と完全に一致する必要があります。パス・セパレータは、URLでエンコードされた形式の「/」文字と一致しないようにする必要があります。つまり、オクテット: %2Fまたは%2fと一致しないようにする必要があります。ルート・パターンの先頭のパス・セパレータはオプションであるため、リクエスト・パスの先頭のパス・セパレータもオプションであり、省略できます。

:

  • パターン/a/bは、リクエスト・パスのa/bおよび/a/bと一致します。
  • 等価パターンa/bも、a/bおよび/a/bと一致します
  • パターン/a/bは、リクエスト・パスのa%2Fbまたは%2fa%2Fbとは一致しません

16.6.2 リテラル値の照合

この項では、リテラル値の照合ルールについて説明します。

各リテラル値トークンは、リクエスト・パスのまったく同じ文字と一致する必要があります。各リテラル値は、URLエンコードされる必要があり、URLエンコードされたリクエスト・パスと比較されます。

パターンa/bは、次のリクエスト・パスと一致します:
  • a/b
  • /a/b
  • /%61/%62 – '%61'は文字aのパーセント・エンコードされた形式で、'%62'は文字bのパーセント・エンコードされた形式です。

16.6.3 名前付きパラメータの照合

この項では、名前付きパラメータの照合について説明します。

名前付きパラメータのトークンは、次のパス・セパレータの出現または文字列の末尾まで、1つ以上の文字と照合されます。

オプション修飾子の照合

名前付きパラメータにオプション修飾子がある場合、文字列の末尾までの0文字以上の文字と照合されます。

イーガー修飾子の照合

名前付きパラメータにイーガー修飾子がある場合、文字列の末尾までのすべての文字と照合されます。

パターン/test/:itemは、次のパスと一致します:

  • test/101
  • /test/true%2Ffalse
  • /test/a,b,c

このパターンは次のパスと一致しません:

  • /test/101/: 余分な末尾のスラッシュ
  • /test/: 名前付きパラメータは1文字以上一致する必要があります

16.6.4 複合名前付きパラメータの照合

この項では、複合名前付きパラメータのルールについて説明します。

複合名前付きパラメータのトークンは、次のパス・セパレータまたは文字列の末尾までの1つ以上の文字と照合されます。一致した文字は、カンマ(「,」)文字でさらに区切られます。複合名前付きパラメータにNコンポーネントがある場合、一致したテキストには最大N-1個のカンマが必要です。N-1個を超えるカンマ文字がある(つまり、N+1)場合は、一致しません。一致したリクエスト・パスでは、末尾のカンマ文字を省略できます。

カンマ文字を含む必要があるリクエスト・パスのコンポーネント値には、カンマ文字(%2C)のパーセント・エンコード形式を使用する必要があります

16.6.4.1 オプション修飾子の照合

この項では、オプション修飾子の照合について説明します。

複合名前付きパラメータにオプション修飾子がある場合、文字列の末尾までの0個以上の文字と照合されます。

  • パターン/line-items/:order_id,item_id/detailは、次のパスと一致します:

    • /line-items/101,493/detail – order_idは101にバインドされ、item_idは493にバインドされます
    • /line-items/101,/detail– order_idは101にバインドされ、item_idnullにバインドされます
    • /line-items/,493/detail – order_idnullにバインドされ、item_idは493にバインドされます
    • /line-items/,/detail – order_idnullにバインドされ、item_idはnullにバインドされます
  • パターン/line-items/:order_iditem_idおよびcategory_id/detail/categoryは、次のパスと一致します:

    • /line-items/101,493,14/detail/category – order_idは101にバインドされ、item_idは493にバインドされ、カテゴリは14にバインドされます
    • /line-items/101,/detail/category – order_idは101にバインドされ、item_idnullにバインドされ、カテゴリはnullにバインドされます
    • /line-items/,493/detail/category – order_idnullにバインドされ、 item_idは493にバインドされ、カテゴリはnullにバインドされます
    • /line-items/,,493/detail/category – order_idnullにバインドされ、item_idnullにバインドされ、カテゴリは493にバインドされます
    • /line-items/,/detail/category – order_idnullにバインドされ、item_idnullにバインドされ、カテゴリはnullにバインドされます
    • 末尾のカンマ区切り文字は省略できるため、次のパスも一致します:

    • /line-items/101/detail - order_idは101にバインドされ、item_idnullにバインドされます
コンポーネント値にカンマ文字が含まれている場合は、リクエスト・パス内でパーセント・エンコードする必要があります。たとえば、パターン/books/title,authorの場合は次のようになります:
  • /books/So%20Long%2C%20and%20Thanks%20for%20All%20the%20Fish,Douglas%20Adamsは一致します。カンマ文字がパーセント・エンコードされています
  • /books/Eats,%20Shoots%20%26%20Leaves,Lynne%20Trussでは、照合する範囲に2つのカンマ文字があります。カンマ文字は1つのみであることが予期されているため、一致しません。

16.6.5 グローバル・パラメータの照合

この項では、globパラメータの照合トークンについて説明します。

globパラメータのトークンは、文字列の末尾までの0個以上の文字と照合されます。

パターン/foo/*は、次のパスと一致します:

  • /foo/では空の文字列と一致します。
  • /foo/barではbarと一致します
  • /foo/bar/ではbar/と一致します
  • /foo/bar/bazではbar/bazと一致します

16.7 ルート・パターン・セット

ルート・パターンのコレクションは、ルート・パターン・セットと呼ばれます。

ルート・パターン・セットは明確である必要があります。つまり、特定のリクエスト・パスでは、リクエスト・パスと一致する最大1つのルート・パターンをセットから選択できる必要があります。ルート・パターンは、ルート・パターン内で最も限定的なパターンから最も限定的ではないパターンに順序付ける必要があります。リクエスト・パスとルート・パターン・セットの照合は、最も限定的なパターンから最も限定的ではないパターンの順序で実行する必要があります。照合は、一致する最初のルート・パターンで停止します。

16.7.1 等価パターンと重複パターン

この項では、等価パターンと重複パターンについて説明します。

等価ルート・パターンまたは重複ルート・パターンは、同じルート・パターン・セットに指定しないでください。

等価パターン

パターン間の唯一の違いがパラメータに割り当てられた名前である場合、名前付きパターンは等価です。

次の2つのパターンは、名前付きパラメータに割り当てられた名前のみが異なるため、同じルート・パターン・セットでは許可されません:
  • /a/:b/
  • a/:c
どちらの名前付きパターンも同じリクエスト・パスのセットと一致するため、特定のリクエスト・パスに対するパターンの選択に関して曖昧さが生じます。

重複パターン

重複パターンは、リクエスト・パスのサブセットで複数のルート・パターンが一致し、次に説明するトークンの優先順位付けによって選択するルート・パターンが解決されないルート・パターンです。

重複する修飾子

ルート・パターン・セットには、修飾子の使用のみが異なる2つ以上の名前付きパターンを含めることはできません。

次の3つのパターンは、名前付きパラメータに割り当てられた修飾子のみが異なるため、同じルート・パターン・セットでは許可されません:

  • /a/:b
  • /a/:b?
  • /a/:b*

重複するリテラル・パターンとGlobパターン

オプション名前付きパターンは、同じルート・パターン・セット内のリテラル・パターンと重複できません。

次のものはルート・パターン・セットです:

  • /a
  • /b
  • /c/d
  • /c/d/a/1
  • /a/b/c/d/e/
このセットの予想される順序は次のとおりです:
  • /c/d/a/1
  • /c/d
  • /b
  • /a/b/c/d/e/
  • /a

16.7.2 トークンの優先順位

この項では、ルート・パターンの優先順位を決定するアプローチについて説明します。

異なるトークン・タイプには、ルート・パターン・セットの確定的なソートを可能にするために、最も限定的なものから最も限定的ではないものに優先順位が割り当てられます。

リテラル値とパス・セパレータ

リテラル値およびパス・セパレータは完全一致を必要とするため、優先度が最も高くなります。リテラル値は辞書の逆順に並べられ、短いリテラル・トークンの前に長いリテラル・トークンがテストされるようにされます。

複合名前付きパラメータ

複合名前付きパラメータは、一致した値内のカンマ文字を照合する要件によって、名前付きパラメータよりも限定されるため、2番目に優先されます。

オプション複合名前付きパラメータ

オプション複合名前付きパラメータの優先順位は3番目に高く、空の文字列と一致する可能性があるため、複合名前付きパラメータより限定的ではありません。

名前付きパラメータ

名前付きパラメータの優先順位は4番目に高く、1つ以上の文字が次のパス・セパレータまたは文字列の末尾まで照合されます。

オプション名前付きパラメータ

オプション名前付きパラメータは、優先順位が5番目に高く、文字列の末尾までのパス・セパレータを除く0文字以上の文字と照合されます。

イーガー名前付きパラメータ

イーガー名前付きパラメータは、優先順位が6番目に高く、文字列の末尾までのパス・セパレータを含む1つ以上の文字と照合されます。

Globパラメータ

Globパラメータは、最も限定的ではないパターンであるため、最も優先順位が低くなり、文字列の末尾までのゼロ個以上の文字と照合されます。

次のルート・パターン・セットがあるとします:

  • /*
  • /foo/*
  • /a/:p1
  • /a/:p1/c
  • /:p1/b/c
  • /b/:p1?
  • /b/c/:p1*
  • /a/:p1/c/:p2
これらのルート・パターンの予期される順序は、最も限定的なものから最も限定的ではないものの順で、次のようになります:
  • /foo/*
  • /b/c/:p1*
  • /b/:p1?
  • /a/:p1/c/:p2
  • /a/:p1/c
  • /a/:p1
  • /:p1/b/c
  • /*

順序の実装例

ノート:

このセクションは標準ではありません。

指定されたルート・パターンの順序付けを実装する1つのアプローチは、各パターンを正規の文字列表現に変換してから、これらの正規文字列を辞書の逆順でソートすることです。これは、パターンの各種のパラメータ・トークンを単一の下限値文字に置き換えることによって実現されます。最も低い優先度のパターンが最小値の文字になり、最も高い優先度のパターンが最大値の文字になります(次のリストを参照):

  • Glob -> '!'
  • イーガー名前付き -> '#'
  • オプション名前付き -> '$'
  • 名前付き -> '''
  • オプション複合 -> '('
  • 複合 -> ')'
前述の例のパターンにこの表を適用すると、各パターンの正規の文字列は次のようになります:
  • /foo/!
  • /b/c/#
  • /b/:$
  • /a/'/c/'
  • /a/'/c
  • /a/'
  • /'/b/c
  • /!

置換文字は予約文字セットの一部であり、リテラル・トークンと競合しないようにされるため、パターン間の曖昧さが排除されます。