[Cloud] Java EE based microservice on Oracle Cloud with Payara Micro

原文はこちら。
https://community.oracle.com/community/cloud_computing/oracle-cloud-developer-solutions/blog/2017/03/09/java-ee-based-microservice-on-application-container-cloud-with-payara-micro

(訳注)
  1. 元記事がやや古い(2017/03/09)ため、この当時はこのようなやり方を使った、とご理解ください。
  2. @khasunuma さんから、以下のコメントをいただきました。どうもありがとうございます!
    • 2017/06/14時点での最新のPayaraを使うと、Mavenプラグインは独自のものが用意されている
    • Mavenを使わず、コマンドラインからでもUber JARを作成できる
このエントリでは、Payara Microを使って、Java EEベースのマイクロサービスを構築する方法をご紹介します。
Payara Micro
http://www.payara.fish/payara_micro
Oracle Cloud (PaaS) Stackの以下のサービスを活用します。
  • Developer Cloud service
    コードのホスト(Git Repository)、および(他のOracle PaaSサービスとの統合による) Continuous Integration & Continuous Deployment機能の提供 
  • Application Container Cloud service
    Java EE マイクロサービスを実行するためのスケーラブルなaPaaS

Overview

Payara Micro?

Payara Microとは、マイクロサービススタイルのアプリケーションを構築するためのJava EEベースのソリューションです。少々説明しますと・・・
  • Java EE
    Payara MicroはJava EE Web Profile標準ならびにWeb Profileに含まれないその他の仕様(例えばBatch、Concurrency Utilitiesなど)もサポートします。
  • It’s a library
    これらの機能は全てカプセル化されたJARファイルとして利用できます。

Development model

Payara Microは複数のデプロイメントスタイルを選択できます。
  • WAR
    Java EEアプリケーションをWARファイルにパッケージし、以下のかたちでPayara Microとともに起動します。
    java –jar payara-micro-<version>.jar --deploy mystocks.war
  • Embedded mode
    ライブラリあので、Javaアプリケーション内にAPIを使って埋め込むことができます。
  • Uber JAR
    Payara MicroはMavenをサポートしているので、 exec pluginを使い、fat JARとしてPayara MicroライブラリとともにWARファイルをパッケージします。
    exec:javaプラグイン
    http://www.mojohaus.org/exec-maven-plugin/java-mojo.html
このエントリでは、fat JARの方法を使うことにします。

Benefits

潜在的なメリットは以下のようです。
  • Microservices friendly
    ライブラリとしてJava EEを使えるため、アプリケーション内で簡単に利用でき、柔軟な方法(WAR+JAR、もしくはただのfat JAR)でパッケージできます。また、PaaS、コンテナベースのプラットフォームといった複数の環境で実行できます。
  • Leverage Java EE skill set
    JAX-RS、JPA、EJB、CDIといったJava EE仕様の知識を活用できます。

About the sample application

JAX-RSやEJB、CDI、WebSocketといったAPIを使うシンプルなJava EEアプリケーションです。これはNYSEの株価の追跡に役立つアプリケーションです。
  • 利用者はNASDAQに上場している株価をシンプルなRESTインターフェースでチェックできます。
  • リアルタイムの株価追跡も可能ですが、この機能はOracle (ORCL) に対してのみ有効です。
ハイレベル図と背景をまとめておきます。
  • EJBスケジューラがORCLを定期的にチェックして株価を取得し、CDIイベントを発行します。(CDI Event Observerとしてマークされた)WebSocketコンポーネントがそのイベントを受け取り、接続済みのクライアントに対し最新の価格をアップデートします。
  • JAX-RS RESTエンドポイントを使ってオンデマンドで任意の企業の株価を取得します。これは(双方向、完全二重型のWebSocketインタラクションとは異なる)典型的なリクエスト-レスポンスベースのHTTPインタラクションです。 

Code

では、関連する部分のコードを見ていきましょう(簡単にするためにimport文は省略しています)。

RealTimeStockTicker.java
@ServerEndpoint("/rt/stocks") 
public class RealTimeStockTicker { 
 
 
    //stores Session (s) a.k.a connected clients 
    private static final List<Session> CLIENTS = new ArrayList<>(); 
 
    /**
     * Connection callback method. Stores connected client info
     *
     * @param s WebSocket session
     */ 
    @OnOpen 
    public void open(Session s) { 
        CLIENTS.add(s); 
        Logger.getLogger(RealTimeStockTicker.class.getName()).log(Level.INFO, "Client connected -- {0}", s.getId()); 
    } 
 
    /**
     * pushes stock prices asynchronously to ALL connected clients
     *
     * @param tickTock the stock price
     */ 
    public void broadcast(@Observes @StockDataEventQualifier String tickTock) { 
        Logger.getLogger(RealTimeStockTicker.class.getName()).log(Level.INFO, "Event for Price {0}", tickTock); 
        for (final Session s : CLIENTS) { 
            if (s != null && s.isOpen()) { 
                /**
                 * Asynchronous push
                 */ 
                s.getAsyncRemote().sendText(tickTock, new SendHandler() { 
                    @Override 
                    public void onResult(SendResult result) { 
                        if (result.isOK()) { 
                            Logger.getLogger(RealTimeStockTicker.class.getName()).log(Level.INFO, "Price sent to client {0}", s.getId()); 
                        } else { 
                            Logger.getLogger(RealTimeStockTicker.class.getName()).log(Level.SEVERE, "Could not send price update to client " + s.getId(), 
                                    result.getException()); 
                        } 
                    } 
                }); 
            } 
        } 
    } 
 
    /**
     * Disconnection callback. Removes client (Session object) from internal
     * data store
     *
     * @param s WebSocket session
     */ 
    @OnClose 
    public void close(Session s) { 
        CLIENTS.remove(s); 
        Logger.getLogger(RealTimeStockTicker.class.getName()).log(Level.INFO, "Client discconnected -- {0}", s.getId()); 
    } 
} 
StockDataEventQualifier.java
/**
 * Custom CDI qualifier to stamp CDI stock price CDI events
 * 
 */ 
@Qualifier 
@Retention(RUNTIME) 
@Target({METHOD, FIELD, PARAMETER, TYPE}) 
public @interface StockDataEventQualifier { 
} 
StockPriceScheduler.java
/** 
 * Periodically polls the Google Finance REST endpoint using the JAX-RS client 
 * API to pull stock prices and pushes them to connected WebSocket clients using 
 * CDI events 
 * 
 */  
@Singleton  
@Startup  
public class StockPriceScheduler {  
  
  
    @Resource  
    private TimerService ts;  
    private Timer timer;  
  
  
    /** 
     * Sets up the EJB timer (polling job) 
     */  
    @PostConstruct  
    public void init() {  
        /** 
         * fires 5 secs after creation 
         * interval = 5 secs 
         * non-persistent 
         * no-additional (custom) info 
         */  
        timer = ts.createIntervalTimer(5000, 5000, new TimerConfig(null, false)); //trigger every 5 seconds  
        Logger.getLogger(StockPriceScheduler.class.getName()).log(Level.INFO, "Timer initiated");  
    }  
  
  
    @Inject  
    @StockDataEventQualifier  
    private Event<String> msgEvent;  
  
  
    /** 
     * Implements the logic. Invoked by the container as per scheduled 
     * 
     * @param timer the EJB Timer object 
     */  
    @Timeout  
    public void timeout(Timer timer) {  
        Logger.getLogger(StockPriceScheduler.class.getName()).log(Level.INFO, "Timer fired at {0}", new Date());  
        /** 
         * Invoked asynchronously 
         */  
        Future<String> tickFuture = ClientBuilder.newClient().  
                target("https://www.google.com/finance/info?q=NASDAQ:ORCL").  
                request().buildGet().submit(String.class);  
  
  
        /** 
         * Extracting result immediately with a timeout (3 seconds) limit. This 
         * is a workaround since we cannot impose timeouts for synchronous 
         * invocations 
         */  
        String tick = null;  
        try {  
            tick = tickFuture.get(3, TimeUnit.SECONDS);  
        } catch (InterruptedException | ExecutionException | TimeoutException ex) {  
            Logger.getLogger(StockPriceScheduler.class.getName()).log(Level.INFO, "GET timed out. Next iteration due on - {0}", timer.getNextTimeout());  
            return;  
        }  
          
        if (tick != null) {  
            /** 
             * cleaning the JSON payload 
             */  
            tick = tick.replace("// [", "");  
            tick = tick.replace("]", "");  
  
  
            msgEvent.fire(StockDataParser.parse(tick));  
        }  
  
  
    }  
  
  
    /** 
     * purges the timer 
     */  
    @PreDestroy  
    public void close() {  
        timer.cancel();  
        Logger.getLogger(StockPriceScheduler.class.getName()).log(Level.INFO, "Application shutting down. Timer will be purged");  
    }  
}  
RESTConfig.java
/**
 * JAX-RS configuration class
 * 
 */ 
@ApplicationPath("api") 
public class RESTConfig extends Application{ 
     
}
StockDataParser.java
/**
 * A simple utility class which leverages the JSON Processing (JSON-P) API to filter the JSON 
 * payload obtained from the Google Finance REST endpoint and returns useful data in a custom format
 * 
 */ 
public class StockDataParser { 
     
    public static String parse(String data){ 
         
        JsonReader reader = Json.createReader(new StringReader(data)); 
                JsonObject priceJsonObj = reader.readObject(); 
                String name = priceJsonObj.getJsonString("t").getString(); 
                String price = priceJsonObj.getJsonString("l_cur").getString(); 
                String time = priceJsonObj.getJsonString("lt_dts").getString(); 
 
        return (String.format("Price for %s on %s = %s USD", name, time, price)); 
    } 
} 

A note on packaging

開発の観点から、前述の通り、通常のWARベースのJava EEアプリケーションをfat JARとしてPayara Microのコンテナとともにパッケージします。
コンテナにアプリケーションをデプロイするのではなく、アプリケーションとともにコンテナをパッケージングする点にご注意ください。

Payara MicroライブラリにJava EE APIは存在するので、Java EE APIはコンパイル時のみ必要です(scope = provided)
<dependency> 
    <groupId>javax</groupId> 
    <artifactId>javaee-api</artifactId> 
    <version>7.0</version> 
    <scope>provided</scope> 
</dependency> 
Mavenプラグインを使ってfat JARを生成します
<plugin> 
    <groupId>org.codehaus.mojo</groupId> 
    <artifactId>exec-maven-plugin</artifactId> 
    <version>1.5.0</version> 
    <dependencies> 
        <dependency> 
            <groupId>fish.payara.extras</groupId> 
            <artifactId>payara-micro</artifactId> 
            <version>4.1.1.164</version> 
        </dependency> 
    </dependencies> 
    <executions> 
        <execution> 
            <id>payara-uber-jar</id> 
            <phase>package</phase> 
            <goals> 
                <goal>java</goal> 
            </goals> 
            <configuration> 
                <mainClass>fish.payara.micro.PayaraMicro</mainClass> 
                <arguments> 
                    <argument>--deploy</argument> 
                    <argument>${basedir}/target/${project.build.finalName}.war</argument> 
                    <argument>--outputUberJar</argument>                                                   
                    <argument>${basedir}/target/${project.build.finalName}.jar</argument> 
                </arguments> 
                <includeProjectDependencies>false</includeProjectDependencies> 
                <includePluginDependencies>true</includePluginDependencies> 
                <executableDependency> 
                    <groupId>fish.payara.extras</groupId> 
                    <artifactId>payara-micro</artifactId> 
                </executableDependency> 
            </configuration> 
        </execution> 
    </executions> 
</plugin>

Setting up Continuous Integration & Deployment

以下の章でOracle Developer Cloud Serviceで実施した構成について取り扱います。

Project & code repository creation

以下のエントリのProject & code repository creationの章をご覧になるか、詳細についてはサービスのドキュメントをご覧ください。
Tracking JUnit test results in Developer Cloud service
https://community.oracle.com/community/cloud_computing/oracle-cloud-developer-solutions/blog/2016/10/05/junit-testing-using-oracle-developer-cloud
Oracle® Cloud Using Oracle Developer Cloud Service
Creating a Project
http://docs.oracle.com/cloud/latest/devcs_common/CSDCS/GUID-3317B279-A9C0-4566-A289-BD651A89D7B5.htm#GUID-7B30C8EC-6CDA-4F14-9791-8AE3BB3E8343

Configure source code in Git repository

ローカルシステムから先ほど作成したDeveloper CloudのGitリポジトリにプロジェクトをPushします。
Oracle® Cloud Using Oracle Developer Cloud Service
Pushing an Existing Local Git Repository to an Empty Oracle Developer Cloud Service Git Repository
http://docs.oracle.com/cloud/latest/devcs_common/CSDCS/GUID-B4C03296-8497-4356-8C74-2031D1FB96FC.htm#CSDCS-GUID-A33E83CE-845C-4393-8C93-936527033715
この操作はコマンドラインから実施しますが、事前にGitクライアントをローカルマシンにインストールしておく必要があります。Gitを使ってもいいですし、お好みのものを使うことができます。
Git
https://git-scm.com/downloads
cd <project_folder>  
git init   
git remote add origin <developer_cloud_git_repo>   
//e.g. https://john.doe@developer.us.oraclecloud.com/developer007-foodomain/s/developer007-foodomain-project_2009/scm/sample.git//john.doe@developer.us.oraclecloud.com/developer007-foodomain/s/developer007-foodomain-project_2009/scm/sample.git    
git add .   
git commit -m "first commit"   
git push -u origin master  //Please enter the password for your Oracle Developer Cloud account when prompted

Configure build

新しいジョブを作成しましょう。


JDKを選択します。


Continuous Integration (CI)

Git リポジトリを選択します。


ビルドトリガーを設定します。このビルドジョブは、(git pushなどの)Gitリポジトリの更新に応じて呼び出されます。


ビルドステップを追加します。
  • Maven : WARとfat JAR作成のためのビルドステップ
  • Execute Shell : 必要なデプロイメント・ディスクリプタ(Application Container Cloudではmanifest.jsonが必要です)とともにアプリケーションJARをパッケージングするステップ


以下はコマンドの例です。
zip -j accs-payara-micro.zip target/mystocks.jar manifest.json  
manifest.json の例です。
{ 
    "runtime": { 
        "majorVersion": "8" 
    }, 
    "command": "java -jar mystocks.jar --port $PORT --noCluster", 
    "release": { 
        "build": "23022017.1202", 
        "commit": "007", 
        "version": "0.0.1" 
    }, 
    "notes": "Java EE on ACC with Payara Micro" 
} 
デプロイ可能なZipファイルをアーカイブするためのビルド後のアクションを有効化します。


Execute Build

デプロイメントの構成前に、デプロイメントの構成が参照可能なアーティファクトを生成するためにビルドを呼び出す必要があります。


ビルドが完了した後、以下のことが可能になっています。
  • ビルドログの確認
  • アーカイブ済みのアーティファクトの確認
ログ


アーティファクト


Continuous Deployment (CD) to Application Container Cloud

新たにデプロイメント用のConfigurationを作成します。


  • 必要事項を入力し、Deployment targetを構成します
  • Application Container Cloudインスタンスを構成します
  • 最後の確認ページの自動デプロイメントオプションを構成します
最終的に以下のような構成になっているはずです。


確認画面


Application Container Cloudのアプリケーションを確認します。


Test the CI/CD flow

ちょっとコードを変更し、Developer Cloud ServiceのGitリポジトリにPushしてみましょう。すると以下を確認できるはずです。
  • 自動的にビルドが呼び出され、成功したら
  • 自動的にデプロイメントプロセスが呼び出され
  • つづいて新しいバージョンのアプリケーションをApplication Container Cloudに再デプロイする

Test the application

  • 特定の企業の株価をチェックする場合、GETでURLをつつきます。以下はその例です(このURLはサンプルで、実際にデプロイした環境にあわせる必要があります)。
    https://acc-p-m-mydomain.apaas.em1.oraclecloud.com/mystocks/api/stocks?ticker=AAPL
  • リアルタイムフィードをサブスクライブする場合、WebSocketクライアントを使って指定のエンドポイントURLにアクセスします。以下はその例です。
    wss://acc-p-m-mydomain.apaas.em1.oraclecloud.com/mystocks/rrt/stocks
Chomeブラウザにプラグインとしてインストールできるクライアントを使うことをお勧めします。例えば、Simple WebSocket Clientなどがよいでしょう。
Simple WebSocket Client
https://chrome.google.com/webstore/detail/simple-websocket-client/pfdhoblngboilpfeibdedpjgfnlcodoo?hl=en

[Cloud] Accessing Oracle Process Cloud Service REST API using OAuth

原文はこちら。
https://community.oracle.com/community/cloud_computing/oracle-cloud-developer-solutions/blog/2017/02/19/accessing-oracle-process-cloud-service-rest-api-using-oauth

Oracle Process Cloud service (PCS) はREST APIを提供しており、これを使って他のアプリケーションをPCSと統合できます。REST APIの詳細はリファレンスを参照ください。
REST API for Oracle Process Cloud Service, Version 4.0
https://docs.oracle.com/en/cloud/paas/process-cloud/cprrb/index.html
Oracle Process Cloud ServiceのREST APIは基本認証だけでなく、OAuthトークンを利用することもできます。このエントリでは、OAuthトークンを使ってPCSのREST APIにアクセスし、プロセスの新規インスタンスを作成する方法をご紹介します。

このエントリで説明するシナリオは、JCS-SXにデプロイ済みのWebアプリケーションが、PCSにデプロイ済みのビジネスプロセス("Funds Transfer Process") を呼び出すというものです。この"Funds Transfer"プロセスは、シンプルなプロセスで、リクエストメッセージに含まれるある属性を検証し、必要に応じて人による承認へと進めるものです。このWebアプリケーションはOAuthサーバからOAuthトークンを取得し、トークンを認証のためにPCS REST APIに渡します。

下図はJCS-SX、PCS、OAuthサーバ間のやりとりの概要を図示したものです。

このユースケースでは、JCS-SXインスタンスとPCSインスタンスがともに同じアイデンティティドメインでプロビジョニングされている前提です。同一アイデンティティドメインでプロビジョニングされる場合、OAuthを使った通信に必要なリソースやクライアントはトークン取得のために利用するOAuthサーバと一緒に自動的に構成されます。マイサービス(My Services)のOAuth管理(OAuth Administration)タブを開き、以下のOAuthリソースおよびデフォルトで登録済みのクライアントを確認できます。詳細は以下のURLをご覧ください。
Oracle® Cloud Administering Oracle Cloud Identity Management Release 17.2
Managing OAuth Resources and Clients
https://docs.oracle.com/en/cloud/get-started/subscriptions-cloud/csimg/managing-oauth-resources-and-clients.html
Note: OAuth管理にアクセスするためには、アイデンティティドメイン管理者ロールが必要です。


Note: クライアント識別子(Id、上図の赤枠で囲んだ部分)および、JCS-SX OAuthクライアントの[機密の表示](Show Secret)をクリックすると確認可能なIdに対応する機密 (secret) は、Webアプリケーションがクライアントのアクセストークン取得ならびにPCS REST APIのアクセスするために利用します。

JCS-SX OAuthクライアントを使って、PCS REST APIをWebアプリケーションから呼び出すため、PCSリソースがクライアントからアクセス可能であることを確認しておきましょう。リソースへのアクセス可否は、[クライアントの登録]セクションのJCS-SX OAuthクライアントで、アクションパレット内の変更(Modify)メニューをクリックすることで管理できます(下図)。
pcs_oauth_blog_image_2.png

Note: このエントリでは、ビジネスプロセス(Funds Transfer Process)をPCSにデプロイされていることを前提とします。参考のためにAppendixセクションにこのビジネスプロセスのエクスポート・アーカイブがあります。

前提条件が整えば、WebアプリケーションがPCS REST APIを呼び出すために使うクライアントアクセストークンの取得に取りかかることができます。このサンプルでは、OAuthグラントタイプ(クライアント資格証明とパスワード)を使い、以下の手順でクライアントアクセストークンを取得します。
  1. クライアント資格証明を使ってクライアントアサーションを取得
  2. 取得したクライアントアサーションを使ってアクセストークンを取得
Note: Oracle Platform Service内でWebサービスを呼び出す場合、OWSMポリシーを使えばID伝播を実現でき、明示的にOAuthトークンを処理する必要はありません。今回はOAuthトークンを使ってPCSで認証するための説明を目的としているため、OWSMポリシーを使いません。

これらの手順を具体的なコード・スニペットを使って詳細に説明します。

呼び出し対象のビジネスプロセスの詳細情報と、OAuthトークンサーバへアクセスするために必要な詳細情報をHashMapに保存します。

Note: 説明の都合上、クライアント機密、ユーザー名、パスワードはjava.HashMapに格納していますが、資格証明の安全な管理を確実にするためには、Oracle Credential Store Framework (CSF) の利用を強く推奨します。文末のReferencesセクションから詳細情報を確認してください。
public static HashMap populateMap() {  
    HashMap map = new HashMap();  
    // PCS  
    map.put("PCS_URL", "https://<PCS_HOST>:443/bpm/api/3.0/processes");  
    map.put("PCS_PROCESS_DEF_ID", "default~MyApplication!1.0~FundsTransferProcess");  
    map.put("PCS_FTS_SVC_NAME", "FundsTransferProcess.service");  
    // OAuth  
    map.put("TOKEN_URL", "https://<ID_DOMAIN_NAME>.identity.<DATA_CENTER>.oraclecloud.com/oam/oauth2/tokens");  
    map.put("CLIENT_ID", "<CLIENT_ID>");  
    map.put("SECRET", "<SECRET>");  
    map.put("DOMAIN_NAME", "<ID_DOMAIN_NAME>");  
    map.put("USER_NAME","<PCS_USER_NAME>");  
    map.put("PASSWORD","<PCS_USER_PWD>");  
    return map;  
}  

public String getOAuthToken() throws Exception {  
    String token = "";  
    String authString = entryMap.get("CLIENT_ID")+":"+entryMap.get("SECRET");  
           
    Map clientAssertionMap = getClientAssertion(authString);  
    token = getAccessToken(authString,clientAssertionMap);  

    return token;  
}  
Note: 上記コードで指定した、PCS_PROCESS_DEF_IDおよび PCS_FTS_SVC_NAME をキーとする値は参考のためです。PCSにfunds transferビジネスプロセスをデプロイした後、以下のcURLコマンドを実行してビジネスプロセスの詳細を取得できます。取得した値を使って置き換えてください。
curl -u <PCS_USER_NAME>:<PCS_USER_PWD> -H "Content-Type:application/json" -H "Accept:application/json" -X GET https://<PCS_HOST>:443/bpm/api/4.0/process-definitions
getOAuthTokenメソッドは、OAuthサーバ(トークンエンドポイント)へアクセスし基本認証ヘッダとしてclient_id:client_secret を渡すことで、クライアントアサーションを取得するための実装です。これらの詳細情報は、前述のOAuth管理タブから取得できます。以下のコードスニペットはその実装例です。
private Map<String,String> getClientAssertion(String authString) throws Exception{  
      
    resource = client.resource( entryMap.get("TOKEN_URL")+"");  
      
    ClientResponse res = null;  
    String payload = "grant_type:client_credentials";  
      
    MultiPart multiPart = new MultiPart().bodyPart(new BodyPart(payload.toString(), MediaType.APPLICATION_JSON_TYPE));  
      
    MultivaluedMap formData = new MultivaluedMapImpl();  
    formData.add("grant_type", "client_credentials");  
              
    try {  
    res =   
        resource.header("X-USER-IDENTITY-DOMAIN-NAME",  entryMap.get("DOMAIN_NAME"))  
        .header("Authorization", "Basic " + DatatypeConverter.printBase64Binary(authString.getBytes("UTF-8")))  
        .header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")  
        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)  
        .accept(MediaType.APPLICATION_JSON_TYPE)  
        .post(ClientResponse.class,formData);  
    } catch (Exception e) {  
        System.out.println("In catch: "+e);  
        e.printStackTrace();  
        throw e;  
    }  
      
    String output = res.getEntity(String.class);  
    JSONObject newJObject = null;  
    org.json.simple.parser.JSONParser parser = new org.json.simple.parser.JSONParser();  
    try {  
           
         newJObject = (JSONObject) parser.parse(output);  
          
        } catch (org.json.simple.parser.ParseException e) {  
            e.printStackTrace();  
    }  
            
    Map<String,String> assertionMap = new HashMap <String,String>();  
      
    assertionMap.put("assertion_token",newJObject.get("access_token")+"");  
    assertionMap.put("assertion_type",newJObject.get("oracle_client_assertion_type")+"");  
      
    if (res != null && res.getStatus() != 200) {  
        System.out.println("Server Problem (getClientAssertion): "+res.getStatusInfo());  
        throw new Exception (res.getStatusInfo().getReasonPhrase());  
    }  
    return assertionMap;  
}  
上記のコードでは、Jerseyクライアントを使ってトークンサーバにアクセスし、クライアントアサーションとクライアントアサーションタイプを取得するとともに、ペイロード内でgrant_type:client_credentialsも渡しています。以下のコードスニペットでは、password grant_typeを使い、ユーザー名とパスワードを先ほど取得したクライアントアサーションとともに渡すことで、クライアントアクセストークンをトークンサーバから取得しています。
private String getAccessToken(String authString,Map clientAssertionMap) throws Exception{  
    resource = client.resource(entryMap.get("TOKEN_URL")+"");  
      
    String clientAssertionType = (String) clientAssertionMap.get("assertion_type");  
    String clientAssertion = (String) clientAssertionMap.get("assertion_token");  
                                            
    ClientResponse res = null;  
      
    MultivaluedMap formData = new MultivaluedMapImpl();  
    formData.add("grant_type", "password");  
    formData.add("username", entryMap.get("USER_NAME"));  
    formData.add("password", entryMap.get("PASSWORD"));  
    formData.add("client_assertion_type", clientAssertionType);          
    formData.add("client_assertion", clientAssertion);          
      
    try {  
    res =   
        resource.header("X-USER-IDENTITY-DOMAIN-NAME",  entryMap.get("DOMAIN_NAME"))  
        .header("Authorization", "Basic " + DatatypeConverter.printBase64Binary(authString.getBytes("UTF-8")))  
        .header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")  
        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)  
        .accept(MediaType.APPLICATION_JSON_TYPE)  
        .post(ClientResponse.class,formData);  
    } catch (Exception e) {  
        e.printStackTrace();  
        throw e;  
    }  
      
    String output = res.getEntity(String.class);  
      
    JSONObject newJObject = null;  
    org.json.simple.parser.JSONParser parser = new org.json.simple.parser.JSONParser();  
    try {  
         
       newJObject = (JSONObject) parser.parse(output);  
      
    } catch (org.json.simple.parser.ParseException e) {  
       e.printStackTrace();  
    }  
      
   String token = newJObject.get("access_token")+"";  
      
    if (res != null && res.getStatus() != 200) {  
        System.out.println("Server Problem (getAccessToken): "+res.getStatusInfo());  
        throw new Exception (res.getStatusInfo().getReasonPhrase());  
    }  
    return token;  
}  
これでクライアントアクセストークンを使ってPCSリソースにアクセスできるようになりました。以下のコードスニペットは、PCS REST APIを呼び出し、Funds Transferビジネスプロセスの新規プロセスインスタンスを生成しようとしています。ペイロードには、インスタンス作成対象のプロセス情報(定義ID、サービス名など)と、JSPページでユーザーが入力した入力パラメータが含まれています。先ほどの手順で取得したOAuthトークンをAuthorizationヘッダーに設定していることに注意してください。
public String invokeFundsTransferProcess(String token,FundsTransferRequest ftr) throws Exception {  
   
    StringBuffer payload = new StringBuffer();  
    payload.append("{");  
    payload.append("\"processDefId\":\""+entryMap.get("PCS_PROCESS_DEF_ID").toString()+"\",");  
    payload.append("\"serviceName\":\""+entryMap.get("PCS_FTS_SVC_NAME").toString()+"\",");  
    payload.append("\"operation\":\"start\",");  
    payload.append("\"params\": {");  
    payload.append("\"incidentId\":\""+ftr.getIncidentId()+"\",");  
    payload.append("\"sourceAcctNo\":\""+ftr.getSourceAcctNo()+"\",");  
    payload.append("\"destAcctNo\":\""+ftr.getDestAcctNo()+"\",");  
    payload.append("\"amount\":"+ftr.getAmount()+",");  
    String tsfrType;  
    if(ftr.getTransferType().equals("tparty"))  
        tsfrType = "intra";  
    else  
        tsfrType = "inter";  

    payload.append("\"transferType\":\""+tsfrType+"\"");  
    payload.append("}, \"action\":\"Submit\"");  
    payload.append("}");  
   
    MultiPart multiPart = new MultiPart().bodyPart(new BodyPart(payload.toString(), MediaType.APPLICATION_JSON_TYPE));  

    resource = client.resource(entryMap.get("PCS_URL").toString());  
    ClientResponse res = null;    
    try {  
    res =   
        resource.header("Authorization", "Bearer " + token)  
        .type("multipart/mixed")  
        .accept(MediaType.APPLICATION_JSON)  
        .post(ClientResponse.class, multiPart);  
    } catch (Exception e) {  
        e.printStackTrace();  
        throw e;  
    }  
      
    if (res != null && res.getStatus() != 200) {  
        System.out.println("Server Problem (PCSRestOAuthClient.invokeFundsTransferProcess): "+res.getStatusInfo() +" while invoking "+entryMap.get("PCS_URL").toString());  
        throw new Exception (res.getStatusInfo().getReasonPhrase());  
    }  
  
return res.getStatus()+"";  
}  
シンプルなJSPページを使ってユーザー入力を捕捉し、Funds Transferビジネスプロセスを起動します。

Funds Transferプロセスを開始出来た場合、下図のように、PCSのTrackingページで、生成済みかつ実行中のプロセスインスタンスを確認できます。



Known Issues:

ご利用のJDKによっては、PCS REST APIをJCS-SXから呼び出した場合に「javax.net.ssl.SSLHandshakeException: server certificate change is restricted during renegotiation」というエラーが出る可能性があります。その場合には、回避策として、JCS-SXの以下のシステムプロパティを設定して、サーバーを再起動してください。
  1. weblogic.security.SSL.minimumProtocolVersion をJCS-SXで TLSv1.2 に設定し、再起動する
  2. まだ問題が解決しない場合、jdk.tls.allowunsafeservercertchange を true に設定し、JCS-SXを再起動する

Appendix:

Funds Transferビジネスプロセス(PCSからエクスポートしたアプリケーション:MyApplication.zip

References:

[Java] Java EE 8 - May recap

原文はこちら。
https://blogs.oracle.com/theaquarium/java-ee-8-may-recap

直近のJava EE 8アップデートをまとめておきます。
5月は、JSON-P 1.1 (JSR 374) 、CDI 2.0 (JSR 365) がそれぞれのFinal Approval Ballotを通過しました。
JSR 374: JavaTM API for JSON Processing 1.1
https://jcp.org/en/jsr/detail?id=374
Final Approval Ballot
https://jcp.org/en/jsr/results?id=5982
JSR 365: Contexts and Dependency Injection for JavaTM 2.0CDI 2.0 (JSR 365)
https://jcp.org/en/jsr/detail?id=365
Final Approval Ballot
https://jcp.org/en/jsr/results?id=5995
Java EEプラットフォーム全体におけるCDIの重要性を考慮すれば、これはJava EE 8の重要なマイルストンです。是非以下のエントリをチェックしてください。
Tour around Weld 3
http://weld.cdi-spec.org/news/2017/05/19/tour-around-weld-3/
先月、JSON-B (JSR 367) はFinal Approval Ballotの期間に入りました。この間、JSON-Bの参照実装であるYassonを以下のURLから入手できます。
JSR 367: JavaTM API for JSON Binding (JSON-B)
https://jcp.org/en/jsr/detail?id=367
Yasson 1.0.0-M2
https://repo.eclipse.org/content/repositories/yasson-releases/org/eclipse/yasson/1.0.0-M2/
Servlet 4.0 (JSR 369)、JAX-RS 2.1 (JSR 370)、Bean Validation 2.0 (JSR 380) はいずれも5月にPublic Reciewを終え、現在Public Review Ballotの期間に入っています。さらに、それぞれのSpec LeadがJSRのステータスアップデートをJCP Executive Committeeに提示しました。
最後に、Java EE 8 (JSR 366) 自体もまたPublic Review Ballotの期間に入りました。
JSR 366: Java Platform, Enterprise Edition 8 (Java EE 8) Specification
https://jcp.org/en/jsr/detail?id=366
Java EE Security API (JSR 375) は現在Public Reviewの期間にあります。これはこの新しいAPIにとって明らかに重要なステップです。
JSR 375: JavaTM EE Security API
https://jcp.org/en/jsr/detail?id=375
JSR-000375 JavaTM EE Security API 1.0 Public Review:
https://jcp.org/aboutJava/communityprocess/pr/jsr375/index.html
Java EE 8に向けてメンテナンスリリースを実施しているさまざまな仕様の進捗状況も確認されています。
全体として、種々のJava EE 8仕様の最終化に向け、着実に進捗していることがわかります。先月はGlassFish 5 Promoted Buildを2個リリースしました。Java.netが廃止された後の最初のビルドであるということで、スコープが比較的制限されています。
GlassFish 5 Promoted Build 6
https://blogs.oracle.com/theaquarium/glassfish-5-promoted-build-6
https://orablogs-jp.blogspot.jp/2017/05/glassfish-5-promoted-build-6.html
GlassFish 5 Promoted Build 7
https://blogs.oracle.com/theaquarium/glassfish-5-promoted-build-7
https://orablogs-jp.blogspot.jp/2017/05/glassfish-5-promoted-build-7.html
新しいGlassFishのビルドパイプラインに対して多くの作業を実施し、ほぼ終了しています。そのため、今後数週間でGlassFish 5の進捗が加速すると期待されます。フロントの基盤についてまとめるにあたり、すべてのJava EE関連のディスカッションが新しいプラットフォームjavaee.groups.ioでホストされていることに言及しておかねばならないでしょう。
javaee@javaee.groups.io
https://javaee.groups.io/
まだ参加されていない場合には、是非ディスカッションに参加してください。

[Cloud] Automating Processes With Application Builder and Process Cloud Services

原文はこちら。
https://blogs.oracle.com/shay/automating-processes-with-application-builder-and-process-cloud-services

Oracle Application Builder Cloud Service (ABCS) を使うと、データを追跡するアプリケーションを開発することができますが、そのデータがプロセスに関わっているとしたらどうでしょうか。データ収集だけでなく、人間のワークフローのやりとりを自動化する必要がある場合はどうすればよいでしょうか?Oracle ABCSとOracle Process Cloud Service(PCS)が今回統合されたことにより、こうした課題を簡単に実現できます。
Oracle Process Cloud Service
https://cloud.oracle.com/ja_JP/process
ABCSビジネス・オブジェクトと関連するプロセスを作成し、直接Oracle ABCSのUIから対話することができます。これは双方向Interaction Patternで、PCSのプロセスがOracle ABCSビジネス・オブジェクトから情報を取得し、ABCS UIをPCSプロセス上に作成してプロセスの開始、進行が可能です。

以下の動画は、基本的な出張申請承認フローを開発する例で、以下の内容を含む統合方法を紹介しています。
  • ビジネス・オブジェクトとプロセスの関連付け
  • プロセスからビジネス・オブジェクトの値へのアクセス
  • PCSとABCS間のセキュリティおよび接続の設定
  • ABCSのページからPCSプロセスの起動
  • タスクを表示するためのカスタムToDoリストページをABCSで作成
  • タスクを進めるためのカスタムタスク詳細ページをABCSで作成
ご覧になるとわかる通り、これらの全てが非常にシンプルかつ完全に宣言的に、ビジュアル開発アプローチで達成しています。

製品の組み合わせによって、PCSならびにABCSのユーザーに対しすばらしい価値を提供します。PCSをご利用のお客様には、プロセスで利用するデータを永続化する機能やずっと抱負な機能のUIやレポートのデザインが可能な点が気に入って頂けるかと思います。ABCSをご利用のお客様は、長時間実行する複雑なプロセスの自動化および管理が可能になる点が気に入って頂けるのではないでしょうか。

[Cloud] Kubernetes Community Engagement: Time to Roll!

原文はこちら。
https://blogs.oracle.com/developers/kubernetes-community-engagement-time-to-roll

原文はT.J. Fontaine @tjfontaine)によるものです。

CoreOS Fest 2017に参加できることを光栄に思っています。この機会を使って重要な取り組み2件をお知らせします。
CoreOS Fest 2017
https://coreos.com/fest/
Kubernetesプロジェクトへの参加のため、エンジニアリング・リソースを捧げることにしています。
Kubernetes - Production-Grade Container Orchestration
https://kubernetes.io/
Kubernetesは、分散システムを展開するための優れたオーケストレーション・プラットフォームの1つで、成熟度と方向性を考えれば、Oracleは大きな貢献をしていくものと確信しています。
また、CoreOSと連携し、CoreOS Container LinuxをOracle Cloud Infrastructureに導入しています。
CoreOS
https://coreos.com/
Oracle Cloud Infrastructure as a Service
https://cloud.oracle.com/ja_JP/iaas
最先端の最適化されたLinux OSと業界で最も包括的な高性能クラウドプラットフォームを組み合わさることで、開発者は、CoreOS Container Linuxのパフォーマンスやセキュリティ機能をエンタープライズ・ワークロードに活用できるようになります。
Why CoreOS
http://coreos.com/why/
Oracleは、我々の新しいクラウドサービスの展開、運用のために何よりもまずKubernetesに投資しています。現実世界の経験をアクションに移しているので、Kubernetesの運用に対する我々の理解が、コミュニティにとっての価値あるものになると考えています。
今後数週間は、コミュニティチャンネルに参加することから始めて、段階的に取り組んでいきます。コミュニティの成功を支援することが、Oracleを含むすべての人のためにKubernetesを改善につながると理解しています。そこで私たちは、Kubernetesを改善し、SlackやStackOverflow、およびGitHubのIssueにある質問に答えることでコミュニティを支援するという私たちのコミットメントを示します。
Kubernetes on Slack
http://slack.k8s.io/
StackOverflow (Tagged Kubernetes)
https://stackoverflow.com/questions/tagged/kubernetes
GitHub
https://github.com/kubernetes/kubernetes
オープンソースへの参加には透明性が求められるため、Kubernetesおよびそのエコシステムに取り入れた作業の全ては以下のGitHubでご覧いただけるようにします。
https://github.com/oracle
我々はコミュニティおよびメンテナーと密接に協力し、コミュニティ全体に最大の影響を与える問題や機能に取り組んでいきます。

Related content:

私たちのチームは、伝統的なエンタープライズソフトウェアベンダー出身者からクラウドネイティブの新興企業出身者といった多様な背景を持つ面々だけでなく、世界的に展開されるサービスを提供してきた経歴を持つ人々から成り立っています。 私たちの専門知識を踏まえれば、Federation、Security、ServerlessなどといったSIG(Special Interest Group)にかなり参加できると考えています。こうしたコミュニティへの参加は、コミュニティのニーズを満たす新機能の同意を進めるだけでなく、製品を改良する上でも有用と考えています。
当社はKubernetesを使ってサービスを提供しようとしており、現在Oracle Cloud Infrastructureのファースト・クラス体験を提供するプラグインを開発しています。既にKubernetesをお使いで、Oracle Cloudを試してみたいのであれば、自然なことに感じるはずです。
私たちはこの活気のあるコミュニティに参加できてわくわくしています。私たちが一緒に作るものを目の当たりにしたくてたまりません。
是非ご期待ください。

著者について
TJ Fontaineは、高可用性を持つクラウドネイティブなコンテナ化されたサービスのアーキテクチャ設計やデリバリを担当するOracleのソフトウェアエンジニアで、またNode.js、Linux、Monoといったオープンソースのベテランコントリビュータ-でもあります。Node.jsのProject Leadをつとめつつ、コミュニティの拡大ならびにエンタープライズでの採用を進めてきました。BDFLモデルからNode.js Foundationへの移行も支援しました。

[Java] Using Java Flight Recorder Triggers

原文はこちら。
https://blogs.oracle.com/jtc/using-java-flight-recorder-triggers

Java Flight Recorderや、Java Flight RecorderがOracle Java SE Java VM(JVM)に統合されていて、有効化してもオーバーヘッドが非常に低いことについては、種々のエントリや書籍に記載されています。このツールは、本番環境でのJavaアプリケーションの詳細なランタイム情報を収集するという考え方を可能にするだけでなく、それを実現させるものです。
Java Mission Control
http://www.oracle.com/technetwork/jp/java/javaseproducts/mission-control/java-mission-control-1998576.html
多くの方は、Java Flight Recorderの連続記録モード(Continuous Recording Mode)を使っていますが、この状態では、無期限にランタイムデータを収集します。上書きする前に保持するデータの量を指定できます(またはデフォルト値を使用します)。このモードを使っている場合、いつでも、さまざまなオプションを使用して、ランタイム情報を内蔵のflight recorderファイルにダンプすることができます。その後、Java Mission Controlを使用してこのファイルを開き、実行中のアプリケーションの挙動をさらに診断することができます。
Java Platform, Standard Edition
Java Mission Controlユーザーズ・ガイド リリース5.5
http://docs.oracle.com/javacomponents/jp/jmc-5-5/jmc-user-guide/toc.htm
Java Mission Control User's Guide Release 5.5
https://docs.oracle.com/javacomponents/jmc-5-5/jmc-user-guide/toc.htm
Flight Recorderダンプをランダムまたは定期的に取得するのではなく、条件付きでダンプを取得することもできます。具体的には、トリガーを使用しつつ、定義済み条件を満たす場合にフライトレコーダーのダンプを発生させるルールを作成できます。このシナリオでは、状況が悪化しはじめた場合に、ずっと大量のデータセットを見るのではなく、トリガーによってランタイム情報の詳細に集中できるという点で役に立つことが想像できます。以下の動画では、簡単な説明後、Flight Recorderのダンプをトリガーを使って取得する方法を説明しています。その後、Mission Controlを使って、ダンプされたFlight Recorderのファイルを読み込み、サンプルアプリケーションの動作を診断しています。

[Java] Early Access documentation for Oracle JDK 9 has been updated

原文はこちら。
https://blogs.oracle.com/thejavatutorials/early-access-documentation-for-oracle-jdk-9-has-been-updated

Oracle JDK 9の早期アクセス版のドキュメントが更新されました。
Oracle JDK 9 Documentation Early Access
https://docs.oracle.com/javase/9/
この更新には、以下の内容が含まれています。
JDK 9早期アクセスリリースは以下からダウンロードできます。
JDK 9 Early-Access Builds
http://jdk.java.net/9/