package net.sourceforge.fenixedu.presentationTier.servlets.gwt; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.SerializationException; import com.google.gwt.user.client.rpc.impl.AbstractSerializationStream; import com.google.gwt.user.server.rpc.RPCServletUtils; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.google.gwt.user.server.rpc.SerializationPolicy; import com.google.gwt.user.server.rpc.SerializationPolicyProvider; import com.google.gwt.user.server.rpc.UnexpectedException; import com.google.gwt.user.server.rpc.impl.LegacySerializationPolicy; import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader; import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter; import com.google.gwt.user.server.rpc.impl.TypeNameObfuscator; /** * Utility class for integrating with the RPC system. This class exposes methods * for decoding of RPC requests, encoding of RPC responses, and invocation of * RPC calls on service objects. The operations exposed by this class can be * reused by framework implementors such as Spring and G4jsf to support a wide * range of service invocation policies. * *
* This method is equivalent to calling {@link #decodeRequest(String, Class)} with null
for the type parameter.
*
Thread.currentThread().getContextClassLoader()
cannot
* load the service interface or any of the types specified in the encodedRequestnull
and is not assignable to the requested {@link RemoteService}
* interface
* null
, the implementation
* checks that the type is assignable to the {@link RemoteService} interface
* requested in the encoded request string.
*
*
* Invoking this method with null
for the type parameter, decodeRequest(encodedRequest, null)
, is
* equivalent to calling decodeRequest(encodedRequest)
.
*
null
, the implementation checks that the
* type is assignable to the {@link RemoteService} interface encoded
* in the encoded request string.
* @return an {@link FenixRPCRequest} instance
*
* @throws NullPointerException if the encodedRequest is null
* @throws IllegalArgumentException if the encodedRequest is an empty string
* @throws IncompatibleRemoteServiceException if any of the following
* conditions apply:
* Thread.currentThread().getContextClassLoader()
cannot
* load the service interface or any of the types specified in the encodedRequestnull
and is not assignable to the requested {@link RemoteService}
* interface
* null
, the implementation
* checks that the type is assignable to the {@link RemoteService} interface
* requested in the encoded request string.
*
*
* If the serializationPolicyProvider parameter is not null
, it is asked for a {@link SerializationPolicy} to use
* to restrict the set of types that can be decoded from the request. If this parameter is null
, then only
* subtypes of {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or types which have custom field
* serializers can be decoded.
*
* Invoking this method with null
for the type parameter, decodeRequest(encodedRequest, null)
, is
* equivalent to calling decodeRequest(encodedRequest)
.
*
null
, the implementation checks that the
* type is assignable to the {@link RemoteService} interface encoded
* in the encoded request string.
* @param serializationPolicyProvider if not null
, the
* implementation asks this provider for a {@link SerializationPolicy} which will be used to restrict the set
* of types that can be decoded from this request
* @return an {@link FenixRPCRequest} instance
*
* @throws NullPointerException if the encodedRequest is null
* @throws IllegalArgumentException if the encodedRequest is an empty string
* @throws IncompatibleRemoteServiceException if any of the following
* conditions apply:
* Thread.currentThread().getContextClassLoader()
cannot
* load the service interface or any of the types specified in the encodedRequestnull
and is not assignable to the requested {@link RemoteService}
* interface
* null
, it is an error if the exception is not in
* the method's
* list of checked exceptions.
*
* @param serviceMethod the method that threw the exception, may be null
* @param cause the {@link Throwable} that was thrown
* @return a string that encodes the exception
*
* @throws NullPointerException if the the cause is null
* @throws SerializationException if the result cannot be serialized
* @throws UnexpectedException if the result was an unexpected exception (a
* checked exception not declared in the serviceMethod's signature)
*/
public static String encodeResponseForFailure(Method serviceMethod, Throwable cause) throws SerializationException {
return encodeResponseForFailure(serviceMethod, cause, getDefaultSerializationPolicy());
}
/**
* Returns a string that encodes an exception. If method is not null
, it is an error if the exception is not in
* the method's
* list of checked exceptions.
*
*
* If the serializationPolicy parameter is not null
, it is used to determine what types can be encoded as part of
* this response. If this parameter is null
, then only subtypes of
* {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or types which have custom field serializers may be
* encoded.
*
null
* @param cause the {@link Throwable} that was thrown
* @param serializationPolicy determines the serialization policy to be used
* @return a string that encodes the exception
*
* @throws NullPointerException if the the cause or the serializationPolicy
* are null
* @throws SerializationException if the result cannot be serialized
* @throws UnexpectedException if the result was an unexpected exception (a
* checked exception not declared in the serviceMethod's signature)
*/
public static String encodeResponseForFailure(Method serviceMethod, Throwable cause, SerializationPolicy serializationPolicy)
throws SerializationException {
return encodeResponseForFailure(serviceMethod, cause, serializationPolicy, AbstractSerializationStream.DEFAULT_FLAGS);
}
public static String encodeResponseForFailure(Method serviceMethod, Throwable cause, SerializationPolicy serializationPolicy,
int flags) throws SerializationException {
if (cause == null) {
throw new NullPointerException("cause cannot be null");
}
if (serializationPolicy == null) {
throw new NullPointerException("serializationPolicy");
}
if (serviceMethod != null && !RPCServletUtils.isExpectedException(serviceMethod, cause)) {
throw new UnexpectedException("Service method '" + getSourceRepresentation(serviceMethod)
+ "' threw an unexpected exception: " + cause.toString(), cause);
}
return encodeResponse(cause.getClass(), cause, true, flags, serializationPolicy);
}
/**
* Returns a string that encodes the object. It is an error to try to encode
* an object that is not assignable to the service method's return type.
*
* @param serviceMethod the method whose result we are encoding
* @param object the instance that we wish to encode
* @return a string that encodes the object, if the object is compatible with
* the service method's declared return type
*
* @throws IllegalArgumentException if the result is not assignable to the
* service method's return type
* @throws NullPointerException if the service method is null
* @throws SerializationException if the result cannot be serialized
*/
public static String encodeResponseForSuccess(Method serviceMethod, Object object) throws SerializationException {
return encodeResponseForSuccess(serviceMethod, object, getDefaultSerializationPolicy());
}
/**
* Returns a string that encodes the object. It is an error to try to encode
* an object that is not assignable to the service method's return type.
*
*
* If the serializationPolicy parameter is not null
, it is used to determine what types can be encoded as part of
* this response. If this parameter is null
, then only subtypes of
* {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or types which have custom field serializers may be
* encoded.
*
null
* @throws SerializationException if the result cannot be serialized
*/
public static String encodeResponseForSuccess(Method serviceMethod, Object object, SerializationPolicy serializationPolicy)
throws SerializationException {
return encodeResponseForSuccess(serviceMethod, object, serializationPolicy, AbstractSerializationStream.DEFAULT_FLAGS);
}
public static String encodeResponseForSuccess(Method serviceMethod, Object object, SerializationPolicy serializationPolicy,
int flags) throws SerializationException {
if (serviceMethod == null) {
throw new NullPointerException("serviceMethod cannot be null");
}
if (serializationPolicy == null) {
throw new NullPointerException("serializationPolicy");
}
Class> methodReturnType = serviceMethod.getReturnType();
if (methodReturnType != void.class && object != null) {
Class> actualReturnType;
if (methodReturnType.isPrimitive()) {
actualReturnType = getPrimitiveClassFromWrapper(object.getClass());
} else {
actualReturnType = object.getClass();
}
if (actualReturnType == null || !methodReturnType.isAssignableFrom(actualReturnType)) {
throw new IllegalArgumentException("Type '" + printTypeName(object.getClass())
+ "' does not match the return type in the method's signature: '"
+ getSourceRepresentation(serviceMethod) + "'");
}
}
return encodeResponse(methodReturnType, object, false, flags, serializationPolicy);
}
/**
* Returns a default serialization policy.
*
* @return the default serialization policy.
*/
public static SerializationPolicy getDefaultSerializationPolicy() {
return LegacySerializationPolicy.getInstance();
}
/**
* Returns a string that encodes the result of calling a service method, which
* could be the value returned by the method or an exception thrown by it.
*
* * This method does no security checking; security checking must be done on the method prior to this invocation. *
* * @param target instance on which to invoke the serviceMethod * @param serviceMethod the method to invoke * @param args arguments used for the method invocation * @return a string which encodes either the method's return or a checked * exception thrown by the method * * @throws SecurityException if the method cannot be accessed or if the number * or type of actual and formal arguments differ * @throws SerializationException if an object could not be serialized by the * stream * @throws UnexpectedException if the serviceMethod throws a checked exception * that is not declared in its signature */ public static String invokeAndEncodeResponse(Object target, Method serviceMethod, Object[] args) throws SerializationException { return invokeAndEncodeResponse(target, serviceMethod, args, getDefaultSerializationPolicy()); } /** * Returns a string that encodes the result of calling a service method, which * could be the value returned by the method or an exception thrown by it. * *
* If the serializationPolicy parameter is not null
, it is used to determine what types can be encoded as part of
* this response. If this parameter is null
, then only subtypes of
* {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or types which have custom field serializers may be
* encoded.
*
* This method does no security checking; security checking must be done on the method prior to this invocation. *
* * @param target instance on which to invoke the serviceMethod * @param serviceMethod the method to invoke * @param args arguments used for the method invocation * @param serializationPolicy determines the serialization policy to be used * @return a string which encodes either the method's return or a checked * exception thrown by the method * * @throws NullPointerException if the serviceMethod or the * serializationPolicy arenull
* @throws SecurityException if the method cannot be accessed or if the number
* or type of actual and formal arguments differ
* @throws SerializationException if an object could not be serialized by the
* stream
* @throws UnexpectedException if the serviceMethod throws a checked exception
* that is not declared in its signature
*/
public static String invokeAndEncodeResponse(Object target, Method serviceMethod, Object[] args,
SerializationPolicy serializationPolicy) throws SerializationException {
return invokeAndEncodeResponse(target, serviceMethod, args, serializationPolicy,
AbstractSerializationStream.DEFAULT_FLAGS);
}
public static String invokeAndEncodeResponse(Object target, Method serviceMethod, Object[] args,
SerializationPolicy serializationPolicy, int flags) throws SerializationException {
if (serviceMethod == null) {
throw new NullPointerException("serviceMethod");
}
if (serializationPolicy == null) {
throw new NullPointerException("serializationPolicy");
}
String responsePayload;
try {
Object result = serviceMethod.invoke(target, args);
responsePayload = encodeResponseForSuccess(serviceMethod, result, serializationPolicy, flags);
} catch (IllegalAccessException e) {
SecurityException securityException = new SecurityException(formatIllegalAccessErrorMessage(target, serviceMethod));
securityException.initCause(e);
throw securityException;
} catch (IllegalArgumentException e) {
SecurityException securityException =
new SecurityException(formatIllegalArgumentErrorMessage(target, serviceMethod, args));
securityException.initCause(e);
throw securityException;
} catch (InvocationTargetException e) {
// Try to encode the caught exception
//
Throwable cause = e.getCause();
responsePayload = encodeResponseForFailure(serviceMethod, cause, serializationPolicy, flags);
}
return responsePayload;
}
/**
* Returns a string that encodes the results of an RPC call. Private overload
* that takes a flag signaling the preamble of the response payload.
*
* @param object the object that we wish to send back to the client
* @param wasThrown if true, the object being returned was an exception thrown
* by the service method; if false, it was the result of the service
* method's invocation
* @return a string that encodes the response from a service method
* @throws SerializationException if the object cannot be serialized
*/
private static String encodeResponse(Class> responseClass, Object object, boolean wasThrown, int flags,
SerializationPolicy serializationPolicy) throws SerializationException {
ServerSerializationStreamWriter stream = new ServerSerializationStreamWriter(serializationPolicy);
stream.setFlags(flags);
stream.prepareToWrite();
if (responseClass != void.class) {
stream.serializeValue(object, responseClass);
}
String bufferStr = (wasThrown ? "//EX" : "//OK") + stream.toString();
return bufferStr;
}
private static String formatIllegalAccessErrorMessage(Object target, Method serviceMethod) {
StringBuffer sb = new StringBuffer();
sb.append("Blocked attempt to access inaccessible method '");
sb.append(getSourceRepresentation(serviceMethod));
sb.append("'");
if (target != null) {
sb.append(" on target '");
sb.append(printTypeName(target.getClass()));
sb.append("'");
}
sb.append("; this is either misconfiguration or a hack attempt");
return sb.toString();
}
private static String formatIllegalArgumentErrorMessage(Object target, Method serviceMethod, Object[] args) {
StringBuffer sb = new StringBuffer();
sb.append("Blocked attempt to invoke method '");
sb.append(getSourceRepresentation(serviceMethod));
sb.append("'");
if (target != null) {
sb.append(" on target '");
sb.append(printTypeName(target.getClass()));
sb.append("'");
}
sb.append(" with invalid arguments");
if (args != null && args.length > 0) {
sb.append(Arrays.asList(args));
}
return sb.toString();
}
private static String formatMethodNotFoundErrorMessage(Class> serviceIntf, String serviceMethodName,
Class>[] parameterTypes) {
StringBuffer sb = new StringBuffer();
sb.append("Could not locate requested method '");
sb.append(serviceMethodName);
sb.append("(");
for (int i = 0; i < parameterTypes.length; ++i) {
if (i > 0) {
sb.append(", ");
}
sb.append(printTypeName(parameterTypes[i]));
}
sb.append(")'");
sb.append(" in interface '");
sb.append(printTypeName(serviceIntf));
sb.append("'");
return sb.toString();
}
/**
* Returns the {@link Class} instance for the named class or primitive type.
*
* @param serializedName the serialized name of a class or primitive type
* @param classLoader the classLoader used to load {@link Class}es
* @return Class instance for the given type name
* @throws ClassNotFoundException if the named type was not found
*/
private static Class> getClassFromSerializedName(String serializedName, ClassLoader classLoader)
throws ClassNotFoundException {
Class> value = TYPE_NAMES.get(serializedName);
if (value != null) {
return value;
}
return Class.forName(serializedName, false, classLoader);
}
/**
* Returns the {@link java.lang.Class Class} for a primitive type given its
* corresponding wrapper {@link java.lang.Class Class}.
*
* @param wrapperClass primitive wrapper class
* @return primitive class
*/
private static Class> getPrimitiveClassFromWrapper(Class> wrapperClass) {
return PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.get(wrapperClass);
}
/**
* Returns the source representation for a method signature.
*
* @param method method to get the source signature for
* @return source representation for a method signature
*/
private static String getSourceRepresentation(Method method) {
return method.toString().replace('$', '.');
}
/**
* Used to determine whether the specified interface name is implemented by
* the service class. This is done without loading the class (for security).
*/
private static boolean implementsInterface(Class> service, String intfName) {
synchronized (serviceToImplementedInterfacesMap) {
// See if it's cached.
//
Set