§設定ファイルのシンタックスと機能
Play における設定ファイルは Typesafe config library をベースにしています。
Play アプリケーションのデフォルトの設定ファイルは、 conf/application.conf
に保存されている必要があります。また、この設定ファイルは、 HOCON フォーマット ( “Human-Optimized Config Object Notation” ) で記述されています。
§代替設定ファイルの指定
システムプロパティを使うことで、異なる設定ソースの利用を強制することができます。
config.resource
はリソースファイルの指定です - application のようなベースネームの指定はできません。また、 application.conf 以外を指定します。config.file
はファイルシステム上のパスの指定です。パスには拡張子を含みます。ベースネームの指定はできません。config.url
は URL の指定です。
これらのシステムプロパティは application.conf
の代替えとなる設定ファイルの指定に使います。代替えであって、追加ではないことに注意してください。ただし、代替えになる設定ファイルに include “application” と記述することで、デフォルトの設定ファイルを include することができます。さらに、 include 文の後で、任意の設定を上書きすることができます。
§Akka と一緒に使う
Akka 2.0 は Play アプリケーションと同じ設定ファイルを読み込みます。つまり、 Akka に関するあらゆる設定は application.conf
で行うことができます。
§HOCON の文法
HOCON の文法は、ほとんど JSON にしたがって定義されています。JSON の仕様は http://json.org/ にあります。
§JSON と同じ部分
- ファイルは UTF-8 形式でなければならない
- 引用された文字列は JSON の文字列と同じフォーマットである
- 利用可能な値の型は string、number、object、array、boolean、null
- JSON と同様の数値形式が利用可能。 JSON 同様、
NaN
のように表現不可能な浮動小数点数がある。
§コメント
//
や #
から次の行まではコメントとして無視されます。ただし、//
や #
が引用文字列のなかにある場合は含みません。
§root の括弧の省略
JSON ドキュメントは root として array や object を一つ持つ必要があります。空ファイルだったり、もしくは string などの array や object 以外しか含まないファイルは JSON ドキュメントとして正しくありません。
HOCON では、ファイルが中括弧や大括弧で始まっていない場合、中括弧 {}
で囲まれているものとしてパースします。
しかし、始め中括弧 {
を省略したにも関わらず 閉じ中括弧 }
が存在するようなファイルは HOCON フォーマットとして正しくありません。中括弧はバランスされている必要があります。
§Key-value の区切り文字
JSON でキーと値を区切るために :
が期待されるような箇所には、=
を使うこともできます。
キーの直後に {
が現れた場合、:
や =
は省略可能です。つまり、 "foo" {}
は "foo" : {}
と同じ意味です。
§カンマ
配列内の値や、オブジェクトのフィールドを区切るためのカンマは、1つ以上の改行(\n
、10進数のASCIIコードでいえば 10)があれば省略可能です。
配列の最後の要素や、オブジェクトの最後のフィールドの後に、一つだけカンマを記述することができます。この余分なカンマは無視されます。
[1,2,3,]
と[1,2,3]
は同じ配列です。[1\n2\n3]
と[1,2,3]
は同じ配列です。[1,2,3,,]
は最後にカンマが二つ連続しているため、正しくありません。[,1,2,3]
は最初にカンマが記述されているため、正しくありません。[1,,2,3]
は途中でカンマが二つ連続しているため、正しくありません。- オブジェクトのフィールドを区切るカンマについても、同様のルールが適用されます。
§重複したキー
JSON の仕様では、同じオブジェクトにおける重複したキーの取り扱いは定義されていません。 HOCON では、キーが重複していて、値の一方がオブジェクト以外の場合は、後に出現している方が先に出現しているものを上書きします。値が両方ともオブジェクトの場合は、二つのオブジェクトがマージされます。
Note: もしあなたが JSON においてキーが重複した場合に何らかの挙動を示すことを期待するのであれば、 HOCON は JSON の上位集合ではないといえます。ここでは、重複キーを含む JSON は正しくない、という仮定をしています。
オブジェクトのマージは次のように行われます。
- 二つのオブジェクトの一方にしか無いフィールドを、マージにより生成される新しいオブジェクトに追加します。
- 両方のオブジェクトに存在する全ての「値がオブジェクトでないフィールド」について、後者のオブジェクトの方のフィールドを採用する。
- 両方のオブジェクトに存在する全ての「値がオブジェクトであるフィールド」について、これら3つのルールに基づき再帰的にマージする。
キーに一旦オブジェクト以外の値をセットすることで、二つのオブジェクトをマージさせないことも可能です。これは、マージが常に二つの値に対して行われるからです。例えば、キーにオブジェクト、オブジェクト以外、オブジェクトの順に値をセットすると、まず非オブジェクトがオブジェクトを上書きします(常に非オブジェクトが優先されます)。次に、オブジェクトが非オブジェクトを上書きします(オブジェクトがそのキーに対する新しい値となります。マージはされません。)。したがって、1番目と3番目のオブジェクトがマージされることはありません。
以下の二つは同じです。
{
"foo" : { "a" : 42 },
"foo" : { "b" : 43 }
}
{
"foo" : { "a" : 42, "b" : 43 }
}
以下の二つも同じです。
{
"foo" : { "a" : 42 },
"foo" : null,
"foo" : { "b" : 43 }
}
{
"foo" : { "b" : 43 }
}
ご覧のとおり、途中で "foo"
に null
をセットすることで、オブジェクトのマージを行わないということが可能です。
§キーとしてのパス
もしキーが複数要素からなるパス式である場合、パスの最後以外の全ての要素が展開されて、オブジェクトが生成されます。パスの最後の要素は、値と組み合わされて、最も内側のオブジェクトのフィールドとなります。
言い換えると、
foo.bar : 42
は、
foo { bar : 42 }
と等しく、また、
foo.bar.baz : 42
は、
foo { bar { baz : 42 } }
と等しくなります。
また、これらの値は前述の方法でマージされます。つまり、
a.x : 42, a.y : 43
は、
a { x : 42, y : 43 }
と等しくなります。
パス式は値の結合と同じように扱われるため、キー中に空白を含めることができます。
a b c : 42
は、
"a b c" : 42
と等しくなります。
パス式は常に文字列に変換されるため、通常は文字列以外の型となるような単一の値さえも文字列になります。
true : 42
は"true" : 42
3.14 : 42
は"3.14" : 42
特例として、引用符を含まない include
は特別扱いされるため、キーにパス式を含んではいけません。
§置換
置換は、設定ツリーの別のパートを参照する機能です。
置換の文法は、 ${pathexpression}
または $(?pathexpression}
です。pathexpression
は前述のパス式です。このパス式は、オブジェクトのキーに利用できるものと同じ文法です。
${?pathexpression}
中の ?
はその直前に空白文字を含んではいけません。 ${?
という3つの文字は、全くこのとおりにまとめて記述されている必要があります。
設定ツリー内に見つからない置換を行おうとした場合、設定ファイルの実装がシステム環境変数や他の外部ソースにある設定を参照して解決を試みます。 (環境変数に関する詳細は後のセクションでご説明します。)
引用文字列中では、置換はパースされません。置換を含むような文字列を取得したい場合、引用符で囲まれていない部分に置換を記述して、値の結合を行うとよいでしょう。
key : ${animal.favorite} is my favorite animal
もしくは、置換以外の部分を引用符で囲ってもよいでしょう。
key : ${animal.favorite}" is my favorite animal"
置換は設定ファイル中のパスを検索して解決されます。このとき、パスは root オブジェクトから始まります。言い換えれば、置換で指定するパスは 相対的
というより 絶対的
です。
置換処理は設定ファイルのパースにおける最終ステップで実行されます。したがって、置換は設定ファイルの前方で定義されたパスを参照することができます。もし、設定が複数のファイルから構成されているのであれば、置換が記述されているファイルとは別のファイルから値を取得することになるかもしれません。キーが複数回指定されている場合、一番最後にセットされた値(そのキーについてマージされたオブジェクトか、もしくは最後にセットされたオブジェクト以外の値)が置換により挿入されることになります。
設定によりある値に明示的に null
がセットされた場合、その値を外部ソースから参照することはできなくなります。残念ながら、これを後の設定ファイルで “undo” する方法はありません。例えば、 root オブジェクトに { "HOME" : null }
というフィールドを定義してしまうと、 ${HOME}
という置換が外部ソースである環境変数を使って解決されることはありません。言い換えれば、 JavaScript の delete
に相当するような操作は用意されていないということです。
もし、置換しようとしている値が設定や外部ソースに存在しない場合、 それは未定義として扱われます。${foo}
という文法では未定義の置換は許されていないので、エラーとなります。
一方で、 ${?foo}
という文法による置換が未定義となった場合、
- もしオブジェクトのフィールドの値であったなら、フィールドは作成されない。既にフィールドが存在して、以前の値を上書きしようとしていた場合、未定義で上書きはされず以前の値が残る。
- もし配列の要素であったなら、要素は追加されない。
- もし値の結合の一部分であったなら、その部分は空文字列に変換される。
foo: ${?bar}
はbar
が未定義の場合は フィールドfoo
を作成しない。しかし、foo : ${?bar} ${?baz}
は値の結合であるため、bar
かbaz
が未定義の場合、結果は空文字列となる。
置換はオブジェクトのフィールドの値や配列の要素(値の結合)においてのみ有効で、キーやその他の置換(パス式)内では利用できない。
置換は任意の値型(number、object、string、array、true、false、null)と置き換えることができます。置換が値の一部分でしかない場合は、型はそのまま維持されます。そうでなければ、値が結合されて string となります。
お互いに循環するような置換は不正であり、エラーとなります。
しかしながら、設定のパーサの実装において、オブジェクトが自身のパスを参照できることに十分注意しなければなりません。例えば、以下の設定は有効です。
bar : { foo : 42,
baz : ${bar.foo}
}
この設定における置換 ${bar.foo}
を解決するにあたって bar
の全ての置換を解決するようなパーサーを実装してしまったとすると、循環が発生します。実際には、このときに bar
に再帰するのではなく、bar
の foo
フィールドだけを解決するようなパーサーを実装しなければなりません。
§インクルード
§インクルードの文法
include 命令は、二重引用符に囲まれていない include
という文字列と、その直後にある引用符に囲まれた文字列の二つの要素から構成されます。incude 命令はオブジェクトのフィールドの箇所に記述することができます。
引用符に囲まれていない include
がパス式の始まり、つまりオブジェクトのキーが期待されるところに現れた場合、パス式やキーとしては解釈されません。
その代わりに、次の値は引用符で囲まれた文字列でなければなりません。その文字列は、インクルードされるファイル名かリソース名として解釈されます。
まとめると、引用符で囲まれていない include
と引用符で囲まれた文字列は、文法的にはオブジェクトのフィールドに置換されます。また、その後に続けて他のフィールドや、さらなるインクルードをカンマ(前述のとおり、改行がある場合は省略可能)で区切って記述することができます。
引用符で囲まれていない include
で始まるキーの後に、引用符で囲まれた文字列以外のものを続けて記述するのは間違いで、エラーとなります。
引用符で囲まれていない include
と、引用符で囲まれた文字列の間は、任意の個数の空白文字または改行を含めることができます。
incude
の引数に対しては、値の結合が機能しません。引数は必ず引用符で囲まれた文字列でなければなりません。置換は効かず、引数は引用符で囲まれていない文字列やその他の値も使うことはできません。
引用符で囲まれていない include
は、パス式の始め以外の箇所に出現した場合は特別な意味を持ちません。
キーの後半に出現したり、
# this is valid
{ foo include : 42 }
# equivalent to
{ "foo include" : 42 }
オブジェクトや配列の値として出現することもあるでしょう。
{ foo : include } # value is the string "include"
[ include ] # array of one string "include"
"include"
という単語で始まるキーが必要な場合は、 "include"
のように引用符をつけることができます。include
が特別な意味を持つのは、引用符で囲まれていない時だけです。
{ "include" : 42 }
§インクルードの意味論: マージ
インクルードした側のファイル は include 命令と include 命令で指定された インクルードされたファイル の2要素から構成されています。(_インクルードされたファイル_ はファイルシステム上のファイルである必要はないのですが、今のところはそう思っておいてください)
インクルードされたファイル は配列ではなくオブジェクトを含んでいる必要があります。通常、JSON や HOCON がドキュメントの root として配列を許していることを考えると、 インクルードされたファイル の root にオブジェクトしか許されないのは特徴的です。
root として配列を持っているような include file は間違いで、エラーとなります。
インクルードされたファイル がパースされると、 root オブジェクトが生成されます。概念的には、root オブジェクトのキーが インクルードした側のファイル の include 命令と置換される、と考えてください。
- included object に含まれるキーが include 命令より前に出現していた場合、インクルードされたキーの値は以前の値を上書き、または以前の値とマージされます。上書きとマージの方法は、単一ファイルに同じキーが複数回出現した場合と全く同じです。
- 以前に included object から読み込まれたキーを インクルードした側のファイル で再度指定した場合、インクルードした側のファイルの値がインクルードされたファイルの値を上書きまたはマージします。
§インクルードの意味論: 置換
インクルードされたファイル 内の置換は2ステップかけて、それぞれパスが異なる解釈をなされて解決されます。最初のステップでは、パスは インクルードされたファイル の root から相対的なものとして解釈されます。次のステップでは、パスは インクルードした側のファイル の root から相対的なものとして解釈されます。
前述のとおり、置換はパースの_後_、最終ステップで実行されます。置換は単一ファイルのファイルそれぞれについてではなく、アプリケーションの設定全体について行われます。
したがって、included file が置換を含む場合、パスはアプリケーションの設定の root から相対的なものとして「修正」されなければなりません。
例えば、次のような root 設定があるとします。
{ a : { include "foo.conf" } }
そして、次のような “foo.conf” があるとします。
{ x : 10, y : ${x} }
“foo.conf” を単体でパースしたとしたら、 ${x}
はパス x
の値 10 と評価されるはずです。しかし、 “foo.conf” を任意のオブジェクトのキー a
にインクルードした場合、このパスは ${x}
ではなく ${a.x}
のように修正されなければなりません。
さらに、 root 設定で a.x
が次のように再定義されているケースを考えてみます。
{
a : { include "foo.conf" }
a : { x : 42 }
}
このとき、 “foo.conf” の ${x}
は ${a.x}
に修正されて、 10
ではなく 42
と評価されます。置換は全ての設定のパース 後
に処理されるからです。
一方で、 インクルードされたファイル から意図的にアプリケーションの root 設定を参照したいケースはよくあります。例えば、システムプロパティやリファレンス設定から値を取得するような場合です。前述のように「修正」されたパスを参照できるだけでは不十分で、今述べたように元々のパスも参照できる必要があるのです。
§インクルードの意味論: 存在しないファイル
インクルードされたファイル が存在しない場合、 include 命令は特にエラーもなく無視されます(インクルードされたファイル に空オブジェクトしか定義されていなかった場合と同じ挙動です)。
§インクルードの意味論: リソースの特定
概念的には、include 命令の引数となる引用符で囲まれた文字列は、その時パースされているファイルやその他のリソースと「隣接」していて、かつ同じ種類のリソースを識別するために使われます。この文字列や「隣接」の意味は、リソースの種類によって異なります。
設定のパーサーの実装によって、サポートしているリソースの種類に違いがある可能性があります。
Java Virtual Machine においては、 include 命令がインクルードされるものに「隣接」するリソースというものを見つけられなかった場合、パーサーの実装はクラスパス上に存在するリソースにフォールバックしてもよいことになっています。このルールのおかげで、ファイルシステムや URL 上の設定ファイルからクラスパス上のリソースを参照することができます。
Java のクラスパスにあるリソースについては、
- インクルードされたリソースは、インクルードした側のリソースを検索するのに使われたものと同じクラスローダーの
getResource()
メソッドを呼び出して検索されます。 - インクルードされたリソースの名前が絶対パス(‘/’ で始まる)である場合は、 ‘/’ を削除した上で
getResource()
に渡されます。 - インクルードされたリソースの名前が ‘/’ で始まっていない場合は、インクルードした側のリソースのディレクトリを先頭に繋げてから、
getResource()
に渡します。インクルードした側のリソース名が絶対パスでない(‘/’ で始まらない)、かつ「親ディレクトリ」(単なるパスの要素)もない場合は、インクルードされた相対的なリソース名がそのまま使われます。 getResource()
を URL の取得や、または そのURL からインクルードされたリソースの 相対 URL を取得するために使うのは間違いです。これは、クラスローダーの URL におけるパスと、getResource()
におけるパスの一対一のマッピングをするとは限らないからです。言い換えれば、前述の「隣接」の計算は、リソースの URL ではなく 名前を使って行うべきだということです。
ファイルシステム上のファイルについては、
- インクルードされたファイルが絶対パスの場合は、絶対パスのまま扱われて、ロードされます。
- インクルードされたファイルが相対パスの場合は、インクルードする側のファイルからの相対パスとして解釈されます。インクルードされるパスを解釈するときに、ファイルをパースしているプロセスのカレントディレクトリを使ってはなりません。
- ファイルが見つからない場合、クラスパス上のリソースにフォールバックします。そのとき、クラスパス上のリソース名の先頭にパッケージ名を追加することはせず、 “root” からの相対パスになります。これは、先頭の “/” が単に削除される(今回は root からの相対パスなので、このルールにより絶対パスも相対パスも同じリソースを指すことになります)ことを意味します。“/” をこのように扱う理由は、他のクラスアパスリソース内からリソースをインクルードするケースとの一貫性のためです。そのケースでは、リソース名は root からの相対パスではなく、 “/” をつけることで root からの相対パスとなります。
URL については、
- ファイルシステム上のファイルと Java のリソースの両方について、インクルードされた名前が URL(プロトコル名で始まる)の場合、名前はファイル名やリソース名ではなく URL として解釈・ロードされるでしょう。
- URL からロードされたファイルについては、「隣接」は URL のパス部分をパースして最終要素をインクルードされた名前に置き換えることで計算されます。
- file: プロトコルの URL が指定された場合は、単なるファイル名が指定された場合と全く同じ振る舞いになります。
§期間指定用フォーマット
期間指定に利用する単位時間を表す文字列は、大文字・小文字の区別あり、かつ全ての小文字で記述されなければなりません。以下の文字列がサポートされています。
ns
,nanosecond
,nanoseconds
us
,microsecond
,microseconds
ms
,millisecond
,milliseconds
s
,second
,seconds
m
,minute
,minutes
h
,hour
,hours
d
,day
,days
§サイズのバイト指定用フォーマット
バイトについては、以下の文字列がサポートされています。
B
,b
,byte
,bytes
10の累乗については、以下の文字列がサポートされています。
kB
,kilobyte
,kilobytes
MB
,megabyte
,megabytes
GB
,gigabyte
,gigabytes
TB
,terabyte
,terabytes
PB
,petabyte
,petabytes
EB
,exabyte
,exabytes
ZB
,zettabyte
,zettabytes
YB
,yottabyte
,yottabytes
2の累乗については、以下の文字列がサポートされています。
K
,k
,Ki
,KiB
,kibibyte
,kibibytes
M
,m
,Mi
,MiB
,mebibyte
,mebibytes
G
,g
,Gi
,GiB
,gibibyte
,gibibytes
T
,t
,Ti
,TiB
,tebibyte
,tebibytes
P
,p
,Pi
,PiB
,pebibyte
,pebibytes
E
,e
,Ei
,EiB
,exbibyte
,exbibytes
Z
,z
,Zi
,ZiB
,zebibyte
,zebibytes
Y
,y
,Yi
,YiB
,yobibyte
,yobibytes
§慣習としてのシステムプロパティによる上書き
アプリケーションの設定において、Java のシステムプロパティは設定ファイルに記述された設定を 上書き します。これにより、設定オプションをコマンドラインから指定することができます。