/*
 * $Id: RSSSyncDaemon.java,v 1.7 2006/01/27 16:39:04 rampil Exp $
 * Copyright (c) 2004 LOGICAL-PARADOX.ORG
 */
package org.logical_paradox.rss.dsync;

import java.util.TooManyListenersException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.logical_paradox.rss.dsync.event.DelayedSyncEvent;
import org.logical_paradox.rss.dsync.event.DelayedSyncEventListener;

/**
 * RSSSyncDaemon
 * RSSのあらゆるノード上で動作する，遅延同期デーモン
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.7 $
 */
public class RSSSyncDaemon extends Thread {
	/** ロガー */
	private static final Log log = LogFactory.getLog(RSSSyncDaemon.class);
	/** 同期エラーの許容回数 */
	public static final int SYNCD_ERROR_LIMIT = 5;
	/** 同期インターフェース */
	private RSSSyncInterface syncIF;
	/** 設定ファイル */
	private RSSSyncConfig syncdConfig;
	/** 同期差分キュー */
	private SyncQueue syncq = null;
	/** 同期エラー連続発生回数 */
	protected int errorCount = 0;
	/** スレッドが稼動中であるかどうか(true:稼動中 / false:停止中) */
	private boolean loop = true;
	/** この遅延同期デーモンが配置されているノードのID */
	private String nodeId;
	/** イベントリスナー */
	private DelayedSyncEventListener eventListener;
	/** 親ノードID */
	private String parentNodeId;

	/**
	 * コンストラクタ
	 * @param conf コンフィグ
	 * @param si 同期インターフェース
	 * @throws Exception インスタンス化に失敗した
	 */
	public RSSSyncDaemon(RSSSyncConfig conf, RSSSyncInterface si) throws Exception {
		syncIF = si;
		syncdConfig = conf;

		parentNodeId = si.getParentNodeId();

		syncq = new SyncQueue(syncdConfig.getSyncQueueSize());

		nodeId = si.getNodeId();
		// デーモンスレッドとして動作
		setDaemon(true);
	}
	/**
	 * イベントリスナーを追加する(リスナーは1こしか追加することができない)
	 * @param listener イベントリスナー
	 * @throws TooManyListenersException 既にイベントリスナーが登録されている
	 */
	public void addListener(DelayedSyncEventListener listener) throws TooManyListenersException {
		if(eventListener != null) {
			throw new TooManyListenersException("リスナーは追加できないので，一度削除する必要があります");
		}
		eventListener = listener;
	}
	/**
	 * イベントリスナーを削除する
	 */
	public void removeListener() {
		if(eventListener != null) {
			eventListener = null;
		}
	}
	/**
	 * スレッドのメインメソッド
	 */
	@Override
	public void run() {
		write("遅延同期デーモンが開始されました");
		try {
			while(loop) {
				sleep(syncdConfig.getSyncInterval());

				// 同期情報の転送と受信
				SyncCommand[] receivedElements = sync();
				if(receivedElements != null) {
					errorCount = 0;
					// 受信したコマンドのハンドラを呼び出す処理
					handleAllEvents(receivedElements);
				} else if(errorCount >= SYNCD_ERROR_LIMIT) {
					loop = false;
				}
			}
			write("同期エラーが指定回数連続して発生したため，同期デーモンを停止します");
			done();
		} catch(InterruptedException intrexp) {
			write("同期デーモンに対する停止命令を受信しました");
			done();
		}
	}
	/**
	 * ログ出力
	 * @param ls 送信する文字列
	 */
	protected void write(String ls) {
		log.trace(ls);
	}
	/**
	 * 外部から同期デーモンを停止させる
	 */
	public void shutdown() {
		loop = false;
	}
	/**
	 * 同期デーモンを停止する
	 */
	protected void done() {
		write("同期デーモンを停止しました");
	}
	/**
	 * 同期デーモンに対してコマンドをエントリーする
	 * @param command 同期コマンド
	 * @throws SyncQueueException 同期コマンドの処理に失敗した
	 */
	public void add(SyncCommand command) throws SyncQueueException {
		try {
			if(command.getTo() == null) {
				// 宛先が指定されていない場合，同期インターフェースから接続先のノードIDを取得してセットする
				command.setTo(parentNodeId);
			}
			if(command.getFrom() == null) {
				// 送信元が指定されていない場合，同期インターフェースから接続元のノードIDを取得してセットする
				command.setFrom(getNodeId());
			}
			syncq.add(command);
		} catch(Exception e) {
			log.warn(e);
			throw new SyncQueueException(e.getMessage());
		}
	}
	/**
	 * 指定のコマンドを同期キューから削除する
	 * @param command 削除対象の同期コマンド
	 */
	public void remove(SyncCommand command) {
		syncq.remove(command);
	}
	/**
	 * 同期処理
	 * あらかじめ設定された同期インターフェースを介して，差分情報のやりとりをする
	 * @return 受信した同期コマンドのリスト
	 */
	public SyncCommand[] sync() {
		SyncCommand[] e = null;

		try {
			syncIF.sync(syncq.getAll());
			syncq.clear();
			e = syncIF.getSyncData();

			if(e != null && e.length > 0) {
				write(e.length + "件の同期情報が無効化されました");
			}
		} catch(Exception exp) {
			errorCount++;
			write("同期情報の送受信中にエラーが発生しました:");
			exp.printStackTrace();
		}

		return e;
	}
	/**
	 * ノードIDを返す
	 * @return ノードID
	 */
	public String getNodeId() {
		return nodeId;
	}
	/**
	 * このノード宛てに送信された同期コマンドを処理する
	 * @param commands 受信した同期コマンドのリスト
	 */
	protected void handleAllEvents(SyncCommand[] commands) {
		if(eventListener == null) {
			// イベントリスナーが登録されていないので，何もしないよ
			return;
		}
		for(int cnt = 0; cnt < commands.length; cnt++) {
			DelayedSyncEvent eventObject = new DelayedSyncEvent(this, commands[cnt]);
			switch(commands[cnt].getType()) {
				case SyncCommand.COMMAND_LOCK:
					eventListener.onLock(eventObject);
					break;

				case SyncCommand.COMMAND_UNLOCK:
					eventListener.onUnlock(eventObject);
					break;

				case SyncCommand.COMMAND_FORWARD_URL:
					eventListener.onForwardURL(eventObject);
					break;

				case SyncCommand.COMMAND_UNFORWARD_URL:
					eventListener.onUnforwardURL(eventObject);
					break;

				case SyncCommand.COMMAND_SHUTDOWN:
					eventListener.onShutdown(eventObject);
					break;

				default:
					write("指定されたコマンドが実装されていません: " + eventObject.getCommand().toString());
			}
		}
	}
}

// end of RSSSyncDaemon.java
