/* * 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 com.sun.jini.jeri.internal.runtime; import com.sun.jini.collection.WeakIdentityMap; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.ObjectInput; import java.io.ObjectOutput; import java.lang.ref.SoftReference; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.URL; import java.rmi.MarshalledObject; import java.rmi.Remote; import java.rmi.RemoteException; import java.rmi.activation.ActivationID; import java.security.DigestOutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Permission; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.HashSet; import java.util.Map; import java.util.WeakHashMap; import java.net.InetAddress; import java.rmi.server.ServerNotActiveException; import javax.security.auth.Subject; import net.jini.export.ServerContext; import net.jini.io.context.ClientHost; import net.jini.io.context.ClientSubject; import net.jini.io.context.ContextPermission; import net.jini.io.context.IntegrityEnforcement; import net.jini.security.proxytrust.TrustEquivalence; /** * Utility methods for implementing custom remote reference types. * * @author Sun Microsystems, Inc. * */ public class Util { /** cache of tables mapping methods to hashes */ private static TableCache methodToHash_TableCache = new TableCache(true); /** cache of valid proxy remote methods */ private static Map proxyRemoteMethodCache = new WeakHashMap(); /** parameter types for activatable constructor or activate method */ private static Class[] paramTypes = { ActivationID.class, MarshalledObject.class }; /** name of the resource containing prohibited proxy interfaces */ private static final String prohibitedProxyInterfacesResource = "com/sun/jini/proxy/resources/" + "InvocationHandler.moreProhibitedProxyInterfaces"; /** names of interfaces that proxies are prohibited from implementing */ private static final Collection prohibitedProxyInterfaces = getProhibitedProxyInterfaces(); /** * Appends the current thread's stack trace to the stack trace of the * given exception. * * This method is used for exceptions that have been unmarshalled as the * exceptional result of a remote method invocation, so that the * client-side stack trace gets recorded in the exception (while * preserving the server-side stack trace in the exception as well). * * Note that the (somewhat odd) names of this method and the method that * this one internally delegates to are significant because these methods * visually highlight the remote call boundary between the server-side * and the client-side portions to readers of the combined stack trace. */ public static void exceptionReceivedFromServer(Throwable t) { __________EXCEPTION_RECEIVED_FROM_SERVER__________(t); } private static void __________EXCEPTION_RECEIVED_FROM_SERVER__________( Throwable t) { StackTraceElement[] serverTrace = t.getStackTrace(); StackTraceElement[] clientTrace = (new Throwable()).getStackTrace(); StackTraceElement[] combinedTrace = new StackTraceElement[serverTrace.length + clientTrace.length]; System.arraycopy(serverTrace, 0, combinedTrace, 0, serverTrace.length); System.arraycopy(clientTrace, 0, combinedTrace, serverTrace.length, clientTrace.length); t.setStackTrace(combinedTrace); } /** * Clear the stack trace of the given exception by replacing it with * an empty StackTraceElement array, and do the same for all of its * chained causative exceptions. * * This method is used when it is desired for the stack trace data of * an exception to be suppressed before the exception gets marshalled * to a remote virtual machine, perhaps for reasons of confidentiality * or performance. */ public static void clearStackTraces(Throwable t) { StackTraceElement[] empty = new StackTraceElement[0]; while (t != null) { t.setStackTrace(empty); t = t.getCause(); } } /** * Marshals <code>value</code> to an <code>ObjectOutput</code> stream, * <code>out</code>, using RMI's serialization format for arguments or * return values. For primitive types, the primitive type's class should * be specified (i.e., for the type <code>int</code>, specify * <code>int.class</code>), and the primitive value should be wrapped in * instances of the appropriate wrapper class, such as * <code>java.lang.Integer</code> or <code>java.lang.Boolean</code>. * * @param type <code>Class</code> object for the value to be marshalled * @param value value to marshal * @param out stream to which the value is marshalled * @throws IOException if an I/O error occurs marshalling * the value to the output stream */ public static void marshalValue(Class type, Object value, ObjectOutput out) throws IOException { if (type.isPrimitive()) { if (type == int.class) { out.writeInt(((Integer) value).intValue()); } else if (type == boolean.class) { out.writeBoolean(((Boolean) value).booleanValue()); } else if (type == byte.class) { out.writeByte(((Byte) value).byteValue()); } else if (type == char.class) { out.writeChar(((Character) value).charValue()); } else if (type == short.class) { out.writeShort(((Short) value).shortValue()); } else if (type == long.class) { out.writeLong(((Long) value).longValue()); } else if (type == float.class) { out.writeFloat(((Float) value).floatValue()); } else if (type == double.class) { out.writeDouble(((Double) value).doubleValue()); } else { throw new AssertionError( "Unrecognized primitive type: " + type); } } else { out.writeObject(value); } } /** * Unmarshals a value of the specified <code>type</code> from the * <code>ObjectInput</code> stream, <code>in</code>, using RMI's * serialization format for arguments or return values and returns the * result. For primitive types, the primitive type's class should be * specified (i.e., for the primitive type <code>int</code>, specify * <code>int.class</code>). * * @param type <code>Class</code> object for the value to be unmarshalled * @param in stream from which the value is unmarshalled * @return value unmarshalled from the input stream * @throws IOException if an I/O error occurs marshalling * the value to the output stream * @throws ClassNotFoundException if the <code>type</code>'s * class could not be found */ public static Object unmarshalValue(Class type, ObjectInput in) throws IOException, ClassNotFoundException { if (type.isPrimitive()) { if (type == int.class) { return new Integer(in.readInt()); } else if (type == boolean.class) { return Boolean.valueOf(in.readBoolean()); } else if (type == byte.class) { return new Byte(in.readByte()); } else if (type == char.class) { return new Character(in.readChar()); } else if (type == short.class) { return new Short(in.readShort()); } else if (type == long.class) { return new Long(in.readLong()); } else if (type == float.class) { return new Float(in.readFloat()); } else if (type == double.class) { return new Double(in.readDouble()); } else { throw new AssertionError( "Unrecognized primitive type: " + type); } } else { return in.readObject(); } } /** * Computes the "method hash" of a remote method, <code>m</code>. The * method hash is a <code>long</code> containing the first 64 bits of the * SHA digest from the UTF encoded string of the method name followed by * its "method descriptor". See section 4.3.3 of The Java(TM) Virtual * Machine Specification for the definition of a "method descriptor". * * @param m remote method * @return the method hash */ private 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); 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; } /** * Returns the method hash for the method <code>m</code>. Subsequent * calls to <code>getMethodHash</code> passing the same method argument * should be faster since this method caches internally the result of the * method to method hash mapping. The method hash is calculated using the * <code>computeMethodHash</code> method. * * @param m the remote method * @return the method hash for the method <code>m</code> */ public static long getMethodHash(Method m) { Map table = methodToHash_TableCache.getTable(m.getDeclaringClass()); Long hash = (Long) table.get(m); return hash.longValue(); } private static class TableCache extends WeakHashMap { /** * if true, the tables map methods to method hashes; if false, * the tables map method hashes to methods */ private boolean mapsMethodToHash; public TableCache(boolean mapsMethodToHash) { super(); this.mapsMethodToHash = mapsMethodToHash; } public Map getTable(Class remoteClass) { SoftReference[] tableRef; /* * Method tables for remote classes are cached in a hash table * using weak references to hold the Class object keys, so that * the cache does not prevent the class from being unloaded, and * using soft references to hold the values, so that the computed * method tables will generally persist while no objects of the * remote class are exported, but their storage may be reclaimed * if necessary, and accidental reachability of the remote class * through its interfaces is avoided. */ synchronized (this) { /* * Look up class in cache; add entry if not found. */ tableRef = (SoftReference[]) get(remoteClass); if (tableRef == null) { tableRef = new SoftReference[] { null }; put(remoteClass, tableRef); } } /* * Check cached reference to method table for this class; * if it is null, go and create the table. */ synchronized (tableRef) { Map table = null; if (tableRef[0] != null) { table = (Map) tableRef[0].get(); } if (table == null) { if (mapsMethodToHash) { /* * REMIND: if we hand out this table directly, via a * public API, we need to make this map "unmodifiable". */ table = new LazyMethodToHash_Map(); } else { throw new UnsupportedOperationException(); } tableRef[0] = new SoftReference(table); } return table; } } } /** * 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); } private static class LazyMethodToHash_Map extends WeakHashMap { public LazyMethodToHash_Map() { super(); } public synchronized Object get(Object key) { Object hash = super.get(key); if (hash == null) { Method method = (Method) key; hash = new Long(computeMethodHash(method)); put(method, hash); } return (Long) hash; } } /* * The following static methods are related to the creation of the * "method table" for a remote class, which maps method hashes to * the appropriate Method objects for the class's remote methods. */ /** * Returns 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(TM) Virtual Machine Specification for * the definition of a "method descriptor". */ public static String getMethodNameAndDescriptor(Method m) { StringBuffer desc = new StringBuffer(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(); } /** * Returns the descriptor of a particular type, as appropriate for either * a parameter type 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 virtual machine 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 an array containing the remote interfaces implemented * by the given class. * * @throws IllegalArgumentException if remoteClass implements * any illegal remote interfaces * @throws NullPointerException if remoteClass is null */ public static Class[] getRemoteInterfaces(Class remoteClass) { ArrayList list = new ArrayList(); getRemoteInterfaces(list, remoteClass); return (Class []) 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 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); } } } } /** * Throws IllegalArgumentException if any superinterface of c declares a * method with the same name and parameter types as m that does not * declare RemoteException or a superclass in its throws clause, or if * any superinterface of c has its name in prohibitedProxyInterfaces. */ public static void checkProxyRemoteMethod(Class c, Method m) { WeakIdentityMap map; synchronized (proxyRemoteMethodCache) { SoftReference ref = (SoftReference) proxyRemoteMethodCache.get(c); map = (ref == null) ? null : (WeakIdentityMap) ref.get(); if (map == null && ref != null) { map = new WeakIdentityMap(); proxyRemoteMethodCache.put(c, new SoftReference(map)); } } if (map == null) { checkProhibitedProxyInterfaces(c); synchronized (proxyRemoteMethodCache) { SoftReference ref = (SoftReference) proxyRemoteMethodCache.get(c); map = (ref == null) ? null : (WeakIdentityMap) ref.get(); if (map == null) { map = new WeakIdentityMap(); proxyRemoteMethodCache.put(c, new SoftReference(map)); } } } synchronized (map) { if (map.get(m) != null) { return; } } checkExceptions(c, m.getName(), m.getParameterTypes()); synchronized (map) { map.put(m, Boolean.TRUE); } } /** * Throws IllegalArgumentException if any superinterface of c declares a * method with the given name and parameter types that does not declare * RemoteException or a superclass in its throws clause. */ private static void checkExceptions(Class c, String name, Class[] types) { Class[] ifaces = c.getInterfaces(); for (int i = ifaces.length; --i >= 0; ) { try { checkMethod(ifaces[i].getMethod(name, types)); checkExceptions(ifaces[i], name, types); } catch (NoSuchMethodException e) { } } } /** * Returns collection of prohibited proxy interfaces read from resources. */ private static Collection getProhibitedProxyInterfaces() { Collection names = new HashSet(); names.add("javax.management.MBeanServerConnection"); Enumeration enum; try { enum = ClassLoader.getSystemResources( prohibitedProxyInterfacesResource); } catch (IOException e) { throw new ExceptionInInitializerError( new IOException( "problem getting resources: " + prohibitedProxyInterfacesResource).initCause(e)); } while (enum.hasMoreElements()) { URL url = (URL) enum.nextElement(); try { InputStream in = url.openStream(); try { BufferedReader r = new BufferedReader(new InputStreamReader(in, "utf-8")); while (true) { String s = r.readLine(); if (s == null) { break; } int i = s.indexOf('#'); if (i >= 0) { s = s.substring(0, i); } s = s.trim(); int n = s.length(); if (n == 0) { continue; } char prev = '.'; for (i = 0; i < n; i++) { char c = s.charAt(i); if (prev == '.' ? !Character.isJavaIdentifierStart(c) : !(Character.isJavaIdentifierPart(c) || (c == '.' && i < n - 1))) { throw new ExceptionInInitializerError( "illegal interface name in " + url + ": " + s); } prev = c; } names.add(s); } } finally { try { in.close(); } catch (IOException e) { } } } catch (IOException e) { throw new ExceptionInInitializerError( new IOException("problem reading " + url).initCause(e)); } } return names; } /** * Throws IllegalArgumentException if any superinterface of c has its * name in prohibitedProxyInterfaces. */ private static void checkProhibitedProxyInterfaces(Class c) { Class[] ifaces = c.getInterfaces(); for (int i = ifaces.length; --i >= 0; ) { String name = ifaces[i].getName(); if (prohibitedProxyInterfaces.contains(name)) { throw new IllegalArgumentException( "prohibited proxy interface encountered: " + name); } checkProhibitedProxyInterfaces(ifaces[i]); } } /** * 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); } /** * Returns true either if both arguments are null or if an * invocation of Object.equals on "subject" with "object" as the * argument returns true; returns false otherwise; **/ public static boolean equals(Object subject, Object object) { return subject == null ? object == null : subject.equals(object); } /** * Returns true either if both arguments are null or if both * arguments refer to objects of the same class and an invocation * of Object.equals on "subject" with "object" as the argument * returns true; returns false otherwise. * * This method is used to compare to possibly-null references for * object equality when neither object's class is trusted, with * the restriction that only objects of the same class can be * considered equal. **/ public static boolean sameClassAndEquals(Object subject, Object object) { return subject == null ? object == null : object != null && subject.getClass() == object.getClass() && subject.equals(object); } /** * Returns true either if both arguments are null of if "subject" * is an instance of TrustEquivalence and an invocation of * TrustEquivalence.checkTrustEquivalence on "subject" with * "object" as the argument returns true; returns false otherwise. **/ public static boolean checkTrustEquivalence(Object subject, Object object) { return subject == null ? object == null : subject instanceof TrustEquivalence && ((TrustEquivalence) subject).checkTrustEquivalence(object); } /** * Returns true if proxy2 is a generated Proxy (proxy1 is assumed to * be one) and the classes of both proxies implement the same ordered * list of interfaces, and returns false otherwise. */ public static boolean sameProxyClass(Object proxy1, Object proxy2) { return (proxy1.getClass() == proxy2.getClass() || (Proxy.isProxyClass(proxy2.getClass()) && equalInterfaces(proxy1, proxy2))); } /** * Returns true if the interfaces implemented by obj1's class * are the same (and in the same order) as obj2's class. */ public static boolean equalInterfaces(Object obj1, Object obj2) { Class[] intf1 = obj1.getClass().getInterfaces(); Class[] intf2 = obj2.getClass().getInterfaces(); if (intf1.length != intf2.length) { return false; } else { for (int i = 0; i < intf1.length; i++) { if (intf1[i] != intf2[i]) { return false; } } return true; } } public static void populateContext(Collection context, InetAddress addr) { if (context == null) { throw new NullPointerException("context is null"); } if (addr != null) { context.add(new ClientHostImpl(addr)); } } public static void populateContext(Collection context, Subject s) { if (context == null) { throw new NullPointerException("context is null"); } context.add(new ClientSubjectImpl(s)); } public static void populateContext(Collection context, boolean integrity) { if (context == null) { throw new NullPointerException("context is null"); } context.add(new IntegrityEnforcementImpl(integrity)); } private static class ClientHostImpl implements ClientHost { private final InetAddress addr; public ClientHostImpl(InetAddress addr) { this.addr = addr; } public InetAddress getClientHost() { return addr; } } private static class ClientSubjectImpl implements ClientSubject { private final Subject s; private static final Permission getClientSubjectPerm = new ContextPermission("net.jini.io.context.ClientSubject.getClientSubject"); public ClientSubjectImpl(Subject s) { this.s = s; } public Subject getClientSubject() { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(getClientSubjectPerm); } return s; } } private static class IntegrityEnforcementImpl implements IntegrityEnforcement { private final boolean integrity; public IntegrityEnforcementImpl(boolean integrity) { this.integrity = integrity; } public boolean integrityEnforced() { return integrity; } } public static InetAddress getClientHost() throws ServerNotActiveException { ClientHost ch = (ClientHost) ServerContext.getServerContextElement(ClientHost.class); return (ch != null) ? ch.getClientHost() : null; } public static String getClientHostString() throws ServerNotActiveException { InetAddress addr = getClientHost(); return (addr != null) ? addr.toString() : null; } public static Subject getClientSubject() throws ServerNotActiveException { ClientSubject cs = (ClientSubject) ServerContext.getServerContextElement(ClientSubject.class); return (cs != null) ? cs.getClientSubject() : null; } /** * Check for permission to access the package of the specified class. * * @throws SecurityException if a security manager exists and invoking * its <code>checkPackageAccess</code> method with the package name of * the specified class throws a <code>SecurityException</code> */ public static void checkPackageAccess(Class type) { SecurityManager security = System.getSecurityManager(); if (security != null) { String name = type.getName(); int i = name.lastIndexOf('.'); if (i != -1) { security.checkPackageAccess(name.substring(0, i)); } } } }