/*
 * $Id: RSSWebSpiderRobot.java,v 1.6 2005/10/22 16:27:27 rampil Exp $
 * Copyright (c) 2005 LOGICAL-PARADOX.ORG
 */
package org.logical_paradox.rss.robot;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.Calendar;
import java.util.Date;
import java.util.TooManyListenersException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.logical_paradox.rss.IllegalObjectStateException;
import org.logical_paradox.rss.ModuleException;
import org.logical_paradox.rss.RSSConstant;
import org.logical_paradox.rss.RSSIllegalConfigException;
import org.logical_paradox.rss.RSSProperties;
import org.logical_paradox.rss.RSSSubModule;
import org.logical_paradox.rss.http.RSSHttpURLConnectionFactory;
import org.logical_paradox.rss.robot.event.RSSWSAgentEvent;
import org.logical_paradox.rss.robot.event.RSSWSAgentListener;

/**
 * RSSWebSpiderRobot
 * RSS Web自動巡回ロボット本体
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.6 $
 */
public class RSSWebSpiderRobot implements RSSWSAgentListener, RSSSubModule {
	/** ロガー */
	private static final Log log = LogFactory.getLog(RSSWebSpiderRobot.class);

	private static final String RSS_SPIDER_CONF_PATH = "conf/rsswebspider.conf";

	private String RSSWSRobotKey = null;				// ロボットの名前(自動的に生成される)
	protected RSSWebSpiderConfig cfg = null;			// ロボットおよびエージェントの設定データ
	protected RSSWebNavigator navigator = null;		// 経路ナビゲータ
	protected RSSWebSpiderAgent agents[] = null;		// 探索を行なうエージェントスレッド

	protected int timeoutSec = 360000;				// 接続タイムアウト秒数(デフォルト = 1分)

	/**
	 * コンストラクタ
	 */
	public RSSWebSpiderRobot() {
	}

	/**
	 * ロボットメイン関数
	 */
	public static void main(String[] args) throws Exception {
		RSSWebSpiderRobot robot = new RSSWebSpiderRobot();

		try {
			robot.startModule();
		} catch(Exception e) {
			log.error("ロボットの初期化に失敗しました．ログを確認してください", e);
			System.exit(1);
		}
	}

	/**
	 * エージェントがスレッドのループを開始する際に呼ばれるコールバック
	 */
	public boolean AtTheBeginningOfAgentCallback(RSSWebSpiderAgent me) {
		// 経路ナビゲータで処理待ちする
		navigator.waitUntilEmpty();
		synchronized(navigator) {
			// 経路ナビゲータの状態が変化した場合は，ここから処理を続行
			// このスレッドを再利用する
			try {
				String url = null;
				while((url = navigator.getNextURL()) == null) {
					navigator.wait();
				}
				log.trace(Thread.currentThread().getName() + " " + url);
				me.setup(url, timeoutSec);
			} catch(Exception e) {
				return false;
			}
		}

		return true;
	}

	/**
	 * エージェントのスレッドが終了した場合のコールバックメソッド
	 * 再スタートが開始された場合はtrue,だめだった場合はfalseを返す
	 */
	public boolean AtTheEndOfAgentCallback(RSSWebSpiderAgent me) {
		return true;
	}

	/**
	 * ロボットの初期化
	 */
	private void init() throws IOException, TooManyListenersException, IllegalArgumentException, RSSIllegalConfigException, IllegalObjectStateException {
		// 設定ファイルの読み込みと内容の検査
		cfg = new RSSWebSpiderConfig(RSS_SPIDER_CONF_PATH);

		log.info("ログライターを初期化しました");

		// http接続オブジェクトファクトリの初期化
		String httpStubModeString = RSSProperties.getString(RSSConstant.RSS_PKEY_STUB_HTTP_CONNECTION);
		if(httpStubModeString != null) {
			boolean testmode = Boolean.valueOf(httpStubModeString).booleanValue();
			RSSHttpURLConnectionFactory.enableTestMode(testmode);

			log.info("テスト接続モード : " + testmode);

			if(testmode == true) {
				String hostname = RSSProperties.getString(RSSConstant.RSS_PKEY_STUB_HTTP_HOSTNAME);
				int port = Integer.parseInt(RSSProperties.getString(RSSConstant.RSS_PKEY_STUB_HTTP_PORT));

				RSSHttpURLConnectionFactory.setLocalhostSetting(hostname, port);
				log.info("テスト中の接続先 : http://" + hostname + ":" + port);
			}
		}

		// 経路ナビゲータを作成する
		navigator = RSSWebNavigator.getNavigator();
		navigator.enableLocalContentsMgr(true);
		log.info("経路ナビゲータが作成されました");


		RSSWSRobotKey = navigator.getRobotId();
		log.info("Robot name : " + getRobotKey());

		// 指定された数量のエージェントを事前に作成する措置
		int countOfAgents = cfg.getAgentsPerRobot();
		agents = new RSSWebSpiderAgent[ countOfAgents ];
		for(int i = 0; i < countOfAgents; i++) {
			agents[i] = new RSSWebSpiderAgent(this);
			agents[i].addRSSWSAgentListener(this);
		}
		log.info(agents.length + "個の探索エージェントを作成しました");

		timeoutSec = cfg.getAgentConnectionTimeoutSec();

		String[] contentTypes = cfg.getAllowedContentType();
		StringBuffer logLine = new StringBuffer();
		logLine.append("収集対象コンテンツタイプ: ");

		if(contentTypes == null || contentTypes.length == 0) {
			logLine.append("全て");
		} else {
			for(int i = 0; i < contentTypes.length; i++) {
				logLine.append(contentTypes[i] + " ");
			}
		}
		log.info(logLine.toString());
	}

	/**
	 * このロボットのconfigurationを返す
	 */
	public RSSWebSpiderConfig getSpiderConfig() {
		return cfg;
	}

	public String getRobotKey() {
		return RSSWSRobotKey;
	}

	public synchronized void onConnect(RSSWSAgentEvent e) {
		log.trace("接続が完了しました");
		RSSWebSpiderAgent src = (RSSWebSpiderAgent)e.getSource();

		// イベントの終了
		e.destroy();
	}

	public void onClose(RSSWSAgentEvent e) {
		log.trace("コンテンツの読み込みが完了しました");
		RSSWebSpiderAgent src = (RSSWebSpiderAgent)e.getSource();
		try {
			// Webコンテンツをナビゲータへ転送する
			// コンテンツが存在しない(404 not foundや転送など)場合で，かつrobots.txtでない場合は無視
			if(e.getContents().getWebContents() != null || e.getContents().isRobotsRule() == true) {
				log.trace("コンテンツの内容を登録しています");
				navigator.sendContents(e.getContents());
			} else {
				log.trace("いくつかの理由により，コンテンツの登録をスキップします");
			}

			// リソースの回収が完了したので，経路ナビゲータにメッセージを送信する
			// このナビゲータの状態変化待ちで待機している全てのエージェントをたたき起こす
			log.trace("ローカル探索キュー:" + navigator.getQueueSize());

			synchronized(navigator) {
				if(navigator.getQueueSize() > 0) {
					navigator.notifyAll();				// getNextURL() == nullで停止しているスレッドを起こす
				}
			}

		} catch(Exception ie) {
			ie.printStackTrace();
		}

		// イベントの終了
		e.destroy();
	}

	/**
	 * ロボットの終了処理
	 * ファイナライザからも呼ばれる
	 */
	public void close() throws RemoteException {
		navigator.close();
		navigator = null;
		RSSWSRobotKey = null;

		// 全てのエージェントを停止する
		if(agents != null) {
			for(int i = 0; i < agents.length; i++) {
				agents[i] = null;
			}
		}
	}

	public void finalize() throws RemoteException {
		close();
	}


	/**
	 * モジュールコントロール用インターフェース::
	 * モジュールを開始する
	 */
	public void startModule() throws ModuleException {
		try {
			init();
			// スレッドを全部スタートする
			for(int i = 0; i < agents.length; i++) {
				agents[i].start();
			}

			Date bgn = Calendar.getInstance().getTime();
			log.info("開始時刻: " + bgn.toString());

			// 経路ナビゲータの状態をたまに調べる処理
			// 終わっている様子であれば，エージェントを停止させる
			boolean exitf = true;
			int suscnt = 0;

			while(exitf) {
				Thread.sleep(500);
				if(suscnt == 30 || navigator.isFinished() == true) {
					// もう全部完了した場合
					for(int i = 0; i < agents.length; i++) {
						// 全部のスレッドを強制終了させる
						agents[i].shutdown();
					}
					exitf = false;
				} else if(navigator.getQueueSize() == 0 && navigator.countSuspendedSites() > 0) {
					// ローカルの探索用URLキューには何も積まれていないけど，探索抑制中のサイトがある場合
					// とりあえず探索抑制カウンタをアップしておく
					// 一定時間が経過しても現状が変わらない場合は，あきらめて終了するため
					suscnt++;
					log.info("探索抑制の回復を待っています:" + suscnt);
				} else {
					// 探索抑制以外の原因で完了していない場合
					suscnt = 0;
				}
			}

			// スレッドの待ち合わせ処理
			// 全部終わったら終了
			for(int i = 0; i < agents.length; i++) {
				agents[i].join();
			}

			// 終了処理
			close();
			log.info("done.");

			Date fin = Calendar.getInstance().getTime();
			log.info("終了時刻: " + fin.toString());
			log.info("探索を完了しました");
		} catch(Exception e) {
			e.printStackTrace();
			throw new ModuleException(e);
		}
	}

	/**
	 * モジュールコントロール用インターフェース::
	 * モジュールを一時停止する
	 */
	public void suspendModule() throws ModuleException {
	}

	/**
	 * モジュールコントロール用インターフェース::
	 * 停止しているモジュールの実行を再開する
	 */
	public void continueModule() throws ModuleException {
	}

	/**
	 * モジュールコントロール用インターフェース::
	 * モジュールを停止する
 	 * このメソッドが呼ばれた場合，モジュールは安全な方法で終了する
	 * 終了したモジュールは，新しくインスタンスが生成されないと開始することはできない
	 */
	public void stopModule() throws ModuleException {
	}
}

// end of rss1test.java
