Configuration
Commons Configurationは、プロパティファイルにアクセスするタイプの企業向けソフトウェアに役立つコンポーネントです。この機能によって、アプリケーションはさまざまな場所から読み込まれる設定情報を統一された方法で参照できます。
このAPIには、他にも便利な機能があります。
- 各種のソース(JNDI、データベース、テキストファイル、XMLファイル、メモリ、システムプロパティ、アプレット、サーブレット初期化プロパティなど)に格納されている設定プロパティへの透過的アクセス
- リロードストラテジに基づくプロパティの自動リロード機能(このリロードストラテジはストラテジクラスの記述によってカスタマイズ可能)
- 変更されたプロパティの自動保存機能
- XPath風の構文を使ったXML設定情報へのアクセス
アプリケーションのモジュールコンテキストにorg.apache.commons.configuration.Configurationのインスタンスへの参照が含まれている場合は、アプリケーションに必要なプロパティをそのモジュール内のすべてのクラスで簡単に利用することができます。
Jakartaのサイトでは、このAPIが初歩的なサンプルと共にわかりやすく紹介されています。なお、プロパティを記憶域に自動保存する機能は、保存先がファイルの場合にのみサポートされます。
今回紹介するサンプルアプリケーションは、リモートのSNTP(Simple Network Time Protocol)サーバーとの間でシステム時刻の同期をとるものです。ただし、実際にSNTPサーバーに対して同期処理を行うメソッドは実装されておらず、Commons Configurationのデモ用スケルトンコードになっています。このサンプルの完全なソースコードは「in.co.narayanan.commons.config」パッケージに含まれています。
リスト10は、このサンプルアプリケーションで設定が必要なプロパティです。ここで、プロパティの内容を簡単に紹介しておきます。
syncintervalhours― 同期の間隔を表す整数型プロパティenablesync― 同期処理のオンオフを表す論理型プロパティ<name>― このツールが接続するSNTPサーバーの名前を表す文字列配列のプロパティlastsync― 最後に成功した同期のタイムスタンプを表す文字列(同期完了のたびに記録される)
application.properties syncintervalhours=12 enablesync=true #This property is set after the first run lastsync=<timestamp in milliseconds> sntpservers.xml <servers> <name>server1</name> <name>server2</name> <name>server3</name> </servers>
リスト11は、Commons Configurationを利用して、このサンプルアプリケーションの設定ファイルにアクセスするためのインターフェイスです。
public interface IConfiguration { void setStringConfig(String nameSpace, String key, String value); String getStringConfig(String nameSpace, String key); String[] getStringArrayConfig(String nameSpace, String key); void setBooleanConfig( String nameSpace, String key, boolean value); boolean getBooleanConfig(String nameSpace, String key); void setIntConfig(String nameSpace, String key, int value); int getIntConfig(String nameSpace, String key); }
このインターフェイスによって、Commons Configuration APIの機能にアクセスしようとするアプリケーションのクラスを分離することができます。このインターフェイスには、文字列、整数型、論理型、文字列配列のプロパティを取得/設定するためのメソッドが用意されています。
リスト12のコードは、このインターフェイスを実装したApplicationConfigurationクラスの一部です。
/** * Get the config property value from configuration storage. The * properties are loaded if it is not already loaded for the * given namespace. * * @param nameSpace Name of the configuration group * @param key Unique key within the namespace * @return Sring value of the property. Null if the property * is not found or if the configuration cannot be loaded */ public String getStringConfig(String nameSpace, String key) { Configuration config = getConfiguration(nameSpace); if(config != null) { return config.getString(key); } return null; } private synchronized Configuration getConfiguration (String nameSpace) { Configuration config = configs.get(nameSpace); if(config == null) { try { if("application".equals(nameSpace)) { config = new PropertiesConfiguration( "application.properties"); } else if("sntpservers".equals(nameSpace)) { config = new XMLConfiguration( getClass().getResource("sntpservers.xml")); } configs.put(nameSpace, config); } catch (ConfigurationException e) { System.out.println("Unable to load the configuration:" + e.getMessage()); } } return config; }
太字の行は、取得メソッドが呼び出されたときにテキストファイルおよびXMLファイルからプロパティを読み込むコードです。Configurationオブジェクトは、nameSpaceごとに内部マップに格納されます。これによって、プロパティどうしを混同することなく、設定情報の読み込みと格納が独立して行われます。種類の異なるプロパティが別々の場所から読み込まれる可能性がある場合にはorg.apache.commons.configuration.CompositeConfigurationクラスを使用できます(詳細については、こちらを参照)。
リスト13に、この設定を記憶域に再保存する方法を示します。
private synchronized void save(Configuration config) { if(config instanceof FileConfiguration) { try { // If reloading strategy is set, then the properties // doesn't get saved ((FileConfiguration)config).save(); } catch (ConfigurationException e) { System.out.println("Config not saved. Error while saving." + e.getMessage()); } } else { System.out.println("Config not saved. Not supported."); } }
Configurationのインスタンスに設定しているときに、プロパティをテキストファイルに保存すると問題が生じました(プロパティファイルの用法の詳細については、こちらを参照)。リスト14に、アプリケーションのクラス内でこのコンテキストを通して設定プロパティにアクセスする方法を示します。
void syncTime() { if(shouldSyncNow()) { context.getLogger().log(Level.INFO, "Sync started"); sync(); recordSyncTime(); } else { context.getLogger().log(Level.INFO, "Sync skipped. Time not yet arrived"); } } private boolean shouldSyncNow() { boolean enableSync = context.getConfiguration() .getBooleanConfig("application", "enablesync"); context.getLogger().log(Level.INFO, "enablesync:" + enableSync); if(enableSync) { if(getCurrentTime() > readLastSync() + getInterval()) { return true; } } return false; } /** * Use SNTP API to sync the time. */ private void sync() { String servers[] = context.getConfiguration() .getStringArrayConfig("sntpservers", "name"); for(String server : servers) { context.getLogger().log(Level.INFO, "Time Server:" + server); } // Use the list of server to sync the system time. // sntp API for java can be used here context.getLogger().log(Level.INFO, "Syncing time.."); }
このコードでは、必要なプロパティを取り出すために、ロガーへの参照とコンフィギュレーションクラスを取得しています。各プロパティの使い方については、in.co.narayanan.commons.config.SyncTimeクラスを調べるとよいでしょう。
リスト15は、SyncTimeクラスの動作を確認するためのテストケースクラスです。
public class TestSyncTime extends TestCase { public void testSyncTime() { Logger consoleLogger = Logger.getAnonymousLogger(); ApplicationContext context = new ApplicationContext(); context.setLogger(consoleLogger); context.setConfiguration(new ApplicationConfiguration()); SyncTime sync = new SyncTime(context); sync.syncTime(); sync.syncTime(); } }
このテストケースクラスでは、Javaのロガーとコンフィギュレーションクラスを初期化し、これらをアプリケーションコンテキストにセットして、SyncTimeクラスに依存関係を与えています。
Commons Configurationを使えば、さまざまな場所で提供されている外部プロパティにアクセスできます。これは確かに便利なコンポーネントですが、企業向けソフトウェアの場合は、JNDIやデータベースを利用するディレクトリサーバーにプロパティを自動保存する必要があるため、改良の余地があるでしょう。しかし、単純なテキストファイルやXMLファイル上でプロパティを扱うのであれば、このフレームワークの利用を検討する価値があります。
