View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    * 
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package net.jini.lookup;
20  
21  import java.io.IOException;
22  import java.rmi.RemoteException;
23  import java.rmi.server.ExportException;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.LinkedList;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  import java.util.concurrent.Callable;
33  import java.util.concurrent.ConcurrentHashMap;
34  import java.util.concurrent.ConcurrentLinkedQueue;
35  import java.util.concurrent.ConcurrentMap;
36  import java.util.concurrent.ExecutorService;
37  import java.util.concurrent.Future;
38  import java.util.concurrent.FutureTask;
39  import java.util.concurrent.LinkedBlockingQueue;
40  import java.util.concurrent.PriorityBlockingQueue;
41  import java.util.concurrent.RunnableFuture;
42  import java.util.concurrent.ScheduledExecutorService;
43  import java.util.concurrent.ScheduledThreadPoolExecutor;
44  import java.util.concurrent.ThreadPoolExecutor;
45  import java.util.concurrent.TimeUnit;
46  import java.util.concurrent.atomic.AtomicLong;
47  import java.util.concurrent.locks.ReentrantReadWriteLock;
48  import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
49  import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
50  import java.util.function.BiConsumer;
51  import java.util.function.BiFunction;
52  import java.util.logging.Level;
53  import net.jini.config.ConfigurationException;
54  import net.jini.core.entry.Entry;
55  import net.jini.core.event.RemoteEvent;
56  import net.jini.core.event.RemoteEventListener;
57  import net.jini.core.event.UnknownEventException;
58  import net.jini.core.lookup.ServiceEvent;
59  import net.jini.core.lookup.ServiceID;
60  import net.jini.core.lookup.ServiceItem;
61  import net.jini.core.lookup.ServiceMatches;
62  import net.jini.core.lookup.ServiceRegistrar;
63  import net.jini.core.lookup.ServiceTemplate;
64  import net.jini.export.Exporter;
65  import net.jini.lookup.ServiceAttributesAccessor;
66  import net.jini.lookup.ServiceIDAccessor;
67  import net.jini.lookup.ServiceProxyAccessor;
68  import net.jini.io.MarshalledInstance;
69  import net.jini.jeri.AtomicILFactory;
70  import net.jini.jeri.BasicILFactory;
71  import net.jini.jeri.BasicJeriExporter;
72  import net.jini.jeri.tcp.TcpServerEndpoint;
73  import net.jini.security.TrustVerifier;
74  import net.jini.security.proxytrust.ServerProxyTrust;
75  import org.apache.river.concurrent.RC;
76  import org.apache.river.concurrent.Ref;
77  import org.apache.river.concurrent.Referrer;
78  import org.apache.river.lookup.entry.LookupAttributes;
79  import org.apache.river.proxy.BasicProxyTrustVerifier;
80  import org.apache.river.thread.DependencyLinker;
81  import org.apache.river.thread.ExtensibleExecutorService;
82  import org.apache.river.thread.FutureObserver;
83  import org.apache.river.thread.NamedThreadFactory;
84  import org.apache.river.thread.ObservableFutureTask;
85  
86  /**
87   * Internal implementation of the LookupCache interface. Instances of this class
88   * are used in the blocking versions of ServiceDiscoveryManager.lookup() and are returned by
89   * createLookupCache.
90   */
91  final class LookupCacheImpl implements LookupCache {
92  
93      private static final int ITEM_ADDED = 0;
94      private static final int ITEM_REMOVED = 2;
95      private static final int ITEM_CHANGED = 3;
96      /* The listener that receives remote events from the lookup services */
97      private final LookupListener lookupListener;
98      /* Exporter for the remote event listener (lookupListener) */
99      private volatile Exporter lookupListenerExporter;
100     /* Proxy to the listener that receives remote events from lookups */
101     private volatile RemoteEventListener lookupListenerProxy;
102     /**
103      * Task manager for the various tasks executed by this LookupCache
104      */
105     private volatile ExecutorService cacheTaskMgr;
106     private volatile CacheTaskDependencyManager cacheTaskDepMgr;
107     
108     private volatile ExecutorService incomingEventExecutor;
109     /* Flag that indicates if the LookupCache has been terminated. */
110     private volatile boolean bCacheTerminated = false;
111     /* Contains the ServiceDiscoveryListener's that receive local events */
112     private final ReadLock sItemListenersRead;
113     private final WriteLock sItemListenersWrite;
114     private final Collection<ServiceDiscoveryListener> sItemListeners;
115     /* Map from ServiceID to ServiceItemReg, this basically holds the replicated
116      * filtered cache of ServiceRegistrar's, containing all the services
117      * and attributes the client has registered interest in.
118      * Regarding synchronization of state, all ServiceItemReg mutations 
119      * are guarded using atoimc BiFunction's with computIfPresent calls,
120      * now that mutations of ServiceItemReg are essentially atomic, the
121      * next step would be to make ServiceItemReg immutable, and replace 
122      * instead of mutate and decide on a sutiable identity, so that when a later
123      * atomic operation occurring after filtering depends on an atomic
124      * operation that preceded it, we can check whether an interleved atomic action
125      * has occurred. At present interleaved atomic actions are prevented by
126      * limiting access to a single thread for each EventReg in eventRegMap
127      * for registration, events and lookup.  Removal from serviceIdMap only 
128      * occurs through discard and insertion when newOldService is called
129      * by RegisterListenerTask or HandleServiceEventTask, both occur
130      * atomically and mutually exclusive (for an EventReg), hence mutating
131      * atomic operations are unlikely to be interleaved, we also check that we 
132      * have the same ServiceItemReg instance between dependant atomic operations.
133      * and check whether a ServiceItemReg or EventReg has been discarded.
134      * The atomic operations are separated by filtering, which may include 
135      * remote calls. */
136     private final ConcurrentMap<ServiceID, ServiceItemReg> serviceIdMap;
137     /* Map from ProxyReg to EventReg: (proxyReg, {source,id,seqNo,lease})
138      * This map is used to assist with managing event registrations with
139      * each ServiceRegistar and replicating filtered state using ServiceEvents */
140     private final ConcurrentMap<ProxyReg, EventReg> eventRegMap;
141     /* Template current cache instance should use for primary matching */
142     private final ServiceTemplate tmpl;
143     /* Filter current cache instance should use for secondary matching */
144     private final ServiceItemFilter filter;
145     /* Desired lease duration to request from lookups' event mechanisms */
146     private final long leaseDuration;
147     /* Log the time when the cache gets created. This value is used to
148      * calculate the time when the cache should expire.
149      */
150     private final long startTime;
151     /**
152      * For tasks waiting on verification events after service discard
153      */
154     private volatile ScheduledExecutorService serviceDiscardTimerTaskMgr;
155     private final ConcurrentMap<ServiceID, Future> serviceDiscardFutures;
156     /**
157      * Whenever a ServiceIdTask is created in this cache, it is assigned a
158      * unique sequence number to allow such tasks associated with the same
159      * ServiceID to be executed in the order in which they were queued in the
160      * TaskManager. This field contains the value of the sequence number
161      * assigned to the most recently created ServiceIdTask.
162      */
163     private final AtomicLong taskSeqN;
164     private final ServiceDiscoveryManager sdm;
165     private final boolean useInsecureLookup;
166 
167     LookupCacheImpl(ServiceTemplate tmpl, ServiceItemFilter filter, 
168             ServiceDiscoveryListener sListener, long leaseDuration,
169             ServiceDiscoveryManager sdm, boolean useInsecureLookup) 
170                                                     throws RemoteException 
171     {
172         this.useInsecureLookup = useInsecureLookup;
173 	this.taskSeqN = new AtomicLong();
174 	this.startTime = System.currentTimeMillis();
175 	this.eventRegMap = new ConcurrentHashMap<ProxyReg, EventReg>();
176 	this.serviceIdMap = new ConcurrentHashMap<ServiceID, ServiceItemReg>();
177 	this.sItemListeners = new HashSet<ServiceDiscoveryListener>();
178 	this.serviceDiscardFutures = RC.concurrentMap(new ConcurrentHashMap<Referrer<ServiceID>, Referrer<Future>>(), Ref.WEAK_IDENTITY, Ref.STRONG, 60000, 60000);
179 	this.tmpl = tmpl.clone();
180 	this.leaseDuration = leaseDuration;
181 	this.filter = filter;
182 	lookupListener = new LookupListener();
183 	if (sListener != null) {
184 	    sItemListeners.add(sListener);
185 	}
186 	this.sdm = sdm;
187         ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
188         sItemListenersRead = rwl.readLock();
189         sItemListenersWrite = rwl.writeLock();
190     } //end constructor
191 
192     private ExecutorService eventNotificationExecutor;
193 
194     /**
195      * RemoteEventListener class that is registered with the proxy to
196      * receive notifications from lookup services when any ServiceItem
197      * changes (NOMATCH_MATCH, MATCH_NOMATCH, MATCH_MATCH)
198      * 
199      * TODO: implement RemoteMethodControl to allow the ServiceRegistrar
200      * to place constraints on the LookupListener.
201      */
202     private final class LookupListener implements RemoteEventListener,
203 	    ServerProxyTrust {
204 
205 	RemoteEventListener export() throws ExportException {
206 	    return (RemoteEventListener) lookupListenerExporter.export(this);
207 	}
208 
209 	@Override
210 	public void notify(RemoteEvent evt) throws UnknownEventException,
211 		java.rmi.RemoteException {
212 	    if (!(evt instanceof ServiceEvent)) {
213 		throw new UnknownEventException("ServiceEvent required,not: " + evt.toString());
214 	    }
215 	    notifyServiceMap((ServiceEvent) evt);
216 	}
217 
218 	/**
219 	 * Returns a <code>TrustVerifier</code> which can be used to verify that
220 	 * a given proxy to this listener can be trusted.
221 	 */
222 	@Override
223 	public TrustVerifier getProxyVerifier() {
224 	    return new BasicProxyTrustVerifier(lookupListenerProxy);
225 	} //end getProxyVerifier
226     } //end class LookupCacheImpl.LookupListener
227     
228     /**
229      * This task class, when executed, first registers to receive ServiceEvents
230      * from the given ServiceRegistrar. If the registration process succeeds (no
231      * RemoteExceptions), it then performs a lookup to query the given
232      * ServiceRegistrar for a "snapshot" of its current state with respect to
233      * services that match the given template.
234      *
235      * Note that the order of execution is important. That is, the lookup must
236      * be executed only after registration for events has completed. This is
237      * because when an entity registers with the event mechanism of a
238      * ServiceRegistrar, the entity will only receive notification of events
239      * that occur "in the future", after the registration is made. The entity
240      * will not receive events about changes to the state of the
241      * ServiceRegistrar that may have occurred before or during the registration
242      * process.
243      *
244      * Thus, if the order of these tasks were reversed and lookup were to be
245      * performed prior to the RegisterListenerTask, then the possibility exists
246      * for the occurrence of a change in the ServiceRegistrar's state between
247      * the time lookup retrieves a snapshot of that state, and the time the
248      * event registration process has completed, resulting in an incorrect view
249      * of the current state of the ServiceRegistrar.
250      */
251     private static final class RegisterListenerTask extends
252 	    CacheTask {
253 
254 	final LookupCacheImpl cache;
255 
256 	public RegisterListenerTask(ProxyReg reg,
257 		long seqN, LookupCacheImpl cache) {
258 	    super(reg, seqN);
259 	    this.cache = cache;
260 	}
261 
262 	@Override
263 	public boolean hasDeps() {
264 	    return true;
265 	}
266 
267 	@Override
268 	public boolean dependsOn(CacheTask t) {
269 	    if (t instanceof ProxyRegDropTask) {
270 		ProxyReg r = getProxyReg();
271 		if (r != null && r.equals(t.getProxyReg())) {
272 		    if (t.getSeqN() < getSeqN()) {
273 			return true;
274 		    }
275 		}
276 	    }
277 	    return false;
278 	}
279 
280 	@Override
281 	public void run() {
282             if (ServiceDiscoveryManager.logger.isLoggable(Level.FINER)){
283                 ServiceDiscoveryManager.log(Level.FINER, 
284                     "ServiceDiscoveryManager - RegisterListenerTask started");
285             }
286 	    long duration = cache.getLeaseDuration();
287 	    if (duration < 0) {
288 		return;
289 	    }
290 	    try {
291 		EventReg eventReg
292 			= cache.sdm.registerListener(
293 				reg.getProxy(),
294 				cache.tmpl,
295 				cache.lookupListenerProxy,
296 				duration
297 			);
298 		// eventReg is a new object not visible to other threads yet.
299 		// It will be safely published using a ConcurrentMap, so
300 		// we don't need to synchronize here.
301 		/* Cancel the lease if the cache has been terminated */
302 		if (cache.bCacheTerminated
303 			|| Thread.currentThread().isInterrupted()) {
304 		    // eventReg.lease is final and is already safely published
305 		    cache.sdm.cancelLease(eventReg.lease);
306 		} else {
307                     eventReg.suspendEvents();
308 		    EventReg existed
309 			    = cache.eventRegMap.putIfAbsent(reg, eventReg);
310 		    if (existed != null){ // A listener has already been registered.
311 			cache.sdm.cancelLease(eventReg.lease);
312 		    } else {
313                         try {
314                             /* Execute the lookup only if there were no problems */
315                             cache.lookup(reg);
316                         } finally {
317                             synchronized (eventReg){
318                                 eventReg.releaseEvents();
319                                 eventReg.notify();
320                             }
321                         }
322                     }
323                     
324 		} //endif
325 	    } catch (Exception e) {
326 		cache.sdm.fail(e,
327 			reg.getProxy(),
328 			this.getClass().getName(),
329 			"run",
330 			"Exception occurred while attempting to register with the lookup service event mechanism",
331 			cache.bCacheTerminated
332 		);
333 	    } finally {
334                 if (ServiceDiscoveryManager.logger.isLoggable(Level.FINER)){
335                     ServiceDiscoveryManager.log(Level.FINER, 
336                         "ServiceDiscoveryManager - RegisterListenerTask completed");
337                 }
338 	    }
339 	} //end run
340     } //end class LookupCacheImpl.RegisterListenerTask
341     
342     /**
343      * When the given registrar is discarded, this Task class is used to remove
344      * the registrar from the various maps maintained by this cache.
345      */
346     private static final class ProxyRegDropTask extends CacheTask {
347 
348 	final LookupCacheImpl cache;
349 	final EventReg eReg;
350 
351 	public ProxyRegDropTask(ProxyReg reg,
352 		EventReg eReg,
353 		long seqN,
354 		LookupCacheImpl cache) {
355 	    super(reg, seqN);
356 	    this.cache = cache;
357 	    this.eReg = eReg;
358 	}
359 
360 	@Override
361 	public void run() {
362             if (ServiceDiscoveryManager.logger.isLoggable(Level.FINEST)){
363                 ServiceDiscoveryManager.log(Level.FINEST, 
364                     "ServiceDiscoveryManager - ProxyRegDropTask started");
365             }
366             // Maybe registrar was discarded before the RegisterLookupListener 
367             // task completed?
368             // That's ok this task should execute after RegisterLookupListener.
369             synchronized (eReg){ //lease has already been cancelled by removeProxyReg or above.
370                 while (eReg.eventsSuspended()) { 
371                     // Lookup is in progress, due to non contiguous 
372                     // event.
373                     try {
374                         eReg.wait(200L);
375                     } catch (InterruptedException e){
376                         Thread.currentThread().interrupt();
377                     }
378                 }
379                 // We've woken up holding the lock on eReg, events and lookup
380                 // cannot proceed until we release this lock.
381                 // Before lookup can start adding state to the serviceIdMap
382                 // it first checks the eReg is not discarded.
383                 if (eReg.discard()) cache.eventRegMap.remove(reg, eReg);
384             }
385 	    /* For each itemReg in the serviceIdMap, disassociate the
386 	     * lookup service referenced here from the itemReg; and
387 	     * if the itemReg then has no more lookup services associated
388 	     * with it, remove the itemReg from the map and send a
389 	     * service removed event.
390 	     */
391 	    Iterator<Map.Entry<ServiceID, ServiceItemReg>> iter = cache.serviceIdMap.entrySet().iterator();
392 	    while (iter.hasNext()) {
393 		Map.Entry<ServiceID, ServiceItemReg> e = iter.next();
394 		ServiceID srvcID = e.getKey();
395                 DissociateLusCleanUpOrphan dlcl = new DissociateLusCleanUpOrphan(cache, reg.getProxy());
396                 cache.serviceIdMap.computeIfPresent(srvcID, dlcl);
397                 if (dlcl.itemRegProxy != null) {
398                     cache.itemMatchMatchChange(srvcID, dlcl.itmReg, dlcl.itemRegProxy, dlcl.newItem, false);
399                 } else if (dlcl.notify && dlcl.filteredItem != null) {
400                     cache.removeServiceNotify(dlcl.filteredItem);
401                 }
402 	    } //end loop
403             if (ServiceDiscoveryManager.logger.isLoggable(Level.FINEST)){
404                 ServiceDiscoveryManager.log(Level.FINEST, 
405                     "ServiceDiscoveryManager - ProxyRegDropTask completed");
406             }
407 	} //end run
408 
409 	@Override
410 	public boolean hasDeps() {
411 	    return true;
412 	}
413 
414 	@Override
415 	public boolean dependsOn(CacheTask t) {
416 	    if (t instanceof RegisterListenerTask || t instanceof ProxyRegDropTask) {
417 		ProxyReg r = getProxyReg();
418 		if (r != null && r.equals(t.getProxyReg())) {
419 		    if (t.getSeqN() < getSeqN()) {
420 			return true;
421 		    }
422 		}
423 	    }
424 	    return false;
425 	}
426     } //end class LookupCacheImpl.ProxyRegDropTask
427     
428     /**
429      * Task class used to determine whether or not to "commit" a service discard
430      * request, increasing the chances that the service will eventually be
431      * re-discovered. This task is also used to attempt a filter retry on an
432      * item in which the cache's filter initially returned indefinite.
433      */
434     private static final class ServiceDiscardTimerTask implements Runnable {
435 
436 	private final ServiceID serviceID;
437 	private final LookupCacheImpl cache;
438 
439 	public ServiceDiscardTimerTask(LookupCacheImpl cache, ServiceID serviceID) {
440 	    this.serviceID = serviceID;
441 	    this.cache = cache;
442 	} //end constructor
443 
444 	@Override
445 	public void run() {
446             if (ServiceDiscoveryManager.logger.isLoggable(Level.FINEST)){
447                 ServiceDiscoveryManager.log(Level.FINEST, 
448                     "ServiceDiscoveryManager - ServiceDiscardTimerTask started");
449             }
450 	    try {
451 		/* Exit if this cache has already been terminated. */
452 		if (cache.bCacheTerminated) {
453 		    return;
454 		}
455 		/* If the service ID is still contained in the serviceIdMap,
456 		 * then a MATCH_NOMATCH event did not arrive, which is
457 		 * interpreted here to mean that the service is still up.
458 		 * The service ID will still be in the map if one (or both)
459 		 * of the following is true:
460 		 *  - the client discarded an unreachable service that never
461 		 *    actually went down (so it's lease never expired, and
462 		 *    a MATCH_NOMATCH event was never received)
463 		 *  - upon applying the cache's filter to the service, the
464 		 *    filter returned indefinite, and this task was queued
465 		 *    to request that filtering be retried at a later time
466 		 *
467 		 * For the first case above, the service is "un-discarded" so
468 		 * the service will be available to the client again. For the
469 		 * second case, the filter is retried. If the service passes
470 		 * the filter, the service is "un-discarded"; otherwise, it is
471 		 * 'quietly' removed from the map (because a service removed
472 		 * event was already sent when the service was originally
473 		 * discarded.
474 		 */
475 		ServiceItemReg itemReg = cache.serviceIdMap.get(serviceID);
476 		if (itemReg != null) {
477                     // Refactoring strategy:
478                     // read item and filter.
479                     // compute atomically, check item undiscards and check
480                     // that filtered item hasn't changed.
481 		    //Discarded status hasn't changed
482 		    ServiceItem itemToSend;
483 		    if (!itemReg.isDiscarded()) return;
484 		    ServiceItem item = null;
485 		    ServiceItem filteredItem = null;
486                     boolean addFilteredItemToMap = false;
487                     boolean remove = false;
488                     boolean notify = true;
489 		    itemToSend = itemReg.getFilteredItem();
490 		    if (itemToSend == null) {
491 			item = itemReg.getItem();
492 			filteredItem = item.clone();
493 			//retry the filter
494 			if (cache.useInsecureLookup){
495 			    if (ServiceDiscoveryManager.filterPassed(filteredItem, cache.filter)) {
496                                 addFilteredItemToMap = true;
497 			    } else {
498 				//'quietly' remove the item
499                                 remove = true;
500                                 notify = false;
501 			    } //endif
502 			} else {
503 			    // We're dealing with a bootstrap proxy.
504 			    // The filter may not be expecting a bootstrap proxy.
505 			    try {
506 				if(ServiceDiscoveryManager.filterPassed(filteredItem, cache.filter)){
507                                     addFilteredItemToMap = true;
508                                 } else {
509                                     //'quietly' remove the item
510                                     remove = true;
511                                     notify = false;
512                                 } //endif
513 			    } catch (SecurityException ex){
514                                 if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE)){
515                                     ServiceDiscoveryManager.log(Level.FINE, 
516                                         "Exception caught, while attempting to filter a bootstrap proxy", ex);
517                                 }
518 				try {
519 				    filteredItem.service = ((ServiceProxyAccessor) filteredItem.service).getServiceProxy();
520 				    if(ServiceDiscoveryManager.filterPassed(filteredItem, cache.filter)){
521                                         addFilteredItemToMap = true;
522                                     } else {
523                                         //'quietly' remove the item
524                                         remove = true;
525                                         notify = false;
526                                     } //endif
527 				} catch (RemoteException ex1) {
528 				    if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE)){
529                                         ServiceDiscoveryManager.log(Level.FINE, 
530                                             "Exception caught, while attempting to filter a bootstrap proxy", ex1);
531                                     }
532                                     //'quietly' remove the item
533                                     remove = true;
534                                     notify = false;
535 				}
536 			    } catch (ClassCastException ex){
537 				if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE)){
538                                     ServiceDiscoveryManager.log(Level.FINE, 
539                                         "Exception caught, while attempting to filter a bootstrap proxy", ex);
540                                 }
541 				try {
542 				    filteredItem.service = ((ServiceProxyAccessor) filteredItem.service).getServiceProxy();
543 				    if(ServiceDiscoveryManager.filterPassed(filteredItem, cache.filter)){
544                                         addFilteredItemToMap = true;
545                                     } else {
546                                         //'quietly' remove the item
547                                         remove = true;
548                                         notify = false;
549                                     } //endif
550 				} catch (RemoteException ex1) {
551 				    if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE)){
552                                         ServiceDiscoveryManager.log(Level.FINE, 
553                                             "Exception caught, while attempting to filter a bootstrap proxy", ex1);
554                                     }
555                                     //'quietly' remove the item
556                                     remove = true;
557                                     notify = false;
558 				}
559 			    }
560 			}
561 		    } //endif
562 		    /* Either the filter was retried and passed, in which case,
563 		     * the filtered itemCopy was placed in the map and
564 		     * "un-discarded"; or the
565 		     * filter wasn't applied above (a non-null filteredItem
566 		     * field in the itemReg in the map means that the filter
567 		     * was applied at some previous time). In the latter case, the
568 		     * service can now be "un-discarded", and a notification
569 		     * that the service is now available can be sent for either case.
570 		     */
571                     AddOrRemove aor = 
572                             new AddOrRemove(cache, item, filteredItem, 
573                                     itemToSend, addFilteredItemToMap,
574                                     remove, notify
575                             );
576                     cache.serviceIdMap.computeIfPresent(serviceID, aor);
577 		    if (aor.notify) cache.addServiceNotify(aor.itemToSend);
578 		}
579 	    } finally {
580                 if (ServiceDiscoveryManager.logger.isLoggable(Level.FINEST)){
581                     ServiceDiscoveryManager.log(Level.FINEST, 
582                         "ServiceDiscoveryManager - ServiceDiscardTimerTask completed");
583                 }
584 	    }
585 	} //end run
586     }
587     
588     /**
589      * Used by ServiceDiscardTimerTask to make removal or add with unDiscard atomic.
590      */
591     private static class AddOrRemove 
592             implements BiFunction<ServiceID, ServiceItemReg, ServiceItemReg> 
593     {
594         final LookupCacheImpl cache;
595         final ServiceItem item;
596         final ServiceItem filteredItem;
597         final boolean addFilteredItemToMap;
598         final boolean remove;
599         boolean notify;
600         ServiceItem itemToSend;
601         
602         AddOrRemove(LookupCacheImpl cache, ServiceItem item,
603                 ServiceItem filteredItem, ServiceItem itemToSend, 
604                 boolean addFilteredItemToMap, boolean remove, boolean notify)
605         {
606             this.cache = cache;
607             this.item = item;
608             this.filteredItem = filteredItem;
609             this.itemToSend = itemToSend;
610             this.addFilteredItemToMap = addFilteredItemToMap;
611             this.remove = remove;
612             this.notify = notify;
613         }
614         
615         @Override
616         public ServiceItemReg apply(ServiceID serviceID, ServiceItemReg itemReg) {
617             if (!itemReg.unDiscard()){
618                 notify = false;
619                 return itemReg;
620             } // Do nothing.
621             /* Either the filter was retried and passed, in which case,
622              * the filtered itemCopy was placed in the map and
623              * "un-discarded"; or the
624              * filter wasn't applied above (a non-null filteredItem
625              * field in the itemReg in the map means that the filter
626              * was applied at some previous time). In the latter case, the
627              * service can now be "un-discarded", and a notification
628              * that the service is now available can be sent for either case.
629              */
630             if (addFilteredItemToMap){
631                 itemReg.replaceProxyUsedToTrackChange(null, item);
632                 itemReg.setFilteredItem(filteredItem);
633                 itemToSend = filteredItem;
634                 return itemReg;
635             } else if (remove){
636                 return null;
637             }
638             return itemReg;
639         }
640         
641     }
642 
643     // This method's javadoc is inherited from an interface of this class
644     @Override
645     public void terminate() {
646 	synchronized (this) {
647 	    if (bCacheTerminated) {
648 		return; //allow for multiple terminations
649 	    }
650 	    bCacheTerminated = true;
651 	} //end sync
652 	sdm.removeLookupCache(this);
653 	/* Terminate all tasks: first, terminate this cache's Executors*/
654 	cacheTaskMgr.shutdownNow();
655 	/* Terminate ServiceDiscardTimerTasks running for this cache */
656 	serviceDiscardTimerTaskMgr.shutdownNow();
657 	eventNotificationExecutor.shutdownNow();
658 	/* Cancel all event registration leases held by this cache. */
659 	Set set = eventRegMap.entrySet();
660 	Iterator iter = set.iterator();
661 	while (iter.hasNext()) {
662 	    Map.Entry e = (Map.Entry) iter.next();
663 	    EventReg eReg = (EventReg) e.getValue();
664 	    sdm.cancelLease(eReg.lease);
665 	} //end loop
666 	/* Un-export the remote listener for events from lookups. */
667 	try {
668 	    lookupListenerExporter.unexport(true);
669 	} catch (IllegalStateException e) {
670             if (ServiceDiscoveryManager.logger.isLoggable(Level.FINEST)){
671                 ServiceDiscoveryManager.log(
672                     Level.FINEST, 
673                     "IllegalStateException occurred while unexporting the cache's remote event listener",
674                     e
675                 );
676             }
677 	}
678         incomingEventExecutor.shutdownNow();
679         if (ServiceDiscoveryManager.logger.isLoggable(Level.FINEST)){
680             ServiceDiscoveryManager.log(
681                 Level.FINEST, 
682                 "ServiceDiscoveryManager - LookupCache terminated"
683             );
684         }
685     } //end LookupCacheImpl.terminate
686 
687     // This method's javadoc is inherited from an interface of this class
688     @Override
689     public ServiceItem lookup(ServiceItemFilter myFilter) {
690 	checkCacheTerminated();
691 	ServiceItem[] ret = getServiceItems(myFilter);
692 	if (ret.length == 0) {
693 	    return null;
694 	}
695 	// Maths.abs(Integer.MIN_VALUE) = -ve, so to avoid random
696 	// hard to debug bugs, this has been changed.
697 	int rand = sdm.random.nextInt(ret.length);
698 	return ret[rand];
699     } //end LookupCacheImpl.lookup
700 
701     // This method's javadoc is inherited from an interface of this class
702     @Override
703     public ServiceItem[] lookup(ServiceItemFilter myFilter, int maxMatches) {
704 	checkCacheTerminated();
705 	if (maxMatches < 1) {
706 	    throw new IllegalArgumentException("maxMatches must be > 0");
707 	}
708 	ServiceItem[] sa = getServiceItems(myFilter);
709 	int len = sa.length;
710 	if (len == 0 || len <= maxMatches) return sa;
711         List<ServiceItem> items = new LinkedList<ServiceItem>();
712 	int rand = sdm.random.nextInt(Integer.MAX_VALUE) % len;
713 	for (int i = 0; i < len; i++) {
714 	    items.add(sa[(i + rand) % len]);
715 	    if (items.size() == maxMatches) {
716 		break;
717 	    }
718 	} //end loop
719 	ServiceItem[] ret = new ServiceItem[items.size()];
720 	items.toArray(ret);
721 	return ret;
722     } //end LookupCacheImpl.lookup
723 
724     // This method's javadoc is inherited from an interface of this class
725     @Override
726     public void discard(Object serviceReference) {
727 	checkCacheTerminated();
728 	/* Loop through the serviceIdMap, looking for the itemReg that
729 	 * corresponds to given serviceReference. If such an itemReg
730 	 * exists, and it's not already discarded, then queue a task
731 	 * to discard the given serviceReference.
732 	 */
733 	Iterator<Map.Entry<ServiceID, ServiceItemReg>> iter = serviceIdMap.entrySet().iterator();
734 	while (iter.hasNext()) {
735 	    Map.Entry<ServiceID, ServiceItemReg> e = iter.next();
736 	    ServiceItemReg itmReg = e.getValue();
737             ServiceID sid = e.getKey();
738             ServiceItem filteredItem = itmReg.getFilteredItem();
739             if (filteredItem != null && (filteredItem.service).equals(serviceReference)) {
740                 Discard dis = new Discard(this, itmReg, filteredItem, sdm.getDiscardWait());
741                 serviceIdMap.computeIfPresent(sid, dis);
742             }
743 	} //end loop
744     } //end LookupCacheImpl.discard
745     
746     private static class Discard 
747             implements BiFunction<ServiceID, ServiceItemReg, ServiceItemReg>{
748         
749         LookupCacheImpl cache;
750         ServiceItemReg expected;
751         ServiceItem filteredItem;
752         long discardWait;
753         
754         Discard(LookupCacheImpl cache, ServiceItemReg itmReg, ServiceItem filteredItem, long discardWait){
755             this.cache = cache;
756             this.expected = itmReg;
757             this.filteredItem = filteredItem;
758             this.discardWait = discardWait;
759         }
760 
761         @Override
762         public ServiceItemReg apply(ServiceID sid, ServiceItemReg itmReg) {
763             if (!expected.equals(itmReg)) return itmReg;
764             if (itmReg.discard()) {
765                 Future f = 
766                     cache.serviceDiscardTimerTaskMgr.schedule(
767                         new ServiceDiscardTimerTask(cache, sid),
768                         discardWait,
769                         TimeUnit.MILLISECONDS
770                     );
771                 cache.serviceDiscardFutures.put(sid, f);
772                 cache.removeServiceNotify(filteredItem);
773             }
774             return itmReg;
775         }
776     }
777 
778     /**
779      * This method returns a <code>ServiceItem</code> array containing elements
780      * that satisfy the following conditions: - is referenced by one of the
781      * <code>itemReg</code> elements contained in the <code>serviceIdMap</code>
782      * - is not currently discarded - satisfies the given
783      * <code>ServiceItemFilter</code>
784      *
785      * Note that the <code>filter</code> parameter is a "2nd stage" filter. That
786      * is, for each <code>itemReg</code> element in the
787      * <code>serviceIdMap</code>, the "1st stage" filter corresponding to the
788      * current instance of <code>LookupCache</code> has already been applied to
789      * the <code>ServiceItem</code> referenced in that <code>itemReg</code>. The
790      * <code>ServiceItemFilter</code> applied here is supplied by the entity
791      * interacting with the cache, and provides a second filtering process.
792      * Thus, this method applies the given <code>filter</code> parameter to the
793      * <code>filteredItem</code> field (not the <code>item</code> field) of each
794      * non-discarded <code>itemReg</code> element in the
795      * <code>serviceIdMap</code>.
796      *
797      * This method returns all the instances of <code>ServiceItem</code> that
798      * pass the given <code>filter</code>; and it discards all the items that
799      * produce an indefinite result when that <code>filter</code> is applied.
800      */
801     private ServiceItem[] getServiceItems(ServiceItemFilter filter2) {
802         FilteredItems items = new FilteredItems(this, filter2);
803         serviceIdMap.forEach(items);
804         return items.result();
805     } //end LookupCacheImpl.getServiceItems
806     
807     private static class FilteredItems implements BiConsumer<ServiceID, ServiceItemReg> {
808         
809         private final List<ServiceItem> items;
810         private final ServiceItemFilter filter2;
811         private final LookupCacheImpl cache;
812         
813         FilteredItems(LookupCacheImpl cache, ServiceItemFilter filter2){
814             this.items = new LinkedList<ServiceItem>();
815             this.filter2 = filter2;
816             this.cache = cache;
817         }
818 
819         @Override
820         public void accept(ServiceID sid, ServiceItemReg itemReg) {
821 	    ServiceItem itemToFilter = itemReg.getFilteredItem();
822             if ((itemToFilter == null) || (itemReg.isDiscarded())) return;
823             /* Make a copy because the filter may change it to null */
824             /* ServiceItemReg now performs defensive copy clone for us,
825              * so we don't forget.
826              */
827             itemToFilter = itemToFilter.clone();
828 	    /* Apply the filter */
829 	    boolean pass = (filter2 == null) || (filter2.check(itemToFilter));
830 	    /* Handle filter fail - skip to next item */
831 	    if (!pass) return;
832 	    /* Handle filter pass - add item to return set */
833 	    if (itemToFilter.service != null) {
834 		items.add(itemToFilter);
835 		return;
836 	    } //endif(pass)
837 	    /* Handle filter indefinite - discard the item */
838             Discard dis = 
839                     new Discard(
840                             cache, 
841                             itemReg, 
842                             itemToFilter, 
843                             cache.sdm.getDiscardWait()
844                     );
845             cache.serviceIdMap.computeIfPresent(sid, dis);
846         }
847         
848         ServiceItem[] result(){
849             ServiceItem[] ret = new ServiceItem[items.size()];
850             items.toArray(ret);
851             return ret;
852         }
853         
854     }
855 
856     ServiceItem [] processBootStrapProxys(Object [] proxys){
857 	int length = proxys.length;
858 	Collection<ServiceItem> result = new ArrayList<ServiceItem>(length);
859 	for (int i = 0; i < length; i++){
860 	    Object bootstrap;
861 	    Entry [] attributes;
862 	    ServiceID id;
863 	    try {
864 		bootstrap = sdm.bootstrapProxyPreparer.prepareProxy(proxys[i]);
865 		attributes = ((ServiceAttributesAccessor) bootstrap).getServiceAttributes();
866 		id = ((ServiceIDAccessor) bootstrap).serviceID();
867 		result.add(new ServiceItem(id, bootstrap, attributes));
868 	    } catch (IOException ex) { } //ignore
869 	}
870 	return result.toArray(new ServiceItem[result.size()]);
871     }
872     
873     // This method's javadoc is inherited from an interface of this class
874     @Override
875     public void addListener(ServiceDiscoveryListener listener) {
876 	checkCacheTerminated();
877 	if (listener == null) {
878 	    throw new NullPointerException("can't add null listener");
879 	}
880 	//No action is taken if not added according to LookupCache
881 	ServiceItem[] items = getServiceItems(null);
882         boolean added;
883         sItemListenersWrite.lock();
884         try {
885             added = sItemListeners.add(listener);
886         } finally {
887             sItemListenersWrite.unlock();
888         }
889         if (added){
890             for (int i = 0, l = items.length; i < l; i++) {
891                 addServiceNotify(items[i], listener);
892             } //end loop
893         }
894     } //end LookupCacheImpl.addListener
895 
896     // This method's javadoc is inherited from an interface of this class
897     @Override
898     public void removeListener(ServiceDiscoveryListener listener) {
899 	checkCacheTerminated();
900 	if (listener == null) {
901 	    return;
902 	}
903         sItemListenersWrite.lock();
904         try {
905             sItemListeners.remove(listener);
906         } finally {
907             sItemListenersWrite.unlock();
908         }
909     } //end LookupCacheImpl.removeListener
910 
911     /**
912      * Add a new ProxyReg to the lookupCache. Called by the constructor and the
913      * DiscMgrListener's discovered() method.
914      *
915      * @param reg a ProxyReg to add.
916      */
917     public void addProxyReg(ProxyReg reg) {
918 	RegisterListenerTask treg = new RegisterListenerTask(reg, taskSeqN.getAndIncrement(), this);
919 	cacheTaskDepMgr.submit(treg);
920     } //end LookupCacheImpl.addProxyReg
921 
922     /**
923      * Remove a ProxyReg from the lookupCache. Called by DiscMgrListener's
924      * discarded() method.
925      *
926      * @param reg a ProxyReg to remove.
927      */
928     public void removeProxyReg(ProxyReg reg) {
929 	ProxyRegDropTask t;
930 	//let the ProxyRegDropTask do the eventRegMap.remove
931 	EventReg eReg = eventRegMap.get(reg);
932 	if (eReg != null) {
933 	    try {
934 		sdm.leaseRenewalMgr.remove(eReg.lease);
935 	    } catch (Exception e) {
936                 if (ServiceDiscoveryManager.logger.isLoggable(Level.FINER)){
937                     ServiceDiscoveryManager.log(
938                         Level.FINER, 
939                         "exception occurred while removing an event registration lease",
940                         e
941                     );
942                 }
943 	    }
944             t = new ProxyRegDropTask(reg, eReg, taskSeqN.getAndIncrement(), this);
945             cacheTaskDepMgr.removeUselessTask(reg); //Possibly RegisterListenerTask before it commences execution.
946             cacheTaskDepMgr.submit(t);
947         } //endif
948     } //end LookupCacheImpl.removeProxyReg
949 
950     /* Throws IllegalStateException if this lookup cache has been
951      * terminated
952      */
953     private void checkCacheTerminated() {
954 	sdm.checkTerminated();
955 	if (bCacheTerminated) {
956 	    throw new IllegalStateException("this lookup cache was terminated");
957 	} //endif
958     } //end LookupCacheImpl.checkCacheTerminated
959     
960     /**
961      * Called by the lookupListener's notify() method
962      * 
963      * @param theEvent 
964      */
965     private void notifyServiceMap(ServiceEvent theEvent){
966         if (theEvent.getSource() == null) {
967 	    return;
968 	}
969         if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE)){
970             ServiceDiscoveryManager.log(
971                 Level.FINE, 
972                 "HandleServiceEventTask submitted"
973             );
974         }
975 	incomingEventExecutor.submit(new HandleServiceEventTask(this, theEvent));
976     }
977 
978     /**
979      * Task used to offload incoming ServiceEvents for LookupListener, so the
980      * remote method call can return quickly.
981      * 
982      * This was initially made comparable for natural queue ordering, however
983      * since events may be in network transit, while some are in the queue
984      * and others are executing, it isn't a safe way to ensure any kind of
985      * consistency or ordering.
986      */
987     private static class HandleServiceEventTask implements Runnable, Comparable {
988         
989         private final LookupCacheImpl cache;
990         private final ServiceEvent theEvent;
991         /* The following fields are only ever accessed by one thread at a time,
992          * they are volatile to ensure visibility between threads */
993         private volatile ProxyReg reg;
994         private volatile EventReg eReg;
995         private volatile long timestamp;
996         private volatile ServiceItem item;
997         
998         HandleServiceEventTask(LookupCacheImpl cache, ServiceEvent event){
999             this.cache = cache;
1000             this.theEvent = event;
1001         }
1002 
1003         @Override
1004         public void run() {
1005             if (ServiceDiscoveryManager.logger.isLoggable(Level.FINER)){
1006                 ServiceDiscoveryManager.log(Level.FINER,"HandleServiceEventTask started");
1007             }
1008             try {
1009                 if (item == null){
1010                     if (cache.useInsecureLookup){
1011                         item = theEvent.getServiceItem();
1012                     } else {
1013                         //REMIND: Consider using the actual service proxy?  No, that
1014                         // would cause unnecessary download, need to allow clients the
1015                         // opportunity to filter first, then clients also need to
1016                         // prepare during filtering before actual service proxy is 
1017                         // safe for use.
1018                         Object proxy = theEvent.getBootstrapProxy();
1019                         if (proxy != null){
1020                             Entry [] attributes;
1021                             try {
1022                                 proxy = cache.sdm.bootstrapProxyPreparer.prepareProxy(proxy);
1023                                 /* A service that registers with a ServiceRegistrar may trigger a 
1024                                  * ServiceEvent before that service receives its ServiceID from
1025                                  * the ServiceRegistrar, which results in the ServiceIDAccessor
1026                                  * returning null, we're provided with the ServiceID anyway,
1027                                  * so we can avoid an unnecessary remote call.
1028                                  */
1029                                 attributes = ((ServiceAttributesAccessor)proxy).getServiceAttributes();
1030                                 item = new ServiceItem(theEvent.getServiceID(), proxy, attributes);
1031                             } catch (IOException ex) {
1032                                 if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE)){
1033                                     ServiceDiscoveryManager.log(
1034                                         Level.FINE, 
1035                                         "exception thrown while attempting to establish contact via a bootstrap proxy",
1036                                         ex
1037                                     );
1038                                 }
1039                                 // Item will be null.
1040                             }
1041                         }
1042                     }
1043                 }
1044                 /* Search eventRegMap for ProxyReg corresponding to event. */
1045                 FIND_ProxyReg: while (reg == null || eReg == null) {
1046                     Set<Map.Entry<ProxyReg, EventReg>> set = cache.eventRegMap.entrySet();
1047                     Iterator<Map.Entry<ProxyReg, EventReg>> iter = set.iterator();
1048                     while (iter.hasNext()) {
1049                         Map.Entry<ProxyReg, EventReg> e = iter.next();
1050                         eReg = e.getValue();
1051                         if (theEvent.getID() == eReg.eventID && theEvent.getSource().equals(eReg.source)) {
1052                             reg = e.getKey();
1053                             break FIND_ProxyReg;
1054                         } //endif
1055                     } //end loop
1056                     try {
1057                         cache.incomingEventExecutor.submit(this);
1058                         Thread.sleep(50L);
1059                         return;
1060                     } catch (InterruptedException ex) {
1061                         Thread.currentThread().interrupt();
1062                         return;
1063                     }
1064                 }
1065                 if (timestamp == 0){
1066                     timestamp = System.currentTimeMillis();
1067                 } else {
1068                     // We've done this before.
1069                     if (!cache.eventRegMap.containsKey(reg)) return; // Discarded.
1070                 }
1071                 long currentTime = System.currentTimeMillis();
1072                 long delta = 0;
1073                 boolean resubmit = false;
1074                 int waiting = 0;
1075                 synchronized (eReg){
1076                     if (eReg.discarded()) return;
1077                     if (eReg.nonContiguousEvent(theEvent.getSequenceNumber()) 
1078                         && (currentTime - timestamp < 500)) // 1/2 seconds.
1079                     {
1080                         resubmit = true;
1081                         eReg.notifyAll();
1082                     } else {
1083                         while (eReg.eventsSuspended()){ // We're next.
1084                             try {
1085                                 waiting ++;
1086                                 eReg.wait(100L);
1087                                 waiting --;
1088                                 if (eReg.discarded()) return;
1089                                 // Give priority to contiguous events.
1090                                 if (waiting > 0 && eReg.nonContiguousEvent(
1091                                                     theEvent.getSequenceNumber()))
1092                                 {
1093                                     eReg.notifyAll();
1094                                     resubmit = true;
1095                                     break;
1096                                 }
1097                             } catch (InterruptedException ex) {
1098                                 Thread.currentThread().interrupt();
1099                                 return;
1100                             }
1101                         }
1102                         if (!resubmit){
1103                             eReg.suspendEvents();
1104                             delta = eReg.updateSeqNo(theEvent.getSequenceNumber());
1105                         }
1106                     }
1107                 }
1108                 if (resubmit){ // To avoid churn and free up thread.
1109                     try {
1110                         cache.incomingEventExecutor.submit(this);
1111                         Thread.sleep(50L);
1112                     } catch (InterruptedException ex) {
1113                         Thread.currentThread().interrupt();
1114                         return;
1115                     }
1116                     return;
1117                 }
1118                 try {
1119                     cache.notifyServiceMap(delta,
1120                         theEvent.getServiceID(),
1121                         item,
1122                         theEvent.getTransition(),
1123                         reg);
1124                 } finally {
1125                     synchronized (eReg){
1126                         eReg.releaseEvents();
1127                         eReg.notifyAll();
1128                     }
1129                 }
1130             } catch (RuntimeException e){
1131                 if (ServiceDiscoveryManager.logger.isLoggable(Level.FINER))
1132                     ServiceDiscoveryManager.log(Level.FINER, "HandleServiceEventTask threw a RuntimeException", e);
1133             } finally {
1134                 if (ServiceDiscoveryManager.logger.isLoggable(Level.FINER))
1135                     ServiceDiscoveryManager.log(Level.FINER, "HandleServiceEventTask completed");
1136             }
1137         }
1138 
1139         @Override
1140         public int compareTo(Object o) {
1141             if (!(o instanceof HandleServiceEventTask)) return 0;
1142             HandleServiceEventTask that = (HandleServiceEventTask) o;
1143             long dif = this.theEvent.getSequenceNumber() - that.theEvent.getSequenceNumber();
1144             if (dif == 0) return 0;
1145             if (dif < 0) return -1; // Which means that.theEvent is larger than this.theEvent
1146             return 1;
1147         }
1148     }
1149     
1150     /**
1151      * Called by the HandleServiceEventTask. Checks the event sequence
1152      * number and, based on whether or not a "gap" is found in in the event
1153      * sequence, performs lookup (if a gap was found) or processes the event if
1154      * no gap was found.
1155      *
1156      * Recall that the Event specification states that if the sequence numbers
1157      * of two successive events differ by only 1, then one can be assured that
1158      * no events were missed. On the other hand, if the difference is greater
1159      * than 1 (the sequence contains a "gap"), then one or more events may -- or
1160      * may not -- have been missed. Thus, if a gap is found in the events,
1161      * although it's possible that no events were missed, this method takes the
1162      * conservative approach by assuming events were missed. When this method
1163      * determines that an event may have been missed, it requests a current
1164      * "snapshot" of the given ServiceRegistrar's state by performing lookup.
1165      * Since this method can safely assume that no events have been missed if it
1166      * finds no gaps in the event sequence, it can safely process the event
1167      * maintaining equivalent state to the registrar, that is finding and
1168      * filtering new services and filtering updated or changed existing
1169      * services.
1170      *
1171      * Note that when a lookup service is discovered, this utility registers
1172      * with that lookup service's event mechanism for service events related to
1173      * the services of interest. Upon registering with the event mechanism, a
1174      * data structure (of type EventReg) containing information about that
1175      * registration is placed in a Map for later processing when events do
1176      * arrive. If the timing is right, it is possible that a service event may
1177      * arrive between the time the registration is made and the time the
1178      * EventReg is stored in the map. Thus, this method may find that the
1179      * eventRegMap does not contain an element corresponding to the event this
1180      * method is currently processing. In that case, this method will do
1181      * nothing. It will simply return so that the service referenced in the
1182      * event can be discovered using the snapshot returned by the lookup method
1183      * that is ultimately c by the RegisterListenerTask (whose listener
1184      * registration caused this method to be invoked in the first place).
1185      */
1186     private void notifyServiceMap(long delta,
1187                                   ServiceID sid,
1188                                   ServiceItem item,
1189                                   int transition,
1190                                   ProxyReg reg) 
1191     {
1192 	/* Look for any gaps in the event sequence. */
1193         if (delta == 1) {
1194             //no gap, handle current event
1195             if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE)){
1196                 ServiceDiscoveryManager.log(
1197                     Level.FINE, 
1198                     "No gap, handle current ServiceEvent, ServiceID: {0} transition: {1}",
1199                     new Object[]{sid, transition}
1200                 );
1201             }
1202             /* Fix for Bug ID 4378751. The conditions described by that
1203              * bug involve a ServiceItem (corresponding to a previously
1204              * discovered service ID) having a null service field. A
1205              * null service field is due to an UnmarshalException caused
1206              * by a SecurityException that results from the lack of a
1207              * connection permission for the lookup service codebase
1208              * to the service's remote codebase. Skip this ServiceItem,
1209              * otherwise an un-expected serviceRemoved event will result
1210              * because the primary if-block will be unintentionally
1211              * entered due to the null service field in the ServiceItem.
1212              */
1213             if ((item != null) && (item.service == null)) {
1214                 return;
1215             }
1216             /* Handle the event by the transition type, and by whether
1217              * the associated ServiceItem is an old, previously discovered
1218              * item, or a newly discovered item.
1219              */
1220             if (transition == ServiceRegistrar.TRANSITION_MATCH_NOMATCH) 
1221             {
1222                 handleMatchNoMatch(reg.getProxy(), sid);
1223             } else if (transition == ServiceRegistrar.TRANSITION_NOMATCH_MATCH 
1224                     || transition == ServiceRegistrar.TRANSITION_MATCH_MATCH) 
1225             {
1226                 newOldService(reg, sid, item, transition == ServiceRegistrar.TRANSITION_MATCH_MATCH);
1227             } //endif(transition)
1228             return;
1229         } 
1230         if (delta == 0) {// Repeat event, ignore.
1231             if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE)){
1232                 ServiceDiscoveryManager.log(
1233                     Level.FINE, 
1234                     "Repeat ServiceEvent, ignore, ServiceID: {0} transition: {1}",
1235                     new Object[]{sid, transition}
1236                 );
1237             }
1238             return;
1239         } 
1240         if (delta < 0) { // Old event, ignore.
1241             if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE)){
1242                 ServiceDiscoveryManager.log(
1243                     Level.FINE, 
1244                     "Old ServiceEvent, ignore, ServiceID: {0} transition: {1}",
1245                     new Object[]{sid, transition}
1246                 );
1247             }
1248             return;
1249         } 
1250         //gap in event sequence, request snapshot
1251         if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE)){
1252             ServiceDiscoveryManager.log(
1253                 Level.FINE, 
1254                 "Gap in ServiceEvent sequence, performing lookup, ServiceID: {0} transition: {1}",
1255                 new Object[]{sid, transition}
1256             );
1257         }
1258         lookup(reg);
1259     }
1260     /**
1261      * Requests a "snapshot" of the given registrar's state.
1262      * 
1263      * Lookup is mutually exclusive with events.
1264      */
1265     private void lookup(ProxyReg reg) {
1266         ServiceRegistrar proxy = reg.getProxy();
1267         ServiceItem[] items;
1268         /* For the given lookup, get all services matching the tmpl */
1269         try {
1270             if (useInsecureLookup){
1271                 ServiceMatches matches = proxy.lookup(tmpl, Integer.MAX_VALUE);
1272                 items = matches.items;
1273             } else {
1274                 Object [] matches = ((SafeServiceRegistrar) proxy).lookUp(
1275 			tmpl, Integer.MAX_VALUE);
1276                 items = processBootStrapProxys(matches);
1277             }
1278         } catch (Exception e) {
1279             // ReRegisterGoodEquals test failure becomes more predictable
1280             // when fail is only called if decrement is successful.
1281             sdm.fail(e, proxy, this.getClass().getName(), "run", "Exception occurred during call to lookup", bCacheTerminated);
1282             return;
1283         }
1284         if (items == null) {
1285             throw new AssertionError("spec violation in queried lookup service: ServicesMatches instance returned by call to lookup() method contains null 'items' field");
1286         }
1287         /* Should we handle new and old items before cleaning up orphans? */
1288         /* 1. Cleanup "orphaned" itemReg's. */
1289         Iterator<Map.Entry<ServiceID, ServiceItemReg>> iter = serviceIdMap.entrySet().iterator();
1290         while (iter.hasNext()) {
1291             Map.Entry<ServiceID, ServiceItemReg> e = iter.next();
1292             ServiceID srvcID = e.getKey();
1293             ServiceItem itemInSnapshot = findItem(srvcID, items);
1294             if (itemInSnapshot != null) continue; //not an orphan
1295             if (Thread.currentThread().isInterrupted()) return; // skip
1296             DissociateLusCleanUpOrphan dlcl = new DissociateLusCleanUpOrphan(this, reg.getProxy());
1297             serviceIdMap.computeIfPresent(srvcID, dlcl);
1298             if (dlcl.itemRegProxy != null) {
1299                 itemMatchMatchChange(srvcID, dlcl.itmReg, dlcl.itemRegProxy, dlcl.newItem, false);
1300             } else if (dlcl.notify && dlcl.filteredItem != null) {
1301                 removeServiceNotify(dlcl.filteredItem);
1302             }
1303         } //end loop
1304         /* 2. Handle "new" and "old" items from the given lookup */
1305         for (int i = 0, l = items.length; i < l; i++) {
1306             /* Skip items with null service field (Bug 4378751) */
1307             if (items[i].service == null) {
1308                 continue;
1309             }
1310             if (items[i].serviceID == null  && !useInsecureLookup){
1311                 if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE))
1312                     ServiceDiscoveryManager.log(Level.FINE, 
1313                         "ServiceItem contained null serviceID field, attempting to retrieve again");
1314                 try {
1315                     ServiceID id = ((ServiceIDAccessor)items[i].service).serviceID();
1316                     // item hasn't been published (shared) yet, safe to mutate.
1317                     if (id == null) continue;
1318                     items[i].serviceID = id;
1319                 } catch ( IOException e){
1320                     if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE))
1321                         ServiceDiscoveryManager.log(Level.FINE, 
1322                             "ServiceItem contained null serviceID field, attempt to retrieve again failed, ignoring",
1323                             e);
1324                     continue;
1325                 }
1326             }
1327             newOldService(reg, items[i].serviceID, items[i], false);
1328         } //end loop
1329     }
1330 
1331     /**
1332      * Method used to process the service state ("snapshot"), matching this
1333      * cache's template, retrieved from the given lookup service.
1334      *
1335      * After retrieving the snapshot S, the lookup method calls this method for
1336      * each service referenced in S. This method determines if the given service
1337      * is an already-discovered service (is currently in this cache's
1338      * serviceIdMap), or is a new service. This method handles the service
1339      * differently, depending on whether the service is a new or old.
1340      *
1341      * a. if the item is old, then this method will: - compare the given item
1342      * from the snapshot to the UN-filtered item in given itemReg if(same
1343      * version but attributes have changed) send changed event else if( version
1344      * has changed ) send removed event followed by added event else do nothing
1345      * - apply the filter to the given item if(filter fails) send removed event
1346      * else if(filter passes) set the filtered item in the itemReg in the map
1347      * else if (filter is indefinite) discard item send removed event queue
1348      * another filter attempt for later b. if the given item is newly
1349      * discovered, then this task will: - create a new ServiceItemReg containing
1350      * the given item - place the new itemReg in the serviceIdMap - apply the
1351      * filter to the given item if(filter fails) remove the item from the map
1352      * but send NO removed event else if(filter passes) send added event for the
1353      * FILTERED item else if (filter is indefinite) discard item queue another
1354      * filter attempt for later but send NO removed event
1355      */
1356     
1357     private void newOldService(ProxyReg reg, ServiceID id, ServiceItem item, boolean matchMatchEvent) {
1358         if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE)){
1359             ServiceDiscoveryManager.log(
1360                 Level.FINE, 
1361                 "newOldService called, ServiceItem: {0}",
1362                 new Object[]{item}
1363             );
1364         }
1365         try {
1366             boolean previouslyDiscovered = false;
1367             ServiceItemReg itemReg;
1368             itemReg = serviceIdMap.get(id);
1369             if (itemReg == null) {
1370                 if (!eventRegMap.containsKey(reg)) {
1371                     /* reg must have been discarded, simply return */
1372                     if (ServiceDiscoveryManager.logger.isLoggable(Level.FINER))
1373                         ServiceDiscoveryManager.log(
1374                             Level.FINER, 
1375                             "eventRegMap doesn't contain ProxyReg, returning, ServiceItem: {0}",
1376                             new Object[]{item}
1377                         );
1378                     return;
1379                 } //endif
1380                 // else
1381                 itemReg = new ServiceItemReg(reg.getProxy(), item);
1382                 ServiceItemReg existed = serviceIdMap.putIfAbsent(id, itemReg);
1383                 if (existed != null) {
1384                     itemReg = existed;
1385                     if (itemReg.isDiscarded()) {
1386                         if (ServiceDiscoveryManager.logger.isLoggable(Level.FINER)){
1387                             ServiceDiscoveryManager.log(
1388                                 Level.FINER, 
1389                                 "newOldService, discarded returning, ServiceItem: {0}",
1390                                 new Object[]{item}
1391                             );
1392                         }
1393                         return;
1394                     }
1395                     if (ServiceDiscoveryManager.logger.isLoggable(Level.FINER)){
1396                         ServiceDiscoveryManager.log(
1397                             Level.FINER, 
1398                             "newOldService, previously discovered, ServiceItem: {0}",
1399                             new Object[]{item}
1400                         );
1401                     }
1402                     previouslyDiscovered = true;
1403                 }
1404             } else if (itemReg.isDiscarded()) {
1405                 if (ServiceDiscoveryManager.logger.isLoggable(Level.FINER)){
1406                     ServiceDiscoveryManager.log(
1407                         Level.FINER, 
1408                         "newOldService, discarded returning, ServiceItem: {0}",
1409                         new Object[]{item}
1410                     );
1411                 }
1412                 return;
1413             } else {
1414                  if (ServiceDiscoveryManager.logger.isLoggable(Level.FINER)){
1415                     ServiceDiscoveryManager.log(
1416                         Level.FINER, 
1417                         "newOldService, previously discovered, ServiceItem: {0}",
1418                         new Object[]{item}
1419                     );
1420                 }
1421                 previouslyDiscovered = true;
1422             }
1423             if (previouslyDiscovered) {
1424                 //a. old, previously discovered item
1425                 itemMatchMatchChange(id, itemReg, reg.getProxy(), item, matchMatchEvent);
1426             } else {
1427                 //b. newly discovered item
1428                 ServiceItem newFilteredItem
1429                         = filterMaybeDiscard(id, itemReg, item, false);
1430 
1431                 if (newFilteredItem != null) {
1432                     addServiceNotify(newFilteredItem);
1433                 } //endif
1434             } //endif
1435         } catch (RuntimeException e) {
1436             if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE))
1437                 ServiceDiscoveryManager.log(Level.FINE, "Runtime exception thrown in newOldService call", e);
1438         } finally {
1439             if (ServiceDiscoveryManager.logger.isLoggable(Level.FINER))
1440                 ServiceDiscoveryManager.log(Level.FINER, 
1441                         "newOldService call complete, ServiceItem: {0}",
1442                         new Object[]{item});
1443         }
1444     }
1445 
1446     /**
1447      * Returns the element in the given items array having the given ServiceID.
1448      */
1449     private ServiceItem findItem(ServiceID sid, ServiceItem[] items) {
1450 	if (items != null) {
1451 	    for (int i = 0, length = items.length; i < length; i++) {
1452 		if (sid.equals(items[i].serviceID)) {
1453 		    return items[i];
1454 		}
1455 	    } //end loop
1456 	} //endif
1457 	return null;
1458     } //end LookupCacheImpl.findItem
1459 
1460     /**
1461      * With respect to a given service (referenced by the parameter newItem), if
1462      * either an event has been received from the given lookup service
1463      * (referenced by the proxy parameter), or a snapshot of the given lookup
1464      * service's state has been retrieved, this method determines whether the
1465      * service's attributes have changed, or whether a new version of the
1466      * service has been registered. After the appropriate determination has been
1467      * made, this method applies the filter associated with the current cache
1468      * and sends the appropriate local ServiceDiscoveryEvent(s).
1469      *
1470      * This method is called under the following conditions: - when a new lookup
1471      * service is discovered, this method will be called for each previously
1472      * discovered service - when a gap in the events from a previously
1473      * discovered lookup service is discovered, this method will be called for
1474      * each previously discovered service - when a MATCH_MATCH event is
1475      * received, this method will be called for each previously discovered
1476      * service - when a NOMATCH_MATCH event is received, this method will be
1477      * called for each previously discovered service Note that this method is
1478      * never called when a MATCH_NOMATCH event is received; such an event is
1479      * always handled by the handleMatchNoMatch method.
1480      *
1481      * When this method is called, it may send one of the following events or
1482      * combination of events: - a service changed event - a service removed
1483      * event followed by a service added event - a service removed event
1484      *
1485      * A service removed event is sent when the service either fails the filter,
1486      * or the filter produces an indefinite result; in which case, the service
1487      * is also discarded.
1488      *
1489      * A service changed event is sent when the service passes the filter, and
1490      * it is determined that the service's attributes have changed. In this
1491      * case, the old and new service proxies are treated as the same if one of
1492      * the following conditions is met: - this method was called because of the
1493      * receipt of a MATCH_MATCH event - the old and new service proxies are
1494      * byte-wise fully equal (Note that the lookup service specification
1495      * guarantees that the proxies are the same when a MATCH_MATCH event is
1496      * received.)
1497      *
1498      * A service removed event followed by a service added event is sent when
1499      * the service passes the filter, and the conditions for which a service
1500      * changed event would be considered are not met; that is, this method was
1501      * not called because of the receipt of a MATCH_MATCH event; or the old and
1502      * new service proxies are not byte-wise fully equal.
1503      *
1504      * The if-else-block contained in this method implements the logic just
1505      * described. The parameter matchMatchEvent reflects the pertinent event
1506      * state that causes this method to be called. That is, either a MATCH_MATCH
1507      * event was received, or it wasn't, (and if it wasn't, then a full
1508      * byte-wise comparison is performed to determine whether the proxies are
1509      * still the same).
1510      *
1511      * To understand when the 'else' part of the if-else-block is executed,
1512      * consider the following conditions: - there is more than one lookup
1513      * service with which the service registers (ex. LUS-0 and LUS-1) - after
1514      * the service registers with LUS-0, a NOMATCH_MATCH event is received and
1515      * handled (so the service is now known to the cache) - before the service
1516      * registers with LUS-1, the service is replaced with a new version - the
1517      * NOMATCH_MATCH event resulting from the service's registration with LUS-1
1518      * is received BEFORE receiving the MATCH_NOMATCH/NOMATCH_MATCH event
1519      * sequence that will ultimately result from the re-registration of that new
1520      * version with LUS-0 When the above conditions occur, the NOMATCH_MATCH
1521      * event that resulted from the service's registration with LUS-1 will cause
1522      * this method to be invoked and the proxies to be fully compared (because
1523      * the event was not a MATCH_MATCH event); and since the old service proxy
1524      * and the new service proxy will not be fully equal, the else part of the
1525      * if-else-block will be executed.
1526      *
1527      * This method applies the filter only after the above comparisons and
1528      * determinations have been completed.
1529      */
1530     private void itemMatchMatchChange(ServiceID srvcID, ServiceItemReg itemReg, ServiceRegistrar proxy, ServiceItem newItem, boolean matchMatchEvent) {
1531 	/* Save the pre-event state. Update the post-event state after
1532 	 * applying the filter.
1533 	 */
1534         if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE)){
1535             ServiceDiscoveryManager.log(
1536                 Level.FINE, 
1537                 "itemMatchMatchChange called, ServiceID: {0} ",
1538                 new Object[]{srvcID}
1539             );
1540         }
1541         PreEventState pev = new PreEventState(proxy,itemReg,newItem,matchMatchEvent);
1542         ServiceItem newFilteredItem;
1543         serviceIdMap.computeIfPresent(srvcID, pev);
1544         if (pev.needToFilter){
1545             /* Now apply the filter, and send events if appropriate */
1546             newFilteredItem = filterMaybeDiscard(srvcID, itemReg, newItem, pev.notDiscarded);
1547             if (newFilteredItem != null) {
1548                 /* Passed the filter, okay to send event(s). */
1549                 if (pev.attrsChanged && pev.oldFilteredItem != null) {
1550                     changeServiceNotify(newFilteredItem, pev.oldFilteredItem);
1551                 }
1552                 if (pev.versionChanged) {
1553                     if (pev.notDiscarded && pev.oldFilteredItem != null) {
1554                         removeServiceNotify(pev.oldFilteredItem);
1555                     } //endif
1556                     addServiceNotify(newFilteredItem);
1557                 } //endif
1558             } //endif
1559         }
1560     }
1561     
1562     private static class PreEventState implements BiFunction<ServiceID, ServiceItemReg, ServiceItemReg> {
1563         
1564         private final ServiceRegistrar proxy;
1565         private final ServiceItemReg reg;
1566         private final ServiceItem newItem;
1567         private final boolean matchMatchEvent;
1568         ServiceItem oldItem;
1569 	ServiceItem oldFilteredItem;
1570 	boolean notDiscarded;
1571 	boolean attrsChanged = false;
1572 	boolean versionChanged = false;
1573         boolean needToFilter = false;
1574 	ServiceRegistrar proxyChanged = null;
1575 
1576         PreEventState(ServiceRegistrar proxy, ServiceItemReg reg, ServiceItem newItem, boolean matchMatchEvent){
1577             this.proxy = proxy;
1578             this.reg = reg;
1579             this.newItem = newItem;
1580             this.matchMatchEvent = matchMatchEvent;
1581         }
1582         
1583         @Override
1584         public ServiceItemReg apply(ServiceID t, ServiceItemReg itemReg) {
1585             boolean loggable = ServiceDiscoveryManager.logger.isLoggable(Level.FINER);
1586             if (! reg.equals(itemReg)) {
1587                 if (loggable)
1588                     ServiceDiscoveryManager.log(
1589                         Level.FINER, 
1590                         "PreEventState.apply, ServiceItemReg's not equal, returning. ServiceID: {0}",
1591                         new Object[]{t}
1592                     );
1593                 return itemReg;
1594             }
1595             notDiscarded = !itemReg.isDiscarded();
1596 	    oldItem = itemReg.getItem();
1597 	    oldFilteredItem = itemReg.getFilteredItem();
1598 	    if (itemReg.proxyNotUsedToTrackChange(proxy, newItem)) {
1599 		// not tracking
1600                 if (loggable)
1601                     ServiceDiscoveryManager.log(
1602                         Level.FINER, 
1603                         "PreEventState.apply, proxyNotUsedToTrackChange. ServiceID: {0}",
1604                         new Object[]{t}
1605                     );
1606 		if (matchMatchEvent) {
1607                     if (loggable)
1608                         ServiceDiscoveryManager.log(
1609                             Level.FINER, 
1610                             "PreEventState.apply, matchMatchEvent true returning. ServiceID: {0}",
1611                             new Object[]{t}
1612                         );
1613 		    return itemReg;
1614 		}
1615 		if (notDiscarded) {
1616                     if (loggable)
1617                         ServiceDiscoveryManager.log(
1618                             Level.FINER, 
1619                             "PreEventState.apply, notifyServiceRemoved true returning. ServiceID: {0}",
1620                             new Object[]{t}
1621                         );
1622 		    return itemReg;
1623 		}
1624                 if (loggable)
1625                     ServiceDiscoveryManager.log(
1626                         Level.FINER, 
1627                         "PreEventState.apply, proxyChanged = proxy. ServiceID: {0}",
1628                         new Object[]{t}
1629                     );
1630 		proxyChanged = proxy; // start tracking instead
1631 	    } //endif
1632 	    if (!notDiscarded) {
1633                 if (loggable)
1634                     ServiceDiscoveryManager.log(
1635                         Level.FINER, 
1636                         "PreEventState.apply, !notifyServiceRemoved, replacProxyUsedToTrackChange ServiceID: {0}",
1637                         new Object[]{t}
1638                     );
1639 		itemReg.replaceProxyUsedToTrackChange(proxyChanged, newItem);
1640 		itemReg.setFilteredItem(null);
1641 		itemReg.discard();
1642 		if (matchMatchEvent) {
1643 		    return itemReg;
1644 		}
1645 	    } //endif
1646 	    /* For an explanation of the logic of the following if-else-block,
1647 	     * refer to the method description above.
1648 	     */
1649 	    if (matchMatchEvent || sameVersion(newItem, oldItem)) {
1650 		if (!notDiscarded) {
1651                     if (loggable)
1652                         ServiceDiscoveryManager.log(
1653                             Level.FINER, 
1654                             "PreEventState.apply, matchMatchEvent || sameVersion && !notifyServiceRemoved return itemReg, no need to filter ServiceID: {0}",
1655                             new Object[]{t}
1656                         );
1657 		    return itemReg; 
1658 		}
1659 		/* Same version, determine if the attributes have changed.
1660 		 * But first, replace the new service proxy with the old
1661 		 * service proxy so the client always uses the old proxy
1662 		 * (at least, until the version is changed).
1663 		 */
1664 		//                newItem.service = oldItem.service; //Data race
1665 		/* Now compare attributes */
1666 		attrsChanged = !LookupAttributes.equal(newItem.attributeSets, oldItem.attributeSets);
1667 		if (!attrsChanged) {
1668                     if (loggable)
1669                         ServiceDiscoveryManager.log(
1670                             Level.FINER, 
1671                             "PreEventState.apply, matchMatchEvent || sameVersion && !attrsChanged return itemReg, no need to filter ServiceID: {0}",
1672                             new Object[]{t}
1673                         );
1674 		    return itemReg; //no change, no need to filter
1675 		}
1676 	    } else {
1677 		//(!matchMatchEvent && !same version) ==> re-registration
1678                 if (loggable)
1679                     ServiceDiscoveryManager.log(
1680                         Level.FINER, 
1681                         "PreEventState.apply, !matchMatchEvent &&! sameVersion ==> re-registrattion, versionChanged. ServiceID: {0}",
1682                         new Object[]{t}
1683                     );
1684 		versionChanged = true;
1685 	    } //endif
1686             if (loggable)
1687                 ServiceDiscoveryManager.log(
1688                     Level.FINER, 
1689                     "PreEventState.apply, need to filter true. ServiceID: {0}",
1690                     new Object[]{t}
1691                 );
1692             needToFilter = true;
1693             return itemReg;
1694         }
1695         
1696     }
1697 
1698     /**
1699      * Convenience method that performs a byte-wise comparison, including
1700      * codebases, of the services referenced by the given service items, and
1701      * returns the result. If the services cannot be compared, it is assumed
1702      * that the versions are not the same, and <code>false</code> is returned.
1703      */
1704     private static boolean sameVersion(ServiceItem item0, ServiceItem item1) {
1705 	boolean fullyEqual = false;
1706 	try {
1707 	    MarshalledInstance mi0 = new MarshalledInstance(item0.service);
1708 	    MarshalledInstance mi1 = new MarshalledInstance(item1.service);
1709 	    fullyEqual = mi0.fullyEquals(mi1);
1710 	} catch (IOException e) {
1711             if (ServiceDiscoveryManager.logger.isLoggable(Level.INFO)){
1712                 ServiceDiscoveryManager.log(
1713                     Level.INFO, 
1714                     "failure marshalling old and new services for equality check",
1715                     e
1716                 );
1717             }
1718 	}
1719 	return fullyEqual;
1720     } //end LookupCacheImpl.sameVersion
1721 
1722     /**
1723      * Gets the remaining time left on the current cache's "lifespan".
1724      */
1725     public long getLeaseDuration() {
1726 	if (leaseDuration == Long.MAX_VALUE) {
1727 	    return Long.MAX_VALUE;
1728 	}
1729 	return leaseDuration + startTime - System.currentTimeMillis();
1730     } //end LookupCacheImpl.getLeaseDuration
1731 
1732     /**
1733      * Sends a notification to all listeners when a ServiceItem has been added.
1734      */
1735     private void addServiceNotify(ServiceItem item) {
1736 	serviceNotifyDo(null, item, ITEM_ADDED);
1737     } //end LookupCacheImpl.addServiceNotify
1738 
1739     /**
1740      * Sends a notification to the given listener when a ServiceItem has been
1741      * added.
1742      */
1743     private void addServiceNotify(ServiceItem item, ServiceDiscoveryListener srvcListener) {
1744 	eventNotificationExecutor.execute(new ServiceNotifyDo(null, item, ITEM_ADDED, srvcListener, this));
1745 	if (ServiceDiscoveryManager.logger.isLoggable(Level.FINEST)) {
1746 	    try {
1747 		throw new Exception("Back Trace");
1748 	    } catch (Exception ex) {
1749 		ex.fillInStackTrace();
1750                 ServiceDiscoveryManager.log(
1751                     Level.FINEST, 
1752                     "Log back trace",
1753                     ex
1754                 );
1755 	    }
1756 	}
1757     } //end LookupCacheImpl.addServiceNotify
1758 
1759     /**
1760      * Sends a notification when a ServiceItem has been removed.
1761      */
1762     private void removeServiceNotify(ServiceItem item) {
1763 	serviceNotifyDo(item, null, ITEM_REMOVED);
1764     } //end LookupCacheImpl.removeServiceNotify
1765 
1766     /**
1767      * Sends a notification when a ServiceItem has been changed, but still
1768      * matches.
1769      */
1770     private void changeServiceNotify(ServiceItem newItem, ServiceItem oldItem) {
1771 	serviceNotifyDo(oldItem, newItem, ITEM_CHANGED);
1772     } //end LookupCacheImpl.changeServiceNotify
1773 
1774     /**
1775      * Common code for performing service notification to all listeners.
1776      */
1777     private void serviceNotifyDo(ServiceItem oldItem, ServiceItem item, int action) {
1778         sItemListenersRead.lock();
1779         try {
1780 	    if (sItemListeners.isEmpty()) {
1781 		return;
1782 	    }
1783 	    Iterator<ServiceDiscoveryListener> iter = sItemListeners.iterator();
1784 	    while (iter.hasNext()) {
1785 		ServiceDiscoveryListener sl = iter.next();
1786 		eventNotificationExecutor.execute(new ServiceNotifyDo(oldItem, item, action, sl, this));
1787 		if (ServiceDiscoveryManager.logger.isLoggable(Level.FINEST)) {
1788 		    try {
1789 			throw new Exception("Back Trace");
1790 		    } catch (Exception ex) {
1791 			ex.fillInStackTrace();
1792                         ServiceDiscoveryManager.log(
1793                             Level.FINEST, 
1794                             "Log back trace",
1795                             ex
1796                         );
1797 		    }
1798 		}
1799 	    } //end loop
1800 	} finally {
1801             sItemListenersRead.unlock();
1802         }
1803     } //end LookupCacheImpl.serviceNotifyDo
1804 
1805     /**
1806      * Common code for performing service notification to one listener in an
1807      * executor task thread.
1808      */
1809     private static class ServiceNotifyDo implements Runnable {
1810 
1811 	final ServiceItem oldItem;
1812 	final ServiceItem item;
1813 	final int action;
1814 	final ServiceDiscoveryListener sl;
1815 	final Object lookupCache;
1816 
1817 	ServiceNotifyDo(ServiceItem oldItem, ServiceItem item, int action, ServiceDiscoveryListener sl, LookupCache lookupCache) {
1818 	    this.oldItem = oldItem;
1819 	    this.item = item;
1820 	    this.action = action;
1821 	    this.sl = sl;
1822 	    this.lookupCache = lookupCache;
1823 	}
1824 	
1825 	@Override
1826 	public void run() {
1827 	    ServiceDiscoveryEvent event;
1828 	    try {
1829 		event = new ServiceDiscoveryEvent(lookupCache, oldItem, item);
1830 	    } catch (NullPointerException e) {
1831 		boolean lookupCacheNull = lookupCache == null;
1832 		boolean oldItemNull = oldItem == null;
1833 		boolean itemNull = item == null;
1834                 if (ServiceDiscoveryManager.logger.isLoggable(Level.INFO))
1835                     ServiceDiscoveryManager.log(
1836                         Level.INFO, 
1837                         "ServiceDiscoveryEvent constructor threw NullPointerException, lookupCache null? {0} oldItem null? {1} item null? {2}",
1838                         new Object[]{lookupCacheNull, oldItemNull, itemNull}
1839                     );
1840 		return;
1841 	    }
1842 	    switch (action) {
1843 		case ITEM_ADDED:
1844 		    sl.serviceAdded(event);
1845 		    break;
1846 		case ITEM_REMOVED:
1847 		    sl.serviceRemoved(event);
1848 		    break;
1849 		case ITEM_CHANGED:
1850 		    sl.serviceChanged(event);
1851 		    break;
1852 		default:
1853 		    throw new IllegalArgumentException("case must be one of the following: ITEM_ADDED, ITEM_REMOVED or ITEM_CHANGED");
1854 	    } //end switch(action)
1855 	}
1856     }
1857 
1858     void initCache() throws RemoteException {
1859 	/* Get the exporter for the remote event listener from the
1860 	 * configuration.
1861 	 */
1862 	try {
1863 	    Exporter defaultExporter = 
1864                 new BasicJeriExporter(TcpServerEndpoint.getInstance(0),
1865                     new AtomicILFactory(null, null, LookupCacheImpl.class.getClassLoader()),
1866 		    false,
1867 		    false
1868                 );
1869 	    lookupListenerExporter = 
1870                 sdm.thisConfig.getEntry(
1871                     ServiceDiscoveryManager.COMPONENT_NAME,
1872                     "eventListenerExporter",
1873                     Exporter.class, 
1874                     defaultExporter
1875                 );
1876 	} catch (ConfigurationException e) {
1877 	    // exception, use default
1878 	    ExportException e1 = new ExportException("Configuration exception while " + "retrieving exporter for " + "cache's remote event listener", e);
1879 	    throw e1;
1880 	}
1881 	/*
1882 	 * Executor dedicated to event notification.
1883 	 */
1884 	try {
1885 	    eventNotificationExecutor = 
1886                 sdm.thisConfig.getEntry(
1887                     ServiceDiscoveryManager.COMPONENT_NAME,
1888                     "eventNotificationExecutor",
1889                     ExecutorService.class
1890                 );
1891 	} catch (ConfigurationException e) {
1892 	    /* use default */
1893 	    eventNotificationExecutor = 
1894                     new ThreadPoolExecutor(1, 1, 15L, TimeUnit.SECONDS, 
1895                             new LinkedBlockingQueue<Runnable>(),
1896                             new NamedThreadFactory("SDM event notifier: " + toString(), false),
1897                             new ThreadPoolExecutor.CallerRunsPolicy());
1898 	}
1899 	/* Get a general-purpose task manager for this cache from the
1900 	 * configuration. This task manager will be used to manage the
1901 	 * various tasks executed by this instance of the lookup cache.
1902 	 */
1903 	try {
1904 	    cacheTaskMgr = sdm.thisConfig.getEntry(
1905                     ServiceDiscoveryManager.COMPONENT_NAME,
1906                     "cacheExecutorService",
1907                     ExecutorService.class
1908             );
1909 	} catch (ConfigurationException e) {
1910 	    /* use default */
1911 	    cacheTaskMgr = new ThreadPoolExecutor(3, 3, 15L, TimeUnit.SECONDS,
1912                     new LinkedBlockingQueue<Runnable>(),
1913                     new NamedThreadFactory("SDM lookup cache: " + toString(), false),
1914                     new ThreadPoolExecutor.CallerRunsPolicy()
1915             );
1916 	}
1917 	cacheTaskMgr = new ExtensibleExecutorService(cacheTaskMgr, new ExtensibleExecutorService.RunnableFutureFactory() {
1918 	    @Override
1919 	    public <T> RunnableFuture<T> newTaskFor(Runnable r, T value) {
1920 		if (r instanceof ObservableFutureTask) {
1921 		    return (RunnableFuture<T>) r;
1922 		}
1923 		return new CacheTaskWrapper<T>(r, value);
1924 	    }
1925 
1926 	    @Override
1927 	    public <T> RunnableFuture<T> newTaskFor(Callable<T> c) {
1928 		if (c instanceof ObservableFutureTask) {
1929 		    return (RunnableFuture<T>) c;
1930 		}
1931 		return new CacheTaskWrapper<T>(c);
1932 	    }
1933 	});
1934 	cacheTaskDepMgr = new CacheTaskDependencyManager(cacheTaskMgr);
1935 	/* Get a special-purpose task manager for this cache from the
1936 	 * configuration. That Executor will be used to manage the
1937 	 * various instances of the special-purpose task, executed by
1938 	 * this instance of the lookup cache, that waits on verification
1939 	 * events after a previousy discovered service has been discarded.
1940 	 */
1941 	try {
1942 	    serviceDiscardTimerTaskMgr = 
1943                 sdm.thisConfig.getEntry(
1944                     ServiceDiscoveryManager.COMPONENT_NAME,
1945                     "discardExecutorService",
1946                     ScheduledExecutorService.class
1947                 );
1948 	} catch (ConfigurationException e) {
1949 	    /* use default */
1950 	    serviceDiscardTimerTaskMgr = 
1951                 new ScheduledThreadPoolExecutor(
1952                     4,
1953                     new NamedThreadFactory("SDM discard timer: " + toString(), false)
1954                 );
1955 	}
1956         /* ExecutorService for processing incoming events.
1957          * 
1958          */
1959         try {
1960             incomingEventExecutor = sdm.thisConfig.getEntry(
1961                 ServiceDiscoveryManager.COMPONENT_NAME, 
1962                 "ServiceEventExecutorService", 
1963                 ExecutorService.class
1964             );
1965         } catch (ConfigurationException e){
1966             incomingEventExecutor = 
1967                 new ThreadPoolExecutor(1, 1, 15L, TimeUnit.SECONDS,
1968                     new PriorityBlockingQueue(256),
1969                     new NamedThreadFactory("SDM ServiceEvent: " + toString(), false),
1970                     new ThreadPoolExecutor.DiscardOldestPolicy()
1971                 );
1972         }
1973         incomingEventExecutor = new ExtensibleExecutorService(incomingEventExecutor,
1974             new ExtensibleExecutorService.RunnableFutureFactory()
1975             {
1976 
1977                 @Override
1978                 public <T> RunnableFuture<T> newTaskFor(Runnable r, T value) {
1979                     return new ComparableFutureTask<T>(r,value);
1980                 }
1981 
1982                 @Override
1983                 public <T> RunnableFuture<T> newTaskFor(Callable<T> c) {
1984                     return new ComparableFutureTask<T>(c);
1985                 }
1986                 
1987             }
1988         );
1989 	// Moved here from constructor to avoid publishing this reference
1990 	lookupListenerProxy = lookupListener.export();
1991         sdm.proxyRegSetRead.lock();
1992         try {
1993             Iterator<ProxyReg> it = sdm.proxyRegSet.iterator();
1994             while (it.hasNext()) {
1995                 addProxyReg(it.next());
1996             }
1997         } finally {
1998             sdm.proxyRegSetRead.unlock();
1999         }
2000             
2001     } //end LookupCacheImpl.initCache
2002     
2003     /**
2004      * This enables a BlockingPriorityQueue to arrange tasks in an
2005      * ExecutorService.
2006      * 
2007      * @param <V> 
2008      */
2009     private static class ComparableFutureTask<V> extends FutureTask<V> 
2010                                 implements Comparable<ComparableFutureTask> 
2011     {
2012         
2013         private final Object task;
2014 
2015         public ComparableFutureTask(Runnable runnable, V result) {
2016             super(runnable, result);
2017             task = runnable;
2018         }
2019         
2020         public ComparableFutureTask(Callable<V> callable){
2021             super(callable);
2022             task = callable;
2023         }
2024 
2025         @Override
2026         public int compareTo(ComparableFutureTask o) {
2027             if (task instanceof Comparable && o.task instanceof Comparable){
2028                 return ((Comparable)task).compareTo(o.task);
2029             }
2030             if (ServiceDiscoveryManager.logger.isLoggable(Level.FINEST))
2031                 ServiceDiscoveryManager.log(
2032                     Level.FINEST, 
2033                     "task not instanceof Comparable {0}",
2034                     new Object [] {task.getClass().getCanonicalName()}
2035                 );
2036             return 0;
2037         }
2038 
2039 	@Override
2040 	public int hashCode() {
2041 	    int hash = 3;
2042 	    hash = 89 * hash + (this.task != null ? this.task.hashCode() : 0);
2043 	    return hash;
2044 	}
2045 	
2046 	@Override
2047 	public boolean equals(Object o){
2048 	    if (!(o instanceof ComparableFutureTask)) return false;
2049 	    return this.task.equals(((ComparableFutureTask)o).task);
2050 	}
2051 
2052     }
2053 
2054     /**
2055      * Applies the first-stage <code>filter</code> associated with the current
2056      * instance of <code>LookupCache</code> to the given <code>item</code> and
2057      * returns the resulting filtered item if the <code>filter</code> is passed
2058      * (or is <code>null</code>); otherwise, returns <code>null</code> and sends
2059      * a service removed event if the <code>sendEvent</code> parameter is
2060      * <code>true</code>.
2061      * <p>
2062      * This method is called only when the <code>item</code> to be filtered
2063      * corresponds to an element that currently exists in the
2064      * <code>serviceIdMap</code>.
2065      * <p>
2066      * As described in the <code>ServiceItemFilter</code> specification, when
2067      * the <code>item</code> passes the <code>filter</code>, the
2068      * <code>service</code> field of the <code>item</code> is replaced with the
2069      * filtered form of the object previously contained in that field. In this
2070      * case, the <code>filteredItem</code> field of the corresponding
2071      * <code>ServiceItemReg</code> element of the <code>serviceIdMap</code> is
2072      * set to this new filtered item.
2073      * <p>
2074      * If the <code>filter</code> returns <code>indefinite</code>, then that
2075      * specification states that the <code>service</code> field is replaced with
2076      * <code>null</code>. In this case, the <code>filteredItem</code> field of
2077      * the corresponding <code>ServiceItemReg</code> element of the
2078      * <code>serviceIdMap</code> is left unchanged.
2079      */
2080     private ServiceItem filterMaybeDiscard(ServiceID srvcID, ServiceItemReg itemReg, ServiceItem item, boolean sendEvent) {
2081         if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE))
2082             ServiceDiscoveryManager.log(
2083                 Level.FINE,
2084                 "filterMaybeDiscard called, ServiceID: {0}", 
2085                 new Object [] {srvcID}
2086             );
2087 	if ((item == null) || (item.service == null)) {
2088             if (ServiceDiscoveryManager.logger.isLoggable(Level.FINER))
2089                 ServiceDiscoveryManager.log(
2090                     Level.FINER,
2091                     "filterMaybeDiscard, item or service was null, returning null, ServiceID: {0}", 
2092                     new Object []{srvcID}
2093                 );
2094 	    return null;
2095 	}
2096         boolean addFilteredItemToMap = false;
2097         /* Make a copy to filter because the filter may modify it. */
2098         ServiceItem filteredItem = item.clone();
2099         boolean discardRetryLater = false;
2100         boolean pass = false;
2101 	if (filter == null) {
2102             pass = true;
2103 	    if (useInsecureLookup){
2104                 addFilteredItemToMap = true;
2105 	    } else {
2106 		try {
2107 		    filteredItem.service =
2108 			    ((ServiceProxyAccessor) filteredItem.service).getServiceProxy();
2109                     addFilteredItemToMap = true;
2110 		} catch (RemoteException ex) {
2111                     if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE))
2112                         ServiceDiscoveryManager.log(Level.FINE,
2113 			    "Exception thrown while trying to download service proxy",
2114 			    ex
2115                         );
2116                     discardRetryLater = true;
2117 		}
2118 	    }
2119 	} else { 
2120             if (useInsecureLookup){
2121                 pass = filter.check(filteredItem);
2122             } else {
2123                 try {
2124                     pass = filter.check(filteredItem);
2125                 } catch (SecurityException ex){ 
2126                     try {
2127                         // Filter didn't expect bootstrap proxy
2128                         filteredItem.service = ((ServiceProxyAccessor) filteredItem.service).getServiceProxy();
2129                         pass = filter.check(filteredItem);
2130                     } catch (RemoteException ex1) {
2131                         if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE))
2132                             ServiceDiscoveryManager.log(Level.FINE, 
2133                                 "Exception thrown while trying to download service proxy",
2134                                 ex1
2135                             );
2136                         discardRetryLater = true;
2137                     }
2138                 } catch (ClassCastException ex){
2139 		    try {
2140                         // Filter didn't expect bootstrap proxy
2141                         filteredItem.service = ((ServiceProxyAccessor) filteredItem.service).getServiceProxy();
2142                         pass = filter.check(filteredItem);
2143                     } catch (RemoteException ex1) {
2144                         if (ServiceDiscoveryManager.logger.isLoggable(Level.FINE))
2145                             ServiceDiscoveryManager.log(Level.FINE, 
2146                                 "Exception thrown while trying to download service proxy",
2147                                 ex1
2148                             );
2149                         discardRetryLater = true;
2150                     }
2151 		}
2152             }
2153             /* Handle filter pass */
2154             if (pass && !discardRetryLater && filteredItem.service != null) {
2155                 addFilteredItemToMap = true;
2156                 if (ServiceDiscoveryManager.logger.isLoggable(Level.FINER))
2157                     ServiceDiscoveryManager.log(Level.FINER, 
2158                         "filterMaybeDiscard, filter passed, ServiceID: {0}", 
2159                         new Object[]{srvcID}
2160                     );
2161             } //endif(pass)
2162         }//endif
2163         PostEventState pes = 
2164                 new PostEventState(this, itemReg, item, filteredItem, sendEvent,
2165                         pass, discardRetryLater, addFilteredItemToMap, sdm.getDiscardWait());
2166         serviceIdMap.computeIfPresent(srvcID, pes);
2167         if (pes.notifyRemoved && pes.oldFilteredItem != null) {
2168             removeServiceNotify(pes.oldFilteredItem);
2169         }
2170         if (ServiceDiscoveryManager.logger.isLoggable(Level.FINER))
2171             ServiceDiscoveryManager.log(Level.FINER, 
2172                 "filterMaybeDiscard, returning filtered ServiceItem: {0}",
2173                 new Object []{pes.filteredItemPass}
2174             );
2175 	return pes.filteredItemPass;
2176     } //end LookupCacheImpl.filterMaybeDiscard
2177     
2178     private static class PostEventState implements BiFunction<ServiceID, ServiceItemReg, ServiceItemReg> {
2179         
2180         ServiceItem filteredItemPass;
2181         ServiceItem oldFilteredItem = null;
2182         final ServiceItem item;
2183         final ServiceItem filteredItem;
2184         final boolean pass;
2185         final boolean discardRetryLater;
2186         final boolean sendEvent;
2187         final boolean addFilteredItemToMap;
2188         boolean notifyRemoved = false;
2189         final ServiceItemReg expected;
2190         final LookupCacheImpl cache;
2191         final long discardWait;
2192         
2193         PostEventState(LookupCacheImpl cache, ServiceItemReg itemReg,
2194                 ServiceItem item, ServiceItem filteredItem, boolean sendEvent,
2195                 boolean pass, boolean discardRetryLater,
2196                 boolean addFilteredItemToMap, long discardWait)
2197         {
2198             this.cache = cache;
2199             this.expected = itemReg;
2200             this.item = item;
2201             this.filteredItem = filteredItem;
2202             this.sendEvent = sendEvent;
2203             this.pass = pass;
2204             this.discardRetryLater = discardRetryLater;
2205             this.addFilteredItemToMap = addFilteredItemToMap;
2206             this.discardWait = discardWait;
2207         }
2208 
2209         @Override
2210         public ServiceItemReg apply(ServiceID id, ServiceItemReg itemReg) {
2211             if (!expected.equals(itemReg)) return itemReg;
2212             ServiceItemReg removeIfNull = itemReg;
2213             /* Handle filter fail */
2214             if (!pass && !discardRetryLater) {
2215                 if (itemReg != null) {
2216                     if (sendEvent) {
2217                         oldFilteredItem = itemReg.getFilteredItem();
2218                         notifyRemoved = true;
2219                         removeIfNull = null;
2220                     } else {
2221                         removeIfNull = null;
2222                     } //endif
2223                 } //endif
2224                 filteredItemPass = null;
2225             } //endif(fail)
2226             if (addFilteredItemToMap){
2227                 /**
2228                  * Replaces the <code>item</code> field of itemReg
2229                  * with the given <code>item</code> parameter; and sets the
2230                  * <code>filteredItem</code> field of itemReg to the value contained in
2231                  * the <code>filteredItem</code> parameter.
2232                  */
2233                 cache.cancelDiscardTask(id);
2234                 itemReg.replaceProxyUsedToTrackChange(null, item);
2235                 itemReg.setFilteredItem(filteredItem);
2236                 filteredItemPass = filteredItem;
2237             }
2238             /* Handle filter indefinite */
2239             if (discardRetryLater){
2240                 /**
2241                  * Sets a service
2242                  * removed event, and queues a <code>ServiceDiscardTimerTask</code> to retry
2243                  * the filter at a later time.
2244                  * If there's been any change in what is being discarded for
2245                  * filter retry, then update the item field in the map to
2246                  * capture that change; and set the filteredItem field to
2247                  * to null to guarantee that the filter is re-applied to
2248                  * that changed item.
2249                  */
2250                 if (itemReg.discard()) {
2251                     itemReg.replaceProxyUsedToTrackChange(null, item);
2252                     itemReg.setFilteredItem(null);
2253                     Future f = cache.serviceDiscardTimerTaskMgr.schedule(
2254                             new ServiceDiscardTimerTask(cache, item.serviceID),
2255                             discardWait,
2256                             TimeUnit.MILLISECONDS
2257                     );
2258                     cache.serviceDiscardFutures.put(item.serviceID, f);
2259                     if (sendEvent) {
2260                         notifyRemoved = true;
2261                     }
2262                 }
2263             }
2264             return removeIfNull;
2265         }
2266         
2267     }
2268 
2269     /**
2270      * Convenience method called (only when a TRANSITION_MATCH_NOMATCH event is
2271      * received) that removes the given <code>item</code> from the
2272      * <code>serviceIdMap</code> and wakes up the
2273      * <code>ServiceDiscardTimerTask</code> if the given <code>item</code> is
2274      * discarded; otherwise, sends a removed event.
2275      */
2276     private void handleMatchNoMatch(ServiceRegistrar proxy, ServiceID srvcID) {
2277         if (ServiceDiscoveryManager.logger.isLoggable(Level.FINER))
2278             ServiceDiscoveryManager.log(Level.FINER, 
2279                 "handleMatchNoMatch called, ServiceID: {0}",
2280                 new Object []{srvcID}
2281             );
2282         DissociateLusCleanUpOrphan dlcl = new DissociateLusCleanUpOrphan(this, proxy);
2283         serviceIdMap.computeIfPresent(srvcID, dlcl);
2284         if (dlcl.itemRegProxy != null) {
2285             itemMatchMatchChange(srvcID, dlcl.itmReg, dlcl.itemRegProxy, dlcl.newItem, false);
2286         } else if (dlcl.notify && dlcl.filteredItem != null) {
2287             removeServiceNotify(dlcl.filteredItem);
2288         }
2289     } //end LookupCacheImpl.handleMatchNoMatch
2290     
2291     /**
2292      * Atomic block of code.
2293      */
2294     private static class DissociateLusCleanUpOrphan 
2295              implements BiFunction<ServiceID, ServiceItemReg, ServiceItemReg> 
2296      {
2297 
2298         final LookupCacheImpl cache;
2299         boolean notify;
2300         final ServiceRegistrar proxy;
2301         ServiceRegistrar itemRegProxy;
2302         ServiceItemReg itmReg;
2303         ServiceItem newItem;
2304         ServiceItem filteredItem;
2305         
2306         DissociateLusCleanUpOrphan(LookupCacheImpl cache, ServiceRegistrar proxy){
2307             this.itmReg = null;
2308             this.notify = false;
2309             this.itemRegProxy = null;
2310             this.cache = cache;
2311             this.proxy = proxy;
2312             this.newItem = null;
2313             this.filteredItem = null;
2314         }
2315         
2316         @Override
2317         public ServiceItemReg apply(ServiceID srvcID, ServiceItemReg itemReg) {
2318             itmReg = itemReg;
2319             newItem = itemReg.removeProxy(proxy);
2320             filteredItem = itemReg.getFilteredItem();
2321             if (newItem != null) {
2322                 itemRegProxy = itemReg.getProxy();
2323             } 
2324             /**
2325              *
2326              */
2327             else if (itemReg.hasNoProxys()) {
2328                 if (itemReg.isDiscarded()) {
2329                     /* Remove item from map and wake up the discard task */
2330                     itmReg = null;
2331                     cache.cancelDiscardTask(srvcID);
2332                 } else {
2333                     //remove item from map and send removed event
2334                     notify = true;
2335                     itmReg = null;
2336                 } //endif
2337             } //endif
2338             return itmReg;
2339         }
2340     }
2341 
2342     /**
2343      * Wake up service discard task if running, else remove from mgr.
2344      */
2345     private void cancelDiscardTask(ServiceID sid) {
2346 	// Might need to record future's and cancel from there.
2347 	Future task = serviceDiscardFutures.get(sid);
2348 	if (task != null) {
2349 	    task.cancel(true);
2350 	}
2351     } //end LookupCacheImpl.cancelDiscardTask
2352 
2353     /**
2354      *      
2355      */
2356     final static class CacheTaskDependencyManager implements FutureObserver {
2357 
2358 	// CacheTasks pending completion.
2359 
2360 	private final ConcurrentLinkedQueue<CacheTaskWrapper> pending;
2361 	private final ExecutorService executor;
2362 
2363 	CacheTaskDependencyManager(ExecutorService e) {
2364 	    this.pending = new ConcurrentLinkedQueue<CacheTaskWrapper>();
2365 	    executor = e;
2366 	}
2367 
2368 	CacheTaskWrapper submit(Runnable t) {
2369 	    CacheTaskWrapper future = new CacheTaskWrapper(t, null);
2370 	    pending.offer(future);
2371 	    future.addObserver(this);
2372 	    if (t instanceof CacheTask && ((CacheTask) t).hasDeps()) {
2373 		List<FutureObserver.ObservableFuture> deps = new LinkedList<FutureObserver.ObservableFuture>();
2374 		Iterator<CacheTaskWrapper> it = pending.iterator();
2375 		while (it.hasNext()) {
2376 		    CacheTaskWrapper w = it.next();
2377 		    Object c = w.getTask();
2378 		    if (c instanceof CacheTask && ((CacheTask) t).dependsOn((CacheTask) c)) {
2379 			deps.add(w);
2380 		    }
2381 		}
2382 		if (deps.isEmpty()) {
2383 		    executor.submit(future);
2384                     if (ServiceDiscoveryManager.logger.isLoggable(Level.FINEST))
2385                         ServiceDiscoveryManager.log(
2386                             Level.FINEST, 
2387                             "ServiceDiscoveryManager {0} submitted to executor task queue",
2388                             new Object []{t.toString()}
2389                         );
2390 		} else {
2391 		    DependencyLinker linker = new DependencyLinker(executor, deps, future);
2392 		    linker.register();
2393                     if (ServiceDiscoveryManager.logger.isLoggable(Level.FINEST))
2394                         ServiceDiscoveryManager.log(
2395                                 Level.FINEST, 
2396                                 "ServiceDiscoveryManager {0} registered dependencies", 
2397                                 new Object [] {t.toString()}
2398                         );
2399 		}
2400 	    } else {
2401 		executor.submit(future);
2402                 if (ServiceDiscoveryManager.logger.isLoggable(Level.FINEST))
2403                     ServiceDiscoveryManager.log(Level.FINEST, 
2404                         "ServiceDiscoveryManager {0} submitted to executor task queue",
2405                         new Object []{t.toString()}
2406                     );
2407 	    }
2408 	    return future;
2409 	}
2410 
2411 	@Override
2412 	public void futureCompleted(Future e) {
2413 	    pending.remove(e);
2414 	    Object t;
2415 	    if (e instanceof CacheTaskWrapper) {
2416 		t = ((CacheTaskWrapper) e).getTask();
2417 	    } else {
2418 		t = e;
2419 	    }
2420             if (ServiceDiscoveryManager.logger.isLoggable(Level.FINEST))
2421                 ServiceDiscoveryManager.log(
2422                     Level.FINEST,
2423                     "ServiceDiscoveryManager {0} completed execution", 
2424                     new Object[]{t.toString()}
2425                 );
2426 	}
2427 
2428 	/**
2429 	 * Removes from the cache's task queue, all pending tasks associated
2430 	 * with the given ProxyReg. This method is called when the given
2431 	 * ProxyReg has been discarded.
2432 	 */
2433 	void removeUselessTask(ProxyReg reg) {
2434 	    Iterator<CacheTaskWrapper> it = pending.iterator();
2435 	    while (it.hasNext()) {
2436 		CacheTaskWrapper w = it.next();
2437 		Object t = w.getTask();
2438 		if (t instanceof CacheTask && ((CacheTask) t).isFromProxy(reg)) {
2439 		    w.cancel(true); // Also causes task to be removed
2440                     if (ServiceDiscoveryManager.logger.isLoggable(Level.FINEST))
2441                         ServiceDiscoveryManager.log(
2442                             Level.FINEST,
2443                             "ServiceDiscoveryManager {0} cancelled", 
2444                             new Object[]{t.toString()}
2445                         );
2446 		}
2447 	    } //end loop
2448 	} //end LookupCacheImpl.removeUselessTask
2449 
2450     }
2451 
2452     /**
2453      * ObservableFuture wrapper class for CacheTask's.
2454      *
2455      * @param <T>
2456      */
2457     final static class CacheTaskWrapper<T> extends ObservableFutureTask<T> {
2458 
2459 	private final Object task;
2460 
2461 	CacheTaskWrapper(Runnable r, T result) {
2462 	    super(r, result);
2463 	    task = r;
2464 	}
2465 
2466 	CacheTaskWrapper(Callable<T> c) {
2467 	    super(c);
2468 	    task = c;
2469 	}
2470 
2471 	Object getTask() {
2472 	    return task;
2473 	}
2474 
2475     }
2476 
2477     /**
2478      * Class for implementing register/lookup/notify/dropProxy/discard tasks
2479      */
2480     abstract static class CacheTask implements Runnable {
2481 
2482 	protected final ProxyReg reg;
2483 	protected volatile long thisTaskSeqN;
2484 
2485 	protected CacheTask(ProxyReg reg, long seqN) {
2486 	    this.reg = reg;
2487 	    this.thisTaskSeqN = seqN;
2488             if (ServiceDiscoveryManager.logger.isLoggable(Level.FINEST))
2489                 ServiceDiscoveryManager.log(
2490                     Level.FINEST,
2491                     "ServiceDiscoveryManager {0} constructed", 
2492                     new Object[]{toString()}
2493                 );
2494 	} //end constructor
2495 	/* check if the task is on a specific ProxyReg, return true if it is */
2496 
2497 	public boolean isFromProxy(ProxyReg reg) {
2498 	    if (this.reg == null) {
2499 		return false;
2500 	    }
2501 	    return (this.reg).equals(reg);
2502 	} //end isFromProxy
2503 
2504 	/**
2505 	 * Returns the ProxyReg associated with this task (if any).
2506 	 */
2507 	public ProxyReg getProxyReg() {
2508 	    return reg;
2509 	} //end ProxyReg
2510 
2511 	/**
2512 	 * Returns the unique sequence number of this task.
2513 	 */
2514 	public long getSeqN() {
2515 	    return thisTaskSeqN;
2516 	} //end getSeqN
2517 
2518 	public abstract boolean hasDeps();
2519 
2520 	public boolean dependsOn(CacheTask task) {
2521 	    return false;
2522 	}
2523     } //end class CacheTask
2524 }