タグ「Advent Calendar」が付けられているもの

Payara Advent Calendar 2016 を振り返って

この記事は、Payara Advent Calendar 2016 の最終日です。昨日はひらおかゆみ (@yumix_h) の「Admin ConsoleのLog Viewer」です。

私ひとりで勝手に始めた Payara の Advent Calendar ですが、かずひらさん (@kazuhira_r)、うらがみさん (@backpaper0)、Wreulicke さん (@wreulicke)、多田さん (@suke_masa)、上妻さん (@n_agetsu) に助けられ、何とか最終日まで完走することができました。もちろん、無理を承知で助け船を出してくれたひらおかゆみ (@yumix_h) にも感謝しています。

2016 年の Payara も話題が盛りだくさんでした。私の担当分はその多くがマニュアルの内容を実際に試したものになりますが、それでも 3 週間分くらいの記事にはなりました。2 年前の GlassFish Advent Calendar 2014 では、当時既に開発が停滞しきっていた GlassFish をネタに Advent Calendar をやったため、毎回内容を考えるだけで相当苦労しましたが、Payara は現在進行形で成長しているため、GlassFish から引き継いだ機能からつい最近リリースされた新機能まで幅広い内容で記事を書くことができました。

私以外の方々からは以下の内容の記事をいただきました。

いずれも資料的価値の高い記事であるため、きっかけこそ Advent Calendar ですが、多くの方々におすすめしたい記事が揃いました。

Java EE 全体をみると、2016 年は Java EE 8 の開発一時停止、Oracle vs. Java EE Guardians、Java EE 8 のスケジュール & 内容の大幅な見直しと暗い話題が続きました (特に注目の MVC 1.0 があっさり drop したのは残念でした)。一方で、MicroProfile のようなコミュニティによる新しい取り組みもみられ、将来に向けてかすかな光が見え始めた年だと感じています。そうした中で PayaraMicroProfile に参画したのも、個人的には評価に値するものだと思っています。

世の中のサーバーサイド Java はもはや Spring 一色ですが、そうした状況下で Java EE の遺伝子を受け継ぐ Payara が今後より一層発展することを願ってやみません。


p.s. 来年、私の主催はかなわないかもしれませんが、誰かが何らかの形でこの Advent Calendar を引き継いでいただけるのであれば、2016 年の主催者としてこれほど嬉しいことはありません。

Puzzle : JapaneseDate

この記事は Java Puzzlers Advent Calendar 2016 の 24 日目です。Date and Time API のローカル暦に関する出題です。

問題

以下の文を実行した結果は次のどれでしょうか?

System.out.println(LocalDate.from(JapaneseDate.of(JapaneseEra.MEIJI, 5, 12, 31)));

(1) 1872-12-02

(2) 1872-12-31

(3) 1873-01-01

(4) DateTimeException がスローされる

解答

(4) DateTimeException がスローされる

解説

これは JapaneseDate クラスの仕様です。もしくは、和暦に関する常識の問題。

日本では明治6年からグレゴリオ暦を導入しています。明治以降の紀元のみサポートされ、明治6年1月1日より前の日付はサポートされません。

https://docs.oracle.com/javase/jp/8/docs/api/java/time/chrono/JapaneseDate.html より

Date and Time API のローカル暦はフィールドに設定できる値が標準 (ISO 8601) とは異なる場合があります。例えば HijrahDate では dayOfMonth に設定できる値は 1~30 に限定され、JapaneseDate では JapaneseEra (元号) によって設定可能な year、month、dayOfMonth の範囲が決まっています (例えば、昭和 64 年 1 月 8 日は無効な日付と見なされる)。Calendar とそのサブクラスとは異なり、Date and Time API では暦に関するバリデーションを厳格に行います

この他にも、異なるローカル暦の日付の直接比較は基本的にサポートされないと考えた方が無難です。日付比較は ISO 8601 の日付に変換した上で行うのが安全なやり方であり、実際に API ドキュメントでもそのように行うよう指示があります。

総じて、ローカル暦は ISO 8601 日付に比べて制約が多く、また暦によってその制約が異なることに注意してください。

Date and Time API の JapaneseDate は明治 6 年 1 月 1 日 (1873-01-01) 以降の日付が有効で、かつ元号と日付の対応が table 1 に従っていなければなりません。

table 1 - 和暦・西暦対応表
元号記号当該元号の日付対応する西暦日付
明治 M M01.01.01 (*1) 1868-01-25
M01.09.08 (*1) 1868-10-23
M05.12.02 (*2) 1872-12-31
M06.01.01 (*2) 1873-01-01
M45.07.29 1912-07-29
大正 T T01.07.30 1912-07-30
T15.12.24 1926-12-24
昭和 S S01.12.25 1926-12-25
S64.01.07 1989-01-07
平成 H H01.01.08 1989-01-08

(*1) 慶応 4 年 9 月 8 日 (西暦 1968 年 10 月 23 日) に改元が即日実施され、この日をもって明治元年 9 月 8 日となりました。また、この改元は慶応 4 年 1 月 1 日 (西暦 1868 年 1 月 25 日) に遡って行われました。

(*2) 西暦 1873 年 1 月 1 日に合わせて旧暦 (天保暦) から西暦 (グレゴリオ暦) への移行が実施されました。前日の西暦 1872 年 12 月 31 日が旧暦の明治 5 年 12 月 2 日であったことから、12 月 3 日~12 月 31 日までをスキップして、明治 6 年 1 月 1 日 = 西暦 1873 年 1 月 1 日となるように改正されました。

参考: ISO 8601 -- Date and Time API の基礎知識

なお、誤りの選択肢は以下のような日付となります。

(1) 1872-12-02 → 1872 年 = 明治 5 年、12 月 2 日 = 明治 5 年の最後の日付、から合成

(2) 1872-12-31 → 明治 6 年 1 月 1 日の前日の西暦換算

(3) 1873-01-01 → 明治 6 年 1 月 1 日 (行き止まりになると思った?)

教訓

  • Date and Time API のフィールドのバリデーションは厳格。特にローカル暦を使用する場合には各フィールドの定義域に注意すること。
  • 日本が旧暦 (天保暦) から西暦 (グレゴリオ暦) に移行したのは明治 6 年 (1873 年) で、明治 5 年までの日付は ISO 8601 とは互換性がない※一般常識としてこのくらいは知っておけ!
  • 和暦を使用するのであれば、各元号の開始日と終了日について完全に把握しておくこと。それができないようであれば、和暦を使用すべきではない。
  • Date and Time API を使うときは、Calendar の動作は一旦忘れること。旧 API はバリデーションがデタラメで、思わぬバグを引き起こす。

蛇足 (1) -- みかしーの誕生日は昭和?平成?

みかしー (三上枝織さん) の誕生日は 1989 年 1 月 6 日、つまり、ぎりぎり昭和 64 年生まれです (平成生まれではないのです)。では、Date and Time API がみかしーの誕生日を正しく和暦で表せるか、確かめてみましょう。これは OpenJDK 9 の JShell があれば簡単に行えます。

System.out.println(JapaneseDate.from(LocalDate.of(1989, 1, 6)));

実行結果は以下の通りです。

Japanese Showa 64-01-06

確かに、みかしーが昭和 64 年生まれだとわかります。

蛇足 (2) -- 平成 100 年問題について

JIS X 0301 における規定では、改元後もシステムが対応するまでの間は古い元号を継続して使用してもよいことになっています。そのために発生しうるのが「昭和 100 年問題」です。Date and Time API の現在の実装では、平成の終わりが定められていないことから、仮に今上天皇陛下が皇太子殿下に譲位することになっても、当面の間はシステム上で平成という元号を使い続けることは可能になっています。そこで現れるのが「平成 100 年問題」です。

和暦は年のフィールドを 2 桁で保持するため、移行措置に甘えてシステム改修を怠ると年フィールドのオーバーフローを起こすことになります。Date and Time API のバリデーションは強力のため、年フィールドのオーバーフロー時には DateTimeException がスローされるはずです。後々面倒なことになるため、日付処理は ISO 8601 で統一するのが一番の安全策です。

官公庁のように和暦しか使わないところであっても、強行採決による法改正や抵抗する公務員のレイオフを徹底的に行い、国際標準かつ副作用の少ない ISO 8601 に統一するのが今後「電子政府」化を推し進めていく上で重要なことなのではないでしょうか?

Puzzle : variable arity parameter

この記事は Java Puzzlers Advent Calendar 2016 の 22 日目です。

問題

以下の文の実行結果はどのようになるでしょうか?

System.out.println(Objects.hash());

(1) コンソールに 0 が表示される

(2) コンソールに 1 が表示される

(3) NullPointerException がスローされる

(4) コンパイルエラーとなる

解答

(2) コンソールに 1 が表示される

解説

Objects.hash( ) メソッドは、hashCode を計算してくれる便利なメソッドです。そのシグネチャは以下の通りとなっています。

public static int hash(Object... values)

仮引数は Object クラスの可変長引数だけのため、引数が 0 個であっても呼び出しは可能です。従って選択肢 (4) はあり得ないとわかります。それ以外の選択肢の可能性については、Objects.hash( ) メソッドのソースコードを見てみないと判断できません。直感的に選択肢 (3) はなさそうな気もしますが...

Objects.hash( ) の実装は以下の通りとなっています。

public static int hash(Object... values) {
  return Arrays.hashCode(values);
}

何だこれ!ただのラッパーじゃないか!

仕方がないので、Arrays.hashCode(Object[]) の実装も見てみましょう。

public static int hashCode(Object a[]) {
  if (a == null)
    return 0;
  
  int result = 1;
  
  for (Object element : a)
    result = 31 * result + (element == null ? 0 : element.hashCode());
  
  return result;
}

これで答えの半分は出たようなものです。残りは、仮引数の可変長引数が 0 個だった場合の挙動です。

可変長引数はいわゆる Syntax Sugar であり、実引数では配列として扱われます。例えば、仮引数の可変長引数 Object... values は、実引数では Object[] values となります。ここで、可変長引数の数は配列の要素数と一致するため、可変長引数で引数がない場合には実引数は要素数ゼロの配列となります。

要素数ゼロの配列を受け取った Arrays.hashCode(Object[]) は、まず最初の if 文で 0 を返すことはありません (要素数ゼロの配列は null にはなりません)。その後、result が 1 で初期化されますが、要素数が 0 のため hashCode の計算を行う for 文を一度も実行しません。従って、戻り値は result が初期値のまま 1 となります。この値がそのまま Objects.hash( ) の戻り値になりますので、正解は選択肢 (2) となります。

なお、Arrays.hashCode(Object[]) が採用している計算式は、hashCode の計算式としてよく知られているものですので、覚えておいて損はありません。参考まで、Stream API で書き換えると以下のようになります。

if (a == null)
return 0;

return Arrays.stream(a) .filter(e -> e != null) .mapToInt(e -> e.hashCode()) .reduce(1, (left, right) -> left * 31 + right);

この問題は、本来であれば Objects.hash( ) の内部実装を知らないと解けない問題です。ただし、可変長引数の実体が配列であることを知っており、hashCode 計算に関する一般的な知識があれば消去法で解くことができます。

  1. 可変長引数 Object... values は仮引数 0 個でも問題ないため、(4) は除外
  2. hashCode の計算において null の場合は値を 0 として計算するのが基本であるため、NullPointerException をスローする必然性はなく、従って (3) は除外
  3. 逆に hashCode が 0 となる場合は原則として対象が null のため (*)、対象が null でない場合は通常 0 以外の値と見なすことが可能で、従って (1) を除外
  4. 最後に (2) が残り、かつ、これが正解である

(*) 例外として、Byte、CharacterShort、IntegerLongnull でなくても <code<hashCode の戻り値が 0 になる場合がある (保持する値が 0 の場合)

一般的な可変長引数を取るメソッドの挙動としては、選択肢 (1) または (2) が自然でしょう。選択肢 (3) のように振る舞う実装は可能ですが、null ではない引数に対して NullPointerException をスローするのは相応しくありません。どうしても例外をスローしたい場合は IllegalArgumentException などの方が適切でしょう。選択肢 (4) は問題外、可変長引数のことを全く分かっていない証拠です。

余談 #1 -- C/C++ における可変長引数の扱い

C/C++ の可変長引数は、Java のような Syntax Sugar ではなく、本当に実引数の数が変動します。stdarg.h (C++ の場合は cstdarg を推奨) で宣言されている関数 (というかマクロ) を使用して実引数の可変部を取得するのですが、タイプセーフではないし個数自体も実引数の固定部から読み取れる情報で判断しなければなりません。

C/C++ の場合は引数の型または個数 (あるいはその両方) の判定に誤りがあると容易にスタックのオーバーフローやアンダーフローが発生し、しかもその種のバグは気づきにくいです (22 年前の教訓)。

先人達からプロダクション・コードで scanf 関数は絶対に使うなと言われる理由は、scanf 関数の可変長引数はユーザー入力に依存するため、固定部に埋め込んだ引数の型と個数に関する情報とは矛盾した入力がなされる可能性があり、かつそれをプログラム側で阻止する手立てがないためです。

Java の可変長引数は妥協の産物のような仕様ですが、C/C++ のそれと比較すると タイプセーフで個数も確定した、安心して使用できるものに仕上がってはいるのです。

余談 #2 -- 可変長引数の内部実装の概要

C/C++ の関数、Pascal のプロシージャと関数は、引数をスタックに積んで、呼び出し先でそれをスタックから取り出しています。

Pascal と Modula では最初の引数からスタックに積むため取り出しは最後の引数から行われますが、可変長引数に関する情報を保持する最初の引数が最後に取り出されるため、Pascal は可変長引数に必要な情報を得られず、従って可変値用を引数を処理できません。。

C/C++ の規定値 (処理系依存ですが基本的には __stdcall または __cdecl 呼び出し) では最後の引数からスタックに積むことで最初の引数から取り出しを行えるようなトリックを仕掛けています。これにより最初の引数を取り出した後、可変長引数の処理に必要な残りの引数のデータ型と個数を判定できる仕組みになっているわけです (前述の scanf 関数のようにアテにならない場合もあります)。

C/C++ ではオプションとして Pascal 等と同じ振る舞いをする __fastcall (旧: __pascal) 宣言もあり、こちらを使うと可変長引数が封印される代わりに関数呼び出しが若干高速化されるメリットがあります。Win32 API では処理効率優先のため原則として __pascal 宣言を行っています (つまり Win32 API では可変長引数は使用できません)。

JavaScipt の引数は、実は数が決まっていません。通常は宣言された引数に対する処理を記述するのですが、未宣言の追加引数 (可変長引数) を取得手段は用意させていなすり

余談 #3 -- まとめ

  • Java の可変長引数は Syntx Suga である
  • C/C++ の可変長引数は、実引数の数が執変動する
  • JavaScipt でも可変長引は扱うこと可能はある

ThreeTen Extra と ThreeTen Backport

この記事は Java Advent Calendar 2016 の 20 日目です。昨日はひらおかゆみ (@yumix_h) の「自作のJavaMailラッパーでiCloudメールにアクセスする」です。

本日は、Date and Time API の拡張ライブラリである ThreeTen Extra と Java SE 6/7 向け互換ライブラリである ThreeTen Backport についてご紹介します。

1. JSR 310 と 3 つの成果物

JSR 310 : Date and Time API は、java.util.Date と java.util.Calendar を代替する API を提供するというものでした。スペック・リードは Stephen Colebourne (Joda-Time 開発者) と Michael Nascimento Santos という 2 人の Java Champion が務めました。ベンダー (特に Oracle) の開発者がスペック・リードを務める JSR が多い中で、JSR 310 は数少ないコミュニティ主体の JSR だったのも特徴的です。

JSR 310 は OpenJDK 8 入りが決まってから Oracle の Roger Riggs が 3 人目のスペック・リードとして加わり、現在は Date and Time API のメンテナンス・リードを務めています。

JSR 310 の基本概念は JavaOne 2008 のセッションの時点でほぼ固まっており (SourceForge に当時の資料がアップロードされています)、そこでは 4 つの特徴―(1) 日付と時刻の包括的なモデル、(2) タイプ・セーフ (プリミティブ型を極力避け、読み書きしやすく)、(3) 既存クラスとの連携 (既存の Date を JSR 310 に合わせて改修)、(4) XML と JDBC の考慮―を掲げ、また人間向け表現として ISO 8601 を採用することが明言されました。

Early Draft Review の頃は Java SE 6 で実装され、当初は Java SE 7 を目標に掲げていました。しかし、Early Draft Review の段階でクラス数が 102 というとち狂った非常に大きな仕様だったため、OpenJDK 8 へのマージに先だって大胆なダイエットを敢行し、Early Draft Review 2 でクラス数を 75 まで削減、Public Review ではクラス数を 69 にまで絞り込みました。これに小さな改修を加えてリリースしたものが Java SE 8 の Date and Time API です。

Early Draft Review 2 で JSR 310 の仕様から除外した UTC/TAI 対応 (うるう秒の考慮)、日付フィールド、暦 (ユリウス暦とコプト暦) などの機能を再構成し、ユーティリティといくつかの暦を追加したものが ThreeTen Extra です。Date and Time API と ThreeTen Extra を合わせると、JSR 310 が当初目指した姿に近いものとなります。

JSR 310 は Java SE 7 が目標だったことから、完成に近い段階まで Java SE 7 ベースで実装していました。OpenJDK 8 の Mercurial リポジトリに移ってからインタフェースのデフォルト実装の利用やラムダ式への対応がなされましたが、プロジェクトの GitHub リポジトリに残された実装を Java SE 8 の Date and Time API とほぼ同じ使い勝手となるように改修したものが ThreeTen Backport です。正式リリース直前に Java SE 6 へのバックポートが行われています。

まとめると、JSR 310 は 3 つの成果物を生みました。

  • Date and Time API -- Java SE 8 以降の標準 API
  • ThreeTen Extra -- Date and Time API から除外された機能 + α
  • ThreeTen Backport -- Java SE 6/7 向けの Date and Time API 互換ライブラリ

2. ThreeTen Extra

2.1. ThreeTen Extra のインストール

Maven Central に登録されています。Java SE 8 以降の他に依存するものはありません。

<dependency>
  <groupId>org.threeten</groupId>
  <artifactId>threeten-extra</artifactId>
  <version>1.0</version>
</dependency>

2.2. ThreeTen Extra の機能

ThreeTen Extra の機能はいくつかのカテゴリに分類することができます。

TAI/UTC 対応

JSR 310 の Early Draft Review 2 で「凝り過ぎた」として真っ先に削除された TAI/UTC 対応 (いわゆるうるう秒の考慮) がなされています。

  • TimeSource - UTC/TAI にも対応した Clock のためのインタフェース
  • TaiInstant - 国際原子時 (TAI) に準拠した時刻のものさし (Instant)
  • UtcInstant - 協定世界時 (UTC) に準拠した時刻のものさし (Instant)
  • UtcRules - うるう秒の管理、Instant / TaiInstant / UtcInstant の相互変換などを提供する

現在 Java が動作するプラットフォームのシステムクロックは、これらを十分に生かせるほどの精度を持ち合わせていませんが、高精度な外部クロックと接続できれば活用の機会はあるかと思われます。

日付・時刻要素

JSR 310 は日付・時刻の要素をプリミティブ型ではなくクラスで持たせて、バリデーションを厳格にする方針を採っていました。しかし、クラス数超過のため Early Draft Review 2 の段階で現在の日付・時刻クラスと YearYearMonthMonthDayMonthDayOfWeek に絞り込まざるを得ませんでした。ThreeTen Extra では JSR 310 から削除された日付・時刻の要素を復活させる形で実装しています。

  • YearQuarter - 年 + 四半期
  • YearWeek - 年の週; これに日が加わると暦週日付 (week date) となる
  • DayOfMonth - 月の日; Date and Time API では int として扱っている
  • DayOfYear - 年の日 = 年間通算日 (ordinal date)
  • AmPm - 午前 / 午後
  • Quarter - 四半期
  • PackedFields - ISO 8601 標準形式 (区切り記号を使わない) の日付・時刻フィールド

時間量

Date and Time API では時間量を PeriodDuration の 2 クラスで表現しますが、元々の JSR 310 ではより直感的な時間量を表すクラスも用意されていました。こちらも Early Draft Review 2 で一旦削除されたものがほぼそのままの形で復活したものです。

ローカル暦

JSR 310 から除外されたユリウス暦とコプト暦の他に、多数のローカル暦が追加されています。ディスコルディア暦とパックス暦は早い時期に実装されていますが、その他のローカル暦は後発の部類となります。

ユーティリティ・クラス

3. ThreeTen Backport

3.1. ThreeTen Backport のインストール

Maven Central に登録されています。Java SE 6 以降の他に依存するものはありません。

<dependency>
  <groupId>org.threeten</groupId>
  <artifactId>threetenbp</artifactId>
  <version>1.3.2</version>
</dependency>

3.2. ThreeTen Backport の機能

ThreeTen Backport は可能な限り Date and Time API と同じ振る舞いをするように考慮されています。Javadoc は http://www.threeten.org/threetenbp/apidocs/ にありますが、Date and Time API とほぼ同様であることが見て取れます。

Date and Time API からの相違点は以下の通りです。

  • パッケージ名 -- java.time ではなく org.threeten.bp
  • Java SE 8 を模倣するためのクラスを追加 -- org.threeten.bp.jdk8 パッケージ (4 クラス)
  • tz database 周りを独自に実装 -- org.threeten.bp.zone.TzdbZoneRulesProvider クラスおよび tzdb のデータファイル (tzdb は JDK ではなく ThreeTen Backport のバージョンアップ時に更新されるので注意)
  • デフォルト実装や static メソッドを持つインタフェースについて、抽象クラスに置き換え (暦周りを中心に)
  • org.threeten.bp.DateTimeUtils -- java.util.Date や java.util.Calendar などとの相互変換ユーティリティ

DateTimeUtils は本家 Date and Time API にはない、ThreeTen Backport 独自のユーティリティ・クラスです。Date and Time API と java.util.Date などの相互変換メソッドを作成する際には、DateTimeUtils の実装を参考にすると良いかもしれません。

明日は rysh(r.ishibashi) さん (@cactaceae) です。

この記事は JavaFX Advent Calendar 2016 の 20 日目です。昨日はひらおかゆみ (@yumix_h) の「JavaFXで小惑星を描いてみよう」です。

2~3 年前、JavaFX のフロントエンドを持つアプリケーションのテキスト入力欄に、Google 検索のような入力補完 (履歴参照) 機能を追加する要望が持ち上がって、会社の後輩と 2 人で対応したことがあります。後輩の退職後に機能のメンテナンスを重ねるうちに、アプリケーションから独立した部品として使用できるようになりました。

実際のアプリケーションで使用しているソースコードは使用できませんが、今回は機能設計から新たに書き起こしたものをご紹介します (Ethereal と Wireshark の関係と同じ位置づけだと認識しています)。

はじめに、入力補完 (履歴参照) 機能の実行サンプルを figure 1 に示します。

javafx-textfield-history.png
figure 1 - 入力補完 (履歴参照) 機能のサンプル

この機能を実装するのに必要なコンポーネントは、以下の 2 つです。

いずれも Snapshot ですが私の Nexus https://repo1.haswell.jp/ にも載せてあります。

  • groupId: jp.coppermine.tools
  • artifactId: coppermine-tools-javafx-history
  • version: 現時点ではともに 0.2.0

前者はメモリやファイルなどをストアとして利用する履歴参照機能を提供するモジュールです。今回はそのうちファイルをストアとするものを使用しました。また、後者は前者を利用した JavaFX のコントロールもどきで、実態は TextField の入力内容と連動する ListView です。前者は私のオリジナルです。後者は後輩がアルゴリズムと初期実装を担当し、私が Lambda と Stream API による再実装とアプリケーション本体からの分離を担当しました。

使い方

詳しくは https://github.com/khasunuma/javafx-history-sample を参照して頂きたいのですが、大雑把には次のようになります。

まず、履歴参照機能を取り付ける TextField と紐付くコントローラー・クラスに FileHistoryOperation インタフェースを実装します。ほとんどの機能はメソッドのデフォルト実装にて提供しています。ただし、getHistory メソッドだけは純粋な抽象メソッドであるため以下のように実装する必要があります。

private History history = new FileHistory(getPath());

@Override
public History getHistory() {
    return history;
}

続いて、HistoryView クラスのインスタンスを生成します。

private HistoryView historyView = new HistoryView();

コントローラー・クラスの initialize メソッドに以下の記述を追加します。

initializeHistory();
loadKeywords();
historyView.attach(textField, () -> getKeywords());

最後にトリガーとなるボタンやキーイベントを適切に実装します。これで、最初のスクリーンショットのような入力補完とコマンド履歴が使えるようになります。

当初、私は過去の入力履歴を保存して編集可能なコンボボックスで選択できるような実装で十分だと考えていて、後輩にもそう伝えていたのですが、彼はあくまで Google 検索のような入力補完にこだわり、2 人で試行錯誤を重ねて最初の実装を実現しました。今回はオリジナルをリバースエンジニアリングしたものであり、Java SE 8 で追加された機能を用いて実装の大部分を書き換えたため、最初の実装からは大きく姿を変えています。しかし、入力補完のアルゴリズムは後輩が考案したものを基本的に踏襲しており、私の修正は複雑なループを Stream API に置き換えたことだけでした。現在、後輩は遠い異国の地にいますが、そこでソフトウェア技術者として大いに活躍していることと思います。

明日は深井さん (@fukai_yas) です。

Payara Micro の GAV デプロイ

この記事は Payara Advent Calendar 2016 の 20 日目です。昨日は @backpaper0 さんの「PayaraをDockerで動かす」です。昨年の前半に Payara の Mike Croft (@croft) が中心となって進めていたプロジェクトの成果を日本語で確かめるよい機会です。Docker 環境をすぐに使用できる方は是非お試しください。

去る 12 月 3 日に開催された JJUG CCC 2016 Fall にて、「Payara Micro の設計と実装」と題した発表を行いました。その後のフィードバックで、Maven リポジトリ上の WAR ファイルを Payara Micro へ直接デプロイできることに驚かれた方も少なくなかったようなので、今回はごく簡単なサンプルを用いて、Maven リポジトリからのデプロイ (GAV デプロイ) についてお話ししようと思います。初回こそ Maven 環境を整えるという面倒な作業を要しますが、一度環境を整備すればその後は楽に作業を進められます。

1. 事前準備: WAR ファイルの作成とリポジトリへの登録

まずは WAR ファイルをアップロードする Maven リポジトリが必要です。Maven Central が使用できるのであれば、それを使用するのが最も簡単です。Maven Central へのアップロード方法については http://maven.apache.org/guides/mini/guide-central-repository-upload.html を参照してください。Maven Central は使用できないが独自にホスティング環境を持っている場合には、Sonatype Nexus で Maven リポジトリを構築すると良いでしょう。

私は Maven Central へのアップロード経験がなく、かの場所のワークフローも面倒に思えたので、VPS (ConoHa) を借りて Nexus をインストールしました (MSDN の Azure にも Nexus をホスティングしているのですが、通信量の制約が...)。

アップロードする WAR は、Payara Micro で動作するものであれば何でも良いです。GAV デプロイでも必要であれば複数の WAR をデプロイすることができます。ただし、Snapshot バージョンの場合は設定次第で苦戦するかもしれませんので、それを避けたければ Release バージョンをアップロードしましょう。

2. Payara Micro API を用いた GAV デプロイの実際

今回はとりあえず、JAX-RS で "Hello, world" を出力する WAR ファイルを作りました。コードは https://github.com/khasunuma/helloweb/tree/version-1.0 にあります (実際は HTML/XML/JSON の 3 出力形式に対応するとか、余計なことをしています)。この WAR ファイルは私がホスティングしている Nexus にアップロード済みです。

まずはコマンドラインから、以下のように実行してください。

java -jar payara-micro-4.1.1.164.jar --deployFromGAV jp.coppermine.samples:helloweb:1.0.0 --additionalRepository http://repo1.cubiwano.org/repository/maven-public

--deployFromGAV で WAR ファイルの groupId、artifactId および version を指定します。また、Maven Central 以外のリポジトリ (特に in-house リポジトリ) にある場合は、その URL (今回は http://repo1.haswell.jp/repository/maven-public) を指定します。これだけでデプロイはできるはずです。

【注意】 上記の Maven リポジトリ (repo1.cubiwano.org) は HTTP と HTTPS の両方に対応させていますが、GAV デプロイの際は "HTTP" を指定してください。

このサンプルの動作確認は、ブラウザで http://localhost:8080/helloweb/api/hello を開くことでできます。"Hello, world" が出力されていれば OK です。

Payara Micro API を使用する場合は、以下のようにコーディングします (完全なコードは https://github.com/khasunuma/payaramicro-gav にあります)。

package jp.copermine.samples.payaramicro;

import fish.payara.micro.BootstrapException;
import fish.payara.micro.PayaraMicro;

public class GavDeploy {

    public static void main(String[] args) throws BootstrapException {
        // Required if not using Maven central repository 
        final String repositoryUrl = "http://repo1.cubiwano.org/repository/maven-public";
        
        // groupId , artifactId , version
        // if version is SNAPSHOT, it may not run well
        final String gav = "jp.coppermine.samples,helloweb,1.0.0";
        
        PayaraMicro.getInstance().addRepoUrl(repositoryUrl).addDeployFromGAV(gav).setNoCluster(true).bootStrap();
    }

}

明日は @wreulicke さんです。

MicroProfile によるアプリケーション開発

この記事は Java EE Advent Calendar 2016 および Payara Advent Calendar 2016 の 18 日目です。昨日はうらがみさん (@backpaper0) の「Jersey OAuth 2クライアントで遊ぶ」(Java EE) およびかずひらさん (@kazuhira_r) 「Payara(Hazelcast)のLite Membersについて」(Payara) です。

1. MicroProfile とは?

MicroProfile は Java EE 技術をベースとしたマイクロサービス向けのプロファイルで、MicroProfile.io によって策定されています。

MicroProfile.ioIBMLondon Java CommunityRed HatTomitribePayara および SouJava といったベンダーおよびユーザーグループで構成されており、現在のところ Java Community Process とは独立して活動しています。MicroProfile は 2016 年 9 月に最初のバージョンがリリースされており、現時点でリリースされている実装には WildFly Swarm、Payara MicroProfile、WebSphere LibertyTomEE の 4 つがあります。

Payara MicroProfile は、利用可能な API が MicroProfile に限定されることを除いて Payara Micro と同じで、Hazelcast によるクラスタリングもサポートします (Hazelcast を同梱している関係で、MicroProfile 実装でありながら JCache も利用できたりします)。細かいところでは、JAX-RS の JAXB/JSON-B に差異がみられます。Payara Micro には EclipseLink (JPA 実装) が含まれるため、既定では EclipseLink MOXy が使用されます。ただし、JAX-RS の実装である Jersey には Jackson が含まれているため、MOXy が無効化された状態では Jackson が処理を代行します。Payara MicroProfile には EclipseLink が含まれないため、はじめから Jackson が使用されます。

MicroProfile は JAX-RS、CDI および JSON-P のわずか 3 つの仕様から成り立っています。このうち JAX-RS と JSON-P はサービス間の通信を主な目的としており、サービス本体を記述するために用意されているものは CDI だけです。例えば、JPA のような永続化のための仕様は MicroProfile には含まれていません。マイクロサービスでは、それぞれのサービスは外部インタフェースからデータストアまで一通り備えた、独立した存在です。そのため、データの永続化ひとつをとっても、サービスごとに最適な方法 (KVS、RDB、etc.) は異なります。他の機能についても同様のことが言えます。

マイクロサービスにおいては、各サービスの実装には標準・非標準を問わず最適な技術が選択されます。裏を返すと、目的を達成するためであればどのような技術を用いても良く、共通で使用されると考えられる JAX-RS、CDI、JSON-P のみを MicroProfile としてまとめています。MicroProfile に規定されていないものについては、サービスごとに最適な技術を任意に組み合わせるべきというのが基本的な考え方です。

2. MicroProfile によるアプリケーション開発

MicroProfile によるアプリケーション開発では、通常は複数のサービスを組み合わせてアプリケーションを構築することになります。サービスは既存のものもあれば、新たに開発するものもあるでしょう。

MicroProfile.io が提供しているデモに microprofile-conference というアプリケーションがあります。このデモ・アプリケーションはカンファレンスのスケジュール・セッション・スピーカーの管理とセッションへの投票受付を行うもので、4 つのサービスと UI を含む Web アプリケーションで構成されています。pom.xml を見ると、サービスごとに必要なモジュールを依存関係に含めていることがわかります (デモのため MicroProfile 本体だけで構築しているサービスもあります)。4 つのサービスがそれぞれ異なる MicroProfile 実装で提供されているのも興味深い点です。実をいうと、このアプリケーションは私の環境ではビルドできないのですが、ソースファイルを見るだけでも感覚はつかめることでしょう。

どのサービスも、JAX-RS でエンドポイントを提供しており、それを Web アプリケーションから呼び出す構成を採っています。サービス間の通信はなく、Web アプリケーションがサービスの取りまとめ役を果たしています。

各サービスは CRUD に相当する機能を提供しており、それぞれ独自の方法でデータストアの操作を行っています。このアプリケーションはデモのため永続化ストアは使用していませんが、実運用を想定したアプリケーションであれば永続化ストアを使用するサービスも出てくることでしょう。データストアはサービスごとに独立して管理しますが、その参照先は同一であっても構いません。例えば、1 つの RDBMS を複数のサービスが参照しても良いのです。ただし、RDBMS に対する操作 (JPA でいうところの PersistenceUnit) はサービスごとに異なります。
マイクロサービスでは、アプリケーション全体としてのトランザクション一貫性は保証せず、別の方法でそれを補います (そして、その方法はアプリケーションによって異なります)。二相コミットメントを使うことも不可能ではありませんが、それではマイクロサービスの大切な何かを失ってしまうような気がします。

3. まとめ

今回は MicroProfile.io が提供するデモ・アプリケーションを題材として、MicroProfile を利用したマイクロサービスの実装例を見てみました。MicroProfile で規定された仕様はわずか 3 つですが、マイクロサービスの視点からは標準化すべき仕様はその程度で問題なく、あとはサービスごとに必要な技術を標準・非標準を問わず必要なだけ利用すれば良いことになります。

本来であれば、MicroProfile を用いてサービスとアプリケーションを開発できれば良かったのですが、そこそこの大きさのデモ・アプリケーションが用意されたため、それを紹介させていただきました。


明日は、Java EE が多田さん (@suke_masa)、Payara がうらがみさん (@backpaper0) です。

Puzzle : EJB Timer #2

この記事は Java Puzzlers Advent Calendar の 18 日目です。昨日同様、EJB タイマーに関する問題を続けて出題します。

問題

次のような EJB タイマーを含む Web アプリケーションを、Java EE 7 Full Platform の環境にデプロイしました。想定される動作は以下のどれでしょうか?

@Stateful
public SomeTimeBean {
  @Schedule(minute = "10", persistent = false)
  public void execute() {
    System.out.println("Just a time!");
  }
}

(1) 毎時 10 分に "Just a time!" という文字列を出力する

(2) 毎日 0 時 10 分に "Just a time!" という文字列を出力する

(3) デプロイに失敗する

解答

(3) デプロイに失敗する

解説

昨日の問題は EJB タイマーを構築する @Schedule アノテーションのパラメータ誤りでした。本日の問題は、@Schedule アノテーションに誤りはありませんし、永続型の EJB タイマーもデプロイ可能な Java EE 7 Full Platform が前提です。

問題は別のところにあります。EJB タイマーの適用範囲は Stateless Bean もしくは Singleton Bean に限定されます。適用外の Stateful Bean に対して @Schedule アノテーションが付加された場合には、デプロイすることができません。これは EJB タイマーの仕様です。

この問題では、クラスに対して @Stateful アノテーションが付加されており、Stateful Bean であることが宣言されています。その上で @Schedule アノテーションを用いて EJB タイマーを宣言しているため、この EJB 自体がデプロイできないのです。

修正方法は、可能であれば @Stateful アノテーションを @Stateless アノテーションまたは @Singleton アノテーションに置き換えて、Stateless Bean または Singleton Bean とします。

  • EJB がフィールドを持たない場合には、@Stateless アノテーションに置き換えて Stateless Bean にすれば問題ありません。
  • EJB がフィールドを持つ場合で、かつ EJB のインスタンスが 1 つで足りるようであれば、@Singleton アノテーションに置き換えて Singleton Bean とすることにより問題を回避できます。
  • EJB がフィールドを持ち、かつ EJB のインスタンスが複数必要となる場合は、Stateful Bean として維持しなければならないため、残念ですが EJB タイマーを諦めることになります。

3 種類の Session Bean の利用頻度を考慮すると、大半が Stateless Bean になるため、問題になることは少ないとは思いますが、@Schedule アノテーションのにわか知識だけで EJB タイマーを使おうとすると以外とハマりやすい落とし穴です。Java EE の勉強会のスライドで EJB タイマーに触れたものは、Stateful Bean が適用外であることを明示していないものがそれなりにあるため (多分、当日のプレゼンでは口頭で説明しているはず...)、初学者はなおさら注意が必要です。

以上の修正を行った場合は、昨日の修正後と同じ挙動を示します。すなわち、

この場合、想定される動作は選択肢 (2)、すなわち、毎日 0 時 10 分に "Just a time!" という文字列を出力します。@Schedule のパラメータの既定値は、日付部である year、month、dayOfMonth、dayOfWeek についてはすべてワイルドカード "*" となっていますが、時刻部である hour、minute、second についてはすべて "0" となっています。

選択肢 (1) のように毎時 10 分に "Just a time!" という文字列を出力したい場合には、以下のように hour をワイルドカード指定する必要があります。

なお、余談になりますが、Stateless Bean であってもフィールドを持たせること自体は可能です。ただし、その内容は保証されません。これは 1 つのセッションに対して常に同じインスタンスが lookup される Stateful Bean と異なり、Session Bean はプーリングされたインスタンスの中から毎回 lookup し直すため、必ずしも同じインスタンスになるとは限らないためです (インスタンスが異なるとフィールド値も異なる可能性が高く、フィールドが値を保持する意味がなくなります)。文献によっては毎回異なるインスタンスが lookup されるとありますが、それも誤った記述です。正確には、どのインスタンスが lookup されるか保証されません。多くの場合では異なるインスタンスが lookup されますが、同一のインスタンスが lookup される可能性もあるのです。具体例を挙げると、サンプルプログラムなどで Stateless Bean のプールにインスタンスが 1 つしか存在しない場合などは、毎回同じインスタンスを lookup することになります。また、実運用であっても、直前に lookup したインスタンスを再び lookup する可能性もゼロではありません。

これは昨日の問題にも共通して言えることですが、Java EE サーバーの Deployer が発するエラーメッセージは不親切なものが多く、ログに出力されたエラーメッセージだけでは何が間違っているのか分からないことが多々あります。かといって Java EE の文献は公式ドキュメントを含めてザルであることが多いため、最終的には「自分だけを信じろ、他人を信じるな」という結論に達します。今後も共に精進しましょう!

Puzzle : EJB Timer #1

この記事は Java Puzzlers Advent Calendar の 17 日目です。今日、明日と続けて EJB タイマーに関する基本的かつわかりにくい仕様について出題します。

問題

次のような EJB タイマーを含む Web アプリケーションを、Java EE 7 Web Profile の環境にデプロイしました。想定される動作は以下のどれでしょうか?

@Stateless
public SomeTimeBean {
  @Schedule(minute = "10")
  public void execute() {
    System.out.println("Just a time!");
  }
}

(1) 毎時 10 分に "Just a time!" という文字列を出力する

(2) 毎日 0 時 10 分に "Just a time!" という文字列を出力する

(3) デプロイに失敗する

解答

(3) デプロイに失敗する

解説

Java EE 7 より Web Profile でも EJB タイマーを使用できるようになりましたが、非永続型のものに限定されます。@Schedule アノテーションでは永続化/非永続化を persistent パラメータで指定しますが、既定値 (省略時) は true = 永続化です。永続型の EJB タイマーは Web Profile ではサポートされず、デプロイを試みても失敗します。

EJB タイマーは、スケジュールを保持するために何らかの永続化ストアを必要としていました。@Schedule アノテーションは Java EE 6 で EJB タイマーの実装を容易にするために導入され、Unix/Linux の cron に似た直感的で分かりやすい設定を可能にしましたが、Java EE 6 では EJB タイマーはまだ Full Platform 限定でした。

Java EE 7 では EJB タイマーにスケジュールを保持しない代わりに永続化ストアも必要としない非永続型を新たに追加して、それを EJB Lite の仕様に含めました。EJB Lite は Web Profile がサポートする EJB のサブセット仕様です。従来通りに永続化ストアを必要とする永続型は EJB Lite には含まれません (Java EE 7 でも Full Platform に限定されます)。

Java EE 6 で導入された時の @Schedule アノテーションには、persistent パラメータが存在していません。Java EE 7 の API ドキュメントには persistent パラメータの導入時期 (Javadoc の @since) が記載されていないため見落としがちですが、2 つのバージョンを見比べてみるとわかります。

ここまで行わないと追加された機能が分からない API ドキュメントは、そもそもドキュメントとして失格だと思うのですが...

このあたりの事情を師匠から公式の場で伺った覚えがないのと、私を含む多くの開発者が自身の開発環境で Full Platform を使用している (と思われる) ことから、意外な落とし穴となっています。

さて、問題を非永続型の EJB タイマーにするには、@Schedule アノテーションで persistent = false を指定します。

@Stateless
public SomeTimeBean {
  @Schedule(minute = "10", persistent = false)
  public void execute() {
    System.out.println("Just a time!");
  }
}

この場合、想定される動作は選択肢 (2)、すなわち、毎日 0 時 10 分に "Just a time!" という文字列を出力します。@Schedule のパラメータの既定値は、日付部である year、month、dayOfMonth、dayOfWeek についてはすべてワイルドカード "*" となっていますが、時刻部である hour、minute、second についてはすべて "0" となっています。

選択肢 (1) のように毎時 10 分に "Just a time!" という文字列を出力したい場合には、以下のように hour をワイルドカード指定する必要があります。

@Stateless
public SomeTimeBean {
  @Schedule(hour = "*", minute = "10", persistent = false)
  public void execute() {
    System.out.println("Just a time!");
  }
}

この記事は、Payara Advent Calendar 2016 の 16 日目です。昨日は「JavaFX から Payara Micro を呼び出す際の注意点」です。

Payara のクラスタリングには、GlassFish から引き継いだ Shoal を利用する方法と、Payara が独自に実装した Hazelcast を利用する方法の 2 種類があります。今回はその概要をご紹介します。

1. Shoal

GlassFish v2 の頃から開発されているクラスタウェアで、永続化ストアを一切使用しない完全なインメモリ・セッション・レプリケーションを実現した、当時としては画期的なものでした。技術的には Sun Java System Web Server 7.0 (後の Oracle iPlanet Web Server 7.0) と共通している部分もあるようです。Shoal の信頼性は非常に高く、故に GlassFish v3 系ではそれまでの HA 構成を廃止して Shoal クラスタに一本化してしまいました。もっとも、GlassFish v3 FCS には間に合わず、実際に搭載されたのは GlassFish 3.1 からとなります。

GlassFish 3.x のクラスタウェアは、Shoal の基本機能に加えて ssh/scp を利用したノードの自動構成をサポートしていました。この機能は、GlassFish の DAS (ドメイン管理サーバー) がリモート接続先のホストに自身のコピーを作成 (scp を使用) し、そこからノードを構成する (ssh を使用) というもので、DAS から自己増殖的にクラスタのノードを追加できる、他のサーバーでは例を見ないユニークな機能でした。この機能の先には、Java EE のマルチテナント機能があったと考えられ、実際に Java EE 7 で導入を試みた (結果として断念した) マルチテナント機能は、Shoal クラスタのこの機能に近いものだったと考えられます。

Shoal ではリモート接続先のホスト上にノードを構成する際、ssh を利用していました。しかし、Windows では ssh を標準搭載しておらず、別途インストールするハードルがあったため、DCOM で ssh/scp と同等の機能を実現しました。GlassFish では ssh/scp で構成されるノードを SSH ノード、DCOM で構成されるノードを DCOM ノードとして区別していました。また、DAS からのリモート接続を使用せず直接ホスト上に構成されたノードを CONFIG ノードと呼んでいました。制約として、同一クラスタ内での SSH ノードと DCOM ノードの共存は動作保証がなされておらず、また SSH ノードであっても OS が異なる (例: Linux と Solaris) と挙動がおかしくなることが多くありました。CONFIG ノードだけで構成すれば異なる OS のノードを混在させることは可能でしたが、それでは Shoal のメリットが半減してしまいます。

GlassFish 4.0 ではマルチテナント機能の搭載を前提としてロードバランサーを内包しましたが、結局マルチテナント機能は搭載されず、Shoal はロードバランサーとともに放置されるに至りました。現在、Shoal は目立ったメンテナンスが行われておらず、それが Payara で Hazelcast を導入した要因のひとつとなっています。Payara Server では GlassFish 4.1.x との互換性のため Shoal は残されていますが、Payara Micro には当初から搭載されていません。

2. Hazelcast

Hazelcast はインメモリ・データグリッドと呼ばれているミドルウェアの一製品です。同種の製品としては CoherenceInfinispanEhcache などが挙げられます。データグリッドとは、複数のマシンに分散している大量のデータを高速にアクセスする仕組みで、インメモリ・データグリッドは KVS (Key Value Store) のようなキャッシュの延長線上にある技術です。用途としてはローカル・キャッシュ、分散キャッシュ、クラスタ (複数ノード間のデータ共有)、データの分散配置および分散処理と多岐に及びます。

キャッシュとしては Coherence のケース・スタディが比較的分かりやすく、WebLogic Server クラスタと Oracle Database クラスタ (RAC) の間に Coherence が入ることで、WebLogic Server クラスタのデータベース・アクセス全体をキャッシュできるようになります (WebLogic Server は単体でも Oracle Database のクラスタと親和性が高くなっています)。

キャッシュとしての Hazelcast は、JCache (JSR 107) をサポートしつつも KVS の枠に囚われない様々なデータ型を分散配置することができます。クラスタとしての Hazelcast は高度なノード管理機能を持ち、スケールアウトが容易になるよう設計されています。様々なハードウェア と OS に対応し、Java 以外の言語 (C++、C#、Python、Node.js など) にも対応しています。

Payara は早い時期から Hazelcast を搭載しており、特に Payara Micro でマイクロサービスを構築する上で欠かせないものとなっています。また、Hazelcast が標準で JCache 実装を持つことから、Payara は Java EE 7 ベースでありながら JCache も使用可能なサーバーとなっています。

Hazelcast はクライアント・サーバー構成と埋め込み構成の双方をサポートしますが、Payara では埋め込み構成の Hazelcast をクラスタウェア (兼 JCache 実装) として使用します。Payara は埋め込み Hazelcast のインスタンスを生成することでクラスタのノードとなり (もしなければ自身が最初のノードとなってクラスタを構成)、インスタンスをシャットダウンすることでクラスタから外れます。実行中のクラスタに関することは基本的に Hazelcast が担当します。

Payara Server では既定で無効となっていますが、管理コンソールのチェックボックス 1 つで有効化できます。一方、Payara Micro では既定で有効となっており、互いに通信可能な Payara Micro のインスタンスが自動的にクラスタを構成するようになっています。同一クラスタ内に Payara Server と Payara Micro を混在させることも可能です。また、Shoal と異なり OS には依存しません。

3. どちらのクラスタリングを採用すべきか?

Payara で新規にクラスタを構築するのであれば、Hazelcast クラスタが第一選択肢となります。Shoal クラスタは GlassFish Server との互換性のために残されているに過ぎず、また Payara Micro ではサポートされていないためです。

Hazelcast クラスタでは、Shoal クラスタの CONFIG ノード相当しかサポートされませんが、実用上は問題ないでしょう。Shoal SSH/DCOM ノードよりも Hazelcast ノードの方が確実に構成できますし、特に Payara Micro の Uber Jar を使用するのであれば ssh/scp を用いたノード構成を比較的容易にスクリプトとして実行できるからです。


明日は「CLOVER を参照せよ」で有名 (?) な、かずひらさん (@kazuhira_r) の出番です。話題も Hazelcast 関連を予定しているとのこと。お楽しみに。

Puzzle : String concatination

この記事は Java Puzzlers Advent Calendar 2016 の 15 日目です。

問題

以下の式文において、評価後の s の値はどれになるでしょうか?

String s = "1" + 1 + 1;

(1) "111"

(2) "12"

(3) "1"

解答

(1) "111"

解説

文字列結合に用いられる + 演算子は、加算演算子 + をオーバーロードしたものです。Java では C++ と異なりプログラマが演算子をオーバーロードすることはできませんが、言語仕様でのオーバーロードは存在します。

C++ における演算子のオーバーロードでは、オペランドの数と式の評価順位については変更することができません。これは Java においても同じです。

文字列結合 s1 + s2 は、s1.concat(s2) に置き換えることができます。問題の式が評価される過程を、括弧を用いて表現すると、以下のようになります。

  1. "1" + 1 + 1
  2. ("1" + 1) + 1
  3. ("1" + "1") + 1
  4. ("1".concat("1")) + 1
  5. ("11") + 1
  6. "11" + 1
  7. "11" + "1"
  8. "11".concat("1")
  9. "111"

繰り返しになりますが、C++ の演算子オーバーロードでは評価順位まで変更することはできません。Java の文字列結合 + は加算演算子 + のオーバーロードであり、C++ と同じルールが適用されます。

この記事は、Payara Advent Calendar 2016JavaFX Advent Calendar 2016Java EE Advent Calendar 2016 の 15 日目です。昨日は、

です。

Payara Micro は Java EE の埋め込みサーバーとして使用することが可能です。Java EE の埋め込みサーバーは、Java SE アプリケーションに対して Java EE API を提供するもので、別途 Java EE サーバーを構築しなくても Java SE アプリケーション上で Web アプリケーションをはじめとする Java EE アプリケーションを実行させることができるようになります。また、Java EE サーバーを準備しなくて良いことから、Java EE アプリケーションの単体テストを実行するための組み込みコンテナとしても活用することができます。

埋め込みサーバーとしての Payara Micro は、Embedded GlassFish Web Profile を使いやすくラップしたものであり、API としては Web Profile に加えて JBatch、Concurrency Utilities、JCache が追加されています (ただし、クラスタリングを無効にすると Hazelcast が使われなくなるため、それによって提供される JCache も同時に使用できなくなります)。

Payara Micro を埋め込みサーバーとして使用する場合、通常は Java SE のアプリケーションに組み込んで使うことになりますが、JavaFX のアプリケーションに組み込むことも可能です。ただし、いくつか制約事項がありますので、以下に示します。

1. スタートアップ

PayaraMicro のスタートアップ・コードは、javafx.application.Application#init メソッドに記述します。この場所でないと Payara Micro を初期化することができません。

private PayaraMicroRuntime runtime;

@Override
public void init() throws Exception {
    runtime = PayaraMicro.getInstance().setNoCluster(true).bootStrap();
}

2. アプリケーション実行時

このセクションは適当に流すつもりだったのですが、3 つの Advent Calendar に対する共通エントリとしたため気が引けて、当日未明になってから 5 時間がかりで全面的に書き直しました。何らかの参考になれば幸いです。

FXML と Controller クラスを使用しているのであれば、Java SE から Web アプリケーションに対してリクエストを送信し、そのレスポンスを受信して UI に反映させる処理となります。ここでは JAX-RS Client と WebSocket の使用例を重点的に示します。

2.1. JAX-RS Client を使用する場合

以下に JAX-RS のクライアント/サーバーを利用した例を示します。

Step 1 - JAX-RS のエンドポイントを定義する

まず、Web アプリケーション側に JAX-RS のエンドポイントを定義します。サンプルコードを以下に示します。

// ApplicationPath クラス
@ApplicationPath("api")
public class ApplicationConfig extends Application { }

// SearchResource クラス
@Path("search")
@RequestScoped
public class SearchResource {
  @GET
  @Path
  @Produces("application/xml")
  public List<List> search(@NotNull @QueryParam("q") String query) {
    List result = new ArrayList<>();
    
    // (ここで検索処理を行い、結果の文字列を result へ格納する)
    
    // JAX-RS は List<String> でも適切な XML にアンマーシャリングする
    return result;
  }
}

Step 2 - JavaFX から JAX-RS エンドポイントへのアクセスを実装する

JavaFX の Controller に対して JAX-RS エンドポイントにアクセスするコードを記述します。JAX-RS に関しては、これだけで JavaFX からデータを取得して ListView などへの表示を行えるようになります。


public class SearchController implements Initializable {
  
  //////////////// 共通処理 ////////////////
  
  /**
   * 検索条件を入力するテキストフィールドです。
   */
  @FXML
  private TextField queryField;
  
  /**
   * 検索結果の文字列を格納するリストです。
   */
  private List results = new ArrayList<>();
  
  //////////////// 検索処理 (非同期) の定義 ////////////////

  /**
   * 検索実行可能な場合に true となるプロパティです。
   */
  private BooleanProperty ready = new SimpleBooleanProperty(true);
  
  /**
   * JAX-RS エンドポイントを呼び出すタスクを作成します。
   * <p>
   * ボタン・クリックのイベントが発生したときに実行することを想定しているため、
   * {@link Service} による非同期処理として実装する必要があります。
   */
  Service<List> searchService = new Service<List>() {
    @Override
    protected Task<List> createTask() {
      return new Task<List>() {
        @Override
        protected List call() throws Exception {
          // 検索条件は queryField に入力される
          String query = queryField.getText();
          
          // 検索結果は listView に設定する
          listView.getItems().clear();
          
          // Web サービスを呼び出し、検索結果を取得する
          List results = client.target(CONTEXT_ROOT).path("api").path("search")
                                       .queryParam("q", query)
                                       .request()
                                       .accept("application/xml")
                                       .get(new GenericType<List>() { });
          // (追加の処理が必要な場合は、ここで実装する)
        
          // 検索結果を設定する
          // このクラス内では getValue() で参照可能になる
          return result;
        }
      };
    }
    
    /**
     * タスク実行時の処理を記述します。
     */
    @Override
    protected void running() {
      // 再検索を行う時は、前回の検索結果を消去する
      tweets.clear();
      
      // 検索中なので ready プロパティの状態を false に変更する
      // これを行わないと検索リクエストが無制限に発生して面倒なことになる
      ready.set(false);
    }
    
    /**
     * タスク成功時の処理を記述します。
     */
    @Override
    protected void succeeded() {
      // 検索結果を取得する
      results.addAll(getValue());
      
      // 検索結果を listView に設定する (重複排除とソートは実施する)
      listView.setItems(FXCollections.observableList(
          getValue().stream().distinct().sort().collect(toList());
      
      // (検索結果のダイアログ表示などは、この場所に記述すること)
    
      // ready プロパティの状態を true に変更して、検索可能な状態に戻す
      ready.set(true);
    }
    
    /**
     * タスクキャンセル時の処理を記述します。
     */
    @Override
    protected void cancelled() {
      // (検索キャンセルのダイアログ表示などは、この場所に記述すること)
      
      // ready プロパティの状態を true に変更して、検索可能な状態に戻す
      ready.set(true);
    }
    
    /**
     * 検索失敗時の処理を記述します。
     */
    @Override
    protected void failed() {
      // (検索失敗のダイアログ表示などは、この場所に記述すること)
      
      // ready プロパティの状態を true に変更して、検索可能な状態に戻す
      ready.set(true);
    }
  };
  
  //////////////// ボタン・イベント処理の定義 ////////////////
  
  /**
   * 検索ボタンをクリックした時の処理を記述します。
   * 
   * @param event アクション実行時のイベント
   */
  @FXML
  public void onSearchAction(ActionEvent event) {
    // searchService による非同期呼び出し
    // もし仮に同期呼び出しを使用すると、JavaFX の UI が硬直するので注意
    searchService.reset();
    searchService.start();
  }
  
  @Override
  public void initialize(URL location, ResourceBundle resources) {
    // 省略
  }  
}

2.2. WebSocket

以下に WebSocket のクライアント/サーバーを利用した例を示します。

Step 1 - WebSocket のエンドポイントを定義する

Web アプリケーション側で WebSocket のエンドポイントを Singleton として実装します。Singleton 以外でも実装可能のはずですが、私の環境では Singleton が最も安定して動作します。

@Singleton
@ServerEndpoint(value = "/api/twitter/publish")
public class TwitterPublisher {
  
  /**
   * WebSocket のセッションを管理するオブジェクトです。
   */
  private Set sessions = new CopyOnWriteArraySet<>();
  
  /**
   * 新たな WebSocket 接続があったときに対応するセッションを追加します。
   *
   * @param 追加されるセッション
   */
  @OnOpen
  public void onOpen(Session session) {
    sessions.add(session);
  }
  
  /**
   * 既存の WebSocket 接続が切断されたときに対応するセッションを削除します。
   *
   * @param 削除されるセッション
   */
  @OnClose
  public void onClose(Session session) {
    sessions.remove(session);
  }
  
  /**
   * エラー発生時の処理を定義します。
   *
   * throws t エラー内容を表す例外オブジェクト
   */
  @OnError
  public void onError(Throwable t) {
    // ログ出力などを行う (省略)
  }
  
  /**
   * 接続済みのすべてのセッションに対してメッセージを配信します。
   * <p>
   * このメソッドは、メッセージ配信のタイミングで外部から呼ばれます。
   * このサンプルでは、外部の EJB タイマーから定期的に呼び出します。
   */
  public void send(String message) {
    sessions.forEach(session -> {
      try {
        session.getBasicRemote().sendText(message);
      } catch (IOException) {
        e.printStackTrace();
      }
    });
  }
  
  @Override
  public void initialize(URL location, ResourceBundle resources) {
    // 省略
  }  
}

Step 2 - Web アプリケーション側に定期配信の仕組み (EJB Timer) を実装する。

WebSocket は定期的に配信するところに強みがあります。エンドポイント自身には定期配信を行うための仕組みがないため、エンドポイントを呼び出して定期配信するためのコードを実装する必要があります。ここでは Web アプリケーション側に EJB Timer を追加して、10 秒おきにエンドポイントからデータを取得するようなコードを用意しました。

@Singleton
public class Scheduler {
  
  /**
   * ポーリング実行時の絞り込み条件を表します。
   */
  private String query;
  
  /**
   * ポーリングの開始・停止を制御します。
   *
   * @pamam flag {@code true} の場合はポーリング実行、{@code false} の場合は停止
   */
  public setPolling(boolean flag) { /* 省略 */ }
  
  /**
   * ポーリングの開始・停止の状態を取得します。
   *
   * @return {@code true} の場合はポーリング実行中、{@code false} の場合は停止済
   */
  public isPolling() { /* 省略 */ }
  
  /**
  * ポーリングを開始します。
  *
  * @param query ポーリング対象の絞り込み条件
  */
  public void startPolling(String query) {
    // ポーリング停止中のみ処理対象 (既に実行中の場合は何もしない)
    if (!isPolling()) {
      this.query = query;
      setPolling(true);
    }
  }
  
  /**
   * ポーリングを停止します。
   */
  public void stopPolling() {
    // ポーリング実行中のみ処理対象 (既に停止中の場合は何もしない)
    if (isPolling()) {
      setPolling(false);
      query = null;
    }
  }
  
  /**
   * Publisher クラス (WebSocket エンドポイント) のインスタンスです。
   */
  @Inject
  private Publisher publisher;
  
  /**
   * 定期的に {@link Publisher#send(String)} を呼び出してデータを配信します。
   * このメソッドは EJB Timer として呼び出されることが補償されているため、
   * {@code private} スコープとしています。
   * <
   * なお、このメソッドは 10 秒おきに EJB Timer として呼び出されます。
   *
   * @see javax.ejb.Scledule
   */
  @Schedule(hour = "*", minute = "*", second = "*/10", persistent = false)
  private void polling() {
    if (isPolling()) {
      // ポーリング実行中: データ配信
      // ここでは getNewResult メソッドで新着メッセージを取得すると仮定
      List result = getNewResult(query);
      result.forEach(s -> publisher.send(s));
    }
  }
}

Step 3 - JavaFX から WebSocket エンドポイントへのアクセスを実装する

JavaFX の Controller に対して WebSocket エンドポイントにアクセスするコードを記述します。WebSocket に関しては、これだけで JavaFX からデータを取得して ListView などへの表示を行えるようになります。

public static class PollingController implements Initializable {

//////////////// 共通処理 ////////////////

/**
 * ポーリング対象条件を入力するテキストフィールドです。
 */
@FXML
private TextField queryField;

/**
 * ポーリングの開始・終了を制御するトグルボタンです。
 */
@FXML
private ToggleButton pollingButton;

/**
 * ポーリング結果の文字列を格納するリストです。
 */
@FXML
private ListView<String> listView;

//////////////// 検索処理 (非同期) の定義 ////////////////

/**
 * ポーリング実行可能な場合に true となるプロパティです。
 */
private BooleanProperty ready = new SimpleBooleanProperty(true);

/**
 * WebSocket エンドポイントを呼び出すタスクを作成します。
 * <p>
 * ボタン・クリックのイベントが発生したときに実行することを想定しているため、
 * {@link Service} による非同期処理として実装する必要があります。
 */
Service<Void> pollingService = new Service<Void>() {
  @Override
  protected Task<Void> createTask() {
    return new Task<Void>() {
      /**
       * This latch works the thread keep awaiting because of accepting cancel request.
       */
      private CountDownLatch messageLatch = new CountDownLatch(1);
      
      /**
       * WebSocket セッションへの参照を表します。
       */
      private Session session;
      
      @Override
      protected Void call() throws Exception {
        // WebSocket のエンドポイント
        final wsEndpoint = URI.create("/api/polling/publish");
        
        // このあたりは WebSocket クライアント API を使うときのほぼ定型コード
        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
        session = container.connectToServer(new Subscriber(listView), wsEndpoint);
        
        messageLatch.await();
        
        return null;
      }
      
      /**
       * ポーリング実行時の処理を記述します。
       */
      @Override
      protected void cancelled() {
        if (session != null) {
          try {
            session.close();
          } catch (IOException e) {
            // ログ出力などを行う (省略)
          }
        }
        
	// pollingButton をクリック可能にする
        pollingButton.setSelected(false);
        
        // ポーリング実行を受け付ける
        ready.set(true);
      }

      /**
       * ポーリングキャンセル時の処理を記述します。
       */
      @Override
      protected void failed() {
        
	// pollingButton をクリック可能にする
        pollingButton.setSelected(false);
        
        // ポーリング実行を受け付ける
        ready.set(true);
      }
    };
  }

  /**
   * タスク失敗時の処理を記述します。
   */
  @Override
  protected void cancelled() {
    // pollingButton をクリック可能にする
    pollingButton.setSelected(false);
    
    // ポーリング実行を受け付ける
    ready.set(true);
  }

  /**
   * タスク失敗時の処理を記述します。
   */
  @Override
  protected void failed() {
    // pollingButton をクリック可能にする
    pollingButton.setSelected(false);
    
    // ポーリング実行を受け付ける
    ready.set(true);
  }
};

/**
 * ポーリング開始・停止ボタンをクリックした時の処理を記述します。
 * 
 * @param event アクション実行時のイベント
 */
@FXML
public void onPollingAction(ActionEvent event) {
  if (pollingButton.isSelected()) {
    pollingButton.setSelected(false);
    
    // 開始要求の GET リクエストを送信するだけ: レスポンスは無視
    client.target(CONTEXT_PATH).path("api").path("polling").path("start")
          .queryParam("q", queryField.getText()).request().get();

    listView.getItems().clear();
    
    // pollingService による非同期呼び出し
    // もし仮に同期呼び出しを使用すると、JavaFX の UI が硬直するので注意
    pollingService.reset();
    pollingService.start();
  } else {
    pollingService.cancel();
    
    // 停止要求の GET リクエストを送信するだけ: レスポンスは無視する
    client.target(CONTEXT_PATH).path("api").path("polling").path("stop")
          .request().get();
  }
}

2.3. その他の手段

JavaServer Faces、Servlet、JSP などに対しては、画面遷移を伴うことから、通常は JavaFX の WebView を経由したアクセスになります。ただし、ユースケースによっては HTTP Client で直接アクセスすることもあり得ます。

JAX-WS については、Java SE に JAX-WS Client が付属しているため、これを使用することができます。

RMI-IIOP などのレガシーな分散処理プロトコルについても、検証こそしていませんが同様の要領で使用できるのではないかと考えています。もっとも、今どき RMI-IIOP なんて (一部の例外を除き) 使用することはないと思いますが。

その他にも Java EE 7 には様々な API が用意されています。JavaFX のアプリケーションに Payara Micro を埋め込むことで、それらの API も気軽に使用することができます。

3. シャットダウン

Payara Micro を JavaFX に組み込むと、通常の Ctrl+C によるシャットダウンができません。これは JavaFX 側でシャットダウン・フックを完全に押さえており、Payara Micro のシャットダウン・コードをシャットダウン・フックに追加する余地がないためだと思われます。この制限のため、Payara Micro のシャットダウンは javafx.application.Application#stop メソッドから PayaraMicro#shutdown または PayaraMicroRuntime#shutdown メソッドを呼び出すことで実現する必要があります。

@Override
public void stop() throws Exception {
    if (runtime != null) {
        runtime.shutdown();
    }
}

なお、Payara Micro を明示的にシャットダウンしなかった場合には、それを動作させていた Java VM (プロセス) が残ったままとなりますので、注意が必要です。

4. まとめ

いくつかの制約事項はありますが、JavaFX と Payara Micro を組み合わせて使用することは可能です。javapackager を使用すれば、Java EE ランタイムを内包した JavaFX アプリケーションをネイティブ・パッケージとして配布することも可能になります。フロントエンドを JavaFX、バックエンドを Payara Micro で構成した、比較的小さな Java EE アプリケーションを開発および配布する場合には、ネイティブ・パッケージも作成できる上記の方法も選択肢のひとつとしてあげられることでしょう。

今回のサンプルコードは一応 https://github.com/khasunuma/payaramicro-on-javafx にあります。急ぎ追加した JAX-RS と WebSocket に関するコードが含まれていないため、あくまでテンプレートとしての扱いでお願いします。

明日は、Payara が「Payara が持つ 2 つのクラスタリング」、JavaFX がさくらばさん (@skrb)、Java EE がタイガーたいぞー (@tigertaizo)さんです。

Payara の歩み (2016-12 版)

この記事は Payara Advent Calendar 2016 の 14 日目です。昨日は「Payara の JDBC サポート機能」です。

今回は、Payara の歴史について振り返ってみたいと思います。

Payara 4.1.144 (2014 年 12 月)

Payara Server 最初のリリースです。当時の Payara は GlassFish 4.x の商用サポート・プロジェクトであり、Payara 4.1.144 も GlassFish 4.1 のバグフィックス版という位置づけでした。

Payara 4.1.151 (2015 年 1 月)

Payara の独自色が姿を見せ始めたリリースです。JBatch に大きな機能拡張があり、従来は永続化マネージャーに Java DB (Apache Derby) のみ使用可能であったものが、MySQL、PostgreSQL、Oracle および DB2 にも対応するようになりました。また、現在の Payara に欠かすことのできない Hazelcast が初めて組み込まれたのも、このリリースです。GlassFish 4.1 で切り捨てられた多国語化対応も、このリリースから復活しています。

Payara 4.1.152 (2015 年 5 月)

Payara Micro はこのリリースで初めて登場しました。構成済みドメインとして GlassFish 4.1 互換の domain1 に加え、プロダクション用途に合わせてチューニングを施した payaradomain も同梱されるようになりました。ただし、起動時にドメインを省略できず、domain1 または payaradomain を明示しなければならない制約がありました。JDK 9 上で動作するようになったのもこのリリースからです。

Payara 4.1.152.1 (2015 年 5 月)

Payara 4.1.152 で管理コンソールに致命的なバグが発見されたため、緊急リリースされたものです。Payara で初めてのパッチリリースとなります。

Payara 4.1.153 (2015 年 7 月)

このリリースでは、Payara Micro に大幅な拡張が施され、API が整備されました。PayaraMicroRuntime クラスや HTTP Auto-binding もこの時に導入されています。また、Payara Blue と呼ばれる、IBM AIX 向けの Payara Server と Payara Micro がラインナップに加わりました。payaradomain 導入によってドメインを省略できなくなった問題についても、ドメイン省略時には domain1 が自動的に選択されるように修正されました。私が管理コンソールの Hazelcast 設定画面を日本語化して Contributor となったのがこのリリースです。

Payara 4.1.1.154 (2015 年 11 月)

このリリースからベースラインが GlassFish 4.1.1 となりました。コンポーネントも GlassFish 4.1.1 と同期するように見直されています。このリリースの新機能として、ログのローテーションを Off にする機能が追加されています。

Payara 4.1.1.161 (2016 年 2 月)

このリリースから、管理コンソールの配色が Payara 独自の、紺色をベースとしたものに変わりました。新機能として、CPU や VM などの状態を監視して結果をログに出力する Health Check Service が追加されました。Health Check Service の実装を皮切りに、Payara は商用サーバーにふさわしい本格的な監視機能を整備し始めることになります。

Payara 4.1.1.161.1 (2016 年 2 月)

Payara 4.1.1.161 の Metro 周りのバグを修正した緊急のパッチリリースです。

Payara 4.1.1.162 (2016 年 5 月)

このリリースと時を同じくして、GitBook ベースの新しいドキュメントが公開されました。Payara 全体としては使用状況をレポートする Phone Home が追加されました。また、Payara Micro では Uber Jar や Maven リポジトリからのデプロイ (GAV deploy) が導入され、マイクロサービスのプラットフォームとしての機能を強化しています。

Payara 4.1.1.163 (2016 年 8 月)

このリリースでは、新たに 2 つの監視機能、Notification Service と Request Tracing Service が追加されました。Request Tracing Service はこのリリースではプレビュー版の位置づけでした。

Payara 4.1.1.164 (2016 年 12 月)

このリリースでは管理コンソールの Hazelcast 設定画面が再構成され、新たに Hazelcast クラスタ・メンバーの一覧を表示できるようになりました。また、ログ出力形式として ULF (Sun GlassFish v3)、ODL (Oracle Fusion Middleware/GlassFish 4) に加え JSON を選択できるようになりました。Payara 4.1.1.163 ではプレビュー版の扱いだった Request Tracing Service は正式版となりました。Payara Micro 独自の機能としては、Uber Jar とそうでない場合でコンテキスト・ルートの設定基準に差異があったものが、Uber Jar 側の基準に統一されました。

Payara 4.1.1.171 (2017 年 第一四半期)

Coming soon... 期待しましょう!

Puzzle : JAX-WS and CDI

このエントリは Java Puzzlers Advent Calendar 2016 の 13 日目です。誰も投稿しなかったので、緊急で投稿します。

問題

以下の JAX-WS エンドポイントを WAR ファイルに含めてデプロイしました。

import javax.jws.*;

@WebService
public class Sample {
  
  private String message;
  
  @WebMethod
  public String sayHello(String name) {
    return "Hello, " + name;
  }
}

このエンドポイントに @Inject で DI を行うよう改修を試みましたが、上手くできませんでした。追加の手順として適切なものはどれでしょうか?

(1) クラスに @Stateless を付加する

(2) クラスに @RequestScoped、@SessionScoped または @ApplicationScoped のいずれかを付加する

(3) WEB-INF 以下に中身が空の beans.xml ファイルを配置する

(4) WEB-INF 以下に bean-discovery-mode を適切に設定した beans.xml ファイルを配置する

解答

(3) WEB-INF 以下に中身が空の beans.xml ファイルを配置する

解説

各選択肢について、それぞれどのようになるのかを説明します。

(1) - @Stateless を付けた時点で、JAX-WS のエンドポイントは EJB となります。EJB のほとんどは WAR ファイルに含めることが可能ですが、JAX-WS のエンドポイントとなる EJB に関しては WAR ファイルには含められず、EAR ファイルによるデプロイが必要です。問題の前提条件が WAR ファイルによるデプロイですので、この選択肢を採った場合はそもそもデプロイできないという現象に陥ります。

(2) - スコープ・アノテーションを使用しただけでは、JAX-WS のエンドポイントは @Inject を認識しません。JAX-WS は Java EE 6 から仕様が変更されておらず、CDI 1.0 にしか対応していないためです。

(3) - WEB-INF 以下に空の beans.xml を配置することで CDI (CDI 1.0) が有効になります。これにより、JAX-WS のエンドポイント内でも @Inject が認識するようになります。

(4) - bean-discovery-mode は CDI 1.1 の beans.xml で追加された属性です。設定値によらず、この属性を持つ beans.xml を配置した時点で CDI 1.1 (またはそれ以降) が有効になります。(2) の説明でも挙げたように、JAX-WS は CDI 1.0 しか認識しないため、CDI 1.1 (またはそれ以降) が有効になった時点で @Inject を認識することができなくなるのです。

参考

Payara のログ・ビューワの制限事項

先日公開した「Payara の JSON Log Formatter」の時点では黙っていましたが、Payara のログ出力形式を JSON にした場合には管理コンソールのログ・ビューワで参照することができません。バグかもしれないが詳細まで調べていないからしばらく黙っていようと思ったら、@yumix_h早速 Issue Tracker で報告したので、こちらでの調査結果を踏まえて報告しておきます。なお、この件は私の調査と Mike Croft (@croft) の判断により、Enhacement として Payara の internal issue になったことを補足しておきます。

この Issue にはタグ "2:WithDev" (Payara 社内持ち帰り)、"c:Enhancement" (機能追加) および "reproducible" (再現可能) が付いています。タグの意味については先日の記事「Payara Issue Tracker のラベル一覧」にまとめていますので参考にしてください。

Payara 本体のロギングには java.util.logging (JUL) を使用しており、ロガーの設定はプロパティ・ファイル logging.properties の内容に記述されています。管理コンソールの「ロガーの設定」画面の操作で変更されるファイルは、domain.xml ではなく logging.properties です。

logging.properties は以下のディレクトリに格納されます:
Payara_installed_directory/glassfish/domains/domain_name/config

管理コンソールの「ロガーの設定」には、「コンソールのロギング・フォーマット」と「ログ・ファイルのロギング・フォーマット」の 2 項目があり、それぞれログ・ハンドラに対応しています。具体的には以下の通りです。

管理コンソールのプルダウン・リストの選択肢である「ULF」、「ODL」および「JSON」は、上記のログ・ハンドラに設定するログ・フォーマッタに対応しています。具体的には以下の通りです。

この他にも、logging.properties を直接操作することにより、任意のログ・フォーマッタを指定することができます。Payara 内部 (具体的には GFFileHandler) では、ULF または ODL ではない形式のログ・フォーマッタを Custom formatter と呼称しています。 Custom formatter にはいくつかの制約があります。

  • 管理コンソールなどでロガーの設定を変更すると GlassFish/Payara の既定値である ULF に再設定される場合がある。
  • 管理コンソールのログ・ビューワでは参照できず、Raw ログ・ビューワを使用する必要がある。これは、ログ・ビューワは標準のログ・フォーマッタのみ対応しているため。

IMPORTANT : Payara 4.1.1.164 では、標準のログ・フォーマッタは UnformLogFormatter および ODLLogFormatter の 2 種類だけです。そのため、JSONLogFormatter は実際には Custom formatter として扱われます。そのため、Custom formatter が出力する JSON 形式のログを管理コンソールのログ・ビューワから参照することはできません (代わりに Raw ログ・ビューワを使用すること)。

ログ・ビューワに対応させるためには、当該ログ出力形式に対応した LogParser の実装や、ログ・ビューワの Backing Bean (※管理コンソールは JavaServer Faces 1.x/2.x で実装されています) の改修などが必要となります。しかし、現在の実装では JSONLogFormatter の追加と、管理コンソールから「JSON」 = JSONLogFormatter を選択可能にする改修を加えただけに過ぎません。

Payara でも管理コンソールのログ・ビューワを JSON 形式のログにも対応させる案について検討に入りましたので、将来のリリースでは管理コンソール上からログ・ビューワを使用して JSON 形式のログを参照できるようになるかもしれません。

Payara の JDBC サポート機能

この記事は Payara Advent Calendar 2016 の 13 日目です。昨日は「Payara の JSON Log Formatter」です。

Payara 4.1.1.161 以降、JDBC 周りの管理・監視機能が強化されました。ベースとなった GlassFish 自身、本格的な実装を備えていましたが、Payara では商用サーバーに必要と考えられる追加機能をさらに実装しています。SQL サポート機能は、管理コンソールから設定を変更することができます。

1. Slow SQL Logging

Slow SQL Logging は、SQL の実行時間がある閾値を超えた場合に、その情報をログに出力するための機能です。データソース定義で Statement Timeout (fish.payara.slow-query-threshold-in-seconds プロパティ) に閾値を設定することで有効になります。既定値は -1 で、この値は Slow SQL Logging を無効化することを意味します。

Slow SQL Logging を有効化した場合、閾値を超えた SQL について、以下のようなログが出力されます。

[#|2016-02-01T22:39:29.289+0000|WARNING|Payara 4.1|javax.enterprise.resource.sqltrace.com.sun.gjc.util|_ThreadID=61;_ThreadName=http-listener-1(2);_TimeMillis=1454366369289;_LevelValue=900;|
  SQL Query Exceeded Threshold Time: 5000(ms): Time Taken: 10000(ms)
Query was SELECT ID, AGE, BIO, BIRTHDATE, BIRTHDAY, DATEFORMAT, DATEOFBIRTH, DATEOFHIRE, EMAIL, HIREDATE, HIREDAY, MEMBERAGE, NAME, TODAYSDATE FROM MEMBERENTITY WHERE (NAME = ?);

java.lang.Exception: Stack Trace shows code path to SQL
    at fish.payara.jdbc.SlowSQLLogger.sqlTrace(SlowSQLLogger.java:123)
    at com.sun.gjc.util.SQLTraceDelegator.sqlTrace(SQLTraceDelegator.java:122)
    at com.sun.gjc.spi.jdbc40.ProfiledConnectionWrapper40$1.invoke(ProfiledConnectionWrapper40.java:448)
    at com.sun.proxy.$Proxy265.executeQuery(Unknown Source)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeSelect(DatabaseAccessor.java:1009)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:644)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeCall(DatabaseAccessor.java:560)
    at org.eclipse.persistence.internal.sessions.AbstractSession.basicExecuteCall(AbstractSession.java:2055)
    at org.eclipse.persistence.sessions.server.ServerSession.executeCall(ServerSession.java:570)
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:242)
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:228)
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeSelectCall(DatasourceCallQueryMechanism.java:299)
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.selectAllRows(DatasourceCallQueryMechanism.java:694)
    at org.eclipse.persistence.internal.queries.ExpressionQueryMechanism.selectAllRowsFromTable(ExpressionQueryMechanism.java:2740)
    at org.eclipse.persistence.internal.queries.ExpressionQueryMechanism.selectAllRows(ExpressionQueryMechanism.java:2693)
    at org.eclipse.persistence.queries.ReadAllQuery.executeObjectLevelReadQuery(ReadAllQuery.java:559)
    at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeDatabaseQuery(ObjectLevelReadQuery.java:1175)
    at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:904)
    at org.eclipse.persistence.queries.ObjectLevelReadQuery.execute(ObjectLevelReadQuery.java:1134)
    at org.eclipse.persistence.queries.ReadAllQuery.execute(ReadAllQuery.java:460)
    at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeInUnitOfWork(ObjectLevelReadQuery.java:1222)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2896)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1857)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1839)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1804)
    at org.eclipse.persistence.internal.jpa.QueryImpl.executeReadQuery(QueryImpl.java:258)
    at org.eclipse.persistence.internal.jpa.QueryImpl.getResultList(QueryImpl.java:473)
    at fish.payara.team.info.controllers.MemberSessionBean.getTeamMemberByName(MemberSessionBean.java:35)

(See also) https://payara.gitbooks.io/payara-server/content/documentation/extended-documentation/advanced-jdbc/slow-sql-logger.html

2. Full JDBC Tracing (Log JDBC Calling)

Full JDBC Tracing (Log JDBC Calling) は、JDBC (あるいは JPA) がどのようなメソッドを呼び出したのかをログに記録する機能です。内部で何をやっているのか把握しづらい JPA のデバッグなどに有効でしょう。

Full JDBC Tracing は規定では無効です。有効化する場合には、管理コンソールのデータベース設定で Log JDBC Calls に Enabled のチェックを入れる (fish.payara.log-jdbc-calls プロパティを true に設定する) 必要があります。

Full JDBC Tracing が出力するログは以下のようになります。

[#|2016-02-04T18:51:01.467+0000|FINE|Payara 4.1|javax.enterprise.resource.sqltrace.com.sun.gjc.util|_ThreadID=35;_ThreadName=http-listener-1(5);_TimeMillis=1454611861467;_LevelValue=500;ClassName=com.sun.gjc.util.SQLTraceLogger;MethodName=sqlTrace;|
  PoolName=DerbyPool | ExecutionTime=1ms | ClassName=org.apache.derby.client.net.NetConnection40 | MethodName=prepareStatement | arg[0]=SELECT ID, AGE, BIO, BIRTHDATE, BIRTHDAY, DATEFORMAT, DATEOFBIRTH, DATEOFHIRE, EMAIL, HIREDATE, HIREDAY, MEMBERAGE, NAME, TODAYSDATE FROM MEMBERENTITY WHERE (NAME = ?) | arg[1]=1003 | arg[2]=1007 | |#]

[#|2016-02-04T18:51:01.467+0000|FINE|Payara 4.1|javax.enterprise.resource.sqltrace.com.sun.gjc.util|_ThreadID=35;_ThreadName=http-listener-1(5);_TimeMillis=1454611861467;_LevelValue=500;ClassName=com.sun.gjc.util.SQLTraceLogger;MethodName=sqlTrace;|
  PoolName=DerbyPool | ExecutionTime=0ms | ClassName=com.sun.gjc.spi.jdbc40.PreparedStatementWrapper40 | MethodName=setString | arg[0]=1 | arg[1]=test | |#]

[#|2016-02-04T18:51:01.468+0000|FINE|Payara 4.1|javax.enterprise.resource.sqltrace.com.sun.gjc.util|_ThreadID=35;_ThreadName=http-listener-1(5);_TimeMillis=1454611861468;_LevelValue=500;ClassName=com.sun.gjc.util.SQLTraceLogger;MethodName=sqlTrace;|
  PoolName=DerbyPool | ExecutionTime=1ms | ClassName=com.sun.gjc.spi.jdbc40.PreparedStatementWrapper40 | MethodName=executeQuery | |#]

[#|2016-02-04T18:51:01.483+0000|FINE|Payara 4.1|javax.enterprise.resource.sqltrace.com.sun.gjc.util|_ThreadID=35;_ThreadName=http-listener-1(5);_TimeMillis=1454611861483;_LevelValue=500;ClassName=com.sun.gjc.util.SQLTraceLogger;MethodName=sqlTrace;|
  PoolName=DerbyPool | ExecutionTime=0ms | ClassName=com.sun.gjc.spi.jdbc40.PreparedStatementWrapper40 | MethodName=close | |#]

(See also) https://payara.gitbooks.io/payara-server/content/documentation/extended-documentation/advanced-jdbc/log-jdbc-calls.html

3. SQL Trace Listener

SQL Trace Listener は、JDBC (SQL) 監視を独自実装したい場合に使用します。詳細はマニュアルを参照してください。

https://payara.gitbooks.io/payara-server/content/documentation/extended-documentation/advanced-jdbc/sql-trace-listeners.html

4. Payara Micro でのサポート状況

今回ご紹介した機能は、Payara Server を主眼に置いていますが、Payara Micro でも使用できます。


2016-12-13 補足: See also http://www.coppermine.jp/docs/promised/2016/12/payara-logviewer-restriction.html

Payara の JSON Log Formatter

この記事は Payara Advent Calendar 2016 の 12 日目です。昨日は「Payara Issue Tracker のラベル一覧」です。

Payara 4.1.1.164 から、Payara のログを JSON 形式で出力できるようになりました。Payara には当初から GlassFish から引き継いだ 2 種類のログ出力形式―ULF (Unified Logging Format : Sun のミドルウェアで使用されていた形式) および ODL (Oracle Diagnostic Logging : Oracle Fusion Middleware で採用されている形式)―が備わっていましたが、最新リリースで新たに第 3 の形式である JSON が加わったことになります。

1. JSON Log Formatter を有効にする

ログ形式の変更は管理コンソールを用いるとプルダウンリストだけで操作できます。asadmin でも変更は可能ですが、コマンド記述が少々長くなります。この記事では管理コンソールでのログ形式変更について取り上げます。まずは figure 1 を参照してください。

logger-settings.png
figure 1 - ロガーの設定画面

ロガーの設定で、ログ形式の種類を選択することができます。ログの種類は ULF、ODL、さらに Payara 4.1.1.164 からは JSON の中から選択します。コンソールとログ・ファイルでそれぞれ別の形式を選択することも可能です。実際に Payara の既定値はコンソールが ULF 形式、ログ・ファイルが ODL 形式となっています。この例では、ログ・ファイルのログ形式を ODL から JSON に変更しています。

2. JSON ログ形式

Payara 4.1.1.163 までの既定値である ODL 形式のログ・ファイルは以下のようになります。

[2016-12-01T10:26:42.428+0900] [Payara 4.1] [INFO] [NCLS-LOGGING-00009] [javax.enterprise.logging] [tid: _ThreadID=17 _ThreadName=RunLevelControllerThread-1480555602091] [timeMillis: 1480555602428] [levelValue: 800] [[
  Running Payara Version: Payara Server  4.1.1.164 #badassfish (build 28)]]

コンソール出力の既定値であり、GlassFish v3 の出力形式であった ULF 形式のログファイルは以下のようになります。

[#|2016-12-02T15:37:59.788+0900|INFO|Payara 4.1|javax.enterprise.logging|_ThreadID=17;_ThreadName=RunLevelControllerThread-1480660679551;_TimeMillis=1480660679788;_LevelValue=800;_MessageID=NCLS-LOGGING-00009;|
  Running Payara Version: Payara Server  4.1.1.164 #badassfish (build 28)|#]

Payara 4.1.1.164 で追加された JSON 形式のログ・ファイルは以下のようになります。

{"_Timestamp":"2016-12-02T14:22:40.237+0900","_Level":"INFO","_Version":"Payara 4.1","_LoggerName":"javax.enterprise.logging","_ThreadID":"17","_ThreadName":"RunLevelControllerThread-1480656159958","_TimeMillis":"1480656160237","_LevelValue":"800","_MessageID":"NCLS-LOGGING-00009","_LogMessage":"Running Payara Version: Payara Server  4.1.1.164 #badassfish (build 28)"}

JSON 形式のログは他の形式と比較して煩雑になりますが、JSON パーサーを使用して解析することができるため、ログ・データの再利用を行う上では有利になります。

Payara Issue Tracker のラベル一覧

この記事は、Payara Advent Calendar 2016 の 11 日目です。昨日は「Payara の Phone Home」です。

Payara は GitHub 上で開発されている OSS ですが、TwitterFacebookLinkedIn および YouTube にそれぞれアカウントを持っており、これら SNS も重視しています。とは言うものの、Payara の不具合や改善要望を直接 Payara Team に伝えるには、GitHub の Issue Tracker を使うのが良いでしょう。Payara の Issue Tracker は https://github.com/payara/Payara/issues になります。

今回は、Issue Tracker で使われているラベルとその意味・使われ方について、一覧形式でまとめます。ラベルは Payara Team が付けるものですが、ユーザーからもそれぞれの Issue が現在どのような状態にあるかを知る手がかりになります。

ラベル説明
0:Triaged 分類済み
1:Investigating 調査中
1:Wait 報告者からの応答待ち
2:WithDev Payara 社内持ち帰り
3:DevInProgress 開発中 ※Pull Request に付けられる
Awaiting CLA CLA 待ち ※Pull Request に付けられる
c:ConfirmedBug バグ
c:Enhancement 機能追加など
c:Misconfiguration 設定ミス
c:PossibleBug バグの可能性あり
c:Question 質問
c:Task タスク
cannot reproduce 事象が再現しない
CLA CLA 済み ※Pull Request に付けられる
documentation ドキュメント関連
duplicate 他の Issue と重複
enhancement 機能追加など ※現在は c:Enhancement
help wanted ヘルプが必要
Insufficient Detail 提供された情報だけでは不十分
payara-micro Payara Micro 関連
payara bug Payara 固有のバグ (GlassFish は無関係)
question 質問 ※現在は c:Question
ready 開発完了 ※Pull Request に付けられる
reproducible 事象が再現した
requestor unresponsive (1) 報告者からしばらく応答がない
requestor unresponsive (2) 報告者から長い間応答がない
Test Case Required テストケースを要求する
try to reproduce 事象の再現待ち
upstream bug GlassFish のバグ
wontfix 修正しない

ラベルには単独でも付けられるものと、そうでないものがあります。例えば、"Triaged" は単独で付けられることはなく、必ず分類を表すラベル (主に "c:ConfirmBug"、"c:Enhancement"、"c:Miscnofiguration"、"c:PossibleBug"、"c:Question"、"c:Task") とあわせて使われます。

いくつかのラベルはワークフローとして使われます。以下に例を挙げます。

  • Pull Request → "InDevProgress" → CLA なし → "Awaiting CLA" → CLA 完了 → "CLA" → マージ完了 → "ready"
  • Issue に対して Payara Team が回答 & 応答待ち → "Wait" → しばらく応答なし → "requestor unresponsive (1)" → 長い間応答なし → "requestor unresponsive (2)" → さらに応答なし → Issue 打ち切り&クローズ

なお、クローズ済みの古い Issue には、以上のラベル分類に必ずしも従っていない、あるいはラベルが全く付けられていないものもあります。それらについてはラベルが現在の形に整備される前であったためと割り切ってください。

Payara の Phone Home

この記事は、Payara Advent Calendar 2016 の 10 日目です。昨日は「Payara の Asadmin Recorder を活用する」です。

Payara 4.1.1.162 より、Phone Home という機能が追加されました。これはユーザーの Payara 使用状況を Payara に送信し、今後の計画に役立てることを目的としています。

実際に Phone Home を行っている fish.payara.nucleus.phonehome.PhoneHomeTask クラスを見れば一目瞭然なのですが、送信している項目は、以下の通りです。

Phone Home で送信する項目
keyvalue
ver Payara のバーション
jvm JVM のバージョン
uptime Payara を起動してからの経過時間
nodes ノード数
servers サーバー数

これらの情報を Payara 起動時に取得し、http://www.payara.fish/phonehome へ送信する仕組みとなっています。具体的にどのような値が送信されるかについては、全部ではありませんが取得するサンプルを作成しましたので、よろしければ参考にしてください。

https://github.com/khasunuma/phonehomedata

Phone Home を無効にする方法

Phone Home は初期状態で有効となっています。Phone Home 自体はユーザー個人を特定するような情報を送信しませんが、それでも何かしら情報が送信されるのは嫌だ、という方は、以下のいずれかで Phone Home を停止することができます。

方法 1: Phone Home のモジュール自体を削除する

Payara が停止している状態で、payara41/glassfish/modules/phonehome-bootstrap.jar を削除してください。Payara のモジュラー・システムのロード対象外となり、Phone Home が動作しなくなります。おそらく、これが究極的な対処法になると思われます。

方法 2: asadmin で Phone Home を無効化する

ドメインが起動している状態で、以下のコマンドを実行してください。

asadmin disable-phone-home

これで、次回以降の起動時には Phone Home は働きません。

方法 3: doman.xml で Phone Home を無効化する

domain.xml (既定では payara41/bin/glassfish/domains/domain1/config 以下にある) を直接更新する方法で対処可能です。くれぐれも domain.xml を壊さないよう、注意してください。

まず domain.xml から <config name="server-config"> 要素を探しだし、子要素として <phone-home-runtime-configuration enabled="false"/> を追加します。その後、ドメインを再起動すれば Phone Home は無効化されます。

補足

Phone Home は非常に小さなプログラムです。HTTP 経由で Payara のサーバーに情報を送信するのですが、Proxy に関する考慮などが全くなされていません。そのため、Proxy により外部へのデータ送信が規制されている場合には、Phone Home は動作しません (接続できずタイムアウトになります)。特に Proxy に BASIC 認証を設定している場合 (SIer でよく行われている設定ですが...個人的には無意味だと思うのですよね。元ネタは IPA のセキュリティ関係の資料なのですが、通読した限りでは結構いい加減なことを書いていたので) には絶対に送信できません。

Payara Micro に関する補足

Payara Micro にも Phone Home は実装されています。初期状態で有効となっている点も Payara Server と同じです。ただし設定方法は異なります (例えば、モジュール削除は使えません)。詳細は Payara Micro のマニュアル (コマンドライン・オプション) を参照してください。

Payara の Asadmin Recorder を活用する

この記事は、Payara Advent Calendar 2016 の 9 日目です。昨日は「Payara の Request Tracing Service」です。今回は Asadmin Recorder について、復習も兼ねて取り上げてみたいと思います。

1. Asadmin Recorder とは

Asadmin Recorder は、Payara の管理コンソール上で行った動作を asadmin コマンドで実行可能なスクリプトとして記録する機能です。

Payara 4.1.1.162 以降で管理コンソールを開くと、右上に「Enable Asadmin Recorder」というボタンが追加されていると思います (figure 1)。

enable-asadmin-recorder.png
figure 1 - Enable Asadmin Recorder ボタン

このボタンをクリックすると、それ以降の管理コンソール上の操作がファイル (既定では payara41/glassfish/domains/domain1/asadmin-commands.txt) に記録されてゆきます。再度ボタンをクリックすると記録を停止します。

生成されたスクリプト (ここでは既定の asadmin-commands.txt とします) は標準出力からパイプで asadmin に渡すことでそのままスクリプトとして使用することができます。

(Linux/Unix/Mac) $ cat asadmin-commands.txt | asadmin

(Windows) > type asadmin-commands.txt | asadmin

Asadmin Recorder によって、例えば WebLogic Server が装備している WLST のような使い方が可能になります (スクリプトに制御構文がないため WLST のような柔軟性はありませんが)。

2. asadmin コマンドについて

asadmin は Payara の起動に使用するコマンドです。ただし、他の Java EE サーバー製品が単なる起動スクリプトであるのに対し、asadmin は CLI の管理ユーティリティーである点が異なります。

Payara の管理は、究極的には asadmin と REST backend の 2 通りしか用意されていません。管理コンソールもその実体は REST backend のフロントエンドとして動作する Web アプリケーションです。

asadmin は Payara のすべてを操作することが可能ですが、REST backend ではいくつかの操作が行えません。具体的には、DAS の起動は REST backend では実行できない操作です。

asadmin にはシングルモードとマルチモードという、2 種類の動作モードがあります。

(1) シングルモード

asadmin の基本となるモードです。

# asadmin サブコマンド名 [パラメーター]

パラメーターはサブコマンドによって異なります(サブコマンドによってはパラメーターがない場合もあります)。

(2) マルチモード

複数のサブコマンドを続けて入力することができるモードです。「asadmin」とだけ入力するとマルチモードになります。その後はプロンプト「asadmin>」に続いてサブコマンドを次々と入力してゆきます。すべてが終わったら「exit」コマンドでマルチモードを終了します。

# asadmin
Use "exit" to exit and "help" for online help.
asadmin> サブコマンド名 [パラメーター]
...
asadmin> exit

マルチモードでは、実行したいサブコマンドのリストをファイルとして作成しておくことで、サブコマンドのバッチ実行が可能になります。

# cat サブコマンド一覧ファイル | asadmin

Asadmin Recorder で生成されたスクリプトの実行も、マルチモードを利用します。

3. Asadmin Recorder を使う

それでは、実際に Asadmin Recorder を使用してみます。まずは Asadmin Recorder を有効化 (figure 1) し、操作記録を開始します。

step1-enable-asadmin-recorder.png
figure 1 - Asadmin Recorder の有効化

続いて、サンプル操作として Hazelcast を有効化してみます (figure 2)。

step2-enable-hazelcast.png
figure 2 - Hazelcast の有効化

もう一つのサンプル操作として、ログ出力形式を既定値の ODL から新しく追加された JSON に変更してみましょう (JSON Log Formatter については後日ご紹介します)。

step3-set-logger-to-json.png
figure 3 - ログ出力形式の変更 (ODL から JSON に変更)

最後に Asadmin Recorder を無効化し、ここまでの操作を記録したスクリプトを生成します。

step4-disable-asadmin-recorder.png
figure 4 - Asadmin Recorder の無効化

ここまでの操作を記録した asadmin-commands.txt の内容は以下の通りです。

set-hazelcast-configuration --startPort=5900 --licenseKey= --hazelcastConfigurationFile=hazelcast-config.xml --clusterName=development --clusterPassword=D3v3l0pm3nt --dynamic=true --multicastPort=54327 --enabled=true --multicastGroup=224.2.2.3 --jndiName=payara/Hazelcast --target=server 
set-log-attributes --target=server-config java.util.logging.FileHandler.pattern='%h/java%u.log'
set-log-attributes --target=server-config java.util.logging.FileHandler.count='1'
set-log-attributes --target=server-config java.util.logging.FileHandler.formatter='java.util.logging.XMLFormatter'
set-log-attributes --target=server-config com.sun.enterprise.server.logging.GFFileHandler.rotationTimelimitInMinutes='0'
set-log-attributes --target=server-config com.sun.enterprise.server.logging.GFFileHandler.excludeFields=''
set-log-attributes --target=server-config com.sun.enterprise.server.logging.GFFileHandler.compressOnRotation='false'
set-log-attributes --target=server-config com.sun.enterprise.server.logging.GFFileHandler.multiLineMode='true'
set-log-attributes --target=server-config com.sun.enterprise.server.logging.GFFileHandler.retainErrorsStasticsForHours='0'
set-log-attributes --target=server-config com.sun.enterprise.server.logging.GFFileHandler.rotationOnDateChange='false'
set-log-attributes --target=server-config com.sun.enterprise.server.logging.GFFileHandler.formatter='fish.payara.enterprise.server.logging.JSONLogFormatter'
set-log-attributes --target=server-config com.sun.enterprise.server.logging.GFFileHandler.maxHistoryFiles='0'
set-log-attributes --target=server-config handlerServices='com.sun.enterprise.server.logging.GFFileHandler,com.sun.enterprise.server.logging.SyslogHandler'
set-log-attributes --target=server-config java.util.logging.FileHandler.limit='50000'
set-log-attributes --target=server-config com.sun.enterprise.server.logging.GFFileHandler.file='${com.sun.aas.instanceRoot}/logs/server.log'
set-log-attributes --target=server-config handlers='java.util.logging.ConsoleHandler'
set-log-attributes --target=server-config java.util.logging.ConsoleHandler.formatter='com.sun.enterprise.server.logging.UniformLogFormatter'
set-log-attributes --target=server-config com.sun.enterprise.server.logging.GFFileHandler.flushFrequency='1'
set-log-attributes --target=server-config com.sun.enterprise.server.logging.GFFileHandler.logtoConsole='false'
set-log-attributes --target=server-config com.sun.enterprise.server.logging.SyslogHandler.useSystemLogging='false'
set-log-attributes --target=server-config log4j.logger.org.hibernate.validator.util.Version='warn'
set-log-attributes --target=server-config com.sun.enterprise.server.logging.GFFileHandler.rotationLimitInBytes='2000000'

4. Asadmin Recorder の活用方法

Payara Server の設定の多くは、管理コンソールで行うのが簡単です。しかし、複数の Payara Server で同一の設定をしなければいけない場合 (クラスタ構成を採る場合など)、すべてのノードに対して管理コンソールで設定を行うのは面倒です。そこで、Asadmin Recorder を有効にして基準となるサーバーの設定を管理コンソールで行い、他のサーバーの設定を出力されたスクリプトでバッチ実行すると、設定に関わる時間を大幅に短縮することができます。

あるいは、Payara Server の設定内容を Asadmin Recorder で記録しておき、障害発生時のリストア作業に用いる方法も考えられます。こちらもバッチ処理で設定が行えるため、リストアにかかる時間を短縮することが可能です。