/*
 * LOGICAL-PARADOX.ORG
 * Copyright (C)2005 satoshi akabane(akabane@logical-paradox.org)
 * $Id: RequestProcessor.java,v 1.1 2005/05/11 17:48:20 rampil Exp $
 */
package org.logical_paradox.httpd;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Iterator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.logical_paradox.common.util.Cache;
import org.logical_paradox.httpd.exception.*;
import org.logical_paradox.httpd.html.ErrorPageGenerator;
import org.logical_paradox.httpd.method.HttpMethodHandler;
import org.logical_paradox.httpd.method.MethodHandlerFactory;

/**
 * リクエストを処理するスレッド
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.1 $
 */
public class RequestProcessor extends Thread {
	/** ロガー */
	private Log log = LogFactory.getLog(RequestProcessor.class);

	/** 現在処理しているソケット */
	private Socket socket;
	/** スレッド番号 */
	private int threadNo;
	/** サーバーのインスタンス */
	private Server parent;
	/** 待機中スレッドキュー */
	private Cache queue;
	/** サーバーの起動オプション */
	private final Config config;

	private final String lock = "lock";

	/** シャットダウン処理時にクライアントとの接続完了を待つ秒数(msec) */
	public static final int SHUTDOWN_TIMEOUT_SEC = 60000;
	/**
	 * コンストラクタ
	 * @param no スレッド番号
	 * @param server サーバーインスタンス
	 */
	public RequestProcessor(int no, Server server, Config c) {
		threadNo = no;
		parent = server;
		queue = server.getWaitingThreads();
		config = c;
	}
	/**
	 * ソケットを設定する
	 * ソケットの設定がトリガーとなって、スレッドがsleepから脱出して再開される
	 * @param s ソケット
	 */
	public void execute(Socket s) {
		log.trace("リクエストを受付ました:" + s.toString());
		socket = s;
		// lockオブジェクトで待っているスレッドを起こす
		synchronized(lock) {
			lock.notifyAll();
		}
	}
	/**
	 * スレッド番号を返す
	 * @return スレッド番号
	 */
	public int getThreadNo() {
		return threadNo;
	}
	/**
	 * 待機中かどうかを返す
	 * @return true:待機中 / false:処理中
	 */
	public boolean isWaiting() {
		return socket == null;
	}
	/**
	 * スレッドメイン
	 */
	public void run() {
		try {
			while(true) {
				synchronized(lock) {
					while(socket == null) {
						lock.wait();
					}
					ClientRequest clientRequest = null;
					ServerResponse serverResponse = null;

					try {
						// ソケットからリクエストの内容を受け取る
						BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
						StringBuffer request = new StringBuffer();
						String line = null;

						while((line = br.readLine()) != null && line.trim().length() > 0) {
							request.append(line + "\n");
						}

						// リクエストオブジェクトの作成
						clientRequest = new ClientRequest(request.toString(), config);

						// リクエストパラメータが正しい形式の場合，リクエストを処理する
						if(clientRequest != null) {
							// リクエストを処理するべきメソッドのハンドラを取得する
							HttpMethodHandler handler = MethodHandlerFactory.getInstance().getMethodHandler(clientRequest.getMethod());
							if(handler == null) {
								// メソッドハンドラが登録されていないので，このリクエストはエラーとする
							} else {
								serverResponse = handler.execute(clientRequest);
							}
						}
					} catch (HttpException e) {
						// ドキュメント取得処理中に発生した例外(Not FoundやForbiddenなど)
						log.warn("ドキュメントの取得に失敗しました: HTTPステータスコード[" + e.getHttpStatusCode() + "]");
						serverResponse = new ServerResponse(new ResponseHeader(), ErrorPageGenerator.getInstance().generateErrorPage(e.getHttpStatusCode()));
					} catch (TerminationException e) {
						// 終了シグナルを受信したのでサーバーの停止を開始する
						parent.setServerStatus(Server.E_CLOSING);
						log.info("サーバーの停止信号を受信しました");
						serverResponse = new ServerResponse(new ResponseHeader(), ErrorPageGenerator.getInstance().generateErrorPage(202));
					} catch (IOException e) {
						// ソケットI/O処理中に通信エラーが発生した
						// よくあることなので別に気にしない
						log.warn("クライアントとの間で通信エラーが発生しました");
						serverResponse = null;
					} finally {
						// クライアントとの接続をクローズする
						try {
							if(socket != null && socket.isConnected() == true) {
								if(serverResponse != null) {
									// クライアントソケットとまだ繋がっている場合はサーバーからのレスポンスを送信する
									send(socket, serverResponse);
								}
								socket.close();
							}
						} catch (IOException e1) {
							log.warn("クライアントが全てのデータを受信していない可能性があります");
						} finally {
							socket = null;
							queue.add(this);
						}
					}
				}
			}
		} catch(InterruptedException e) {
			tearDown();
		}
	}
	/**
	 * クライアントソケットに対してサーバーからのレスポンスを送信する
	 * @param client クライアントソケット
	 * @param response 送信内容
	 * @throws IOException 入出力例外
	 */
	public static void send(Socket client, ServerResponse response) throws IOException {
		BufferedWriter out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));

		// HTTPステータス
		out.write(response.getHttpStatusCode() + "\r\n");

		// ヘッダの送信
		for(Iterator it = response.headers(); it.hasNext();) {
			out.write(it.next().toString() + "\r\n");
		}
		out.write("\r\n");

		// データの送信
		out.write(response.getData());

		out.flush();
		out.close();
	}
	/**
	 * スレッドの終了処理
	 */
	protected void tearDown() {
		log.info("スレッド[" + getThreadNo() + "]の停止処理中");
		int waitcnt = SHUTDOWN_TIMEOUT_SEC / 500;
		try {
			while(socket != null && waitcnt > 0) {
				waitcnt--;
				Thread.sleep(500);
			}
		} catch (InterruptedException e) {
			log.warn("リクエストプロセッサの停止待ち中に割り込みが発生しました");
		}
		interrupt();
		log.info("スレッド[" +  getThreadNo() + "]の停止処理完了");
	}
}
