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  package org.apache.river.norm;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InterruptedIOException;
23  import java.io.ObjectInputStream;
24  import java.io.ObjectOutputStream;
25  import java.io.OutputStream;
26  import java.rmi.MarshalledObject;
27  import java.rmi.NoSuchObjectException;
28  import java.rmi.RemoteException;
29  import java.security.PrivilegedActionException;
30  import java.security.PrivilegedExceptionAction;
31  import java.util.Collection;
32  import java.util.Collections;
33  import java.util.HashMap;
34  import java.util.Iterator;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Set;
38  import java.util.logging.Level;
39  import java.util.logging.LogRecord;
40  import java.util.logging.Logger;
41  
42  import javax.security.auth.Subject;
43  import javax.security.auth.login.LoginContext;
44  
45  import net.jini.config.Configuration;
46  import net.jini.config.ConfigurationProvider;
47  import net.jini.core.constraint.RemoteMethodControl;
48  import net.jini.core.discovery.LookupLocator;
49  import net.jini.core.entry.Entry;
50  import net.jini.core.event.EventRegistration;
51  import net.jini.core.event.RemoteEvent;
52  import net.jini.core.event.RemoteEventListener;
53  import net.jini.core.lease.Lease;
54  import net.jini.core.lease.LeaseDeniedException;
55  import net.jini.core.lease.UnknownLeaseException;
56  import net.jini.core.lookup.ServiceID;
57  import net.jini.export.Exporter;
58  import net.jini.export.ProxyAccessor;
59  import net.jini.id.ReferentUuid;
60  import net.jini.id.Uuid;
61  import net.jini.id.UuidFactory;
62  import net.jini.lease.LeaseRenewalEvent;
63  import net.jini.lease.LeaseRenewalManager;
64  import net.jini.lease.LeaseRenewalService;
65  import net.jini.lease.LeaseRenewalSet;
66  import net.jini.lookup.entry.ServiceInfo;
67  import net.jini.security.ProxyPreparer;
68  import net.jini.security.TrustVerifier;
69  import net.jini.security.proxytrust.ServerProxyTrust;
70  import net.jini.security.proxytrust.TrustEquivalence;
71  
72  import org.apache.river.constants.ThrowableConstants;
73  import org.apache.river.constants.VersionConstants;
74  import org.apache.river.landlord.Landlord.RenewResults;
75  import org.apache.river.landlord.LandlordUtil;
76  import org.apache.river.landlord.LeaseFactory;
77  import org.apache.river.landlord.LeasePeriodPolicy;
78  import org.apache.river.landlord.LocalLandlord;
79  import org.apache.river.lookup.entry.BasicServiceType;
80  import org.apache.river.norm.event.EventType;
81  import org.apache.river.norm.event.EventTypeGenerator;
82  import org.apache.river.norm.event.SendMonitor;
83  import org.apache.river.norm.lookup.JoinState;
84  import org.apache.river.proxy.ThrowThis;
85  import org.apache.river.start.lifecycle.LifeCycle;
86  import org.apache.river.reliableLog.LogException;
87  import org.apache.river.reliableLog.LogHandler;
88  import org.apache.river.api.util.Startable;
89  import org.apache.river.thread.InterruptedStatusThread;
90  import org.apache.river.norm.proxy.*;
91  import java.security.AccessControlContext;
92  import java.security.AccessController;
93  import net.jini.export.CodebaseAccessor;
94  import net.jini.lookup.ServiceAttributesAccessor;
95  import net.jini.lookup.ServiceIDAccessor;
96  import net.jini.lookup.ServiceProxyAccessor;
97  import net.jini.loader.ClassLoading;
98  import org.apache.river.proxy.CodebaseProvider;
99  
100 /**
101  * Base class for implementations of NormServer.  Provides a complete
102  * non-activatable (but still logging) implementation.
103  *
104  * @author Sun Microsystems, Inc.
105  */
106 abstract class NormServerBaseImpl
107     implements NormServer, LocalLandlord, ServerProxyTrust, ProxyAccessor,
108 	Startable, ServiceProxyAccessor, ServiceAttributesAccessor,
109 	ServiceIDAccessor, CodebaseAccessor
110 {
111     /** Current version of log format */
112     private static final int CURRENT_LOG_VERSION = 2;
113 
114     /** Logger and configuration component name for Norm */
115     static final String NORM = "org.apache.river.norm";
116 
117     /** Logger for logging information about this instance */
118     static final Logger logger = Logger.getLogger(NORM);
119 
120     /** Whether this server is persistent. */
121     final boolean persistent;
122 
123     /** The login context, for logging out */
124     final LoginContext loginContext;
125 
126     /** The location of our persistent storage, or null if not persistent. */
127     final String persistenceDirectory;
128 
129     /** Proxy preparer for leases supplied through the API */
130     final private ProxyPreparer leasePreparer;
131 
132     /**
133      * Proxy preparer for leases recovered from persistent storage, or null if
134      * not persistent.
135      */
136     final private ProxyPreparer recoveredLeasePreparer;
137 
138     /** Proxy preparer for listeners supplied through the API */
139     final private ProxyPreparer listenerPreparer;
140 
141     /**
142      * Proxy preparer for listeners recovered from persistent storage, or null
143      * if not persistent.
144      */
145     final private ProxyPreparer recoveredListenerPreparer;
146 
147     /**
148      * Proxy preparer for lookup locators supplied through the API, and not
149      * including initial lookup locators.
150      */
151     final private ProxyPreparer locatorPreparer;
152 
153     /**
154      * Proxy preparer for lookup locators recovered from persistent storage, or
155      * null if not persistent.
156      */
157     final private ProxyPreparer recoveredLocatorPreparer;
158 
159     /** The exporter for exporting and unexporting */
160     final Exporter exporter;
161 
162     /** Object to notify when this service is destroyed, or null */
163     final private LifeCycle lifeCycle;
164 
165     /** The unique ID for this server. */
166     volatile private Uuid serverUuid;
167 
168     /** Our JoinManager */
169     volatile private JoinState joinState;
170 
171     /** Map of Uuids to LeaseSets */
172     final private Map<Uuid,LeaseSet> setTable = Collections.synchronizedMap(new HashMap<Uuid,LeaseSet>());
173 
174     /** Lease renewal manager that actually renews the leases */
175     final private LeaseRenewalManager lrm;
176 
177     /** Object that expires sets and generates expiration warning events */
178     final private LeaseExpirationMgr expMgr;
179 
180     /** Factory for creating leases */
181     volatile private LeaseFactory leaseFactory;
182 
183     /** Policy we use for granting and renewing renewal set leases */
184     final private LeasePeriodPolicy setLeasePolicy;
185 
186     /**
187      * Whether to isolate leases in their own renewal sets as opposed to
188      * batching leases across sets.
189      */
190     final private boolean isolateSets;
191 
192     /** Our persistant store */
193     volatile private PersistentStore store;
194 
195     /** Factory we use to create ClientLeaseWrapper IDs */
196     final private UIDGenerator idGen = new UIDGenerator() ;
197 
198     /** List of leases that have been renewed but not persisted */
199     final private List renewedList;
200 
201     /**
202      * Thread that pulls wrapped client leases off the renewedList and logs
203      * them to disk
204      */
205     final private RenewLogThread renewLogger;
206 
207     /** Object used to generate new event types */
208     volatile private EventTypeGenerator generator;
209 
210     /**
211      * Object that transfers events from the renewal manager to
212      * us so we can remove dead leases and send events
213      */
214     final private LRMEventListener lrmEventListener;
215 
216     /** Log file must contain this many records before snapshot allowed */
217     final private int logToSnapshotThresh;
218 
219     /** Weight factor applied to snapshotSize when deciding to take snapshot */
220     final private float snapshotWt;
221 
222     /** Inner server proxy */
223     volatile NormServer serverProxy = null;
224 
225     /** Outer service proxy */
226     volatile LeaseRenewalService normProxy = null;
227 
228     /** Admin proxy */
229     volatile private AdminProxy adminProxy;
230 
231     /** Thread that performs snapshots when signaled */
232     volatile private SnapshotThread snapshotter;
233 
234     /** Lock protecting startup and shutdown */
235     private final ReadyState ready = new ReadyState();
236 
237     /** Keep track of the number of leases. */
238     private final CountLeases countLeases = new CountLeases();
239     
240     /** Fields access only by thread that called constructor, then called start()
241      *  set to null at completion of start() */
242     private final AccessControlContext context;
243     private Configuration config;
244     
245     private boolean started;
246     private String certFactoryType;
247     private String certPathEncoding;
248     private byte[] encodedCerts;
249     private String codebase;
250 
251     ////////////////////////////////
252     // Methods defined in NormServer
253 
254     // Inherit java doc from super type
255     public void renewFor(Uuid id, Lease leaseToRenew,
256 			 long membershipDuration, long renewDuration) 
257 	throws RemoteException, ThrowThis
258     {
259 	ready.check();
260 
261 	// Lookup the set
262 	final LeaseSet set = getSet(id);
263 
264 	if (leaseToRenew == null) {
265 	    throw new NullPointerException("LeaseRenewalSet.renewFor:Must " +
266 					   "pass a non-null lease");
267 	}
268 
269 	if ((membershipDuration != Lease.FOREVER) &&
270 	    (renewDuration == Lease.ANY))
271 	{
272 	    throw new IllegalArgumentException(
273 	        "LeaseRenewalSet.renewFor:renewDuration can only be " +
274 		"Lease.ANY if membershipDuration is Lease.FOREVER");
275 	}
276 
277 	if (!(renewDuration == Lease.ANY || renewDuration == Lease.FOREVER ||
278 	      renewDuration > 0))
279 	{
280 	    throw new IllegalArgumentException(
281 	        "LeaseRenewalSet.renewFor:renewDuration can only be " +
282 		"Lease.ANY, Lease.FOREVER, or positive");
283 	}
284 
285 	leaseToRenew = (Lease) leasePreparer.prepareProxy(leaseToRenew);
286 
287 	// Ensure that the lease is not a current lease granted by this server
288 	if (leaseToRenew instanceof ReferentUuid) {
289 	    Uuid cookie = ((ReferentUuid) leaseToRenew).getReferentUuid();
290 	    LeaseSet setForLease = (LeaseSet) setTable.get(cookie);
291 	    if (setForLease != null) {
292 		synchronized (setForLease) {
293 		    if (isCurrent(setForLease)) {
294 			throw new IllegalArgumentException(
295 			    "Cannot add leases granted by a " +
296 			    "LeaseRenewalService to a set created by " +
297 			    "that service");
298 		    }
299 		}
300 	    }
301 	}
302 	
303 	// If we are told to dump the codebase of the lease we are adding
304 	if (logger.isLoggable(Level.FINE)) {
305 	    final Class lc = leaseToRenew.getClass();
306 	    logger.log(Level.FINE,
307 		       "Adding lease of class {0} with annotation {1}",
308 		       new Object[] {
309 			   leaseToRenew.getClass(),
310 			   ClassLoading.getClassAnnotation(lc) });
311 	}
312 
313 	// Add the lease to the set
314 	add(set, leaseToRenew, membershipDuration, renewDuration); 
315     }
316 
317     @Override
318     public ServiceID serviceID() throws IOException {
319 	return new ServiceID(serverUuid.getMostSignificantBits(),
320 			    serverUuid.getLeastSignificantBits());
321     }
322 
323     @Override
324     public Entry[] getServiceAttributes() throws IOException {
325 	return getLookupAttributes();
326     }
327 
328     @Override
329     public String getClassAnnotation() throws IOException {
330 	return "".equals(codebase) ? 
331 		CodebaseProvider.getClassAnnotation(NormServer.class) 
332 		: codebase;
333     }
334 
335     @Override
336     public String getCertFactoryType() throws IOException {
337 	return certFactoryType;
338     }
339 
340     @Override
341     public String getCertPathEncoding() throws IOException {
342 	return certPathEncoding;
343     }
344 
345     @Override
346     public byte[] getEncodedCerts() throws IOException {
347 	return encodedCerts.clone();
348     }
349 
350 
351     /**
352      * Prevents access to the service before it is ready or after it starts to
353      * shutdown.  Each public entrypoint to the service should call check or
354      * shutdown, and initialization should call ready when the service is ready
355      * to use. 
356      */
357     private static final class ReadyState {
358 	private static final int INITIALIZE = 0;
359 	private static final int READY = 1;
360 	private static final int SHUTDOWN = 2;
361 	private int state = INITIALIZE;
362 
363 	/**
364 	 * Checks if the service is ready to use, waiting if it is
365 	 * initializing, and throwing IllegalStateException if it is shutting
366 	 * down.
367 	 */
368 	synchronized void check() {
369 	    while (true) {
370 		switch (state) {
371 		  case INITIALIZE:
372 		      try {
373 			  wait();
374 		      } catch (InterruptedException e) {
375 		      }
376 		      break;
377 		case READY:
378 		    return;
379 		default:
380 		    throw new IllegalStateException(
381 			"Norm service is unavailable");
382 		}
383 	    }
384 	}
385 
386 	/**
387 	 * Marks the service ready for use, throwing IllegalStateException if
388 	 * it is shutting down.
389 	 */
390 	synchronized void ready() {
391 	    switch (state) {
392 	    case INITIALIZE:
393 		state = READY;
394 		notifyAll();
395 		break;
396 	    case READY:
397 		break;
398 	    default:
399 		throw new IllegalStateException("Norm service is unavailable");
400 	    }
401 	}
402 
403 	/**
404 	 * Marks the service as shutting down, waiting if it is initializing,
405 	 * and throwing IllegalStateException if it is already shutting down.
406 	 */
407 	synchronized void shutdown() {
408 	    check();
409 	    state = SHUTDOWN;
410 	    notifyAll();
411 	}
412     }
413 
414     /** Keeps track of the number of leases. */
415     private static class CountLeases {
416 	private int count;
417 
418 	private synchronized void updateCount(int change) {
419 	    count += change;
420 	    assert count >= 0;
421 	}
422 
423 	private synchronized int getCount() {
424 	    return count;
425 	}
426     }
427 
428     /** Update the number of leases being managed by this server. */
429     void updateLeaseCount(int change) {
430 	countLeases.updateCount(change);
431     }
432 
433     /**
434      * Add the lease to the set.
435      *
436      * @param set the LeaseSet to add the leaseToRenew to
437      * @param leaseToRenew the lease the client wants managed
438      * @param membershipDuration the length of time the client
439      *        wants the lease managed for
440      * @param renewDuration the length of time the client want the 
441      *        lease renewed for each time it is renewed
442      * @throws ThrowThis if the set no longer exists
443      */
444     private void add(LeaseSet set, Lease leaseToRenew,
445 		     long membershipDuration, long renewDuration) 
446 	throws ThrowThis
447     {
448 	try {
449 	    store.acquireMutatorLock();	    
450 	    synchronized (set) {
451 		ensureCurrent(set);
452 
453 		// Get a wrapper for the lease
454 		final long now = System.currentTimeMillis();
455 		ClientLeaseWrapper clw =
456 		    set.getClientLeaseWrapper(leaseToRenew);
457 		if (clw == null) {
458 		    // We don't know about this lease, create a new wrapper
459 		    try {
460 			clw = new ClientLeaseWrapper(
461 			    leaseToRenew, idGen.newID(), renewedList, set,
462 			    membershipDuration, renewDuration, now);
463 		    } catch (IOException e) {
464 			throw new IllegalArgumentException(
465 			    "NormServerBaseImpl.renewFor:Handed lease " +
466 			    "that can't be marshalled");
467 		    }
468 		} else {
469 		    // We know about this lease -- update its stats
470 		    clw.update(membershipDuration, renewDuration, now);
471 		}
472 
473 		set.update(clw);
474 		lrm.renewUntil(clw, clw.getMembershipExpiration(), 
475 			       clw.getRenewDuration(), lrmEventListener);
476 
477 		// $$$ What if this lease was just dropped by the LRM
478 		// (say because there was some problem renewing the
479 		// lease)?  Are we going to lose the lease because
480 		// we will shortly process a renewalFailureEvent?
481 		// Is it a problem if we do? Presumably re-adding it
482 		// is not going to fix the underlying problem...
483 	    }
484 	} finally {
485 	    store.releaseMutatorLock();
486 	}    
487     }
488 
489 
490     // Inherit java doc from super type
491     public Lease remove(Uuid id, Lease leaseToRemove)
492 	throws RemoteException, ThrowThis
493     {
494 	ready.check();
495 	final LeaseSet set = getSet(id);
496 
497 	if (leaseToRemove == null) {
498 	    throw new NullPointerException("LeaseRenewalSet.remove:Must " +
499 	        "pass a non-null lease");
500 	}
501 
502 	leaseToRemove = (Lease) leasePreparer.prepareProxy(leaseToRemove);
503 	logger.log(Level.FINE, "Removing lease {0}", leaseToRemove);
504 
505 	// The most up-to-date ref to the lease we have
506 	Lease rslt = null;
507 	try {
508 	    store.acquireMutatorLock();	    
509 	    synchronized (set) {
510 		ensureCurrent(set);
511 		
512 		final ClientLeaseWrapper clw 
513 		    = set.getClientLeaseWrapper(leaseToRemove);
514 
515 		if (clw == null) {
516 		    // Lease must have been removed already
517 		    return null;
518 		}
519 
520 		try {
521 		    lrm.remove(clw);
522 		} catch (UnknownLeaseException e) {
523 		    logger.log(Level.FINE,
524 			"Exception thrown {0} while trying to remove lease {1}",
525 			new Object[]{e, leaseToRemove}
526 		    );
527 		    // This can happen if there was some problem
528 		    // renewing the lease or its LRM expiration just
529 		    // ran out. Since we are removing the lease anyway
530 		    // ignore. 
531 		}
532 
533 		final boolean present = set.remove(clw);	
534 
535 		// Only return a non-null result if the removed lease
536 		// had not be removed already
537 		if (present) {
538 		    // At this point we can assume clw is no
539 		    // longer deformed
540 		    rslt = clw.getClientLease();
541 		}
542 	    }
543 	} finally {
544 	    store.releaseMutatorLock();
545 	}    
546 
547 	if (rslt == null) 
548 	    return null;
549 
550 	// Whenever we serialize a lease we have to make sure that
551 	// its serial form will stay the same during the
552 	// serialization.  Since we have removed it from the set we
553 	// don't have to worry about this lease being serialized to disk
554 	// any more so changing the serial format here should be safe.
555 	rslt.setSerialFormat(Lease.DURATION);
556 	return rslt;
557     }
558 
559     // Inherit java doc from super type
560     public GetLeasesResult getLeases(Uuid id) throws ThrowThis {
561 	ready.check();
562 	final LeaseSet set = getSet(id);
563 
564 	// We are not modifying the set so we don't need the mutator lock
565 	// $$$ Do we need a reader lock, or is the lock on the set enough?
566 	// $$$ Need to make sure we really don't mutate any persistent state
567 	// or have issues with serializing the leases.
568 	synchronized (set) {
569 	    ensureCurrent(set);
570 	    return new GetLeasesResult(set.getLeases());
571 	}
572     }
573 
574 
575     // Inherit java doc from super type
576     public EventRegistration setExpirationWarningListener(
577 				 Uuid		     id,
578 				 RemoteEventListener listener, 
579 				 long		     minWarning, 
580 				 MarshalledObject    handback)
581 	throws RemoteException, ThrowThis
582     {
583 	ready.check();
584 	final LeaseSet set = getSet(id);
585 
586 	if (listener == null) {
587 	    minWarning = NO_LISTENER;
588 	    handback = null;
589 	} else if (minWarning < 0) {
590 	    throw new IllegalArgumentException(
591 	        "LeaseRenewalSet.setExpirationWarningListener:minWarning " +
592 		"must be positive");
593 	} else {
594 	    listener = (RemoteEventListener) listenerPreparer.prepareProxy(
595 		listener);
596 	}
597 
598 	try {
599 	    store.acquireMutatorLock();	    
600 	    synchronized (set) {
601 		ensureCurrent(set);
602 
603 		try {
604 		    final boolean haveBefore = set.haveWarningRegistration();
605 		    final EventRegistration rslt =
606 			set.setExpirationWarningListener(
607 			    listener, minWarning, handback);
608 		    final boolean haveAfter = set.haveWarningRegistration();
609 
610 		    if (haveAfter || (haveBefore != haveAfter)) {
611 			// Either we had a registration before and we
612 			// don't now, we do now and not before, or we
613 			// had one before and we still do.  In the
614 			// first two cases we have to wack the
615 			// expiration manager so it can schedule the
616 			// right task.  In the last case wack the
617 			// expiration manager so it can reschedule in case
618 			// minWarning has changed.
619 			expMgr.reschedule(set);
620 		    }
621 
622 		    return rslt;
623 		} catch (IOException e) {
624 		    // This means the listener could not be serialized,
625 		    // re-throw as an IllegalArgumentException 
626 		    throw new IllegalArgumentException("Passed a listener " +
627 			      "that could not be serialized");
628 		}
629 	    }
630 	} finally {
631 	    store.releaseMutatorLock();
632 	}    
633     }
634 
635     /**
636      * Remote a set if its expiration time has been reached.
637      */
638     void expireIfTime(LeaseSet set) {
639 	try {
640 	    store.acquireMutatorLock();
641 
642 	    synchronized (set) {
643 		if (isCurrent(set)) {
644 		    // Someone must have renewed the lease...don't expire
645 		    return;
646 		}
647 
648 		removeSet(set);
649 	    }
650 	} finally {
651 	    store.releaseMutatorLock();
652 	}
653     }
654 
655     /**
656      * Schedule the sending of an expiration warning event.
657      * This could be a method on the set itself but this keeps all
658      * of the high level synchronization logic in one file.
659      */
660     void sendWarningEvent(LeaseSet set) {
661 	// We don't need to acquire the store lock because we 
662 	// won't be mutating any persistent state.
663 	// We will be reading state of the set so we do need to
664 	// sync on it to ensure we get a constant view
665 	synchronized (set) {
666 	    if (!isCurrent(set)) {
667 		// Must have just been canceled or expired, don't send event
668 		return;
669 	    }
670 
671 	    set.sendWarningEvent();
672 	}
673     }
674 
675     // Inherit java doc from super type
676     public EventRegistration setRenewalFailureListener(
677 				 Uuid		     id,
678 				 RemoteEventListener listener, 
679 				 MarshalledObject    handback)
680 	throws RemoteException, ThrowThis
681     {
682 	ready.check();
683 	final LeaseSet set = getSet(id);
684 
685 	if (listener == null) {
686 	    handback = null;
687 	} else {
688 	    listener = (RemoteEventListener) listenerPreparer.prepareProxy(
689 		listener);
690 	}
691 
692 	try {
693 	    store.acquireMutatorLock();	    
694 	    synchronized (set) {
695 		ensureCurrent(set);
696 
697 		try {
698 		    return set.setRenewalFailureListener(
699 			listener, handback);
700 		} catch (IOException e) {
701 		    // This means the listener could not be serialized,
702 		    // re-throw as an IllegalArgumentException 
703 		    throw new IllegalArgumentException("Passed a listener " +
704 			      "that could not be serialized");
705 		}
706 	    }
707 	} finally {
708 	    store.releaseMutatorLock();
709 	}    
710     }
711 
712     /**
713      * Handle failures to renew a lease by removing the lease from its set
714      * and if needed schedule sending an event.
715      * @param clw the wrapped client lease for the lease that could not
716      *            be renewed.  <code>clw.isDeformed</code> must be
717      *            <code>false</code>.
718      */
719     void renewalFailure(ClientLeaseWrapper clw) {
720 	final LeaseSet set = clw.getLeaseSet();
721 	if (set == null) {
722 	    // set must have just been removed, no state to update, no
723 	    // events to send, just return
724 	    return;
725 	}
726 
727 	try {
728 	    store.acquireMutatorLock();	    
729 
730 	    synchronized (set) {
731 		logger.log(Level.INFO, "Lease renewal failed for {0}", clw);
732 
733 		if (!isCurrent(set)) {
734 		    // expired, no state to update, no
735 		    // events to send, just return
736 		    return;
737 		}
738 
739 		set.renewalFailure(clw);
740 
741 		// Remove from LRM.  Should already be
742                 // gone from LRM, but it might have been added back in
743                 // between the time the renewal failure occurred and
744                 // when we got around to processing it (that is now).
745                 // Making sure it is gone will keep things
746                 // consistent.
747 		try {
748 		    lrm.remove(clw);
749 		} catch (UnknownLeaseException e) {
750 		    // As long as the lease is gone we don't care
751 		}
752 	    }
753 	} finally {
754 	    store.releaseMutatorLock();
755 	}    	
756     }
757 
758 
759     /**
760      * Remove a lease that has reached its desired expiration.
761      * @param clw the wrapped client lease for the lease that we are done with
762      */
763     void desiredExpirationReached(ClientLeaseWrapper clw) {
764 	final LeaseSet set = clw.getLeaseSet();
765 	if (set == null) {
766 	    // set must have just been removed, no state to update, no
767 	    // events to send, just return
768 	    return;
769 	}
770 
771 	try {
772 	    store.acquireMutatorLock();	    
773 
774 	    synchronized (set) {
775 		if (!isCurrent(set)) {
776 		    // expired, no state to update, no events to send,
777 		    // just return
778 		    return;
779 		}
780 
781 		// Make sure the lease is still in the set it thinks
782 		// it is
783 		if (!set.doesContainWrapper(clw)) {
784 		    // Must have been removed somewhere else
785 		    // forget about this event
786 		    return;
787 		}
788 		    
789 		// The client could have re-added the lease to the set
790 		// after the event occured but before we processed the
791 		// event.  Check to make sure it really should be
792 		// removed.
793 		final long desiredExpiration = clw.getMembershipExpiration();
794 		if (desiredExpiration > System.currentTimeMillis()) {  
795 		    // Not dead yet...still in the rest of our tables,
796 		    // just need to make sure it is in the LRM
797 		    lrm.renewUntil(clw, clw.getMembershipExpiration(),
798 				   clw.getRenewDuration(), lrmEventListener);
799 		    return;
800 		}
801 
802 		// They could have re-added the lease but in way that
803 		// it should generate a renewal failure event. Note if
804 		// we are here the lease must be pass its desired
805 		// expiration
806 		if (clw.getExpiration() < desiredExpiration) {
807 		    // This time just return, the LRM will generate a
808 		    // renewal failure event, or may have
809 		    // already. Either way we don't need to add the
810 		    // lease back and failure event will remove it
811 		    // from the other tables
812 		    return;
813 		}
814 
815 		// If we are here the lease should be removed
816 		logger.log(Level.FINE,
817 			   "Reached desired expiration for lease {0}",
818 			   clw);
819 
820 		set.remove(clw);
821 
822 		// Remove from LRM. Should already be
823                 // gone from LRM, but it might have been added back in
824                 // between the time the renewal failure occurred and
825                 // when we got around to processing it (that is now).
826                 // Make sure it is gone will keep things
827                 // consistent.
828 		try {
829 		    lrm.remove(clw);
830 		} catch (UnknownLeaseException e) {
831 		    // As long as the lease is gone we don't care
832 		}
833 	    }
834 	} finally {
835 	    store.releaseMutatorLock();
836 	}    	
837     }
838 
839     /**
840      * The implementation of <code>SendMonitor</code> we use to track 
841      * event delivery threads.  Each set gets its own object.
842      */
843     private class SendMonitorImpl implements SendMonitor {
844 	/** Set this is the monitor for */
845 	final private LeaseSet set;
846 
847 	/**
848 	 * Simple constructor.
849 	 * @param set the set this monitor is associated with
850 	 */
851 	private SendMonitorImpl(LeaseSet set) {
852 	    this.set = set;
853 	}
854 
855 	// Methods needed to meet contract of SendMonitor
856 	
857 	// Inherit java doc from super type
858 	public void definiteException(EventType           type,
859 				      RemoteEvent         ev,
860 				      long                registrationNumber,
861 				      Throwable           t)
862 	{
863 	    // This may be more locking than we need (especially
864 	    // locking on the set) since EventType objects already
865 	    // perform a lot of locking, but this discipline is
866 	    // consistent with the rest of Norm and is therefore less
867 	    // likely to lead to bugs down the road.
868 	    try {
869 		store.acquireMutatorLock();	    
870 
871 		if (!NormServerBaseImpl.isCurrent(set)) {
872 		    // Set is dead, don't bother updating its state
873 		    return;
874 		}
875 
876 		synchronized (set) {
877 		    set.definiteException(type, ev, registrationNumber);
878 		}
879 	    } finally {
880 		store.releaseMutatorLock();
881 	    }    
882 	}
883 	
884 	// Inherit java doc from super type
885 	public boolean isCurrent() {
886 	    synchronized (set) {
887 		return NormServerBaseImpl.isCurrent(set);
888 	    }
889 	}
890     }
891 
892 
893     /**
894      * Method used to remove membership expired leases from the server.
895      * Assumes that they have already been removed from the set.
896      * @param deadLeases an iterator with the leases that have to be 
897      *                   removed
898      */
899     private void removeClientLeases(Iterator deadLeases) {
900 	while(deadLeases.hasNext()) {
901 	    ClientLeaseWrapper clw = (ClientLeaseWrapper) deadLeases.next();
902 
903 	    // Remove from lrm
904 	    try {
905 		lrm.remove(clw);
906 	    } catch (UnknownLeaseException e) {
907 		// This can happen if there was some problem renewing
908 		// the lease, or its LRM expiration just ran out.
909 		// Since we are remove the lease any way ignore.
910 	    }
911 	}
912     }
913 
914     /**
915      * Throw a NoSuchObjectException, wrapped in a ThrowThis, if the
916      * passed set has expired. Assumes set is locked.
917      */
918     private static void ensureCurrent(LeaseSet set) throws ThrowThis {
919 	if (!isCurrent(set)) {
920 	    throw new ThrowThis(new NoSuchObjectException("Set has expired"));
921 	}
922     }
923 
924     /**
925      * Returns true if the lease on the lease set is still current, else
926      * false.
927      */
928     private static boolean isCurrent(LeaseSet set) {
929 	return set.getExpiration() > System.currentTimeMillis();
930     }
931 
932     /** 
933      * Return the set with the specified id, or throw a
934      * NoSuchObjectException, wrapped in a ThrowThis if the set can't be found
935      */
936     private LeaseSet getSet(Uuid id) throws ThrowThis {
937 	final LeaseSet rslt = (LeaseSet) setTable.get(id);
938 	if (rslt == null) 
939 	    throw new ThrowThis(new NoSuchObjectException("Can't find set"));
940 	return rslt;
941     }
942 
943 
944     /////////////////////////////////////////
945     // Methods defined in LeaseRenewalService
946 
947     // Inherit java doc from super type
948     public LeaseRenewalSet createLeaseRenewalSet(long leaseDuration) {
949 	ready.check();
950 	final Uuid newID      = UuidFactory.generate();
951 	final LeaseSet newSet = new LeaseSet(newID, generator, store, this, context);
952 	
953 	LeasePeriodPolicy.Result leasePeriod;
954 	try {
955 	    leasePeriod = setLeasePolicy.grant(newSet, leaseDuration);
956 	} catch (LeaseDeniedException e) {
957 	    // This will never happen because we never use a policy that
958 	    // denies granting (or renewing) a lease; complain bitterly
959 	    // and throw a runtime exception
960 	    logger.log(Level.WARNING,
961 		       "Got LeaseDeniedException creating lease -- " +
962 		       "this should not happen!",
963 		       e);
964 	    throw new InternalNormException("Error creating lease", e);
965 	}
966 
967 	Lease newLease = leaseFactory.newLease(newID, leasePeriod.expiration);
968 	newSet.setExpiration(leasePeriod.expiration);
969 	try {
970 	    store.acquireMutatorLock();
971 	    final Object u = new CreateLeaseSet(newSet);
972 	    store.update(u);
973 	    expMgr.register(newSet);
974 	    setTable.put(newID, newSet);
975 	} finally {
976 	    store.releaseMutatorLock();
977 	}
978 
979 	LeaseRenewalSet result = SetProxy.create(serverProxy, newID, newLease);
980 	logger.log(Level.FINE, "Created lease renewal set {0}", result);
981 	return result;
982     }
983 
984     //////////////////////////////
985     // Callbacks used by LeaseSet
986 
987     /**
988      * Method used by <code>LeaseSet</code> when it needs to cons up
989      * a SetProxy with an up-to-date Lease.  Assumes the appropriate 
990      * locks have been obtained.
991      */
992     SetProxy newSetProxy(LeaseSet set) {
993 	Lease l = leaseFactory.newLease(set.getUuid(), set.getExpiration());
994 	return SetProxy.create(serverProxy, set.getUuid(), l);
995     }
996 
997     /**
998      *  Create a new <code>SendMonitorImpl</code>
999      */
1000     // $$$ This feels a bit questionable, one of the places where this
1001     // is called back is in LeaseSet's constructor! So the constructor is
1002     // letting a reference to the constructed object escape before the object
1003     // is done.  There are a couple alternatives but I am not sure they are
1004     // better, the best one is probably to defer creation of the set's 
1005     // EventType object until first use and grab the necessary SendMonitor
1006     // then, but then we will need all sorts of null guards...
1007     SendMonitor newSendMonitor(LeaseSet set) {
1008 	return new SendMonitorImpl(set);
1009     }
1010 
1011     //////////////////////////////////////////
1012     // Implement ServerProxyTrust
1013     /**
1014      * @throws UnsupportedOperationException if the server proxy does not
1015      *	       implement both {@link RemoteMethodControl} and {@link
1016      *	       TrustEquivalence}
1017      */
1018     public TrustVerifier getProxyVerifier() {
1019 	return new ProxyVerifier(serverProxy, serverUuid);
1020     }
1021 
1022     //////////////////////////////////////////
1023     // Thread to persist client lease renewals
1024     static class RenewLogThread extends InterruptedStatusThread {
1025         private PersistentStore store;
1026         private final List renewedList;
1027 	/** Don't create a daemon thread incase the jvm exits during store */
1028 	RenewLogThread(List renewedList) {
1029 	    super("log renewals thread");
1030 	    setDaemon(false);
1031             this.renewedList = renewedList;
1032             
1033 	}
1034         
1035         private void setStore(PersistentStore store){
1036             synchronized (this){
1037                 if (this.store == null) this.store = store;
1038             }
1039         }
1040 
1041 	public void run() {
1042 	    while (!hasBeenInterrupted()) {
1043 		try {
1044 		    ClientLeaseWrapper clw;
1045                     PersistentStore store;
1046                     synchronized (this){
1047                         store = this.store;
1048                     }
1049 		    synchronized (renewedList) {
1050 			// If there is an item on the list pull it off for
1051 			// processing, otherwise wait and try again
1052 			if (renewedList.isEmpty()) {
1053 			    try {
1054 				renewedList.wait();
1055 				continue; // go back to top of loop
1056 			    } catch (InterruptedException e) {
1057 				// someone wants us dead
1058                                 // Reset the interrupt
1059                                 Thread.currentThread().interrupt();
1060 				return;
1061 			    }
1062 			} else {
1063 			    clw = (ClientLeaseWrapper) renewedList.remove(0);
1064 			}			
1065 		    }
1066 
1067 		    if (logger.isLoggable(Level.FINER)) {
1068 			logger.log(Level.FINER,
1069 				   "Attempting to renew lease {0} at {1}",
1070 				   new Object[] {
1071 				       clw, 
1072 				       Long.valueOf(System.currentTimeMillis()) });
1073 		    }
1074 
1075 		    // A lease was renewed, log the new state
1076 		    final LeaseSet set = clw.getLeaseSet();
1077 		    if (set == null) {
1078 			// set must have just been removed, no state
1079 			// to update, go to next item in list
1080 			continue;
1081 		    }
1082 		    
1083 		    try {	
1084 			store.acquireMutatorLock();  
1085 			synchronized (set) {
1086 			    if (!isCurrent(set)) {
1087 				// expired, no state to update, go to next item
1088 				// in list
1089 				continue;
1090 			    }
1091 			    
1092 			    clw.clearRenewed();
1093 			    // Small window here where the lease can
1094 			    // be renewed, it's renewed flag is re-set
1095 			    // and because it was cleared the clw ends
1096 			    // up on renewed list, we then come back
1097 			    // to this thread and log the new
1098 			    // state, and come back later and log that
1099 			    // state again.  Since this is just
1100 			    // slightly wasteful, not incorrect, this
1101 			    // is ok. [Reversing these two lines of
1102 			    // course would be wrong...]
1103 			    set.logRenewal(clw);	
1104 			}
1105 		    } finally {
1106 			store.releaseMutatorLock();
1107 		    }
1108                     try {
1109                         // Give other threads a chance to run
1110                         Thread.sleep(100L);
1111                         // Thread.yield();
1112                     } catch (InterruptedException ex) {
1113                         // Reset the interrupt status.
1114                         Thread.currentThread().interrupt();
1115                     }
1116 		} catch (RuntimeException e) {
1117 		    logger.log(
1118 			Level.INFO,
1119 			"Runtime exception in RenewLogThread -- restarting",
1120 			e);
1121 		}
1122 	    }
1123 	}
1124     }
1125 
1126 
1127     //////////////////////////////
1128     // Methods defined in Landlord
1129 
1130     // Inherit java doc from super type
1131     public long renew(Uuid cookie, long extension) 
1132 	throws UnknownLeaseException, LeaseDeniedException
1133     {
1134 	ready.check();
1135 	final LeaseSet set = (LeaseSet) setTable.get(cookie);
1136 	if (set == null) 
1137 	    throw new UnknownLeaseException("No lease for cookie:" + cookie);
1138 	try {
1139 	    store.acquireMutatorLock();	    
1140 
1141 	    synchronized (set) {
1142 		if (!isCurrent(set)) {
1143 		    // Lease has expired, don't renew
1144 		    throw new UnknownLeaseException(
1145 			"Lease has already expired");
1146 		}
1147 		// No one can expire the lease, so it is safe to update.
1148 		// $$$ Might be better to make an extra call to currentTime
1149 		// and calculate the new duration just be for returning
1150 		LeasePeriodPolicy.Result leasePeriod =
1151 		    setLeasePolicy.renew(set, extension);
1152 
1153 		// Log update 
1154 		final Object u = new LeaseSet.ChangeSetExpiration(
1155 		    set, leasePeriod.expiration);
1156 		store.update(u);
1157 
1158 		set.setExpiration(leasePeriod.expiration);
1159 		expMgr.reschedule(set);
1160 		return leasePeriod.duration;
1161 	    }
1162 	} finally {
1163 	    store.releaseMutatorLock();
1164 	}
1165     }
1166 
1167     // Inherit java doc from super type
1168     public void cancel(Uuid cookie) throws UnknownLeaseException {
1169 	ready.check();
1170 	final LeaseSet set = (LeaseSet) setTable.get(cookie);
1171 	if (set == null) 
1172 	    throw new UnknownLeaseException("No lease for cookie:" + cookie);
1173 
1174 	try {
1175 	    store.acquireMutatorLock();
1176 	    
1177 	    synchronized (set) {
1178 		if (!isCurrent(set)) {
1179 		    //Someone else beat us to it, just return
1180 		    return;
1181 		}
1182 
1183 		removeSet(set);
1184 	    }
1185 	} finally {
1186 	    store.releaseMutatorLock();
1187 	}
1188     }
1189 
1190     /**
1191      * Do the heavy lifting on removing a set, assumes the locks on the
1192      * set and store have been acquired.
1193      */
1194     private void removeSet(LeaseSet set) {
1195 	// handle the possibility of a race calling removeSet twice for a set
1196 	if (setTable.remove(set.getUuid()) != null) {
1197 	    // set.destroy will persist the removal of the set, and 
1198 	    // change the set's expiration to -1 which will cause any
1199 	    // other operations on the set to throw NoSuchObjectException
1200 	    // or UnknownLeaseException (they will all lock on the set
1201 	    // and ensure current before doing anything substantive)
1202 	    final Set leases = set.destroy(); 
1203 	    removeClientLeases(leases.iterator());
1204 	}
1205     }
1206 
1207     // Inherit java doc from super type
1208     public RenewResults renewAll(Uuid[] cookies, long[] extensions) {
1209 	ready.check();
1210 	/* Cookie types checked in individual renew calls */
1211 	return LandlordUtil.renewAll(this, cookies, extensions);
1212     }
1213 
1214     // Inherit java doc from super type
1215     public Map cancelAll(Uuid[] cookies) {
1216 	ready.check();
1217 	/* Cookie types checked in individual cancel calls */
1218 	return LandlordUtil.cancelAll(this, cookies);	    
1219     }
1220 
1221     ////////////////////////////////////////////////
1222     // Methods and classes needed by PersistentStore
1223 
1224     /**
1225      * Called by <code>PersistentStore</code> after every update to give
1226      * server a chance to trigger a snapshot.
1227      * @param updateCount number of updates since last snapshot
1228      */
1229     void updatePerformed(int updateCount) {
1230 	// First check to see if the size of the log is larger than a 
1231 	// minimum threshold, this keeps snapshots from happening when 
1232 	// the state of the server is very small (like initially when
1233 	// the snapshot size is zero)
1234 	if (updateCount >= logToSnapshotThresh) {
1235 	    // Compare the size of log to what next snapshot would be
1236 	    final int snapshotSize = setTable.size() + countLeases.getCount();
1237 	    if ((float) updateCount >= snapshotWt*((float) snapshotSize)) {
1238 		// Both conditions meet, trigger snapshot
1239 		snapshotter.takeSnapshot();
1240 	    }
1241 	}
1242     }
1243 
1244     /**
1245      * Perform the 3rd stage of log recovery, restoring the various pieces of
1246      * transient state (populating the LRM, restoring various transient
1247      * fields).
1248      */
1249     void restoreTransientState() {
1250 	final long now = System.currentTimeMillis();
1251 
1252 	for (Iterator i = setTable.values().iterator(); i.hasNext(); ) {
1253 
1254 	    final LeaseSet set = (LeaseSet) i.next();
1255 
1256 	    // Has this set expired?
1257 	    if (now > set.getExpiration()) {
1258 		// yes it has, remove it and move on
1259 		i.remove();
1260 		// This is all we have to do because none of the other
1261 		// bookkeeping has been done yet for this set
1262 		continue;
1263 	    }
1264 
1265 	    final Iterator leases = set.restoreTransientState(generator, store, this, recoveredListenerPreparer, context);
1266 	
1267 	    // Go through all the leases in the set and add them to 
1268 	    // the right tables.
1269 	    while (leases.hasNext()) {
1270 		// We sync here so all the updates will be flushed to
1271 		// memory before the the LRM's threads start working on
1272 		// this lease
1273 		ClientLeaseWrapper clw;
1274 		synchronized (this) {
1275 		    clw = (ClientLeaseWrapper) leases.next();
1276 
1277 		    // Let the clw recover its state	    
1278 		    clw.recoverTransient(
1279 			renewedList, idGen, set, recoveredLeasePreparer);
1280 		}
1281 
1282 		// Note: there is no race condition here because
1283 		// lrmEventListener and renewedList will buffer any
1284 		// renewals/renewal failure until the rest of the server
1285 		// is ready to handle them
1286 
1287 		// Note: We may be adding client leases who's
1288 		// desired expirations were before `now'. However, this is
1289 		// ok as the LRM will generate either a renewal failure
1290 		// (if the actual expiration of the lease is before
1291 		// the desired expiration) or a desired expiration 
1292 		// reached event (if the actual expiration of the lease
1293 		// equal to or after the desired expiration), which in
1294 		// turn will cause the "right things" to happen. Also
1295                 // all the other methods are smart enough not be confused by 
1296 		// leases that have reached their desired expiration
1297 		// but have not yet been removed.
1298 
1299 		final Throwable lt = clw.getLastFailure();
1300 
1301 		if ((lt == null) || 
1302 		    (ThrowableConstants.retryable(lt) == 
1303 		     ThrowableConstants.INDEFINITE)) 
1304 		{
1305 		    lrm.renewUntil(clw, clw.getMembershipExpiration(), 
1306 				   clw.getRenewDuration(), lrmEventListener);
1307 		} else {
1308 		    // This lease has already suffered a definite failure,
1309 		    // don't let the LRM renew it.  Note we will only get
1310 		    // here if we logged a renewal failure, but could not
1311 		    // log the LeaseRenewalEvent from the LRM
1312 		    lrmEventListener.notify(new LeaseRenewalEvent(lrm,
1313 			clw, clw.getMembershipExpiration(), lt));
1314 		}
1315 	    }
1316 	}
1317     }
1318 
1319     /**
1320      * Implementation of <code>LogHandler</code> used by NormServerBaseImpl
1321      */
1322     private class OurLogHandler extends LogHandler {
1323 	// Inherit java doc from super type
1324 	// Snapshot format
1325 	//     Version
1326 	//     Server unique ID
1327 	//     Generator we use to create EventType objects
1328 	//     The number of LeaseSet objects in the setTable
1329 	//     All of the LeaseSet objects
1330 	public void snapshot(OutputStream out) throws Exception {
1331 	    final ObjectOutputStream oostream = new ObjectOutputStream(out);
1332 
1333 	    oostream.writeInt(CURRENT_LOG_VERSION);
1334 	    oostream.writeObject(serverUuid);
1335 	    oostream.writeObject(generator);
1336 	    oostream.writeInt(setTable.size());
1337 	    final Collection sets = setTable.values();
1338 	    for (Iterator i = sets.iterator(); i.hasNext();) {
1339 		final LeaseSet set = (LeaseSet) i.next();
1340 
1341 		// Grab lock on set so any concurrent getLeases() calls
1342 		// will not be corrupted
1343 		synchronized (set) { 
1344 		    // Note, we used to drop desired expiration reached
1345 		    // client leases here, but that was a bug.  In particular
1346 		    // there could have been a renewal failure event 
1347 		    // that we have not yet been notified of 		    
1348 		    oostream.writeObject(set);
1349 		}
1350 	    }
1351 	    oostream.flush();
1352 
1353 	    // $$$ We are missing an optimization here: as we write each
1354 	    // set we could check to see if any of the client leases
1355 	    // in that set have been renewed, but not logged, and clear
1356 	    // them from the renewedList (this assumes that the
1357 	    // renewal changes the persisted state of the wrapper, not
1358 	    // the processing by logRenewal()).  This is not a bug
1359 	    // since any clw we pull off the renewedList and persist
1360 	    // after this point will have state at least as up-to-date
1361 	    // as this snapshot.
1362 	}
1363 
1364 	// Inherit java doc from super type
1365 	public void recover(InputStream in) throws Exception {
1366 	    final ObjectInputStream oistream = new ObjectInputStream(in);
1367 
1368 	    int version;
1369 	    version = oistream.readInt();
1370 
1371 	    if (version != CURRENT_LOG_VERSION) {
1372 		    throw new CorruptedStoreException("Incompatible version " +
1373 		        "ID in log, looking for " + CURRENT_LOG_VERSION +
1374 			", got " + version);
1375 
1376 	    }
1377 
1378 	    serverUuid = (Uuid) oistream.readObject();
1379 	    generator = (EventTypeGenerator) oistream.readObject();
1380 	    final int size = oistream.readInt();
1381 	    setTable.clear();
1382 	    for (int i = 0; i < size; i++) {
1383 		final LeaseSet set = (LeaseSet) oistream.readObject();
1384 		setTable.put(set.getUuid(), set);
1385 	    }
1386 	}
1387 
1388 	// Inherit java doc from super type
1389 	public void applyUpdate(Object update) throws Exception {
1390 	    final LoggedOperation op = (LoggedOperation) update;
1391 	    op.apply(setTable);
1392 	}
1393     }
1394 
1395     /**
1396      * Return a string summarizing the inventory of the server 
1397      */
1398     private String inventory() {
1399 	return countLeases.getCount() + " client leases, " + 
1400 	    setTable.size() + " sets.";
1401     }
1402 
1403     /**
1404      * Thread that performs the actual snapshots, done in a separate thread
1405      * so it will not hang up in-progress remote calls
1406      */
1407     class SnapshotThread extends InterruptedStatusThread {
1408         private final PersistentStore store;
1409 	/** Don't create a daemon thread in case the jvm exits during snapshot */
1410 	SnapshotThread(PersistentStore store) {
1411 	    super("snapshot thread");
1412 	    setDaemon(false);
1413             this.store = store;
1414 	}
1415 
1416 	/** Signal this thread that is should take a snapshot */
1417 	private synchronized void takeSnapshot() {
1418 	    notifyAll();
1419 	}
1420 
1421 	public void run() {
1422 	    while (!hasBeenInterrupted()) {
1423 		synchronized (this) {
1424 		    try {
1425 			wait();
1426 		    } catch (InterruptedException e) {
1427 			return;
1428 		    }
1429 		}
1430 
1431 		try {
1432 		    if (logger.isLoggable(Level.FINER)) {
1433 			logger.log(
1434 			    Level.FINER, "Taking snapshot: {0}", inventory());
1435 		    }
1436 
1437 		    store.snapshot();
1438 
1439 		} catch (InterruptedIOException e) {
1440 		    // Some one wants us dead
1441 		    return;
1442 		} catch (Exception e) {
1443 		    if (e instanceof LogException &&
1444 			((LogException) e).detail instanceof
1445 			InterruptedIOException)
1446 		    {
1447 			return;
1448 		    }
1449 		    /* $$$
1450 		     * if taking the snapshot fails for any reason,
1451 		     * then one of the following must be done:
1452 		     *   -- output the problem to a file and exit
1453 		     *   -- output the problem to a file and continue
1454 		     *   -- set an "I have a problem" attribute and
1455 		     *      then send a notification
1456 		     * this issue will be addressed at a later time
1457 		     */
1458 		    logger.log(Level.WARNING, "Snapshot failed", e);
1459 		}
1460 	    }
1461 	}
1462     }
1463 
1464     ///////////////////////////////////
1465     // Methods defined in Administrable
1466 
1467     // Inherit java doc from super type
1468     public Object getAdmin() {
1469 	ready.check();
1470 	return adminProxy;
1471     }
1472 
1473     ///////////////////////////////
1474     // Methods defined in JoinAdmin
1475 
1476     // Inherit java doc from super type
1477     public Entry[] getLookupAttributes() {
1478 	ready.check();
1479 	return joinState.getAttributes();
1480     }
1481 
1482     // Inherit java doc from super type
1483     public void addLookupAttributes(Entry[] attrSets) {
1484 	ready.check();
1485 	joinState.addAttributes(attrSets, true);
1486 	logger.log(Level.CONFIG, "Added attributes");
1487     }
1488 
1489     // Inherit java doc from super type
1490     public void modifyLookupAttributes(Entry[] attrSetTemplates, 
1491 				       Entry[] attrSets) 
1492     {
1493 	ready.check();
1494 	joinState.modifyAttributes(attrSetTemplates, attrSets, true);
1495 	logger.log(Level.CONFIG, "Modified attributes");
1496     }
1497   
1498     // Inherit java doc from super type
1499     public String[] getLookupGroups() {
1500 	ready.check();
1501 	return joinState.getGroups();
1502     }
1503 
1504     // Inherit java doc from super type
1505     public void addLookupGroups(String[] groups) {
1506 	ready.check();
1507 	joinState.addGroups(groups);
1508 	if (logger.isLoggable(Level.CONFIG)) {
1509 	    logger.log(Level.CONFIG, "Added lookup groups: {0}",
1510 		       toString(groups));
1511 	}
1512     }
1513 
1514     // Inherit java doc from super type
1515     public void removeLookupGroups(String[] groups) {
1516 	ready.check();
1517 	joinState.removeGroups(groups);
1518 	if (logger.isLoggable(Level.CONFIG)) {
1519 	    logger.log(Level.CONFIG, "Removed lookup groups: {0}",
1520 		       toString(groups));
1521 	}
1522     }
1523 
1524     // Inherit java doc from super type
1525     public void setLookupGroups(String[] groups) {
1526 	ready.check();
1527 	joinState.setGroups(groups);
1528 	if (logger.isLoggable(Level.CONFIG)) {
1529 	    logger.log(Level.CONFIG, "Set lookup groups: {0}",
1530 		       toString(groups));
1531 	}
1532     }
1533 
1534     // Inherit java doc from super type
1535     public LookupLocator[] getLookupLocators() {
1536 	ready.check();
1537 	return joinState.getLocators();
1538     }
1539 
1540     // Inherit java doc from super type
1541     public void addLookupLocators(LookupLocator[] locators)
1542 	throws RemoteException
1543     {
1544 	ready.check();
1545 	for (int i = locators.length; --i >= 0; ) {
1546 	    locators[i] = (LookupLocator) locatorPreparer.prepareProxy(
1547 		locators[i]);
1548 	}	    
1549 	joinState.addLocators(locators);
1550 	if (logger.isLoggable(Level.CONFIG)) {
1551 	    logger.log(Level.CONFIG, "Added lookup locators: {0}",
1552 		       toString(locators));
1553 	}
1554     }
1555 
1556     // Inherit java doc from super type
1557     public void removeLookupLocators(LookupLocator[] locators)
1558 	throws RemoteException
1559     {
1560 	ready.check();
1561 	for (int i = locators.length; --i >= 0; ) {
1562 	    locators[i] = (LookupLocator) locatorPreparer.prepareProxy(
1563 		locators[i]);
1564 	}	    
1565 	joinState.removeLocators(locators);
1566 	if (logger.isLoggable(Level.CONFIG)) {
1567 	    logger.log(Level.CONFIG, "Removed lookup locators: {0}",
1568 		       toString(locators));
1569 	}
1570     }
1571 
1572     // Inherit java doc from super type
1573     public void setLookupLocators(LookupLocator[] locators)
1574 	throws RemoteException
1575     {
1576 	ready.check();
1577 	for (int i = locators.length; --i >= 0; ) {
1578 	    locators[i] = (LookupLocator) locatorPreparer.prepareProxy(
1579 		locators[i]);
1580 	}	    
1581 	joinState.setLocators(locators);
1582 	if (logger.isLoggable(Level.CONFIG)) {
1583 	    logger.log(Level.CONFIG, "Set lookup locators: {0}",
1584 		       toString(locators));
1585 	}
1586     }
1587 
1588     /** Returns the contents of an array as a string. */
1589     private static String toString(Object[] array) {
1590 	if (array == null) {
1591 	    return "null";
1592 	}
1593 	StringBuffer sb = new StringBuffer(String.valueOf(array[0]));
1594 	for (int i = 1; i < array.length; i++) {
1595 	    sb.append(", ").append(array[i]);
1596 	}
1597 	return sb.toString();
1598     }
1599 
1600     //////////////////////////////////
1601     // Methods defined in DestroyAdmin
1602 
1603     // Inherit java doc from super type
1604     public void destroy() throws RemoteException {
1605 	ready.shutdown();
1606 	logger.log(Level.INFO, "Destroying Norm service");
1607 
1608 	joinState.terminateJoin();
1609 	lrmEventListener.interrupt();
1610 	renewLogger.interrupt();
1611 	snapshotter.interrupt();
1612 	expMgr.terminate();
1613 	generator.terminate();
1614 	lrm.clear();
1615         lrm.close();
1616 
1617 	logger.log(Level.FINEST, "Independent threads interrupted");
1618 
1619 	new DestroyThread().start();
1620 	logger.log(Level.FINEST, "Destroy thread started");
1621     }
1622 
1623     /**
1624      * Unexport our stub appropriately.
1625      * @param force terminate in progress calls if necessary
1626      * @return true if unexport succeeds
1627      */
1628     boolean unexport(boolean force) throws NoSuchObjectException {
1629 	return exporter.unexport(force);
1630     }
1631 
1632     /**
1633      * Method subclasses can override to perform any necessary post
1634      * log destruction cleanup.
1635      */
1636     void postDestroy() {
1637     }
1638     
1639     /**
1640      * Termination thread code.  We do this in a separate thread to
1641      * avoid deadlock, because Activatable.inactive will block until
1642      * in-progress RMI calls are finished.
1643      */
1644     private class DestroyThread extends Thread {
1645         /** Maximum delay for unexport attempts */
1646         private static final long MAX_DELAY = 2 * 60 * 1000;
1647 
1648 	/** Create a non-daemon thread */
1649 	private DestroyThread() {
1650 	    super("DestroyThread");
1651 	    /* override inheritance from RMI daemon thread */
1652 	    setDaemon(false);
1653 	}
1654 
1655 	public void run() {
1656 	    logger.log(Level.FINEST, "DestroyThread running");
1657 	    
1658 	    /*
1659 	     * Work for up to MAX_DELAY to try to nicely
1660 	     * unexport our stub, but if that does not work just end
1661 	     */
1662 	    final long end_time = System.currentTimeMillis() + MAX_DELAY;
1663 	    boolean unexported = false;
1664 
1665 	    try {
1666 		while ((!unexported) &&
1667 		       (System.currentTimeMillis() < end_time))
1668                 {
1669 		    /* wait for any pending operations to complete */
1670 		    logger.log(Level.FINEST,
1671 			       "Calling unexport (force=false)...");
1672 
1673 		    unexported = unexport(false);
1674 
1675 		    logger.log(Level.FINEST, "...rslt = " + unexported);
1676 
1677 		    if (!unexported) {
1678 			// Thread.yield();
1679                         try {
1680                             Thread.sleep(100L);
1681                         } catch (InterruptedException e){
1682                             // Reset interrupt status
1683                             Thread.currentThread().interrupt();
1684                         }
1685 		    }
1686 		}
1687 	    } catch (NoSuchObjectException e) {
1688 		logger.log(Level.FINEST, "...rslt = NoSuchObjectException");
1689 
1690 		unexported = true; // This works too
1691 	    } catch (Throwable t) {
1692 		logger.log(Level.FINEST, "...rslt = ", t);
1693 	    }
1694 
1695 	    if (!unexported) {
1696 		/* Attempt to forcefully export the service */
1697 		try {
1698 		    logger.log(Level.FINEST, "Calling unexport (force=true)");
1699 
1700 		    unexport(true);
1701 		} catch (NoSuchObjectException e) {
1702 		    // This works too
1703 		}
1704 	    }
1705 
1706 	    // Try to join the independent threads before deleting the store
1707 	    try {
1708 		logger.log(Level.FINEST, "Joining independent threads");
1709 
1710 		lrmEventListener.join(MAX_DELAY);
1711 		renewLogger.join(MAX_DELAY);
1712 		snapshotter.join(MAX_DELAY);	       
1713 	    } catch (InterruptedException e) {
1714 		// Will not happen
1715 	    }
1716    
1717 	    try {
1718 		logger.log(Level.FINEST, "Destroying store");
1719 
1720 		store.destroy();
1721 	    } catch (Exception t) {
1722 		logger.log(Level.INFO,
1723 			   "While destroying persistent store -- " +
1724 			   "destroy continuing",
1725 			   t);
1726 	    }	    
1727 
1728 	    if (lifeCycle != null) {
1729 		/* Unregister the service implementation */
1730 		lifeCycle.unregister(this);
1731 	    }
1732 
1733 	    logger.log(Level.FINEST, "Calling postDestroy");
1734 
1735 	    postDestroy();
1736 
1737 	    if (loginContext != null) {
1738 		try {
1739 		    logger.log(Level.FINEST, "Logging out");
1740 		    loginContext.logout();
1741 		} catch (Exception e) {
1742 		    logger.log(
1743 			Level.INFO, "Exception while logging out", e);
1744 		}
1745 	    }
1746 
1747 	    logger.log(Level.FINEST, "Ending DestroyThread");
1748 	}
1749     }
1750 
1751     /* -- Implement ServiceProxyAccessor -- */
1752 
1753     /** {@inheritDoc} */
1754     public Object getServiceProxy() {
1755 	ready.check();
1756 	return normProxy;
1757     }
1758 
1759     /* -- Implement ProxyAccessor -- */
1760 
1761     /** {@inheritDoc} */
1762     public Object getProxy() {
1763 	/* Don't wait until ready to return the server proxy */
1764 	return serverProxy;
1765     }
1766 
1767     ////////////////////
1768     // Server setup code
1769 
1770     /** Returns a string representation of this object. */
1771     public String toString() {
1772 	String className = getClass().getName();
1773 	className = className.substring(className.lastIndexOf('.') + 1);
1774 	return className + "[" + serverUuid + "]";
1775     }
1776 
1777     /**
1778      * Simple container for an alternative return a value so we
1779      * can provide more detailed diagnostics.
1780      */
1781     static class InitException extends Exception {
1782 	private static final long serialVersionUID = 1;
1783 	private InitException(String message, Throwable nested) {
1784 	    super(message, nested);
1785 	}
1786     }
1787     
1788     /**
1789      * Portion of construction that is common between the activatable and not
1790      * activatable cases.  This method performs the minimum number of
1791      * operations before establishing the Subject, and logs errors.
1792      */
1793     static NormServerInitializer init(String[] configOptions, final NormServerInitializer init)
1794 	throws Exception
1795     {
1796 	try {
1797 	    final Configuration config = ConfigurationProvider.getInstance(
1798 		configOptions, NormServerBaseImpl.class.getClassLoader());
1799 	    init.loginContext = (LoginContext) config.getEntry(
1800 		NORM, "loginContext", LoginContext.class, null);
1801 	    if (init.loginContext == null) {
1802 		init.initAsSubject(config);
1803 	    } else {
1804 		init.loginContext.login();
1805 		try {
1806 		    Subject.doAsPrivileged(
1807 			init.loginContext.getSubject(),
1808 			new PrivilegedExceptionAction() {
1809 			    public Object run() throws Exception {
1810 				init.initAsSubject(config);
1811 				return null;
1812 			    }
1813 			},
1814 			null);
1815 		} catch (PrivilegedActionException e) {
1816 		    throw e.getCause();
1817 		}
1818 	    }
1819 	} catch (Throwable e) {
1820 	    initFailed(e);
1821 	}
1822         return init;
1823     }
1824 
1825     /**
1826      * Log information about failing to initialize the service and rethrow the
1827      * appropriate exception.
1828      *
1829      * @param e the exception produced by the failure
1830      */
1831     static void initFailed(Throwable e) throws Exception {
1832 	String message = null;
1833 	if (e instanceof InitException) {
1834 	    message = e.getMessage();
1835 	    e = e.getCause();
1836 	}
1837 	if (logger.isLoggable(Level.SEVERE)) {
1838 	    if (message != null) {
1839 		logThrow(Level.SEVERE, "initFailed",
1840 			 "Unable to start Norm service: {0}",
1841 			 new Object[] { message }, e);
1842 	    } else {
1843 		logger.log(Level.SEVERE, "Unable to start Norm service", e);
1844 	    }
1845 	}
1846 	if (e instanceof Exception) {
1847 	    throw (Exception) e;
1848 	} else if (e instanceof Error) {
1849 	    throw (Error) e;
1850 	} else {
1851 	    throw new IllegalStateException(e);
1852 	}
1853     }
1854 
1855     /** Logs a throw */
1856     private static void logThrow(Level level, String method,
1857 				 String msg, Object[] msgParams, Throwable t)
1858     {
1859 	LogRecord r = new LogRecord(level, msg);
1860 	r.setLoggerName(logger.getName());
1861 	r.setSourceClassName(NormServerBaseImpl.class.getName());
1862 	r.setSourceMethodName(method);
1863 	r.setParameters(msgParams);
1864 	r.setThrown(t);
1865 	logger.log(r);
1866     }
1867     
1868     public void start() throws Exception {
1869         synchronized (this){
1870             if (started) return;
1871             started = true;
1872         }
1873         try {
1874             AccessController.doPrivileged(new PrivilegedExceptionAction(){
1875 
1876                 @Override
1877                 public Object run() throws Exception {
1878                     serverProxy = (NormServer) exporter.export(NormServerBaseImpl.this);
1879 
1880                     boolean done = false;
1881                     try {
1882                         // We use some of these during the recovery process
1883                         expMgr.setServer(NormServerBaseImpl.this);
1884                         lrmEventListener.setServer(NormServerBaseImpl.this);
1885 
1886                         try {
1887                             store = new PersistentStore(
1888                                 persistenceDirectory, new OurLogHandler(), NormServerBaseImpl.this);
1889                             // Creating the store completes the first two stages of 
1890                             // log recovery (reading the snapshot and the updates)
1891                             // Perform the last stage here of restoring transient state
1892                             restoreTransientState();
1893                             if (logger.isLoggable(Level.FINER)) {
1894                                 logger.log(Level.FINER, "Log recovered: {0}",
1895                                            inventory());
1896                             }
1897 
1898                         } catch (CorruptedStoreException e) {
1899                             throw new InitException("Log corrupted, can't recover ", e);
1900                         } catch (StoreException e) {
1901                             throw new InitException("Can't recover log", e);
1902                         }
1903                         
1904                         renewLogger.setStore(store);
1905                         snapshotter = new SnapshotThread(store);
1906 
1907                         if (serverUuid == null) {
1908                             serverUuid = UuidFactory.generate();
1909                         }
1910                         normProxy = NormProxy.create(serverProxy, serverUuid);
1911                         adminProxy = AdminProxy.create(serverProxy, serverUuid);
1912 
1913                         // Create new baseline snapshot
1914                         try {
1915                             store.snapshot();
1916                             if (logger.isLoggable(Level.FINER)) {
1917                                 logger.log(
1918                                     Level.FINER, "Completed new baseline snapshot: {0}",
1919                                     inventory());
1920                             }
1921                         } catch (IOException e) {
1922                             throw new InitException(
1923                                 "Can't create new baseline snapshot", e);
1924                         }
1925 
1926                         Entry[] serviceAttributes = {
1927                             new ServiceInfo(
1928                                 "Lease Renewal Service",		// name
1929                                 "Sun Microsystems, Inc.",		// manufacturer
1930                                 "Sun Microsystems, Inc.",		// vender
1931                                 VersionConstants.SERVER_VERSION,	// version
1932                                 "",					// model
1933                                 ""),				// serialNumber
1934                             new BasicServiceType("Lease Renewal Service")
1935                         };
1936                         try {
1937                             JoinState joinState = new JoinState(
1938                                 normProxy, lrm, config, serviceAttributes,
1939                                 recoveredLocatorPreparer,
1940                                 new ServiceID(serverUuid.getMostSignificantBits(),
1941                                               serverUuid.getLeastSignificantBits()));
1942                             store.addSubStore(joinState);
1943                             // joinState is mutated when added to store, makes
1944                             // sure that's published after mutation.
1945                             NormServerBaseImpl.this.joinState = joinState;
1946                         } catch (StoreException e) {
1947                             throw new InitException("Can't create JoinState", e);
1948                         }
1949 
1950                         leaseFactory = new LeaseFactory(serverProxy, serverUuid);
1951 
1952                         // $$$ By rights this code should be in
1953                         // restoreTransientState(), however we can't have an independent
1954                         // thread running around changing persistant state util we get
1955                         // to this point (I think the only real issue is the baseline
1956                         // snapshot) and once we place a set in the expMgr it the
1957                         // underlying wakeup queue will start running which can cause
1958                         // calls to expireIfTime() (and sendWarningEvent() though those
1959                         // should not be a problem since they don't log anything).
1960                         //
1961                         // I would prefer to ether modify WakeupQueue() to have a "start
1962                         // now" method (equivalent to how we create lrmEventListener above
1963                         // but call start() bellow) or be able to hold an exclusive
1964                         // snapshot lock until the initial snapshot is done.  Ether should
1965                         // allow this code to be moved into restoreTransientState
1966 
1967                         // Move to Starter method, is WakeupQueue() above 
1968                         // actually WakeupManager() renamed?  I suspect so.
1969                         expMgr.start();
1970                         for (Iterator i = setTable.values().iterator(); i.hasNext(); ) {
1971                             final LeaseSet set = (LeaseSet) i.next();
1972                             synchronized (set) {
1973                                 expMgr.schedule(set);
1974                             }
1975                         }
1976 
1977                         lrmEventListener.start();
1978                         renewLogger.start();
1979                         snapshotter.start();
1980                         done = true;
1981                     } finally {
1982                         if (!done) {
1983                             try {
1984                                 unexport(true);
1985                             } catch (Exception e) {
1986                                 logger.log(
1987                                     Level.INFO,
1988                                     "Unable to unexport after failure during startup",
1989                                     e);
1990                             }
1991                         }
1992                     }
1993                     return null;
1994                 }
1995 
1996             }, context);
1997 	    ready.ready();
1998 	    logger.log(Level.INFO, "Norm service started: {0}", this);
1999         } catch (PrivilegedActionException e){
2000             initFailed(e.getException());
2001         } catch (Throwable e) {
2002 	    initFailed(e);
2003 	} finally {
2004             config = null;
2005         }
2006     }
2007 
2008     /** Returns whether to isolate renewal sets or batch lease across sets. */
2009     boolean isolateSets() {
2010 	return isolateSets;
2011     }
2012 
2013     /**
2014      * Creates an instance of this class.
2015      */
2016     NormServerBaseImpl(NormServerInitializer init){
2017         persistent = init.persistent;
2018         lifeCycle = init.lifeCycle;
2019         loginContext = init.loginContext;
2020         persistenceDirectory = init.persistenceDirectory;
2021         renewedList = init.renewedList;
2022         snapshotWt = init.snapshotWt;
2023         logToSnapshotThresh = init.logToSnapshotThresh;
2024         leasePreparer = init.leasePreparer;
2025         listenerPreparer = init.listenerPreparer;
2026         locatorPreparer = init.locatorPreparer;
2027         recoveredLeasePreparer = init.recoveredLeasePreparer;
2028         recoveredListenerPreparer = init.recoveredListenerPreparer;
2029         recoveredLocatorPreparer = init.recoveredLocatorPreparer;
2030         setLeasePolicy = init.setLeasePolicy;
2031         isolateSets = init.isolateSets;
2032         lrm = init.lrm;
2033         exporter = init.exporter;
2034         expMgr = init.expMgr;
2035         generator = init.generator;
2036         lrmEventListener = init.lrmEventListener;
2037         renewLogger = init.renewLogger;
2038         context = init.context;
2039         config = init.config;
2040 	this.codebase = init.codebase;
2041 	this.certFactoryType = init.certFactoryType;
2042 	this.certPathEncoding = init.certPathEncoding;
2043 	this.encodedCerts = init.encodedCerts.clone();
2044     }
2045 }