/*
 * Created on 2004-6-8
 *
 * To change the template for this generated file go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */
package org.huihoo.willow.core;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.AccessControlException;
import java.util.Random;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.huihoo.willow.Globals;
import org.huihoo.willow.Lifecycle;
import org.huihoo.willow.LifecycleException;
import org.huihoo.willow.LifecycleListener;
import org.huihoo.willow.Server;
import org.huihoo.willow.ServerFactory;
import org.huihoo.willow.Service;
import org.huihoo.willow.naming.WillowContext;
import org.huihoo.willow.naming.ContextFactory;
import org.huihoo.willow.util.LifecycleSupport;
import org.huihoo.willow.util.StringManager;


/**
 * @author reic
 *
 * To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */
public final class StandardServer implements Lifecycle, Server
{
	private static Log log = LogFactory.getLog(StandardServer.class);

	/**
	 * ServerLifecycleListener classname.
	 */
	private static String SERVER_LISTENER_CLASS_NAME ="org.huihoo.willow.core.ServerLifecycleListener";

	// ------------------------------------------------------------ Constructor

	/**
	 * Construct a default instance of this class.
	 */
	public StandardServer()
	{
		super();
		ServerFactory.setServer(this);
		ContextFactory.setWillowContext(new WillowContext("Willow.WillowContext"));
	}

	// ----------------------------------------------------- Instance Variables

	/**
	 * Debugging detail level.
	 */
	private int debug = 0;

	/**
	 * Descriptive information about this NamingServer implementation.
	 */
	private static final String info = "org.huihoo.willow.core.StandardServer/1.0";

	/**
	 * The lifecycle event support for this component.
	 */
	private LifecycleSupport lifecycle = new LifecycleSupport(this);

	/**
	 * The port number on which we wait for shutdown commands.
	 */
	private int port = 8023;

	/**
	 * A random number generator that is <strong>only</strong> used if
	 * the shutdown command string is longer than 1024 characters.
	 */
	private Random random = null;

	/**
	 * The set of Services associated with this NamingServer.
	 */
	private Service services[] = new Service[0];

	/**
	 * The shutdown command string we are looking for.
	 */
	private String shutdown = "SHUTDOWN";

	/**
	 * The string manager for this package.
	 */
	private static final StringManager sm = StringManager.getManager(Constants.PACKAGE);

	/**
	 * Has this component been started?
	 */
	private boolean started = false;

	/**
	 * The property change support for this component.
	 */
	protected PropertyChangeSupport support = new PropertyChangeSupport(this);

	// ------------------------------------------------------------- Properties

	/**
	 * Return the debugging detail level.
	 */
	public int getDebug()
	{

		return (this.debug);

	}

	/**
	 * Set the debugging detail level.
	 *
	 * @param debug The new debugging detail level
	 */
	public void setDebug(int debug)
	{

		this.debug = debug;

	}


	/**
	 * Return descriptive information about this NamingServer implementation and
	 * the corresponding version number, in the format
	 * <code>&lt;description&gt;/&lt;version&gt;</code>.
	 */
	public String getInfo()
	{

		return (info);

	}

	/**
	 * Return the port number we listen to for shutdown commands.
	 */
	public int getPort()
	{

		return (this.port);

	}

	/**
	 * Set the port number we listen to for shutdown commands.
	 *
	 * @param port The new port number
	 */
	public void setPort(int port)
	{

		this.port = port;

	}

	/**
	 * Return the shutdown command string we are waiting for.
	 */
	public String getShutdown()
	{

		return (this.shutdown);

	}

	/**
	 * Set the shutdown command we are waiting for.
	 *
	 * @param shutdown The new shutdown command
	 */
	public void setShutdown(String shutdown)
	{

		this.shutdown = shutdown;

	}

	// --------------------------------------------------------- NamingServer Methods

	/**
	 * Add a new Service to the set of defined Services.
	 *
	 * @param service The Service to be added
	 */
	public void addService(Service service)
	{

		service.setServer(this);

		synchronized (services)
		{
			Service results[] = new Service[services.length + 1];
			System.arraycopy(services, 0, results, 0, services.length);
			results[services.length] = service;
			services = results;

			if (started && (service instanceof Lifecycle))
			{
				try
				{
					((Lifecycle) service).start();
				}
				catch (LifecycleException e)
				{
					;
				}
			}

			// Report this property change to interested listeners
			support.firePropertyChange("service", null, service);
		}

	}

	/**
	 * Wait until a proper shutdown command is received, then return.
	 */
	public void await()
	{

		// Set up a server socket to wait on
		ServerSocket serverSocket = null;
		try
		{
			port=(port<=0)?Globals.PORT_SHUTDOWN:port;
			serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
		}
		catch (IOException e)
		{
			System.err.println("StandardServer.await: create[" + port + "]: " + e);
			e.printStackTrace();
			System.exit(1);
		}

		// Loop waiting for a connection and a valid command
		while (true)
		{

			// Wait for the next connection
			Socket socket = null;
			InputStream stream = null;
			try
			{
				socket = serverSocket.accept();
				socket.setSoTimeout(10 * 1000); // Ten seconds
				stream = socket.getInputStream();
			}
			catch (AccessControlException ace)
			{
				System.err.println("StandardServer.accept security exception: " + ace.getMessage());
				continue;
			}
			catch (IOException e)
			{
				System.err.println("StandardServer.await: accept: " + e);
				e.printStackTrace();
				System.exit(1);
			}

			// Read a set of characters from the socket
			StringBuffer command = new StringBuffer();
			int expected = 1024; // Cut off to avoid DoS attack
			while (expected < shutdown.length())
			{
				if (random == null)
					random = new Random(System.currentTimeMillis());
				expected += (random.nextInt() % 1024);
			}
			while (expected > 0)
			{
				int ch = -1;
				try
				{
					ch = stream.read();
				}
				catch (IOException e)
				{
					System.err.println("StandardServer.await: read: " + e);
					e.printStackTrace();
					ch = -1;
				}
				if (ch < 32) // Control character or EOF terminates loop
					break;
				command.append((char) ch);
				expected--;
			}

			// Close the socket now that we are done with it
			try
			{
				socket.close();
			}
			catch (IOException e)
			{
				;
			}

			// Match against our command string
			boolean match = command.toString().equals(shutdown);
			if (match)
			{
				break;
			}
			else
				System.err.println(
					"StandardServer.await: Invalid command '" + command.toString() + "' received");

		}

		// Close the server socket and return
		try
		{
			serverSocket.close();
		}
		catch (IOException e)
		{
			;
		}

	}

	/**
	 * Return the specified Service (if it exists); otherwise return
	 * <code>null</code>.
	 *
	 * @param name Name of the Service to be returned
	 */
	public Service findService(String name)
	{

		if (name == null)
		{
			return (null);
		}
		synchronized (services)
		{
			for (int i = 0; i < services.length; i++)
			{
				if (name.equals(services[i].getName()))
				{
					return (services[i]);
				}
			}
		}
		return (null);

	}

	/**
	 * Return the set of Services defined within this NamingServer.
	 */
	public Service[] findServices()
	{

		return (services);

	}

	/**
	 * Remove the specified Service from the set associated from this
	 * NamingServer.
	 *
	 * @param service The Service to be removed
	 */
	public void removeService(Service service)
	{

		synchronized (services)
		{
			int j = -1;
			for (int i = 0; i < services.length; i++)
			{
				if (service == services[i])
				{
					j = i;
					break;
				}
			}
			if (j < 0)
				return;
			if (services[j] instanceof Lifecycle)
			{
				try
				{
					((Lifecycle) services[j]).stop();
				}
				catch (LifecycleException e)
				{
					;
				}
			}
			int k = 0;
			Service results[] = new Service[services.length - 1];
			for (int i = 0; i < services.length; i++)
			{
				if (i != j)
					results[k++] = services[i];
			}
			services = results;

			// Report this property change to interested listeners
			support.firePropertyChange("service", service, null);
		}

	}

	// --------------------------------------------------------- Public Methods

	/**
	 * Add a property change listener to this component.
	 *
	 * @param listener The listener to add
	 */
	public void addPropertyChangeListener(PropertyChangeListener listener)
	{

		support.addPropertyChangeListener(listener);

	}

	/**
	 * Remove a property change listener from this component.
	 *
	 * @param listener The listener to remove
	 */
	public void removePropertyChangeListener(PropertyChangeListener listener)
	{

		support.removePropertyChangeListener(listener);

	}

	/**
	 * Return a String representation of this component.
	 */
	public String toString()
	{

		StringBuffer sb = new StringBuffer("StandardServer[");
		sb.append(getPort());
		sb.append("]");
		return (sb.toString());

	}

	// ------------------------------------------------------ Lifecycle Methods

	/**
	 * Add a LifecycleEvent listener to this component.
	 *
	 * @param listener The listener to add
	 */
	public void addLifecycleListener(LifecycleListener listener)
	{

		lifecycle.addLifecycleListener(listener);

	}

	/**
	 * Get the lifecycle listeners associated with this lifecycle. If this
	 * Lifecycle has no listeners registered, a zero-length array is returned.
	 */
	public LifecycleListener[] findLifecycleListeners()
	{

		return lifecycle.findLifecycleListeners();

	}

	/**
	 * Remove a LifecycleEvent listener from this component.
	 *
	 * @param listener The listener to remove
	 */
	public void removeLifecycleListener(LifecycleListener listener)
	{

		lifecycle.removeLifecycleListener(listener);

	}

	/**
	 * Prepare for the beginning of active use of the public methods of this
	 * component.  This method should be called before any of the public
	 * methods of this component are utilized.  It should also send a
	 * LifecycleEvent of type START_EVENT to any registered listeners.
	 *
	 * @exception LifecycleException if this component detects a fatal error
	 *  that prevents this component from being used
	 */
	public void start() throws LifecycleException
	{

		// Validate and update our current component state
		if (started)
		{
			log.debug(sm.getString("standardServer.start.started"));
			return;
		}
		
		if(ContextFactory.getWillowContext()==null)
		{
			throw new LifecycleException(sm.getString("standardServer.notStarted"));
		}
		
		// Notify our interested LifecycleListeners
		lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);

		lifecycle.fireLifecycleEvent(START_EVENT, null);
		started = true;

		// Start our defined Services
		synchronized (services)
		{
			for (int i = 0; i < services.length; i++)
			{
				if (services[i] instanceof Lifecycle)
				{
					((Lifecycle) services[i]).start();
				}
			}
		}

		// Notify our interested LifecycleListeners
		lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);

	}

	/**
	 * Gracefully terminate the active use of the public methods of this
	 * component.  This method should be the last one called on a given
	 * instance of this component.  It should also send a LifecycleEvent
	 * of type STOP_EVENT to any registered listeners.
	 *
	 * @exception LifecycleException if this component detects a fatal error
	 *  that needs to be reported
	 */
	public void stop() throws LifecycleException
	{

		// Validate and update our current component state
		if (!started)
		{
			return;
		}

		// Notify our interested LifecycleListeners
		lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);

		lifecycle.fireLifecycleEvent(STOP_EVENT, null);
		started = false;

		// Stop our defined Services
		for (int i = 0; i < services.length; i++)
		{
			if (services[i] instanceof Lifecycle)
			{
				((Lifecycle) services[i]).stop();
			}
		}

		// Notify our interested LifecycleListeners
		lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
	}

}