Modified: river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/lease/AbstractLease.java URL: http://svn.apache.org/viewvc/river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/lease/AbstractLease.java?rev=1702386&r1=1702385&r2=1702386&view=diff ============================================================================== --- river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/lease/AbstractLease.java (original) +++ river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/lease/AbstractLease.java Fri Sep 11 07:24:54 2015 @@ -1,157 +1,156 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.river.lease; - -import java.io.IOException; -import java.io.InvalidObjectException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.rmi.RemoteException; -import net.jini.core.lease.Lease; -import net.jini.core.lease.LeaseDeniedException; -import net.jini.core.lease.UnknownLeaseException; -import net.jini.id.Uuid; -import org.apache.river.api.util.ID; - -/** - * A base class for implementing lease objects. This class takes care of - * absolute vs relative time issues and implements some of the Lease methods. - * The subclass is responsible for implementing: doRenew, cancel, - * createLeaseMap, canBatch, hashCode, equals, and serialization of - * any subclass state. - * - * @author Sun Microsystems, Inc. - * - */ -public abstract class AbstractLease implements Lease, java.io.Serializable { - - private static final long serialVersionUID = -9067179156916102052L; - - /** - * The lease expiration, in local absolute time. - * - * This field has been made volatile to ensure visibility, since - * synchronized access isn't guaranteed to be performed - * by overriding classes. - */ - protected volatile transient long expiration; - /** - * Serialization format for the expiration. - * - * @serial - */ - protected volatile int serialFormat = Lease.DURATION; - - /** Construct a relative-format lease. */ - protected AbstractLease(long expiration) { - this.expiration = expiration; - } - - /** Return the lease expiration. */ - public long getExpiration() { - return expiration; - } - - /** Return the serialization format for the expiration. */ - public int getSerialFormat() { - return serialFormat; - } - - /** Set the serialization format for the expiration. */ - public void setSerialFormat(int format) { - if (format != Lease.DURATION && format != Lease.ABSOLUTE) - throw new IllegalArgumentException("invalid serial format"); - serialFormat = format; - } - - /** Renew the lease for a duration relative to now. */ - public void renew(long duration) - throws UnknownLeaseException, LeaseDeniedException, RemoteException - { - long exp = doRenew(duration) + System.currentTimeMillis(); - // We added two positive numbers, so if the result is negative - // we must have overflowed, so use Long.MAX_VALUE - if (exp < 0) exp = Long.MAX_VALUE; - expiration = exp; - } - - /** - * Renew the lease for a duration relative to now, and return - * the duration actually granted. - */ - protected abstract long doRenew(long duration) - throws UnknownLeaseException, LeaseDeniedException, RemoteException; - - /** - * @serialData a long, which is the absolute expiration if serialFormat - * is ABSOLUTE, or the relative duration if serialFormat is DURATION - */ - private void writeObject(ObjectOutputStream stream) throws IOException { - int format; - long val; - format = serialFormat; - val = expiration; - if (format == Lease.DURATION) { - long exp = val; - val -= System.currentTimeMillis(); - // If we subtract positive from negative, and the result is - // positive, we must have underflowed, so use Long.MIN_VALUE - if (exp < 0 && val > 0) - val = Long.MIN_VALUE; - } - stream.putFields().put("serialFormat", format); - stream.writeFields(); - stream.writeLong(val); - } - - /** - * Throws an <code>InvalidObjectException</code>. - * - * @throws InvalidObjectException unconditionally - */ - private synchronized void readObjectNoData() throws InvalidObjectException { - throw new InvalidObjectException("no data in stream"); - } - - /** - * If serialFormat is DURATION, add the current time to the expiration, - * to make it absolute (and if the result of the addition is negative, - * correct the overflow by resetting the expiration to Long.MAX_VALUE). - * - * @throws InvalidObjectException if serialFormat is neither ABSOLUTE - * nor DURATION - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException - { - stream.defaultReadObject(); - long val = stream.readLong(); - if (serialFormat == Lease.DURATION) { - long dur = val; - val += System.currentTimeMillis(); - // If we add two positive numbers, and the result is negative, - // we must have overflowed, so use Long.MAX_VALUE - if (val < 0 && dur > 0) - val = Long.MAX_VALUE; - } else if (serialFormat != Lease.ABSOLUTE) { - throw new InvalidObjectException("invalid serial format"); - } - expiration = val; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.river.lease; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.rmi.RemoteException; +import net.jini.core.lease.Lease; +import net.jini.core.lease.LeaseDeniedException; +import net.jini.core.lease.UnknownLeaseException; +import net.jini.id.Uuid; + +/** + * A base class for implementing lease objects. This class takes care of + * absolute vs relative time issues and implements some of the Lease methods. + * The subclass is responsible for implementing: doRenew, cancel, + * createLeaseMap, canBatch, hashCode, equals, and serialization of + * any subclass state. + * + * @author Sun Microsystems, Inc. + * + */ +public abstract class AbstractLease implements Lease, java.io.Serializable { + + private static final long serialVersionUID = -9067179156916102052L; + + /** + * The lease expiration, in local absolute time. + * + * This field has been made volatile to ensure visibility, since + * synchronized access isn't guaranteed to be performed + * by overriding classes. + */ + protected volatile transient long expiration; + /** + * Serialization format for the expiration. + * + * @serial + */ + protected volatile int serialFormat = Lease.DURATION; + + /** Construct a relative-format lease. */ + protected AbstractLease(long expiration) { + this.expiration = expiration; + } + + /** Return the lease expiration. */ + public long getExpiration() { + return expiration; + } + + /** Return the serialization format for the expiration. */ + public int getSerialFormat() { + return serialFormat; + } + + /** Set the serialization format for the expiration. */ + public void setSerialFormat(int format) { + if (format != Lease.DURATION && format != Lease.ABSOLUTE) + throw new IllegalArgumentException("invalid serial format"); + serialFormat = format; + } + + /** Renew the lease for a duration relative to now. */ + public void renew(long duration) + throws UnknownLeaseException, LeaseDeniedException, RemoteException + { + long exp = doRenew(duration) + System.currentTimeMillis(); + // We added two positive numbers, so if the result is negative + // we must have overflowed, so use Long.MAX_VALUE + if (exp < 0) exp = Long.MAX_VALUE; + expiration = exp; + } + + /** + * Renew the lease for a duration relative to now, and return + * the duration actually granted. + */ + protected abstract long doRenew(long duration) + throws UnknownLeaseException, LeaseDeniedException, RemoteException; + + /** + * @serialData a long, which is the absolute expiration if serialFormat + * is ABSOLUTE, or the relative duration if serialFormat is DURATION + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + int format; + long val; + format = serialFormat; + val = expiration; + if (format == Lease.DURATION) { + long exp = val; + val -= System.currentTimeMillis(); + // If we subtract positive from negative, and the result is + // positive, we must have underflowed, so use Long.MIN_VALUE + if (exp < 0 && val > 0) + val = Long.MIN_VALUE; + } + stream.putFields().put("serialFormat", format); + stream.writeFields(); + stream.writeLong(val); + } + + /** + * Throws an <code>InvalidObjectException</code>. + * + * @throws InvalidObjectException unconditionally + */ + private synchronized void readObjectNoData() throws InvalidObjectException { + throw new InvalidObjectException("no data in stream"); + } + + /** + * If serialFormat is DURATION, add the current time to the expiration, + * to make it absolute (and if the result of the addition is negative, + * correct the overflow by resetting the expiration to Long.MAX_VALUE). + * + * @throws InvalidObjectException if serialFormat is neither ABSOLUTE + * nor DURATION + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + stream.defaultReadObject(); + long val = stream.readLong(); + if (serialFormat == Lease.DURATION) { + long dur = val; + val += System.currentTimeMillis(); + // If we add two positive numbers, and the result is negative, + // we must have overflowed, so use Long.MAX_VALUE + if (val < 0 && dur > 0) + val = Long.MAX_VALUE; + } else if (serialFormat != Lease.ABSOLUTE) { + throw new InvalidObjectException("invalid serial format"); + } + expiration = val; + } +}
Copied: river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/lease/ID.java (from r1701523, river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/api/util/ID.java) URL: http://svn.apache.org/viewvc/river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/lease/ID.java?p2=river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/lease/ID.java&p1=river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/api/util/ID.java&r1=1701523&r2=1702386&rev=1702386&view=diff ============================================================================== --- river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/api/util/ID.java (original) +++ river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/lease/ID.java Fri Sep 11 07:24:54 2015 @@ -15,7 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.river.api.util; +package org.apache.river.lease; /** * A mix in interface that provides an identity to be used as a key in Collections. Modified: river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/outrigger/EntryRep.java URL: http://svn.apache.org/viewvc/river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/outrigger/EntryRep.java?rev=1702386&r1=1702385&r2=1702386&view=diff ============================================================================== --- river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/outrigger/EntryRep.java (original) +++ river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/outrigger/EntryRep.java Fri Sep 11 07:24:54 2015 @@ -1,928 +1,926 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.river.outrigger; - -import org.apache.river.landlord.LeasedResource; -import org.apache.river.logging.Levels; -import org.apache.river.proxy.MarshalledWrapper; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.InvalidObjectException; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.net.MalformedURLException; -import java.rmi.MarshalException; -import java.rmi.UnmarshalException; -import java.security.DigestOutputStream; -import java.security.MessageDigest; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.WeakHashMap; -import java.util.logging.Logger; - -import net.jini.core.entry.Entry; -import net.jini.core.entry.UnusableEntryException; -import net.jini.id.Uuid; -import net.jini.id.UuidFactory; -import net.jini.io.MarshalledInstance; -import net.jini.loader.ClassLoading; -import net.jini.space.JavaSpace; - -/** - * An <code>EntryRep</code> object contains a packaged - * <code>Entry</code> object for communication between the client and a - * <code>JavaSpace</code>. - * - * @author Sun Microsystems, Inc. - * - * @see JavaSpace - * @see Entry - */ -class EntryRep implements StorableResource<EntryRep>, LeasedResource, Serializable { - static final long serialVersionUID = 3L; - - // Synchronization isn't used where volatile access would be atomic. - // External operations should synchronize if atomicicity is required for - // multiple operations. - // Synchronization is used where multiple fields are accessed or one field - // is accessed more than once to ensure atomicity. - /** - * The fields of the entry in marshalled form. Use <code>null</code> - * for <code>null</code> fields. - */ - private volatile MarshalledInstance[] values; - - private volatile String[] superclasses; // class names of the superclasses - private volatile long[] hashes; // superclass hashes - private volatile long hash; // hash for the entry class - private volatile String className; // the class ID of the entry - private volatile String codebase; // the codebase for this entry class - private volatile Uuid id; // space-relative storage id - private volatile transient long expires;// expiration time - - /** - * <code>true</code> if the last time this object was unmarshalled - * integrity was being enforced, <code>false</code> otherwise. - */ - private volatile transient boolean integrity; - - /** Comparator for sorting fields */ - private static final FieldComparator comparator = new FieldComparator(); - - /** - * This object represents the passing of a <code>null</code> - * parameter as a template, which is designed to match any entry. - * When a <code>null</code> is passed, it is replaced with this - * rep, which is then handled specially in a few relevant places. - */ - private static final EntryRep matchAnyRep; - - static { - classHashes = new WeakHashMap<Class,Long>(); - try { - matchAnyRep = new EntryRep(new Entry() { - // keeps tests happy - static final long serialVersionUID = -4244768995726274609L; - }, false); - } catch (MarshalException e) { - throw new AssertionError(e); - } - } - - /** - * The realClass object is transient because we neither need nor want - * it reconstituted on the other side. All we want is to be able to - * recreate it on the receiving client side. If it were not transient, - * not only would an unnecessary object creation occur, but it might - * force the download of the actual class to the server. - */ - private volatile transient Class realClass; // real class of the contained object - - /** - * Logger for logging information about operations carried out in - * the client. Note, we hard code "org.apache.river.outrigger" so - * we don't drag in OutriggerServerImpl to outrigger-dl.jar. - */ - private static final Logger logger = - Logger.getLogger("org.apache.river.outrigger.proxy"); - - /** - * Set this entry's generic data to be shared with the <code>other</code> - * object. Those fields that are object references that will be the same - * for all objects of the same type are shared this way. - * <p> - * Note that <code>codebase</code> is <em>not</em> shared. If it were, - * then the failure of one codebase could make all entries inaccessible. - * Each entry is usable insofar as the codebase under which it was - * written is usable. - */ - synchronized void shareWith(EntryRep other) { - className = other.className; - superclasses = other.superclasses; - hashes = other.hashes; - hash = other.hash; - } - - /** - * Get the entry fields associated with the passed class and put - * them in a canonical order. The fields are sorted so that fields - * belonging to a superclasses are before fields belonging to - * subclasses and within a class fields are ordered - * lexicographically by their name. - */ - static private Field[] getFields(Class cl) { - final Field[] fields = cl.getFields(); - Arrays.sort(fields, comparator); - return fields; - } - - /** - * Cached hash values for all classes we encounter. Weak hash used - * in case the class is GC'ed from the client's VM. - */ - static final private WeakHashMap<Class,Long> classHashes; - - /** - * Lookup the hash value for the given class. If it is not - * found in the cache, generate the hash for the class and - * save it. - */ - static synchronized private Long findHash(Class clazz, - boolean marshaling) - throws MarshalException, UnusableEntryException - { - - Long hash = classHashes.get(clazz); - - // If hash not cached, calculate it for this class and, - // recursively, all superclasses - // - if (hash == null) { - try { - Field[] fields = getFields(clazz); - MessageDigest md = MessageDigest.getInstance("SHA"); - DataOutputStream out = - new DataOutputStream( - new DigestOutputStream(new ByteArrayOutputStream(127), - md)); - Class c = clazz.getSuperclass(); - if (c != Object.class) - // recursive call - out.writeLong(findHash(c, marshaling).longValue()); - - // Hash only usable fields, this means that we do not - // detect changes in non-usable fields. This should be ok - // since those fields do not move between space and client. - // - for (int i = 0; i < fields.length; i++) { - if (!usableField(fields[i])) - continue; - out.writeUTF(fields[i].getName()); - out.writeUTF(fields[i].getType().getName()); - } - out.flush(); - byte[] digest = md.digest(); - long h = 0; - for (int i = Math.min(8, digest.length); --i >= 0; ) { - h += ((long)(digest[i] & 0xFF)) << (i * 8); - } - hash = Long.valueOf(h); - } catch (Exception e) { - if (marshaling) - throw throwNewMarshalException( - "Exception calculating entry class hash for " + - clazz, e); - else - throw throwNewUnusableEntryException( - "Exception calculating entry class hash for " + - clazz, e); - } - classHashes.put(clazz, hash); - } - return hash; - } - - /** - * Create a serialized form of the entry. If <code>validate</code> is - * <code>true</code>, basic sanity checks are done on the class to - * ensure that it meets the requirements to be an <code>Entry</code>. - * <code>validate</code> is <code>false</code> only when creating the - * stand-in object for "match any", which is never actually marshalled - * on the wire and so which doesn't need to be "proper". - */ - private EntryRep(Entry entry, boolean validate) throws MarshalException { - realClass = entry.getClass(); - if (validate) - ensureValidClass(realClass); - className = realClass.getName(); - codebase = ClassLoading.getClassAnnotation(realClass); - - /* - * Build up the per-field and superclass information through - * the reflection API. - */ - final Field[] fields = getFields(realClass); - int numFields = fields.length; - - // collect the usable field values in vals[0..nvals-1] - MarshalledInstance[] vals = new MarshalledInstance[numFields]; - int nvals = 0; - - for (int fnum = 0; fnum < fields.length; fnum++) { - final Field field = fields[fnum]; - if (!usableField(field)) - continue; - - final Object fieldValue; - try { - fieldValue = field.get(entry); - } catch (IllegalAccessException e) { - /* In general between using getFields() and - * ensureValidClass this should never happen, however - * there appear to be a few screw cases and - * IllegalArgumentException seems appropriate. - */ - final IllegalArgumentException iae = - new IllegalArgumentException("Couldn't access field " + - field); - iae.initCause(e); - throw throwRuntime(iae); - } - - if (fieldValue == null) { - vals[nvals] = null; - } else { - try { - vals[nvals] = new MarshalledInstance(fieldValue); - } catch (IOException e) { - throw throwNewMarshalException( - "Can't marshal field " + field + " with value " + - fieldValue, e); - } - } - - nvals++; - } - - // copy the vals with the correct length - MarshalledInstance [] values = new MarshalledInstance[nvals]; - System.arraycopy(vals, 0, values, 0, nvals); - this.values = values; // safe publication - - try { - hash = findHash(realClass, true).longValue(); - } catch (UnusableEntryException e) { - // Will never happen when we pass true to findHash - throw new AssertionError(e); - } - - // Loop through the supertypes, making a list of all superclasses. - ArrayList<String> sclasses = new ArrayList<String>(); - ArrayList<Long> shashes = new ArrayList<Long>(); - for (Class c = realClass.getSuperclass(); - c != Object.class; - c = c.getSuperclass()) - { - try { - sclasses.add(c.getName()); - shashes.add(findHash(c, true)); - } catch (ClassCastException cce) { - break; // not Serializable - } catch (UnusableEntryException e) { - // Will never happen when we pass true to findHash - throw new AssertionError(e); - } - } - superclasses = sclasses.toArray(new String[sclasses.size()]); // safe publication. - long [] hashes = new long[shashes.size()]; - for (int i=0; i < hashes.length; i++) { - hashes[i] = (shashes.get(i)).longValue(); - } - this.hashes = hashes; // safe publication. - } - - /** - * Create a serialized form of the entry with our object's - * relevant fields set. - */ - public EntryRep(Entry entry) throws MarshalException { - this(entry, true); - } - - /** Used in recovery */ - EntryRep() { } - - /** Used to look up no-arg constructors. */ - private final static Class[] noArg = new Class[0]; - - /** - * Ensure that the entry class is valid, that is, that it has appropriate - * access. If not, throw <code>IllegalArgumentException</code>. - */ - private static void ensureValidClass(Class c) { - boolean ctorOK = false; - try { - if (!Modifier.isPublic(c.getModifiers())) { - throw throwRuntime(new IllegalArgumentException( - "entry class " + c.getName() + " not public")); - } - Constructor ctor = c.getConstructor(noArg); - ctorOK = Modifier.isPublic(ctor.getModifiers()); - } catch (NoSuchMethodException e) { - ctorOK = false; - } catch (SecurityException e) { - ctorOK = false; - } - if (!ctorOK) { - throw throwRuntime(new IllegalArgumentException("entry class " + - c.getName() +" needs public no-arg constructor")); - } - } - - /** - * The <code>EntryRep</code> that marks a ``match any'' request. - * This is used to represent a <code>null</code> template. - */ - static EntryRep matchAnyEntryRep() { - return matchAnyRep; - } - - /** - * Return <code>true</code> if the given rep is that ``match any'' - * <code>EntryRep</code>. - */ - private static boolean isMatchAny(EntryRep rep) { - return matchAnyRep.equals(rep); - } - - /** - * Return the class name that is used by the ``match any'' EntryRep - */ - static String matchAnyClassName() { - return matchAnyRep.classFor(); - } - - /** - * Return an <code>Entry</code> object built out of this - * <code>EntryRep</code> This is used by the client-side proxy to - * convert the <code>EntryRep</code> it gets from the space server - * into the actual <code>Entry</code> object it represents. - * - * @throws UnusableEntryException - * One or more fields in the entry cannot be - * deserialized, or the class for the entry type - * itself cannot be deserialized. - */ - Entry entry() throws UnusableEntryException { - ObjectInputStream objIn = null; - String className = ""; // set before any exception can be thrown. - try { - ArrayList badFields = null; - ArrayList except = null; - Entry entryObj = null; - int valuesLength = 0; - int nvals = 0; // index into this.values[] - - synchronized (this){ - className = this.className; - realClass = ClassLoading.loadClass(codebase, className, - null, integrity, null); - - if (findHash(realClass, false).longValue() != hash) - throw throwNewUnusableEntryException( - new IncompatibleClassChangeError(realClass + " changed")); - - entryObj = (Entry) realClass.newInstance(); - - Field[] fields = getFields(realClass); - - /* - * Loop through the fields, ensuring no primitives and - * checking for wildcards. - */ - - int fLength = fields.length; - valuesLength = values.length; - for (int i = 0; i < fLength; i++) { - Throwable nested = null; - try { - if (!usableField(fields[i])) - continue; - - final MarshalledInstance val = values[nvals++]; - Object value = (val == null ? null : val.get(integrity)); - fields[i].set(entryObj, value); - } catch (Throwable e) { - nested = e; - } - - if (nested != null) { // some problem occurred - if (badFields == null) { - badFields = new ArrayList(fLength); - except = new ArrayList(fLength); - } - badFields.add(fields[i].getName()); - except.add(nested); - } - } - } - - /* See if any fields have vanished from the class, - * because of the hashing this should never happen but - * throwing an exception that provides more info - * (instead of AssertionError) seems harmless. - */ - if (nvals < valuesLength) { - throw throwNewUnusableEntryException( - entryObj, // should this be null? - null, // array of bad-field names - new Throwable[] { // array of exceptions - new IncompatibleClassChangeError( - "A usable field has been removed from " + - entryObj.getClass().getName() + - " since this EntryRep was created") - }); - } - - // if there were any bad fields, throw the exception - if (badFields != null) { - String[] bf = - (String[]) badFields.toArray( - new String[badFields.size()]); - Throwable[] ex = - (Throwable[]) except.toArray(new Throwable[bf.length]); - throw throwNewUnusableEntryException(entryObj, bf, ex); - } - - // everything fine, return the entry - return entryObj; - } catch (InstantiationException e) { - /* - * If this happens outside a per-field deserialization then - * this is a complete failure The per-field ones are caught - * inside the per-field loop. - */ - throw throwNewUnusableEntryException(e); - } catch (ClassNotFoundException e) { - // see above - throw throwNewUnusableEntryException("Encountered a " + - "ClassNotFoundException while unmarshalling " + className, e); - } catch (IllegalAccessException e) { - // see above - throw throwNewUnusableEntryException(e); - } catch (RuntimeException e) { - // see above - throw throwNewUnusableEntryException("Encountered a " + - "RuntimeException while unmarshalling " + className, e); - } catch (MalformedURLException e) { - // see above - throw throwNewUnusableEntryException("Malformed URL " + - "associated with entry of type " + className, e); - } catch (MarshalException e) { - // because we call findHash() w/ false, should never happen - throw new AssertionError(e); - } - } - - // inherit doc comment - public int hashCode() { - return className.hashCode(); - } - - /** - * To be equal, the other object must by an <code>EntryRep</code> for - * an object of the same class with the same values for each field. - * This is <em>not</em> a template match -- see <code>matches</code>. - * - * @see #matches - */ - public boolean equals(Object o) { - // The other passed in was null--obviously not equal - if (o == null) - return false; - - // The other passed in was ME--obviously I'm the same as me... - if (this == o) - return true; - - if (!(o instanceof EntryRep)) - return false; - - EntryRep other = (EntryRep) o; - - synchronized (this){ - // If we're not the same class then we can't be equal - if (hash != other.hash) - return false; - - /* Paranoid check just to make sure we can't get an - * IndexOutOfBoundsException. Should never happen. - */ - if (values.length != other.values.length) - return false; - - /* OPTIMIZATION: - * If we have a case where one element is null and the corresponding - * element within the object we're comparing ourselves with is - * non-null (or vice-versa), we can stop right here and declare the - * two objects to be unequal. This is slightly faster than checking - * the bytes themselves. - * LOGIC: They've both got to be null or both have got to be - * non-null or we're out-of-here... - */ - for (int i = 0; i < values.length; i++) { - if ((values[i] == null) && (other.values[i] != null)) - return false; - if ((values[i] != null) && (other.values[i] == null)) - return false; - } - - /* The most expensive tests we save for last. - * Because we've made the null/non-null check above, we can - * simplify our comparison here: if our element is non-null, - * we know the other value is non-null, too. - * If any equals() calls from these element comparisons come - * back false then return false. If they all succeed, we fall - * through and return true (they were equal). - */ - for (int i = 0; i < values.length; i++) { - // Short-circuit evaluation if null, compare otherwise. - if (values[i] != null && !values[i].equals(other.values[i])) - return false; - } - } - - return true; - } - - /** - * Return <code>true</code> if the field is to be used for the - * entry. That is, return <code>true</code> if the field isn't - * <code>transient</code>, <code>static</code>, or <code>final</code>. - * @throws IllegalArgumentException - * The field is not <code>transient</code>, - * <code>static</code>, or <code>final</code>, but - * is primitive and hence not a proper field for - * an <code>Entry</code>. - */ - static private boolean usableField(Field field) { - // ignore anything that isn't a public non-static mutable field - final int ignoreMods = - (Modifier.TRANSIENT | Modifier.STATIC | Modifier.FINAL); - - if ((field.getModifiers() & ignoreMods) != 0) - return false; - - // if it isn't ignorable, it has to be an object of some kind - if (field.getType().isPrimitive()) { - throw throwRuntime(new IllegalArgumentException( - "primitive field, " + field + ", not allowed in an Entry")); - } - - return true; - } - - /** - * Return the ID. - */ - Uuid id() { - return id; - } - - /** - * Pick a random <code>Uuid</code> and set our id field to it. - * @throws IllegalStateException if this method has already - * been called. - */ - void pickID() { - synchronized (this){ - if (id != null) - throw new IllegalStateException("pickID called more than once"); - id = UuidFactory.generate(); - } - } - - /** - * Return the <code>MarshalledObject</code> for the given field. - */ - public MarshalledInstance value(int fieldNum) { - return values[fieldNum]; - } - - /** - * Return the number of fields in this kind of entry. - */ - public int numFields() { - synchronized (this){ - if (values != null) return values.length; - } - return 0; - } - - /** - * Return the class name for this entry. - */ - public String classFor() { - return className; - } - - /** - * Return the array names of superclasses of this entry type. - */ - public String[] superclasses() { - return superclasses; - } - - /** - * Return the hash of this entry type. - */ - long getHash() { - return hash; - } - - /** - * Return the array of superclass hashes of this entry type. - */ - long[] getHashes() { - return hashes; - } - - /** - * See if the other object matches the template object this - * represents. (Note that even though "this" is a template, it may - * have no wildcards -- a template can have all values.) - */ - boolean matches(EntryRep other) { - /* - * We use the fact that this is the template in several ways in - * the method implementation. For instance, in this next loop, - * we know that the real object must be at least my type, which - * means (a) the field types already match, and (b) it has at - * least as many fields as the this does. - */ - - //Note: If this object is the MatchAny template then - // return true (all entries match MatchAny) - synchronized (this){ - if (EntryRep.isMatchAny(this)) return true; - - for (int f = 0; f < values.length; f++) { - if (values[f] == null) { // skip wildcards - continue; - } - if (!values[f].equals(other.values[f])) { - return false; - } - } - } - return true; // no mismatches, so must be OK - } - - public String toString() { - return ("EntryRep[" + className + "]"); - } - - /** - * Return <code>true</code> if this entry represents an object that - * is at least the type of the <code>otherClass</code>. - */ - boolean isAtLeastA(String otherClass) { - if (otherClass.equals(matchAnyClassName())) - // The other is a null template, all entries are at least entry. - return true; - synchronized (this){ - if (className.equals(otherClass)) - return true; - for (int i = 0; i < superclasses.length; i++) - if (superclasses[i].equals(otherClass)) - return true; - return false; - } - } - - /** Comparator for sorting fields. Cribbed from Reggie */ - private static class FieldComparator implements Comparator { - public FieldComparator() {} - - /** Super before subclass, alphabetical within a given class */ - public int compare(Object o1, Object o2) { - Field f1 = (Field)o1; - Field f2 = (Field)o2; - if (f1 == f2) - return 0; - if (f1.getDeclaringClass() == f2.getDeclaringClass()) - return f1.getName().compareTo(f2.getName()); - if (f1.getDeclaringClass().isAssignableFrom( - f2.getDeclaringClass())) - return -1; - return 1; - } - } - - /** - * Use <code>readObject</code> method to capture whether or - * not integrity was being enforced when this object was - * unmarshalled, and to perform basic integrity checks. - */ - private void readObject(ObjectInputStream in) - throws IOException, ClassNotFoundException - { - in.defaultReadObject(); - if (className == null) - throw new InvalidObjectException("null className"); - - if (values == null) - throw new InvalidObjectException("null values"); - - if (superclasses == null) - throw new InvalidObjectException("null superclasses"); - - if (hashes == null) - throw new InvalidObjectException("null hashes"); - - if (hashes.length != superclasses.length) - throw new InvalidObjectException("hashes.length (" + - hashes.length + ") does not equal superclasses.length (" + - superclasses.length + ")"); - - // get value for integrity flag - integrity = MarshalledWrapper.integrityEnforced(in); - } - - /** - * We should always have data in the stream, if this method - * gets called there is something wrong. - */ - private void readObjectNoData() throws InvalidObjectException { - throw new - InvalidObjectException("SpaceProxy should always have data"); - } - - - // ------------------------------------------------------- - // Methods required by LeasedResource and StorableResource - // ------------------------------------------------------- - - // inherit doc comment from LeasedResource - public void setExpiration(long newExpiration) { - expires = newExpiration; - } - - // inherit doc comment from LeasedResource - public long getExpiration() { - return expires; - } - - // inherit doc comment from LeasedResource - // We use the Rep ID as the cookie - public Uuid getCookie() { - return id; - } - - // ------------------------------------- - // Methods required by StorableResource - // ------------------------------------- - - // inherit doc comment - public synchronized void store(ObjectOutputStream out) throws IOException { - final long bits0; - final long bits1; - if (id == null) { - bits0 = 0; - bits1 = 0; - } else { - bits0 = id.getMostSignificantBits(); - bits1 = id.getLeastSignificantBits(); - } - out.writeLong(bits0); - out.writeLong(bits1); - out.writeLong(expires); - out.writeObject(codebase); - out.writeObject(className); - out.writeObject(superclasses); - out.writeObject(values); - out.writeLong(hash); - out.writeObject(hashes); - } - - // inherit doc comment - public synchronized EntryRep restore(ObjectInputStream in) - throws IOException, ClassNotFoundException - { - final long bits0 = in.readLong(); - final long bits1 = in.readLong(); - if (bits0 == 0 && bits1 == 0) { - id = null; - } else { - id = UuidFactory.create(bits0, bits1); - } - - expires = in.readLong(); - codebase = (String)in.readObject(); - className = (String)in.readObject(); - superclasses = (String [])in.readObject(); - values = (MarshalledInstance [])in.readObject(); - hash = in.readLong(); - hashes = (long[])in.readObject(); - return this; - } - - // Utility methods for throwing and logging exceptions - /** Log and throw a runtime exception */ - private static RuntimeException throwRuntime(RuntimeException e) { - if (logger.isLoggable(Levels.FAILED)) { - logger.log(Levels.FAILED, e.getMessage(), e); - } - - throw e; - } - - /** Construct, log, and throw a new MarshalException */ - private static MarshalException throwNewMarshalException( - String msg, Exception nested) - throws MarshalException - { - final MarshalException me = new MarshalException(msg, nested); - if (logger.isLoggable(Levels.FAILED)) { - logger.log(Levels.FAILED, msg, me); - } - - throw me; - } - - /** - * Construct, log, and throw a new UnusableEntryException - */ - private UnusableEntryException throwNewUnusableEntryException( - Entry partial, String[] badFields, Throwable[] exceptions) - throws UnusableEntryException - { - final UnusableEntryException uee = - new UnusableEntryException(partial, badFields, exceptions); - - if (logger.isLoggable(Levels.FAILED)) { - logger.log(Levels.FAILED, - "failure constructing entry of type " + className, uee); - } - - throw uee; - } - - /** - * Construct, log, and throw a new UnusableEntryException, that - * wraps a given exception. - */ - private static UnusableEntryException throwNewUnusableEntryException( - Throwable nested) - throws UnusableEntryException - { - final UnusableEntryException uee = new UnusableEntryException(nested); - - if (logger.isLoggable(Levels.FAILED)) { - logger.log(Levels.FAILED, nested.getMessage(), uee); - } - - throw uee; - } - - /** - * Construct, log, and throw a new UnusableEntryException, that - * will rap a newly constructed UnmarshalException (that optional - * wraps a given exception). - */ - private static UnusableEntryException throwNewUnusableEntryException( - String msg, Exception nested) - throws UnusableEntryException - { - final UnmarshalException ue = new UnmarshalException(msg, nested); - final UnusableEntryException uee = new UnusableEntryException(ue); - - if (logger.isLoggable(Levels.FAILED)) { - logger.log(Levels.FAILED, msg, uee); - } - - throw uee; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.river.outrigger; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.net.MalformedURLException; +import java.rmi.MarshalException; +import java.rmi.UnmarshalException; +import java.security.DigestOutputStream; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.WeakHashMap; +import java.util.logging.Logger; +import net.jini.core.entry.Entry; +import net.jini.core.entry.UnusableEntryException; +import net.jini.id.Uuid; +import net.jini.id.UuidFactory; +import net.jini.io.MarshalledInstance; +import net.jini.space.JavaSpace; +import org.apache.river.landlord.LeasedResource; +import org.apache.river.logging.Levels; +import org.apache.river.proxy.CodebaseProvider; +import org.apache.river.proxy.MarshalledWrapper; + +/** + * An <code>EntryRep</code> object contains a packaged + * <code>Entry</code> object for communication between the client and a + * <code>JavaSpace</code>. + * + * @author Sun Microsystems, Inc. + * + * @see JavaSpace + * @see Entry + */ +class EntryRep implements StorableResource<EntryRep>, LeasedResource, Serializable { + static final long serialVersionUID = 3L; + + // Synchronization isn't used where volatile access would be atomic. + // External operations should synchronize if atomicicity is required for + // multiple operations. + // Synchronization is used where multiple fields are accessed or one field + // is accessed more than once to ensure atomicity. + /** + * The fields of the entry in marshalled form. Use <code>null</code> + * for <code>null</code> fields. + */ + private volatile MarshalledInstance[] values; + + private volatile String[] superclasses; // class names of the superclasses + private volatile long[] hashes; // superclass hashes + private volatile long hash; // hash for the entry class + private volatile String className; // the class ID of the entry + private volatile String codebase; // the codebase for this entry class + private volatile Uuid id; // space-relative storage id + private volatile transient long expires;// expiration time + + /** + * <code>true</code> if the last time this object was unmarshalled + * integrity was being enforced, <code>false</code> otherwise. + */ + private volatile transient boolean integrity; + + /** Comparator for sorting fields */ + private static final FieldComparator comparator = new FieldComparator(); + + /** + * This object represents the passing of a <code>null</code> + * parameter as a template, which is designed to match any entry. + * When a <code>null</code> is passed, it is replaced with this + * rep, which is then handled specially in a few relevant places. + */ + private static final EntryRep matchAnyRep; + + static { + classHashes = new WeakHashMap<Class,Long>(); + try { + matchAnyRep = new EntryRep(new Entry() { + // keeps tests happy + static final long serialVersionUID = -4244768995726274609L; + }, false); + } catch (MarshalException e) { + throw new AssertionError(e); + } + } + + /** + * The realClass object is transient because we neither need nor want + * it reconstituted on the other side. All we want is to be able to + * recreate it on the receiving client side. If it were not transient, + * not only would an unnecessary object creation occur, but it might + * force the download of the actual class to the server. + */ + private volatile transient Class realClass; // real class of the contained object + + /** + * Logger for logging information about operations carried out in + * the client. Note, we hard code "org.apache.river.outrigger" so + * we don't drag in OutriggerServerImpl to outrigger-dl.jar. + */ + private static final Logger logger = + Logger.getLogger("org.apache.river.outrigger.proxy"); + + /** + * Set this entry's generic data to be shared with the <code>other</code> + * object. Those fields that are object references that will be the same + * for all objects of the same type are shared this way. + * <p> + * Note that <code>codebase</code> is <em>not</em> shared. If it were, + * then the failure of one codebase could make all entries inaccessible. + * Each entry is usable insofar as the codebase under which it was + * written is usable. + */ + synchronized void shareWith(EntryRep other) { + className = other.className; + superclasses = other.superclasses; + hashes = other.hashes; + hash = other.hash; + } + + /** + * Get the entry fields associated with the passed class and put + * them in a canonical order. The fields are sorted so that fields + * belonging to a superclasses are before fields belonging to + * subclasses and within a class fields are ordered + * lexicographically by their name. + */ + static private Field[] getFields(Class cl) { + final Field[] fields = cl.getFields(); + Arrays.sort(fields, comparator); + return fields; + } + + /** + * Cached hash values for all classes we encounter. Weak hash used + * in case the class is GC'ed from the client's VM. + */ + static final private WeakHashMap<Class,Long> classHashes; + + /** + * Lookup the hash value for the given class. If it is not + * found in the cache, generate the hash for the class and + * save it. + */ + static synchronized private Long findHash(Class clazz, + boolean marshaling) + throws MarshalException, UnusableEntryException + { + + Long hash = classHashes.get(clazz); + + // If hash not cached, calculate it for this class and, + // recursively, all superclasses + // + if (hash == null) { + try { + Field[] fields = getFields(clazz); + MessageDigest md = MessageDigest.getInstance("SHA"); + DataOutputStream out = + new DataOutputStream( + new DigestOutputStream(new ByteArrayOutputStream(127), + md)); + Class c = clazz.getSuperclass(); + if (c != Object.class) + // recursive call + out.writeLong(findHash(c, marshaling).longValue()); + + // Hash only usable fields, this means that we do not + // detect changes in non-usable fields. This should be ok + // since those fields do not move between space and client. + // + for (int i = 0; i < fields.length; i++) { + if (!usableField(fields[i])) + continue; + out.writeUTF(fields[i].getName()); + out.writeUTF(fields[i].getType().getName()); + } + out.flush(); + byte[] digest = md.digest(); + long h = 0; + for (int i = Math.min(8, digest.length); --i >= 0; ) { + h += ((long)(digest[i] & 0xFF)) << (i * 8); + } + hash = Long.valueOf(h); + } catch (Exception e) { + if (marshaling) + throw throwNewMarshalException( + "Exception calculating entry class hash for " + + clazz, e); + else + throw throwNewUnusableEntryException( + "Exception calculating entry class hash for " + + clazz, e); + } + classHashes.put(clazz, hash); + } + return hash; + } + + /** + * Create a serialized form of the entry. If <code>validate</code> is + * <code>true</code>, basic sanity checks are done on the class to + * ensure that it meets the requirements to be an <code>Entry</code>. + * <code>validate</code> is <code>false</code> only when creating the + * stand-in object for "match any", which is never actually marshalled + * on the wire and so which doesn't need to be "proper". + */ + private EntryRep(Entry entry, boolean validate) throws MarshalException { + realClass = entry.getClass(); + if (validate) + ensureValidClass(realClass); + className = realClass.getName(); + codebase = CodebaseProvider.getClassAnnotation(realClass); + + /* + * Build up the per-field and superclass information through + * the reflection API. + */ + final Field[] fields = getFields(realClass); + int numFields = fields.length; + + // collect the usable field values in vals[0..nvals-1] + MarshalledInstance[] vals = new MarshalledInstance[numFields]; + int nvals = 0; + + for (int fnum = 0; fnum < fields.length; fnum++) { + final Field field = fields[fnum]; + if (!usableField(field)) + continue; + + final Object fieldValue; + try { + fieldValue = field.get(entry); + } catch (IllegalAccessException e) { + /* In general between using getFields() and + * ensureValidClass this should never happen, however + * there appear to be a few screw cases and + * IllegalArgumentException seems appropriate. + */ + final IllegalArgumentException iae = + new IllegalArgumentException("Couldn't access field " + + field); + iae.initCause(e); + throw throwRuntime(iae); + } + + if (fieldValue == null) { + vals[nvals] = null; + } else { + try { + vals[nvals] = new MarshalledInstance(fieldValue); + } catch (IOException e) { + throw throwNewMarshalException( + "Can't marshal field " + field + " with value " + + fieldValue, e); + } + } + + nvals++; + } + + // copy the vals with the correct length + MarshalledInstance [] values = new MarshalledInstance[nvals]; + System.arraycopy(vals, 0, values, 0, nvals); + this.values = values; // safe publication + + try { + hash = findHash(realClass, true).longValue(); + } catch (UnusableEntryException e) { + // Will never happen when we pass true to findHash + throw new AssertionError(e); + } + + // Loop through the supertypes, making a list of all superclasses. + ArrayList<String> sclasses = new ArrayList<String>(); + ArrayList<Long> shashes = new ArrayList<Long>(); + for (Class c = realClass.getSuperclass(); + c != Object.class; + c = c.getSuperclass()) + { + try { + sclasses.add(c.getName()); + shashes.add(findHash(c, true)); + } catch (ClassCastException cce) { + break; // not Serializable + } catch (UnusableEntryException e) { + // Will never happen when we pass true to findHash + throw new AssertionError(e); + } + } + superclasses = sclasses.toArray(new String[sclasses.size()]); // safe publication. + long [] hashes = new long[shashes.size()]; + for (int i=0; i < hashes.length; i++) { + hashes[i] = (shashes.get(i)).longValue(); + } + this.hashes = hashes; // safe publication. + } + + /** + * Create a serialized form of the entry with our object's + * relevant fields set. + */ + public EntryRep(Entry entry) throws MarshalException { + this(entry, true); + } + + /** Used in recovery */ + EntryRep() { } + + /** Used to look up no-arg constructors. */ + private final static Class[] noArg = new Class[0]; + + /** + * Ensure that the entry class is valid, that is, that it has appropriate + * access. If not, throw <code>IllegalArgumentException</code>. + */ + private static void ensureValidClass(Class c) { + boolean ctorOK = false; + try { + if (!Modifier.isPublic(c.getModifiers())) { + throw throwRuntime(new IllegalArgumentException( + "entry class " + c.getName() + " not public")); + } + Constructor ctor = c.getConstructor(noArg); + ctorOK = Modifier.isPublic(ctor.getModifiers()); + } catch (NoSuchMethodException e) { + ctorOK = false; + } catch (SecurityException e) { + ctorOK = false; + } + if (!ctorOK) { + throw throwRuntime(new IllegalArgumentException("entry class " + + c.getName() +" needs public no-arg constructor")); + } + } + + /** + * The <code>EntryRep</code> that marks a ``match any'' request. + * This is used to represent a <code>null</code> template. + */ + static EntryRep matchAnyEntryRep() { + return matchAnyRep; + } + + /** + * Return <code>true</code> if the given rep is that ``match any'' + * <code>EntryRep</code>. + */ + private static boolean isMatchAny(EntryRep rep) { + return matchAnyRep.equals(rep); + } + + /** + * Return the class name that is used by the ``match any'' EntryRep + */ + static String matchAnyClassName() { + return matchAnyRep.classFor(); + } + + /** + * Return an <code>Entry</code> object built out of this + * <code>EntryRep</code> This is used by the client-side proxy to + * convert the <code>EntryRep</code> it gets from the space server + * into the actual <code>Entry</code> object it represents. + * + * @throws UnusableEntryException + * One or more fields in the entry cannot be + * deserialized, or the class for the entry type + * itself cannot be deserialized. + */ + Entry entry() throws UnusableEntryException { + ObjectInputStream objIn = null; + String className = ""; // set before any exception can be thrown. + try { + ArrayList badFields = null; + ArrayList except = null; + Entry entryObj = null; + int valuesLength = 0; + int nvals = 0; // index into this.values[] + + synchronized (this){ + className = this.className; + realClass = CodebaseProvider.loadClass(codebase, className, + null, integrity, null); + + if (findHash(realClass, false).longValue() != hash) + throw throwNewUnusableEntryException( + new IncompatibleClassChangeError(realClass + " changed")); + + entryObj = (Entry) realClass.newInstance(); + + Field[] fields = getFields(realClass); + + /* + * Loop through the fields, ensuring no primitives and + * checking for wildcards. + */ + + int fLength = fields.length; + valuesLength = values.length; + for (int i = 0; i < fLength; i++) { + Throwable nested = null; + try { + if (!usableField(fields[i])) + continue; + + final MarshalledInstance val = values[nvals++]; + Object value = (val == null ? null : val.get(integrity)); + fields[i].set(entryObj, value); + } catch (Throwable e) { + nested = e; + } + + if (nested != null) { // some problem occurred + if (badFields == null) { + badFields = new ArrayList(fLength); + except = new ArrayList(fLength); + } + badFields.add(fields[i].getName()); + except.add(nested); + } + } + } + + /* See if any fields have vanished from the class, + * because of the hashing this should never happen but + * throwing an exception that provides more info + * (instead of AssertionError) seems harmless. + */ + if (nvals < valuesLength) { + throw throwNewUnusableEntryException( + entryObj, // should this be null? + null, // array of bad-field names + new Throwable[] { // array of exceptions + new IncompatibleClassChangeError( + "A usable field has been removed from " + + entryObj.getClass().getName() + + " since this EntryRep was created") + }); + } + + // if there were any bad fields, throw the exception + if (badFields != null) { + String[] bf = + (String[]) badFields.toArray( + new String[badFields.size()]); + Throwable[] ex = + (Throwable[]) except.toArray(new Throwable[bf.length]); + throw throwNewUnusableEntryException(entryObj, bf, ex); + } + + // everything fine, return the entry + return entryObj; + } catch (InstantiationException e) { + /* + * If this happens outside a per-field deserialization then + * this is a complete failure The per-field ones are caught + * inside the per-field loop. + */ + throw throwNewUnusableEntryException(e); + } catch (ClassNotFoundException e) { + // see above + throw throwNewUnusableEntryException("Encountered a " + + "ClassNotFoundException while unmarshalling " + className, e); + } catch (IllegalAccessException e) { + // see above + throw throwNewUnusableEntryException(e); + } catch (RuntimeException e) { + // see above + throw throwNewUnusableEntryException("Encountered a " + + "RuntimeException while unmarshalling " + className, e); + } catch (MalformedURLException e) { + // see above + throw throwNewUnusableEntryException("Malformed URL " + + "associated with entry of type " + className, e); + } catch (MarshalException e) { + // because we call findHash() w/ false, should never happen + throw new AssertionError(e); + } + } + + // inherit doc comment + public int hashCode() { + return className.hashCode(); + } + + /** + * To be equal, the other object must by an <code>EntryRep</code> for + * an object of the same class with the same values for each field. + * This is <em>not</em> a template match -- see <code>matches</code>. + * + * @see #matches + */ + public boolean equals(Object o) { + // The other passed in was null--obviously not equal + if (o == null) + return false; + + // The other passed in was ME--obviously I'm the same as me... + if (this == o) + return true; + + if (!(o instanceof EntryRep)) + return false; + + EntryRep other = (EntryRep) o; + + synchronized (this){ + // If we're not the same class then we can't be equal + if (hash != other.hash) + return false; + + /* Paranoid check just to make sure we can't get an + * IndexOutOfBoundsException. Should never happen. + */ + if (values.length != other.values.length) + return false; + + /* OPTIMIZATION: + * If we have a case where one element is null and the corresponding + * element within the object we're comparing ourselves with is + * non-null (or vice-versa), we can stop right here and declare the + * two objects to be unequal. This is slightly faster than checking + * the bytes themselves. + * LOGIC: They've both got to be null or both have got to be + * non-null or we're out-of-here... + */ + for (int i = 0; i < values.length; i++) { + if ((values[i] == null) && (other.values[i] != null)) + return false; + if ((values[i] != null) && (other.values[i] == null)) + return false; + } + + /* The most expensive tests we save for last. + * Because we've made the null/non-null check above, we can + * simplify our comparison here: if our element is non-null, + * we know the other value is non-null, too. + * If any equals() calls from these element comparisons come + * back false then return false. If they all succeed, we fall + * through and return true (they were equal). + */ + for (int i = 0; i < values.length; i++) { + // Short-circuit evaluation if null, compare otherwise. + if (values[i] != null && !values[i].equals(other.values[i])) + return false; + } + } + + return true; + } + + /** + * Return <code>true</code> if the field is to be used for the + * entry. That is, return <code>true</code> if the field isn't + * <code>transient</code>, <code>static</code>, or <code>final</code>. + * @throws IllegalArgumentException + * The field is not <code>transient</code>, + * <code>static</code>, or <code>final</code>, but + * is primitive and hence not a proper field for + * an <code>Entry</code>. + */ + static private boolean usableField(Field field) { + // ignore anything that isn't a public non-static mutable field + final int ignoreMods = + (Modifier.TRANSIENT | Modifier.STATIC | Modifier.FINAL); + + if ((field.getModifiers() & ignoreMods) != 0) + return false; + + // if it isn't ignorable, it has to be an object of some kind + if (field.getType().isPrimitive()) { + throw throwRuntime(new IllegalArgumentException( + "primitive field, " + field + ", not allowed in an Entry")); + } + + return true; + } + + /** + * Return the ID. + */ + Uuid id() { + return id; + } + + /** + * Pick a random <code>Uuid</code> and set our id field to it. + * @throws IllegalStateException if this method has already + * been called. + */ + void pickID() { + synchronized (this){ + if (id != null) + throw new IllegalStateException("pickID called more than once"); + id = UuidFactory.generate(); + } + } + + /** + * Return the <code>MarshalledObject</code> for the given field. + */ + public MarshalledInstance value(int fieldNum) { + return values[fieldNum]; + } + + /** + * Return the number of fields in this kind of entry. + */ + public int numFields() { + synchronized (this){ + if (values != null) return values.length; + } + return 0; + } + + /** + * Return the class name for this entry. + */ + public String classFor() { + return className; + } + + /** + * Return the array names of superclasses of this entry type. + */ + public String[] superclasses() { + return superclasses; + } + + /** + * Return the hash of this entry type. + */ + long getHash() { + return hash; + } + + /** + * Return the array of superclass hashes of this entry type. + */ + long[] getHashes() { + return hashes; + } + + /** + * See if the other object matches the template object this + * represents. (Note that even though "this" is a template, it may + * have no wildcards -- a template can have all values.) + */ + boolean matches(EntryRep other) { + /* + * We use the fact that this is the template in several ways in + * the method implementation. For instance, in this next loop, + * we know that the real object must be at least my type, which + * means (a) the field types already match, and (b) it has at + * least as many fields as the this does. + */ + + //Note: If this object is the MatchAny template then + // return true (all entries match MatchAny) + synchronized (this){ + if (EntryRep.isMatchAny(this)) return true; + + for (int f = 0; f < values.length; f++) { + if (values[f] == null) { // skip wildcards + continue; + } + if (!values[f].equals(other.values[f])) { + return false; + } + } + } + return true; // no mismatches, so must be OK + } + + public String toString() { + return ("EntryRep[" + className + "]"); + } + + /** + * Return <code>true</code> if this entry represents an object that + * is at least the type of the <code>otherClass</code>. + */ + boolean isAtLeastA(String otherClass) { + if (otherClass.equals(matchAnyClassName())) + // The other is a null template, all entries are at least entry. + return true; + synchronized (this){ + if (className.equals(otherClass)) + return true; + for (int i = 0; i < superclasses.length; i++) + if (superclasses[i].equals(otherClass)) + return true; + return false; + } + } + + /** Comparator for sorting fields. Cribbed from Reggie */ + private static class FieldComparator implements Comparator { + public FieldComparator() {} + + /** Super before subclass, alphabetical within a given class */ + public int compare(Object o1, Object o2) { + Field f1 = (Field)o1; + Field f2 = (Field)o2; + if (f1 == f2) + return 0; + if (f1.getDeclaringClass() == f2.getDeclaringClass()) + return f1.getName().compareTo(f2.getName()); + if (f1.getDeclaringClass().isAssignableFrom( + f2.getDeclaringClass())) + return -1; + return 1; + } + } + + /** + * Use <code>readObject</code> method to capture whether or + * not integrity was being enforced when this object was + * unmarshalled, and to perform basic integrity checks. + */ + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + if (className == null) + throw new InvalidObjectException("null className"); + + if (values == null) + throw new InvalidObjectException("null values"); + + if (superclasses == null) + throw new InvalidObjectException("null superclasses"); + + if (hashes == null) + throw new InvalidObjectException("null hashes"); + + if (hashes.length != superclasses.length) + throw new InvalidObjectException("hashes.length (" + + hashes.length + ") does not equal superclasses.length (" + + superclasses.length + ")"); + + // get value for integrity flag + integrity = MarshalledWrapper.integrityEnforced(in); + } + + /** + * We should always have data in the stream, if this method + * gets called there is something wrong. + */ + private void readObjectNoData() throws InvalidObjectException { + throw new + InvalidObjectException("SpaceProxy should always have data"); + } + + + // ------------------------------------------------------- + // Methods required by LeasedResource and StorableResource + // ------------------------------------------------------- + + // inherit doc comment from LeasedResource + public void setExpiration(long newExpiration) { + expires = newExpiration; + } + + // inherit doc comment from LeasedResource + public long getExpiration() { + return expires; + } + + // inherit doc comment from LeasedResource + // We use the Rep ID as the cookie + public Uuid getCookie() { + return id; + } + + // ------------------------------------- + // Methods required by StorableResource + // ------------------------------------- + + // inherit doc comment + public synchronized void store(ObjectOutputStream out) throws IOException { + final long bits0; + final long bits1; + if (id == null) { + bits0 = 0; + bits1 = 0; + } else { + bits0 = id.getMostSignificantBits(); + bits1 = id.getLeastSignificantBits(); + } + out.writeLong(bits0); + out.writeLong(bits1); + out.writeLong(expires); + out.writeObject(codebase); + out.writeObject(className); + out.writeObject(superclasses); + out.writeObject(values); + out.writeLong(hash); + out.writeObject(hashes); + } + + // inherit doc comment + public synchronized EntryRep restore(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + final long bits0 = in.readLong(); + final long bits1 = in.readLong(); + if (bits0 == 0 && bits1 == 0) { + id = null; + } else { + id = UuidFactory.create(bits0, bits1); + } + + expires = in.readLong(); + codebase = (String)in.readObject(); + className = (String)in.readObject(); + superclasses = (String [])in.readObject(); + values = (MarshalledInstance [])in.readObject(); + hash = in.readLong(); + hashes = (long[])in.readObject(); + return this; + } + + // Utility methods for throwing and logging exceptions + /** Log and throw a runtime exception */ + private static RuntimeException throwRuntime(RuntimeException e) { + if (logger.isLoggable(Levels.FAILED)) { + logger.log(Levels.FAILED, e.getMessage(), e); + } + + throw e; + } + + /** Construct, log, and throw a new MarshalException */ + private static MarshalException throwNewMarshalException( + String msg, Exception nested) + throws MarshalException + { + final MarshalException me = new MarshalException(msg, nested); + if (logger.isLoggable(Levels.FAILED)) { + logger.log(Levels.FAILED, msg, me); + } + + throw me; + } + + /** + * Construct, log, and throw a new UnusableEntryException + */ + private UnusableEntryException throwNewUnusableEntryException( + Entry partial, String[] badFields, Throwable[] exceptions) + throws UnusableEntryException + { + final UnusableEntryException uee = + new UnusableEntryException(partial, badFields, exceptions); + + if (logger.isLoggable(Levels.FAILED)) { + logger.log(Levels.FAILED, + "failure constructing entry of type " + className, uee); + } + + throw uee; + } + + /** + * Construct, log, and throw a new UnusableEntryException, that + * wraps a given exception. + */ + private static UnusableEntryException throwNewUnusableEntryException( + Throwable nested) + throws UnusableEntryException + { + final UnusableEntryException uee = new UnusableEntryException(nested); + + if (logger.isLoggable(Levels.FAILED)) { + logger.log(Levels.FAILED, nested.getMessage(), uee); + } + + throw uee; + } + + /** + * Construct, log, and throw a new UnusableEntryException, that + * will rap a newly constructed UnmarshalException (that optional + * wraps a given exception). + */ + private static UnusableEntryException throwNewUnusableEntryException( + String msg, Exception nested) + throws UnusableEntryException + { + final UnmarshalException ue = new UnmarshalException(msg, nested); + final UnusableEntryException uee = new UnusableEntryException(ue); + + if (logger.isLoggable(Levels.FAILED)) { + logger.log(Levels.FAILED, msg, uee); + } + + throw uee; + } +} Copied: river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/proxy/CodebaseProvider.java (from r1702364, river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/proxy/CodebaseAnnotation.java) URL: http://svn.apache.org/viewvc/river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/proxy/CodebaseProvider.java?p2=river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/proxy/CodebaseProvider.java&p1=river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/proxy/CodebaseAnnotation.java&r1=1702364&r2=1702386&rev=1702386&view=diff ============================================================================== --- river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/proxy/CodebaseAnnotation.java (original) +++ river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/proxy/CodebaseProvider.java Fri Sep 11 07:24:54 2015 @@ -18,37 +18,59 @@ package org.apache.river.proxy; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.net.MalformedURLException; import java.rmi.server.RMIClassLoader; -import java.util.logging.Level; -import java.util.logging.Logger; import net.jini.loader.ClassLoading; /** - * + * Provided only for proxy binary backward compatibility with River versions + * before 3.0 * @author peter + * @since 3.0 */ -public final class CodebaseAnnotation { - private CodebaseAnnotation(){ +public final class CodebaseProvider { + private CodebaseProvider(){ throw new AssertionError(); } public static String getClassAnnotation(Class clas){ try { - Method m = ClassLoading.class.getMethod("getClassAnnotation", Class.class); - return (String) m.invoke(null, clas); - } catch (NoSuchMethodException e){ + return ClassLoading.getClassAnnotation(clas); + } catch (NoSuchMethodError e){ // Ignore, earlier version of River. - } catch (IllegalAccessException ex) { - Logger.getLogger(CodebaseAnnotation.class.getName()).log(Level.SEVERE, null, ex); - } catch (IllegalArgumentException ex) { - Logger.getLogger(CodebaseAnnotation.class.getName()).log(Level.SEVERE, null, ex); - } catch (InvocationTargetException ex) { - Logger.getLogger(CodebaseAnnotation.class.getName()).log(Level.SEVERE, null, ex); - } catch (SecurityException ex){ - Logger.getLogger(CodebaseAnnotation.class.getName()).log(Level.CONFIG, null, ex); } return RMIClassLoader.getClassAnnotation(clas); } + + public static Class loadClass(String codebase, + String name, + ClassLoader defaultLoader, + boolean verifyCodebaseIntegrity, + ClassLoader verifierLoader) + throws MalformedURLException, ClassNotFoundException + { + try { + return ClassLoading.loadClass(codebase, name, defaultLoader, + verifyCodebaseIntegrity, verifierLoader); + } catch (NoSuchMethodError e){ + // Ignore, earlier version of River. + } + return RMIClassLoader.loadClass(codebase, name, defaultLoader); + } + + public static Class loadProxyClass(String codebase, + String[] interfaceNames, + ClassLoader defaultLoader, + boolean verifyCodebaseIntegrity, + ClassLoader verifierLoader) + throws MalformedURLException, ClassNotFoundException + { + try { + return ClassLoading.loadProxyClass(codebase, interfaceNames, defaultLoader, + verifyCodebaseIntegrity, verifierLoader); + } catch (NoSuchMethodError e){ + // Ignore, earlier version of River. + } + return RMIClassLoader.loadProxyClass(codebase, interfaceNames, defaultLoader); + } } Modified: river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/EntryClass.java URL: http://svn.apache.org/viewvc/river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/EntryClass.java?rev=1702386&r1=1702385&r2=1702386&view=diff ============================================================================== --- river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/EntryClass.java (original) +++ river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/EntryClass.java Fri Sep 11 07:24:54 2015 @@ -17,7 +17,6 @@ */ package org.apache.river.reggie; -import org.apache.river.proxy.MarshalledWrapper; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; @@ -28,7 +27,8 @@ import java.rmi.MarshalException; import java.rmi.UnmarshalException; import java.security.DigestOutputStream; import java.security.MessageDigest; -import net.jini.loader.ClassLoading; +import org.apache.river.proxy.CodebaseProvider; +import org.apache.river.proxy.MarshalledWrapper; /** * An EntryClass is a descriptor for an entry class, packaged up for @@ -174,7 +174,7 @@ class EntryClass implements Serializable throws IOException, ClassNotFoundException { Class cls = - ClassLoading.loadClass(codebase, name, null, integrity, null); + CodebaseProvider.loadClass(codebase, name, null, integrity, null); EntryClass local; try { local = ClassMapper.toEntryClassBase(cls).eclass; Modified: river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/EntryClassBase.java URL: http://svn.apache.org/viewvc/river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/EntryClassBase.java?rev=1702386&r1=1702385&r2=1702386&view=diff ============================================================================== --- river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/EntryClassBase.java (original) +++ river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/EntryClassBase.java Fri Sep 11 07:24:54 2015 @@ -18,8 +18,7 @@ package org.apache.river.reggie; import java.io.Serializable; -import net.jini.loader.ClassLoading; -import org.apache.river.proxy.CodebaseAnnotation; +import org.apache.river.proxy.CodebaseProvider; /** * An EntryClass annotated with a codebase. @@ -52,7 +51,7 @@ class EntryClassBase implements Serializ /** Sets the codebase to the codebase of the given class. */ public void setCodebase(Class cls) { - codebase = CodebaseAnnotation.getClassAnnotation(cls); + codebase = CodebaseProvider.getClassAnnotation(cls); } /** Modified: river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/RegistrarLease.java URL: http://svn.apache.org/viewvc/river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/RegistrarLease.java?rev=1702386&r1=1702385&r2=1702386&view=diff ============================================================================== --- river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/RegistrarLease.java (original) +++ river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/RegistrarLease.java Fri Sep 11 07:24:54 2015 @@ -29,7 +29,7 @@ import net.jini.core.lookup.ServiceID; import net.jini.id.ReferentUuid; import net.jini.id.ReferentUuids; import net.jini.id.Uuid; -import org.apache.river.api.util.ID; +import org.apache.river.lease.ID; /** * The base class for lease proxies. Modified: river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/RegistrarLeaseMap.java URL: http://svn.apache.org/viewvc/river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/RegistrarLeaseMap.java?rev=1702386&r1=1702385&r2=1702386&view=diff ============================================================================== --- river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/RegistrarLeaseMap.java (original) +++ river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/RegistrarLeaseMap.java Fri Sep 11 07:24:54 2015 @@ -27,7 +27,7 @@ import net.jini.core.lease.Lease; import net.jini.core.lease.LeaseMapException; import net.jini.core.lookup.ServiceID; import net.jini.id.Uuid; -import org.apache.river.impl.lease.AbstractLeaseMap; +import org.apache.river.impl.lease.AbstractIDLeaseMap; /** * The LeaseMap implementation class for registrar leases. Clients only see @@ -36,7 +36,7 @@ import org.apache.river.impl.lease.Abstr * @author Sun Microsystems, Inc. * */ -class RegistrarLeaseMap extends AbstractLeaseMap<RegistrarLease> { +class RegistrarLeaseMap extends AbstractIDLeaseMap<RegistrarLease> { private static final long serialVersionUID = 2L; Modified: river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/ServiceType.java URL: http://svn.apache.org/viewvc/river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/ServiceType.java?rev=1702386&r1=1702385&r2=1702386&view=diff ============================================================================== --- river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/ServiceType.java (original) +++ river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/ServiceType.java Fri Sep 11 07:24:54 2015 @@ -17,7 +17,6 @@ */ package org.apache.river.reggie; -import org.apache.river.proxy.MarshalledWrapper; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; @@ -27,11 +26,12 @@ import java.io.Serializable; import java.lang.reflect.Proxy; import java.rmi.MarshalException; import java.rmi.UnmarshalException; -import java.util.StringTokenizer; import java.security.DigestOutputStream; -import java.security.NoSuchAlgorithmException; import java.security.MessageDigest; -import net.jini.loader.ClassLoading; +import java.security.NoSuchAlgorithmException; +import java.util.StringTokenizer; +import org.apache.river.proxy.CodebaseProvider; +import org.apache.river.proxy.MarshalledWrapper; /** * A ServiceType is a descriptor for a class, packaged up for @@ -218,7 +218,7 @@ class ServiceType implements Serializabl throws IOException, ClassNotFoundException { if (name.charAt(0) != ';') { - return ClassLoading.loadClass( + return CodebaseProvider.loadClass( codebase, name, null, integrity, null); } StringTokenizer st = new StringTokenizer(name, ";"); @@ -226,7 +226,7 @@ class ServiceType implements Serializabl for (int i = 0; i < ifs.length; i++) { ifs[i] = st.nextToken(); } - return ClassLoading.loadProxyClass( + return CodebaseProvider.loadProxyClass( codebase, ifs, null, integrity, null); } Modified: river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/ServiceTypeBase.java URL: http://svn.apache.org/viewvc/river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/ServiceTypeBase.java?rev=1702386&r1=1702385&r2=1702386&view=diff ============================================================================== --- river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/ServiceTypeBase.java (original) +++ river/jtsk/skunk/qa-refactor-namespace/trunk/src/org/apache/river/reggie/ServiceTypeBase.java Fri Sep 11 07:24:54 2015 @@ -18,7 +18,7 @@ package org.apache.river.reggie; import java.io.Serializable; -import org.apache.river.proxy.CodebaseAnnotation; +import org.apache.river.proxy.CodebaseProvider; /** * A ServiceType annotated with a codebase. @@ -51,7 +51,7 @@ class ServiceTypeBase implements Seriali /** Sets the codebase to the codebase of the given class. */ public void setCodebase(Class cls) { - codebase = CodebaseAnnotation.getClassAnnotation(cls); + codebase = CodebaseProvider.getClassAnnotation(cls); } /**
