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