/* $Id: DerbyProcessDAO.java,v 1.3 2007/11/27 02:06:21 knakata Exp $
 *
 * Copyright (c)ARGO 21, Corporation. 2005, 2006.  All rights reserved.
 * 
 * This file is part of Nautica Workflow Core.
 * 
 *  Nautica Workflow Core is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2.1 of the License, or
 *  (at your option) any later version.
 * 
 *  Nautica Workflow Core is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 * 
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with Nautica Workflow Core; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *  
 */
package jp.co.argo21.nautica.workflow.dataaccess.derby;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import jp.co.argo21.nautica.workflow.dataaccess.AbstractDAO;
import jp.co.argo21.nautica.workflow.dataaccess.DAOException;
import jp.co.argo21.nautica.workflow.dataaccess.ProcessBean;
import jp.co.argo21.nautica.workflow.dataaccess.ProcessDAO;
import jp.co.argo21.nautica.workflow.dataaccess.ProcessHistoryBean;
import jp.co.argo21.nautica.workflow.dataaccess.ProcessHistoryDAO;
import jp.co.argo21.nautica.workflow.dataaccess.WorkflowDAOFactory;
import jp.co.argo21.nautica.workflow.engine.DataAccessManager;
import jp.co.argo21.nautica.workflow.engine.LogManager;
import jp.co.argo21.nautica.workflow.filter.FilterConverter;
import jp.co.argo21.nautica.workflow.util.StringManager;
import jp.co.argo21.nautica.workflow.wfmc.Filter;
import jp.co.argo21.nautica.workflow.wfmc.InvalidFilterException;
import jp.co.argo21.nautica.workflow.wfmc.ProcessState;

/**
 * ProcessBeanをデータとするDerby用DAOクラスである。
 * <pre><tt>
 * WF_PROCESSテーブル
 * +----------------+--------------+------+-----+---------------------+-------+
 * | Field          | Type         | Null | Key | Default             | Extra |
 * +----------------+--------------+------+-----+---------------------+-------+
 * | PROC_ID        | varchar(64)  |      | PRI |                     |       |
 * | NAME           | varchar(128) | YES  |     | NULL                |       |
 * | PACKAGE_ID     | varchar(64)  |      |     |                     |       |
 * | PROC_DEF_ID    | varchar(64)  |      |     |                     |       |
 * | START_DATE     | datetime     |      |     | 0000-00-00 00:00:00 |       |
 * | STARTER_ID     | varchar(64)  |      |     |                     |       |
 * | DURATION_LIMIT | datetime     |      |     | 0000-00-00 00:00:00 |       |
 * | PROC_STATE     | int(1)       |      |     | 0                   |       |
 * | UPDATE_DATE    | datetime     | YES  |     | NULL                |       |
 * +----------------+--------------+------+-----+---------------------+-------+
 * </tt></pre>
 *
 * @author  knakata(Argo 21, Corp.)
 * @version $Revision: 1.3 $
 * @since   Nautica Workflow 1.0
 */
public class DerbyProcessDAO extends AbstractDAO implements ProcessDAO
{
	/** エンジンログ */
	protected static Logger eLog = LogManager.getEngineLogger();

	/** プロセスIDのカラム名 */
	public static final String PROC_ID        = "PROC_ID";
	/** プロセス名のカラム名 */
	public static final String NAME           = "NAME";
	/** パッケージIDのカラム名 */
	public static final String PACKAGE_ID     = "PACKAGE_ID";
	/** プロセス定義IDのカラム名 */
	public static final String PROC_DEF_ID    = "PROC_DEF_ID";
	/** 開始日時のカラム名 */
	public static final String START_DATE     = "START_DATE";
	/** 開始者のカラム名 */
	public static final String STARTER_ID     = "STARTER_ID";
	/** 実行期限のカラム名 */
	public static final String DURATION_LIMIT = "DURATION_LIMIT";
	/** プロセス状態のカラム名 */
	public static final String PROC_STATE     = "PROC_STATE";
	/** 更新日時のカラム名 */
	public static final String UPDATE_DATE    = "UPDATE_DATE";

	private static final String SQL_SELECT_BY_PRIMARY_KEY
	= "select * from WF_PROCESS where PROC_ID = ? ";

	private static final String SQL_SELECT_BY_PID_AND_PDID
	= "select * from WF_PROCESS where PROC_DEF_ID = ? and PROC_ID = ? ";

	private static final String SQL_SELECT_BY_PDID
	= "select * from WF_PROCESS where PROC_DEF_ID = ? ";

	private static final String SQL_SELECT
	= "select * from WF_PROCESS ";

	private static final String SQL_SELECT_COUNT_BY_PDID
	= "select COUNT(*) from WF_PROCESS where PROC_DEF_ID = ? ";

	private static final String SQL_INSERT
	= "insert into WF_PROCESS ("
		+ PROC_ID + ", "
		+ NAME + ", "
		+ PACKAGE_ID + ", "
		+ PROC_DEF_ID + ", "
		+ START_DATE + ", "
		+ STARTER_ID + ", "
		+ DURATION_LIMIT + ", "
		+ PROC_STATE + ", "
		+ UPDATE_DATE + ") values (?,?,?,?,?,?,?,?,?)";

	private static final String SQL_UPDATE_NAME
	= "UPDATE WF_PROCESS SET NAME = ? WHERE PROC_ID = ? ";

	private static final String SQL_UPDATE_STATE
	= "UPDATE WF_PROCESS SET PROC_STATE = ?, UPDATE_DATE = ? WHERE PROC_ID = ? ";

	private static final String SQL_UPDATE_DATE
	= "UPDATE WF_PROCESS SET UPDATE_DATE = ? WHERE PROC_ID = ? ";

	/**
	 * プロセス管理テーブルに対するアクセスオブジェクトを生成する。
	 *
	 * @param conn コネクション
	 */
	DerbyProcessDAO(Connection conn)
	{
		super(conn);
	}
	
	/**
	 * 主キーにより、プロセスを取得する。
	 *
	 * @param pid プロセスID
	 * @return プロセス
	 * @throws SQLException 任意のSQL例外
	 * @throws DAOException DAO実行中に処理としてあげる場合がある例外
	 * @see jp.co.argo21.nautica.workflow.dataaccess.ProcessDAO#findByPrimaryKey(java.lang.String)
	 */
	public ProcessBean findByPrimaryKey(String pid)
	throws SQLException, DAOException
	{
		PreparedStatement st = getConnection().prepareStatement(SQL_SELECT_BY_PRIMARY_KEY);
		try {
			st.setString(1, pid);

			/************** DEBUG **************/
			eLog.debug(st.toString());
			/***********************************/
			
			ResultSet rs = st.executeQuery();
			
			ProcessBean bean = null;
			if (rs.next()) {
				bean = createBean(rs);
			}
			return bean;
		} finally {
			st.close();
		}
	}
	
	/**
	 * プロセス定義IDとプロセスIDからプロセスを取得する。
	 *
	 * @param pdid	プロセス定義ID
	 * @param pid プロセスID
	 * @return プロセス
	 * @throws SQLException 任意のSQL例外
	 * @throws DAOException DAO実行中に処理としてあげる場合がある例外
	 * @see jp.co.argo21.nautica.workflow.dataaccess.ProcessDAO#findByPDIDandPID(java.lang.String, java.lang.String)
	 */
	public ProcessBean findByPDIDandPID(String pdid, String pid)
	throws SQLException, DAOException
	{
		PreparedStatement st = getConnection().prepareStatement(SQL_SELECT_BY_PID_AND_PDID);
		try {
			st.setString(1, pdid);
			st.setString(2, pid);

			/************** DEBUG **************/
			eLog.debug(st.toString());
			/***********************************/
			
			ResultSet rs = st.executeQuery();
			
			ProcessBean bean = null;
			if (rs.next()) {
				bean = createBean(rs);
			}
			return bean;
		} finally {
			st.close();
		}
	}
	
	/**
	 * プロセス定義IDに一致するプロセスを取得する。
	 *
	 * @param pdid	プロセス定義ID
	 * @return プロセス
	 * @throws SQLException 任意のSQL例外
	 * @throws DAOException DAO実行中に処理としてあげる場合がある例外
	 * @see jp.co.argo21.nautica.workflow.dataaccess.ProcessDAO#findByPDID(java.lang.String)
	 */
	public ProcessBean[] findByPDID(String pdid)
	throws SQLException, DAOException
	{
		PreparedStatement st = getConnection().prepareStatement(SQL_SELECT_BY_PDID);
		try {
			st.setString(1, pdid);

			/************** DEBUG **************/
			eLog.debug(st.toString());
			/***********************************/
			
			ResultSet rs = st.executeQuery();
			
			List<ProcessBean> list = new ArrayList<ProcessBean>();
			while (rs.next()) {
				ProcessBean bean = createBean(rs);
				list.add(bean);
			}
			return (ProcessBean[])list.toArray(new ProcessBean[list.size()]);
		} finally {
			st.close();
		}
	}
	
	/**
	 * プロセス定義IDに一致するプロセスの個数を返す。
	 *
	 * @param pdid	プロセス定義ID
	 * @return プロセス個数
	 * @throws SQLException 任意のSQL例外
	 * @throws DAOException DAO実行中に処理としてあげる場合がある例外
	 * @see jp.co.argo21.nautica.workflow.dataaccess.ProcessDAO#findCountByPDID(java.lang.String)
	 */
	public int findCountByPDID(String pdid)
	throws SQLException, DAOException
	{
		PreparedStatement st = getConnection().prepareStatement(SQL_SELECT_COUNT_BY_PDID);
		try {
			st.setString(1, pdid);

			/************** DEBUG **************/
			eLog.debug(st.toString());
			/***********************************/
			
			ResultSet rs = st.executeQuery();
			
			int count = 0;
			if (rs.next()) {
				count = rs.getInt(1);
			}
			return count;
		} finally {
			st.close();
		}
	}
	
	/**
	 * フィルターに一致したプロセスを返す。
	 *
	 * @param filter フィルター
	 * @return プロセス
	 * @throws SQLException 任意のSQL例外
	 * @throws DAOException DAO実行中に処理としてあげる場合がある例外
	 * @throws InvalidFilterException
	 * @see jp.co.argo21.nautica.workflow.dataaccess.ProcessDAO#findByFilter(jp.co.argo21.nautica.workflow.wfmc.Filter)
	 */
	public ProcessBean[] findByFilter(Filter filter)
	throws SQLException, DAOException, InvalidFilterException
	{
		FilterConverter converter = new ProcessFilterConverter();
		String where = converter.convertToSQL(filter);
		
		if (where.equals("") == false) {
			where = " where " + where;
		}
		PreparedStatement st = getConnection().prepareStatement(SQL_SELECT + where);

		/************** DEBUG **************/
		eLog.debug(st.toString());
		/***********************************/

		try {
			ResultSet rs = st.executeQuery();
			
			List<ProcessBean> list = new ArrayList<ProcessBean>();
			while (rs.next()) {
				ProcessBean bean = createBean(rs);
				list.add(bean);
			}
			return (ProcessBean[])list.toArray(new ProcessBean[list.size()]);
		} finally {
			st.close();
		}
	}

	/**
	 * プロセス定義IDとフィルターに一致したプロセスを返す。
	 *
	 * @param pdid	プロセス定義ID
	 * @param filter フィルター
	 * @return プロセス
	 * @throws SQLException 任意のSQL例外
	 * @throws DAOException DAO実行中に処理としてあげる場合がある例外
	 * @see jp.co.argo21.nautica.workflow.dataaccess.ProcessDAO#findByFilter(java.lang.String, jp.co.argo21.nautica.workflow.wfmc.Filter)
	 */
	public ProcessBean[] findByFilter(String pdid, Filter filter)
	throws SQLException, DAOException, InvalidFilterException
	{
		FilterConverter converter = new ProcessFilterConverter();
		String where = converter.convertToSQL(filter);
		
		if (where.equals("")) {
			where = " where PROC_DEF_ID = '" + pdid + "' ";
		} else {
			where = " where PROC_DEF_ID = '" + pdid + "' and " + where;
		}
		PreparedStatement st = getConnection().prepareStatement(SQL_SELECT + where);

		/************** DEBUG **************/
		eLog.debug(st.toString());
		/***********************************/

		try {
			ResultSet rs = st.executeQuery();
			
			List<ProcessBean> list = new ArrayList<ProcessBean>();
			while (rs.next()) {
				ProcessBean bean = createBean(rs);
				list.add(bean);
			}
			return (ProcessBean[])list.toArray(new ProcessBean[list.size()]);
		} finally {
			st.close();
		}
	}
	
	/**
	 * プロセスを挿入する。
	 *
	 * @param bean プロセス
	 * @throws SQLException 任意のSQL例外
	 * @throws DAOException DAO実行中に処理としてあげる場合がある例外
	 * @see jp.co.argo21.nautica.workflow.dataaccess.ProcessDAO#insert(jp.co.argo21.nautica.workflow.dataaccess.ProcessBean)
	 */
	public void insert(ProcessBean bean)
	throws SQLException, DAOException
	{
		PreparedStatement st = getConnection().prepareStatement(SQL_INSERT);
		try {
			st.setString(1, bean.getID());
			st.setString(2, bean.getName());
			st.setString(3, bean.getPackageID());
			st.setString(4, bean.getProcDefinitionID());
			st.setTimestamp(5, bean.getStartDate());
			st.setString(6, bean.getStarterID());
			st.setTimestamp(7, bean.getDurationLimit());
			st.setInt(8, bean.getProcessState());
			st.setTimestamp(9, bean.getUpdateDate());
			
			int i = st.executeUpdate();
			
			if (i != 1) {
				// プロセスレコードの挿入に失敗しました。
				String errMsg = StringManager.get("E0018");
				throw new DAOException(errMsg);
			}
		} finally {
			st.close();
		}

		try {
			WorkflowDAOFactory factory = DataAccessManager.getDAOFactory();
			ProcessHistoryDAO phDAO = factory.getProcessHistoryDAO();
			ProcessHistoryBean history = new ProcessHistoryBean();
			history.setProcID(bean.getID());
			history.setPackageID(bean.getPackageID());
			history.setProcDefID(bean.getProcDefinitionID());
			history.setPrevState(-1);
			history.setNewState(bean.getProcessState());
			history.setUpdateDate(bean.getUpdateDate());
			history.setReason("NEW PROCESS CREATION");
			phDAO.insert(history);
		} catch (SQLException ex) {
			throw ex;
		} catch (DAOException ex) {
			throw ex;
		} catch (Exception ex) {
			// プロセス状態履歴の更新に失敗しました。
			String errMsg = StringManager.get("E0019");
			throw new DAOException(errMsg, ex);
		}
	}
	
	/**
	 * プロセス名を更新する。
	 *
	 * @param bean プロセス
	 * @param name プロセス名
	 * @throws SQLException 任意のSQL例外
	 * @throws DAOException DAO実行中に処理としてあげる場合がある例外
	 * @see jp.co.argo21.nautica.workflow.dataaccess.ProcessDAO#updateName(jp.co.argo21.nautica.workflow.dataaccess.ProcessBean, java.lang.String)
	 */
	public void updateName(ProcessBean bean, String name)
	throws SQLException, DAOException
	{
		PreparedStatement st = getConnection().prepareStatement(SQL_UPDATE_NAME);
		try {
			st.setString(1, name);
			st.setString(2, bean.getID());
			
			int i = st.executeUpdate();
			
			if (i != 1) {
				// プロセス名の更新に失敗しました。
				String errMsg = StringManager.get("E0020");
				throw new DAOException(errMsg);
			}
			bean.setName(name);
		} finally {
			st.close();
		}
	}
	
	/**
	 * プロセス状態を更新する。
	 *
	 * @param bean プロセス
	 * @param state プロセス状態
	 * @param ts タイムスタンプ
	 * @throws SQLException 任意のSQL例外
	 * @throws DAOException DAO実行中に処理としてあげる場合がある例外
	 * @see jp.co.argo21.nautica.workflow.dataaccess.ProcessDAO#updateState(jp.co.argo21.nautica.workflow.dataaccess.ProcessBean, jp.co.argo21.nautica.workflow.wfmc.ProcessState, java.sql.Timestamp)
	 */
	public void updateState(ProcessBean bean, ProcessState state, Timestamp ts)
	throws SQLException, DAOException
	{
		PreparedStatement st = getConnection().prepareStatement(SQL_UPDATE_STATE);
		try {
			st.setInt(1, state.toInt());
			st.setTimestamp(2, ts);
			st.setString(3, bean.getID());
			
			int i = st.executeUpdate();
			
			if (i != 1) {
				// プロセス状態の更新に失敗しました。
				String errMsg = StringManager.get("E0021");
				throw new DAOException(errMsg);
			}
		} finally {
			st.close();
		}

		try {
			WorkflowDAOFactory factory = DataAccessManager.getDAOFactory();
			ProcessHistoryDAO phDAO = factory.getProcessHistoryDAO();
			ProcessHistoryBean history = new ProcessHistoryBean();
			history.setProcID(bean.getID());
			history.setPackageID(bean.getPackageID());
			history.setProcDefID(bean.getProcDefinitionID());
			history.setPrevState(bean.getProcessState());
			history.setNewState(state.toInt());
			history.setUpdateDate(bean.getUpdateDate());
			ProcessState old = ProcessState.getState(bean.getProcessState());
			String msg = "PROCESS STATE CHANGED FROM " + old.toString() + " TO " + state.toString();
			history.setReason(msg);
			phDAO.insert(history);
		} catch (SQLException ex) {
			throw ex;
		} catch (DAOException ex) {
			throw ex;
		} catch (Exception ex) {
			// プロセス状態履歴の更新に失敗しました。
			String errMsg = StringManager.get("E0019");
			throw new DAOException(errMsg, ex);
		}
		bean.setUpdateDate(ts);
		bean.setProcessState(state.toInt());
	}
	
	/**
	 * プロセス変更日付を更新する。
	 *
	 * @param bean プロセス
	 * @param ts タイムスタンプ
	 * @throws SQLException 任意のSQL例外
	 * @throws DAOException DAO実行中に処理としてあげる場合がある例外
	 * @see jp.co.argo21.nautica.workflow.dataaccess.ProcessDAO#updateDate(jp.co.argo21.nautica.workflow.dataaccess.ProcessBean, java.sql.Timestamp)
	 */
	public void updateDate(ProcessBean bean, Timestamp ts)
	throws SQLException, DAOException
	{
		PreparedStatement st = getConnection().prepareStatement(SQL_UPDATE_DATE);
		try {
			st.setTimestamp(1, ts);
			st.setString(2, bean.getID());
			
			int i = st.executeUpdate();
			
			if (i != 1) {
				// プロセス状態の更新に失敗しました。
				String errMsg = StringManager.get("E0021");
				throw new DAOException(errMsg);
			}
		} finally {
			st.close();
		}

		try {
			ProcessState old = ProcessState.getState(bean.getProcessState());
			WorkflowDAOFactory factory = DataAccessManager.getDAOFactory();
			ProcessHistoryDAO phDAO = factory.getProcessHistoryDAO();
			ProcessHistoryBean history = new ProcessHistoryBean();
			history.setProcID(bean.getID());
			history.setPackageID(bean.getPackageID());
			history.setProcDefID(bean.getProcDefinitionID());
			history.setPrevState(bean.getProcessState());
			history.setNewState(old.toInt());
			history.setUpdateDate(bean.getUpdateDate());
			String msg = "PROCESS STATE NOTIFIED FROM " + old.toString();
			history.setReason(msg);
			phDAO.insert(history);
		} catch (SQLException ex) {
			throw ex;
		} catch (DAOException ex) {
			throw ex;
		} catch (Exception ex) {
			// プロセス状態履歴の更新に失敗しました。
			String errMsg = StringManager.get("E0019");
			throw new DAOException(errMsg, ex);
		}
		bean.setUpdateDate(ts);
	}
	
	/**
	 * 結果セットからプロセスを作成する。
	 *
	 * @param rs 結果セット
	 * @return プロセス
	 * @throws SQLException 任意のSQL例外
	 */
	private ProcessBean createBean(ResultSet rs)
	throws SQLException
	{
		ProcessBean bean = new ProcessBean();

		bean.setID(rs.getString(PROC_ID));
		bean.setName(rs.getString(NAME));
		bean.setPackageID(rs.getString(PACKAGE_ID));
		bean.setProcDefinitionID(rs.getString(PROC_DEF_ID));
		bean.setStartDate(rs.getTimestamp(START_DATE));
		bean.setStarterID(rs.getString(STARTER_ID));
		bean.setDurationLimit(rs.getTimestamp(DURATION_LIMIT));
		bean.setProcessState(rs.getInt(PROC_STATE));
		bean.setUpdateDate(rs.getTimestamp(UPDATE_DATE));
		
		return bean;
	}
}
