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