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 org.apache.river.lease.BasicRenewalFailureEvent;
21  import org.apache.river.logging.Levels;
22  import org.apache.river.norm.event.EventFactory;
23  import org.apache.river.proxy.ConstrainableProxyUtil;
24  import java.io.IOException;
25  import java.io.Serializable;
26  import java.lang.reflect.Method;
27  import java.rmi.MarshalledObject;
28  import java.rmi.RemoteException;
29  import java.rmi.UnmarshalException;
30  import java.util.List;
31  import java.util.logging.Level;
32  import java.util.logging.Logger;
33  import net.jini.core.constraint.RemoteMethodControl;
34  import net.jini.core.event.RemoteEvent;
35  import net.jini.core.lease.Lease;
36  import net.jini.core.lease.LeaseDeniedException;
37  import net.jini.core.lease.LeaseMap;
38  import net.jini.core.lease.UnknownLeaseException;
39  import net.jini.io.MarshalledInstance;
40  import net.jini.lease.LeaseRenewalSet;
41  import net.jini.security.ProxyPreparer;
42  import org.apache.river.api.io.AtomicMarshalledInstance;
43  import org.apache.river.api.io.AtomicSerial;
44  import org.apache.river.api.io.AtomicSerial.GetArg;
45  
46  /**
47   * Class that wraps client Leases.  Provides hooks for synchronization 
48   * and data associated with each client lease while allowing us to 
49   * use <code>LeaseRenewalManager</code>.
50   * <p>
51   * This code assumes most synchronization is being done at the set level,
52   * this works because no lease is in more than one set at a given time.
53   * The only place where additional synchronization is going on is in
54   * renewals and the logging of renewals.
55   *
56   * @author Sun Microsystems, Inc.
57   */
58  @AtomicSerial
59  class ClientLeaseWrapper implements Lease, Serializable {
60      private static final long serialVersionUID = 2;
61  
62      /** Logger for logging messages for this class */
63      private static final Logger logger = Logger.getLogger("org.apache.river.norm");
64  
65      /* Map for comparing lease constraints. */
66      private static final Method[] leaseToLeaseMethods;
67      static {
68  	try {
69  	    Method cancelMethod =
70  		Lease.class.getMethod("cancel", new Class[] { });
71  	    Method renewMethod =
72  		Lease.class.getMethod("renew", new Class[] { long.class });
73  	    leaseToLeaseMethods = new Method[] {
74  		cancelMethod, cancelMethod, renewMethod, renewMethod };
75  	} catch (NoSuchMethodException e) {
76  	    throw new NoSuchMethodError(e.getMessage());
77  	}
78      }
79  
80      /**
81       * Throwable thrown by the last renew attempt on the client lease.
82       * <code>null</code> if the renew has not been called yet or
83       * the last renewal call succeeded.
84       * @serial
85       */
86      private Throwable lastFailure = null;
87  
88      /**
89       * Client lease in marshalled form.
90       * @serial
91       */
92      private MarshalledInstance marshalledClientLease;
93  
94      /**
95       * Most current expiration time of client Lease that we know of	
96       * @serial
97       */
98      private long clientLeaseExpiration;
99  
100     /**
101      * Sequence number that uniquely identifies this wrapper
102      * @serial
103      */
104     private final long UID;
105 
106     /**
107      * Membership expiration of this lease
108      * @serial
109      */
110     private long membershipExpiration;
111 
112     /**
113      * renewDuration of this lease
114      * @serial
115      */
116     private long renewDuration;
117 
118     /**
119      * The LeaseSet we belong to
120      */
121     private transient LeaseSet set;
122 
123     /**
124      * Transient copy of unpacked lease, <code>null</code> if we could not
125      * unpack or prepare the lease.
126      */
127     private transient Lease clientLease;
128 
129     /**
130      * The proxy preparer to use to prepare a newly unmarshalled client lease,
131      * or null if this instance was created using an already prepared client
132      * lease, which is how instances are created initially.
133      */
134     private transient ProxyPreparer recoveredLeasePreparer;
135 
136     /** 
137      * Flag used to check if lease has been persisted since last
138      * renewal, <code>true</code> means it has not been
139      */
140     private transient boolean renewalPending = false;
141 
142     /**
143      * Reference to list of leases that have been renewed, but not
144      * persisted
145      */
146     private transient List renewedList;
147 
148     /**
149      * Simple constructor
150      * @param clientLease lease from client that is to be renewed
151      * @param UID ID number for this wrapper unique for all wrappers in a given
152      *		  server
153      * @param renewedList list that wrapper should go on after renewing their
154      *			  client lease is renewed
155      * @param leaseSet	  the <code>LeaseSet</code> this lease is in
156      * @param membershipDuration 
157      *                    initial membership duration for this lease
158      * @param renewDuration
159      *                    initial membership expiration for the lease
160      * @param now         the current time
161      */
162     ClientLeaseWrapper(Lease clientLease, long UID, List renewedList,
163 		       LeaseSet leaseSet, long membershipDuration,
164 		       long renewDuration, long now) 
165 	throws IOException
166     {
167 	this.renewedList = renewedList;
168 	this.UID = UID;
169 	this.clientLease = clientLease;
170 
171 	set = leaseSet;
172 
173 	clientLeaseExpiration = clientLease.getExpiration();
174 	clientLease.setSerialFormat(Lease.ABSOLUTE);
175 	marshalledClientLease = new AtomicMarshalledInstance(clientLease);
176 
177 	this.renewDuration = renewDuration;
178 	membershipExpiration = calcMembershipExpiration(membershipDuration, now);
179     }
180 
181     ClientLeaseWrapper(GetArg arg) throws IOException {
182 	this((Throwable) arg.get("lastFailure", null),
183 	    (MarshalledInstance) arg.get("marshalledClientLease", null),
184 	    arg.get("clientLeaseExpiration", 0L),
185 	    arg.get("UID", 0L),
186 	    arg.get("membershipExpiration", 0L),
187 	    arg.get("renewDuration", 0L));
188     }
189     
190     ClientLeaseWrapper(Throwable lastFailure, 
191 	    MarshalledInstance marshalledClientLease,
192 	    long clientLeaseExpiration,
193 	    long UID,
194 	    long membershipExpiration,
195 	    long renewDuration)
196     {
197 	this.lastFailure = lastFailure;
198 	this.marshalledClientLease = marshalledClientLease;
199 	this.clientLeaseExpiration = clientLeaseExpiration;
200 	this.UID = UID;
201 	this.membershipExpiration = membershipExpiration;
202 	this.renewDuration = renewDuration;
203     }
204 
205     /**
206      * Given the current time and a membershipDuration set membershipExpiration
207      * the correct value.
208      */
209     private static long calcMembershipExpiration(long membershipDuration, long now) {
210 	long membershipExpiration;
211 	if (membershipDuration == Lease.FOREVER) {
212 	    membershipExpiration = Lease.FOREVER;
213 	} else {
214 	    membershipExpiration = membershipDuration + now;
215 	    // Check for overflow
216 	    if (membershipExpiration < 0)
217 		membershipExpiration = Long.MAX_VALUE;
218 	}
219 	return membershipExpiration;
220     }
221 
222 
223     /**
224      * Update the membership expiration and renewDuration of this lease
225      */
226     void update(long membershipDuration, long renewDuration, long now) {
227 	this.renewDuration = renewDuration;
228 	membershipExpiration = calcMembershipExpiration(membershipDuration, now);
229     }	
230 
231     /**
232      * Return the membershipExpiration of this lease    
233      */
234     long getMembershipExpiration() {
235 	return membershipExpiration;
236     }
237 
238     /**
239      * Return the renewDuration of this lease    
240      */
241     long getRenewDuration() {
242 	return renewDuration;
243     }
244 
245     /**
246      * Atomically clear the renewalPending flag.
247      */
248     synchronized void clearRenewed() {
249 	renewalPending = false;
250     }
251 
252     /**
253      * Get a reference to the client lease, unmarshalling and preparing it if
254      * necessary.  If it can't be unpacked or prepared, return null.
255      */
256     // $$$ this might want to be synchronized
257     Lease getClientLease() {
258 	if (clientLease == null) {
259 	    Lease unmarshalledLease = null;
260 	    try {
261 		unmarshalledLease = (Lease) marshalledClientLease.get(false);
262 	    } catch (IOException e) {
263 		logger.log(Levels.HANDLED,
264 			   "Problem unmarshalling lease -- will retry later",
265 			   e);
266 	    } catch (ClassNotFoundException e) {
267 		logger.log(Levels.HANDLED,
268 			   "Problem unmarshalling lease -- will retry later",
269 			   e);
270 	    }
271 	    if (unmarshalledLease != null) {
272 		try {
273 		    clientLease = (Lease) recoveredLeasePreparer.prepareProxy(
274 			unmarshalledLease);
275 		} catch (RemoteException e) {
276 		    logger.log(Levels.HANDLED,
277 			       "Problem preparing lease -- will retry later",
278 			       e);
279 		} catch (SecurityException e) {
280 		    logger.log(Levels.HANDLED,
281 			       "Problem preparing lease -- will retry later",
282 			       e);
283 		}
284 	    }
285 	}
286 	return clientLease;
287     }
288 
289     /**
290      * Return the client lease in marshalled form.  If possible
291      * marshal using the Lease.DURATION serialization format.
292      * <p>
293      * Assumes that no one else will be serializing the lease 
294      * during this call.
295      */
296     MarshalledInstance getMarshalledClientLease() {
297 	final Lease cl = getClientLease();
298 
299 	if (cl == null) {
300 	    // We are deformed, best we can do is return the (possibly
301 	    // stale) pre-marshalled lease we already have
302 	    return marshalledClientLease;
303 	} else {
304 	    try {
305 		cl.setSerialFormat(Lease.DURATION);
306 		return new MarshalledInstance(cl);
307 	    } catch (IOException e) {
308 		// Can't create a new MarshalledInstance, return the old one
309 		return marshalledClientLease;
310 	    } finally {
311 		cl.setSerialFormat(Lease.ABSOLUTE);
312 	    }
313 	}
314     }
315 
316     // Inherit java doc from super type
317     public boolean equals(Object that) {
318 	if (that instanceof ClientLeaseWrapper) {
319 	    return UID == ((ClientLeaseWrapper) that).UID;
320 	} else {
321 	    return false;
322 	}
323     }
324 
325     // Inherit java doc from super type
326     public int hashCode() {
327 	return (int) UID;
328     }
329 
330     // Inherit java doc from super type
331     public String toString() {
332 	return "CLW:" + UID + " exp:" + clientLeaseExpiration + " dexp:" + 
333 	    membershipExpiration + " dur:" + renewDuration + " failure:" +
334 	    lastFailure;
335     }
336 
337 
338     // Inherit java doc from super type
339     public long getExpiration() {
340 	final Lease cl = getClientLease();
341 	if (cl == null) {
342 	    // $$$ Why is this synchronized?
343 	    synchronized (this) {		
344 		return clientLeaseExpiration;
345 	    }
346 	} else {
347 	    return cl.getExpiration();
348 	}
349     }
350 
351     /**
352      * Always throws <code>UnsupportedOperationException</code> since
353      * a lease renewal service should never cancel a client lease.  
354      */
355     public void cancel() {
356 	throw new UnsupportedOperationException("ClientLeaseWrapper.cancel:" + 
357 	     "LRS should not being canceling client leases ");
358     }
359 
360     /**
361      * Return the exception (if any) that occured at the last renewal attempt
362      */
363     Throwable getLastFailure() {
364 	return lastFailure;
365     }
366 
367     /**
368      * Atomically test and set the renewalPending flag.
369      * @return the state of the renewalPending flag before it was set
370      */
371     private synchronized boolean testAndSetRenewalPending() {
372 	final boolean result = renewalPending;
373 	renewalPending = true;
374 	return result;
375     }
376 
377     /**
378      * Called when a renewal thread changes the persistent state of the 
379      * this wrapper.  If necessary places this object on the queue of 
380      * wrappers to be persisted.
381      */
382     private void changed() {
383 	if (!testAndSetRenewalPending()) {
384 	    synchronized (renewedList) {
385 		renewedList.add(this);
386 		renewedList.notifyAll();
387 	    }
388 	}
389     }
390 
391     /**
392      * Called by ClientLeaseMapWrapper to see if the set associated with
393      * this wrapper is still valid. Returns true if it still is
394      */
395     boolean ensureCurrent(long now) {
396 	return set.ensureCurrent(now);
397     }
398 
399     /**
400      * Sets lastFailure and adds the this to the renewed list so it 
401      * so the change can be logged. Note this is only called from
402      * from renew methods that are renewing the client lease associated
403      * with this wrapper so additional synchronization should not be
404      * necessary.
405      */
406     void failedRenewal(Throwable t) {
407 	lastFailure = t;
408 	changed();
409     }
410 
411     /**
412      * Log a successful lease renewalNote this is only called from
413      * from renew methods that are renewing the client lease associated
414      * with this wrapper so additional synchronization should not be
415      * necessary.
416      */
417     void successfulRenewal() {
418 	lastFailure = null;
419 
420 	// Update the marshalled version of the lease 
421 	// $$$ We could only do this in writeObject but then we would have
422 	// to make sure it had changed since the last serialization, the 
423 	// renewalPending flag almost does this but not quite (maybe it should 
424 	// and we don't have it quite right...maybe writeObject should
425 	// clear the  flag instead of explicit clearing it after writing
426 	// the clw out?).  Not clear doing it here is significantly less
427 	// efficient.
428 	try {
429 	    marshalledClientLease = new MarshalledInstance(clientLease);
430 	} catch (IOException e) {
431 	    // $$$ Besides printing a message drop this on the floor,
432 	    // exception in some pathological cases we are extremely
433 	    // unlikely to get this exception, further more this is really
434 	    // just a logging error, which our current policy is to drop.
435 	    // In the abstract it would be better to do this in writeObject
436 	    // since this really is an error while trying to persist the 
437 	    // state of this object, however, that solution would require
438 	    // more synchronization.  Lastly this method may go away if
439 	    // we decide to stop using the LRM.
440 	    logger.log(Level.WARNING,
441 		       "IOException while marshalling client lease " +
442 		       "after renewal",
443 		       e);
444 	}
445 
446 	// synchronize in case long assignment is not atomic
447 	synchronized (this) {
448 	    clientLeaseExpiration = clientLease.getExpiration();
449 	}
450 
451 	// If necessary notify rest of system that this lease has to 
452 	// be persisted
453 	changed();
454     }
455 
456     // Inherit java doc from super type
457     // This method assumes that only this thread is renewing this lease
458     public void renew(long duration)
459 	throws LeaseDeniedException, UnknownLeaseException, RemoteException 
460     {
461 	// Check to make sure the set has not expired
462 	if (!set.ensureCurrent(System.currentTimeMillis())) {
463 	    // The set has expired, throw an exception that will
464 	    // tell the failure logging thread to ignore this failure
465 	    throw LRMEventListener.EXPIRED_SET_EXCEPTION;
466 
467 	    // Note, we don't call failedRenewal() because the set is
468 	    // dead so logging changes is pointless (besides, there
469 	    // is no change to log.)
470 	}
471 	
472 	// Do we have the real lease in unmarshalled form?
473 	if (clientLease == null) {
474 	    // try to unmarshal client lease
475 	    Lease unmarshalledLease;
476 	    try {
477 		unmarshalledLease = (Lease) marshalledClientLease.get(false);
478 	    } catch (IOException e) {
479 		UnmarshalException t = new UnmarshalException(
480 		    "ClientLeaseWrapper.renew:Could not unmarshal client " +
481 		    "lease for renewal", e);
482 		failedRenewal(t);
483 		throw t;
484 	    } catch (ClassNotFoundException e) {
485 		UnmarshalException t = new UnmarshalException(
486 		    "ClientLeaseWrapper.renew:Could not unmarshal client " +
487 		    "lease for renewal", e);
488 		failedRenewal(t);
489 		throw t;
490 	    }
491 	    // Try to prepare the client lease
492 	    try {
493 		clientLease = (Lease) recoveredLeasePreparer.prepareProxy(
494 		    unmarshalledLease);
495 	    } catch (RemoteException e) {
496 		failedRenewal(e);
497 		throw e;
498 	    } catch (SecurityException e) {
499 		failedRenewal(e);
500 		throw e;
501 	    }
502 	}
503 
504 	// If we get here clientLease must be non-null
505 
506 	// $$$ do we want to ensure that the lease on the set is
507 	// current here or just rely on the expiration thread.
508 	try {
509 	    clientLease.renew(duration);
510 	} catch (LeaseDeniedException e) {
511 	    failedRenewal(e);
512 	    throw e;
513 	} catch (UnknownLeaseException e) {
514 	    failedRenewal(e);
515 	    throw e;
516 	} catch (RemoteException e) {
517 	    failedRenewal(e);
518 	    throw e;
519 	} catch (RuntimeException e) {
520 	    failedRenewal(e);
521 	    throw e;
522 	} catch (Error e) {
523 	    failedRenewal(e);
524 	    throw e;
525 	}
526 
527 	// If we get here we must have successfully renewed the lease
528 	successfulRenewal();
529     }
530 
531     /**
532      * Always throws <code>UnsupportedOperationException</code>.  The
533      * <code>LeaseRenewalManager</code> should never call this method and 
534      * norm always serializes the wrapper with absolute times.
535      */
536     public void setSerialFormat(int format) {
537 	throw new UnsupportedOperationException(
538 	     "ClientLeaseWrapper.setSerialFormat:" + 
539 	     "LRS should not be setting serial format through the wrapper");
540     }
541 
542 
543     /**
544      * Always throws <code>UnsupportedOperationException</code>.  The
545      * <code>LeaseRenewalManager</code> should never call this method and 
546      * norm always serializes the wrapper with absolute times.
547      */
548     public int getSerialFormat() {
549 	throw new UnsupportedOperationException(
550 	     "ClientLeaseWrapper.setSerialFormat:" + 
551 	     "LRS should not be setting serial format through the wrapper");
552     }
553 
554     // Inherit java doc from super type
555     public LeaseMap<Lease, Long> createLeaseMap(long duration) {
556 	if (isDeformed()) {
557 	    return new DeformedClientLeaseMapWrapper(this, duration);
558 	} else {
559 	    return new ClientLeaseMapWrapper(this, duration);
560 	}
561     }
562 
563     /**
564      * Another lease can be batched with this one if it is a
565      * ClientLeaseMapWrapper, if it is either a member of the same lease
566      * renewal set or sets are not isolated, if neither lease is deformed, if
567      * the leases either both have the same client constraints or do not
568      * implement RemoteMethodControl, and if the underlying client leases can
569      * be batched.
570      */
571     public boolean canBatch(Lease lease) {
572 	if (!(lease instanceof ClientLeaseWrapper)) 
573 	    return false;
574 
575 	final ClientLeaseWrapper clw = (ClientLeaseWrapper) lease;
576 	
577 	if (set.isolateSets() && !set.equals(clw.set))
578 	    return false;
579 
580 	if (isDeformed() || clw.isDeformed())
581 	    return false;
582 
583 	Lease clientLease = getClientLease();
584 	Lease otherClientLease = clw.getClientLease();
585 	return sameConstraints(clientLease, otherClientLease) &&
586 	    clientLease.canBatch(otherClientLease);
587     }
588 
589     /**
590      * Returns true if the two leases both implement RemoteMethodControl and
591      * have the same constraints for Lease methods, or both don't implement
592      * RemoteMethodControl, else returns false.
593      */
594     private static boolean sameConstraints(Lease l1, Lease l2) {
595 	if (!(l1 instanceof RemoteMethodControl)) {
596 	    return !(l2 instanceof RemoteMethodControl);
597 	} else if (!(l2 instanceof RemoteMethodControl)) {
598 	    return false;
599 	} else {
600 	    return ConstrainableProxyUtil.equivalentConstraints(
601 		((RemoteMethodControl) l1).getConstraints(),
602 		((RemoteMethodControl) l2).getConstraints(),
603 		leaseToLeaseMethods);
604 	}
605     }
606 
607     /**
608      * After recovering a lease wrapper call this method before using
609      * any other of the wrappers methods and before allocating any new
610      * wrappers can not recover itself.
611      * @param renewedList List that wrapper should go on after renewing their
612      *	      client lease is renewed
613      * @param generator ID generator being used to generate IDs client lease
614      *	      wrappers
615      * @param leaseSet the set this wrapper is associated with
616      * @param recoveredLeasePreparer the proxy preparer to use to prepare
617      *	      client leases recovered from persistent storage
618      */
619     void recoverTransient(List renewedList, UIDGenerator generator, 
620 			  LeaseSet leaseSet,
621 			  ProxyPreparer recoveredLeasePreparer) 
622     {
623 	this.renewedList = renewedList;
624 	set = leaseSet;
625 	generator.inUse(UID);
626 	this.recoveredLeasePreparer = recoveredLeasePreparer;
627 	// Try to get the client lease unpacked
628 	getClientLease();
629 	// Add to set's lease table now that the transient state is
630 	// restored and we have tried unpacking once
631 	set.addToLeaseTable(this);
632     }
633 
634     /**
635      * Return true if the underlying client lease has not yet been deserialized
636      */
637     boolean isDeformed() {
638 	return getClientLease() == null;
639     }
640 
641     /**
642      * The <code>LeaseSet</code> this lease is in
643      */
644     LeaseSet getLeaseSet() {
645 	return set;
646     }
647 
648     /**
649      * Return an <code>EventFactory</code> that will create an
650      * appropriate <code>RenewalFailureEvent</code> for the client
651      * lease associated with this wrapper.  This method assumes
652      * that no one else will be setting the serialization format
653      * of the lease during the course of the call.
654      * @param source Source object for the event
655      * @throws IOException if the client lease could not be pre
656      * marshalled.
657      */
658     EventFactory newFailureFactory(LeaseRenewalSet source) throws IOException {
659 	final MarshalledInstance ml = getMarshalledClientLease();
660 
661 	MarshalledInstance mt = null;
662 	if (lastFailure != null)
663 	    mt = new MarshalledInstance(lastFailure);
664 
665 	return new FailureFactory(source, ml, mt); 
666     }
667 
668     /**
669      * Nested top-level implementation of <code>EventFactory</code> that
670      * generates  <code>RenewalFailureEvent</code> events
671      */
672     // $$$ We could have this object share state with wrapper, but weird
673     // things could happen if the wrapper then changes -- right now
674     // the wrapper won't change after this method is called but in the 
675     // future who is to say.
676     private static class FailureFactory implements EventFactory {
677 	/** Source for event */
678 	private final LeaseRenewalSet source;
679 	
680 	/** Client lease that could not be renewed in marshalled form */
681 	final private MarshalledInstance marshalledLease;
682 
683 	/** 
684 	 * Throwable (if any) that was thrown when we tried to renew the lease
685 	 * in marshalled form
686 	 */
687 	final private MarshalledInstance marshalledThrowable;
688 
689 	/**
690 	 * Simple constructor
691 	 * @param source event source
692 	 * @param marshalledLease client lease that could not be renewed
693 	 *        in marshalled form
694 	 * @param marshalledThrowable exception (if any) that was thrown when
695 	 *        the lease could not be renewed
696 	 */
697 	private FailureFactory(LeaseRenewalSet  source,
698 			       MarshalledInstance marshalledLease,
699 			       MarshalledInstance marshalledThrowable)
700 	{
701 	    this.source = source;
702 	    this.marshalledLease = marshalledLease;
703 	    this.marshalledThrowable = marshalledThrowable;
704 	}
705 
706 	// Inherit java doc from super type
707 	public RemoteEvent createEvent(long             eventID, 
708 				       long             seqNum, 
709 				       MarshalledObject handback) 
710 	{
711 	    return new BasicRenewalFailureEvent(source, seqNum,
712 	        handback, marshalledLease, marshalledThrowable);
713 	}
714     }
715 }