/*
* Copyright (c) 2007 NTT DATA Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jp.terasoluna.fw.util;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* プロパティファイルからプロパティを取得するユーティリティクラス。
* <p>
* デフォルトでは ApplicationResources ファイルを読み込むが、 ApplicationResources ファイルで以下のように指定することにより、 他のプロパティファイルを追加で読み込むこともできる。
* </p>
* <strong>ApplicationResources.propertiesの設定書式</strong><br>
* <code><pre>
* add.property.file.1 = <i><追加プロパティファイル名1></i>
* add.property.file.2 = <i><追加プロパティファイル名2></i>
* ...
* </pre></code>
* <p>
* また、プロパティファイルを個別に指定した以下の機能がある
* <ol>
* <li>部分キー検索による値取得</li>
* <li>部分キー取得</li>
* </ol>
* 詳細は、 getPropertyNames() メソッド、 getPropertiesValues() メソッドを参照。
* </p>
*/
public class PropertyUtil {
/**
* ログクラス。
*/
private static Log log = LogFactory.getLog(PropertyUtil.class);
/**
* デフォルトプロパティファイル名。
*/
public static final String DEFAULT_PROPERTY_FILE = "ApplicationResources.properties";
/**
* 追加プロパティファイル指定のプリフィックス。
*/
private static final String ADD_PROPERTY_PREFIX = "add.property.file.";
/**
* プロパティファイルの拡張子。
*/
private static final String PROPERTY_EXTENSION = ".properties";
/**
* プロパティのキーと値を保持するオブジェクト。
*/
private static TreeMap<String, String> props = new TreeMap<String, String>();
/**
* 読み込んだプロパティファイル名リスト。
*/
private static Set<String> files = new HashSet<String>();
/**
* クラスロード時にプロパティファイルを読み込み初期化する。
*/
static {
StringBuilder key = new StringBuilder();
load(DEFAULT_PROPERTY_FILE);
if (props != null) {
for (int i = 1;; i++) {
key.setLength(0);
key.append(ADD_PROPERTY_PREFIX);
key.append(i);
String path = getProperty(key.toString());
if (path == null) {
break;
}
addPropertyFile(path);
}
}
overrideProperties();
}
/**
* 指定されたプロパティファイルを読み込む。
* <p>
* 読み込まれたプロパティファイルは、 以前読み込んだ内容に追加される。
* </p>
* @param name プロパティファイル名
*/
private static void load(String name) {
StringBuilder key = new StringBuilder();
Properties p = readPropertyFile(name);
for (@SuppressWarnings("rawtypes") Map.Entry e : p.entrySet()) {
// 読み込んだものをすべてpropsに追加する。
props.put((String) e.getKey(), (String) e.getValue());
}
if (p != null) {
for (int i = 1;; i++) {
key.setLength(0);
key.append(ADD_PROPERTY_PREFIX);
key.append(i);
String addfile = p.getProperty(key.toString());
if (addfile == null) {
break;
}
String path = getPropertiesPath(name, addfile);
addPropertyFile(path);
}
}
}
/**
* 指定されたプロパティファイルを読み込む。
* <p>
* 以前読み込んだ内容に追加される。
* </p>
* @param name プロパティファイル名
* @return プロパティリスト
*/
private static Properties readPropertyFile(String name) {
// カレントスレッドのコンテキストクラスローダを使用すると
// WEB-INF/classesのプロパティファイルを読むことができない場合がある。
// だがJNLPでリソースを取得するには、メインスレッドのコンテキスト
// クラスローダを利用しなければならないため両方を併用する。
InputStream is = Thread.currentThread().getContextClassLoader()
.getResourceAsStream(name);
if (is == null) {
is = PropertyUtil.class.getResourceAsStream("/" + name);
}
Properties p = new Properties();
try {
try {
p.load(is);
files.add(name);
} catch (NullPointerException e) {
System.err.println("!!! PANIC: Cannot load " + name + " !!!");
System.err.println(ExceptionUtil.getStackTrace(e));
} catch (IOException e) {
System.err.println("!!! PANIC: Cannot load " + name + " !!!");
System.err.println(ExceptionUtil.getStackTrace(e));
}
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
log.error("", e);
}
}
return p;
}
/**
* プロパティファイルから読み込まれた内容を、 コマンドラインの "-D" オプション等で指定された システムプロパティで上書きする。
*/
private static void overrideProperties() {
Enumeration<String> enumeration = Collections.enumeration(props
.keySet());
while (enumeration.hasMoreElements()) {
String name = enumeration.nextElement();
String value = System.getProperty(name);
if (value != null) {
props.put(name, value);
}
}
}
/**
* 指定されたプロパティファイルを追加で読み込む。
* <p>
* 複数回呼び出しても1度しか読み込まれない。 プロパティファイル名の ".properties" は省略できる。
* </p>
* @param name プロパティファイル名
*/
public static synchronized void addPropertyFile(String name) {
if (!name.endsWith(PROPERTY_EXTENSION)) {
StringBuilder nameBuf = new StringBuilder();
nameBuf.append(name);
nameBuf.append(PROPERTY_EXTENSION);
name = nameBuf.toString();
}
if (!files.contains(name)) {
load(name);
}
}
/**
* 指定されたキーのプロパティを取得する。
* <p>
* 参照値が "@" 付きの文字列である時、間接キーとみなし もう一度 "@" を外した文字列をキーとして検索する。 <code>key=@key</code>
* という形で定義されている時、無限ループを回避するため、 <code>@key</code>を直接返却する。 先頭が "@" である文字列を値として設定する際には 先頭の "@@" を
* "@" に変更しプロパティファイル に設定する事で、間接キー検索の機能を回避できる。
* </p>
* @param key プロパティのキー
* @return 指定されたキーのプロパティの値
*/
public static String getProperty(String key) {
String result = props.get(key);
// (キー)=@(キー)の時、無限ループ回避
if (result != null && result.equals("@" + key)) {
return result;
}
// @@の場合は間接キー検索を回避し、@と見なす。
if (result != null && result.startsWith("@@")) {
return result.substring(1);
}
if (result != null && result.startsWith("@")) {
result = getProperty(result.substring(1));
}
return result;
}
/**
* 指定されたキーのプロパティを取得する。
* <p>
* プロパティが見つからなかった場合には、指定されたデフォルトが返される。
* </p>
* @param key プロパティのキー
* @param defaultValue プロパティのデフォルト値
* @return 指定されたキーのプロパティの値
*/
public static String getProperty(String key, String defaultValue) {
String result = props.get(key);
if (result == null) {
return defaultValue;
}
return result;
}
/**
* プロパティのすべてのキーのリストを取得する。
* @return プロパティのすべてのキーのリスト
*/
public static Enumeration<String> getPropertyNames() {
return Collections.enumeration(props.keySet());
}
/**
* 指定されたプリフィックスから始まるキーのリストを取得する。
* @param keyPrefix キーのプリフィックス
* @return 指定されたプリフィックスから始まるキーのリスト
*/
public static Enumeration<String> getPropertyNames(String keyPrefix) {
Map<String, String> map = props.tailMap(keyPrefix);
Iterator<String> iter = map.keySet().iterator();
while (iter.hasNext()) {
String name = iter.next();
if (!name.startsWith(keyPrefix)) {
return Collections.enumeration(props.subMap(keyPrefix, name)
.keySet());
}
}
return Collections.enumeration(map.keySet());
}
/**
* プロパティファイル名、部分キー文字列を指定することにより 値セットを取得する。
* @param propertyName プロパティファイル名
* @param keyPrefix 部分キー文字列
* @return 値セット
*/
public static Set<?> getPropertiesValues(String propertyName,
String keyPrefix) {
Properties localProps = loadProperties(propertyName);
if (localProps == null) {
return null;
}
Enumeration<String> keyEnum = getPropertyNames(localProps, keyPrefix);
if (keyEnum == null) {
return null;
}
return getPropertiesValues(localProps, keyEnum);
}
/**
* プロパティを指定し、部分キープリフィックスに合致する キー一覧を取得する。
* @param localProps プロパティ
* @param keyPrefix 部分キープリフィックス
* @return 部分キープリフィックスに合致するキー一覧
*/
public static Enumeration<String> getPropertyNames(Properties localProps,
String keyPrefix) {
if (localProps == null || keyPrefix == null) {
return null;
}
Collection<String> matchedNames = new ArrayList<String>();
Enumeration<?> propNames = localProps.propertyNames();
while (propNames.hasMoreElements()) {
String name = (String) propNames.nextElement();
if (name.startsWith(keyPrefix)) {
matchedNames.add(name);
}
}
return Collections.enumeration(matchedNames);
}
/**
* キー一覧に対し、プロパティより取得した値を取得する。
* @param localProps プロパティ
* @param propertyNames キーの一覧
* @return 値セット
*/
public static Set<String> getPropertiesValues(Properties localProps,
Enumeration<String> propertyNames) {
if (localProps == null || propertyNames == null) {
return null;
}
Set<String> retSet = new HashSet<String>();
while (propertyNames.hasMoreElements()) {
retSet.add(localProps.getProperty(propertyNames.nextElement()));
}
return retSet;
}
/**
* 指定したプロパティファイル名で、プロパティオブジェクトを取得する。
* @param propertyName プロパティファイル
* @return プロパティオブジェクト
*/
public static Properties loadProperties(String propertyName) {
// propertyNameがnullまたは空文字の時、nullを返却する。
if (propertyName == null || "".equals(propertyName)) {
return null;
}
Properties retProps = new Properties();
StringBuilder resourceName = new StringBuilder();
resourceName.append(propertyName);
if (!propertyName.endsWith(PROPERTY_EXTENSION)) {
resourceName.append(PROPERTY_EXTENSION);
}
// カレントスレッドのコンテキストクラスローダを使用すると
// WEB-INF/classesのプロパティファイルを読むことができない場合がある。
// だがJNLPでリソースを取得するには、メインスレッドのコンテキスト
// クラスローダを利用しなければならないため両方を併用する。
InputStream is = Thread.currentThread().getContextClassLoader()
.getResourceAsStream(resourceName.toString());
if (is == null) {
is = PropertyUtil.class.getResourceAsStream("/" + propertyName
+ PROPERTY_EXTENSION);
}
try {
retProps.load(is);
} catch (NullPointerException npe) {
log.warn("*** Can not find property-file [" + propertyName
+ ".properties] ***", npe);
retProps = null;
} catch (IOException ie) {
log.error("", ie);
retProps = null;
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException ie) {
log.error("", ie);
retProps = null;
}
}
return retProps;
}
/**
* プロパティファイルの読み出しパスを取得する。 プロパティファイルを追加を行ったプロパティファイルが 存在するディレクトリをベースにして追加されたプロパティファイルを読む為、 プロパティファイルの読み出しディレクトリを取得する。
* @param resource 追加指定を記述しているプロパティファイル
* @param addFile 追加するプロパティファイル
* @return 追加するプロパティファイルの読み出しパス
*/
private static String getPropertiesPath(String resource, String addFile) {
File file = new File(resource);
String dir = file.getParent();
if (dir != null) {
StringBuilder dirBuf = new StringBuilder();
dirBuf.setLength(0);
dirBuf.append(dir);
dirBuf.append(File.separator);
dir = dirBuf.toString();
} else {
dir = "";
}
StringBuilder retBuf = new StringBuilder();
retBuf.setLength(0);
retBuf.append(dir);
retBuf.append(addFile);
return retBuf.toString();
}
}