View Javadoc

1   /*
2    * Copyright (c) 1998-2002 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.rmi;
20  
21  import java.io.ByteArrayOutputStream;
22  import java.io.DataOutputStream;
23  import java.io.IOException;
24  import java.lang.reflect.Method;
25  import java.security.AccessController;
26  import java.security.DigestOutputStream;
27  import java.security.MessageDigest;
28  import java.security.NoSuchAlgorithmException;
29  import java.security.PrivilegedAction;
30  import java.util.HashMap;
31  import java.util.Iterator;
32  
33  import jgroup.core.InternalGMIListener;
34  
35  /**
36   *  Helper class usueful to associate a "method hash" to each of the
37   *  methods contained in a set of interfaces. The method hash is a long
38   *  containing the first 64 bits of the SHA digest from the UTF encoded
39   *  string of the method name and descriptor. The table maintains two
40   *  hash table, one for methods and another for hash numbers.
41   *
42   *  (Partly copied from Java 2 SDK 1.3)
43   *
44   *  @author Alberto Montresor
45   *  @author Hein Meling
46   *  @since Jgroup 0.9
47   */
48  public final class ServerMethodTable
49  {
50    ////////////////////////////////////////////////////////////////////////////////////////////
51    // Fields
52    ////////////////////////////////////////////////////////////////////////////////////////////
53  
54    private HashMap   table;
55    private HashMap   invertedTable;
56    private HashMap   serverTable;
57  
58  
59    ////////////////////////////////////////////////////////////////////////////////////////////
60    // Constructors
61    ////////////////////////////////////////////////////////////////////////////////////////////
62  
63    /**
64     *  Creates a new <code>ServerMethodTable</code> instance, with empty
65     *  tables.
66     */
67    public ServerMethodTable()
68    {
69      table = new HashMap();
70      invertedTable = new HashMap();
71      serverTable = new HashMap();
72    }
73  
74  
75    ////////////////////////////////////////////////////////////////////////////////////////////
76    // Access methods
77    ////////////////////////////////////////////////////////////////////////////////////////////
78  
79    /**
80     *  Add methods from the given GMI interface implemented by the
81     *  provided server.  For each remote method in
82     *  the given GMI interfaces, a mapping is created between a hash of the
83     *  method and the java.lang.reflect.Method object (and the reverse
84     *  mapping).  In addition, the hash of the method is also associated
85     *  with the server object that is actually implementing these
86     *  methods.  The latter is used to enable multiple listeners (servers/layers)
87     *  to receive method invocations, through the same EGMI/IGMI layer.
88     */
89    public void addMethods(Object server, Class gmiListenerClass)
90    {
91      /*
92       * Determine the GMI listener interfaces implemented by the
93       * server object, and compute relevant method information.
94       */
95      Class[] interfaces = server.getClass().getInterfaces();
96      for (int i = 0; i < interfaces.length; i++) {
97        if (gmiListenerClass.isAssignableFrom(interfaces[i])) {
98          Method methods[] = interfaces[i].getMethods();
99          for (int j = 0; j < methods.length; j++) {
100           /*
101            * Special handling for InternalGMI interfaces; they are not
102            * allowed to have other return types than Object or void.
103            * This is since the return type from the IGMI proxy is an
104            * array of values.  That is, the Object class can also
105            * encapsulate an Object[], and thus IGMI is allowed to return
106            * multiple values.
107            */
108           if (gmiListenerClass.equals(InternalGMIListener.class)) {
109             Class retType = methods[j].getReturnType();
110             if (!(retType.toString().equals("void") || retType.equals(Object.class))) {
111               throw new IllegalArgumentException(
112                 "Return type for methods in the internal interfaces must be Object or void:\n"
113                 + methods[j]);
114             }
115           }
116           String name = getMethodNameAndDescriptor(methods[j]);
117           Long hashLong = new Long(computeInfo(methods[j]));
118           table.put(hashLong, methods[j]);
119           serverTable.put(hashLong, server);
120           invertedTable.put(name, hashLong);
121         }
122       }
123     }
124   }
125 
126 
127   /**
128    *  Get the method associated with the given hash value.
129    */
130   public Method get(long hash)
131   {
132     Long hashLong = new Long(hash);
133     return (Method) table.get(hashLong);
134   }
135 
136 
137   /**
138    *  Get the hash value associated with the given method.
139    */
140   public long get(Method m)
141   {
142     Long hashLong = (Long) invertedTable.get(getMethodNameAndDescriptor(m));
143     return hashLong.longValue();
144   }
145 
146 
147   /**
148    *  Get the server associated with the given hash value representing a
149    *  method.
150    */
151   public Object getServer(long hash)
152   {
153     return serverTable.get(new Long(hash));
154   }
155 
156 
157   ////////////////////////////////////////////////////////////////////////////////////////////
158   // Object methods
159   ////////////////////////////////////////////////////////////////////////////////////////////
160 
161   public String toString()
162   {
163     StringBuilder buf = new StringBuilder("ServerMethodTable[");
164     for (Iterator iter = table.keySet().iterator(); iter.hasNext();) {
165       Long hash = (Long) iter.next();
166       buf.append("\n");
167       buf.append(hash);
168       buf.append(" -> ");
169       buf.append(((Method) table.get(hash)).getName());
170       buf.append("; server=");
171       buf.append(serverTable.get(hash).getClass().getName());
172     }
173     buf.append("]");
174     return buf.toString();
175   }
176 
177 
178   ////////////////////////////////////////////////////////////////////////////////////////////
179   // Static methods
180   ////////////////////////////////////////////////////////////////////////////////////////////
181 
182   /**
183    * Computes and returns a hash of the given method. Used when 
184    * serializing remote invocations.
185    */
186   static long computeInfo(final Method method)
187   {
188     /*
189      * Set this Method object to override language
190      * access checks so that the dispatcher can invoke
191      * methods from non-public remote interfaces.
192      */
193     AccessController.doPrivileged(new PrivilegedAction() {
194       public Object run() {
195         method.setAccessible(true);
196         return null;
197       }
198     });
199 
200     return computeHash(method);
201   }
202 
203   /**
204    * Compute the "method hash" of a remote method.  The method hash
205    * is a long containing the first 64 bits of the SHA digest from
206    * the UTF encoded string of the method name and descriptor.
207    */
208   static long computeHash(Method m)
209   {
210     long hash = 0;
211     ByteArrayOutputStream sink = new ByteArrayOutputStream(127);
212     try {
213       MessageDigest md = MessageDigest.getInstance("SHA");
214       DataOutputStream out = new DataOutputStream(
215         new DigestOutputStream(sink, md));
216 
217       String s = getMethodNameAndDescriptor(m);
218 
219       out.writeUTF(s);
220 
221       // use only the first 64 bits of the digest for the hash
222       out.flush();
223       byte hasharray[] = md.digest();
224       for (int i = 0; i < Math.min(8, hasharray.length); i++) {
225         hash += ((long) (hasharray[i] & 0xFF)) << (i * 8);
226       }
227     } catch (IOException ignore) {
228       /* can't happen, but be deterministic anyway. */
229       hash = -1;
230     } catch (NoSuchAlgorithmException complain) {
231       throw new SecurityException(complain.getMessage());
232     }
233     return hash;
234   }
235 
236 
237   /**
238    * Return a string consisting of the given method's name followed by
239    * its "method descriptor", as appropriate for use in the computation
240    * of the "method hash".
241    *
242    * See section 4.3.3 of The Java Virtual Machine Specification for
243    * the definition of a "method descriptor".
244    */
245   static String getMethodNameAndDescriptor(Method m)
246   {
247     StringBuilder desc = new StringBuilder(m.getName());
248     desc.append("(");
249     Class[] paramTypes = m.getParameterTypes();
250     for (int i = 0; i < paramTypes.length; i++) {
251       desc.append(getTypeDescriptor(paramTypes[i]));
252     }
253     desc.append(")");
254     Class returnType = m.getReturnType();
255     if (returnType == void.class) { // optimization: handle void here
256       desc.append("V");
257     } else {
258       desc.append(getTypeDescriptor(returnType));
259     }
260     return desc.toString();
261   }
262 
263   /**
264    * Get the descriptor of a particular type, as appropriate for either
265    * a parameter or return type in a method descriptor.
266    */
267   static String getTypeDescriptor(Class type)
268   {
269     if (type.isPrimitive()) {
270       if (type == int.class) {
271         return "I";
272       } else if (type == boolean.class) {
273         return "Z";
274       } else if (type == byte.class) {
275         return "B";
276       } else if (type == char.class) {
277         return "C";
278       } else if (type == short.class) {
279         return "S";
280       } else if (type == long.class) {
281         return "J";
282       } else if (type == float.class) {
283         return "F";
284       } else if (type == double.class) {
285         return "D";
286       } else if (type == void.class) {
287         return "V";
288       } else {
289         throw new Error("unrecognized primitive type: " + type);
290       }
291     } else if (type.isArray()) {
292       /*
293        * According to JLS 20.3.2, the getName() method on Class does
294        * return the VM type descriptor format for array classes (only);
295        * using that should be quicker than the otherwise obvious code:
296        *
297        *     return "[" + getTypeDescriptor(type.getComponentType());
298        */
299       return type.getName().replace('.', '/');
300     } else {
301       return "L" + type.getName().replace('.', '/') + ";";
302     }
303   }
304 
305 } // END ServerMethodTable