package jadex.commons.service; import jadex.commons.Future; import jadex.commons.IFuture; import jadex.commons.Tuple; import jadex.commons.collection.Cache; import jadex.commons.concurrent.DelegationResultListener; import jadex.commons.concurrent.IResultListener; import jadex.commons.service.clock.IClockService; import java.util.Collection; import java.util.Iterator; /** * Service container that uses caching for fast service access. */ public class CacheServiceContainer implements IServiceContainer { //-------- attributes -------- /** The original service container. */ protected IServiceContainer container; /** The LRU storing the last searches. */ protected Cache cache; /** The clock service. */ protected IClockService clock; /** Flag if cache is turned on. */ protected boolean cacheon = true; //-------- constructors -------- /** * Create a new service container. */ public CacheServiceContainer(IServiceContainer container) { this(container, 25, -1); } /** * Create a new service container. */ public CacheServiceContainer(IServiceContainer container, int max, long ttl) { this.container = container; this.cache = new Cache(max, ttl); } //-------- methods -------- /** * Get all services of a type. * @param type The class. * @return The corresponding services. */ public IFuture getServices(final ISearchManager manager, final IVisitDecider decider, final IResultSelector selector, Collection results) { final Future ret = new Future(); // final Tuple key = manager.getCacheKey()!=null && decider.getCacheKey()!=null && selector.getCacheKey()!=null // ? new Tuple(manager.getCacheKey(), decider.getCacheKey(), selector.getCacheKey()) : null; final Tuple key = decider.getCacheKey()!=null && selector.getCacheKey()!=null ? new Tuple(decider.getCacheKey(), selector.getCacheKey()) : null; Object data = null; // Todo: cast hack??? While no clock service found (during init) search without cache. final long now = clock!=null && ((IInternalService)clock).isValid()? clock.getTime(): -1; if(cacheon && !manager.isForcedSearch()) { synchronized(cache) { // todo: currently services of unfinished containers can be searched // should be strict and a container should exposed only when running. // In case the clock if not available caching will not be used // till it is available. if(key!=null && cache.containsKey(key)) { data = cache.get(key, now); if(data!=null) { // Replace non-expireable entry if(!cache.canExpire(key)) { cache.put(key, data, now); } } // if(selector instanceof TypeResultSelector && ((TypeResultSelector)selector).getType().getName().indexOf("Clock")!=-1) // { // System.out.println("hit: "+selector+" "+getId()); // } // if(data!=null && data.getClass().getName().indexOf("ComponentManagement")!=-1) // { // System.out.println("hit: "+data+" "+getId()); // } if(data instanceof IInternalService) { if(!((IInternalService)data).isValid()) { cache.remove(key); data = null; } else { if(!results.contains(data)) { // if(data.getClass().getName().indexOf("Shop")!=-1) // System.out.println("cache add: "+data+" to: "+results); results.add(data); } } } else if(data instanceof Collection) { Collection coll = (Collection)data; // Check if all results are still ok. for(Iterator it=coll.iterator(); it.hasNext(); ) { Object next = it.next(); if(next instanceof IInternalService) { if(!((IInternalService)next).isValid()) { // if one is invalid whole result is invalid cache.remove(key); data = null; } } } if(data!=null) { for(Iterator it=coll.iterator(); it.hasNext(); ) { Object next = it.next(); if(!results.contains(next)) { // if(data.getClass().getName().indexOf("Shop")!=-1) // System.out.println("cache add: "+data+" to: "+results); results.add(next); } } } } else if(data!=null) { ret.setException(new RuntimeException("Unknown service type: "+data)); return ret; } } else { // if(selector instanceof TypeResultSelector && ((TypeResultSelector)selector).getType().getName().indexOf("Clock")!=-1) // { // System.out.println("no hit: "+selector+" "+getId()+" "+now); // } } } } if(data!=null) { ret.setResult(data); } else { container.getServices(manager, decider, selector, results).addResultListener(new IResultListener() { public void resultAvailable(Object source, Object result) { if(key!=null && result!=null) { synchronized(cache) { // if(selector instanceof TypeResultSelector && ((TypeResultSelector)selector).getType().getName().indexOf("Clock")!=-1) // { // System.out.println("putting: "+getId()+" "+result); // } // Put result in cache, even if service may not (yet) be valid. // May lead to cache hit and still service put in cache again. cache.put(key, result, now); } } // if(result==null) // System.out.println("found null: "+key); ret.setResult(result); } public void exceptionOccurred(Object source, Exception exception) { ret.setException(exception); } }); } return ret; } /** * Get the parent service container. * @return The parent container. */ public IFuture getParent() { return container.getParent(); } /** * Get the children container. * @return The children container. */ public IFuture getChildren() { return container.getChildren(); } /** * Get the globally unique id of the provider. * @return The id of this provider. */ public Object getId() { return container.getId(); } /** * Start the service. * @return A future that is done when the service has completed starting. */ public IFuture start() { final Future ret = new Future(); // System.out.println("search clock: "+getId()); SServiceProvider.getServiceUpwards(this, IClockService.class).addResultListener(new IResultListener() { public void resultAvailable(Object source, Object result) { clock = (IClockService)result; // System.out.println("Has clock: "+getId()+" "+clock); // Services may need other services and thus need to be able to search // the container. container.start().addResultListener(new DelegationResultListener(ret)); } public void exceptionOccurred(Object source, Exception exception) { ret.setException(exception); } }); return ret; } /** * Shutdown the service. * @return A future that is done when the service has completed its shutdown. */ public IFuture shutdown() { return container.shutdown(); } /** * Add a service to the platform. * If under the same name and type a service was contained, * the old one is removed and shutdowned. * @param name The name. * @param service The service. */ public IFuture addService(IInternalService service) { return container.addService(service); } /** * Removes a service from the platform (shutdowns also the service). * @param name The name. * @param service The service. */ public IFuture removeService(IServiceIdentifier sid) { return container.removeService(sid); } /** * Get the string representation. * @return The string representation. */ public String toString() { return "CacheServiceContainer(name="+getId()+", container="+container+")"; } }