package org.jboss.seam.remoting.gwt;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
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.RPC;
import com.google.gwt.user.server.rpc.SerializationPolicy;
import com.google.gwt.user.server.rpc.SerializationPolicyProvider;
import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader;
import com.google.gwt.user.server.rpc.impl.TypeNameObfuscator;
public final class SeamRPC {
private static final HashMap<String, Class<?>> TYPE_NAMES;
static {
TYPE_NAMES = new HashMap<String, Class<?>>();
TYPE_NAMES.put("Z", boolean.class);
TYPE_NAMES.put("B", byte.class);
TYPE_NAMES.put("C", char.class);
TYPE_NAMES.put("D", double.class);
TYPE_NAMES.put("F", float.class);
TYPE_NAMES.put("I", int.class);
TYPE_NAMES.put("J", long.class);
TYPE_NAMES.put("S", short.class);
}
public static SeamRPCRequest decodeRequest(String encodedRequest, Class<?> type, SerializationPolicyProvider serializationPolicyProvider) {
if (encodedRequest == null) {
throw new NullPointerException("encodedRequest cannot be null");
}
if (encodedRequest.length() == 0) {
throw new IllegalArgumentException("encodedRequest cannot be empty");
}
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
ServerSerializationStreamReader streamReader = new ServerSerializationStreamReader(classLoader, serializationPolicyProvider);
streamReader.prepareToRead(encodedRequest);
// Read the name of the RemoteService interface
String serviceIntfName = maybeDeobfuscate(streamReader, streamReader.readString());
// if (type != null) {
// if (!implementsInterface(type, serviceIntfName)) {
// // The service does not implement the requested interface
// throw new IncompatibleRemoteServiceException(
// "Blocked attempt to access interface '" + serviceIntfName
// + "', which is not implemented by '" + printTypeName(type)
// + "'; this is either misconfiguration or a hack attempt");
// }
// }
SerializationPolicy serializationPolicy = streamReader.getSerializationPolicy();
Class<?> serviceIntf;
try {
serviceIntf = getClassFromSerializedName(serviceIntfName, classLoader);
if (!RemoteService.class.isAssignableFrom(serviceIntf)) {
// The requested interface is not a RemoteService interface
throw new IncompatibleRemoteServiceException("Blocked attempt to access interface '"+ printTypeName(serviceIntf)
+ "', which doesn't extend RemoteService; this is either misconfiguration or a hack attempt");
}
} catch (ClassNotFoundException e) {
throw new IncompatibleRemoteServiceException(
"Could not locate requested interface '" + serviceIntfName + "' in default classloader", e);
}
String serviceMethodName = streamReader.readString();
int paramCount = streamReader.readInt();
if (paramCount > streamReader.getNumberOfTokens()) {
throw new IncompatibleRemoteServiceException("Invalid number of parameters");
}
Class<?>[] parameterTypes = new Class[paramCount];
for (int i = 0; i < parameterTypes.length; i++) {
String paramClassName = maybeDeobfuscate(streamReader, streamReader.readString());
try {
parameterTypes[i] = getClassFromSerializedName(paramClassName, classLoader);
} catch (ClassNotFoundException e) {
throw new IncompatibleRemoteServiceException(
"Parameter " + i + " of is of an unknown type '" + paramClassName + "'", e);
}
}
try {
Method method = serviceIntf.getMethod(serviceMethodName, parameterTypes);
Object[] parameterValues = new Object[parameterTypes.length];
for (int i = 0; i < parameterValues.length; i++) {
parameterValues[i] = streamReader.deserializeValue(parameterTypes[i]);
}
return new SeamRPCRequest(method, parameterValues, parameterTypes, serializationPolicy);
} catch (NoSuchMethodException e) {
throw new IncompatibleRemoteServiceException(
formatMethodNotFoundErrorMessage(serviceIntf, serviceMethodName, parameterTypes));
}
} catch (SerializationException ex) {
throw new IncompatibleRemoteServiceException(ex.getMessage(), ex);
}
}
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);
}
@SuppressWarnings("unchecked")
public static String invokeAndEncodeResponse(
Object target, Method serviceMethod, Class[] paramTypes, Object[] args, SerializationPolicy serializationPolicy)
throws SerializationException
{
if (serviceMethod == null) {
throw new NullPointerException("serviceMethod");
}
if (serializationPolicy == null) {
throw new NullPointerException("serializationPolicy");
}
String responsePayload;
try {
GWTToSeamAdapter adapter = GWTToSeamAdapter.instance();
String serviceIntfName = serviceMethod.getDeclaringClass().getName();
GWTToSeamAdapter.ReturnedObject returnedObject =
adapter.callWebRemoteMethod(serviceIntfName, serviceMethod.getName(), paramTypes, args);
// Object result = serviceMethod.invoke(target, args);
responsePayload = RPC.encodeResponseForSuccess(serviceMethod, returnedObject.returnedObject, serializationPolicy);
} 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 = RPC.encodeResponseForFailure(serviceMethod, cause,serializationPolicy);
}
return responsePayload;
}
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();
}
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 getSourceRepresentation(Method method) {
return method.toString().replace('$', '.');
}
private static String maybeDeobfuscate(ServerSerializationStreamReader streamReader, String name) throws SerializationException {
int index;
if (streamReader.hasFlags(AbstractSerializationStream.FLAG_ELIDE_TYPE_NAMES)) {
SerializationPolicy serializationPolicy = streamReader.getSerializationPolicy();
if (!(serializationPolicy instanceof TypeNameObfuscator)) {
throw new IncompatibleRemoteServiceException("RPC request was encoded with obfuscated type names, "
+ "but the SerializationPolicy in use does not implement "+ TypeNameObfuscator.class.getName());
}
String maybe = ((TypeNameObfuscator) serializationPolicy).getClassNameForTypeId(name);
if (maybe != null) {
return maybe;
}
} else if ((index = name.indexOf('/')) != -1) {
return name.substring(0, index);
}
return name;
}
private static String printTypeName(Class<?> type) {
// Primitives
//
if (type.equals(Integer.TYPE)) {
return "int";
} else if (type.equals(Long.TYPE)) {
return "long";
} else if (type.equals(Short.TYPE)) {
return "short";
} else if (type.equals(Byte.TYPE)) {
return "byte";
} else if (type.equals(Character.TYPE)) {
return "char";
} else if (type.equals(Boolean.TYPE)) {
return "boolean";
} else if (type.equals(Float.TYPE)) {
return "float";
} else if (type.equals(Double.TYPE)) {
return "double";
}
// Arrays
//
if (type.isArray()) {
Class<?> componentType = type.getComponentType();
return printTypeName(componentType) + "[]";
}
// Everything else
//
return type.getName().replace('$', '.');
}
}