/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This 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 software 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.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.remoting.transport.async.bio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;

import org.jboss.logging.Logger;
import org.jboss.remoting.InvokerLocator;
import org.jboss.remoting.transport.async.ChannelPool;
import org.jboss.remoting.transport.async.ChannelServer;
import org.jboss.remoting.transport.async.Registry;

/**
 * Provides a Blocking implemenation of the AsynchChannelServer interface.
 * 
 * Sets up a blocking ServerSocket to accept blocking client connections.
 * 
 * @author <a href="mailto:hiram@coredevelopers.net">Hiram Chirino</a>
 */
public final class BlockingServer
	implements java.lang.Runnable, ChannelServer {

	private boolean enableTcpNoDelay;
	final static private Logger log = Logger.getLogger(BlockingServer.class);

	/**
	 * The default timeout for the server socket. This is
	 * set so the socket will periodically return to check
	 * the running flag.
	 */
	private final static int SO_TIMEOUT = 5000;
	private ServerSocket serverSocket;
	private InvokerLocator uri;
	private InvokerLocator connectURI;
	private Thread worker;
	private boolean running;
	private int compression=-1;

	/* 
	 * @see org.jboss.remoting.transport.async.AsynchChannelServer#bind(org.jboss.remoting.InvokerLocator)
	 */
	public void bind(InvokerLocator localURI) throws IOException {

		this.uri = localURI;

		String serverBindAddress = uri.getHost();
		String clientConnectAddress = null;
		int serverBindPort = uri.getPort();
		int clientConnectPort = serverBindPort;

		int connectBackLog = 50;
		enableTcpNoDelay = true;

		if( uri.getParameters()!=null ) {
			if (uri.getParameters().get("tcp.nodelay") != null)
				enableTcpNoDelay =
					!uri.getParameters().get("tcp.nodelay").equals("false");
			if (uri.getParameters().get("tcp.backlog") != null)
				connectBackLog =
					Integer.parseInt(
						(String) uri.getParameters().get("tcp.backlog"));
			if (uri.getParameters().get("client.host") != null)
				clientConnectAddress =
					(String) uri.getParameters().get("client.host");
			if (uri.getParameters().get("client.port") != null)
				clientConnectPort =
					Integer.parseInt(
						(String) uri.getParameters().get("client.port"));
			if (uri.getParameters().get("compression") != null)
				clientConnectPort =
					Integer.parseInt(
						(String) uri.getParameters().get("compression"));
			compression = Math.min(compression, 9);
			compression = Math.max(compression, -1);
		}

		serverSocket =
			new ServerSocket(
				serverBindPort,
				connectBackLog,
				InetAddress.getByName(serverBindAddress));
		serverSocket.setSoTimeout(SO_TIMEOUT);

		// Lookup the local host name if needed.
		clientConnectAddress =
			(clientConnectAddress == null
				|| clientConnectAddress.length() == 0)
				? InetAddress.getLocalHost().getHostName()
				: clientConnectAddress;
		clientConnectPort =
			(clientConnectPort <= 0)
				? serverSocket.getLocalPort()
				: clientConnectPort;

		// Create the client URI:
		HashMap clientParms = new HashMap();
		clientParms.put("tcp.nodelay", enableTcpNoDelay ? "true" : "false");
		clientParms.put("compression", ""+compression);
		this.connectURI =
			new InvokerLocator(
				"async",
				clientConnectAddress,
				clientConnectPort,
				"",
				clientParms);

		log.info(
			"Remoting 'async' protocol available at: "
				+ serverSocket.getInetAddress()
				+ ":"
				+ serverSocket.getLocalPort());
		log.info(
			"Remoting 'async' protocol clients will connect to: " + connectURI);
	}

	/* (non-Javadoc)
	 * @see org.jboss.remoting.transport.async.AsynchChannelServer#getLocalURI()
	 */
	public InvokerLocator getLocalURI() {
		return connectURI;
	}

	/* (non-Javadoc)
	 * @see org.jboss.remoting.transport.async.AsynchChannelServer#start()
	 */
	synchronized public void start() throws IOException {
		if (running)
			return;
		running = true;
		worker =
			new Thread(this, "Acceptor "+getLocalURI().getLocatorURI());
		worker.setDaemon(true);
		worker.start();
	}

	/* (non-Javadoc)
	 * @see org.jboss.remoting.transport.async.AsynchChannelServer#stop()
	 */
	public void stop() {
		if (!running)
			return;
		running = false;

		try {
			worker.interrupt();
			worker.join();
		} catch (InterruptedException e) {
		}
	}

	public void close() {
		try {
			serverSocket.close();
		} catch (Exception e) {
			log.debug("error closing server socket", e);
		}
		return;
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
	public void run() {
		
		try {
			while (running) {
				Socket socket = null;
				try {
					socket = serverSocket.accept();
					if (log.isTraceEnabled())
						log.trace("Accepted connection: " + socket);
				} catch (java.io.InterruptedIOException e) {
					// It's ok, this is due to the SO_TIME_OUT
					continue;
				}

				try {
					socket.setTcpNoDelay(enableTcpNoDelay);

					BlockingChannel channel = new BlockingChannel();
					channel.init(connectURI,socket);
					ChannelPool pool = Registry.getChannelPool(channel.getRemoteURI());
					pool.associate(channel);

				} catch (IOException ie) {
					log.debug("Client connection could not be accepted: ", ie);
				}
			}
		} catch (SocketException e) {
			// There is no easy way (other than string comparison) to
			// determine if the socket exception is caused by connection
			// reset by peer. In this case, it's okay to ignore both
			// SocketException and IOException.
			log.warn("SocketException occured (Connection reset by peer?). Shutting down remoting 'async' protocol.");
		} catch (IOException e) {
			log.warn("IOException occured. Shutting down remoting 'async' protocol.");
		} 
	}

}
// vim:expandtab:tabstop=3:shiftwidth=3
