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.Serializable;
27  import java.lang.reflect.Proxy;
28  import java.rmi.MarshalException;
29  import java.rmi.UnmarshalException;
30  import java.security.DigestOutputStream;
31  import java.security.MessageDigest;
32  import java.security.NoSuchAlgorithmException;
33  import java.util.StringTokenizer;
34  import org.apache.river.api.io.AtomicSerial;
35  import org.apache.river.api.io.AtomicSerial.GetArg;
36  import org.apache.river.proxy.CodebaseProvider;
37  import org.apache.river.proxy.MarshalledWrapper;
38  
39  /**
40   * A ServiceType is a descriptor for a class, packaged up for
41   * transmission between client-side proxies and the registrar server.
42   * Instances are never visible to clients, they are private to the
43   * communication between the proxies and the server.
44   * <p>
45   * This class only has a bare minimum of methods, to minimize
46   * the amount of code downloaded into clients.
47   *
48   * @author Sun Microsystems, Inc.
49   *
50   * @see ClassMapper
51   * see ClassResolver (org.apache.river.reggie.test.share.ClassResolver?)
52   */
53  @AtomicSerial
54  public class ServiceType implements Serializable {
55  
56      private static final long serialVersionUID = 2L;
57      private static final ServiceType[] empty = {};
58  
59      /**
60       * Class name. If the class is generated by java.lang.reflect.Proxy,
61       * then the name is of the form ";iface1;iface2;...;ifaceN".
62       *
63       * @serial
64       */
65      private final String name;
66      /**
67       * Hash for the type
68       *
69       * @serial
70       */
71      protected final long hash;
72      /**
73       * Descriptor for the superclass.
74       *
75       * @serial
76       */
77      protected final ServiceType superclass;
78      /**
79       * Descriptor for the interfaces.  As a special case, interfaces is
80       * null for the descriptor for java.lang.Object, and non-null otherwise.
81       * This avoids carrying a boolean isInterface around.
82       *
83       * @serial
84       */
85      protected final ServiceType[] interfaces;
86      
87      private static long check(GetArg arg) throws IOException {
88  	String name = (String) arg.get("name", null); // Throws ClassCastException
89  	if (name == null)
90  	    throw new InvalidObjectException("name cannot be null");
91  	long hash = arg.get("hash", 0L);
92  	if (hash == 0) {
93  	    try {
94  		hash = computeHash(name);
95  	    } catch (Exception e) {
96  		throw new UnmarshalException("unable to calculate the type"
97  					     + " hash for " + name, e);
98  	    }
99  	}
100 	Object superclass = arg.get("superclass", null); // Throws ClassCastException
101 	if (superclass != null && !(superclass instanceof ServiceType)){
102 	    throw new InvalidObjectException(
103 		"superclass must be instances of ServiceType");
104 	}
105 	Object interfaces = arg.get("interfaces", null); // Throws ClassCastException
106 	
107 	if (interfaces != null && !(interfaces instanceof ServiceType[])){
108 	    throw new InvalidObjectException(
109 		"interfaces must be instances of ServiceType[]");
110 	}
111 	return hash;
112     }
113     
114     ServiceType(GetArg arg) throws IOException {
115 	this(arg,check(arg));
116     }
117     
118     private ServiceType(GetArg arg, long hash) throws IOException{
119 	name = (String) arg.get("name", null);
120 	this.hash = hash;
121 	superclass = (ServiceType) arg.get("superclass", null);
122 	ServiceType [] interfaces = (ServiceType[]) arg.get("interfaces", null);
123 	if (interfaces != null) interfaces = interfaces.clone();
124 	this.interfaces = interfaces;
125     }
126 
127     /**
128      * An instance containing only name, no supertype info.
129      * This is only used on the registrar side, to minimize the amount
130      * of info transmitted back to clients.
131      */
132     protected transient ServiceType replacement;
133     /** 
134      * Flag set to true if this instance was unmarshalled from an
135      * integrity-protected stream, or false otherwise
136      */
137     private transient boolean integrity = false;
138 
139     /** Should only be called by ClassMapper */
140     public ServiceType(Class clazz,
141 		       ServiceType superclass,
142 		       ServiceType[] interfaces)
143 	throws MarshalException
144     {
145 	if (!Proxy.isProxyClass(clazz)) {
146 	    name = clazz.getName();
147 	} else if (interfaces.length == 0) {
148 	    name = ";";
149 	} else {
150 	    StringBuffer buf = new StringBuffer();
151 	    for (int i = 0; i < interfaces.length; i++) {
152 		buf.append(';');
153 		buf.append(interfaces[i].getName());
154 	    }
155 	    name = buf.toString();
156 	}
157 	this.superclass = superclass;
158 	if (clazz != Object.class){
159 	    this.interfaces = interfaces;
160 	} else {
161 	    this.interfaces = null;
162 	}
163 	try {
164 	    hash = computeHash(name);
165 	} catch (Exception e) {
166 	    throw new MarshalException("unable to calculate the type hash for "
167 				       + name, e);
168 	}
169     }
170 
171     /**
172      * Constructor used for creating replacement instances,
173      * containing only name.
174      */
175     private ServiceType(ServiceType stype) {
176 	name = stype.name;
177 	hash = stype.hash;
178 	superclass = stype.superclass == null ? null : stype.superclass.getReplacement();
179 	interfaces = null;
180     }
181 
182     /**
183      * Returns the name of this type
184      * @return the name of this type
185      */
186     public String getName() {
187 	return name;
188     }
189 
190     /** Return the superclass descriptor */
191     public ServiceType getSuperclass() {
192 	return superclass;
193     }
194 
195     /** Return the interfaces.  The array is not a copy; do not modify it. */
196     public ServiceType[] getInterfaces() {
197 	if (interfaces != null)
198 	    return interfaces;
199 	return empty;
200     }
201 
202     /** Return the replacement, if any, containing only name and rep. */
203     public synchronized ServiceType getReplacement() {
204 	if (replacement == null)
205 	    replacement = new ServiceType(this);
206 	return replacement;
207     }
208 
209     /** 
210      * Test if this isAssignableFrom any of the given interface types. 
211      * Note ifaces cannot be null.
212      */
213     private boolean isAssignableFrom(ServiceType[] ifaces)
214     {
215 	for (int i = ifaces.length; --i >= 0; ) {
216 	    if (hash == ifaces[i].hash ||
217 		isAssignableFrom(ifaces[i].interfaces))
218 		return true;
219 	}
220 	return false;
221     }
222 
223     /** @see Class#isInterface */
224     public boolean isInterface() {
225 	return (superclass == null && interfaces != null);
226     }
227 
228    /**
229     * Returns true if this type is equal to <code>type</code> or if this type
230     * is equal to a superclass of <code>type</code>.
231     *
232     * @param cls Type to check if subclass of this class
233     * @return true if <code>type</code> is a subclass of this type, false
234     * otherwise
235     * @see java.lang.Class#isAssignableFrom
236      */
237     public boolean isAssignableFrom(ServiceType cls) {
238        if (hash == cls.hash)
239 	    return true;
240 	if (isInterface()) {
241 	    if (cls.interfaces != null && isAssignableFrom(cls.interfaces))
242 		return true;
243 	    for (ServiceType sup = cls.superclass;
244 		 sup != null && sup.interfaces != null;
245 		 sup = sup.superclass)
246 	    {
247 		if (isAssignableFrom(sup.interfaces))
248 		    return true;
249 	    }
250 	} else {
251 	    for (ServiceType sup = cls.superclass;
252 		 sup != null;
253 		 sup = sup.superclass)
254 	    {
255 	       if (hash == sup.hash)
256 		    return true;
257 	    }
258 	}
259 	return false;
260     }
261 
262     /**
263      * Converts this descriptor to a Class instance, loading from codebase
264      *
265      * @param codebase String the codebase to load the class from
266      * @return Class the class this descriptor represents
267      */
268     public Class toClass(String codebase)
269 	throws IOException, ClassNotFoundException
270     {
271 	if (name.charAt(0) != ';') {
272 	    return CodebaseProvider.loadClass(
273 		codebase, name, null, integrity, null);
274 	}
275 	StringTokenizer st = new StringTokenizer(name, ";");
276 	String[] ifs = new String[st.countTokens()];
277 	for (int i = 0; i < ifs.length; i++) {
278 	    ifs[i] = st.nextToken();
279 	}
280 	return CodebaseProvider.loadProxyClass(
281 	    codebase, ifs, null, integrity, null);
282     }
283 
284     /**
285      * Returns true if the object passed in is an instance of Type
286      * with the same type hash.  Returns false otherwise.
287      * @param o object to compare this object against
288      * @return true if this object equals the object passed in; false
289      * otherwise.
290      */
291     @Override
292     public boolean equals(Object o) {
293 	if (this == o) return true;
294 	if (!(o instanceof ServiceType))
295 	    return false;
296 	ServiceType t = (ServiceType) o;
297 	return hash == t.hash;
298     }
299 
300     /**
301      * Return a hashcode for this type.
302      * @return int the hashcode for this type
303      */
304     @Override
305     public int hashCode() {
306 	return (int) (hash ^ (hash >>> 32));
307     }
308 
309     /* Inherit javadoc */
310     @Override
311     public String toString() {
312 	return getClass() + "[name=" + getName() + "]";
313     }
314     
315     /**
316      * Computes a SHA-1 digest from the hash of the superclass, if there
317      * is a superclass, followed by the name of this class, followed by
318      * the name and type for each field, if any, declared by this class and
319      * ordered alphabetically by field name.  The first 8 bytes of the digest
320      * are used to form the 64-bit hash value for this type.
321      */
322     private static long computeHash(String name) throws IOException, NoSuchAlgorithmException
323     {
324 	long hash = 0;
325 	MessageDigest md = MessageDigest.getInstance("SHA");
326 	DataOutputStream out = new DataOutputStream(
327 	    new DigestOutputStream(new ByteArrayOutputStream(127),md));	    
328 	out.writeUTF(name);	   
329 	out.flush();
330 	byte[] digest = md.digest();
331 	for (int i = Math.min(8, digest.length); --i >= 0; ) {
332 	    hash += ((long) (digest[i] & 0xFF)) << (i * 8);
333 	}
334 	return hash;
335     }
336 
337     private void writeObject(ObjectOutputStream out) throws IOException {
338 	out.defaultWriteObject();
339     }
340 
341 
342     /**
343      * Samples integrity protection setting (if any) of the stream from which
344      * this instance is being deserialized.
345      */
346     private void readObject(ObjectInputStream in)
347 	throws IOException, ClassNotFoundException
348     {
349 	in.defaultReadObject();
350 	if (name == null)
351 	    throw new InvalidObjectException("name cannot be null");
352 	integrity = MarshalledWrapper.integrityEnforced(in);
353 	if (hash == 0) {
354 	    throw new InvalidObjectException("hash cannot be zero");
355 //	    try {
356 //		hash = computeHash(name);
357 //	    } catch (Exception e) {
358 //		throw new UnmarshalException("unable to calculate the type"
359 //					     + " hash for " + name, e);
360 //	    }
361 	    
362 	    }
363 	}
364 
365     /**
366      * Throws InvalidObjectException, since data for this class is required.
367      */
368     private void readObjectNoData() throws InvalidObjectException {
369 	throw new InvalidObjectException("no data");
370     }
371 
372 }
373