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.reggie.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.ObjectInputStream;
25  import java.io.ObjectOutputStream;
26  import java.io.ObjectStreamField;
27  import java.io.Serializable;
28  import java.rmi.MarshalException;
29  import java.rmi.UnmarshalException;
30  import java.security.DigestOutputStream;
31  import java.security.MessageDigest;
32  import org.apache.river.api.io.AtomicSerial;
33  import org.apache.river.api.io.AtomicSerial.GetArg;
34  import org.apache.river.proxy.CodebaseProvider;
35  import org.apache.river.proxy.MarshalledWrapper;
36  
37  /**
38   * An EntryClass is a descriptor for an entry class, packaged up for
39   * transmission between client-side proxies and the registrar server.
40   * Instances are never visible to clients, they are private to the
41   * communication between the proxies and the server.  Note that we don't
42   * transmit information about interfaces implemented by the class, because it
43   * isn't necessary given the specific use of type information for entries.
44   * <p>
45   * This class only has a bare minimum of methods, to minimize
46   * the amount of code downloaded into clients.
47   * <p>
48   *
49   * @author Sun Microsystems, Inc.
50   *
51   * @see ClassMapper
52   */
53  @AtomicSerial
54  public class EntryClass implements Serializable {
55  
56      private static final long serialVersionUID = 2L;
57      private static final ObjectStreamField[] serialPersistentFields = 
58      { 
59          /** @serialField Class name */
60          new ObjectStreamField("name", String.class),
61          /** @serialField Hash for the type */
62          new ObjectStreamField("hash", Long.TYPE),
63          /** @serialField Descriptor for the superclass */
64          new ObjectStreamField("superclass", EntryClass.class),
65  	 /** @serialField Descriptor for the superclass */
66          new ObjectStreamField("numFields", Integer.TYPE)
67      };
68      /**
69       * Class name
70       *
71       * @serial
72       */
73      protected final String name;
74      /**
75       * Hash for the type
76       *
77       * @serial
78       */
79      protected final long hash;
80      /**
81       * Descriptor for the superclass
82       *
83       * @serial
84       */
85      protected final EntryClass superclass;
86      /**
87       * Number of public fields
88       *
89       * @serial
90       */
91      protected final int numFields;
92  
93      private static boolean check(GetArg arg) throws IOException{
94  	String name = (String) arg.get("name", null);
95  	if (name == null) throw new InvalidObjectException("name cannot be null");
96  	long hash = arg.get("hash", 0L);
97  	if (hash == 0) throw new InvalidObjectException("hash cannot be zero");
98  	Object superclass = arg.get("superclass", null);
99  	if (superclass != null) {
100 	    if (!(superclass instanceof EntryClass))
101 		throw new InvalidObjectException(
102 			"superclass must be an instance of EntryClass");
103 	} else {
104 	    if (!Object.class.getName().equals(name))
105 		throw new InvalidObjectException(
106 		    "superclass may only be null if name is an instance of java.lang.Object");
107 	}
108 	int numFields = arg.get("numFields", 0);
109 	return true;
110     }
111     
112     public EntryClass(GetArg arg) throws IOException{
113 	this(arg, check(arg));
114     }
115     
116     private EntryClass(GetArg arg, boolean check) throws IOException {
117 	name = (String) arg.get("name", null);
118 	hash = arg.get("hash", 0L);
119 	superclass = (EntryClass) arg.get("superclass", null);
120 	numFields = arg.get("numFields", 0);
121 	integrity = MarshalledWrapper.integrityEnforced(arg);
122     }
123 
124     /** Number of instances of this class in service registrations */
125     private transient int numInstances;
126     /** Number of templates of this class in event registrations */
127     private transient int numTemplates;
128     /**
129      * An instance containing only name and hash, no superclass info.
130      * This is only used on the registrar side, to minimize the amount
131      * of info transmitted back to clients.
132      */
133     private transient EntryClass replacement;
134     /** 
135      * Flag set to true if this instance was unmarshalled from an
136      * integrity-protected stream, or false otherwise
137      */
138     private transient boolean integrity = false;
139 
140     /** Should only be called by ClassMapper */
141     public EntryClass(Class clazz, EntryClass superclass)
142 	throws MarshalException
143     {
144 	name = clazz.getName();
145 	this.superclass = superclass;
146 	ClassMapper.EntryField[] fields = ClassMapper.getFields(clazz);
147 	numFields = fields.length;
148 	hash = computeHash(fields);
149     }
150 
151     /**
152      * Constructor used for creating replacement instances,
153      * containing only name and hash.
154      */
155     private EntryClass(EntryClass orig) {
156 	name = orig.name;
157 	hash = orig.hash;
158 	superclass = orig.superclass == null ? null : orig.superclass.getReplacement();
159 	numFields = orig.numFields;
160     }
161 
162     /** Return the superclass descriptor */
163     public EntryClass getSuperclass() {
164 	return superclass;
165     }
166 
167     /** Return the number of public fields (including superclasses) */
168     public int getNumFields() {
169 	return numFields;
170     }
171 
172     /** Set the number of instances of this class */
173     public void setNumInstances(int numInstances) {
174 	this.numInstances = numInstances;
175     }
176 
177     /** Set the number of templates of this class */
178     public void setNumTemplates(int numTemplates) {
179 	this.numTemplates = numTemplates;
180     }
181 
182     /** Return the replacement, if any, containing only name and rep. */
183     public synchronized EntryClass getReplacement() {
184 	if (replacement == null)
185 	    replacement = new EntryClass(this);
186 	return replacement;
187     }
188 
189     /**
190      * This is really only needed in the registrar, but it's very
191      * convenient to have here.
192      * @see Class#isAssignableFrom
193      */
194     public boolean isAssignableFrom(EntryClass cls) {
195 	for (EntryClass sup = cls; sup != null; sup = sup.superclass) {
196 	    if (hash == sup.hash)
197 		return true;
198 	}
199 	return false;
200     }
201 
202     /**
203      * Returns the number of times this type is used in service
204      * registrations
205      * @return number of instances of this type in use in service
206      * registrations
207      */
208     public int getNumInstances() {
209 	return numInstances;
210     }
211 
212     /**
213      * Returns the number of times this type is used in event
214      * registrations
215      * @return number of times this type is used in event registrations
216      */
217     public int getNumTemplates() {
218 	return numTemplates;
219     }
220 
221     // Converts this type descriptor to a Class object
222     public Class toClass(String codebase)
223 	throws IOException, ClassNotFoundException
224     {
225 	Class cls = 
226 	    CodebaseProvider.loadClass(codebase, name, null, integrity, null);
227 	EntryClass local;
228 	try {
229 	    local = ClassMapper.toEntryClassBase(cls).eclass;
230 	} catch (MarshalException e) {
231 	    throw new UnmarshalException("problem obtaining local version of "
232 					 + toString(), e);
233 	}
234 	if (hash != local.hash)
235 	    throw new UnmarshalException("incoming entry type: " + toString()
236 					 + " is not assignable to the local"
237 					 + " version of the type: " + local);
238 	return cls;
239     }
240 
241     /**
242      * Returns the name of this type
243      * @return the name of this type
244      */
245     public String getName() {
246 	return name;
247     }
248 
249     /**
250      * Returns true if the object passed in is an instance of EntryClass
251      * with the same type hash as this object.  Returns false otherwise.
252      * @param o object to compare this object against
253      * @return true if this object equals the object passed in; false
254      * otherwise.
255      */
256     public boolean equals(Object o) {
257 	if (this == o) return true;
258 	if (!(o instanceof EntryClass))
259 	    return false;
260 	EntryClass t = (EntryClass) o;
261 	return hash == t.hash;
262     }
263 
264     /**
265      * Return a hashcode for this type.
266      * @return int the hashcode for this type
267      */
268     public int hashCode() {
269 	return (int) (hash ^ (hash >>> 32));
270     }
271 
272     /* Inherit javadoc */
273     public String toString() {
274 	return getClass() + "[name=" + getName() + ", hash=" + hash + "]";
275     }
276     
277     /**
278      * Computes a SHA-1 digest from the hash of the superclass, if there
279      * is a superclass, followed by the name of this class, followed by
280      * the name and type for each field, if any, declared by this class and
281      * ordered alphabetically by field name.  The first 8 bytes of the digest
282      * are used to form the 64-bit hash value for this type.
283      */
284     private long computeHash(ClassMapper.EntryField[] fields)  
285 	throws MarshalException 
286     {
287 	long hash = 0;
288 	try {
289 	    MessageDigest md = MessageDigest.getInstance("SHA");
290 	    DataOutputStream out = new DataOutputStream(
291 		new DigestOutputStream(new ByteArrayOutputStream(127),md));
292 	    if (superclass != null)
293 		out.writeLong(superclass.hash);
294 	    out.writeUTF(name);
295 	    int startDeclaredFields = superclass != null ? 
296 		superclass.numFields : 0;
297 	    for (int i = startDeclaredFields; i < fields.length; i++) {
298 		out.writeUTF(fields[i].field.getName());
299 		out.writeUTF(fields[i].field.getType().getName());
300 	    }
301 	    out.flush();
302 	    byte[] digest = md.digest();
303 	    for (int i = Math.min(8, digest.length); --i >= 0; ) {
304 		hash += ((long) (digest[i] & 0xFF)) << (i * 8);
305 	    }
306 	} catch (Exception e) {
307 	    throw new MarshalException("Unable to calculate type hash for "
308 				       + name, e);
309 	}
310 	return hash;
311     }
312 
313     private void writeObject(ObjectOutputStream out) throws IOException {
314 	out.defaultWriteObject();
315     }
316 
317 
318     /**
319      * Samples integrity protection setting (if any) of the stream from which
320      * this instance is being deserialized and checks that valid values
321      * for this object have been read from the stream.
322      */
323     private void readObject(ObjectInputStream in)
324 	throws IOException, ClassNotFoundException
325     {
326 	in.defaultReadObject();
327 	if (name == null)
328 	    throw new InvalidObjectException("name cannot be null");
329 	if (hash == 0)
330 	    throw new InvalidObjectException("hash cannot be zero");
331 	integrity = MarshalledWrapper.integrityEnforced(in);
332     }
333 
334     /**
335      * Throws InvalidObjectException, since data for this class is required.
336      */
337     private void readObjectNoData() throws InvalidObjectException {
338 	throw new InvalidObjectException("no data");
339     }
340 
341 }