/* 
 * E-XML Library:  For XML, XML-RPC, HTTP, and related.
 * Copyright (C) 2002-2008  Elias Ross
 * 
 * genman@noderunner.net
 * http://noderunner.net/~genman
 * 
 * 1025 NE 73RD ST
 * SEATTLE WA 98115
 * USA
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * $Id$
 */

package net.noderunner.http;

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

/**
 * This class is for unit testing.  It accepts one request at a time in a single thread.
 * Override the handleRequest method to do something interesting.
 *
 * @author Elias Ross
 * @version 1.0
 */
public abstract class ThreadedHttpServer
	implements Runnable
{
	protected ServerSocket ss;
	private Thread t;
	private volatile boolean run = true;

	/**
	 * Constructs a new SingleHttpServer, initialized with a server socket.
	 * @param ss server socket to listen with
	 */
	public ThreadedHttpServer(ServerSocket ss)
	{
		this.ss = ss;
	}
	
	public ThreadedHttpServer()
		throws IOException
	{
		ss = new ServerSocket(0);
	}
	
	/**
	 * Starts listening in a new thread.
	 */
	public void start() {
		t = new Thread(this);
		t.setName("listen-" + ss.getLocalSocketAddress());
		t.setDaemon(true);
		t.start();
	}

	public int getPort() {
		return ss.getLocalPort();
	}
	
	/**
	 * Inner class for handling a single HTTP server request.
	 */
	public class Request {
		
    	private HttpServer server;
    	private InetSocketAddress address;
    	
		/**
		 * Constructs a new Request.
		 * @param server
		 * @param address
		 */
		public Request(HttpServer server, InetSocketAddress address) {
			this.server = server;
			this.address = address;
		}

		/**
		 * Returns address.
		 */
		public InetSocketAddress getRemoteAddress() {
			return address;
		}

		/**
		 * Returns server.
		 */
		public HttpServer getServer() {
			return server;
		}
		
	}

	/**
	 * Override this method to handle the request.
	 * You may optionally process this request asynchronously.
	 * @param s server request
	 * @param address address of request 
	 * @throws IOException
	 */
	protected abstract void handleRequest(Request request)
		throws IOException;
	
	/**
	 * Called when an exception occurs when listening; does nothing now.
	 */
	protected void exception(Exception e) {
	}

	public final void run() {
		while (run) {
			Socket socket = null;
			try {
				socket = ss.accept();
				socket.setTcpNoDelay(true);
				new SocketHandler(socket).start();
			} catch (IOException e) {
				if (socket != null) {
					try {
						socket.close();
					} catch (IOException e2) { }
				}
				if (run) 
    				exception(e);
			}
		}
	}
	
	private class SocketHandler extends Thread {
		
		private Socket socket;
		
		/**
		 * Constructs a new SocketHandler.
		 * @param socket
		 */
		public SocketHandler(Socket socket) {
			this.socket = socket;
			setName("handle-" + socket.getInetAddress());
		}
		
		public final void run() {
			try {
    			HttpServer server = new BasicHttpServer(socket);
    			while (true) {
    				Request request = new Request(server, (InetSocketAddress)socket.getRemoteSocketAddress());
    				handleRequest(request);
    			}
			} catch (IOException e) {
				exception(e);
			} finally {
				try {
					socket.close();
				} catch (IOException e) {
    				exception(e);
				}
			}
		}
	}

	/**
	 * Closes as soon as possible.
	 */
	public void close() throws IOException {
		run = false; 
		if (ss != null)
    		ss.close();
		ss = null;
		if (t == null)
			return;
		t.interrupt();
		try {
			t.join();
		} catch (InterruptedException e) {
			exception(e);
			Thread.currentThread().interrupt();
		}
	}

	/**
	 * Interrupts processing.
	 */
	void interrupt() throws IOException {
		t.interrupt();
	}

	/**
	 * Returns a debug string.
	 */
	public String toString() {
		return "SingleHttpServer ss=" + ss;
	}

}
