View Javadoc

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