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

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.logical_paradox.common.util.Cache;
import org.logical_paradox.httpd.html.ErrorPageGenerator;


/**
 * HTTPサーバー
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.1 $
 */
public class Server {
	/** HTTPサーバーの状態 - 初期状態 */
	public static final int E_INIT = 0;
	/** HTTPサーバーの状態 - クライアント接続待ち */
	public static final int E_WAITING = 1;
	/** HTTPサーバーの状態 - 終了中 */
	public static final int E_CLOSING = 100;
	/** HTTPサーバーの状態 - 停止 */
	public static final int E_CLOSED = 255;

	/** デフォルトのリクエスト受け取り待ち時間(1分) */
	public static final int DEFAULT_CONNECTION_WAIT_MSEC = 60000;
	/** サーバーソケットのaccept待ち状態継続時間 */
	public static final int SERVER_SOCKET_TIMEOUT_SEC = 5000;

	/** 現在のサーバーステータス */
	private int serverStatus = E_INIT;

	/** ロガー */
	private static final Log log = LogFactory.getLog(Server.class);

	/** サーバーソケット */
	private ServerSocket serverSocket;

	/** サーバーコンフィグレーション */
	private final Config config;
	/** リクエストを処理するスレッド */
	private RequestProcessor[] processor;
	/** リクエストプロセッサの空き待ち時間(msec) */
	private int waittime = DEFAULT_CONNECTION_WAIT_MSEC;
	/** サーバーソケットのaccept処理中断タイミング */
	private int serverSocketTimeoutSec = SERVER_SOCKET_TIMEOUT_SEC;

	/** 待機中のスレッドリスト */
	private Cache waitingThreads = new Cache();

	/**
	 * コンストラクタ
	 * @param c サーバーコンフィグレーション
	 */
	public Server(Config c) {
		config = c;
	}
	/**
	 * httpサーバーを開始する
	 */
	public void start() {
		// リクエストを処理するスレッドを生成する(設定されている最大数確保する)
		processor = new RequestProcessor[config.getThreads()];
		// デフォルトのプーリング数だけ最初にスレッドを作成しておく
		for(int i = 0; i < config.getThreads(); i++) {
			processor[i] = new RequestProcessor(i+1, this, config);
			waitingThreads.add(processor[i]);
			processor[i].start();
		}

		// 接続待ち状態に入る
		waiting();

		// 接続待ち状態が解除されたので，終了状態に入る
		try {
			shutdown();
		} catch (InterruptedException e) {
			log.warn("リクエストプロセッサスレッドの停止待ち処理が中断されました。強制停止します。");
		}
	}
	/**
	 * サーバーステータスを返す
	 * @return サーバーステータス
	 */
	public int getServerStatus() {
		return serverStatus;
	}
	/**
	 * サーバーステータスを設定する
	 * @param status サーバーステータス
	 */
	public void setServerStatus(int status) {
		serverStatus = status;
	}
	/**
	 * 待機中スレッドリストを返す
	 * @return 待機中スレッドリスト
	 */
	Cache getWaitingThreads() {
		return waitingThreads;
	}
	/**
	 * 接続待ち状態に入る
	 */
	protected void waiting() {
		try {
			// サーバーソケットの作成
			serverSocket = new ServerSocket(config.getPort());
			serverSocket.setSoTimeout(serverSocketTimeoutSec);

			// サーバーステータスを"接続待ち"に遷移させる
			setServerStatus(E_WAITING);

			// litening開始
			log.info("ポート番号[" + config.getPort() + "]でlistenしています");

			// サーバーステータスがWAITINGの間待ち続ける
			while(serverStatus == E_WAITING) {
				Socket clientSocket = null;
				try {
					clientSocket = serverSocket.accept();
				} catch(SocketTimeoutException soe) {
					// 一定間隔でサーバーのステータス変更を検出するための処置なので
					// サーバーソケットがタイムアウトしたからといっても何もしない(ループを最初から継続)
					continue;
				}
				// リクエストプロセッサスレッドに処理を引き継ぐ
				try {
					RequestProcessor processor = getRequestProcessor();
					processor.execute(clientSocket);
				} catch (InterruptedException e1) {
					log.warn("" + waittime + "msec待ちましたが、リクエストプロセッサの取得ができませんでした");
					// サーバーbusyのメッセージ送信
					ServerResponse serverResponse = new ServerResponse(new ResponseHeader(), ErrorPageGenerator.getInstance().generateErrorPage(503));
					try {
						RequestProcessor.send(clientSocket, serverResponse);
					} catch(IOException e) {
						// エラーページ送信中にエラーが発生した
						log.warn("エラーページ送信中に入出力エラーが発生しました");
					}
				}
			}
		} catch (IOException e) {
			// bindに失敗したなどの例外が発生した
			e.printStackTrace();
			log.error("ポート番号[" + config.getPort() + "でlistening状態に入ることができませんでした");
			return;
		}
	}
	/**
	 * httpサーバーを停止する
	 */
	public void shutdown() throws InterruptedException {
		setServerStatus(E_CLOSING);

		log.info("httpサーバーのシャットダウンを開始しました");
		log.info("全ての待ち受けスレッドを停止しています");
		for(int i = 0; i < processor.length; i++) {
			processor[i].tearDown();
		}
		// リクエストプロセッサスレッドの停止待ち
		for(int i = 0; i < processor.length; i++) {
			processor[i].join();
		}
		log.info("全ての待ち受けスレッドが停止しました");

		// 全てのスレッドが無事に停止した場合、ステータス変更
		setServerStatus(E_CLOSED);
	}
	/**
	 * 現在暇にしているリクエストプロセッサを返す
	 * しばらく待ってみて、取れないようならサーバーbusyとする
	 * @return 取得されたリクエストプロセッサ
	 * @throws InterruptedException 待ち処理がタイムアウトした
	 */
	protected RequestProcessor getRequestProcessor() throws InterruptedException {
		synchronized(waitingThreads) {
			while(waitingThreads.size() == 0) {
				waitingThreads.wait(waittime);
			}
			// 1つスレッドを取り出す
			return (RequestProcessor)waitingThreads.getOne();
		}
	}
	/**
	 * メインメソッド
	 * webserverを起動します
	 * @param args プログラム引数
	 * @throws Exception 処理されなかった例外
	 */
	public static final void main(String[] args) throws Exception {
		log.info("httpサーバーを起動しています");
		// httpサーバーの起動
		Server server = new Server(new Config(args));
		server.start();

		log.info("httpサーバーを停止しました");
	}
}
