001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.user.server.rpc;
017:
018: import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
019: import com.google.gwt.user.client.rpc.RemoteService;
020: import com.google.gwt.user.client.rpc.SerializationException;
021: import com.google.gwt.user.server.rpc.impl.LegacySerializationPolicy;
022: import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader;
023: import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter;
024:
025: import java.lang.reflect.InvocationTargetException;
026: import java.lang.reflect.Method;
027: import java.util.Arrays;
028: import java.util.HashMap;
029: import java.util.HashSet;
030: import java.util.Map;
031: import java.util.Set;
032:
033: /**
034: * Utility class for integrating with the RPC system. This class exposes methods
035: * for decoding of RPC requests, encoding of RPC responses, and invocation of
036: * RPC calls on service objects. The operations exposed by this class can be
037: * reused by framework implementors such as Spring and G4jsf to support a wide
038: * range of service invocation policies.
039: *
040: * <h3>Canonical Example</h3>
041: * The following example demonstrates the canonical way to use this class.
042: *
043: * {@example com.google.gwt.examples.rpc.server.CanonicalExample#processCall(String)}
044: *
045: * <h3>Advanced Example</h3>
046: * The following example shows a more advanced way of using this class to create
047: * an adapter between GWT RPC entities and POJOs.
048: *
049: * {@example com.google.gwt.examples.rpc.server.AdvancedExample#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
050: */
051: public final class RPC {
052:
053: /**
054: * Maps primitive wrapper classes to their corresponding primitive class.
055: */
056: private static final Map<Class<?>, Class<?>> PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS = new HashMap<Class<?>, Class<?>>();
057:
058: /**
059: * Static map of classes to sets of interfaces (e.g. classes). Optimizes
060: * lookup of interfaces for security.
061: */
062: private static Map<Class<?>, Set<String>> serviceToImplementedInterfacesMap;
063:
064: private static final HashMap<String, Class<?>> TYPE_NAMES;
065:
066: static {
067: PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Boolean.class,
068: Boolean.TYPE);
069: PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Byte.class,
070: Byte.TYPE);
071: PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Character.class,
072: Character.TYPE);
073: PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Double.class,
074: Double.TYPE);
075: PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Float.class,
076: Float.TYPE);
077: PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Integer.class,
078: Integer.TYPE);
079: PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Long.class,
080: Long.TYPE);
081: PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Short.class,
082: Short.TYPE);
083:
084: TYPE_NAMES = new HashMap<String, Class<?>>();
085: TYPE_NAMES.put("Z", boolean.class);
086: TYPE_NAMES.put("B", byte.class);
087: TYPE_NAMES.put("C", char.class);
088: TYPE_NAMES.put("D", double.class);
089: TYPE_NAMES.put("F", float.class);
090: TYPE_NAMES.put("I", int.class);
091: TYPE_NAMES.put("J", long.class);
092: TYPE_NAMES.put("S", short.class);
093:
094: serviceToImplementedInterfacesMap = new HashMap<Class<?>, Set<String>>();
095: }
096:
097: /**
098: * Returns an {@link RPCRequest} that is built by decoding the contents of an
099: * encoded RPC request.
100: *
101: * <p>
102: * This method is equivalent to calling {@link #decodeRequest(String, Class)}
103: * with <code>null</code> for the type parameter.
104: * </p>
105: *
106: * @param encodedRequest a string that encodes the {@link RemoteService}
107: * interface, the service method to call, and the arguments to for
108: * the service method
109: * @return an {@link RPCRequest} instance
110: *
111: * @throws IncompatibleRemoteServiceException if any of the following
112: * conditions apply:
113: * <ul>
114: * <li>if the types in the encoded request cannot be deserialized</li>
115: * <li>if the {@link ClassLoader} acquired from
116: * <code>Thread.currentThread().getContextClassLoader()</code>
117: * cannot load the service interface or any of the types specified
118: * in the encodedRequest</li>
119: * <li>the requested interface is not assignable to
120: * {@link RemoteService}</li>
121: * <li>the service method requested in the encodedRequest is not a
122: * member of the requested service interface</li>
123: * <li>the type parameter is not <code>null</code> and is not
124: * assignable to the requested {@link RemoteService} interface
125: * </ul>
126: */
127: public static RPCRequest decodeRequest(String encodedRequest) {
128: return decodeRequest(encodedRequest, null);
129: }
130:
131: /**
132: * Returns an {@link RPCRequest} that is built by decoding the contents of an
133: * encoded RPC request and optionally validating that type can handle the
134: * request. If the type parameter is not <code>null</code>, the
135: * implementation checks that the type is assignable to the
136: * {@link RemoteService} interface requested in the encoded request string.
137: *
138: * <p>
139: * Invoking this method with <code>null</code> for the type parameter,
140: * <code>decodeRequest(encodedRequest, null)</code>, is equivalent to
141: * calling <code>decodeRequest(encodedRequest)</code>.
142: * </p>
143: *
144: * @param encodedRequest a string that encodes the {@link RemoteService}
145: * interface, the service method, and the arguments to pass to the
146: * service method
147: * @param type if not <code>null</code>, the implementation checks that the
148: * type is assignable to the {@link RemoteService} interface encoded
149: * in the encoded request string.
150: * @return an {@link RPCRequest} instance
151: *
152: * @throws NullPointerException if the encodedRequest is <code>null</code>
153: * @throws IllegalArgumentException if the encodedRequest is an empty string
154: * @throws IncompatibleRemoteServiceException if any of the following
155: * conditions apply:
156: * <ul>
157: * <li>if the types in the encoded request cannot be deserialized</li>
158: * <li>if the {@link ClassLoader} acquired from
159: * <code>Thread.currentThread().getContextClassLoader()</code>
160: * cannot load the service interface or any of the types specified
161: * in the encodedRequest</li>
162: * <li>the requested interface is not assignable to
163: * {@link RemoteService}</li>
164: * <li>the service method requested in the encodedRequest is not a
165: * member of the requested service interface</li>
166: * <li>the type parameter is not <code>null</code> and is not
167: * assignable to the requested {@link RemoteService} interface
168: * </ul>
169: */
170: public static RPCRequest decodeRequest(String encodedRequest,
171: Class<?> type) {
172: return decodeRequest(encodedRequest, type, null);
173: }
174:
175: /**
176: * Returns an {@link RPCRequest} that is built by decoding the contents of an
177: * encoded RPC request and optionally validating that type can handle the
178: * request. If the type parameter is not <code>null</code>, the
179: * implementation checks that the type is assignable to the
180: * {@link RemoteService} interface requested in the encoded request string.
181: *
182: * <p>
183: * If the serializationPolicyProvider parameter is not <code>null</code>,
184: * it is asked for a {@link SerializationPolicy} to use to restrict the set of
185: * types that can be decoded from the request. If this parameter is
186: * <code>null</code>, then only subtypes of
187: * {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or
188: * types which have custom field serializers can be decoded.
189: * </p>
190: *
191: * <p>
192: * Invoking this method with <code>null</code> for the type parameter,
193: * <code>decodeRequest(encodedRequest, null)</code>, is equivalent to
194: * calling <code>decodeRequest(encodedRequest)</code>.
195: * </p>
196: *
197: * @param encodedRequest a string that encodes the {@link RemoteService}
198: * interface, the service method, and the arguments to pass to the
199: * service method
200: * @param type if not <code>null</code>, the implementation checks that the
201: * type is assignable to the {@link RemoteService} interface encoded
202: * in the encoded request string.
203: * @param serializationPolicyProvider if not <code>null</code>, the
204: * implementation asks this provider for a
205: * {@link SerializationPolicy} which will be used to restrict the set
206: * of types that can be decoded from this request
207: * @return an {@link RPCRequest} instance
208: *
209: * @throws NullPointerException if the encodedRequest is <code>null</code>
210: * @throws IllegalArgumentException if the encodedRequest is an empty string
211: * @throws IncompatibleRemoteServiceException if any of the following
212: * conditions apply:
213: * <ul>
214: * <li>if the types in the encoded request cannot be deserialized</li>
215: * <li>if the {@link ClassLoader} acquired from
216: * <code>Thread.currentThread().getContextClassLoader()</code>
217: * cannot load the service interface or any of the types specified
218: * in the encodedRequest</li>
219: * <li>the requested interface is not assignable to
220: * {@link RemoteService}</li>
221: * <li>the service method requested in the encodedRequest is not a
222: * member of the requested service interface</li>
223: * <li>the type parameter is not <code>null</code> and is not
224: * assignable to the requested {@link RemoteService} interface
225: * </ul>
226: */
227: public static RPCRequest decodeRequest(String encodedRequest,
228: Class<?> type,
229: SerializationPolicyProvider serializationPolicyProvider) {
230: if (encodedRequest == null) {
231: throw new NullPointerException(
232: "encodedRequest cannot be null");
233: }
234:
235: if (encodedRequest.length() == 0) {
236: throw new IllegalArgumentException(
237: "encodedRequest cannot be empty");
238: }
239:
240: ClassLoader classLoader = Thread.currentThread()
241: .getContextClassLoader();
242:
243: try {
244: ServerSerializationStreamReader streamReader = new ServerSerializationStreamReader(
245: classLoader, serializationPolicyProvider);
246: streamReader.prepareToRead(encodedRequest);
247:
248: // Read the name of the RemoteService interface
249: String serviceIntfName = streamReader.readString();
250:
251: if (type != null) {
252: if (!implements Interface(type, serviceIntfName)) {
253: // The service does not implement the requested interface
254: throw new IncompatibleRemoteServiceException(
255: "Blocked attempt to access interface '"
256: + serviceIntfName
257: + "', which is not implemented by '"
258: + printTypeName(type)
259: + "'; this is either misconfiguration or a hack attempt");
260: }
261: }
262:
263: SerializationPolicy serializationPolicy = streamReader
264: .getSerializationPolicy();
265: Class<?> serviceIntf;
266: try {
267: serviceIntf = getClassFromSerializedName(
268: serviceIntfName, classLoader);
269: if (!RemoteService.class.isAssignableFrom(serviceIntf)) {
270: // The requested interface is not a RemoteService interface
271: throw new IncompatibleRemoteServiceException(
272: "Blocked attempt to access interface '"
273: + printTypeName(serviceIntf)
274: + "', which doesn't extend RemoteService; this is either misconfiguration or a hack attempt");
275: }
276: } catch (ClassNotFoundException e) {
277: throw new IncompatibleRemoteServiceException(
278: "Could not locate requested interface '"
279: + serviceIntfName
280: + "' in default classloader", e);
281: }
282:
283: String serviceMethodName = streamReader.readString();
284:
285: int paramCount = streamReader.readInt();
286: Class<?>[] parameterTypes = new Class[paramCount];
287:
288: for (int i = 0; i < parameterTypes.length; i++) {
289: String paramClassName = streamReader.readString();
290: try {
291: parameterTypes[i] = getClassFromSerializedName(
292: paramClassName, classLoader);
293: } catch (ClassNotFoundException e) {
294: throw new IncompatibleRemoteServiceException(
295: "Parameter " + i
296: + " of is of an unknown type '"
297: + paramClassName + "'", e);
298: }
299: }
300:
301: try {
302: Method method = serviceIntf.getMethod(
303: serviceMethodName, parameterTypes);
304:
305: Object[] parameterValues = new Object[parameterTypes.length];
306: for (int i = 0; i < parameterValues.length; i++) {
307: parameterValues[i] = streamReader
308: .deserializeValue(parameterTypes[i]);
309: }
310:
311: return new RPCRequest(method, parameterValues,
312: serializationPolicy);
313:
314: } catch (NoSuchMethodException e) {
315: throw new IncompatibleRemoteServiceException(
316: formatMethodNotFoundErrorMessage(serviceIntf,
317: serviceMethodName, parameterTypes));
318: }
319: } catch (SerializationException ex) {
320: throw new IncompatibleRemoteServiceException(ex
321: .getMessage(), ex);
322: }
323: }
324:
325: /**
326: * Returns a string that encodes an exception. If method is not
327: * <code>null</code>, it is an error if the exception is not in the
328: * method's list of checked exceptions.
329: *
330: * @param serviceMethod the method that threw the exception, may be
331: * <code>null</code>
332: * @param cause the {@link Throwable} that was thrown
333: * @return a string that encodes the exception
334: *
335: * @throws NullPointerException if the the cause is <code>null</code>
336: * @throws SerializationException if the result cannot be serialized
337: * @throws UnexpectedException if the result was an unexpected exception (a
338: * checked exception not declared in the serviceMethod's signature)
339: */
340: public static String encodeResponseForFailure(Method serviceMethod,
341: Throwable cause) throws SerializationException {
342: return encodeResponseForFailure(serviceMethod, cause,
343: getDefaultSerializationPolicy());
344: }
345:
346: /**
347: * Returns a string that encodes an exception. If method is not
348: * <code>null</code>, it is an error if the exception is not in the
349: * method's list of checked exceptions.
350: *
351: * <p>
352: * If the serializationPolicy parameter is not <code>null</code>, it is
353: * used to determine what types can be encoded as part of this response. If
354: * this parameter is <code>null</code>, then only subtypes of
355: * {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or
356: * types which have custom field serializers may be encoded.
357: * </p>
358: *
359: * @param serviceMethod the method that threw the exception, may be
360: * <code>null</code>
361: * @param cause the {@link Throwable} that was thrown
362: * @param serializationPolicy determines the serialization policy to be used
363: * @return a string that encodes the exception
364: *
365: * @throws NullPointerException if the the cause or the serializationPolicy
366: * are <code>null</code>
367: * @throws SerializationException if the result cannot be serialized
368: * @throws UnexpectedException if the result was an unexpected exception (a
369: * checked exception not declared in the serviceMethod's signature)
370: */
371: public static String encodeResponseForFailure(Method serviceMethod,
372: Throwable cause, SerializationPolicy serializationPolicy)
373: throws SerializationException {
374: if (cause == null) {
375: throw new NullPointerException("cause cannot be null");
376: }
377:
378: if (serializationPolicy == null) {
379: throw new NullPointerException("serializationPolicy");
380: }
381:
382: if (serviceMethod != null
383: && !RPC.isExpectedException(serviceMethod, cause)) {
384: throw new UnexpectedException("Service method '"
385: + getSourceRepresentation(serviceMethod)
386: + "' threw an unexpected exception: "
387: + cause.toString(), cause);
388: }
389:
390: return encodeResponse(cause.getClass(), cause, true,
391: serializationPolicy);
392: }
393:
394: /**
395: * Returns a string that encodes the object. It is an error to try to encode
396: * an object that is not assignable to the service method's return type.
397: *
398: * @param serviceMethod the method whose result we are encoding
399: * @param object the instance that we wish to encode
400: * @return a string that encodes the object, if the object is compatible with
401: * the service method's declared return type
402: *
403: * @throws IllegalArgumentException if the result is not assignable to the
404: * service method's return type
405: * @throws NullPointerException if the service method is <code>null</code>
406: * @throws SerializationException if the result cannot be serialized
407: */
408: public static String encodeResponseForSuccess(Method serviceMethod,
409: Object object) throws SerializationException {
410: return encodeResponseForSuccess(serviceMethod, object,
411: getDefaultSerializationPolicy());
412: }
413:
414: /**
415: * Returns a string that encodes the object. It is an error to try to encode
416: * an object that is not assignable to the service method's return type.
417: *
418: * <p>
419: * If the serializationPolicy parameter is not <code>null</code>, it is
420: * used to determine what types can be encoded as part of this response. If
421: * this parameter is <code>null</code>, then only subtypes of
422: * {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or
423: * types which have custom field serializers may be encoded.
424: * </p>
425: *
426: * @param serviceMethod the method whose result we are encoding
427: * @param object the instance that we wish to encode
428: * @param serializationPolicy determines the serialization policy to be used
429: * @return a string that encodes the object, if the object is compatible with
430: * the service method's declared return type
431: *
432: * @throws IllegalArgumentException if the result is not assignable to the
433: * service method's return type
434: * @throws NullPointerException if the serviceMethod or the
435: * serializationPolicy are <code>null</code>
436: * @throws SerializationException if the result cannot be serialized
437: */
438: public static String encodeResponseForSuccess(Method serviceMethod,
439: Object object, SerializationPolicy serializationPolicy)
440: throws SerializationException {
441: if (serviceMethod == null) {
442: throw new NullPointerException(
443: "serviceMethod cannot be null");
444: }
445:
446: if (serializationPolicy == null) {
447: throw new NullPointerException("serializationPolicy");
448: }
449:
450: Class<?> methodReturnType = serviceMethod.getReturnType();
451: if (methodReturnType != void.class && object != null) {
452: Class<?> actualReturnType;
453: if (methodReturnType.isPrimitive()) {
454: actualReturnType = getPrimitiveClassFromWrapper(object
455: .getClass());
456: } else {
457: actualReturnType = object.getClass();
458: }
459:
460: if (actualReturnType == null
461: || !methodReturnType
462: .isAssignableFrom(actualReturnType)) {
463: throw new IllegalArgumentException(
464: "Type '"
465: + printTypeName(object.getClass())
466: + "' does not match the return type in the method's signature: '"
467: + getSourceRepresentation(serviceMethod)
468: + "'");
469: }
470: }
471:
472: return encodeResponse(methodReturnType, object, false,
473: serializationPolicy);
474: }
475:
476: /**
477: * Returns a default serialization policy.
478: *
479: * @return the default serialization policy.
480: */
481: public static SerializationPolicy getDefaultSerializationPolicy() {
482: return LegacySerializationPolicy.getInstance();
483: }
484:
485: /**
486: * Returns a string that encodes the result of calling a service method, which
487: * could be the value returned by the method or an exception thrown by it.
488: *
489: * <p>
490: * This method does no security checking; security checking must be done on
491: * the method prior to this invocation.
492: * </p>
493: *
494: * @param target instance on which to invoke the serviceMethod
495: * @param serviceMethod the method to invoke
496: * @param args arguments used for the method invocation
497: * @return a string which encodes either the method's return or a checked
498: * exception thrown by the method
499: *
500: * @throws SecurityException if the method cannot be accessed or if the number
501: * or type of actual and formal arguments differ
502: * @throws SerializationException if an object could not be serialized by the
503: * stream
504: * @throws UnexpectedException if the serviceMethod throws a checked exception
505: * that is not declared in its signature
506: */
507: public static String invokeAndEncodeResponse(Object target,
508: Method serviceMethod, Object[] args)
509: throws SerializationException {
510: return invokeAndEncodeResponse(target, serviceMethod, args,
511: getDefaultSerializationPolicy());
512: }
513:
514: /**
515: * Returns a string that encodes the result of calling a service method, which
516: * could be the value returned by the method or an exception thrown by it.
517: *
518: * <p>
519: * If the serializationPolicy parameter is not <code>null</code>, it is
520: * used to determine what types can be encoded as part of this response. If
521: * this parameter is <code>null</code>, then only subtypes of
522: * {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or
523: * types which have custom field serializers may be encoded.
524: * </p>
525: *
526: * <p>
527: * This method does no security checking; security checking must be done on
528: * the method prior to this invocation.
529: * </p>
530: *
531: * @param target instance on which to invoke the serviceMethod
532: * @param serviceMethod the method to invoke
533: * @param args arguments used for the method invocation
534: * @param serializationPolicy determines the serialization policy to be used
535: * @return a string which encodes either the method's return or a checked
536: * exception thrown by the method
537: *
538: * @throws NullPointerException if the serviceMethod or the
539: * serializationPolicy are <code>null</code>
540: * @throws SecurityException if the method cannot be accessed or if the number
541: * or type of actual and formal arguments differ
542: * @throws SerializationException if an object could not be serialized by the
543: * stream
544: * @throws UnexpectedException if the serviceMethod throws a checked exception
545: * that is not declared in its signature
546: */
547: public static String invokeAndEncodeResponse(Object target,
548: Method serviceMethod, Object[] args,
549: SerializationPolicy serializationPolicy)
550: throws SerializationException {
551: if (serviceMethod == null) {
552: throw new NullPointerException("serviceMethod");
553: }
554:
555: if (serializationPolicy == null) {
556: throw new NullPointerException("serializationPolicy");
557: }
558:
559: String responsePayload;
560: try {
561: Object result = serviceMethod.invoke(target, args);
562:
563: responsePayload = encodeResponseForSuccess(serviceMethod,
564: result, serializationPolicy);
565: } catch (IllegalAccessException e) {
566: SecurityException securityException = new SecurityException(
567: formatIllegalAccessErrorMessage(target,
568: serviceMethod));
569: securityException.initCause(e);
570: throw securityException;
571: } catch (IllegalArgumentException e) {
572: SecurityException securityException = new SecurityException(
573: formatIllegalArgumentErrorMessage(target,
574: serviceMethod, args));
575: securityException.initCause(e);
576: throw securityException;
577: } catch (InvocationTargetException e) {
578: // Try to encode the caught exception
579: //
580: Throwable cause = e.getCause();
581:
582: responsePayload = encodeResponseForFailure(serviceMethod,
583: cause, serializationPolicy);
584: }
585:
586: return responsePayload;
587: }
588:
589: /**
590: * Returns a string that encodes the results of an RPC call. Private overload
591: * that takes a flag signaling the preamble of the response payload.
592: *
593: * @param object the object that we wish to send back to the client
594: * @param wasThrown if true, the object being returned was an exception thrown
595: * by the service method; if false, it was the result of the service
596: * method's invocation
597: * @return a string that encodes the response from a service method
598: * @throws SerializationException if the object cannot be serialized
599: */
600: private static String encodeResponse(Class<?> responseClass,
601: Object object, boolean wasThrown,
602: SerializationPolicy serializationPolicy)
603: throws SerializationException {
604:
605: ServerSerializationStreamWriter stream = new ServerSerializationStreamWriter(
606: serializationPolicy);
607:
608: stream.prepareToWrite();
609: if (responseClass != void.class) {
610: stream.serializeValue(object, responseClass);
611: }
612:
613: String bufferStr = (wasThrown ? "//EX" : "//OK")
614: + stream.toString();
615: return bufferStr;
616: }
617:
618: private static String formatIllegalAccessErrorMessage(
619: Object target, Method serviceMethod) {
620: StringBuffer sb = new StringBuffer();
621: sb.append("Blocked attempt to access inaccessible method '");
622: sb.append(getSourceRepresentation(serviceMethod));
623: sb.append("'");
624:
625: if (target != null) {
626: sb.append(" on target '");
627: sb.append(printTypeName(target.getClass()));
628: sb.append("'");
629: }
630:
631: sb
632: .append("; this is either misconfiguration or a hack attempt");
633:
634: return sb.toString();
635: }
636:
637: private static String formatIllegalArgumentErrorMessage(
638: Object target, Method serviceMethod, Object[] args) {
639: StringBuffer sb = new StringBuffer();
640: sb.append("Blocked attempt to invoke method '");
641: sb.append(getSourceRepresentation(serviceMethod));
642: sb.append("'");
643:
644: if (target != null) {
645: sb.append(" on target '");
646: sb.append(printTypeName(target.getClass()));
647: sb.append("'");
648: }
649:
650: sb.append(" with invalid arguments");
651:
652: if (args != null && args.length > 0) {
653: sb.append(Arrays.asList(args));
654: }
655:
656: return sb.toString();
657: }
658:
659: private static String formatMethodNotFoundErrorMessage(
660: Class<?> serviceIntf, String serviceMethodName,
661: Class<?>[] parameterTypes) {
662: StringBuffer sb = new StringBuffer();
663:
664: sb.append("Could not locate requested method '");
665: sb.append(serviceMethodName);
666: sb.append("(");
667: for (int i = 0; i < parameterTypes.length; ++i) {
668: if (i > 0) {
669: sb.append(", ");
670: }
671: sb.append(printTypeName(parameterTypes[i]));
672: }
673: sb.append(")'");
674:
675: sb.append(" in interface '");
676: sb.append(printTypeName(serviceIntf));
677: sb.append("'");
678:
679: return sb.toString();
680: }
681:
682: /**
683: * Returns the {@link Class} instance for the named class or primitive type.
684: *
685: * @param serializedName the serialized name of a class or primitive type
686: * @param classLoader the classLoader used to load {@link Class}es
687: * @return Class instance for the given type name
688: * @throws ClassNotFoundException if the named type was not found
689: */
690: private static Class<?> getClassFromSerializedName(
691: String serializedName, ClassLoader classLoader)
692: throws ClassNotFoundException {
693: Class<?> value = TYPE_NAMES.get(serializedName);
694: if (value != null) {
695: return value;
696: }
697:
698: return Class.forName(serializedName, false, classLoader);
699: }
700:
701: /**
702: * Returns the {@link java.lang.Class Class} for a primitive type given its
703: * corresponding wrapper {@link java.lang.Class Class}.
704: *
705: * @param wrapperClass primitive wrapper class
706: * @return primitive class
707: */
708: private static Class<?> getPrimitiveClassFromWrapper(
709: Class<?> wrapperClass) {
710: return PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS
711: .get(wrapperClass);
712: }
713:
714: /**
715: * Returns the source representation for a method signature.
716: *
717: * @param method method to get the source signature for
718: * @return source representation for a method signature
719: */
720: private static String getSourceRepresentation(Method method) {
721: return method.toString().replace('$', '.');
722: }
723:
724: /**
725: * Used to determine whether the specified interface name is implemented by
726: * the service class. This is done without loading the class (for security).
727: */
728: private static boolean implements Interface(Class<?> service,
729: String intfName) {
730: synchronized (serviceToImplementedInterfacesMap) {
731: // See if it's cached.
732: //
733: Set<String> interfaceSet = serviceToImplementedInterfacesMap
734: .get(service);
735: if (interfaceSet != null) {
736: if (interfaceSet.contains(intfName)) {
737: return true;
738: }
739: } else {
740: interfaceSet = new HashSet<String>();
741: serviceToImplementedInterfacesMap.put(service,
742: interfaceSet);
743: }
744:
745: if (!service.isInterface()) {
746: while ((service != null)
747: && !RemoteServiceServlet.class.equals(service)) {
748: Class<?>[] intfs = service.getInterfaces();
749: for (Class<?> intf : intfs) {
750: if (implements InterfaceRecursive(intf, intfName)) {
751: interfaceSet.add(intfName);
752: return true;
753: }
754: }
755:
756: // did not find the interface in this class so we look in the
757: // superclass
758: //
759: service = service.getSuperclass();
760: }
761: } else {
762: if (implements InterfaceRecursive(service, intfName)) {
763: interfaceSet.add(intfName);
764: return true;
765: }
766: }
767:
768: return false;
769: }
770: }
771:
772: /**
773: * Only called from implementsInterface().
774: */
775: private static boolean implements InterfaceRecursive(Class<?> clazz,
776: String intfName) {
777: assert (clazz.isInterface());
778:
779: if (clazz.getName().equals(intfName)) {
780: return true;
781: }
782:
783: // search implemented interfaces
784: Class<?>[] intfs = clazz.getInterfaces();
785: for (Class<?> intf : intfs) {
786: if (implements InterfaceRecursive(intf, intfName)) {
787: return true;
788: }
789: }
790:
791: return false;
792: }
793:
794: /**
795: * Returns true if the {@link java.lang.reflect.Method Method} definition on
796: * the service is specified to throw the exception contained in the
797: * InvocationTargetException or false otherwise. NOTE we do not check that the
798: * type is serializable here. We assume that it must be otherwise the
799: * application would never have been allowed to run.
800: *
801: * @param serviceIntfMethod the method from the RPC request
802: * @param cause the exception that the method threw
803: * @return true if the exception's type is in the method's signature
804: */
805: private static boolean isExpectedException(
806: Method serviceIntfMethod, Throwable cause) {
807: assert (serviceIntfMethod != null);
808: assert (cause != null);
809:
810: Class<?>[] exceptionsThrown = serviceIntfMethod
811: .getExceptionTypes();
812: if (exceptionsThrown.length <= 0) {
813: // The method is not specified to throw any exceptions
814: //
815: return false;
816: }
817:
818: Class<? extends Throwable> causeType = cause.getClass();
819:
820: for (Class<?> exceptionThrown : exceptionsThrown) {
821: assert (exceptionThrown != null);
822:
823: if (exceptionThrown.isAssignableFrom(causeType)) {
824: return true;
825: }
826: }
827:
828: return false;
829: }
830:
831: /**
832: * Straight copy from
833: * {@link com.google.gwt.dev.util.TypeInfo#getSourceRepresentation(Class)} to
834: * avoid runtime dependency on gwt-dev.
835: */
836: private static String printTypeName(Class<?> type) {
837: // Primitives
838: //
839: if (type.equals(Integer.TYPE)) {
840: return "int";
841: } else if (type.equals(Long.TYPE)) {
842: return "long";
843: } else if (type.equals(Short.TYPE)) {
844: return "short";
845: } else if (type.equals(Byte.TYPE)) {
846: return "byte";
847: } else if (type.equals(Character.TYPE)) {
848: return "char";
849: } else if (type.equals(Boolean.TYPE)) {
850: return "boolean";
851: } else if (type.equals(Float.TYPE)) {
852: return "float";
853: } else if (type.equals(Double.TYPE)) {
854: return "double";
855: }
856:
857: // Arrays
858: //
859: if (type.isArray()) {
860: Class<?> componentType = type.getComponentType();
861: return printTypeName(componentType) + "[]";
862: }
863:
864: // Everything else
865: //
866: return type.getName().replace('$', '.');
867: }
868:
869: /**
870: * Static classes have no constructability.
871: */
872: private RPC() {
873: // Not instantiable
874: }
875: }
|