/*
このファイルの中身は、NTT オープンソースライセンス　バージョン 1.0（「本
契約」という）の適用を受けます。
本契約を遵守しない限り、このファイルを使用してはなりません。
本契約のコピーは、次のＵＲＬから入手できます。
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

本契約に基づき頒布されるソフトウェアは、現状のまま、明示又は黙示のあらゆ
る種類の保証なしで、頒布されます。本契約に基づく権利及び制限を律する特定
の文言については、本契約を参照してください。

「オリジナルコード」は、 NTT Cyber Space Laboratories Code です。 
「オリジナルコード」の「初期開発者」は、日本電信電話株式会社です。  
日本電信電話株式会社により創作された部分は、Copyright (C) 2004 
日本電信電話株式会社 です。
全ての権利が留保されます。 
「コントリビュータ」：_____________________________________ 


The contents of this file are subject to the NTT Opensource License
Version 1.0 (the License); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

Software distributed under the License is distributed on an AS IS
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
License for the specific language governing rights and limitations
under the License.

The Original Code is NTT Cyber Space Laboratories Code .

The Initial Developer of the Original Code is NIPPON TELEGRAPH AND 
TELEPHONE CORPORATION.
Portions created by the NIPPON TELEGRAPH AND TELEPHONE CORPORATION 
are Copyright (C) 2004 NIPPON TELEGRAPH AND TELEPHONE CORPORATION. 
All Rights Reserved.

Contributor(s) ______________________________________.
*/

//
//	ScormLoLogControl
//	変更履歴
//		2004.02.01	新規作成
//      2006.12.01　変更 T.Kiyokawa
//                  終了時送信ログをXMLからテキスト形式に変更し、高速化
//

package jp.co.ntt.lms.lo.scorm;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import jp.co.ntt.lms.Common.status.PropagateControl;
import jp.co.ntt.lms.Common.status.StatusPropagation;
import jp.co.ntt.lms.lo.LOException;
import jp.co.ntt.lms.lo.LoLogDataAccess;
import jp.co.ntt.lms.lo.LoLogRecord;
import jp.co.ntt.lms.lo.LoLogSessionDataAccess;
import jp.co.ntt.lms.lo.LoLogSessionRecord;
import jp.co.ntt.lms.xmo.DataAccess;
import jp.co.ntt.lms.xmo.Lo.LoClient;
import jp.co.ntt.lms.xmo.util.DebugLog;

// CommentOut Start 2006.11.24 T.Kiyokawa 学習ログCSV化のため
//import org.apache.xpath.XPathAPI;
//import org.w3c.dom.Document;
//import org.w3c.dom.Node;
//import org.w3c.dom.NodeList;
// CommentOut End

/**
 * SCORM 学習ログ登録クラス。
 *
 * @author T.Nishiki
 */
public class ScormLoLogControl {

	/**
	 * &lt;scoactivitylog&gt;要素のパス。
	 */
	public static final String PATH_SCOACTIVITY_LOG =
		"/aicc/scoactivitylog";

	/**
	 * &lt;scoactivitylog&gt;要素から&lt;attemptcount&gt;までの相対パス。
	 */
	public static final String PATH_ATTEMPT_COUNT_FROM_ACTIVITY =
		"./attemptcount";

	/**
	 * &lt;attemptcount&gt;要素の&lt;count&gt;属性の相対パス。
	 */
	public static final String PATH_ATTEMPT_COUNT_COUNT_ATTRIBUTE =
		"./@count";

	/**
	 * &lt;attemptcount&gt;要素から&lt;learnsession&gt;までの相対パス。
	 */
	public static final String PATH_LEARN_SESSION_FROM_ATTEMPT_COUNT =
		"./learnsession";

	/**
	 * &lt;learnsession&gt;要素から&lt;attempt&gt;までの相対パス。
	 */
	public static final String PATH_ATTEMPT_FROM_LEARN_SESSION
		= "./attempt";

	/**
	 * &lt;learnsession&gt;要素から&lt;interaction&gt;までの相対パス。
	 */
	public static final String PATH_INTERACTION_FROM_LEARN_SESSION =
		"./interactions/interaction";

	/**
	 * &lt;learnsession&gt;要素から&lt;learnercomment&gt;までの相対パス。
	 */
	public static final String PATH_LEARNER_COMMENT_FROM_LEARN_SESSION =
		"./learnercomments/learnercomment";

	/**
	 * &lt;learnsession&gt;要素から&lt;lmscomment&gt;までの相対パス。
	 */
	public static final String PATH_LMS_COMMENT_FROM_LEARN_SESSION =
		"./lmscomments/lmscomment";

	/**
	 * &lt;learnsession&gt;要素から&lt;objective&gt;までの相対パス。
	 */
	public static final String PATH_OBJECTIVE_FROM_LEARN_SESSION =
		"./objectives/objective";

	/**
	 * &lt;scoactivitylog&gt;要素からid属性を取得するためのパス。
	 */
	public static final String PATH_ACTIVITY_ID =
		"./@id";

	/**
	 * &lt;learnsession&gt;要素からdate属性を取得するためのパス。
	 */
	public static final String PATH_LEARN_SESSION_DATE =
		"./@date";

//	ADD start 2006.11.22 T.Kiyokawa
	/** 
	CSV・列番号：SCOアクティビティログ
	 */
	public static final int CSVCOL_SCO_ACTIVITY_LOG = 0 ;


	/** 
	CSV・列番号：試行回数 
	 */
	public static final int CSVCOL_ATTEMPT_COUNT = 1 ;


	/** 
	CSV・列番号：LearnSession情報
	 */
	public static final int CSVCOL_LEARN_SESSION = 2 ;


	/** 
	CSV・列番号：登録テーブル種別
	 */
	public static final int CSVCOL_TABLE_KIND = 3 ;

	/**
	 * テーブル種別:"attempt"
	 */
	private static final String TABLE_KIND_ATTEMPT = "attempt";
	
	/**
	 * テーブル種別:"interaction"
	 */
	private static final String TABLE_KIND_INTERACTION = "interaction";
	
	/**
	 * テーブル種別:"learnerComment"
	 */
	private static final String TABLE_KIND_LEARNER_COMMENT = "learnerComment";
	
	/**
	 * テーブル種別:"lmsComment"
	 */
	private static final String TABLE_KIND_LMS_COMMENT = "lmsComment";

	/**
	 * テーブル種別:"objectives"
	 */
	private static final String TABLE_KIND_OBJECTIVES = "objectives";
//	ADD end

	/**
	 * コンストラクタです。<br>
	 */
	public ScormLoLogControl() {
	}

	/**
	 * 渡された終了情報から共通ログならび個別ログへの書込みを実行します。<br>
	 *
	 * @param dataAccess データアクセスクラス。
	 * @param endLearnData 学習終了情報。
	 *
	 * @throws LOException データベースへの書込に失敗した場合。
	 */
	public void insertLog(
		DataAccess dataAccess,
		ScormLoEndLearnData endLearnData)
		throws LOException {

		DebugLog.write(getClass(), "HEAD insertLog()", DebugLog.ROW);
		try {
			// Mod Start 2006.11.24 T.Kiyokawa(aiccLogをxmlからcsv形式に変更)
			
//			////////////////////////////////////////////////////////////////////
//			// ログXMLの取得、DOM Document化
//			String studyLog = endLearnData.getStudyLog();
//			Document studyLogDocument =
//				ScormLoUtil.transformXmlStringToDocument(studyLog);
			////////////////////////////////////////////////////////////////////
			// 個別ログの格納
			insertAiccLog(dataAccess, endLearnData);
//			insertAiccLog(dataAccess, endLearnData, studyLogDocument);
			
			// Mod End 
			////////////////////////////////////////////////////////////////////
			// 共通ログテーブルを更新
			insertRestOfAiccLog(dataAccess, endLearnData);
		}
		catch (LOException e) {
			throw e;
		}
		catch (Exception e) {
			throw new LOException(e.getMessage());
		}
		DebugLog.write(getClass(), "TAIL insertLog()", DebugLog.ROW);
	}

	/**
	 * 渡された終了情報からAICCログによらないログを登録します。
	 * 共通ログならびScormSessionへの書込みを実行します。<br>
	 *
	 * @param dataAccess データアクセスクラス。
	 * @param endLearnData 学習終了情報。
	 *
	 * @throws LOException データベースへの書込に失敗した場合。
	 */
	public void insertRestOfAiccLog(
		DataAccess dataAccess,
		ScormLoEndLearnData endLearnData)
		throws LOException {

		DebugLog.write(getClass(), "HEAD insertRestOfAiccLog()", DebugLog.ROW);
		try {
			////////////////////////////////////////////////////////////////////
			// ScormSessionデータベースに登録
			ScormSessionDataAccess.insert(dataAccess, endLearnData);
			////////////////////////////////////////////////////////////////////
			// 共通ログLoLogSessionテーブルを更新
			insertLoLogSession(dataAccess, endLearnData);
			////////////////////////////////////////////////////////////////////
			// 共通ログLoLogテーブルを更新
			insertLoLog(dataAccess, endLearnData);
			////////////////////////////////////////////////////////////////////
			// ステータス伝播
			propagateStatus(endLearnData);
		}
		catch (LOException e) {
			throw e;
		}
		catch (Exception e) {
			throw new LOException(e.getMessage());
		}
		DebugLog.write(getClass(), "TAIL insertRestOfAiccLog()", DebugLog.ROW);
	}
	
	// Add Start T.Kiyokawa 2006.11.24
	/**
	 * 渡された学習ログからAICCログの書込みを実行します。<br>
	 * Scorm個別ログテーブルのうち、ScormSessionテーブル以外を対象とします。
	 *
	 * @param dataAccess データアクセスクラス。
	 * @param endLearnData 学習終了情報。
	 * @param studyLogDocument 学習ログXMLを表すDOM Document。
	 *
	 * @throws LOException データベースへの書込に失敗した場合。
	 */
	private void insertAiccLog(
		DataAccess dataAccess,
		ScormLoEndLearnData endLearnData)
		throws LOException {

		////////////////////////////////////////////////////////////////////////
		// メソッド開始ログ
		DebugLog.write(
			getClass(),
			"HEAD insertAiccLog(dataAccess,endLearnData)",
			DebugLog.ROW);

		try {
			////////////////////////////////////////////////////////////////////
			// 最大Learnセッション回数を取得
			Map learnSessionCountMap =
				makeLearnSessionCountMap(dataAccess, endLearnData);
			////////////////////////////////////////////////////////////////////
			// CSVデータのエンコードを取得
			ScormLoEnv env = new ScormLoEnv() ;
			String csvEncode = env.getCharsetUrlEncode() ;
			////////////////////////////////////////////////////////////////////
			// 直前行のデータを保存する変数
			ScormLoCsvData lastCsv = null ;			 // 直前行データ
			int lastPreviousLearnSessionCount= -1 ;	 // 直前の最大Learnセッション回数
			int currentLearnSessionCount = 0 ;		 // 今回のLearnセッション回数
			int sameKeyRecordCount = 0 ;			 // キーが同じ行のカウンタ
			////////////////////////////////////////////////////////////////////
			// CSVデータを行ごとに分割
			String[] csvDataRows = new String[0];
			String studyLog = endLearnData.getStudyLog() ;
			if(studyLog!=null)
			{
				csvDataRows = studyLog.split("\r\n") ;
			}
			for (int i = 0; i < csvDataRows.length ; i++) {
				// =============================================================
				// 1行分のCSVデータの取得
				ScormLoCsvData currentCSV = 
					new ScormLoCsvData(csvDataRows[i].split(",", -1),csvEncode);
				// =============================================================
				// アクティビティIDを取得します。
				String activityID = 
					currentCSV.getData(CSVCOL_SCO_ACTIVITY_LOG) ;
				// =============================================================
				// 試行回数を取得します。
				String attemptCountString =
					currentCSV.getData(CSVCOL_ATTEMPT_COUNT) ;
				int attemptCount = Integer.parseInt(attemptCountString);
				//　=============================================================
				// 学習日時を取得します。
				String learnSessionDate = 
					currentCSV.getData(CSVCOL_LEARN_SESSION) ;
				//　=============================================================
				// 登録先を取得します。
				String tableKind = 
					currentCSV.getData(CSVCOL_TABLE_KIND) ;
				// =============================================================
				// 最大Learnセッション回数（＝前回までに登録した回数）を設定します。
				int previousLearnSessionCount ;
				if ( isEqualsActidAttcnt(currentCSV, lastCsv) )
				{
					// 直前行と同一アクティビティID・同一試行回数の場合、直前行の値を引き継ぎます。
					previousLearnSessionCount = lastPreviousLearnSessionCount ;
				}
				else
				{
					// 直前行とアクティビティID・試行回数が異なる場合、再計算します。
					previousLearnSessionCount = getLearnSessionCount(
						learnSessionCountMap, activityID, attemptCount);
				}
				// =============================================================
				// 今回のLearnセッション回数を設定します。
				if (!isEqualsActidAttcnt(currentCSV, lastCsv) )
				{
					// ActivityID,試行回数が等しくない場合、リセット
					currentLearnSessionCount = 0 ;
				}
				else if(!currentCSV.equals(lastCsv, CSVCOL_LEARN_SESSION))
				{
					// ActivityID,試行回数が等しく、
					// LearnSessionDateが直前行と異なる場合、インクリメント
					currentLearnSessionCount ++ ;
				}
				// =============================================================
				// 同じキーの行が何行連続して同じテーブルへ挿入されたかカウントします。
				if ( isEqualsActidAttcntLsndtInstbl(currentCSV, lastCsv) )
				{
					// 同じ場合はインクリメントします。
					sameKeyRecordCount ++ ;
				}
				else
				{
					// 異なる場合はリセットします。
					sameKeyRecordCount = 0 ;
				}
				// =============================================================
				// セッションカウントを取得します。
				int learnSessionCount = 
					previousLearnSessionCount + currentLearnSessionCount + 1;
				//　=============================================================
				// attemptへのデータ登録 
				if (tableKind.equals(TABLE_KIND_ATTEMPT)) 
				{
					ScormAttemptDataAccess.insert(
						dataAccess, endLearnData,
						currentCSV, activityID,
						attemptCount, learnSessionCount, learnSessionDate);
				}
				// =============================================================
				// interactionへのデータ登録 
				if (tableKind.equals(TABLE_KIND_INTERACTION))
				{
					ScormInteractionDataAccess.insert(
							dataAccess, endLearnData,
							currentCSV, activityID,
							attemptCount, learnSessionCount, 
							sameKeyRecordCount + 1);
				}
				// =============================================================
				// learnerCommentへのデータ登録 
				if (tableKind.equals(TABLE_KIND_LEARNER_COMMENT))
				{
					ScormLearnerCommentDataAccess.insert(
							dataAccess,	endLearnData,
							currentCSV, activityID,
							attemptCount, learnSessionCount, 
							sameKeyRecordCount + 1);
				}
				// =============================================================
				// lmsCommentへのデータ登録 
				if (tableKind.equals(TABLE_KIND_LMS_COMMENT))
				{
					ScormLMSCommentDataAccess.insert(
							dataAccess, endLearnData,
							currentCSV, activityID,
							attemptCount, learnSessionCount, 
							sameKeyRecordCount + 1);
				}
				// =============================================================
				// objectivesへのデータ登録 
				if (tableKind.equals(TABLE_KIND_OBJECTIVES))
				{
					ScormObjectiveDataAccess.insert(
							dataAccess, endLearnData,
							currentCSV, activityID,
							attemptCount, learnSessionCount, 
							sameKeyRecordCount + 1);
				}
				
				// カレント行のデータを保存
				lastCsv = currentCSV ;
				lastPreviousLearnSessionCount = previousLearnSessionCount ;
			}
		}
		catch (LOException e) {
			throw e;
		}
		catch (Exception e) {
			DebugLog.write(getClass(), e, DebugLog.HIGHT);
			throw new LOException(e.getMessage());
		}

		////////////////////////////////////////////////////////////////////////
		// メソッド終了ログ
		DebugLog.write(
			getClass(),
			"TAIL insertAiccLog("
				+ "dataAccess,endLearnData)",
			DebugLog.ROW);
	}
	
	/**
	 * ActivityID,試行回数が直前行と等しいか比較します。
	 * @param current カレント行
	 * @param last 直前行
	 * @return 等しいならばtrue
	 * @throws LOException 
	 */
	private boolean isEqualsActidAttcnt(
			ScormLoCsvData current,
			ScormLoCsvData last) throws LOException
	{
		boolean ret ;
		
		ret = 
			current.equals(last, CSVCOL_SCO_ACTIVITY_LOG) &&
			current.equals(last, CSVCOL_ATTEMPT_COUNT) ;
		
		return ret ;
	}

	/**
	 * CSVのキーとなる情報が直前行と等しいか比較します。<br>
	 * キーとなる情報とは、ActivityID,試行回数,Learnセッション日付,登録対象テーブルです。
	 * @param current カレント行
	 * @param last 直前行
	 * @return 等しいならばtrue
	 * @throws LOException 
	 */
	private boolean isEqualsActidAttcntLsndtInstbl(
			ScormLoCsvData current,
			ScormLoCsvData last) throws LOException
	{
		boolean ret ;
		
		ret = 
			current.equals(last, CSVCOL_SCO_ACTIVITY_LOG) &&
			current.equals(last, CSVCOL_ATTEMPT_COUNT) && 
			current.equals(last, CSVCOL_LEARN_SESSION) &&
			current.equals(last, CSVCOL_TABLE_KIND) ;
		
		return ret ;
	}
	// Add End 
	
	// Comment Start 2006.11.24 T.Kiyokawa(学習ログをXMLからCSVへ変更したため)
//	/**
//	 * 渡された学習ログからAICCログの書込みを実行します。<br>
//	 * Scorm個別ログテーブルのうち、ScormSessionテーブル以外を対象とします。
//	 *
//	 * @param dataAccess データアクセスクラス。
//	 * @param endLearnData 学習終了情報。
//	 * @param studyLogDocument 学習ログXMLを表すDOM Document。
//	 *
//	 * @throws LOException データベースへの書込に失敗した場合。
//	 */
//	private void insertAiccLog(
//		DataAccess dataAccess,
//		ScormLoEndLearnData endLearnData,
//		Document studyLogDocument)
//		throws LOException {
//
//		////////////////////////////////////////////////////////////////////////
//		// メソッド開始ログ
//		DebugLog.write(
//			getClass(),
//			"HEAD insertAiccLog("
//				+ "dataAccess,endLearnData," + studyLogDocument + ")",
//			DebugLog.ROW);
//
//		try {
//			////////////////////////////////////////////////////////////////////
//			// 最大Learnセッション回数を取得
//			Map learnSessionCountMap =
//				makeLearnSessionCountMap(dataAccess, endLearnData);
//			////////////////////////////////////////////////////////////////////
//			// Activityの部分のログを取得、データベースに登録
//			NodeList activityLogNodeList =
//				XPathAPI.selectNodeList(studyLogDocument, PATH_SCOACTIVITY_LOG);
//			// Activityごとにログを登録
//			for (int i = 0; i < activityLogNodeList.getLength(); i++) {
//				Node activityLogNode = activityLogNodeList.item(i);
//				insertActivityLog(dataAccess,
//					endLearnData, activityLogNode, learnSessionCountMap);
//			}
//		}
//		catch (LOException e) {
//			throw e;
//		}
//		catch (Exception e) {
//			DebugLog.write(getClass(), e, DebugLog.HIGHT);
//			throw new LOException(e.getMessage());
//		}
//
//		////////////////////////////////////////////////////////////////////////
//		// メソッド終了ログ
//		DebugLog.write(
//			getClass(),
//			"TAIL insertAiccLog("
//				+ "dataAccess,endLearnData," + studyLogDocument + ")",
//			DebugLog.ROW);
//	}
//
//	/**
//	 * 学習ログのうち、１アクティビティのログ書込みを実行します。<br>
//	 * &lt;scoactivitylog&gt;要素ごとに呼び出され、
//	 * 配下の要素に従い、各テーブルにレコードを追加します。<br>
//	 *
//	 * @param dataAccess データアクセスクラス。
//	 * @param endLearnData 学習終了情報。
//	 * @param activityNode &lt;scoactivitylog&gt;要素を表すノード。
//	 * @param learnSessionCountMap Learnセッション回数の最大値情報マップ。
//	 * マップの構造、登録するデータについては、
//	 * {@link #makeLearnSessionCountMap(DataAccess, ScormLoEndLearnData)}を参照。
//	 *
//	 * @throws LOException データベースへの書込に失敗した場合。
//	 * @see #makeLearnSessionCountMap(DataAccess, ScormLoEndLearnData)
//	 */
//	private void insertActivityLog(
//		DataAccess dataAccess,
//		ScormLoEndLearnData endLearnData,
//		Node activityNode, Map learnSessionCountMap)
//		throws LOException {
//
//		////////////////////////////////////////////////////////////////////////
//		// メソッド開始ログ
//		DebugLog.write(
//			getClass(),
//			"HEAD insertActivityLog("
//				+ "dataAccess,endLearnData," + activityNode
//				+ ",learnSessionCountMap)",
//			DebugLog.ROW);
//
//		try {
//			// アクティビティID
//			String activityID = ScormLoUtil.getNodeValue(
//					activityNode, PATH_ACTIVITY_ID);
//			////////////////////////////////////////////////////////////////////
//			// 試行回数（AttemptCount）の部分のログを取得、データベースに登録
//			NodeList attemptCountNodeList =
//				XPathAPI.selectNodeList(
//					activityNode,
//					PATH_ATTEMPT_COUNT_FROM_ACTIVITY);
//			// 試行回数ごとにログを登録
//			for (int i = 0; i < attemptCountNodeList.getLength(); i++) {
//				Node attemptCountNode = attemptCountNodeList.item(i);
//				insertAttemptCountLog(
//					dataAccess, endLearnData, attemptCountNode,
//					activityID, learnSessionCountMap);
//			}
//		}
//		catch (LOException e) {
//			throw e;
//		}
//		catch (Exception e) {
//			DebugLog.write(getClass(), e, DebugLog.HIGHT);
//			throw new LOException(e.getMessage());
//		}
//
//		////////////////////////////////////////////////////////////////////////
//		// メソッド終了ログ
//		DebugLog.write(
//			getClass(),
//			"TAIL insertActivityLog("
//				+ "dataAccess,endLearnData," + activityNode
//				+ ",learnSessionCountMap)",
//			DebugLog.ROW);
//	}
//
//	/**
//	 * 学習ログのうち、１試行以下のログ書込みを実行します。<br>
//	 * &lt;attemptCount&gt;要素ごとに呼び出され、
//	 * 配下の要素に従い、各テーブルにレコードを追加します。<br>
//	 *
//	 * @param dataAccess データアクセスクラス。
//	 * @param endLearnData 学習終了情報。
//	 * @param attemptCountNode &lt;attemptCount&gt;要素を表すノード。
//	 * @param activityID データを書き込む対象が属しているアクティビティのID。
//	 * @param learnSessionCountMap Learnセッション回数の最大値情報マップ。
//	 * マップの構造、登録するデータについては、
//	 * {@link #makeLearnSessionCountMap(DataAccess, ScormLoEndLearnData)}を参照。
//	 *
//	 * @throws LOException データベースへの書込に失敗した場合。
//	 * @see #makeLearnSessionCountMap(DataAccess, ScormLoEndLearnData)
//	 */
//	private void insertAttemptCountLog(
//		DataAccess dataAccess,
//		ScormLoEndLearnData endLearnData,
//		Node attemptCountNode,
//		String activityID,
//		Map learnSessionCountMap)
//		throws LOException {
//
//		////////////////////////////////////////////////////////////////////////
//		// メソッド開始ログ
//		DebugLog.write(
//			getClass(),
//			"HEAD insertAttemptCountLog("
//				+ "dataAccess,endLearnData"
//				+ "," + attemptCountNode + "," + activityID
//				+ ",learnSessionCountMap)",
//			DebugLog.ROW);
//
//		try {
//			////////////////////////////////////////////////////////////////////
//			// AttempCount配下のノードについて登録します。
//			// =================================================================
//			// 試行回数を取得します。
//			String attemptCountAttrValue = ScormLoUtil.getNodeValue(
//				attemptCountNode, PATH_ATTEMPT_COUNT_COUNT_ATTRIBUTE);
//			int attemptCount = Integer.parseInt(attemptCountAttrValue);
//			// =================================================================
//			// 同一アクティビティID、同一試行回数での
//			// 最大Learnセッション回数（＝前回までに登録した回数）を取得します。
//			int previousLearnSessionCount = getLearnSessionCount(
//						learnSessionCountMap, activityID, attemptCount);
//			// =================================================================
//			// LearnSessionの部分のログを取得、データベースに登録
//			NodeList learnSessionNodeList =
//				XPathAPI.selectNodeList(
//					attemptCountNode,
//					PATH_LEARN_SESSION_FROM_ATTEMPT_COUNT);
//			// =================================================================
//			// LearnSessionごとにログを登録
//			for (int i = 0; i < learnSessionNodeList.getLength(); i++) {
//				Node learnSessionNode = learnSessionNodeList.item(i);
//				int learnSessionCount = previousLearnSessionCount + i + 1;
//				insertLearnSessionLog( dataAccess,
//					endLearnData, learnSessionNode,
//					activityID, attemptCount, learnSessionCount);
//			}
//		}
//		catch (LOException e) {
//			throw e;
//		}
//		catch (Exception e) {
//			DebugLog.write(getClass(), e, DebugLog.HIGHT);
//			throw new LOException(e.getMessage());
//		}
//
//		////////////////////////////////////////////////////////////////////////
//		// メソッド終了ログ
//		DebugLog.write(
//			getClass(),
//			"TAIL insertAttemptCountLog("
//				+ "dataAccess,endLearnData"
//				+ "," + attemptCountNode + "," + activityID
//				+ ",learnSessionCountMap)",
//			DebugLog.ROW);
//	}
//
//	/**
//	 * 学習ログのうち、Learn Session以下のログ書込みを実行します。<br>
//	 * &lt;learnsession&gt;要素ごとに呼び出され、
//	 * 配下の要素に従い、各テーブルにレコードを追加します。<br>
//	 *
//	 * @param dataAccess データアクセスクラス。
//	 * @param endLearnData 学習終了情報。
//	 * @param learnSessionNode &lt;learnsession&gt;要素を表すノード。
//	 * @param activityID データを書き込む対象が属しているアクティビティのID。
//	 * @param attemptCount 試行回数。
//	 *         学習ログ内で、データを書き込む対象が属している
//	 *         &lt;attemptcount&gt;要素の位置（１以上の整数）。
//	 * @param learnSessionCount Learnセッション回数。
//	 *         学習ログ内で、データを書き込む対象である
//	 *         &lt;learnsession&gt;要素の位置（１以上の整数）。
//	 *
//	 * @throws LOException データベースへの書込に失敗した場合。
//	 */
//	private void insertLearnSessionLog(
//		DataAccess dataAccess,
//		ScormLoEndLearnData endLearnData,
//		Node learnSessionNode,
//		String activityID,
//		int attemptCount,
//		int learnSessionCount)
//		throws LOException {
//
//		////////////////////////////////////////////////////////////////////////
//		// メソッド開始ログ
//		DebugLog.write(
//			getClass(),
//			"HEAD insertLearnSessionLog("
//				+ "dataAccess ,endLearnData"
//				+ "," + learnSessionNode + "," + activityID
//				+ "," + attemptCount + "," + learnSessionCount + ")",
//			DebugLog.ROW);
//
//		try {
//			// 学習日時
//			String learnSessionDate = ScormLoUtil.getNodeValue(
//					learnSessionNode, PATH_LEARN_SESSION_DATE);
//			////////////////////////////////////////////////////////////////////
//			// ScormAttemptの部分のログを取得、データベースに登録
//			// 取得できない場合は、無視
//			Node attemptNode =
//				XPathAPI.selectSingleNode(
//					learnSessionNode,
//					PATH_ATTEMPT_FROM_LEARN_SESSION);
//			if (attemptNode != null) {
//				ScormAttemptDataAccess.insert(
//					dataAccess, endLearnData,
//					attemptNode, activityID,
//					attemptCount, learnSessionCount, learnSessionDate);
//			}
//			////////////////////////////////////////////////////////////////////
//			// ScormInteractionの部分のログを取得、データベースに登録
//			NodeList interactionNodeList =
//				XPathAPI.selectNodeList(
//					learnSessionNode,
//					PATH_INTERACTION_FROM_LEARN_SESSION);
//			// Interactionごとにログを登録
//			for (int i = 0; i < interactionNodeList.getLength(); i++) {
//				Node interactionNode = interactionNodeList.item(i);
//				ScormInteractionDataAccess.insert(
//					dataAccess, endLearnData,
//					interactionNode, activityID,
//					attemptCount, learnSessionCount, i + 1);
//			}
//			////////////////////////////////////////////////////////////////////
//			// ScormLearnerCommentの部分のログを取得、データベースに登録
//			NodeList learnerCommentNodeList =
//				XPathAPI.selectNodeList(
//					learnSessionNode,
//					PATH_LEARNER_COMMENT_FROM_LEARN_SESSION);
//			// LearnerCommentごとにログを登録
//			for (int i = 0; i < learnerCommentNodeList.getLength(); i++) {
//				Node learnerCommentNode = learnerCommentNodeList.item(i);
//				ScormLearnerCommentDataAccess.insert(
//					dataAccess,	endLearnData,
//					learnerCommentNode, activityID,
//					attemptCount, learnSessionCount, i + 1);
//			}
//			////////////////////////////////////////////////////////////////////
//			// ScormLMSCommentの部分のログを取得、データベースに登録
//			NodeList lmsCommentNodeList =
//				XPathAPI.selectNodeList(
//					learnSessionNode,
//					PATH_LMS_COMMENT_FROM_LEARN_SESSION);
//			// LMSCommentごとにログを登録
//			for (int i = 0; i < lmsCommentNodeList.getLength(); i++) {
//				Node lmsCommentNode = lmsCommentNodeList.item(i);
//				ScormLMSCommentDataAccess.insert(
//					dataAccess, endLearnData,
//					lmsCommentNode, activityID,
//					attemptCount, learnSessionCount, i + 1);
//			}
//			////////////////////////////////////////////////////////////////////
//			// ScormObjectiveの部分のログを取得、データベースに登録
//			NodeList objectiveNodeList =
//				XPathAPI.selectNodeList(
//					learnSessionNode,
//					PATH_OBJECTIVE_FROM_LEARN_SESSION);
//			// Objectiveごとにログを登録
//			for (int i = 0; i < objectiveNodeList.getLength(); i++) {
//				Node objectiveNode = objectiveNodeList.item(i);
//				ScormObjectiveDataAccess.insert(
//					dataAccess, endLearnData,
//					objectiveNode, activityID,
//					attemptCount, learnSessionCount, i + 1);
//			}
//		}
//		catch (LOException e) {
//			throw e;
//		}
//		catch (Exception e) {
//			DebugLog.write(getClass(), e, DebugLog.HIGHT);
//			throw new LOException(e.getMessage());
//		}
//
//		////////////////////////////////////////////////////////////////////////
//		// メソッド終了ログ
//		DebugLog.write(
//			getClass(),
//			"TAIL insertAttemptCountLog("
//				+ "dataAccess,endLearnData"
//				+ "," + learnSessionNode + "," + activityID
//				+ "," + attemptCount + "," + learnSessionCount + ")",
//			DebugLog.ROW);
//	}
	// Comment End

	/**
	 * 指定したアクティビティID、試行回数に対応するLearnセッション回数を取得する。
	 * @param learnSessionCountMap Learnセッション回数の最大値情報マップ。
	 * マップの構造、登録するデータについては、
	 * {@link #makeLearnSessionCountMap(DataAccess, ScormLoEndLearnData)}を参照。
	 * @param activityID アクティビティID。
	 * @param attemptCount 試行回数。
	 * @return learnSessionCountMapから、activityID、試行回数に対応する
	 *          Learnセッション回数を取得し、その値を戻す。
	 *          取得できない場合は０を戻す。
	 */
	private static int getLearnSessionCount(
			Map learnSessionCountMap, String activityID, int attemptCount) {
		////////////////////////////////////////////////////////////////////////
		// メソッド開始ログ
		DebugLog.write(ScormLoLogControl.class,
			"HEAD getLearnSessionCount(learnSessionCountMap"
				+ "," + activityID + "," + attemptCount + ")",
			DebugLog.ROW);
		// このメソッドの戻り値
		int maxLearnSessionCount = 0;
		try {
			// アクティビティをキーにして、
			// 「試行回数をキー、Learnセッション回数を値とするマップ」を
			// 取得する。
			Map attemptLearnSessionMap =
				(Map)learnSessionCountMap.get(activityID);
			// 取得できた場合、試行回数をキーにして、
			// 対応するLearnセッション回数を取得する。
			if (attemptLearnSessionMap != null) {
				Integer attemptCountInteger = new Integer(attemptCount);
				Integer maxLearnSessionCountInteger =
					(Integer)attemptLearnSessionMap.get(attemptCountInteger);
				// Learnセッション回数を取得できた場合、その値を戻り値とする。
				if (maxLearnSessionCountInteger != null) {
					maxLearnSessionCount =
						maxLearnSessionCountInteger.intValue();
				}
			}
		}
		catch (Exception e) {
			maxLearnSessionCount = 0;
		}
		////////////////////////////////////////////////////////////////////////
		// メソッド開始ログ
		DebugLog.write(ScormLoLogControl.class,
			"TAIL getLearnSessionCount(learnSessionCountMap"
				+ "," + activityID + "," + attemptCount + ")"
				+ " return " + maxLearnSessionCount,
			DebugLog.ROW);
		return maxLearnSessionCount;
	}

	/**
	 * Learnセッション回数情報を収集し、マップを生成します。<br>
	 * 戻すMapは、アクティビティをキー、
	 * 「試行回数をキー、Learnセッション回数を値とするマップ」を値とする
	 * マップです。<br>
	 * @param dataAccess データアクセス・オブジェクト。
	 * @param endLearnData 学習終了情報。
	 * @return アクティビティをキー、「試行回数をキー、
	 * 			Learnセッション回数を値とするマップ」を値とするマップ。
	 * @throws LOException データベースアクセスに失敗などの処理で
	 *          エラーが発生した場合。
	 */
	private static Map makeLearnSessionCountMap(
			DataAccess dataAccess, ScormLoEndLearnData endLearnData)
			throws LOException {
		////////////////////////////////////////////////////////////////////////
		// メソッド開始ログ
		DebugLog.write(ScormLoLogControl.class,
			"HEAD makeLearnSessionCountMap(dataAccess, endLearnData)",
			DebugLog.ROW);

		Map learnSessionCountMap = null;

		try {
			learnSessionCountMap = new HashMap();
			String userID = endLearnData.getUserID();
			String loID = endLearnData.getLoID();
			int studySession = endLearnData.getStudySession();
			String[][] matrix = null;
			// ScormAttemptテーブル
			matrix = ScormAttemptDataAccess.getMaxLearnSessionCount(
					dataAccess, userID, loID, studySession);
			convertLearnSessionCountMap(learnSessionCountMap, matrix);
			// Scorm_Interactionテーブル
			matrix = ScormInteractionDataAccess.getMaxLearnSessionCount(
					dataAccess, userID, loID, studySession);
			convertLearnSessionCountMap(learnSessionCountMap, matrix);
			// ScormLearnerCommentテーブル
			matrix = ScormLearnerCommentDataAccess.getMaxLearnSessionCount(
					dataAccess, userID, loID, studySession);
			convertLearnSessionCountMap(learnSessionCountMap, matrix);
			// ScormLMSCommentテーブル
			matrix = ScormLMSCommentDataAccess.getMaxLearnSessionCount(
					dataAccess, userID, loID, studySession);
			convertLearnSessionCountMap(learnSessionCountMap, matrix);
			// ScormObjectiveテーブル
			matrix = ScormObjectiveDataAccess.getMaxLearnSessionCount(
					dataAccess, userID, loID, studySession);
			convertLearnSessionCountMap(learnSessionCountMap, matrix);
		}
		catch (LOException e) {
			throw e;
		}
		catch (Exception e) {
			DebugLog.write(ScormLoLogControl.class, e, DebugLog.HIGHT);
			throw new LOException( e.getMessage() );
		}
		////////////////////////////////////////////////////////////////////////
		// メソッド終了ログ
		DebugLog.write(ScormLoLogControl.class,
			"TAIL makeLearnSessionCountMap(dataAccess, endLearnData)"
			 + " return " + learnSessionCountMap,
			DebugLog.ROW);
		return learnSessionCountMap;
	}

	/**
	 * Learnセッション回数情報配列の内容を、マップに登録し直します。<br>
	 * matrixから、順次、アクティビティID、試行回数、Learnセッション回数を
	 * 取得し、アクティビティID、試行回数の組み合わせで
	 * マップにLearnセッション回数の登録がなければ、
	 * Learnセッション回数をマップに登録します。
	 * 登録があれば、matrixから取得したLearnセッション回数が、
	 * マップに登録されているLearnセッション回数より大きい場合のみ、登録し直します。
	 * <br>
	 * @param learnSessionCountMap Learnセッション回数の最大値情報マップ。
	 * マップの構造、登録するデータについては、
	 * {@link #makeLearnSessionCountMap(DataAccess, ScormLoEndLearnData)}を参照。
	 * @param matrix Learnセッション回数情報配列。
	 * 以下の１次元文字列配列を要素とする配列。
	 * <table border="1">
	 * 	<tr>
	 * 		<th>インデックス</th>
	 * 		<th>値</th>
	 * 	</tr>
	 * 	<tr>
	 * 		<td align="right">0</td>
	 * 		<td>アクティビティID</td>
	 * 	</tr>
	 * 	<tr>
	 * 		<td align="right">1</td>
	 * 		<td>同じアクティビティの中で最大の試行回数</td>
	 * 	</tr>
	 * 	<tr>
	 * 		<td align="right">2</td>
	 * 		<td>
	 * 			同じアクティビティの中で最大の試行回数の中の
	 * 			最大のLearnセッション回数
	 * 		</td>
	 * 	</tr>
	 * </table>
	 * <br>
	 */
	private static void convertLearnSessionCountMap(
		Map learnSessionCountMap, String[][] matrix) {

		for (int i = 0; i < matrix.length; i++ ) {
			String activityID = matrix[i][0];
			Integer attemptCount = new Integer(matrix[i][1]);
			Integer learnSessionCount = new Integer(matrix[i][2]);
			// アクティビティをキーにして、
			// 「試行回数をキー、Learnセッション回数を値とするマップ」を
			// 取得する。取得できない場合は、生成・登録
			Map attemptLearnSessionMap =
				(Map)learnSessionCountMap.get(activityID);
			if (attemptLearnSessionMap == null) {
				attemptLearnSessionMap = new HashMap();
				learnSessionCountMap.put(activityID, attemptLearnSessionMap);
			}
			// 試行回数をキーにして、Learnセッション回数を取得します。
			// 取得できなかった場合は配列の値を登録します。
			// 取得できた場合は、値を比較して、より大きい方を登録します。
			Integer currentLearnSessionCount =
				(Integer)attemptLearnSessionMap.get(attemptCount);
			if (currentLearnSessionCount == null) {
				attemptLearnSessionMap.put(attemptCount, learnSessionCount);
			}
			else if (learnSessionCount.compareTo(
						currentLearnSessionCount) > 0) {
				attemptLearnSessionMap.put(attemptCount, learnSessionCount);
			}
		}
	}

	/**
	 * 共通ログLoLogSessionテーブルにレコードを追加を実行します。
	 * @param dataAccess データアクセス・オブジェクト。
	 * @param endLearnData 学習終了情報。
	 * @throws LOException データベースアクセスに例外が発生した場合。
	 */
	private void insertLoLogSession(
			DataAccess dataAccess,
			ScormLoEndLearnData endLearnData) throws LOException {
		////////////////////////////////////////////////////////////////////////
		// メソッド開始ログ
		DebugLog.write(getClass(),
			"HEAD insertLoLogSession(dataAccess,endLearnData)",
			DebugLog.ROW);
		try {
			// 現在日時
			Date thisTime = new Date();
			String thisTimeString = ScormLoUtil.convertDateToDbFormat(thisTime);

			////////////////////////////////////////////////////////////////////
			// 挿入するレコードを生成
			LoLogSessionRecord loLogSessionRecord = null;
			loLogSessionRecord = new LoLogSessionRecord();
			// ユーザID
			String userID = endLearnData.getUserID();
			loLogSessionRecord.setUserID(userID);
			// LOID
			String loID = endLearnData.getLoID();
			loLogSessionRecord.setLoID(loID);
			// セッションID
			String sessionID =
				Integer.toString(endLearnData.getLoginSession());
			loLogSessionRecord.setSessionID(sessionID);
			// 学習開始日時
			Date studyStartDate = endLearnData.getStartDate();
			String studyStartDateString =
				ScormLoUtil.convertDateToDbFormat(studyStartDate);
			loLogSessionRecord.setStudyStartDate(studyStartDateString);
			// 学習時間
			String residanceTime = Long.toString(
					thisTime.getTime() - studyStartDate.getTime());
			loLogSessionRecord.setResidanceTime(residanceTime);
			// 合格日時
			if (endLearnData.isSatisfiedStatus() == true) {
				loLogSessionRecord.setPassday(thisTimeString);
			}
			// 完了日時
			if (endLearnData.isCompletionStatus() == true) {
				loLogSessionRecord.setStudyEndDate(thisTimeString);
			}
			// 起動回数
			String lauchtimes =
				Integer.toString(endLearnData.getLoginSession());
			loLogSessionRecord.setLunchTimes(lauchtimes);

			// ScormAttemtテーブルからLoLogSession投入用のデータを取得
			double[] scores = ScormAttemptDataAccess.getTotalMaxMinScores(
					dataAccess, userID, loID, sessionID);
			// 最高得点
			String highScore = Double.toString(
					scores[ScormAttemptDataAccess.INDEX_MAX_SOCRE]);
			loLogSessionRecord.setHighScore(highScore);
			// 最低得点
			String lowScore = Double.toString(
					scores[ScormAttemptDataAccess.INDEX_MIN_SOCRE]);
			loLogSessionRecord.setLowScore(lowScore);
			// 最新得点
			double latestScore =
				ScormAttemptDataAccess.getTotalLatestScore(
					dataAccess, userID, loID, sessionID);
			String execisePoint = Double.toString(latestScore);
			loLogSessionRecord.setExecisePoint(execisePoint);
			// 満点
			double fullScore =
				ScormAttemptDataAccess.getTotalFullScore(
					dataAccess, userID, loID, sessionID);
			String strFullScore = Double.toString(fullScore);
			loLogSessionRecord.setFullScore(strFullScore);
			////////////////////////////////////////////////////////////////////
			// レコード追加
			LoLogSessionDataAccess logSessionDataAccess
				= new LoLogSessionDataAccess();
			logSessionDataAccess.insert(loLogSessionRecord, dataAccess);
		}
		catch (LOException e) {
			throw e;
		}
		catch (Exception e) {
			DebugLog.write(getClass(), e, DebugLog.HIGHT);
			throw new LOException(e.getMessage());
		}
		// メソッド終了ログ
		DebugLog.write(getClass(),
			"TAIL insertLoLogSession(dataAccess,endLearnData)",
			DebugLog.ROW);
	}

	/**
	 * 共通ログLoLogテーブルにレコードを追加／更新を実行します。
	 * @param dataAccess データアクセス・オブジェクト。
	 * @param endLearnData 学習終了情報。
	 * @throws LOException データベースアクセスに例外が発生した場合。
	 */
	private void insertLoLog(
		DataAccess dataAccess, ScormLoEndLearnData endLearnData)
		throws LOException {

		// メソッド開始ログ
		DebugLog.write(getClass(),
			"HEAD insertLoLog(dataAccess,endLearnData)",
			DebugLog.ROW);
		try {

			LoLogDataAccess loLogAccess = new LoLogDataAccess();
			////////////////////////////////////////////////////////////////////
			// 挿入するレコードを生成
			LoLogRecord loLogRecord = null;
			loLogRecord = new LoLogRecord();
			// ユーザID
			String userID = endLearnData.getUserID();
			loLogRecord.setUserID(userID);
			// LOID
			String loID = endLearnData.getLoID();
			loLogRecord.setLoID(loID);
			// アクセス回数
			String accessCount =
				Integer.toString(endLearnData.getLoginSession());
			loLogRecord.setAccessCnt(accessCount);
			// 最終アクセス日時
			Date studyStartDate = endLearnData.getStartDate();
			String studyStartDateString =
				ScormLoUtil.convertDateToDbFormat(studyStartDate);
			loLogRecord.setLastAccess(studyStartDateString);
			////////////////////////////////////////////////////////////////////
			// LoLogSessionからの取得データ
			LoLogSessionDataAccess loLogSessionAccess = null;
			loLogSessionAccess = new LoLogSessionDataAccess();
			// 学習開始日時
			LoLogRecord oldRecord = loLogAccess.getLoLog(userID,loID,dataAccess);
			if(oldRecord==null){
				loLogRecord.setStudyStartDate(studyStartDateString);
			}else{
				loLogRecord.setStudyStartDate(oldRecord.getStudyStartDate());
			}
			// 合格日時
			String passday =
				loLogSessionAccess.getFirstPassDay(
					userID, loID, dataAccess);
			loLogRecord.setPassday(passday);
			// 完了日時
			String studyEndDate =
				loLogSessionAccess.getFirstStudyEndDate(
					userID, loID, dataAccess);
			loLogRecord.setStudyEndDate(studyEndDate);
			// 学習時間
			long residanceTime =
				LoLogSessionDataAccess.getResidanceTime(
					userID, loID, dataAccess);
			loLogRecord.setResidanceTime(Long.toString(residanceTime));
			// 最高得点
			String highScore =
				loLogSessionAccess.getMaxHighScore(
					userID, loID, dataAccess);
			loLogRecord.setHighScore(highScore);
			// 最低得点
			String lowScore =
				loLogSessionAccess.getMinLowScore(
					userID, loID, dataAccess);
			loLogRecord.setLowScore(lowScore);
			// 満点
			String fullScore =
				loLogSessionAccess.getMaxFullScore(
					userID, loID, dataAccess);
			loLogRecord.setFullScore(fullScore);
			// 最新得点
			String execisePoint =
				loLogSessionAccess.getNewestExcisePoint(
					userID, loID, dataAccess);
			loLogRecord.setExecisePoint(execisePoint);
			////////////////////////////////////////////////////////////////////
			// レコード追加
			loLogAccess.insertUpdate(loLogRecord, userID, loID, dataAccess);
		}
		catch (LOException e) {
			throw e;
		}
		catch (Exception e) {
			DebugLog.write(getClass(), e, DebugLog.HIGHT);
			throw new LOException(e.getMessage());
		}
		// メソッド終了ログ
		DebugLog.write(getClass(),
			"TAIL insertLoLog(dataAccess,endLearnData)",
			DebugLog.ROW);
	}

	/**
	 * ステータス伝播処理を実行します。<br>
	 * @param endLearnData 学習終了情報。
	 * @throws LOException 処理中に例外が発生した場合。
	 */
	private void propagateStatus(ScormLoEndLearnData endLearnData)
			throws LOException {
		// メソッド開始ログ
		DebugLog.write(getClass(),
			"HEAD propagateStatus(endLearnData)",
			DebugLog.ROW);
		String userID = endLearnData.getUserID();
		String loID = endLearnData.getLoID();

		try {
			// 参照LOIDリストを取得します
			LoClient loClient = new LoClient();
			if (!loClient.getListFromRealLOID(loID)) {
				new LOException("参照LOIDリストの取得失敗しました。");
			}
			String[] refers = loClient.getRefListResult();

			// 参照リスト分ステータス伝播
			for (int i = 0; i < refers.length; i++) {
				// ステータス伝搬処理
				StatusPropagation statusPropagation =
					new StatusPropagation(userID, refers[i]);
				PropagateControl.getInstance().addPropagate(statusPropagation);
			}
		}
		catch (Exception e) {
			DebugLog.write(this.getClass(), e, DebugLog.HIGHT);
			throw new LOException(e.getMessage());
		}
		// メソッド終了ログ
		DebugLog.write(getClass(),
			"TAIL propagateStatus(endLearnData)",
			DebugLog.ROW);
	}
}
