1 /*
2  * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 package sun.rmi.server;
26 
27 import java.io.ByteArrayOutputStream;
28 import java.io.IOException;
29 import java.io.DataOutputStream;
30 import java.lang.reflect.Constructor;
31 import java.lang.reflect.InvocationHandler;
32 import java.lang.reflect.InvocationTargetException;
33 import java.lang.reflect.Proxy;
34 import java.lang.reflect.Method;
35 import java.rmi.Remote;
36 import java.rmi.RemoteException;
37 import java.rmi.StubNotFoundException;
38 import java.rmi.registry.Registry;
39 import java.rmi.server.LogStream;
40 import java.rmi.server.ObjID;
41 import java.rmi.server.RMIClientSocketFactory;
42 import java.rmi.server.RemoteObjectInvocationHandler;
43 import java.rmi.server.RemoteRef;
44 import java.rmi.server.RemoteStub;
45 import java.rmi.server.Skeleton;
46 import java.rmi.server.SkeletonNotFoundException;
47 import java.security.AccessController;
48 import java.security.MessageDigest;
49 import java.security.DigestOutputStream;
50 import java.security.NoSuchAlgorithmException;
51 import java.security.PrivilegedAction;
52 import java.util.ArrayList;
53 import java.util.Collections;
54 import java.util.Map;
55 import java.util.WeakHashMap;
56 import sun.rmi.registry.RegistryImpl;
57 import sun.rmi.runtime.Log;
58 import sun.rmi.transport.LiveRef;
59 import sun.rmi.transport.tcp.TCPEndpoint;
60 
61 /**
62  * A utility class with static methods for creating stubs/proxies and
63  * skeletons for remote objects.
64  */
65 @SuppressWarnings("deprecation")
66 public final class Util {
67 
68     /** "server" package log level */
69     static final int logLevel = LogStream.parseLevel(
70         AccessController.doPrivileged(
71             (PrivilegedAction<String>) () -> System.getProperty("sun.rmi.server.logLevel")));
72 
73     /** server reference log */
74     public static final Log serverRefLog =
75         Log.getLog("sun.rmi.server.ref", "transport", Util.logLevel);
76 
77     /** cached value of property java.rmi.server.ignoreStubClasses */
78     private static final boolean ignoreStubClasses =
79         AccessController.doPrivileged(
80             (PrivilegedAction<Boolean>) () -> Boolean.getBoolean("java.rmi.server.ignoreStubClasses"));
81 
82     /** cache of  impl classes that have no corresponding stub class */
83     private static final Map<Class<?>, Void> withoutStubs =
84         Collections.synchronizedMap(new WeakHashMap<Class<?>, Void>(11));
85 
86     /** parameter types for stub constructor */
87     private static final Class<?>[] stubConsParamTypes = { RemoteRef.class };
88 
Util()89     private Util() {
90     }
91 
92     /**
93      * Returns a proxy for the specified implClass.
94      *
95      * If both of the following criteria is satisfied, a dynamic proxy for
96      * the specified implClass is returned (otherwise a RemoteStub instance
97      * for the specified implClass is returned):
98      *
99      *    a) either the property java.rmi.server.ignoreStubClasses is true or
100      *       a pregenerated stub class does not exist for the impl class, and
101      *    b) forceStubUse is false.
102      *
103      * If the above criteria are satisfied, this method constructs a
104      * dynamic proxy instance (that implements the remote interfaces of
105      * implClass) constructed with a RemoteObjectInvocationHandler instance
106      * constructed with the clientRef.
107      *
108      * Otherwise, this method loads the pregenerated stub class (which
109      * extends RemoteStub and implements the remote interfaces of
110      * implClass) and constructs an instance of the pregenerated stub
111      * class with the clientRef.
112      *
113      * @param implClass the class to obtain remote interfaces from
114      * @param clientRef the remote ref to use in the invocation handler
115      * @param forceStubUse if true, forces creation of a RemoteStub
116      * @throws IllegalArgumentException if implClass implements illegal
117      * remote interfaces
118      * @throws StubNotFoundException if problem locating/creating stub or
119      * creating the dynamic proxy instance
120      **/
createProxy(Class<?> implClass, RemoteRef clientRef, boolean forceStubUse)121     public static Remote createProxy(Class<?> implClass,
122                                      RemoteRef clientRef,
123                                      boolean forceStubUse)
124         throws StubNotFoundException
125     {
126         Class<?> remoteClass;
127 
128         try {
129             remoteClass = getRemoteClass(implClass);
130         } catch (ClassNotFoundException ex ) {
131             throw new StubNotFoundException(
132                 "object does not implement a remote interface: " +
133                 implClass.getName());
134         }
135 
136         if (forceStubUse ||
137             !(ignoreStubClasses || !stubClassExists(remoteClass)))
138         {
139             return createStub(remoteClass, clientRef);
140         }
141 
142         final ClassLoader loader = implClass.getClassLoader();
143         final Class<?>[] interfaces = getRemoteInterfaces(implClass);
144         final InvocationHandler handler =
145             new RemoteObjectInvocationHandler(clientRef);
146 
147         /* REMIND: private remote interfaces? */
148 
149         try {
150             return AccessController.doPrivileged(new PrivilegedAction<Remote>() {
151                 public Remote run() {
152                     return (Remote) Proxy.newProxyInstance(loader,
153                                                            interfaces,
154                                                            handler);
155                 }});
156         } catch (IllegalArgumentException e) {
157             throw new StubNotFoundException("unable to create proxy", e);
158         }
159     }
160 
161     /**
162      * Returns true if a stub class for the given impl class can be loaded,
163      * otherwise returns false.
164      *
165      * @param remoteClass the class to obtain remote interfaces from
166      */
167     private static boolean stubClassExists(Class<?> remoteClass) {
168         if (!withoutStubs.containsKey(remoteClass)) {
169             try {
170                 Class.forName(remoteClass.getName() + "_Stub",
171                               false,
172                               remoteClass.getClassLoader());
173                 return true;
174 
175             } catch (ClassNotFoundException cnfe) {
176                 withoutStubs.put(remoteClass, null);
177             }
178         }
179         return false;
180     }
181 
182     /*
183      * Returns the class/superclass that implements the remote interface.
184      * @throws ClassNotFoundException if no class is found to have a
185      * remote interface
186      */
187     private static Class<?> getRemoteClass(Class<?> cl)
188         throws ClassNotFoundException
189     {
190         while (cl != null) {
191             Class<?>[] interfaces = cl.getInterfaces();
192             for (int i = interfaces.length -1; i >= 0; i--) {
193                 if (Remote.class.isAssignableFrom(interfaces[i]))
194                     return cl;          // this class implements remote object
195             }
196             cl = cl.getSuperclass();
197         }
198         throw new ClassNotFoundException(
199                 "class does not implement java.rmi.Remote");
200     }
201 
202     /**
203      * Returns an array containing the remote interfaces implemented
204      * by the given class.
205      *
206      * @param   remoteClass the class to obtain remote interfaces from
207      * @throws  IllegalArgumentException if remoteClass implements
208      *          any illegal remote interfaces
209      * @throws  NullPointerException if remoteClass is null
210      */
211     private static Class<?>[] getRemoteInterfaces(Class<?> remoteClass) {
212         ArrayList<Class<?>> list = new ArrayList<>();
213         getRemoteInterfaces(list, remoteClass);
214         return list.toArray(new Class<?>[list.size()]);
215     }
216 
217     /**
218      * Fills the given array list with the remote interfaces implemented
219      * by the given class.
220      *
221      * @throws  IllegalArgumentException if the specified class implements
222      *          any illegal remote interfaces
223      * @throws  NullPointerException if the specified class or list is null
224      */
225     private static void getRemoteInterfaces(ArrayList<Class<?>> list, Class<?> cl) {
226         Class<?> superclass = cl.getSuperclass();
227         if (superclass != null) {
228             getRemoteInterfaces(list, superclass);
229         }
230 
231         Class<?>[] interfaces = cl.getInterfaces();
232         for (int i = 0; i < interfaces.length; i++) {
233             Class<?> intf = interfaces[i];
234             /*
235              * If it is a remote interface (if it extends from
236              * java.rmi.Remote) and is not already in the list,
237              * then add the interface to the list.
238              */
239             if (Remote.class.isAssignableFrom(intf)) {
240                 if (!(list.contains(intf))) {
241                     Method[] methods = intf.getMethods();
242                     for (int j = 0; j < methods.length; j++) {
243                         checkMethod(methods[j]);
244                     }
245                     list.add(intf);
246                 }
247             }
248         }
249     }
250 
251     /**
252      * Verifies that the supplied method has at least one declared exception
253      * type that is RemoteException or one of its superclasses.  If not,
254      * then this method throws IllegalArgumentException.
255      *
256      * @throws IllegalArgumentException if m is an illegal remote method
257      */
258     private static void checkMethod(Method m) {
259         Class<?>[] ex = m.getExceptionTypes();
260         for (int i = 0; i < ex.length; i++) {
261             if (ex[i].isAssignableFrom(RemoteException.class))
262                 return;
263         }
264         throw new IllegalArgumentException(
265             "illegal remote method encountered: " + m);
266     }
267 
268     /**
269      * Creates a RemoteStub instance for the specified class, constructed
270      * with the specified RemoteRef.  The supplied class must be the most
271      * derived class in the remote object's superclass chain that
272      * implements a remote interface.  The stub class name is the name of
273      * the specified remoteClass with the suffix "_Stub".  The loading of
274      * the stub class is initiated from class loader of the specified class
275      * (which may be the bootstrap class loader).
276      **/
277     private static RemoteStub createStub(Class<?> remoteClass, RemoteRef ref)
278         throws StubNotFoundException
279     {
280         String stubname = remoteClass.getName() + "_Stub";
281 
282         /* Make sure to use the local stub loader for the stub classes.
283          * When loaded by the local loader the load path can be
284          * propagated to remote clients, by the MarshalOutputStream/InStream
285          * pickle methods
286          */
287         try {
288             Class<?> stubcl =
289                 Class.forName(stubname, false, remoteClass.getClassLoader());
290             Constructor<?> cons = stubcl.getConstructor(stubConsParamTypes);
291             return (RemoteStub) cons.newInstance(new Object[] { ref });
292 
293         } catch (ClassNotFoundException e) {
294             throw new StubNotFoundException(
295                 "Stub class not found: " + stubname, e);
296         } catch (NoSuchMethodException e) {
297             throw new StubNotFoundException(
298                 "Stub class missing constructor: " + stubname, e);
299         } catch (InstantiationException e) {
300             throw new StubNotFoundException(
301                 "Can't create instance of stub class: " + stubname, e);
302         } catch (IllegalAccessException e) {
303             throw new StubNotFoundException(
304                 "Stub class constructor not public: " + stubname, e);
305         } catch (InvocationTargetException e) {
306             throw new StubNotFoundException(
307                 "Exception creating instance of stub class: " + stubname, e);
308         } catch (ClassCastException e) {
309             throw new StubNotFoundException(
310                 "Stub class not instance of RemoteStub: " + stubname, e);
311         }
312     }
313 
314     /**
315      * Locate and return the Skeleton for the specified remote object
316      */
317     static Skeleton createSkeleton(Remote object)
318         throws SkeletonNotFoundException
319     {
320         Class<?> cl;
321         try {
322             cl = getRemoteClass(object.getClass());
323         } catch (ClassNotFoundException ex ) {
324             throw new SkeletonNotFoundException(
325                 "object does not implement a remote interface: " +
326                 object.getClass().getName());
327         }
328 
329         // now try to load the skeleton based ont he name of the class
330         String skelname = cl.getName() + "_Skel";
331         try {
332             Class<?> skelcl = Class.forName(skelname, false, cl.getClassLoader());
333 
334             return (Skeleton)skelcl.newInstance();
335         } catch (ClassNotFoundException ex) {
336             throw new SkeletonNotFoundException("Skeleton class not found: " +
337                                                 skelname, ex);
338         } catch (InstantiationException ex) {
339             throw new SkeletonNotFoundException("Can't create skeleton: " +
340                                                 skelname, ex);
341         } catch (IllegalAccessException ex) {
342             throw new SkeletonNotFoundException("No public constructor: " +
343                                                 skelname, ex);
344         } catch (ClassCastException ex) {
345             throw new SkeletonNotFoundException(
346                 "Skeleton not of correct class: " + skelname, ex);
347         }
348     }
349 
350     /**
351      * Compute the "method hash" of a remote method.  The method hash
352      * is a long containing the first 64 bits of the SHA digest from
353      * the UTF encoded string of the method name and descriptor.
354      */
355     public static long computeMethodHash(Method m) {
356         long hash = 0;
357         ByteArrayOutputStream sink = new ByteArrayOutputStream(127);
358         try {
359             MessageDigest md = MessageDigest.getInstance("SHA");
360             DataOutputStream out = new DataOutputStream(
361                 new DigestOutputStream(sink, md));
362 
363             String s = getMethodNameAndDescriptor(m);
364             if (serverRefLog.isLoggable(Log.VERBOSE)) {
365                 serverRefLog.log(Log.VERBOSE,
366                     "string used for method hash: \"" + s + "\"");
367             }
368             out.writeUTF(s);
369 
370             // use only the first 64 bits of the digest for the hash
371             out.flush();
372             byte hasharray[] = md.digest();
373             for (int i = 0; i < Math.min(8, hasharray.length); i++) {
374                 hash += ((long) (hasharray[i] & 0xFF)) << (i * 8);
375             }
376         } catch (IOException ignore) {
377             /* can't happen, but be deterministic anyway. */
378             hash = -1;
379         } catch (NoSuchAlgorithmException complain) {
380             throw new SecurityException(complain.getMessage());
381         }
382         return hash;
383     }
384 
385     /**
386      * Return a string consisting of the given method's name followed by
387      * its "method descriptor", as appropriate for use in the computation
388      * of the "method hash".
389      *
390      * See section 4.3.3 of The Java Virtual Machine Specification for
391      * the definition of a "method descriptor".
392      */
393     private static String getMethodNameAndDescriptor(Method m) {
394         StringBuilder desc = new StringBuilder(m.getName());
395         desc.append('(');
396         Class<?>[] paramTypes = m.getParameterTypes();
397         for (int i = 0; i < paramTypes.length; i++) {
398             desc.append(getTypeDescriptor(paramTypes[i]));
399         }
400         desc.append(')');
401         Class<?> returnType = m.getReturnType();
402         if (returnType == void.class) { // optimization: handle void here
403             desc.append('V');
404         } else {
405             desc.append(getTypeDescriptor(returnType));
406         }
407         return desc.toString();
408     }
409 
410     /**
411      * Get the descriptor of a particular type, as appropriate for either
412      * a parameter or return type in a method descriptor.
413      */
414     private static String getTypeDescriptor(Class<?> type) {
415         if (type.isPrimitive()) {
416             if (type == int.class) {
417                 return "I";
418             } else if (type == boolean.class) {
419                 return "Z";
420             } else if (type == byte.class) {
421                 return "B";
422             } else if (type == char.class) {
423                 return "C";
424             } else if (type == short.class) {
425                 return "S";
426             } else if (type == long.class) {
427                 return "J";
428             } else if (type == float.class) {
429                 return "F";
430             } else if (type == double.class) {
431                 return "D";
432             } else if (type == void.class) {
433                 return "V";
434             } else {
435                 throw new Error("unrecognized primitive type: " + type);
436             }
437         } else if (type.isArray()) {
438             /*
439              * According to JLS 20.3.2, the getName() method on Class does
440              * return the VM type descriptor format for array classes (only);
441              * using that should be quicker than the otherwise obvious code:
442              *
443              *     return "[" + getTypeDescriptor(type.getComponentType());
444              */
445             return type.getName().replace('.', '/');
446         } else {
447             return "L" + type.getName().replace('.', '/') + ";";
448         }
449     }
450 
451     /**
452      * Returns the binary name of the given type without package
453      * qualification.  Nested types are treated no differently from
454      * top-level types, so for a nested type, the returned name will
455      * still be qualified with the simple name of its enclosing
456      * top-level type (and perhaps other enclosing types), the
457      * separator will be '$', etc.
458      **/
459     public static String getUnqualifiedName(Class<?> c) {
460         String binaryName = c.getName();
461         return binaryName.substring(binaryName.lastIndexOf('.') + 1);
462     }
463 }
464