/**
 * ServiceCache is a class that maintains a cache of Jini services.
 * Both lookup services discovered dynamically via multicast and those
 * statically specified lookup services accessed via unicast can be used
 * as sources of services.
 * <br/>
 * A security manager such as RMISecurityManager must be in effect for
 * this class to work.
 * <br/>
 * This class is intended to be immutable, hence the lack of methods to
 * change groups, locators, and service templates.
 * <br/>
 * This source code is copyright 2008 by Patrick May.  All
 * rights reserved.
 *
 * @author Patrick May (patrick@softwarematters.org)
 * @author &copy; 2008 Patrick May.  All rights reserved.
 * @version 1
 */

package org.softwarematters.jini.util;

import java.util.logging.Logger;

import net.jini.discovery.LookupDiscoveryManager;
import net.jini.discovery.DiscoveryGroupManagement;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;

import net.jini.lookup.ServiceDiscoveryListener;
import net.jini.lookup.ServiceDiscoveryManager;
import net.jini.lookup.LookupCache;
import net.jini.lookup.ServiceItemFilter;
import net.jini.lookup.ServiceDiscoveryEvent;

import net.jini.core.discovery.LookupLocator;

import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.lookup.ServiceItem;

import net.jini.lease.LeaseRenewalManager;

public class ServiceCache
  implements DiscoveryListener, ServiceDiscoveryListener
{
  private static Logger logger_
    = Logger.getLogger(ServiceCache.class.getName());

  private LookupDiscoveryManager lookupDiscoveryManager_ = null;
  private LeaseRenewalManager leaseRenewalManager_ = new LeaseRenewalManager();
  private ServiceDiscoveryManager serviceDiscoveryManager_ = null;
  private LookupCache lookupCache_ = null;

  /**
   * The full constructor for the ServiceCache class.
   *
   * @param groups The names of the Jini groups to which Lookup Services
   *               must belong.
   * @param locators Jini unicast Lookup Service locators.
   * @param template The ServiceTemplate that specifies the services of
   *                 interest.
   * @param filter The ServiceItemFilter that further specifies the
   *               services of interest.
   */
  public ServiceCache(String[] groups,
                      LookupLocator[] locators,
                      ServiceTemplate template,
                      ServiceItemFilter filter)
    {
    try
      {
      lookupDiscoveryManager_
        = new LookupDiscoveryManager(groups,locators,this);
      serviceDiscoveryManager_
        = new ServiceDiscoveryManager(lookupDiscoveryManager_,
                                      leaseRenewalManager_);
      lookupCache_
        = serviceDiscoveryManager_.createLookupCache(template,filter,this);
      }
    catch (Exception e)
      {
      logger_.severe(e.getMessage());
      throw new JiniUtilityException(e);
      }
    }


  /**
   * A convenience constructor for the ServiceCache class.
   *
   * @param template The ServiceTemplate that specifies the services of
   *                 interest.
   */
  public ServiceCache(ServiceTemplate template)
    {
    this(DiscoveryGroupManagement.ALL_GROUPS,  // all groups, via multicast
         null,                                 // no unicast lookup servers
         template,
         null);                                // no service filter
    }


  /**
   * A convenience constructor for the ServiceCache class.
   *
   * @param groups The names of the Jini groups to which Lookup Services
   *               must belong.
   * @param serviceClass The class of the service of interest.
   */
  public ServiceCache(String[] groups,Class serviceClass)
    {
    this(groups,
         null,                                 // no unicast lookup servers
         new ServiceTemplate(null,new Class[] { serviceClass },null),
         null);                                // no service filter
    }


  /**
   * A convenience constructor for the ServiceCache class.
   *
   * @param serviceClass The class of the service of interest.
   */
  public ServiceCache(Class serviceClass)
    {
    this(DiscoveryGroupManagement.ALL_GROUPS,  // all groups, via multicast
         null,                                 // no unicast lookup servers
         new ServiceTemplate(null,new Class[] { serviceClass },null),
         null);                                // no service filter
    }


  /**
   * The default constructor for the ServiceCache class.
   */
  public ServiceCache()
    {
    this(DiscoveryGroupManagement.ALL_GROUPS,  // all groups, via multicast
         null,                                 // no unicast lookup servers
         new ServiceTemplate(null,null,null),  // all services
         null);                                // no service filter
    }


  /**
   * Called when a new lookup service is found.  Inherited from
   * DiscoveryListener.
   */
  public void discovered(DiscoveryEvent event)
    {
    logger_.info("Discovery event received.");

    ServiceTemplate template = new ServiceTemplate(null,null,null);
    ServiceRegistrar[] registrars = event.getRegistrars();

    for (ServiceRegistrar registrar : registrars)
      {
      try
        {
        logger_.info("Registrar discovered on "
                     + registrar.getLocator().getHost()
                     + ":"
                     + registrar.getLocator().getPort()
                     + ".");
        }
      catch (Exception e)
        { logger_.warning(e.getMessage()); }
      }
    }


  /**
   * Called when a lookup service is discarded.  Inherited from
   * DiscoveryListener.
   */
  public void discarded(DiscoveryEvent event)
    {
    logger_.info("Lookup service discarded.");
    }


  /**
   * Called when a service of interest is registered with one of the
   * cached lookup services.  Inherited from ServiceDiscoveryListener.
   */
  public void serviceAdded(ServiceDiscoveryEvent event)
    {
    ServiceItem item = event.getPostEventServiceItem();

    logger_.info("Service discovery event received:  "
                 + item.service.getClass().getName()
                 + "("
                 + item.serviceID.toString()
                 + ")");
    }


  /**
   * Called when a service of interest is changed within one of the
   * cached lookup services.  Inherited from ServiceDiscoveryListener.
   */
  public void serviceChanged(ServiceDiscoveryEvent event)
    {
    ServiceItem preEventItem = event.getPreEventServiceItem();
    ServiceItem postEventItem = event.getPostEventServiceItem();

    logger_.info("Service change event received:  "
                 + preEventItem.service.getClass().getName()
                 + "("
                 + preEventItem.serviceID.toString()
                 + ") to "
                 + postEventItem.service.getClass().getName()
                 + "("
                 + postEventItem.serviceID.toString()
                 + ")");
    }


  /**
   * Called when a service of interest is removed from one of the
   * cached lookup services.  Inherited from ServiceDiscoveryListener.
   */
  public void serviceRemoved(ServiceDiscoveryEvent event)
    {
    ServiceItem item = event.getPreEventServiceItem();

    logger_.info("Service removed event received:  "
                 + item.service.getClass().getName()
                 + "("
                 + item.serviceID.toString()
                 + ")");
    }


  /**
   * Find a service that passes the specified filter.
   *
   * @param filter The filter to apply to the services in the cache.
   */
  public Object service(ServiceItemFilter filter)
    {
    ServiceItem item = lookupCache_.lookup(filter);

    return (item == null) ? null : item.service;
    }


  /**
   * Find a service with the specified class.
   *
   * @param serviceClass The class of the service to find.
   */
  public Object service(final Class serviceClass)
    {
    return service(new ServiceItemFilter()
                     {
                     public boolean check(ServiceItem item)
                       {
                       return (item.service.getClass() == serviceClass);
                       }
                     });
    }


  /**
   * Find any service.  This is only useful if the ServiceCache instance
   * has been configured to only cache services of a single type.
   */
  public Object service()
    {
    return service(new ServiceItemFilter()
                     {
                     public boolean check(ServiceItem item)
                       {
                       return true;
                       }
                     });
    }
}  // end ServiceCache

