| /* | |
| * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package sun.rmi.server; | |
| import java.io.ByteArrayOutputStream; | |
| import java.io.IOException; | |
| import java.io.DataOutputStream; | |
| import java.lang.reflect.Constructor; | |
| import java.lang.reflect.InvocationHandler; | |
| import java.lang.reflect.InvocationTargetException; | |
| import java.lang.reflect.Proxy; | |
| import java.lang.reflect.Method; | |
| import java.rmi.Remote; | |
| import java.rmi.RemoteException; | |
| import java.rmi.StubNotFoundException; | |
| import java.rmi.server.LogStream; | |
| import java.rmi.server.RemoteObjectInvocationHandler; | |
| import java.rmi.server.RemoteRef; | |
| import java.rmi.server.RemoteStub; | |
| import java.rmi.server.Skeleton; | |
| import java.rmi.server.SkeletonNotFoundException; | |
| import java.security.MessageDigest; | |
| import java.security.DigestOutputStream; | |
| import java.security.NoSuchAlgorithmException; | |
| import java.util.ArrayList; | |
| import java.util.Collections; | |
| import java.util.Map; | |
| import java.util.WeakHashMap; | |
| import sun.rmi.runtime.Log; | |
| /** | |
| * A utility class with static methods for creating stubs/proxies and | |
| * skeletons for remote objects. | |
| */ | |
| @SuppressWarnings("deprecation") | |
| public final class Util { | |
| /** "server" package log level */ | |
| static final int logLevel = LogStream.parseLevel(System.getProperty("sun.rmi.server.logLevel")); | |
| /** server reference log */ | |
| public static final Log serverRefLog = | |
| Log.getLog("sun.rmi.server.ref", "transport", Util.logLevel); | |
| /** cached value of property java.rmi.server.ignoreStubClasses */ | |
| private static final boolean ignoreStubClasses = | |
| Boolean.getBoolean("java.rmi.server.ignoreStubClasses"); | |
| /** cache of impl classes that have no corresponding stub class */ | |
| private static final Map<Class<?>, Void> withoutStubs = | |
| Collections.synchronizedMap(new WeakHashMap<Class<?>, Void>(11)); | |
| /** parameter types for stub constructor */ | |
| private static final Class<?>[] stubConsParamTypes = { RemoteRef.class }; | |
| private Util() { | |
| } | |
| /** | |
| * Returns a proxy for the specified implClass. | |
| * | |
| * If both of the following criteria is satisfied, a dynamic proxy for | |
| * the specified implClass is returned (otherwise a RemoteStub instance | |
| * for the specified implClass is returned): | |
| * | |
| * a) either the property java.rmi.server.ignoreStubClasses is true or | |
| * a pregenerated stub class does not exist for the impl class, and | |
| * b) forceStubUse is false. | |
| * | |
| * If the above criteria are satisfied, this method constructs a | |
| * dynamic proxy instance (that implements the remote interfaces of | |
| * implClass) constructed with a RemoteObjectInvocationHandler instance | |
| * constructed with the clientRef. | |
| * | |
| * Otherwise, this method loads the pregenerated stub class (which | |
| * extends RemoteStub and implements the remote interfaces of | |
| * implClass) and constructs an instance of the pregenerated stub | |
| * class with the clientRef. | |
| * | |
| * @param implClass the class to obtain remote interfaces from | |
| * @param clientRef the remote ref to use in the invocation handler | |
| * @param forceStubUse if true, forces creation of a RemoteStub | |
| * @throws IllegalArgumentException if implClass implements illegal | |
| * remote interfaces | |
| * @throws StubNotFoundException if problem locating/creating stub or | |
| * creating the dynamic proxy instance | |
| **/ | |
| public static Remote createProxy(Class<?> implClass, | |
| RemoteRef clientRef, | |
| boolean forceStubUse) | |
| throws StubNotFoundException | |
| { | |
| Class<?> remoteClass; | |
| try { | |
| remoteClass = getRemoteClass(implClass); | |
| } catch (ClassNotFoundException ex ) { | |
| throw new StubNotFoundException( | |
| "object does not implement a remote interface: " + | |
| implClass.getName()); | |
| } | |
| if (forceStubUse || | |
| !(ignoreStubClasses || !stubClassExists(remoteClass))) | |
| { | |
| return createStub(remoteClass, clientRef); | |
| } | |
| final ClassLoader loader = implClass.getClassLoader(); | |
| final Class<?>[] interfaces = getRemoteInterfaces(implClass); | |
| final InvocationHandler handler = | |
| new RemoteObjectInvocationHandler(clientRef); | |
| /* REMIND: private remote interfaces? */ | |
| try { | |
| return (Remote) Proxy.newProxyInstance(loader, interfaces, handler); | |
| } catch (IllegalArgumentException e) { | |
| throw new StubNotFoundException("unable to create proxy", e); | |
| } | |
| } | |
| /** | |
| * Returns true if a stub class for the given impl class can be loaded, | |
| * otherwise returns false. | |
| * | |
| * @param remoteClass the class to obtain remote interfaces from | |
| */ | |
| private static boolean stubClassExists(Class<?> remoteClass) { | |
| if (!withoutStubs.containsKey(remoteClass)) { | |
| try { | |
| Class.forName(remoteClass.getName() + "_Stub", | |
| false, | |
| remoteClass.getClassLoader()); | |
| return true; | |
| } catch (ClassNotFoundException cnfe) { | |
| withoutStubs.put(remoteClass, null); | |
| } | |
| } | |
| return false; | |
| } | |
| /* | |
| * Returns the class/superclass that implements the remote interface. | |
| * @throws ClassNotFoundException if no class is found to have a | |
| * remote interface | |
| */ | |
| private static Class<?> getRemoteClass(Class<?> cl) | |
| throws ClassNotFoundException | |
| { | |
| while (cl != null) { | |
| Class<?>[] interfaces = cl.getInterfaces(); | |
| for (int i = interfaces.length -1; i >= 0; i--) { | |
| if (Remote.class.isAssignableFrom(interfaces[i])) | |
| return cl; // this class implements remote object | |
| } | |
| cl = cl.getSuperclass(); | |
| } | |
| throw new ClassNotFoundException( | |
| "class does not implement java.rmi.Remote"); | |
| } | |
| /** | |
| * Returns an array containing the remote interfaces implemented | |
| * by the given class. | |
| * | |
| * @param remoteClass the class to obtain remote interfaces from | |
| * @throws IllegalArgumentException if remoteClass implements | |
| * any illegal remote interfaces | |
| * @throws NullPointerException if remoteClass is null | |
| */ | |
| private static Class<?>[] getRemoteInterfaces(Class<?> remoteClass) { | |
| ArrayList<Class<?>> list = new ArrayList<>(); | |
| getRemoteInterfaces(list, remoteClass); | |
| return list.toArray(new Class<?>[list.size()]); | |
| } | |
| /** | |
| * Fills the given array list with the remote interfaces implemented | |
| * by the given class. | |
| * | |
| * @throws IllegalArgumentException if the specified class implements | |
| * any illegal remote interfaces | |
| * @throws NullPointerException if the specified class or list is null | |
| */ | |
| private static void getRemoteInterfaces(ArrayList<Class<?>> list, Class<?> cl) { | |
| Class<?> superclass = cl.getSuperclass(); | |
| if (superclass != null) { | |
| getRemoteInterfaces(list, superclass); | |
| } | |
| Class<?>[] interfaces = cl.getInterfaces(); | |
| for (int i = 0; i < interfaces.length; i++) { | |
| Class<?> intf = interfaces[i]; | |
| /* | |
| * If it is a remote interface (if it extends from | |
| * java.rmi.Remote) and is not already in the list, | |
| * then add the interface to the list. | |
| */ | |
| if (Remote.class.isAssignableFrom(intf)) { | |
| if (!(list.contains(intf))) { | |
| Method[] methods = intf.getMethods(); | |
| for (int j = 0; j < methods.length; j++) { | |
| checkMethod(methods[j]); | |
| } | |
| list.add(intf); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Verifies that the supplied method has at least one declared exception | |
| * type that is RemoteException or one of its superclasses. If not, | |
| * then this method throws IllegalArgumentException. | |
| * | |
| * @throws IllegalArgumentException if m is an illegal remote method | |
| */ | |
| private static void checkMethod(Method m) { | |
| Class<?>[] ex = m.getExceptionTypes(); | |
| for (int i = 0; i < ex.length; i++) { | |
| if (ex[i].isAssignableFrom(RemoteException.class)) | |
| return; | |
| } | |
| throw new IllegalArgumentException( | |
| "illegal remote method encountered: " + m); | |
| } | |
| /** | |
| * Creates a RemoteStub instance for the specified class, constructed | |
| * with the specified RemoteRef. The supplied class must be the most | |
| * derived class in the remote object's superclass chain that | |
| * implements a remote interface. The stub class name is the name of | |
| * the specified remoteClass with the suffix "_Stub". The loading of | |
| * the stub class is initiated from class loader of the specified class | |
| * (which may be the bootstrap class loader). | |
| **/ | |
| private static RemoteStub createStub(Class<?> remoteClass, RemoteRef ref) | |
| throws StubNotFoundException | |
| { | |
| String stubname = remoteClass.getName() + "_Stub"; | |
| /* Make sure to use the local stub loader for the stub classes. | |
| * When loaded by the local loader the load path can be | |
| * propagated to remote clients, by the MarshalOutputStream/InStream | |
| * pickle methods | |
| */ | |
| try { | |
| Class<?> stubcl = | |
| Class.forName(stubname, false, remoteClass.getClassLoader()); | |
| Constructor<?> cons = stubcl.getConstructor(stubConsParamTypes); | |
| return (RemoteStub) cons.newInstance(new Object[] { ref }); | |
| } catch (ClassNotFoundException e) { | |
| throw new StubNotFoundException( | |
| "Stub class not found: " + stubname, e); | |
| } catch (NoSuchMethodException e) { | |
| throw new StubNotFoundException( | |
| "Stub class missing constructor: " + stubname, e); | |
| } catch (InstantiationException e) { | |
| throw new StubNotFoundException( | |
| "Can't create instance of stub class: " + stubname, e); | |
| } catch (IllegalAccessException e) { | |
| throw new StubNotFoundException( | |
| "Stub class constructor not public: " + stubname, e); | |
| } catch (InvocationTargetException e) { | |
| throw new StubNotFoundException( | |
| "Exception creating instance of stub class: " + stubname, e); | |
| } catch (ClassCastException e) { | |
| throw new StubNotFoundException( | |
| "Stub class not instance of RemoteStub: " + stubname, e); | |
| } | |
| } | |
| /** | |
| * Locate and return the Skeleton for the specified remote object | |
| */ | |
| static Skeleton createSkeleton(Remote object) | |
| throws SkeletonNotFoundException | |
| { | |
| Class<?> cl; | |
| try { | |
| cl = getRemoteClass(object.getClass()); | |
| } catch (ClassNotFoundException ex ) { | |
| throw new SkeletonNotFoundException( | |
| "object does not implement a remote interface: " + | |
| object.getClass().getName()); | |
| } | |
| // now try to load the skeleton based ont he name of the class | |
| String skelname = cl.getName() + "_Skel"; | |
| try { | |
| Class<?> skelcl = Class.forName(skelname, false, cl.getClassLoader()); | |
| return (Skeleton)skelcl.newInstance(); | |
| } catch (ClassNotFoundException ex) { | |
| throw new SkeletonNotFoundException("Skeleton class not found: " + | |
| skelname, ex); | |
| } catch (InstantiationException ex) { | |
| throw new SkeletonNotFoundException("Can't create skeleton: " + | |
| skelname, ex); | |
| } catch (IllegalAccessException ex) { | |
| throw new SkeletonNotFoundException("No public constructor: " + | |
| skelname, ex); | |
| } catch (ClassCastException ex) { | |
| throw new SkeletonNotFoundException( | |
| "Skeleton not of correct class: " + skelname, ex); | |
| } | |
| } | |
| /** | |
| * Compute the "method hash" of a remote method. The method hash | |
| * is a long containing the first 64 bits of the SHA digest from | |
| * the UTF encoded string of the method name and descriptor. | |
| */ | |
| public static long computeMethodHash(Method m) { | |
| long hash = 0; | |
| ByteArrayOutputStream sink = new ByteArrayOutputStream(127); | |
| try { | |
| MessageDigest md = MessageDigest.getInstance("SHA"); | |
| DataOutputStream out = new DataOutputStream( | |
| new DigestOutputStream(sink, md)); | |
| String s = getMethodNameAndDescriptor(m); | |
| if (serverRefLog.isLoggable(Log.VERBOSE)) { | |
| serverRefLog.log(Log.VERBOSE, | |
| "string used for method hash: \"" + s + "\""); | |
| } | |
| out.writeUTF(s); | |
| // use only the first 64 bits of the digest for the hash | |
| out.flush(); | |
| byte hasharray[] = md.digest(); | |
| for (int i = 0; i < Math.min(8, hasharray.length); i++) { | |
| hash += ((long) (hasharray[i] & 0xFF)) << (i * 8); | |
| } | |
| } catch (IOException ignore) { | |
| /* can't happen, but be deterministic anyway. */ | |
| hash = -1; | |
| } catch (NoSuchAlgorithmException complain) { | |
| throw new SecurityException(complain.getMessage()); | |
| } | |
| return hash; | |
| } | |
| /** | |
| * Return a string consisting of the given method's name followed by | |
| * its "method descriptor", as appropriate for use in the computation | |
| * of the "method hash". | |
| * | |
| * See section 4.3.3 of The Java Virtual Machine Specification for | |
| * the definition of a "method descriptor". | |
| */ | |
| private static String getMethodNameAndDescriptor(Method m) { | |
| StringBuilder desc = new StringBuilder(m.getName()); | |
| desc.append('('); | |
| Class<?>[] paramTypes = m.getParameterTypes(); | |
| for (int i = 0; i < paramTypes.length; i++) { | |
| desc.append(getTypeDescriptor(paramTypes[i])); | |
| } | |
| desc.append(')'); | |
| Class<?> returnType = m.getReturnType(); | |
| if (returnType == void.class) { // optimization: handle void here | |
| desc.append('V'); | |
| } else { | |
| desc.append(getTypeDescriptor(returnType)); | |
| } | |
| return desc.toString(); | |
| } | |
| /** | |
| * Get the descriptor of a particular type, as appropriate for either | |
| * a parameter or return type in a method descriptor. | |
| */ | |
| private static String getTypeDescriptor(Class<?> type) { | |
| if (type.isPrimitive()) { | |
| if (type == int.class) { | |
| return "I"; | |
| } else if (type == boolean.class) { | |
| return "Z"; | |
| } else if (type == byte.class) { | |
| return "B"; | |
| } else if (type == char.class) { | |
| return "C"; | |
| } else if (type == short.class) { | |
| return "S"; | |
| } else if (type == long.class) { | |
| return "J"; | |
| } else if (type == float.class) { | |
| return "F"; | |
| } else if (type == double.class) { | |
| return "D"; | |
| } else if (type == void.class) { | |
| return "V"; | |
| } else { | |
| throw new Error("unrecognized primitive type: " + type); | |
| } | |
| } else if (type.isArray()) { | |
| /* | |
| * According to JLS 20.3.2, the getName() method on Class does | |
| * return the VM type descriptor format for array classes (only); | |
| * using that should be quicker than the otherwise obvious code: | |
| * | |
| * return "[" + getTypeDescriptor(type.getComponentType()); | |
| */ | |
| return type.getName().replace('.', '/'); | |
| } else { | |
| return "L" + type.getName().replace('.', '/') + ";"; | |
| } | |
| } | |
| /** | |
| * Returns the binary name of the given type without package | |
| * qualification. Nested types are treated no differently from | |
| * top-level types, so for a nested type, the returned name will | |
| * still be qualified with the simple name of its enclosing | |
| * top-level type (and perhaps other enclosing types), the | |
| * separator will be '$', etc. | |
| **/ | |
| public static String getUnqualifiedName(Class<?> c) { | |
| String binaryName = c.getName(); | |
| return binaryName.substring(binaryName.lastIndexOf('.') + 1); | |
| } | |
| } |