22.2. カスタムデータトランスポートを使用するための KIE Server の拡張

デフォルトでは、KIE Server の拡張が REST または JMS データトランスポートを使用して公開されます。KIE Server を拡張して、カスタムのデータトランスポートのサポートを追加し、KIE Server トランスポートプロトコルをビジネスニーズに合わせます。

たとえば、以下の手順では、Drools 拡張を使用し、Apache MINA (オープンソースの Java ネットワークアプリケーションフレームワーク) をベースとする KIE Server にカスタムのデータトランスポートを追加します。カスタムの MINA トランスポートの例では、既存のマーシャリング操作に依存し、JSON 形式のみをサポートする文字列ベースのデータを変換します。

手順

  1. 空の Maven プロジェクトを作成して、以下のパッケージタイプと依存関係を、プロジェクトの pom.xml ファイルに定義します。

    サンプルプロジェクトの pom.xml ファイルの例

    <packaging>jar</packaging>
    
    <properties>
      <version.org.kie>7.48.0.Final-redhat-00004</version.org.kie>
    </properties>
    
    <dependencies>
      <dependency>
        <groupId>org.kie</groupId>
        <artifactId>kie-api</artifactId>
        <version>${version.org.kie}</version>
      </dependency>
      <dependency>
        <groupId>org.kie</groupId>
        <artifactId>kie-internal</artifactId>
        <version>${version.org.kie}</version>
      </dependency>
      <dependency>
        <groupId>org.kie.server</groupId>
        <artifactId>kie-server-api</artifactId>
        <version>${version.org.kie}</version>
      </dependency>
      <dependency>
        <groupId>org.kie.server</groupId>
        <artifactId>kie-server-services-common</artifactId>
        <version>${version.org.kie}</version>
      </dependency>
      <dependency>
        <groupId>org.kie.server</groupId>
        <artifactId>kie-server-services-drools</artifactId>
        <version>${version.org.kie}</version>
      </dependency>
      <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-core</artifactId>
        <version>${version.org.kie}</version>
      </dependency>
      <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-compiler</artifactId>
        <version>${version.org.kie}</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.25</version>
      </dependency>
      <dependency>
        <groupId>org.apache.mina</groupId>
        <artifactId>mina-core</artifactId>
        <version>2.1.3</version>
      </dependency>
    </dependencies>

  2. 以下の例のように、プロジェクトの Java クラスに org.kie.server.services.api.KieServerExtension インターフェースを実装します。

    KieServerExtension インターフェースの実装例

    public class MinaDroolsKieServerExtension implements KieServerExtension {
    
        private static final Logger logger = LoggerFactory.getLogger(MinaDroolsKieServerExtension.class);
    
        public static final String EXTENSION_NAME = "Drools-Mina";
    
        private static final Boolean disabled = Boolean.parseBoolean(System.getProperty("org.kie.server.drools-mina.ext.disabled", "false"));
        private static final String MINA_HOST = System.getProperty("org.kie.server.drools-mina.ext.port", "localhost");
        private static final int MINA_PORT = Integer.parseInt(System.getProperty("org.kie.server.drools-mina.ext.port", "9123"));
    
        // Taken from dependency on the `Drools` extension:
        private KieContainerCommandService batchCommandService;
    
        // Specific to MINA:
        private IoAcceptor acceptor;
    
        public boolean isActive() {
            return disabled == false;
        }
    
        public void init(KieServerImpl kieServer, KieServerRegistry registry) {
    
            KieServerExtension droolsExtension = registry.getServerExtension("Drools");
            if (droolsExtension == null) {
                logger.warn("No Drools extension available, quitting...");
                return;
            }
    
            List<Object> droolsServices = droolsExtension.getServices();
            for( Object object : droolsServices ) {
                // If the given service is null (not configured), continue to the next service:
                if (object == null) {
                    continue;
                }
                if( KieContainerCommandService.class.isAssignableFrom(object.getClass()) ) {
                    batchCommandService = (KieContainerCommandService) object;
                    continue;
                }
            }
            if (batchCommandService != null) {
                acceptor = new NioSocketAcceptor();
                acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" ))));
    
                acceptor.setHandler( new TextBasedIoHandlerAdapter(batchCommandService) );
                acceptor.getSessionConfig().setReadBufferSize( 2048 );
                acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 );
                try {
                    acceptor.bind( new InetSocketAddress(MINA_HOST, MINA_PORT) );
    
                    logger.info("{} -- Mina server started at {} and port {}", toString(), MINA_HOST, MINA_PORT);
                } catch (IOException e) {
                    logger.error("Unable to start Mina acceptor due to {}", e.getMessage(), e);
                }
    
            }
        }
    
        public void destroy(KieServerImpl kieServer, KieServerRegistry registry) {
            if (acceptor != null) {
                acceptor.dispose();
                acceptor = null;
            }
            logger.info("{} -- Mina server stopped", toString());
        }
    
        public void createContainer(String id, KieContainerInstance kieContainerInstance, Map<String, Object> parameters) {
            // Empty, already handled by the `Drools` extension
    
        }
    
        public void disposeContainer(String id, KieContainerInstance kieContainerInstance, Map<String, Object> parameters) {
          // Empty, already handled by the `Drools` extension
    
        }
    
        public List<Object> getAppComponents(SupportedTransports type) {
            // Nothing for supported transports (REST or JMS)
            return Collections.emptyList();
        }
    
        public <T> T getAppComponents(Class<T> serviceType) {
    
            return null;
        }
    
        public String getImplementedCapability() {
            return "BRM-Mina";
        }
    
        public List<Object> getServices() {
            return Collections.emptyList();
        }
    
        public String getExtensionName() {
            return EXTENSION_NAME;
        }
    
        public Integer getStartOrder() {
            return 20;
        }
    
        @Override
        public String toString() {
            return EXTENSION_NAME + " KIE Server extension";
        }
    }

    KieServerExtension インターフェースは、新規の MINA トランスポートの機能を追加する時に KIE Server が使用する主要な拡張インターフェースです。このインターフェースには、以下のコンポーネントが含まれます。

    KieServerExtension インターフェースの概要

    public interface KieServerExtension {
    
        boolean isActive();
    
        void init(KieServerImpl kieServer, KieServerRegistry registry);
    
        void destroy(KieServerImpl kieServer, KieServerRegistry registry);
    
        void createContainer(String id, KieContainerInstance kieContainerInstance, Map<String, Object> parameters);
    
        void disposeContainer(String id, KieContainerInstance kieContainerInstance, Map<String, Object> parameters);
    
        List<Object> getAppComponents(SupportedTransports type);
    
        <T> T getAppComponents(Class<T> serviceType);
    
        String getImplementedCapability();  1
    
        List<Object> getServices();
    
        String getExtensionName();  2
    
        Integer getStartOrder();  3
    }

    1
    この拡張で対応している機能を指定します。この機能は、KIE Server 内で一意でなければなりません。
    2
    拡張は、人間が解読可能な名前に定義します。
    3
    指定した拡張の起動のタイミングを決定します。他の拡張と依存関係がある拡張の場合、この設定は親の設定と競合しないようにしてください。たとえば、今回の場合、このカスタムの拡張は Drools 拡張に依存しており、Drool 拡張の StartOrder0 に設定されているため、このカスタムのアドオン拡張は 0 を超える値でなければなりません (サンプルの実装では 20 に設定)。

    このインターフェースの先程の MinaDroolsKieServerExtension 実装例では、init メソッドが主に、Drools 拡張からサービスを収集して、MINA サーバーをブートストラップ化する要素となっています。KieServerExtension インターフェースの他のメソッドは、標準の実装のままで、インターフェースの要件を満たします。

    TextBasedIoHandlerAdapter クラスは、受信要求に対応する MINA サーバーにあるハンドラーです。

  3. 以下の例のように、MINA サーバーの TextBasedIoHandlerAdapter ハンドラーを実装します。

    TextBasedIoHandlerAdapter ハンドラーの実装例

    public class TextBasedIoHandlerAdapter extends IoHandlerAdapter {
    
        private static final Logger logger = LoggerFactory.getLogger(TextBasedIoHandlerAdapter.class);
    
        private KieContainerCommandService batchCommandService;
    
        public TextBasedIoHandlerAdapter(KieContainerCommandService batchCommandService) {
            this.batchCommandService = batchCommandService;
        }
    
        @Override
        public void messageReceived( IoSession session, Object message ) throws Exception {
            String completeMessage = message.toString();
            logger.debug("Received message '{}'", completeMessage);
            if( completeMessage.trim().equalsIgnoreCase("quit") || completeMessage.trim().equalsIgnoreCase("exit") ) {
                session.close(false);
                return;
            }
    
            String[] elements = completeMessage.split("\\|");
            logger.debug("Container id {}", elements[0]);
            try {
                ServiceResponse<String> result = batchCommandService.callContainer(elements[0], elements[1], MarshallingFormat.JSON, null);
    
                if (result.getType().equals(ServiceResponse.ResponseType.SUCCESS)) {
                    session.write(result.getResult());
                    logger.debug("Successful message written with content '{}'", result.getResult());
                } else {
                    session.write(result.getMsg());
                    logger.debug("Failure message written with content '{}'", result.getMsg());
                }
            } catch (Exception e) {
    
            }
        }
    }

    この例では、ハンドラークラスはテキストメッセージを受信して、Drools サービスでこのメッセージを実行します。

    TextBasedIoHandlerAdapter ハンドラー実装を使用する場合は、以下のハンドラー要件と動作を考慮してください。

    • 各受信トランスポート要求が 1 行であるため、ハンドラーに送信する内容は、1 行でなければなりません。
    • ハンドラーで containerID|payload の形式が想定されるように、この 1 行に KIE コンテナー ID を渡す必要があります。
    • マーシャラーで生成される方法で応答を設定できます。応答は複数行にすることができます。
    • このハンドラーは stream mode をサポートし、KIE Server セッションを切断せずにコマンドを送信できます。ストリームモードで KIE Server セッションを終了するには、サーバーに exit コマンドまたは quit コマンドを送信してください。
  4. 新規のデータトランスポートを KIE Server で検出できるようにするには、Maven プロジェクトで META-INF/services/org.kie.server.services.api.KieServerExtension ファイルを作成し、このファイルに KieServerExtension 実装クラスの完全修飾名を追加します。たとえば、このファイルには org.kie.server.ext.mina.MinaDroolsKieServerExtension の 1 行が含まれます。
  5. プロジェクトを構築して、作成された JAR ファイルと mina-core-2.0.9.jar ファイル (今回の例でこの拡張が依存) をプロジェクトの ~/kie-server.war/WEB-INF/lib ディレクトリーにコピーします。たとえば、Red Hat JBoss EAP ではこのディレクトリーへのパスは EAP_HOME/standalone/deployments/kie-server.war/WEB-INF/lib です。
  6. KIE Server を起動して、実行中の KIE Server に構築したプロジェクトをデプロイします。プロジェクトは、Business Central インターフェースまたは KIE Server REST API (http://SERVER:PORT/kie-server/services/rest/server/containers/{containerId} への PUT 要求) を使用してデプロイできます。

    プロジェクトを実行中の KIE Server にデプロイした後に、KIE Server ログで新規データトランスポートのステータスを表示して、新規データトランスポートの使用を開始できます。

    サーバーログの新規データトランスポート

    Drools-Mina KIE Server extension -- Mina server started at localhost and port 9123
    Drools-Mina KIE Server extension has been successfully registered as server extension

    この例では、Telnet を使用して KIE Server の 新しい MINA ベースのデータトランスポートと対話できます。

    コマンドターミナルでの Telnet の開始およびポート 9123 での KIE Server の接続

    telnet 127.0.0.1 9123

    コマンドターミナルでの KIE Server との対話例

    Trying 127.0.0.1...
    Connected to localhost.
    Escape character is '^]'.
    
    # Request body:
    demo|{"lookup":"defaultKieSession","commands":[{"insert":{"object":{"org.jbpm.test.Person":{"name":"john","age":25}}}},{"fire-all-rules":""}]}
    
    # Server response:
    {
      "results" : [ {
        "key" : "",
        "value" : 1
      } ],
      "facts" : [ ]
    }
    
    demo|{"lookup":"defaultKieSession","commands":[{"insert":{"object":{"org.jbpm.test.Person":{"name":"mary","age":22}}}},{"fire-all-rules":""}]}
    {
      "results" : [ {
        "key" : "",
        "value" : 1
      } ],
      "facts" : [ ]
    }
    
    demo|{"lookup":"defaultKieSession","commands":[{"insert":{"object":{"org.jbpm.test.Person":{"name":"james","age":25}}}},{"fire-all-rules":""}]}
    {
      "results" : [ {
        "key" : "",
        "value" : 1
      } ],
      "facts" : [ ]
    }
    exit
    Connection closed by foreign host.

    サーバーログの出力例

    16:33:40,206 INFO  [stdout] (NioProcessor-2) Hello john
    16:34:03,877 INFO  [stdout] (NioProcessor-2) Hello mary
    16:34:19,800 INFO  [stdout] (NioProcessor-2) Hello james