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.ByteArrayOutputStream;
21  import java.io.DataOutputStream;
22  import java.io.IOException;
23  import java.io.InvalidObjectException;
24  import java.io.ObjectInput;
25  import java.io.ObjectInputStream;
26  import java.io.ObjectOutputStream;
27  import java.io.Serializable;
28  import java.lang.reflect.Constructor;
29  import java.lang.reflect.Field;
30  import java.lang.reflect.Modifier;
31  import java.net.MalformedURLException;
32  import java.rmi.MarshalException;
33  import java.rmi.UnmarshalException;
34  import java.security.DigestOutputStream;
35  import java.security.MessageDigest;
36  import java.util.ArrayList;
37  import java.util.Arrays;
38  import java.util.Comparator;
39  import java.util.WeakHashMap;
40  import java.util.logging.Logger;
41  import net.jini.core.entry.Entry;
42  import net.jini.core.entry.UnusableEntryException;
43  import net.jini.id.Uuid;
44  import net.jini.id.UuidFactory;
45  import net.jini.io.MarshalledInstance;
46  import net.jini.space.JavaSpace;
47  import org.apache.river.api.io.AtomicSerial;
48  import org.apache.river.api.io.AtomicSerial.GetArg;
49  import org.apache.river.api.io.AtomicSerial.ReadInput;
50  import org.apache.river.api.io.AtomicSerial.ReadObject;
51  import org.apache.river.landlord.LeasedResource;
52  import org.apache.river.logging.Levels;
53  import org.apache.river.proxy.CodebaseProvider;
54  import org.apache.river.proxy.MarshalledWrapper;
55  
56  /**
57   * An <code>EntryRep</code> object contains a packaged
58   * <code>Entry</code> object for communication between the client and a
59   * <code>JavaSpace</code>.
60   *
61   * @author Sun Microsystems, Inc.
62   *
63   * @see JavaSpace
64   * @see Entry
65   */
66  @AtomicSerial
67  public class EntryRep implements StorableResource<EntryRep>, LeasedResource, Serializable {
68      static final long serialVersionUID = 3L;
69  
70      // Synchronization isn't used where volatile access would be atomic.  
71      // External operations should synchronize if atomicicity is required for 
72      // multiple operations.
73      // Synchronization is used where multiple fields are accessed or one field
74      // is accessed more than once to ensure atomicity.
75      /**
76       * The fields of the entry in marshalled form. Use <code>null</code>
77       * for <code>null</code> fields.
78       */
79      private volatile MarshalledInstance[] values;
80  
81      private volatile String[]	superclasses;	// class names of the superclasses
82      private volatile long[]	hashes;		// superclass hashes
83      private volatile long	hash;		// hash for the entry class
84      private volatile String	className;	// the class ID of the entry
85      private volatile String	codebase;	// the codebase for this entry class
86      private volatile Uuid	id;		// space-relative storage id
87      private volatile transient long	expires;// expiration time
88  
89      /** 
90       * <code>true</code> if the last time this object was unmarshalled 
91       * integrity was being enforced, <code>false</code> otherwise.
92       */
93      private volatile transient boolean integrity;
94  
95      /** Comparator for sorting fields */
96      private static final FieldComparator comparator = new FieldComparator();
97  
98      /**
99       * This object represents the passing of a <code>null</code>
100      * parameter as a template, which is designed to match any entry.
101      * When a <code>null</code> is passed, it is replaced with this
102      * rep, which is then handled specially in a few relevant places.  
103      */
104     private static final EntryRep matchAnyRep;
105 
106     static {
107         classHashes = new WeakHashMap<Class,Long>();
108 	try {
109 	    matchAnyRep = new EntryRep(new Entry() {
110 		// keeps tests happy
111 		static final long serialVersionUID = -4244768995726274609L;
112 	    }, false);
113 	} catch (MarshalException e) {
114 	    throw new AssertionError(e);
115 	}
116     }
117 
118     /**
119      * The realClass object is transient because we neither need nor want
120      * it reconstituted on the other side.  All we want is to be able to
121      * recreate it on the receiving client side.  If it were not transient,
122      * not only would an unnecessary object creation occur, but it might
123      * force the download of the actual class to the server.
124      */
125     private volatile transient Class realClass;	// real class of the contained object
126 
127     /** 
128      * Logger for logging information about operations carried out in
129      * the client. Note, we hard code "org.apache.river.outrigger" so
130      * we don't drag in OutriggerServerImpl to outrigger-dl.jar.
131      */
132     private static final Logger logger = 
133 	Logger.getLogger("org.apache.river.outrigger.proxy");
134 
135     /**
136      * Set this entry's generic data to be shared with the <code>other</code>
137      * object.  Those fields that are object references that will be the same
138      * for all objects of the same type are shared this way.
139      * <p>
140      * Note that <code>codebase</code> is <em>not</em> shared.  If it were,
141      * then the failure of one codebase could make all entries inaccessible.
142      * Each entry is usable insofar as the codebase under which it was
143      * written is usable.
144      * @param other object to share this entry's generic data with.
145      */
146     public synchronized void shareWith(EntryRep other) {
147 	className = other.className;
148 	superclasses = other.superclasses;
149 	hashes = other.hashes;
150 	hash = other.hash;
151     }
152 
153     /**
154      * Get the entry fields associated with the passed class and put
155      * them in a canonical order. The fields are sorted so that fields
156      * belonging to a superclasses are before fields belonging to
157      * subclasses and within a class fields are ordered
158      * lexicographically by their name.
159      */
160     static private Field[] getFields(Class cl) {
161 	final Field[] fields = cl.getFields();
162 	Arrays.sort(fields, comparator);
163 	return fields;
164     }
165 
166     /**
167      * Cached hash values for all classes we encounter. Weak hash used
168      * in case the class is GC'ed from the client's VM.
169      */
170     static final private WeakHashMap<Class,Long> classHashes;
171 
172     /**
173      * Lookup the hash value for the given class. If it is not
174      * found in the cache, generate the hash for the class and
175      * save it.
176      */
177     static synchronized private Long findHash(Class clazz, 
178 					      boolean marshaling) 
179 	throws MarshalException, UnusableEntryException
180     {
181 
182 	Long hash = classHashes.get(clazz);
183 
184 	// If hash not cached, calculate it for this class and,
185 	// recursively, all superclasses
186 	//
187 	if (hash == null) {
188 	    try {
189 		Field[] fields = getFields(clazz);
190 		MessageDigest md = MessageDigest.getInstance("SHA");
191 		DataOutputStream out =
192 		    new DataOutputStream(
193 			new DigestOutputStream(new ByteArrayOutputStream(127),
194 					       md));
195 		Class c = clazz.getSuperclass();
196 		if (c != Object.class)
197 		    // recursive call
198 		    out.writeLong(findHash(c, marshaling).longValue()); 
199 
200 		// Hash only usable fields, this means that we do not
201 		// detect changes in non-usable fields. This should be ok
202 		// since those fields do not move between space and client.
203 		// 
204 		for (int i = 0; i < fields.length; i++) {
205 		    if (!usableField(fields[i]))
206 			continue;
207 		    out.writeUTF(fields[i].getName());
208 		    out.writeUTF(fields[i].getType().getName());
209 		}
210 		out.flush();
211 		byte[] digest = md.digest();
212 		long h = 0;
213 		for (int i = Math.min(8, digest.length); --i >= 0; ) {
214 		    h += ((long)(digest[i] & 0xFF)) << (i * 8);
215 		}
216 		hash = Long.valueOf(h);
217 	    } catch (Exception e) {
218 		if (marshaling)
219 		    throw throwNewMarshalException(
220 		       "Exception calculating entry class hash for " +
221 		       clazz, e);
222 		else 
223 		    throw throwNewUnusableEntryException(
224 		       "Exception calculating entry class hash for " +
225 		       clazz, e);
226 	    }
227 	    classHashes.put(clazz, hash);
228 	}
229 	return hash;
230     }
231 
232     /**
233      * Create a serialized form of the entry.  If <code>validate</code> is
234      * <code>true</code>, basic sanity checks are done on the class to
235      * ensure that it meets the requirements to be an <code>Entry</code>.
236      * <code>validate</code> is <code>false</code> only when creating the
237      * stand-in object for "match any", which is never actually marshalled
238      * on the wire and so which doesn't need to be "proper".
239      */
240     private EntryRep(Entry entry, boolean validate) throws MarshalException {
241 	realClass = entry.getClass();
242 	if (validate)
243 	    ensureValidClass(realClass);
244 	className = realClass.getName();
245 	codebase = CodebaseProvider.getClassAnnotation(realClass);
246 
247 	/*
248 	 * Build up the per-field and superclass information through
249 	 * the reflection API.
250 	 */
251 	final Field[] fields = getFields(realClass);
252 	int numFields = fields.length;
253 
254 	// collect the usable field values in vals[0..nvals-1]
255 	MarshalledInstance[] vals = new MarshalledInstance[numFields];
256 	int nvals = 0;
257 
258 	for (int fnum = 0; fnum < fields.length; fnum++) {
259 	    final Field field = fields[fnum];
260 	    if (!usableField(field))
261 		continue;
262 		
263 	    final Object fieldValue;
264 	    try {
265 		fieldValue = field.get(entry);
266 	    } catch (IllegalAccessException e) {
267 		/* In general between using getFields() and 
268 		 * ensureValidClass this should never happen, however
269 		 * there appear to be a few screw cases and
270 		 * IllegalArgumentException seems appropriate.
271 		 */
272 		throw throwRuntime(
273 		    new IllegalArgumentException("Couldn't access field " 
274 			    + field, e)
275 		);
276 	    }
277 
278 	    if (fieldValue == null) {
279 		vals[nvals] = null;
280 	    } else {
281 		try {
282 		    vals[nvals] = new MarshalledInstance(fieldValue);
283 		} catch (IOException e) {
284 		    throw throwNewMarshalException(
285 		        "Can't marshal field " + field + " with value " +
286 			fieldValue, e);
287 		}
288 	    }
289 
290 	    nvals++;
291 	}
292 
293 	// copy the vals with the correct length
294 	MarshalledInstance [] values = new MarshalledInstance[nvals];
295 	System.arraycopy(vals, 0, values, 0, nvals);
296         this.values = values; // safe publication
297         
298 	try {
299 	    hash = findHash(realClass, true).longValue();
300 	} catch (UnusableEntryException e) {
301 	    // Will never happen when we pass true to findHash
302 	    throw new AssertionError(e);
303 	}
304 
305 	// Loop through the supertypes, making a list of all superclasses.
306 	ArrayList<String> sclasses = new ArrayList<String>();
307 	ArrayList<Long> shashes = new ArrayList<Long>();
308 	for (Class c = realClass.getSuperclass();
309 	     c != Object.class;
310 	     c = c.getSuperclass())
311 	{
312 	    try {
313 		sclasses.add(c.getName());
314 		shashes.add(findHash(c, true));
315 	    } catch (ClassCastException cce) {
316 		break;	// not Serializable
317 	    } catch (UnusableEntryException e) {
318 		// Will never happen when we pass true to findHash
319 		throw new AssertionError(e);
320 	    }
321 	}
322 	superclasses = sclasses.toArray(new String[sclasses.size()]); // safe publication.
323 	long [] hashes = new long[shashes.size()];
324 	for (int i=0; i < hashes.length; i++) {
325 	    hashes[i] = (shashes.get(i)).longValue();
326 	}
327         this.hashes = hashes; // safe publication.
328     }
329 
330     /**
331      * Create a serialized form of the entry with our object's
332      * relevant fields set.
333      */
334     public EntryRep(Entry entry) throws MarshalException {
335 	this(entry, true);
336     }
337     private static boolean checkIntegrity(GetArg arg) throws IOException {
338 	MarshalledInstance[] values = (MarshalledInstance[]) arg.get("values", null);
339 	if (values == null) throw new InvalidObjectException("null values");
340 	String[] superclasses = (String[]) arg.get("superclasses", null); // class names of the superclasses
341 	if (superclasses == null) throw new InvalidObjectException("null superclasses");
342 	long[]	hashes = (long[]) arg.get("hashes", null); // superclass hashes
343 	if (hashes == null) throw new InvalidObjectException("null hashes");
344 	if (hashes.length != superclasses.length)
345 	    throw new InvalidObjectException("hashes.length (" +
346                 hashes.length + ") does not equal  superclasses.length (" +
347 	        superclasses.length + ")");
348 	arg.get("hash", 0L); // hash for the entry class, causes IllegalArgumentException if doesn't exist
349 	String	className = (String) arg.get("className", null); // the class ID of the entry
350 	if (className == null) throw new InvalidObjectException("null className");
351 	Object	codebase = arg.get("codebase", null); // the codebase for this entry class
352 	if (codebase != null && !((codebase instanceof String))) throw 
353 		new InvalidObjectException("codebase must be an instance of string");
354 	Object	id = arg.get("id", null); // space-relative stor
355 	if (id != null && !((id instanceof Uuid))) throw 
356 		new InvalidObjectException("id must be an instance of Uuid");
357 	return ((RO) arg.getReader()).integrity;
358     }
359 
360     private EntryRep(GetArg arg, boolean integrity) throws IOException {
361 	values = (MarshalledInstance[]) arg.get("values", null);
362 	superclasses = (String[]) arg.get("superclasses", null); // class names of the superclasses
363 	hashes = (long[]) arg.get("hashes", null); // superclass hashes
364 	hash = arg.get("hash", 0L); // hash for the entry class
365 	className = (String) arg.get("className", null); // the class ID of the entry
366 	codebase = (String) arg.get("codebase", null); // the codebase for this entry class
367 	id = (Uuid) arg.get("id", null); // space-relative stor
368 	this.integrity = integrity;
369     }
370     
371     EntryRep(GetArg arg) throws IOException {
372 	this(arg, checkIntegrity(arg));
373     }
374 
375 
376     /** Used in recovery */
377     public EntryRep() { }
378 
379     @ReadInput
380     private static ReadObject getRO() {
381 	return new RO();
382     }
383     
384     private static class RO implements ReadObject {
385 
386 	boolean integrity;
387 	
388 	@Override
389 	public void read(ObjectInput input) throws IOException, ClassNotFoundException {
390 	    // get value for integrity flag
391 	    integrity = MarshalledWrapper.integrityEnforced((ObjectInputStream)input);
392 	}
393     
394     }
395 
396 
397     /** Used to look up no-arg constructors.  */
398     private final static Class[] noArg = new Class[0];
399 
400     /**
401      * Ensure that the entry class is valid, that is, that it has appropriate
402      * access.  If not, throw <code>IllegalArgumentException</code>.
403      */
404     private static void ensureValidClass(Class c) {
405 	boolean ctorOK = false;
406 	try {
407 	    if (!Modifier.isPublic(c.getModifiers())) {
408 		throw throwRuntime(new IllegalArgumentException(
409 		    "entry class " + c.getName() + " not public"));
410 	    }
411 	    Constructor ctor = c.getConstructor(noArg);
412 	    ctorOK = Modifier.isPublic(ctor.getModifiers());
413 	} catch (NoSuchMethodException e) {
414 	    ctorOK = false;
415 	} catch (SecurityException e) {
416 	    ctorOK = false;
417 	}
418 	if (!ctorOK) {
419 	    throw throwRuntime(new IllegalArgumentException("entry class " +
420 		c.getName() +" needs public no-arg constructor"));
421 	}
422     }
423 
424     /**
425      * The <code>EntryRep</code> that marks a ``match any'' request.
426      * This is used to represent a <code>null</code> template.
427      * @return null object template entry.
428      */
429     public static EntryRep matchAnyEntryRep() {
430 	return matchAnyRep;
431     }
432 
433     /**
434      * Return <code>true</code> if the given rep is that ``match any''
435      * <code>EntryRep</code>.
436      */
437     private static boolean isMatchAny(EntryRep rep) {
438 	return matchAnyRep.equals(rep);
439     }
440 
441     /**
442      * @return class name that is used by the ``match any'' EntryRep
443      */
444     public static String matchAnyClassName() {
445 	return matchAnyRep.classFor();
446     }
447 
448     /**
449      * @return An <code>Entry</code> object built out of this
450      * <code>EntryRep</code> This is used by the client-side proxy to
451      * convert the <code>EntryRep</code> it gets from the space server
452      * into the actual <code>Entry</code> object it represents.
453      * @throws UnusableEntryException
454      *		    One or more fields in the entry cannot be
455      *		    deserialized, or the class for the entry type
456      *		    itself cannot be deserialized.
457      */
458     public Entry entry() throws UnusableEntryException {
459 	ObjectInputStream objIn = null;
460         String className = ""; // set before any exception can be thrown.
461 	try {
462 	    ArrayList badFields = null;
463 	    ArrayList except = null;
464             Entry entryObj = null;
465             int valuesLength = 0;
466             int nvals = 0;		// index into this.values[]
467                       
468             synchronized (this){
469                 className = this.className;
470                 realClass = CodebaseProvider.loadClass(codebase, className,
471                                                    null, integrity, null);
472 
473                 if (findHash(realClass, false).longValue() != hash)
474                     throw throwNewUnusableEntryException(
475                         new IncompatibleClassChangeError(realClass + " changed"));
476 
477                 entryObj = (Entry) realClass.newInstance();
478 
479                 Field[] fields = getFields(realClass);
480 
481                 /*
482                  * Loop through the fields, ensuring no primitives and
483                  * checking for wildcards.
484                  */
485 
486                 int fLength = fields.length;
487                 valuesLength = values.length;
488                 for (int i = 0; i < fLength; i++) {
489                     Throwable nested = null;
490                     try {
491                         if (!usableField(fields[i]))
492                             continue;
493 
494                         final MarshalledInstance val = values[nvals++];
495                         Object value = (val == null ? null : val.get(integrity));
496                         fields[i].set(entryObj, value);
497                     } catch (Throwable e) {
498                         nested = e;
499                     }
500 
501                     if (nested != null) {	// some problem occurred
502                         if (badFields == null) {
503                             badFields = new ArrayList(fLength);
504                             except = new ArrayList(fLength);
505                         }
506                         badFields.add(fields[i].getName());
507                         except.add(nested);
508                     }
509                 }
510             }
511 
512 	    /* See if any fields have vanished from the class, 
513 	     * because of the hashing this should never happen but
514 	     * throwing an exception that provides more info
515 	     * (instead of AssertionError) seems harmless.
516 	     */
517 	    if (nvals < valuesLength) {
518 		throw throwNewUnusableEntryException(
519 			entryObj,		// should this be null?
520 			null,			// array of bad-field names
521 			new Throwable[] {	// array of exceptions
522 			    new IncompatibleClassChangeError(
523 				    "A usable field has been removed from " +
524 				    entryObj.getClass().getName() +
525 				    " since this EntryRep was created")
526 			});
527 	    }
528             
529 	    // if there were any bad fields, throw the exception
530 	    if (badFields != null) {
531 		String[] bf =
532 		    (String[]) badFields.toArray(
533 			new String[badFields.size()]);
534 		Throwable[] ex =
535 		    (Throwable[]) except.toArray(new Throwable[bf.length]);
536 		throw throwNewUnusableEntryException(entryObj, bf, ex);
537 	    }
538 
539 	    // everything fine, return the entry
540 	    return entryObj;
541 	} catch (InstantiationException e) {
542 	    /*
543 	     * If this happens outside a per-field deserialization then
544 	     * this is a complete failure  The per-field ones are caught
545 	     * inside the per-field loop.
546 	     */
547 	    throw throwNewUnusableEntryException(e);
548 	} catch (ClassNotFoundException e) {
549 	    // see above
550 	    throw throwNewUnusableEntryException("Encountered a " +
551 		"ClassNotFoundException while unmarshalling " + className, e);
552 	} catch (IllegalAccessException e) {
553 	    // see above
554 	    throw throwNewUnusableEntryException(e);
555 	} catch (RuntimeException e) {
556 	    // see above
557 	    throw throwNewUnusableEntryException("Encountered a " +
558 		"RuntimeException while unmarshalling " + className, e);
559 	} catch (MalformedURLException e) {
560 	    // see above
561 	    throw throwNewUnusableEntryException("Malformed URL " +
562 		"associated with entry of type " + className, e);
563 	} catch (MarshalException e) {
564 	    // because we call findHash() w/ false, should never happen
565 	    throw new AssertionError(e);
566 	}
567     }
568 
569     // inherit doc comment
570     @Override
571     public int hashCode() {
572 	return className.hashCode();
573     }
574 
575     /**
576      * To be equal, the other object must by an <code>EntryRep</code> for
577      * an object of the same class with the same values for each field.
578      * This is <em>not</em> a template match -- see <code>matches</code>.
579      *
580      * @see #matches
581      */
582     @Override
583     public boolean equals(Object o) {
584 	// The other passed in was null--obviously not equal
585 	if (o == null)
586 	    return false;
587 
588 	// The other passed in was ME--obviously I'm the same as me...
589 	if (this == o)
590 	    return true;
591 
592 	if (!(o instanceof EntryRep))
593 	    return false;
594 
595 	EntryRep other = (EntryRep) o;
596 
597         synchronized (this){
598             // If we're not the same class then we can't be equal
599             if (hash != other.hash)
600                 return false;
601 
602             /* Paranoid checkIntegrity just to make sure we can't get an
603              * IndexOutOfBoundsException. Should never happen.
604              */
605             if (values.length != other.values.length)
606                 return false;
607 
608             /* OPTIMIZATION:
609              * If we have a case where one element is null and the corresponding
610              * element within the object we're comparing ourselves with is
611              * non-null (or vice-versa), we can stop right here and declare the
612              * two objects to be unequal. This is slightly faster than checking 
613              * the bytes themselves.
614              * LOGIC: They've both got to be null or both have got to be
615              *        non-null or we're out-of-here...
616              */
617             for (int i = 0; i < values.length; i++) {
618                 if ((values[i] == null) && (other.values[i] != null))
619                     return false;
620                 if ((values[i] != null) && (other.values[i] == null))
621                     return false;
622             }
623 
624             /* The most expensive tests we save for last.
625              * Because we've made the null/non-null checkIntegrity above, we can
626              * simplify our comparison here: if our element is non-null,
627              * we know the other value is non-null, too.
628              * If any equals() calls from these element comparisons come
629              * back false then return false. If they all succeed, we fall
630              * through and return true (they were equal).
631              */
632             for (int i = 0; i < values.length; i++) {
633                 // Short-circuit evaluation if null, compare otherwise.
634                 if (values[i] != null && !values[i].equals(other.values[i]))
635                     return false;
636             }
637         }
638 
639 	return true;
640     }
641 
642     /**
643      * Return <code>true</code> if the field is to be used for the
644      * entry.  That is, return <code>true</code> if the field isn't
645      * <code>transient</code>, <code>static</code>, or <code>final</code>.
646      * @throws IllegalArgumentException
647      *			The field is not <code>transient</code>,
648      *			<code>static</code>, or <code>final</code>, but
649      *			is primitive and hence not a proper field for
650      *			an <code>Entry</code>.
651      */
652     static private boolean usableField(Field field) {
653 	// ignore anything that isn't a public non-static mutable field
654 	final int ignoreMods =
655 	    (Modifier.TRANSIENT | Modifier.STATIC | Modifier.FINAL);
656 
657 	if ((field.getModifiers() & ignoreMods) != 0)
658 	    return false;
659 
660 	// if it isn't ignorable, it has to be an object of some kind
661 	if (field.getType().isPrimitive()) {
662 	    throw throwRuntime(new IllegalArgumentException(
663 		"primitive field, " + field + ", not allowed in an Entry"));
664 	}
665 
666 	return true;
667     }
668 
669     /**
670      * @return the ID.
671      */
672     public Uuid id() {
673 	return id;
674     }
675 
676     /**
677      * Pick a random <code>Uuid</code> and set our id field to it.
678      * @throws IllegalStateException if this method has already
679      *         been called.
680      */
681     public void pickID() {
682         synchronized (this){
683             if (id != null)
684                 throw new IllegalStateException("pickID called more than once");
685             id = UuidFactory.generate();
686         }
687     }
688 
689     /**
690      * @return the <code>MarshalledObject</code> for the given field.
691      */
692     public MarshalledInstance value(int fieldNum) {
693             return values[fieldNum];
694     }
695 
696     /**
697      * @return the number of fields in this kind of entry.
698      */
699     public int numFields() {
700         synchronized (this){
701             if (values != null) return values.length;
702         }
703 	return 0;
704     }
705 
706     /**
707      * @return the class name for this entry.
708      */
709     public String classFor() {
710 	return className;
711     }
712 
713     /**
714      * @return the array names of superclasses of this entry type.
715      */
716     public String[] superclasses() {
717 	return superclasses != null ? superclasses.clone() : new String [0];
718     }
719 
720     /**
721      * @return the hash of this entry type.
722      */
723     public long getHash() {
724 	return hash;
725     }
726 
727     /**
728      * @return the array of superclass hashes of this entry type.
729      */
730     public long[] getHashes() {
731 	return hashes != null ? hashes.clone() : new long[0];
732     }
733 
734     /**
735      * See if the other object matches the template object this
736      * represents.  (Note that even though "this" is a template, it may
737      * have no wildcards -- a template can have all values.)
738      * @param other object to check if it matches this objects template.
739      * @return true if matches the template object this EntryRep represents.
740      */
741     public boolean matches(EntryRep other) {
742 	/*
743 	 * We use the fact that this is the template in several ways in
744 	 * the method implementation.  For instance, in this next loop,
745 	 * we know that the real object must be at least my type, which
746 	 * means (a) the field types already match, and (b) it has at
747 	 * least as many fields as the this does.
748 	 */
749 
750 	//Note: If this object is the MatchAny template then 
751 	//      return true (all entries match MatchAny)
752         synchronized (this){
753             if (EntryRep.isMatchAny(this)) return true;
754         
755             for (int f = 0; f < values.length; f++) {
756                 if (values[f] == null) {		// skip wildcards
757                     continue;
758                 }
759                 if (!values[f].equals(other.values[f])) {
760                     return false;
761                 }
762             }
763         }
764 	return true;	     // no mismatches, so must be OK
765     }
766 
767     @Override
768     public String toString() {
769 	return ("EntryRep[" + className + "]");
770     }
771 
772     /**
773      * @param otherClass class name of class or interface this is the same or
774      * superclass of the object that this EntryRep represents.
775      * @return <code>true</code> if this entry represents an object that
776      * is at least the type of the <code>otherClass</code>.
777      */
778     public boolean isAtLeastA(String otherClass) {
779         if (otherClass.equals(matchAnyClassName()))
780 	    // The other is a null template, all entries are at least entry.
781 	    return true;
782         synchronized (this){
783             if (className.equals(otherClass))
784                 return true;
785             for (int i = 0; i < superclasses.length; i++)
786                 if (superclasses[i].equals(otherClass))
787                     return true;
788             return false;
789         }
790     }
791 
792     /** Comparator for sorting fields. Cribbed from Reggie */
793     private static class FieldComparator implements Comparator {
794 	public FieldComparator() {}
795 
796 	/** Super before subclass, alphabetical within a given class */
797 	public int compare(Object o1, Object o2) {
798 	    Field f1 = (Field)o1;
799 	    Field f2 = (Field)o2;
800 	    if (f1 == f2)
801 		return 0;
802 	    if (f1.getDeclaringClass() == f2.getDeclaringClass())
803 		return f1.getName().compareTo(f2.getName());
804 	    if (f1.getDeclaringClass().isAssignableFrom(
805 						     f2.getDeclaringClass()))
806 		return -1;
807 	    return 1;
808 	}
809     }
810 
811     /**
812      * Use <code>readObject</code> method to capture whether or
813      * not integrity was being enforced when this object was
814      * unmarshalled, and to perform basic integrity checks.
815      * @param in stream used to de-serialize.
816      * @throws IOException
817      * @throws ClassNotFoundException
818      */
819     private void readObject(ObjectInputStream in)
820 	throws IOException, ClassNotFoundException
821     {
822 	in.defaultReadObject();
823 	if (className == null)
824 	    throw new InvalidObjectException("null className");
825 
826 	if (values == null)
827 	    throw new InvalidObjectException("null values");
828 
829 	if (superclasses == null)
830 	    throw new InvalidObjectException("null superclasses");
831 
832 	if (hashes == null) 
833 	    throw new InvalidObjectException("null hashes");
834 
835 	if (hashes.length != superclasses.length)
836 	    throw new InvalidObjectException("hashes.length (" +
837                 hashes.length + ") does not equal  superclasses.length (" +
838 	        superclasses.length + ")");
839 
840 	// get value for integrity flag
841 	integrity = MarshalledWrapper.integrityEnforced(in);
842     }
843 
844     /** 
845      * We should always have data in the stream, if this method
846      * gets called there is something wrong.
847      * @throws InvalidObjectException
848      */
849     private void readObjectNoData() throws InvalidObjectException {
850 	throw new 
851 	    InvalidObjectException("SpaceProxy should always have data");
852     }
853 
854     /**
855      * @param out stream to write out default serial form.
856      * @throws IOException 
857      */
858     private void writeObject(ObjectOutputStream out) throws IOException {
859 	out.defaultWriteObject();
860     }
861 
862 
863     // -------------------------------------------------------
864     // Methods required by LeasedResource and StorableResource
865     // -------------------------------------------------------
866 
867     // inherit doc comment from LeasedResource
868     public void setExpiration(long newExpiration) {
869 	expires = newExpiration;
870     }
871 
872     // inherit doc comment from LeasedResource
873     public long getExpiration() {
874 	return expires;
875     }
876 
877     // inherit doc comment from LeasedResource
878     // We use the Rep ID as the cookie
879     public Uuid getCookie() {
880 	return id;
881     }
882 
883     // -------------------------------------
884     //  Methods required by StorableResource
885     // -------------------------------------
886 
887     // inherit doc comment
888     public synchronized void store(ObjectOutputStream out) throws IOException {
889 	final long bits0;
890 	final long bits1;
891 	if (id == null) {
892 	    bits0 = 0;
893 	    bits1 = 0;
894 	} else {
895 	    bits0 = id.getMostSignificantBits();
896 	    bits1 = id.getLeastSignificantBits();
897 	}
898 	out.writeLong(bits0);
899 	out.writeLong(bits1);
900 	out.writeLong(expires);
901 	out.writeObject(codebase);
902 	out.writeObject(className);
903 	out.writeObject(superclasses);
904 	out.writeObject(values);
905 	out.writeLong(hash);
906 	out.writeObject(hashes);
907     }
908 
909     // inherit doc comment
910     public synchronized EntryRep restore(ObjectInputStream in) 
911 	throws IOException, ClassNotFoundException 
912     {
913 	final long bits0 = in.readLong();
914 	final long bits1 = in.readLong();
915 	if (bits0 == 0 && bits1 == 0) {
916 	    id = null;
917 	} else {	    
918 	    id = UuidFactory.create(bits0, bits1);
919 	}
920 
921 	expires      = in.readLong();
922 	codebase     = (String)in.readObject();
923 	className    = (String)in.readObject();
924 	superclasses = (String [])in.readObject();
925 	values       = (MarshalledInstance [])in.readObject();
926 	hash	     = in.readLong();
927 	hashes       = (long[])in.readObject();
928         return this;
929     }
930 
931     // Utility methods for throwing and logging exceptions
932     /** Log and throw a runtime exception */
933     private static RuntimeException throwRuntime(RuntimeException e) {
934 	if (logger.isLoggable(Levels.FAILED)) {
935 	    logger.log(Levels.FAILED, e.getMessage(), e);
936 	}
937 
938 	throw e;
939     }
940     
941     /** Construct, log, and throw a new MarshalException */
942     private static MarshalException throwNewMarshalException(
943 	    String msg, Exception nested) 
944 	throws MarshalException
945     {
946 	final MarshalException me = new MarshalException(msg, nested);
947 	if (logger.isLoggable(Levels.FAILED)) {
948 	    logger.log(Levels.FAILED, msg, me);
949 	}
950 
951 	throw me;
952     }
953 
954     /**
955      * Construct, log, and throw a new UnusableEntryException
956      */
957     private UnusableEntryException throwNewUnusableEntryException(
958 	    Entry partial, String[] badFields, Throwable[] exceptions)
959 	throws UnusableEntryException
960     {
961 	final UnusableEntryException uee = 
962 	    new UnusableEntryException(partial, badFields, exceptions);
963 
964 	if (logger.isLoggable(Levels.FAILED)) {
965 	    logger.log(Levels.FAILED, 
966 		       "failure constructing entry of type " + className, uee);
967 	}
968 
969 	throw uee;
970     }	
971 
972     /**
973      * Construct, log, and throw a new UnusableEntryException, that
974      * wraps a given exception.
975      */
976     private static UnusableEntryException throwNewUnusableEntryException(
977             Throwable nested) 
978 	throws UnusableEntryException
979     {
980 	final UnusableEntryException uee = new UnusableEntryException(nested);
981 
982 	if (logger.isLoggable(Levels.FAILED)) {
983 	    logger.log(Levels.FAILED, nested.getMessage(), uee);
984 	}
985 
986 	throw uee;
987     }	
988     
989     /**
990      * Construct, log, and throw a new UnusableEntryException, that
991      * will rap a newly constructed UnmarshalException (that optional
992      * wraps a given exception).
993      */
994     private static UnusableEntryException throwNewUnusableEntryException(
995             String msg, Exception nested) 
996 	throws UnusableEntryException
997     {
998 	final UnmarshalException ue = new UnmarshalException(msg, nested);
999 	final UnusableEntryException uee = new UnusableEntryException(ue);
1000 
1001 	if (logger.isLoggable(Levels.FAILED)) {
1002 	    logger.log(Levels.FAILED, msg, uee);
1003 	}
1004 
1005 	throw uee;
1006     }
1007 }