/*
 * Created on 2004-6-18
 *
 * 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.io.IOException;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.MarshalledObject;
import java.rmi.Remote;
import java.rmi.server.UnicastRemoteObject;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.huihoo.willow.Globals;
import org.huihoo.willow.LifecycleException;
import org.huihoo.willow.NamingProvider;
import org.huihoo.willow.client.NamingImpl;
import org.huihoo.willow.naming.WillowContext;
import org.huihoo.willow.naming.ContextFactory;
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 class StandardNamingProvider extends ProviderBase implements NamingProvider
{
	private static Log log = LogFactory.getLog(StandardNamingProvider.class);

	// ----------------------------------------------------- Instance Variables
	/**
	 * Descriptive information about this NamingProvider implementation.
	 */
	private static final String info = "org.huihoo.willow.core.StandardNamingProvider/1.0";
	/**
	 * The request scheme that will be set on all requests received
	 * through this namingProvider.
	 */
	private String scheme = "workflow";

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

	/** The Naming interface server implementation */
	private NamingImpl namingServer;
	private MarshalledObject serverStub;

	/** The naming server socket through which the NamingImpl stub is vended */
	private ServerSocket serverSocket;

	/**
	 * The background thread.
	 */
	private Thread thread = null;

	/**
	 * The background thread completion semaphore.
	 */
	private boolean threadDone = false;

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

	/**
	 * Return descriptive information about this NamingProvider implementation.
	 */
	public String getInfo()
	{

		return (info);

	}

	/**
	 * Return the scheme that will be assigned to requests received
	 * through this namingProvider.  Default value is "http".
	 */
	public String getScheme()
	{

		return (this.scheme);

	}

	/**
	 * Set the scheme that will be assigned to requests received through
	 * this namingProvider.
	 *
	 * @param scheme The new scheme
	 */
	public void setScheme(String scheme)
	{

		this.scheme = scheme;
		setProperty("scheme", scheme);

	}

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

	/**
	 * Begin processing requests via this NamingProvider.
	 *
	 * @exception LifecycleException if a fatal startup error occurs
	 */
	public void start() throws LifecycleException
	{
		// Validate and update our current state
		if (started)
		{
			log.info(sm.getString("standardNamingProvider.alreadyStarted"));
			return;
		}

		try
		{
			namingServer = new NamingImpl();
			WillowContext willowContext=ContextFactory.getWillowContext();
			willowContext.setGlobalNamingImpl(namingServer);
		}
		catch (Throwable e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		lifecycle.fireLifecycleEvent(START_EVENT, null);
		threadStart();
		
		super.start();
	}

	/**
	 * Terminate processing requests via this NamingProvider.
	 *
	 * @exception LifecycleException if a fatal shutdown error occurs
	 */
	public void stop() throws LifecycleException
	{

		// Validate and update our current state
		if (!started)
		{
			log.error(sm.getString("standardNamingProvider.notStarted"));
			return;

		}
		lifecycle.fireLifecycleEvent(STOP_EVENT, null);

		// Stop the existing server
		threadStop();
		super.stop();
	}

	/**
	 * Start the background thread that will periodically check for
	 * session timeouts.
	 */
	protected void threadStart()
	{

		if (thread != null)
			return;

		try
		{
			Remote namingStub = UnicastRemoteObject.exportObject(namingServer, Globals.PORT_RMI_EXPORT);
			log.debug("NamingImpl stub: " + namingStub);
			serverStub = new MarshalledObject(namingStub);
			serverSocket = new ServerSocket(port, acceptCount, InetAddress.getByName(address));
		}
		catch (Exception e)
		{
			log.fatal(sm.getString("standardNamingProvider.startFailed", e));
			return;
		}

		threadDone = false;
		String threadName = "Willow NamingProvider[" + toString() + "]";
		thread = new Thread(new ListenerProcessor(), threadName);
		thread.setDaemon(true);
		thread.start();

	}

	/**
	 * Stop the background thread that is periodically checking for
	 * session timeouts.
	 */
	protected void threadStop()
	{

		if (thread == null)
			return;

		threadDone = true;
		
		WillowContext willowContext=ContextFactory.getWillowContext();
		willowContext.setGlobalNamingImpl(null);		
		try
		{
			Socket s = null;
			// Need to create a connection to unlock the accept();
			if (address == null)
			{
				s = new Socket("127.0.0.1", port);
			}
			else
			{
				s = new Socket(address, port);				
			}
			
			s.close();
		}
		catch (Exception e)
		{
			log.error("Caught exception trying to unlock accept on " + port + " " + e.toString());
		}
		
		try
		{
			serverSocket.close();
			UnicastRemoteObject.unexportObject(namingServer, true);	
		}
		catch (Exception e)
		{
			log.error("Caught exception trying to close socket.", e);
		}
		serverSocket = null;
		namingServer=null;
		thread = null;
	}

	protected class ListenerProcessor implements Runnable
	{
		public void run()
		{
			while (!threadDone)
			{
				Socket socket = null;
				// Accept a connection
				try
				{
					socket = serverSocket.accept();
				}
				catch (IOException e)
				{
				}

				if (threadDone)
				{					
					if(socket!=null)
					{
						try{socket.close();}catch(Exception ex){}
					}
					break;
				}
				
				new Thread(new ServiceProcessor(socket)).start();
			}
		}
	}

	protected class ServiceProcessor implements Runnable
	{
		private Socket socket;
		public ServiceProcessor(Socket socket)
		{
			this.socket = socket;
		}
		public void run()
		{
			//Return the naming server stub
			try
			{
				ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
				out.writeObject(serverStub);
			}
			catch (IOException ex)
			{
				log.error("Error writing response", ex);
			}
		}
	}

}