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.outrigger.proxy;
19  
20  import java.io.IOException;
21  import java.io.InvalidObjectException;
22  import java.io.ObjectInputStream;
23  import java.io.Serializable;
24  import java.rmi.MarshalException;
25  import java.rmi.MarshalledObject;
26  import java.rmi.RemoteException;
27  import java.security.PrivilegedAction;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.Collections;
31  import java.util.Iterator;
32  import java.util.LinkedList;
33  import java.util.List;
34  import java.util.logging.Level;
35  import java.util.logging.Logger;
36  import net.jini.admin.Administrable;
37  import net.jini.core.entry.Entry;
38  import net.jini.core.entry.UnusableEntryException;
39  import net.jini.core.event.EventRegistration;
40  import net.jini.core.event.RemoteEventListener;
41  import net.jini.core.lease.Lease;
42  import net.jini.core.transaction.Transaction;
43  import net.jini.core.transaction.TransactionException;
44  import net.jini.entry.UnusableEntriesException;
45  import net.jini.export.ProxyAccessor;
46  import net.jini.id.ReferentUuid;
47  import net.jini.id.ReferentUuids;
48  import net.jini.id.Uuid;
49  import net.jini.id.UuidFactory;
50  import net.jini.security.Security;
51  import net.jini.space.JavaSpace05;
52  import net.jini.space.MatchSet;
53  import org.apache.river.api.io.AtomicSerial;
54  import org.apache.river.api.io.AtomicSerial.GetArg;
55  import org.apache.river.landlord.LandlordLease;
56  
57  /**
58   * This class is the client-side proxy for the Outrigger
59   * implementation of a JavaSpaces<sup>TM</sup>
60   * service.  <code>OutriggerServerImpl</code> implements the
61   * <code>OutriggerSpace</code> interface, and each
62   * <code>SpaceProxy2</code> object holds a reference to the remote
63   * OutriggerSpace server it represents to the client. The client makes
64   * calls from the <code>JavaSpace</code> interface, which the
65   * <code>SpaceProxy2</code> translates into appropriate
66   * <code>OutriggerSpace</code> calls to the
67   * <code>OutriggerServerImpl</code> server.
68   *
69   * @author Sun Microsystems, Inc.
70   *
71   */
72  // @see OutriggerSpace
73  @AtomicSerial
74  public class SpaceProxy2 implements JavaSpace05, Administrable, ReferentUuid,
75  			     Serializable, ProxyAccessor
76  {
77      static final long serialVersionUID = 1L;
78  
79      /**
80       * The remote server this proxy works with.
81       * Package protected so it can be read by subclasses and proxy verifier
82       * @serial
83       */
84      final OutriggerServer space;
85  
86      /** 
87       * The <code>Uuid</code> that identifies the space this proxy is for.
88       * Package protected so it can be read by subclasses and proxy verifier
89       * @serial
90       */
91      final Uuid spaceUuid;
92  
93      /**
94       * The value to use for maxServerQueryTimeout if no
95       * local value is provided. Package protected so it can be read by
96       * subclasses.
97       * @serial
98       */
99      final long serverMaxServerQueryTimeout;
100 
101     /**
102      * Maximum time any sub-query should be allowed to run for.
103      */
104     private transient volatile long maxServerQueryTimeout;
105     
106 
107     /**
108      * Value (as a long) of the
109      * <code>org.apache.river.outrigger.maxServerQueryTimeout</code>
110      * property in this VM, or a non-positive number if it is not set.
111      */
112     private static final long maxServerQueryTimeoutPropertyValue =
113 	getMaxServerQueryTimeoutPropertyValue();
114 
115     /** 
116      * Logger for logging information about operations carried out in
117      * the client. Note, we hard code "org.apache.river.outrigger" so
118      * we don't drag in OutriggerServerImpl to outrigger-dl.jar.
119      */
120     private static final Logger logger = 
121 	Logger.getLogger("org.apache.river.outrigger.proxy");
122 
123     // --------------------------------------------------
124     //		Construction
125     // --------------------------------------------------
126 
127     /**
128      * Create a new <code>SpaceProxy2</code> for the given space.
129      * @param space The <code>OutriggerServer</code> for the 
130      *              space.
131      * @param spaceUuid The universally unique ID for the
132      *              space
133      * @param serverMaxServerQueryTimeout The value this proxy
134      *              should use for the <code>maxServerQueryTimeout</code>
135      *              if no local value is provided.
136      * @throws NullPointerException if <code>space</code> or
137      *         <code>spaceUuid</code> is <code>null</code>.
138      * @throws IllegalArgumentException if 
139      *         <code>serverMaxServerQueryTimeout</code> is not
140      *         larger than zero.
141      */
142     public SpaceProxy2(OutriggerServer space, Uuid spaceUuid, 
143 		long serverMaxServerQueryTimeout)
144     {
145 	this (notNull(space),
146 		notNull(spaceUuid),
147 		serverMaxServerQueryTimeout,
148 		setMaxServerQueryTimeout(serverMaxServerQueryTimeout));
149     }
150     
151     private static <T> T notNull(T value){
152 	if (value == null) throw new NullPointerException();
153 	return value;
154     }
155     
156     private SpaceProxy2( OutriggerServer space, Uuid spaceUuid, 
157 	    long serverMaxServerQueryTimeout, long maxServerQueryTimeout ){
158 	this.space = space;
159 	this.spaceUuid = spaceUuid;
160 	this.serverMaxServerQueryTimeout = serverMaxServerQueryTimeout;
161 	this.maxServerQueryTimeout = maxServerQueryTimeout;
162     }
163 
164     SpaceProxy2(GetArg arg) throws IOException {
165 	this((OutriggerServer) arg.get("space", null),
166 		(Uuid) arg.get("spaceUuid", null),
167 		arg.get("serverMaxServerQueryTimeout", -1L));
168     }
169 
170     @Override
171     public String toString() {
172 	return getClass().getName() + " for " + spaceUuid + 
173 	    " (through " + space + ")";
174     }
175 
176     // inherit doc comment
177     @Override
178     public boolean equals(Object other) {
179 	return ReferentUuids.compare(this, other);
180     }
181 
182     // inherit doc comment
183     @Override
184     public int hashCode() {
185 	return spaceUuid.hashCode();
186     }
187 
188     public Uuid getReferentUuid() {
189 	return spaceUuid;
190     }
191 
192     /**
193      * Safely read the value of 
194      * <code>org.apache.river.outrigger.maxServerQueryTimeout</code>.
195      * If it can't be read return -1.
196      */
197     private static long getMaxServerQueryTimeoutPropertyValue() {
198 	try {
199 	    final String propValue = 
200 		(String)Security.doPrivileged(
201                     new ReadProperityPrivilegedAction(
202                        "org.apache.river.outrigger.maxServerQueryTimeout"));
203 	    if (propValue == null)
204 		return -1;
205 
206 	    // Note, if we throw NumberFormatException we return -1.
207 	    return Long.parseLong(propValue);
208 	} catch (Throwable t) {
209 	    return -1;
210 	}
211     }
212 
213     @Override
214     public Object getProxy() {
215 	return space;
216     }
217 
218     /**
219      * PrivilegedAction for reading a property. Returns a string.
220      */
221     private static class ReadProperityPrivilegedAction 
222 	implements PrivilegedAction 
223     {
224 	/** Property to read */
225 	final private String propName;
226 
227 	/** 
228 	 * Construct a ReadProperityPrivilegedAction that will read
229 	 * the specified property.
230 	 */
231 	ReadProperityPrivilegedAction(String propName) {
232 	    this.propName = propName;
233 	}
234 
235 	public Object run() {
236 	    try {
237 		return System.getProperty(propName);
238 	    } catch (SecurityException e) {
239 		return null;
240 	    }
241 	}
242     }
243 
244     /**
245      * Set <code>maxServerQueryTimeout</code> based on the values
246      * of <code>serverMaxServerQueryTimeout</code> and 
247      * <code>maxServerQueryTimeoutPropertyValue</code>.
248      */
249     private static long setMaxServerQueryTimeout(long serverMaxServerQueryTimeout) {
250 	if (serverMaxServerQueryTimeout <= 0)
251 	    throw new 
252 		IllegalArgumentException("serverMaxServerQueryTimeout " +
253 					 "must be positive");
254 	/* If the org.apache.river.outrigger.maxServerQueryTimeout property
255 	 * was set in this VM, override the value set by the server
256 	 * when we were created.
257 	 */
258 	long maxServerQueryTimeout;
259 	if (maxServerQueryTimeoutPropertyValue > 0) 
260 	    maxServerQueryTimeout = maxServerQueryTimeoutPropertyValue;
261 	else if (serverMaxServerQueryTimeout > 0)
262 	    maxServerQueryTimeout = serverMaxServerQueryTimeout;
263 	else 
264 	    /* should never get here, the constructor and readObject
265 	     * check to make sure that serverMaxServerQueryTimeout
266 	     * is positive.
267 	     */
268 	    throw new 
269 	        AssertionError("serverMaxServerQueryTimeout invalid:" + 
270 			       serverMaxServerQueryTimeout);
271 
272 	if (logger.isLoggable(Level.CONFIG)) {
273 	    logger.log(Level.CONFIG, 
274 		"Outrigger proxy using {0} ms for maxServerQueryTimeout",
275 		Long.valueOf(maxServerQueryTimeout));
276 	}
277 	return maxServerQueryTimeout;
278     }
279 
280     /**
281      * Read this object back setting the <code>maxServerQueryTimeout</code>
282      * field and validate state.
283      */
284     private void readObject(ObjectInputStream in)
285 	throws IOException, ClassNotFoundException
286     {
287 	in.defaultReadObject();
288 
289 	if (space == null) 
290 	    throw new InvalidObjectException("null server reference");
291 	    
292 	if (spaceUuid == null)
293 	    throw new InvalidObjectException("null Uuid");
294 
295 	if (serverMaxServerQueryTimeout <= 0)
296 	    throw new 
297 		InvalidObjectException("Bad serverMaxServerQueryTimeout " +
298 		    "value:" + serverMaxServerQueryTimeout);
299 
300 	maxServerQueryTimeout = setMaxServerQueryTimeout(serverMaxServerQueryTimeout);
301     }
302 
303     /** 
304      * We should always have data in the stream, if this method
305      * gets called there is something wrong.
306      */
307     private void readObjectNoData() throws InvalidObjectException {
308 	throw new 
309 	    InvalidObjectException("SpaceProxy2 should always have data");
310     }
311 
312     // --------------------------------------------------
313     //		JavaSpace method implementations
314     // --------------------------------------------------
315 
316     // inherit doc comment
317     public Lease write(Entry entry, Transaction txn, long lease)
318 	throws TransactionException, RemoteException
319     {
320 	if (entry == null)
321 	    throw new NullPointerException("Cannot write null Entry");
322 	long[] leaseData = space.write(repFor(entry), txn, lease);
323 	if (leaseData == null || leaseData.length != 3){
324             StringBuilder sb = new StringBuilder(180);
325             sb.append("space.write returned malformed data \n");
326             int l = leaseData == null? 0 : leaseData.length;
327             for (int i =0; i < l; i++){
328                 sb.append(leaseData[i]).append("\n");
329             }
330 	    throw new AssertionError(sb);
331         }
332 	return newLease(UuidFactory.create(leaseData[1], leaseData[2]),
333 			leaseData[0]);
334     }
335 
336     public Entry read(Entry tmpl, Transaction txn, long timeout)
337 	throws UnusableEntryException, TransactionException,
338 	       InterruptedException, RemoteException
339     {
340 	// Figure out the max time this query should last
341 	final long endTime = calcEndTime(timeout);
342     
343 	long remaining = timeout;
344 	OutriggerServer.QueryCookie queryCookie = null;
345 	
346  	// Loop util timeout or we get an answer (call at least once!)
347 	do {
348 	    final long serverTimeout = 
349 		Math.min(remaining, maxServerQueryTimeout);
350 	    logQuery("read", serverTimeout, queryCookie, remaining);
351 
352 	    final Object rslt = 
353 		space.read(repFor(tmpl), txn, serverTimeout, queryCookie);
354 	    if (rslt == null) {
355 		// should never get null from a non-ifExists query
356 		throw new AssertionError("space.read() returned null");
357 	    } else if (rslt instanceof EntryRep) {
358 		// Got an answer, return it
359 		return entryFrom((EntryRep)rslt);
360 	    } else if (rslt instanceof OutriggerServer.QueryCookie) {
361 		/* Will still want to go on if there is time, but pass
362 		 * the new cookie
363 		 */
364 		queryCookie = (OutriggerServer.QueryCookie)rslt;
365 	    } else {
366 		throw new AssertionError(
367                     "Unexpected return type from space.read()");
368 	    }
369 
370 	    /* Update remaining and loop, checking to see if the timeout has
371 	     * expired.
372 	     */
373 	    remaining = endTime - System.currentTimeMillis();
374 	} while (remaining > 0);
375 
376 	/* If we get here then there must not have been an entry available
377 	 * to us before the endTime.
378 	 */
379 	return null;
380     }
381 
382     // inherit doc comment, use internal routine for common code
383     public Entry readIfExists(Entry tmpl, Transaction txn, long timeout)
384 	throws UnusableEntryException, TransactionException,
385 	       InterruptedException, RemoteException
386     {
387 	// Figure out the max time this query should last
388 	final long endTime = calcEndTime(timeout);
389     
390 	long remaining = timeout;
391 	OutriggerServer.QueryCookie queryCookie = null;
392 	
393  	// Loop util timeout or we get an answer (call at least once!)
394 	do {
395 	    final long serverTimeout = 
396 		Math.min(remaining, maxServerQueryTimeout);
397 	    logQuery("readIfExists", serverTimeout, queryCookie, remaining);
398 
399 	    final Object rslt = 
400 		space.readIfExists(repFor(tmpl), txn, serverTimeout,
401 				   queryCookie);
402 	    if (rslt == null) {
403 		// Must be no matches in the space at all
404 		return null;
405 	    } else if (rslt instanceof EntryRep) {
406 		// Got an answer, return it
407 		return entryFrom((EntryRep)rslt);
408 	    } else if (rslt instanceof OutriggerServer.QueryCookie) {
409 		/* Will still want to go on if there is time, but pass
410 		 * the new cookie
411 		 */
412 		queryCookie = (OutriggerServer.QueryCookie)rslt;
413 	    } else {
414 		throw new AssertionError(
415                     "Unexpected return type from space.readIfExists()");
416 	    }
417 
418 	    /* Update remaining and loop, checking to see if the timeout has
419 	     * expired.
420 	     */
421 	    remaining = endTime - System.currentTimeMillis();
422 	} while (remaining > 0);
423 
424 	/* If we get here then there must not have been an entry available
425 	 * to us before the endTime.
426 	 */
427 	return null;
428     }
429 
430     // inherit doc comment, use internal routine for common code
431     public Entry take(Entry tmpl, Transaction txn, long timeout)
432 	throws UnusableEntryException, TransactionException,
433 	       InterruptedException, RemoteException
434     {
435 	// Figure out the max time this query should last
436 	final long endTime = calcEndTime(timeout);
437     
438 	long remaining = timeout;
439 	OutriggerServer.QueryCookie queryCookie = null;
440 	
441  	// Loop util timeout or we get an answer (call at least once!)
442 	do {
443 	    final long serverTimeout = 
444 		Math.min(remaining, maxServerQueryTimeout);
445 	    logQuery("take", serverTimeout, queryCookie, remaining);
446 
447 	    final Object rslt = 
448 		space.take(repFor(tmpl), txn, serverTimeout, queryCookie);
449 	    if (rslt == null) {
450 		// should never get null from a non-ifExists query
451 		throw new AssertionError("space.take() returned null");
452 	    } else if (rslt instanceof EntryRep) {
453 		// Got an answer, return it
454 		return entryFrom((EntryRep)rslt);
455 	    } else if (rslt instanceof OutriggerServer.QueryCookie) {
456 		/* Will still want to go on if there is time, but pass
457 		 * the new cookie
458 		 */
459 		queryCookie = (OutriggerServer.QueryCookie)rslt;
460 	    } else {
461 		throw new AssertionError(
462                     "Unexpected return type from space.take()");
463 	    }
464 
465 	    /* Update remaining and loop, checking to see if the timeout has
466 	     * expired.
467 	     */
468 	    remaining = endTime - System.currentTimeMillis();
469 	} while (remaining > 0);
470 
471 	/* If we get here then there must not have been an entry available
472 	 * to us before the endTime.
473 	 */
474 	return null;
475     }
476 
477     // inherit doc comment, use internal routine for common code
478     public Entry takeIfExists(Entry tmpl, Transaction txn, long timeout)
479 	throws UnusableEntryException, TransactionException,
480 	       InterruptedException, RemoteException
481     {
482 	// Figure out the max time this query should last
483 	final long endTime = calcEndTime(timeout);
484     
485 	long remaining = timeout;
486 	OutriggerServer.QueryCookie queryCookie = null;
487 	
488  	// Loop util timeout or we get an answer (call at least once!)
489 	do {
490 	    final long serverTimeout = 
491 		Math.min(remaining, maxServerQueryTimeout);
492 	    logQuery("takeIfExists", serverTimeout, queryCookie, remaining);
493 
494 	    final Object rslt = 
495 		space.takeIfExists(repFor(tmpl), txn, serverTimeout, 
496 				   queryCookie);
497 	    if (rslt == null) {
498 		// Must be no matches in the space at all
499 		return null;
500 	    } else if (rslt instanceof EntryRep) {
501 		// Got an answer, return it
502 		return entryFrom((EntryRep)rslt);
503 	    } else if (rslt instanceof OutriggerServer.QueryCookie) {
504 		/* Will still want to go on if there is time, but pass
505 		 * the new cookie
506 		 */
507 		queryCookie = (OutriggerServer.QueryCookie)rslt;
508 	    } else {
509 		throw new AssertionError(
510                     "Unexpected return type from space.takeIfExists()");
511 	    }
512 
513 	    /* Update remaining and loop, checking to see if the timeout has
514 	     * expired.
515 	     */
516 	    remaining = endTime - System.currentTimeMillis();
517 	} while (remaining > 0);
518 
519 	/* If we get here then there must not have been an entry available
520 	 * to us before the endTime.
521 	 */
522 	return null;
523     }
524 
525     // inherit doc comment
526     public Entry snapshot(Entry entry) throws MarshalException {
527 	if (entry == null)
528 	    return null;
529 	else
530 	    return new SnapshotRep(entry);
531     }
532 
533     // inherit doc comment
534     public EventRegistration
535 	notify(Entry tmpl, Transaction txn, RemoteEventListener listener,
536 	       long lease, MarshalledObject handback)
537 	throws TransactionException, RemoteException
538     {
539 	return space.notify(repFor(tmpl), txn, listener, lease, handback);
540     }
541 
542     public List write(List entries, Transaction txn, List leaseDurations)
543         throws RemoteException, TransactionException 
544     {
545 	final long[] leases = new long[leaseDurations.size()];
546 	int j = 0;
547 	for (Iterator i=leaseDurations.iterator(); i.hasNext(); ) {
548 	    final Object l = i.next();
549 
550 	    if (l == null)
551 		throw new NullPointerException(
552 		    "leaseDurations contatins a null element");
553 
554 	    if (!(l instanceof Long))
555 		throw new IllegalArgumentException(
556 		    "leaseDurations contatins an element which is not a Long");
557 
558 	    leases[j++] = ((Long)l).longValue();
559 	}
560 
561 	long[] leaseData = space.write(repFor(entries, "entries"), txn, leases);
562 	if (leaseData == null)
563 	    throw new AssertionError("space.write<multiple> returned null");
564 
565 	final List rslt = new ArrayList(leaseData.length/3);
566 	try {
567 	    int m=0;
568 	    while (m<leaseData.length) {
569 		final long duration = leaseData[m++];
570 		final long high = leaseData[m++];
571 		final long low = leaseData[m++];
572 		final Uuid uuid = UuidFactory.create(high, low);
573 		rslt.add(newLease(uuid, duration));
574 	    }
575 	} catch (ArrayIndexOutOfBoundsException e) {
576 	    throw new 
577 		AssertionError("space.write<multiple> returned malformed data");
578 	}
579 
580 	return rslt;
581     }
582 
583     public Collection take(Collection tmpls, Transaction txn,
584 			   long timeout, long maxEntries)
585         throws UnusableEntriesException, TransactionException, RemoteException 
586     {
587 	// Figure out the max time this query should last
588 	final long endTime = calcEndTime(timeout);
589     
590 	long remaining = timeout;
591 	OutriggerServer.QueryCookie queryCookie = null;
592 	final EntryRep[] treps = repFor(tmpls, "tmpls");
593 
594 	final int limit;
595 	if (maxEntries < 1) {
596 	    throw new IllegalArgumentException("maxEntries must be positive");
597 	} else if (maxEntries <= Integer.MAX_VALUE) {
598 	    limit = (int)maxEntries;
599 	} else {
600 	    limit = Integer.MAX_VALUE; // ok to return fewer than requested
601 	}
602 	
603  	// Loop util timeout or we get an answer (call at least once!)
604 	do {
605 	    final long serverTimeout = 
606 		Math.min(remaining, maxServerQueryTimeout);
607 	    logQuery("take(multiple)", serverTimeout, queryCookie, remaining);
608 	
609 
610 	    final Object rslt = 
611 		space.take(treps, txn, serverTimeout, limit, queryCookie);
612 	    if (rslt == null) {
613 		// should never get null from a non-ifExists query
614 		throw new AssertionError("space.take<multiple>() returned null");
615 	    } else if (rslt instanceof EntryRep[]) {
616 		EntryRep[] reps = (EntryRep[])rslt;
617 		// Got an answer, return it
618 		final Collection entries = new LinkedList();
619 		Collection exceptions = null;
620 		
621 		for (int i=0,l=reps.length; i<l; i++) {
622 		    try {
623 			entries.add(entryFrom(reps[i]));
624 		    } catch (UnusableEntryException e) {
625 			if (exceptions == null)
626 			    exceptions = new LinkedList();
627 
628 			exceptions.add(e);
629 		    }
630 		}
631 
632 		if (exceptions == null) {
633 		    return entries;
634 		} else {
635 		    throw new UnusableEntriesException(
636                         "some of the removed entries could not be unmarshalled", 
637 			entries, exceptions);
638 		}			
639 	    } else if (rslt instanceof OutriggerServer.QueryCookie) {
640 		/* Will still want to go on if there is time, but pass
641 		 * the new cookie
642 		 */
643 		queryCookie = (OutriggerServer.QueryCookie)rslt;
644 	    } else {
645 		throw new AssertionError(
646                     "Unexpected return type from space.take<multiple>()");
647 	    }
648 
649 	    /* Update remaining and loop, checking to see if the timeout has
650 	     * expired.
651 	     */
652 	    remaining = endTime - System.currentTimeMillis();
653 	} while (remaining > 0);
654 
655 	/* If we get here then there must not have been any entries available
656 	 * to us before the endTime.
657 	 */
658 	return Collections.EMPTY_LIST;
659     }
660 
661     public EventRegistration 
662 	registerForAvailabilityEvent(Collection tmpls,
663 				     Transaction txn, 
664 				     boolean visibilityOnly,
665 				     RemoteEventListener listener,
666 				     long leaseDuration, 
667 				     MarshalledObject handback)
668         throws TransactionException, RemoteException
669     {
670 	return space.registerForAvailabilityEvent(
671 	    repFor(tmpls, "tmpls"), txn, visibilityOnly, listener,
672 	    leaseDuration, handback);
673     }
674 
675 
676     // inherit doc comment
677     public MatchSet contents(Collection tmpls,		      
678 			     Transaction txn,
679 			     long leaseDuration,
680 			     long maxEntries)
681 	throws RemoteException, TransactionException
682     {
683 	final MatchSetData msd = 
684 	    space.contents(repFor(tmpls, "tmpls"), txn, leaseDuration, maxEntries);
685 	return new MatchSetProxy(msd, this, space);
686     }
687 
688     /* We break up lease creation into two methods. newLease takes
689      * care of converting from duration to expiration and we should
690      * never need to override (so we make it final), while
691      * constructLease takes care of invoking the right constructor for
692      * the given context (e.g. constrainable v. not) 
693      */
694 
695     /** Create a new lease with the specified id and initial duration
696      * @param uuid lease id
697      * @param duration lease duration in milliseconds, note actual lease duration
698      *			granted may be less.
699      * @return  new LandlordLease.
700      */
701     final protected Lease newLease(Uuid uuid, long duration) {
702 	long expiration = duration + System.currentTimeMillis();
703 
704 	// We added two positive numbers, so if the result is negative
705 	// we must have overflowed, so use Long.MAX_VALUE
706 	if (expiration < 0)
707 	    expiration = Long.MAX_VALUE;
708 	return constructLease(uuid, expiration);
709     }
710 
711     /** Create a new lease with the specified id and initial expiration
712      * @param uuid lease id.
713      * @param expiration time lease expires, in milliseconds since epoch.
714      * @return new LandlordLease.
715      */
716     protected Lease constructLease(Uuid uuid, long expiration) {
717 	return new LandlordLease(uuid, space, spaceUuid, expiration);
718     }
719 
720     // --------------------------------------------------
721     //          Administrable methods implementation
722     // --------------------------------------------------
723     // inherit doc comment
724     public Object getAdmin() throws RemoteException {
725 	return space.getAdmin();
726     }
727 
728     // --------------------------------------------------
729     //		Private implementation
730     // --------------------------------------------------
731 
732     /**
733      * Utility method to calculate the absolute end time of a query.
734      * @param timeout relative timeout of query.
735      * @return timeout plus the current time, or 
736      *         <code>Long.MAX_VALUE</code> if timeout plus the current time
737      *         is larger than <code>Long.MAX_VALUE</code>.
738      */
739     private long calcEndTime(long timeout) {
740 	if (timeout < 0)
741 	    throw new IllegalArgumentException("timeout must be non-negative");
742 	final long now = System.currentTimeMillis();
743 	if (Long.MAX_VALUE - timeout <= now)
744 	    return Long.MAX_VALUE;
745 	else
746 	    return now + timeout;
747     }
748 
749     static EntryRep[] repFor(Collection entries, String argName)
750 	throws MarshalException 
751     {
752 	final EntryRep[] reps = new EntryRep[entries.size()];
753 	int j = 0;
754 	for (Iterator i=entries.iterator(); i.hasNext(); ) {
755 	    final Object e = i.next();
756 	    if (!(e == null || e instanceof Entry))
757 		throw new IllegalArgumentException(
758 		    argName + " contatins an element which is not an Entry");
759 
760 	    reps[j++] = repFor((Entry)e);
761 	}
762 
763 	return reps;
764     }
765 
766     /**
767      * Return an <code>EntryRep</code> object for the given
768      * <code>Entry</code>.  
769      */
770     static EntryRep repFor(Entry entry) throws MarshalException {
771 	if (entry == null)
772 	    return null;
773 	if (entry instanceof SnapshotRep)    // snapshots are pre-calculated
774 	    return ((SnapshotRep) entry).rep();
775 	return new EntryRep(entry);
776     }
777 
778     /**
779      * Return an entry generated from the given rep.
780      */
781     static Entry entryFrom(EntryRep rep) throws UnusableEntryException {
782 	if (rep == null)
783 	    return null;
784 	return rep.entry();
785     }
786 
787     /** Log query call to server */
788     private void logQuery(String op, long serverTimeout, 
789 			  OutriggerServer.QueryCookie cookie, long remaining) 
790     {
791 	if (logger.isLoggable(Level.FINER)) {
792 	    logger.log(Level.FINER, "Outrigger calling {0} on server with " +
793 		"timeout of {1} ms for serverTimeout, using QueryCookie " +
794 		"{2}, {3} ms remaining on query",
795 		new Object[] {op, Long.valueOf(serverTimeout), cookie,
796 			      Long.valueOf(remaining)});
797 	    }
798     }
799 }