
/**
 * ServiceFinderImpl.java
 *
 *
 * Created: Wed Apr 18 23:00:44 2001
 *
 * @author <a href="mailto:jan.newmarch@infotech.monash.edu.au"Jan Newmarch</a>
 * @version 1.0
 */

package services;

import java.rmi.RemoteException;
import java.net.URL;

import net.jini.core.discovery.LookupLocator;
import net.jini.core.lookup.ServiceItem;
import net.jini.discovery.LookupLocatorDiscovery;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.discovery.DiscoveryEvent;
import net.jini.discovery.DiscoveryListener;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceMatches;
import net.jini.core.lookup.ServiceEvent;
import net.jini.core.lease.Lease;
import com.sun.jini.lease.landlord.LeaseManager;
import net.jini.core.event.EventRegistration;
import net.jini.core.event.UnknownEventException;
import net.jini.lease.LeaseRenewalManager;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.lookup.ServiceID;

import java.util.HashMap;
import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;
import java.rmi.MarshalledObject;

import servicefinder.RemoteServiceFinder;

public class ServiceFinderImpl extends java.rmi.server.UnicastRemoteObject
    implements RemoteServiceFinder, DiscoveryListener, RemoteEventListener {

    protected LookupLocatorDiscovery discovery;
    protected HashMap serviceMap = new HashMap();
    protected HashMap leaseMap = new HashMap();
    protected static LeaseRenewalManager leaseManager = 
	new LeaseRenewalManager();
    protected final int TRANSITIONS = 
	                          ServiceRegistrar.TRANSITION_MATCH_NOMATCH |
                                  ServiceRegistrar.TRANSITION_NOMATCH_MATCH |
                                  ServiceRegistrar.TRANSITION_MATCH_MATCH;

    public void addDiscovery(LookupLocatorDiscovery discovery) {
	this.discovery = discovery;
	this.discovery.addDiscoveryListener(this);
    }

    public void discovered(DiscoveryEvent e) {
	// new set of LUS discovered
	// find all services on them
        Class [] classes = new Class[] {};
        ServiceTemplate template = new ServiceTemplate(null, classes, 
                                                       null);
	ServiceRegistrar[] registrars = e.getRegistrars();
	for (int n = 0; n < registrars.length; n++) {
	    ServiceRegistrar registrar = registrars[n];
	    System.out.println("Discovered LUS with ServiceID " +
			       registrar.getServiceID().toString());
	    ServiceItem[] serviceItems = null;
	    try {
		ServiceMatches serviceMatches = 
		    registrar.lookup(template, Integer.MAX_VALUE);
		serviceItems = serviceMatches.items;

	    } catch(java.rmi.RemoteException ex) {
		ex.printStackTrace();
		break;
	    }

	    // store the current set of services for this registrar
	    // in a map
	    String host = null;
	    try {
		host = registrar.getLocator().getHost();
	    } catch(Exception ex) {
		ex.printStackTrace();
		break;
	    }
	    System.out.println("Adding host " + host);
	    serviceMap.put(host.intern(), serviceItems);
	    for (int m = 0; m < serviceItems.length; m++) {
		System.out.println("  with service " + serviceItems[m].service);
		System.out.println("  and service ID " +
				   serviceItems[m].serviceID);
	    }

	    // add ourselves as listener for any changes to services
	    // and save the lease for later removal
	    EventRegistration reg = null;
	    try {
		reg = registrar.notify(template,
				       TRANSITIONS,
				       (RemoteEventListener) this,
				       (MarshalledObject) null,
				       Lease.ANY);
		Lease lease = reg.getLease();
		leaseManager.renewUntil(lease, 
					Lease.FOREVER, null);
		leaseMap.put(host, lease);
		System.out.println("notifed id " + reg.getID());
	    } catch(RemoteException ex) {
		ex.printStackTrace();
	    }

	}
    }

    public void discarded(DiscoveryEvent e) {
	ServiceRegistrar[] registrars = e.getRegistrars();
	for (int n = 0; n < registrars.length; n++) {
	    ServiceRegistrar registrar = registrars[n];
	    String host = null;
	    try {
		host = registrar.getLocator().getHost();
	    } catch(Exception ex) {
		ex.printStackTrace();
		break;
	    }
	    System.out.println("Removing host " + host);
	    serviceMap.remove(host.intern());
	}
    }

    public void notify(RemoteEvent evt)
        throws RemoteException, UnknownEventException {
        try {
            ServiceEvent sevt = (ServiceEvent) evt;
            int transition = sevt.getTransition();
	    ServiceRegistrar registrar = (ServiceRegistrar) evt.getSource();
	    String host = registrar.getLocator().getHost();
	    System.out.println("Registrar from " + host);
            System.out.println("Service's ID " + sevt.getServiceID().toString());
            System.out.println("transition " + transition);
            switch (transition) {
            case ServiceRegistrar.TRANSITION_NOMATCH_MATCH:
                System.out.println("nomatch -> match");
		addService(host.intern(), sevt.getServiceItem());
                break;
            case ServiceRegistrar.TRANSITION_MATCH_MATCH:
                System.out.println("match -> match");
		replaceService(host.intern(), sevt.getServiceItem());
                break;
            case ServiceRegistrar.TRANSITION_MATCH_NOMATCH:
                System.out.println("match -> nomatch");
		removeService(host.intern(), sevt.getServiceID());
                break;
            }
            System.out.println(sevt.toString());
            if (sevt.getServiceItem() == null) {
                System.out.println("now null");
            } else {
                Object service = sevt.getServiceItem().service;
                System.out.println("Service is " + service.toString());
            }
        } catch(Exception e) {
            e.printStackTrace();
        }

    }

    public void addService(String host, ServiceItem item) {
	ServiceItem[] items = (ServiceItem []) serviceMap.remove(host);
	if (items == null) {
	    System.err.println("no services for host " + host);
	    return;
	}

	int length = items.length;
	ServiceItem[] newItems = new ServiceItem[length + 1];
	for (int n = 0; n < length; n++) {
	    newItems[n] = items[n];
	}
	newItems[length] = item;
	serviceMap.put(host, newItems);
    }

    public void removeService(String host, ServiceID id) {
	ServiceItem[] items = (ServiceItem []) serviceMap.remove(host);
	int length = items.length;
	ServiceItem[] newItems = new ServiceItem[length - 1];
	int n;
	for (n = 0; n < length; n++) {
	    if (items[n].serviceID.equals(id)) {
		System.out.println("Removing service");
		break;
	    } else {
		newItems[n] = items[n];
	    }
	}
	if (n == length) {
	    System.out.println("Couldn't find item to remove");
	    return;
	}
	for (; n < length - 1; n++) {
	    newItems[n] = items[n + 1];
	}
	serviceMap.put(host, newItems);
    }

    public void replaceService(String host, ServiceItem item) {
	ServiceItem[] items = (ServiceItem []) serviceMap.remove(host);
	int length = items.length;
	for (int n = 0; n < length; n++) {
	    if (items[n].serviceID.equals(item.serviceID)) {
		System.out.println("Replacing service");
		items[n] = item;
	    }
	}
	serviceMap.put(host, items);
    }

    public ServiceFinderImpl ()
	throws RemoteException {
    }

    public void addLocator(String locatorURL)
	throws RemoteException {
	LookupLocator[] locators = null;
	try {
	    locators = new LookupLocator[] 
	                   {new LookupLocator("jini://" + locatorURL)};
	} catch(java.net.MalformedURLException e) {
	    throw new RemoteException(e.toString());
	}
	discovery.addLocators(locators);
    }

    public void addLocator(String locatorURL, long timeout)
	throws RemoteException {
	addLocator(locatorURL);
	new RemoveLocator(locatorURL, timeout).start();
    }

    public void removeLocator(String locatorURL)
	throws RemoteException {
	// remove from the list of lookup locators
	LookupLocator[] locators = discovery.getLocators();
	int n;
	for (n = 0; n < locators.length; n++) {
	    if (locatorURL.equals(locators[n].getHost())) {
		discovery.removeLocators(new LookupLocator[] {locators[n]});
		break;
	    }
	}
	if (n == locators.length) {
	    // didn't find it
	    System.out.println("Locator not removed " + locatorURL);
	    return;
	}

	// cancel the lease
	Lease lease = (Lease) leaseMap.remove(locatorURL);
	try {
	    lease.cancel();
	} catch(net.jini.core.lease.UnknownLeaseException e) {
	    // ignore
	}
    }

    public LookupLocator[] getLocators() {
	System.out.println("Finding locators");
	LookupLocator[] locators = discovery.getLocators();
	for (int n = 0; n < locators.length; n++) {
	    System.out.println("Locator: " + locators[n].toString());
	}
	return locators;
    }

    public ServiceRegistrar[] getRegistrars() {
	return discovery.getRegistrars();
    }

    public ServiceRegistrar getRegistrar(String host) {
	ServiceRegistrar[] registrars = discovery.getRegistrars();
	for (int n = 0; n < registrars.length; n++) {
	    LookupLocator locator = null;
	    try {
		locator = registrars[n].getLocator();
	    } catch(RemoteException e) {
		e.printStackTrace();
		break;
	    }
	    if (locator.getHost().equals(host)) {
		return registrars[n];
	    }
	}
	return null;
    }

    public ServiceItem[] getServices()
	throws RemoteException {
	Collection values = serviceMap.values();
	Iterator iterator = values.iterator();
	Vector items = new Vector();
	while (iterator.hasNext()) {
	    ServiceItem[] serviceItems = (ServiceItem[]) iterator.next();
	    for (int n = 0; n < serviceItems.length; n++) {
		items.add(serviceItems[n]);
	    }
	}
	System.out.println("Service vector found");
	
	// we can't apparently just cast the array to ServiceItem[]
	// so iterate through elements
	Object[] objs = items.toArray();
	ServiceItem[] serviceItems = new ServiceItem[objs.length];
	for (int n = 0; n < objs.length; n++) {
	    serviceItems[n] = (ServiceItem) objs[n];
	}
	System.out.println("Service array found");
	return serviceItems;
    }

    public ServiceItem[] getServices(String locatorURL)
	throws RemoteException {
	System.out.println("locator " + locatorURL);
	ServiceItem[] items = (ServiceItem []) 
	    serviceMap.get(locatorURL.intern());
	System.out.println("Items" + items);
	if (items == null) {
	    return new ServiceItem[] {};
	} else {
	    return items;
	}
    }

    class RemoveLocator extends Thread {
	String locator;
	long timeout;

	public RemoveLocator(String locator, long timeout) {
	    this.locator = locator;
	    this.timeout = timeout;
	}

	public void run() {
	    try {
		Thread.sleep(timeout);
	    } catch(InterruptedException e) {
		// ignore
	    }
	    try{
		removeLocator(locator);
	    } catch(RemoteException e) {
		// ignore
	    }
	}
    }
}// ServiceFinderImpl
