package demodispatch;

import java.util.*;
import java.io.*;
import java.net.URL;

import javax.servlet.http.*;

import jet.server.api.*;
import jet.server.api.http.*;
import jet.server.api.rmi.*;

/**
 * This is a demo of session level dispatcher of server cluster.
 * It dispatches request to a remote server cluster according to Round-Robin algorithm.
 * All hosts and ports of servers in cluster are acquired from a configuration file 
 * which has the following format rmiserver=<host>:<port> of each line
 * There should be more than one line in the file and each line contains the host and port of one server.
 * The location of the configuration file can be defined by the system property, e.g.
 * -Dcom.jinfonet.dispatcher.configFile = "C:\temp\hostport.properties"
 */
public class DemoRemoteDispatcher implements RemoteDispatcher {

	private static final String CONFIG_FILE = "com.jinfonet.dispatcher.configFile";

    /**
     * interval of updating dispatchers connection's status.
     * unit: ms
     */
    private static final int UPDATE_INTERVAL = 30000;
    
    private static Hashtable htSession = new Hashtable();
	private int num = 0;
    private boolean isGot = false;
    private Vector dispatchers = new Vector(2); 

    private MonitorThread monitor = new MonitorThread();
    private class MonitorThread extends Thread
    {
        public void run()
        {
            while(true)
            {
                try
                {
                    Thread.sleep(UPDATE_INTERVAL);
                    updateAllDispatchersStatus();                
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
        }        
    }
    
    private synchronized void updateAllDispatchersStatus()
    {
        System.out.println("\n" + new Date(System.currentTimeMillis()) + ": updating all dispatchers' status... ");
        Dispatcher dispatcher = null;
        for(int i = 0; i < this.dispatchers.size(); i++)
        {
            dispatcher = (Dispatcher) this.dispatchers.get(i);
            updateDispatcherStatus(dispatcher);
        }
        System.out.println("updating done, all dispatchers' status: " + this.dispatchers);
    }
    
    /**
     * @param dispatcher
     */
    private void updateDispatcherStatus(Dispatcher dispatcher)
    {
        boolean isConnectable = this.isConnectable(dispatcher.host, dispatcher.port);
        //only print change status
        if(dispatcher.isConnectable ^ isConnectable)
        {
            String statusChange = "";
            if(isConnectable) 
            {
                statusChange = " recover";
                
            }
            else
            {
                statusChange = " disconnect";
            }
            System.out.println(dispatcher.host + ":" + dispatcher.port + statusChange);
        }
        
        dispatcher.isConnectable = isConnectable;
    }

    private static final String HTTPHEAD="http://";
    private boolean isConnectable(String host, String port)
    {
        String urlString = HTTPHEAD + host + ":" + port;
        try
        {
            URL url = new URL(urlString);
            url.openConnection().connect(); 
        }
        catch (Exception e)
        {
            return false;
        }
        
        return true;
    }

	/**
	 * get HttpRptServer instance which will executes the dispatched task
	 * @param obj the Object
	 * @return HttpRptServer instance
	 */
	public synchronized HttpRptServer getDispatchedServer(Object obj) {
		return (HttpRptServer) getDispatchedService(obj, HttpRptServer.class);
	}

    private Object getDispatchedService(Object session, Class target)
    {
        Object service = null;
        Dispatcher dispatcher = null;
        
        try {			
			this.initialize();
            
			String sessionId = null;
			if (session instanceof HttpServletRequest) {
				sessionId = ((HttpServletRequest)session).getSession().getId();
			} else {
                System.out.println("return service: " + service);
				return null;
			}         
			
            System.out.println("\n htSession = " + htSession);
            
            dispatcher = (Dispatcher) htSession.get(sessionId);
            
            if (dispatcher == null)
            {
                System.out.println("new session: " + sessionId);
                dispatcher = this.getNextConnectableDispatch();
            }
            else
            {
                System.out.println("old session: " + sessionId);
            }

            if (dispatcher != null)
            {
                if(target.equals(HttpRptServer.class))
                {
                    service = RemoteReportServerToolkit.getRemoteWrappedRptServer(
                            dispatcher.host, dispatcher.port);
                }
                else if(target.equals(RemoteFileService.class))
                {
                    service = RemoteReportServerToolkit.getRemoteFileService(dispatcher.host, dispatcher.port);
                }
                else
                {
                    throw new UnsupportedOperationException("Can not get corresponding service. Only support HttpRptServer or RemoteFileService. Please check");
                }
                
                htSession.put(sessionId, dispatcher);
            }
            else
            {
                throw new Exception("Can not get usable dispatcher, please check: ");
            }
			
            
		} catch (Throwable t) {
            System.out.println("\nCan not get usable dispatcher, please check: ");
            System.out.println(CONFIG_FILE + "="  +System.getProperty(CONFIG_FILE));
            for(int i = 0; i < this.dispatchers.size(); i++)
            {
                System.out.println(this.dispatchers.get(i));
            }
            t.printStackTrace();
            System.out.println();
            
			return null;
		}
        
        System.out.println("return service: " + service + ", host=" + dispatcher.host + ", port=" + dispatcher.port);
        return service;
    }

    private Dispatcher getNextConnectableDispatch()
    {
        if(this.dispatchers == null || this.dispatchers.size() == 0) return null;
                
        Dispatcher dispatcher = null;
        
        //ensure exit after travel all
        for(int count = 0; count < this.dispatchers.size(); count++)
        {
            if (this.num == this.dispatchers.size()) this.num = 0; //reach the end, restart from head
            
            dispatcher = (Dispatcher) this.dispatchers.get(this.num);
            this.num++;
            
            if(dispatcher.isConnectable) 
            {
                return dispatcher;
            }
        }
        return null;
    }

    /**
	 * get RemoteFileService instance which will execute the dispatched task
	 * @param obj the Object
	 * @return RemoteFileService instance
	 */
	public synchronized RemoteFileService getDispatchedFileService(Object obj) {
        return (RemoteFileService) getDispatchedService(obj, RemoteFileService.class);
	}

    private void initialize()
    {
        if (!this.isGot) {
        	getHostsPorts();
            updateAllDispatchersStatus();
        	this.isGot = true;
            this.monitor.start();
        }
    }
	

	/**
	 * get hosts and ports from a config file
	 */ 
	private synchronized void getHostsPorts() {
		String configFile = System.getProperty(CONFIG_FILE);
		if (configFile == null || configFile.length() == 0) {
			configFile = "C:\\temp\\hostport.properties";
		}
		String host, port;
		try {
			File f = new File(configFile);
			if (f.exists()) {
				FileInputStream fin = new FileInputStream(f);
				BufferedReader reader = new BufferedReader(new InputStreamReader(fin));
				String ret = null;
				while ((ret = reader.readLine()) != null) {
					if (ret.startsWith("rmiserver")) {
						ret = ret.substring(ret.indexOf("=") + 1);
						host = ret.substring(0, ret.indexOf(":"));
						port = ret.substring(ret.indexOf(":") + 1);
                        
                        Dispatcher dispatcher = new Dispatcher(host, port, true);
                        this.dispatchers.add(dispatcher);
					}
				}
				fin.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
    
    private class Dispatcher
    {
        String host = null;
        String port = null;
        boolean isConnectable = false;
        Dispatcher(String _host, String _port, boolean _connectable)
        {
            this.host = _host;
            this.port = _port;
            this.isConnectable = _connectable;
        }
        
        public String toString()
        {
            return "[Dispatcher: " 
                + this.host + ":" + this.port + ", isConnectable=" + this.isConnectable
                + "]";
        }
    }
}
