20 stycznia 2006

Wprowadzenie do Commons Configuration

Pod adresem http://jakarta.apache.org/commons/configuration znajduje się ciekawy projekt Commons Configuration do zarządzania konfiguracją aplikacji.

Istnieje wiele technik obsługi konfiguracji, ale to, co przykuło moją uwagę w Commons Configuration, to wyjątkowa prostota użycia. Żadnych wstępnych wymagań, ograniczeń i dodatkowo różnorodność źródeł skąd pobierane są dane konfiguracyjne - wszystko to sprawia, że korzystanie z projektu sprowadza się do następujących kroków:
  • Umieszczenia biblioteki Commons Configuration - commons-configuration-1.2.jar - do CLASSPATH. Oczywiście jak z każdym projektem z Jakarta Commons należy dodać jeszcze inne biblioteki zależne - Commons BeanUtils, Commons Collections, Commons Digester, Commons Lang i w końcu Commons Logging.
  • Stworzeniu pliku konfiguracyjnego - w XML, jako plik w formacie zgodnym z java.lang.Properties - bądź konfiguracja drzewa JNDI czy bazy danych. W rachubę wchodzi również nie definowanie niczego. Skorzystamy wtedy z konfiguracji podawanej z linii poleceń. Wybierzmy najpierw plik XML.
    <nieistotnyznacznik>
    <bazadanych typ="MySQL">
    <alias>test</alias>
    <uzytkownik>jlaskowski</uzytkownik>
    <haslo>********</haslo>
    </bazadanych>
    <bazadanych typ="Oracle">
    <alias>prod</alias>
    <uzytkownik>jacek.laskowski</uzytkownik>
    <haslo>inne</haslo>
    </bazadanych>
    </nieistotnyznacznik>
  • Następny krok to napisanie programu do wczytywania danych konfiguracyjnych - pl.org.laskowski.MyConfigurationService.
    package pl.org.laskowski;

    import org.apache.commons.configuration.Configuration;
    import org.apache.commons.configuration.ConfigurationException;
    import org.apache.commons.configuration.XMLConfiguration;

    public class MyConfigurationService {

    public static void main(String[] args) {
    try {
    Configuration cfg = new XMLConfiguration("config-service.xml");

    String dbTyp = cfg.getString("bazadanych[@typ]");
    String dbAlias = cfg.getString("bazadanych.alias");
    String dbUser = cfg.getString("bazadanych.uzytkownik");
    String dbPassword = cfg.getString("bazadanych.haslo");

    String dbUrl = dbUser + ":" + dbPassword + "@" + dbAlias;
    System.out.println("Bazy danych " + dbTyp + " o adresie " + dbUrl);
    } catch (ConfigurationException ce) {
    ce.printStackTrace();
    }
    }
    }
  • Uruchomienie programu.
    $ java pl.org.laskowski.MyConfigurationService
    Bazy danych MySQL o adresie jlaskowski:********@test
Pamiętajmy, że plik konfiguracyjny config-service.xml musi znajdować się w katalogu bieżącym, w którym uruchamiamy aplikację, bądź w katalogu, czy pliku zawartym w CLASSPATH.

Powyższe kroki można zastosować do dowolnej konfiguracji, jednakże przy zmianie z pliku XML na inne źródło należy pamiętać o zmianie klasy obsługującej źródło. Źródła danych dziedziczą z abstrakcyjnej klasy org.apache.commons.configuration.AbstractConfiguration i odnalezienie wszystkich standardowo dostępnych klas i ich miejsce składowania danych znajdziemy w sekcji Direct Known Subclasses.

Często stosowanym rozwiązaniem przy obsłudze plików konfiguracyjnych jest przesłanianie konfiguracji i łączenie wielu źródeł. Projekt Commons Configuration dostarcza klasę org.apache.commons.configuration.CompositeConfiguration, która została stworzona właśnie w tym celu - do łączenia źródeł danych i ustawiania ich znaczenia (ważności). Pierwsze wystąpienie poszukiwanej zmiennej ma najwyższy priorytet i dalsze źródła nie są odpytywane.

Zmodyfikujmy nasz przykład i stwórzmy nową klasę pl.org.laskowski.MyCompositeConfigurationService - tak, aby skorzystać z możliwości dostarczanych przez o.a.c.c.CompositeConfiguration, a właściwie o.a.c.c.ConfigurationFactory
package pl.org.laskowski;

import java.util.Iterator;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.ConfigurationFactory;

public class MyCompositeConfigurationService {

public static void main(String[] args) {
try {
ConfigurationFactory factory = new ConfigurationFactory("composite-config.xml");
Configuration cfg = factory.getConfiguration();

// Uruchom przykład z parametrem -Denv=prod
boolean prodEnv = "prod".equals(cfg.getString("env"));

String dbAlias;
String dbUser;
String dbPassword;
if (!prodEnv) {
// Środowisko testowe
dbAlias = cfg.getString("bazadanych(0).alias");
dbUser = cfg.getString("bazadanych(0).uzytkownik");
dbPassword = cfg.getString("bazadanych(0).haslo");
} else {
// Środowisko produkcyjne
dbAlias = cfg.getString("bazadanych(1).alias");
dbUser = cfg.getString("bazadanych(1).uzytkownik");
dbPassword = cfg.getString("bazadanych(1).haslo");
}

String dbUrl = dbUser + ":" + dbPassword + "@" + dbAlias;
System.out.println("Podłączam się do bazy danych " + dbUrl);

System.out.println("Wyświetlam dostępne klucze konfiguracyjne:");
Iterator it = cfg.getKeys();
while (it.hasNext()) {
System.out.println("\t" + it.next());
}
} catch (ConfigurationException ce) {
ce.printStackTrace();
}
}
}
W celu uruchomienia naszego przykładu konieczne jest zbudowanie nowego pliku konfiguracji wymaganego przez ConfigurationFactory - composite-config.xml - który bedzie instruował z jakich źródeł danych będzie korzystała nasza aplikacja.
<configuration>
<system/>
<xml fileName="config-service.xml"/>
</configuration>
Konfiguracja fabryki wymaga pliku o ustalonej strukturze (która nawiasem mówiąc jest obsługiwana przez inny projekt Commons Digester). Więcej informacji o strukturze znajdziesz na stronie projektu. Na początek znaczniki system i xml są wystarczające.

UWAGA: plik composite-config.xml musi znajdować się w katalogu bieżącym bądź w katalogu, czy pliku zawartym w CLASSPATH. Powyższy przykład łączy dwa źródła danych konfigurację podawaną na linii poleceń i z pliku XML.

Sercem działania złożonej konfiguracji Commons Configuration jest klasa-fabryka - org.apache.commons.configuration.ConfigurationFactory. Służy ona do tworzenia konfiguracji, która będzie odpytywała wszystkie zdefiniowane konfiguracje o parametr i wygrywa pierwsze wystapienie. Musimy zatem pamiętać o kolejności wpisów poszczególnych konfiguracji w pliku konfiguracyjnym dla ConfigurationFactory.

Uruchomienie przykładu demonstruje kolejne możliwości Commons Configuration:
$ java -Denv=prod pl.org.laskowski.MyCompositeConfigurationService

Podłączam się do bazy danych jacek.laskowski:inne@prod
Wyświetlam dostępne klucze konfiguracyjne:
java.runtime.name
sun.boot.library.path
java.vm.version
java.vm.vendor
java.vendor.url
path.separator
java.vm.name
file.encoding.pkg
env
user.country
sun.os.patch.level
java.vm.specification.name
user.dir
java.runtime.version
java.awt.graphicsenv
java.endorsed.dirs
os.arch
java.io.tmpdir
line.separator
java.vm.specification.vendor
user.variant
os.name
sun.jnu.encoding
java.library.path
java.specification.name
java.class.version
sun.management.compiler
os.version
user.home
user.timezone
java.awt.printerjob
file.encoding
java.specification.version
java.class.path
user.name
java.vm.specification.version
java.home
sun.arch.data.model
user.language
java.specification.vendor
awt.toolkit
java.vm.info
java.version
java.ext.dirs
sun.boot.class.path
java.vendor
file.separator
java.vendor.url.bug
sun.io.unicode.encoding
sun.cpu.endian
sun.desktop
sun.cpu.isalist
bazadanych[@typ]
bazadanych.alias
bazadanych.uzytkownik
bazadanych.haslo
Parametr env jest opcjonalny w naszej aplikacji. Jak widać wszystkie parametry systemowe JVM są również zawarte w konfiguracji poprzez wyspecyfikowanie <system/> w pliku konfiguracyjnym dla CompositeConfiguration.

Podczas uruchomienia klasy p.o.l.MyCompositeConfigurationService wyświetlona została pewna wartość bazadanych[@typ]. Jest to właśnie sposób na odwołanie się do atrybutów (w naszym przypadku typ) znacznika (u nas bazadanych).

Inną ciekawostką, której wykorzystanie można zobaczyć w przykładzie p.o.l.MyCompositeConfigurationService jest odwoływanie się do zmiennych występujących wielokrotnie w pliku konfiguracyjnym. W przykładzie sprawdza się wartość zmiennej env i w zależności od niej odwołujemy się do pierwszego wystąpienia sekcji bazadanych (bazadanych(0)) bądź kolejnego (bazadanych(1)). Numeracja zaczyna się od 0.

Komentarze, spostrzeżenia mile widziane. Skorzystaj z możliwości komentowania artykułu, poniżej.