倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

ServiceLoader による Java SE 6 での RELAX NG 妥当性検証

以前の記事で、Java SE 6 で javax.xml.validation パッケージ内のクラスを用いて RELAX NG による妥当性検証を行う方法を見ました(一覧)。 今回は同様のことを java.util.ServiceLoader クラスを用いて 行う方法を見ていきます。 今回の方法を用いると、プロパティファイルを書き換えることで実装を変更することができるようになります。

設定ファイル


まず、javax.xml.validation.SchemaFactory の実装を指定するプロパティファイルを作成する必要があります。 これは ServiceLoader から読み込まれるプロパティファイルの配置場所、形式に合わせる必要があります。 これには「META-INF/services」フォルダ下*1に 「javax.xml.validation.SchemaFactory」ファイルを以下の内容で作成します(1行目はコメントなので不必要):

# javax.xml.validation.SchemaFactory
org.iso_relax.verifier.jaxp.validation.RELAXNGSchemaFactoryImpl

ファイルの内容は SchemaFactory の実装クラス名です*2

Java コード


次は Java コード。 Java コード内で行うことは次の2つ:

  1. ServiceLoader のインスタンスを取得する
  2. SchemaFactory のインスタンスを取得する

サンプルコードを見てみましょう。 メソッド getSchema() は、RELAX NG によるスキーマ定義ファイル (rngFile) を引数に取り、javax.xml.validation.Schema オブジェクトを返すメソッドです:

import java.util.ServiceLoader;
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.*;

public class RelaxngSchemaProvider{

    public Schema getSchema(String rngFile)throws Exception {
        
        // 1. ServiceLoader のインスタンスを取得する
        ServiceLoader<SchemaFactory> loader = ServiceLoader.load(SchemaFactory.class);  // ・・・(i)

        // 2. SchemaFactory のインスタンスを取得する
        SchemaFactory factory = null;
        for(SchemaFactory sf: loader){  // ・・・(ii)
            if(sf.isSchemaLanguageSupported(XMLConstants.RELAXNG_NS_URI)){  // ・・・(iii)
                factory = sf;
                break;
            }
        }
        
        if(factory == null)
            throw new RuntimeException(
                "実装が見つかりませんでした : " + SchemaFactory.class.getName());
        
        return factory.newSchema(new StreamSource(rngFile));
    }
}

幾つか注意を:

  • (i) ServiceLoader のインスタンスは、コンストラクタではなく ServiceLoader の static メソッド load() によって取得します。
  • (ii) ServiceLoader は Iterable インターフェースを実装しているので、拡張 for 文を用いてロードした要素を列挙することができます。
  • (iii) SchemaFactory#isSchemaLanguageSupported(String) メソッドによって、RELAX NG をサポートしている SchemaFactory オブジェクトをフィルタリングしています。 このメソッドの引数は RELAX NG 名前空間 http://relaxng.org/ns/structure/1.0 です。

もう少し行数を減らして書くことができて、以下のようになります:

import java.util.ServiceLoader;
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.*;

public class RelaxngSchemaProvider{

    public Schema getSchema(String rngFile)throws Exception {

        for(SchemaFactory factory: ServiceLoader.load(SchemaFactory.class)){
            if(sf.isSchemaLanguageSupported(XMLConstants.RELAXNG_NS_URI)){
                return factory.newSchema(new StreamSource(rngFile));
            }
        }
        
        throw new RuntimeException(
                "実装が見つかりませんでした : " + SchemaFactory.class.getName());
    }
}

SchemaFactory のインスタンスを返す方が良いかも知れません:

import java.util.ServiceLoader;
import javax.xml.XMLConstants;
import javax.xml.validation.*;

public class RelaxngSchemaFactoryProvider{

    public SchemaFactory getSchemaFactory()throws Exception {

        for(SchemaFactory factory: ServiceLoader.load(SchemaFactory.class)){
            if(sf.isSchemaLanguageSupported(XMLConstants.RELAXNG_NS_URI)){
                return factory;
            }
        }
        
        throw new RuntimeException(
                "実装が見つかりませんでした : " + SchemaFactory.class.getName());
    }
}

jre1.5 上でも動作するようにするためにはこんな感じでしょうか:

import java.util.ServiceLoader;
import javax.xml.XMLConstants;
import javax.xml.validation.*;

public class RelaxngSchemaFactoryProvider{

    public SchemaFactory getSchemaFactory(){

        try{
            return SchemaFactory.newInstance(XMLConstants.RELAXNG_NS_URI);
            
        }catch(IllegalArgumentException ex){
            
            for(SchemaFactory factory: ServiceLoader.load(SchemaFactory.class)){

                if(factory.isSchemaLanguageSupported(XMLConstants.RELAXNG_NS_URI))
                    return factory;
            }
            
            throw ex;
        }
    }
}

*1:Maven2 で開発を行っている場合は、「src/main/resouces/」フォルダ下に「META-INF/services」フォルダを作成してください。

*2:Java SE 5 と Java SE 6 でこの内容の形式に変更が生じたために javax.xml.validation パッケージの通常の使用方法がうまくいかなくなっているようです。