/*
 * ConfigurationLoader.java
 * Copyright (C) 2008 Cyber Beans Corporation. All rights reserved.
 */
package jp.co.cybec.cb3.util;

import java.io.IOException;
import java.net.URL;

import javax.xml.parsers.ParserConfigurationException;

import jp.co.cybec.cb3.exception.ConfigFileDefinitionException;
import jp.co.cybec.cb3.exception.ConfigFileIOException;
import jp.co.cybec.cb3.exception.ConfigFileNotFoundException;
import jp.co.cybec.cb3.exception.ConfigFileParseException;
import jp.co.cybec.cb3.util.logging.ErrorCodeConstant;
import jp.co.cybec.cb3.util.logging.LogMessageConstant;
import jp.co.cybec.cb3.util.logging.LogMessageFactory;

import org.apache.commons.digester.Digester;
import org.apache.commons.digester.xmlrules.DigesterLoader;
import org.apache.commons.digester.xmlrules.XmlLoadException;
import org.xml.sax.SAXException;

/**
 * 設定ファイルを読み込むためのユーティリティクラス。<br>
 * digesterを使用して設定ファイルを読み込みます。
 * @author Daisuke Suga
 */
public class ConfigurationLoader {

	/** メッセージ */
	private static final LogMessageFactory MESSAGE = new LogMessageFactory(
			LogMessageConstant.CB3_MESSAGE_FILENAME);

	/** 定義ファイルの拡張子 */
	private static final String EXTENSION_XML = "xml";

	/**
	 * 設定ファイルを読み込みます。
	 *
	 * @param rule digesterのルールファイル
	 * @param config 設定ファイル
	 * @throws ConfigFileNotFoundException 設定ファイルが存在しない場合に発生します。
	 * @throws ConfigFileDefinitionException 設定ファイルの間違いにより発生します。
	 * @throws ConfigFileIOException 設定ファイル読み込み時のIOエラーにより発生します。
	 * @throws ConfigFileParseException 設定ファイルのXMLファイル解析が失敗した時に発生します。
	 * @return 読み込み結果。オブジェクトの型は、ルールファイルに定義されます。
	 */
	public static Object load(String rule, String config) throws ConfigFileNotFoundException,
			ConfigFileDefinitionException, ConfigFileIOException, ConfigFileParseException {

		String ruleFile = toFileName(rule);
		String configFile = toFileName(config);

		// digesterのルールファイルのURLを取得します。
		URL ruleUrl = ConfigurationLoader.class.getClassLoader().getResource(ruleFile);
		if (ruleUrl == null) {
			throw new ConfigFileNotFoundException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.UTIL_001, LogMessageConstant.CB3_UTIL_006, rule));
		}

		// 設定ファイルのURLを取得します。
		URL configUrl = ConfigurationLoader.class.getClassLoader().getResource(configFile);
		if (configUrl == null) {
			throw new ConfigFileNotFoundException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.UTIL_002, LogMessageConstant.CB3_UTIL_006, config));
		}

		// 設定ファイルを読み込みます。
		return load(ruleUrl, configUrl);
	}

	/**
	 * 設定ファイルを読み込みます。
	 *
	 * @param rule digesterのルールファイル
	 * @param config 設定ファイル
	 * @throws ConfigFileDefinitionException 設定ファイル内の設定が間違っている場合に発生します。
	 * @throws ConfigFileIOException 設定ファイル読み込み時のIOエラーにより発生します。
	 * @throws ConfigFileParseException 設定ファイルのXMLファイル解析が失敗した時に発生します。
	 * @return 読み込み結果。オブジェクトの型は、ルールファイルに定義されます。
	 */
	public static Object load(URL rule, URL config) throws ConfigFileDefinitionException,
			ConfigFileIOException, ConfigFileParseException {

		try {
			// Digesterの作成と設定
			Digester digester = DigesterLoader.createDigester(rule);
			digester.setNamespaceAware(true);
			digester.setValidating(true);
			digester.setUseContextClassLoader(true);
			digester.setFeature("http://xml.org/sax/features/validation", true);
			digester.setFeature("http://apache.org/xml/features/validation/schema",true);
			digester.setFeature("http://xml.org/sax/features/namespaces",true);

			// 妥当性検証エラーハンドルを設定
			ValidationErrorHandler myErrorHandler = new ValidationErrorHandler();
			digester.setErrorHandler(myErrorHandler);

			// XMLスキーマエンティティを設定
			ValidationEntityResolver schemaResolver = new ValidationEntityResolver();
			schemaResolver.setXmlFile(config);
			digester.setEntityResolver(schemaResolver);

			// XMLファイルをパーサする
			Object object = digester.parse(config);

			// 妥当性検証がエラーの場合
			if (!myErrorHandler.isValid()) {
				throw new ConfigFileDefinitionException(MESSAGE.getErrorMessage(
						ErrorCodeConstant.UTIL_003, LogMessageConstant.CB3_UTIL_002, config));
			}

			// パーサに失敗した場合
			if (object == null) {
				throw new ConfigFileDefinitionException(MESSAGE.getErrorMessage(
						ErrorCodeConstant.UTIL_004, LogMessageConstant.CB3_UTIL_003, config));
			}

			return object;

		} catch (IOException e) {
			throw new ConfigFileIOException(MESSAGE.getErrorMessage(ErrorCodeConstant.UTIL_005,
					LogMessageConstant.CB3_UTIL_003, config), e);
		} catch (SAXException e) {
			throw new ConfigFileParseException(MESSAGE.getErrorMessage(ErrorCodeConstant.UTIL_006,
					LogMessageConstant.CB3_UTIL_004, config), e);
		} catch (XmlLoadException e) {
			throw new ConfigFileParseException(MESSAGE.getErrorMessage(ErrorCodeConstant.UTIL_007,
					LogMessageConstant.CB3_UTIL_004, config), e);
		} catch (ParserConfigurationException e) {
			throw new ConfigFileParseException(MESSAGE.getErrorMessage(ErrorCodeConstant.UTIL_008,
					LogMessageConstant.CB3_UTIL_004, config), e);
		}
	}

	/**
	 * アクセッサのキー名からファイル名に変換して返却します。
	 *
	 * @param value アクセッサのキー名
	 * @return ファイル名（キー名に拡張子を付加）
	 */
	private static String toFileName(String value) {

		String filename = value;

		// 拡張子を取得する
		String extension = null;
		int point = filename.lastIndexOf(".");
		if (point != -1) {
			extension = filename.substring(point + 1);
		}

		// 拡張子がない場合や「xml」でない場合場合は、「.xml」を付加する
		if (extension == null || !extension.equalsIgnoreCase(EXTENSION_XML)) {
			filename = filename + "." + EXTENSION_XML;
		}

		return filename;
	}
}
