/*
 * XMLFileReaderImpl.java
 * Copyright (C) 2008 Cyber Beans Corporation. All rights reserved.
 */
package jp.co.cybec.cb3.accessor.fileaccess.xml;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

import jp.co.cybec.cb3.accessor.AccessorsLifeCycle;
import jp.co.cybec.cb3.exception.AccessorAlreadyOpenedException;
import jp.co.cybec.cb3.exception.AccessorNotOpenException;
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.exception.DataFileIOException;
import jp.co.cybec.cb3.exception.DataFileNotFoundException;
import jp.co.cybec.cb3.exception.DataFileParseException;
import jp.co.cybec.cb3.util.ConfigurationLoader;
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.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;

/**
 * XMLファイルの読み込みクラス。
 * @author Daisuke Suga
 */
public class XMLFileReaderImpl implements XMLFileReader, AccessorsLifeCycle {

	/** ログ */
	private static final Log LOG = LogFactory.getLog(XMLFileReaderImpl.class);

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

	/**
	 * XML定義ファイルのdigesterルールのファイルパス。
	 */
	private static final String XML_FILE_READER_RULE_FILE_NAME = "XMLFileReaderRule.xml";

	/**
	 * アクセッサのキー名。
	 */
	private String key = null;

	/**
	 * XML定義ファイル。
	 */
	private XMLFileReaderConfig config = null;

	/**
	 * ファイルのオープン状態を保持するフラグ。
	 */
	private boolean isOpen = false;

	/**
	 * XMLファイルのBufferedInputStream。
	 */
	private BufferedInputStream stream = null;

	/**
	 * XMLファイルのXmlObject。
	 */
	private XmlObject xml = null;

	/**
	 * コンストラクタ。
	 */
	public XMLFileReaderImpl() {
		super();
	}

	/**
	 * {@inheritDoc}
	 */
	public XMLPath selectPath(String path) throws AccessorNotOpenException {

		// デバッグログ
		if (LOG.isDebugEnabled()) {
			LOG.debug(MESSAGE.getMessage(LogMessageConstant.CB3_ACCESSOR_FILEACCESS_XML_007, key,
					path));
		}

		// ファイルがオープンされていない場合、例外をスローします。
		if (!isOpen) {
			AccessorNotOpenException ex = new AccessorNotOpenException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_013,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_001, key));
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

		// XMLPathを作成して返却します。
		return new XMLPath(key, xml, path);

	}

	/**
	 * {@inheritDoc}
	 */
	public void open() throws AccessorAlreadyOpenedException, DataFileNotFoundException,
			DataFileParseException, DataFileIOException {

		// すでにopenされている場合、例外をスローします。
		if (isOpen) {
			AccessorAlreadyOpenedException ex = new AccessorAlreadyOpenedException(MESSAGE
					.getErrorMessage(ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_014,
							LogMessageConstant.CB3_ACCESSOR_FILEACCESS_002, key));
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

		// ファイルパスが設定されていない場合は例外をスローします。
		if (config.getFilepath() == null || config.getFilepath().length() == 0) {
			DataFileNotFoundException ex = new DataFileNotFoundException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_041,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_018));
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

		try {

			// XMLファイルをオープンします。
			File file = new File(config.getFilepath());
			stream = new BufferedInputStream(new FileInputStream(file));
			xml = XmlObject.Factory.parse(stream);
			isOpen = true;

		} catch (FileNotFoundException e) {
			DataFileNotFoundException ex = new DataFileNotFoundException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_015,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_003, config.getFilepath()), e);
			LOG.info(ex.getMessage(), ex);
			throw ex;
		} catch (IOException e) {
			DataFileIOException ex = new DataFileIOException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_016,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_004, config.getFilepath()), e);
			LOG.info(ex.getMessage(), ex);
			throw ex;
		} catch (XmlException e) {
			DataFileParseException ex = new DataFileParseException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_017,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_XML_001, config.getFilepath()), e);
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

	}

	/**
	 * {@inheritDoc}
	 */
	public void open(String filepath) throws AccessorAlreadyOpenedException,
			DataFileNotFoundException, DataFileParseException, DataFileIOException {
		config.setFilepath(filepath);
		open();
	}

	/**
	 * {@inheritDoc}
	 */
	public void close() throws DataFileIOException {

		if (isOpen) {
			try {
				stream.close();
				xml = null;
				stream = null;
				isOpen = false;
			} catch (IOException e) {
				throw new DataFileIOException(MESSAGE.getErrorMessage(
						ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_018,
						LogMessageConstant.CB3_ACCESSOR_FILEACCESS_004, config.getFilepath()), e);
			}
		}

	}

	/**
	 * {@inheritDoc}
	 *
	 * @throws ConfigFileNotFoundException 設定ファイルが存在しない場合に発生します。
	 * @throws ConfigFileDefinitionException 設定ファイルの間違いにより発生します。
	 * @throws ConfigFileIOException 設定ファイル読み込み時のIOエラーにより発生します。
	 * @throws ConfigFileParseException 設定ファイルのXMLファイル解析が失敗した時に発生します。
	 */
	public void initialize(String key) throws ConfigFileNotFoundException,
			ConfigFileDefinitionException, ConfigFileIOException, ConfigFileParseException {

		// アクセッサのキー名を保持します。
		this.key = key;

		// key（設定ファイル名）から設定ファイルを読み込む
		config = (XMLFileReaderConfig) ConfigurationLoader
				.load(XML_FILE_READER_RULE_FILE_NAME, key);

	}

	/**
	 * {@inheritDoc}
	 *
	 * @throws DataFileIOException データファイル読み書き時のIOエラーにより発生します。
	 */
	public void release() throws DataFileIOException {
		// XMLファイルをクローズします。
		close();
	}

}
