/*
 * $Id: RSSWebSpiderAgent.java,v 1.11 2006/02/04 16:18:29 rampil Exp $
 * Copyright (c) 2005 LOGICAL-PARADOX.ORG
 */
package org.logical_paradox.rss.robot;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
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.http.RSSHttpResponse;
import org.logical_paradox.rss.http.RSSHttpURLConnection;
import org.logical_paradox.rss.http.RSSHttpURLConnectionFactory;
import org.logical_paradox.rss.http.WebContents;
import org.logical_paradox.rss.robot.event.RSSWSAgentEvent;
import org.logical_paradox.rss.robot.event.RSSWSAgentListener;

import HTTPClient.NVPair;

/**
 * RSSWebSpiderAgent
 * Web自動巡回エージェント
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.11 $
 */
public class RSSWebSpiderAgent extends Thread {
	/** ロガー */
	private static final Log log = LogFactory.getLog(RSSWebSpiderAgent.class);
	/** URL接続 */
	private RSSHttpURLConnection con = null;
	/** コンテンツ */
	private String content = null;
	/** イベントリスナー */
	private RSSWSAgentListener eventListener = null;
	/** 実行状態(true: 稼動中 / false:停止中) */
	private boolean running = false;
	/** このエージェントを管理している親クラス */
	private RSSWebSpiderRobot robot = null;

	/**
	 * コンストラクタ
	 * @param r 親ロボット
	 */
	public RSSWebSpiderAgent(RSSWebSpiderRobot r) {
		robot = r;
		running = false;
	}
	/**
	 * エージェントの実行状態を返す
	 * @return true:動作中 / false:終了
	 */
	public boolean isRunning() {
		return running;
	}
	/**
	 * エージェントの初期化をする
	 * @param remoteHost 接続先ホスト
	 * @param timeoutSec 接続タイムアウト秒数(msec)
	 * @throws MalformedURLException URLがおかしい
	 * @throws IOException 接続に失敗
	 * @throws TooManyListenersException イベントリスナーの登録数が多すぎる
	 */
	public void setup(String remoteHost, int timeoutSec) throws MalformedURLException, IOException, TooManyListenersException {
		log.trace("スレッドのセットアップをしています");

		// 送出するヘッダ情報の登録作業
		NVPair[] nvp = {
				new NVPair("User-Agent", robot.getSpiderConfig().getHttpUserAgent()),
				new NVPair("Cache-Control", "no-cache")
		};

		RSSHttpURLConnection c = RSSHttpURLConnectionFactory.getConnection(new URL(remoteHost));
		c.setAllowUserInteraction(false);
		c.setDefaultHeaders(nvp);
		c.setTimeout(timeoutSec);
		try {
			c.removeModule(Class.forName("HTTPClient.CookieModule"));
		} catch(Exception e) {
		}
		con = c;

		log.trace("スレッドのセットアップを完了しました");
	}
	/**
	 * 接続しているリモートホスト(webサーバー)を返す
	 * @return リモートホスト
	 * @throws IllegalObjectStateException リモートホストに接続していない
	 */
	public String getRemoteHost() throws IllegalObjectStateException {
		if(con == null) {
			throw new IllegalObjectStateException();
		}
		return con.getURL().toString();
	}
	/**
	 * スレッドメイン
	 */
	public void run() {
		running = true;

		while(running) {
			log.trace("スレッドが接続を開始しました");

			// 接続イベント発行
			sendOnConnectEvent(new RSSWSAgentEvent(this));

			boolean rc = robot.AtTheBeginningOfAgentCallback(this);
			if(rc == false) {
				// スレッドの開始条件を満たしていない場合は，これをもって終了
				robot.AtTheEndOfAgentCallback(this);
				return;
			}

			log.trace("スレッドがrun()を開始しました");
			log.info("URL: " + con.getURL().toString());

			RSSHttpResponse res = null;
			try {
				res = RSSHttpURLConnectionFactory.getResponse(con, "8859_1");
				int responseCode = res.getResponseCode();

				if(robot.getSpiderConfig().isEnabledContentType(res.getHeader("Content-Type")) == false) {
					// 取得できないタイプの場合はデータを捨てる
					content = null;
				} else if(responseCode == java.net.HttpURLConnection.HTTP_OK) {
					// 正常にページの取得ができた場合
					log.trace("スレッドがコンテンツの読み込みを始めました");
					content = res.getText();
					log.trace("スレッドが接続を閉じています");
				} else if(
					responseCode == java.net.HttpURLConnection.HTTP_MOVED_PERM ||
					responseCode == java.net.HttpURLConnection.HTTP_MOVED_TEMP
				) {
					// 転送された場合
					String forwardedTo = res.getHeader("Location");
					if(forwardedTo != null) {
						log.trace("転送先:" + forwardedTo);
					}
				} else {
					// それ以外のステータス(500や403など)の場合は無視する
					content = null;
				}
			} catch(Exception e) {
				// 何らかのエラーが発生して接続できなかった
				String errmsg = "failed";
				if(res != null) {
					errmsg += "(" + res.getResponseCode() + ")";
				}
				errmsg += " : " + con.getURL().toString();

				log.info(errmsg);

				content = null;
			} finally {
				// 切断イベント発行
				log.trace("スレッドがonClose()イベントを発行しています");
				try {
					WebContents webContents = robot.getContentsAnalyzer().analyze(res, content);
					// 何らかのコンテンツが取得できたか，あるいはrobots.txtの場合はコンテンツを送信する．
					if(content != null || webContents.isRobotsTxtFlg() == true) {
						sendOnCloseEvent(new RSSWSAgentEvent(this, webContents));
					}
				} catch(Exception e) {
					log.error("コンテンツ登録処理中に予期しないエラーが発生したので無視します：", e);
				}
			}
		}
	}
	/**
	 * スレッドを停止する．
	 * スレッドが何か作業をしている場合，それらが完了した段階で安全に停止する．
	 */
	public void shutdown() {
		log.info("停止信号を受信しました．スレッドを停止します．");
		running = false;
		interrupt();
	}
	/**
	 * イベントリスナーの登録処理
	 * 既に登録されている場合、追加して登録できない
	 * @param listener イベントリスナー
	 */
	public void addRSSWSAgentListener(RSSWSAgentListener listener) throws TooManyListenersException {
		// イベントリスナーは1個だけしか登録できない
		// 複数のイベントリスナーを登録しようとした場合は，例外を投げる
		if(eventListener != null) {
			throw new TooManyListenersException();
		}
		eventListener = listener;
	}

	/**
	 * イベントリスナーの削除
	 */
	public void removeRSSWSAgentListener() {
		eventListener = null;
	}

	/**
	 * 接続イベントの送信
	 * @param e イベントオブジェクト
	 */
	public void sendOnConnectEvent(RSSWSAgentEvent e) {
		eventListener.onConnect(e);
	}

	/**
	 * 切断イベントの送信
	 * @param e イベントオブジェクト
	 */
	public void sendOnCloseEvent(RSSWSAgentEvent e) {
		eventListener.onClose(e);
	}
}

// end of RSSWebSpiderAgent.java
