1 /* 2 * Copyright (c) 1998-2004 The Jgroup Team. 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU Lesser General Public License version 2 as 6 * published by the Free Software Foundation. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU Lesser General Public License for more details. 12 * 13 * You should have received a copy of the GNU Lesser General Public License 14 * along with this program; if not, write to the Free Software 15 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 * 17 */ 18 19 package jgroup.relacs.gmi; 20 21 import static jgroup.relacs.gmi.MethodSemantics.ANYCAST; 22 23 import java.io.ByteArrayOutputStream; 24 import java.io.DataOutputStream; 25 import java.io.IOException; 26 import java.io.Serializable; 27 import java.lang.annotation.Annotation; 28 import java.lang.reflect.Method; 29 import java.security.AccessController; 30 import java.security.DigestOutputStream; 31 import java.security.MessageDigest; 32 import java.security.NoSuchAlgorithmException; 33 import java.security.PrivilegedAction; 34 35 /** 36 * @author Hein Meling 37 * @since Jgroup 3.0 38 */ 39 public class MethodDetails 40 implements Serializable 41 { 42 43 //////////////////////////////////////////////////////////////////////////////////////////// 44 // Constants 45 //////////////////////////////////////////////////////////////////////////////////////////// 46 47 private static final long serialVersionUID = -6799041492486905534L; 48 49 50 //////////////////////////////////////////////////////////////////////////////////////////// 51 // Fields 52 //////////////////////////////////////////////////////////////////////////////////////////// 53 54 /** Hash identifier of this method */ 55 private final long hash; 56 57 /** The server object associated with this method (only on server side) */ 58 private final transient Object server; 59 60 /** The actual method object associated with this method (only on server side) */ 61 private final transient Method method; 62 63 /** The method semantics for this method */ 64 private MethodSemantics semantics = null; 65 66 /** Prepared string for toString() to return */ 67 private final String methString; 68 69 70 //////////////////////////////////////////////////////////////////////////////////////////// 71 // Constructor 72 //////////////////////////////////////////////////////////////////////////////////////////// 73 74 /** 75 * Determine the method hash and invocation semantics associated 76 * with the given method and store these values. Also store the 77 * associated server object. 78 * 79 * Note that the provided method object should be obtained from an 80 * interface unless annotations are only declared in the server class. 81 * This is since annotations are not inherited from interfaces 82 * implemented by the server. 83 * 84 * Annotation markers declared in the server class takes precedence over 85 * those that <i>may</i> be declared in the interface. 86 */ 87 MethodDetails(final Method method, final Object server) 88 { 89 /* Store the server object associated with the given method. */ 90 this.server = server; 91 /* Store the method object (used only on the server-side) */ 92 this.method = method; 93 94 /* 95 * Compute the "method hash" of a remote method. The method hash 96 * is a long containing the first 64 bits of the SHA digest from 97 * the UTF encoded string of the method name and descriptor. 98 */ 99 hash = computeHash(method); 100 101 /* 102 * Note that the following lines may seem unecessary, but the Method 103 * object provided to this constructor 'may' be from an interface. 104 * We want to obtain the Method object from the server class since we 105 * then can support annotation markers directly in the server implementation, 106 * and they do not have to appear in the interface. 107 */ 108 Method srvMethod; 109 try { 110 Class[] params = method.getParameterTypes(); 111 srvMethod = server.getClass().getMethod(method.getName(), params); 112 } catch (NoSuchMethodException e) { 113 // Ignored; cannot happen unless things where not compiled correctly. 114 throw new Error("Should never happen", e); 115 } 116 117 /* 118 * Determine the method invocation semantics based on the defined annotations. 119 */ 120 Annotation[] ann = srvMethod.getAnnotations(); 121 if (ann.length == 0) { 122 /* 123 * Fallback to the interface method declaration and check if there 124 * are annotations declared in the interface instead. 125 */ 126 ann = method.getAnnotations(); 127 } 128 for (Annotation a : ann) { 129 MethodSemantics s = MethodSemantics.getInvocationSemantics(a.annotationType()); 130 if (s != null) { 131 if (semantics == null) 132 semantics = s; 133 else 134 throw new Error("Cannot declare multiple semantics for a single method: " 135 + method.getName() + " in server: " + server.getClass().getSimpleName()); 136 } 137 } 138 // If no annotation marker is given, fallback to default 139 if (semantics == null) 140 semantics = ANYCAST; 141 142 /* Buffer used to prepare string for this method's details */ 143 StringBuilder buf = new StringBuilder(); 144 buf.append(semantics); 145 buf.append(" "); 146 buf.append(method.getReturnType().getSimpleName()); 147 buf.append(" "); 148 buf.append(method.getName()); 149 buf.append("(), hash="); 150 buf.append(hash); 151 methString = buf.toString(); 152 } 153 154 155 //////////////////////////////////////////////////////////////////////////////////////////// 156 // Access methods 157 //////////////////////////////////////////////////////////////////////////////////////////// 158 159 /** 160 * Return the hash value associated to this method. 161 */ 162 public long getHash() 163 { 164 return hash; 165 } 166 167 /** 168 * Return the server object associated to this method. This method 169 * should only be used on the server-side. 170 */ 171 public Object getServer() 172 { 173 return server; 174 } 175 176 /** 177 * Returns the actual method object associated with this method details 178 * object. This method should only be used on the server-side. 179 */ 180 public Method getMethod() 181 { 182 return method; 183 } 184 185 /** 186 * Return the invocation semantics associated with this method. 187 */ 188 public MethodSemantics getInvocationSemantics() 189 { 190 return semantics; 191 } 192 193 /** 194 * Returns a string representation of this method object. 195 */ 196 public String toString() 197 { 198 return methString; 199 } 200 201 202 //////////////////////////////////////////////////////////////////////////////////////////// 203 // Private methods 204 //////////////////////////////////////////////////////////////////////////////////////////// 205 206 /** 207 * Compute the "method hash" of a remote method. The method hash 208 * is a long containing the first 64 bits of the SHA digest from 209 * the UTF encoded string of the method name and descriptor. 210 */ 211 @SuppressWarnings("unchecked") 212 static long computeHash(final Method m) 213 { 214 /* 215 * Set this Method object to override language access checks so that 216 * the dispatcher can invoke methods from non-public remote interfaces. 217 */ 218 AccessController.doPrivileged(new PrivilegedAction() { 219 public Object run() { 220 m.setAccessible(true); 221 return null; 222 } 223 }); 224 225 long hash = 0; 226 ByteArrayOutputStream sink = new ByteArrayOutputStream(127); 227 try { 228 MessageDigest md = MessageDigest.getInstance("SHA"); 229 DataOutputStream out = new DataOutputStream( 230 new DigestOutputStream(sink, md)); 231 232 String s = getMethodNameAndDescriptor(m); 233 234 out.writeUTF(s); 235 236 // use only the first 64 bits of the digest for the hash 237 out.flush(); 238 byte hasharray[] = md.digest(); 239 for (int i = 0; i < Math.min(8, hasharray.length); i++) { 240 hash += ((long) (hasharray[i] & 0xFF)) << (i * 8); 241 } 242 } catch (IOException ignore) { 243 /* can't happen, but be deterministic anyway. */ 244 hash = -1; 245 } catch (NoSuchAlgorithmException complain) { 246 throw new SecurityException(complain.getMessage()); 247 } 248 return hash; 249 } 250 251 252 /** 253 * Return a string consisting of the given method's name followed by 254 * its "method descriptor", as appropriate for use in the computation 255 * of the "method hash". 256 * 257 * See section 4.3.3 of The Java Virtual Machine Specification for 258 * the definition of a "method descriptor". 259 */ 260 private static String getMethodNameAndDescriptor(Method m) 261 { 262 StringBuilder desc = new StringBuilder(m.getName()); 263 desc.append("("); 264 Class[] paramTypes = m.getParameterTypes(); 265 for (int i = 0; i < paramTypes.length; i++) { 266 desc.append(getTypeDescriptor(paramTypes[i])); 267 } 268 desc.append(")"); 269 Class returnType = m.getReturnType(); 270 if (returnType == void.class) { // optimization: handle void here 271 desc.append("V"); 272 } else { 273 desc.append(getTypeDescriptor(returnType)); 274 } 275 return desc.toString(); 276 } 277 278 /** 279 * Get the descriptor of a particular type, as appropriate for either 280 * a parameter or return type in a method descriptor. 281 */ 282 private static String getTypeDescriptor(Class type) 283 { 284 if (type.isPrimitive()) { 285 if (type == int.class) { 286 return "I"; 287 } else if (type == boolean.class) { 288 return "Z"; 289 } else if (type == byte.class) { 290 return "B"; 291 } else if (type == char.class) { 292 return "C"; 293 } else if (type == short.class) { 294 return "S"; 295 } else if (type == long.class) { 296 return "J"; 297 } else if (type == float.class) { 298 return "F"; 299 } else if (type == double.class) { 300 return "D"; 301 } else if (type == void.class) { 302 return "V"; 303 } else { 304 throw new Error("unrecognized primitive type: " + type); 305 } 306 } else if (type.isArray()) { 307 /* 308 * According to JLS 20.3.2, the getName() method on Class does 309 * return the VM type descriptor format for array classes (only); 310 * using that should be quicker than the otherwise obvious code: 311 * 312 * return "[" + getTypeDescriptor(type.getComponentType()); 313 */ 314 return type.getName().replace('.', '/'); 315 } else { 316 return "L" + type.getName().replace('.', '/') + ";"; 317 } 318 } 319 320 } // END MethodDetails