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('$', '.'); } }