Package com.google.gwt.rpc.server

Source Code of com.google.gwt.rpc.server.RPC

/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.rpc.server;

import com.google.gwt.rpc.client.ast.CommandSink;
import com.google.gwt.rpc.client.ast.HasValues;
import com.google.gwt.rpc.client.ast.ReturnCommand;
import com.google.gwt.rpc.client.ast.RpcCommand;
import com.google.gwt.rpc.client.ast.ThrowCommand;
import com.google.gwt.rpc.client.impl.HasValuesCommandSink;
import com.google.gwt.rpc.client.impl.RemoteException;
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.server.rpc.RPCRequest;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* EXPERIMENTAL and subject to change. Do not use this in production code.
* <p>
* Utility class for integrating with the RPC system.
*/
public class RPC {

  private static final HashMap<String, Class<?>> TYPE_NAMES = new HashMap<String, Class<?>>();

  /**
   * Static map of classes to sets of interfaces (e.g. classes). Optimizes
   * lookup of interfaces for security.
   */
  private static final Map<Class<?>, Set<String>> serviceToImplementedInterfacesMap = new com.google.gwt.dev.util.collect.HashMap<Class<?>, Set<String>>();

  static {
    // The space is needed to prevent name collisions
    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 RPCRequest decodeRequest(String encodedRequest, Class<?> type,
      ClientOracle clientOracle) throws RemoteException {
    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 {
      SimplePayloadDecoder decoder;
      try {
        decoder = new SimplePayloadDecoder(clientOracle, encodedRequest);
      } catch (ClassNotFoundException e) {
        throw new IncompatibleRemoteServiceException(
            "Client does not have a type sent by the server", e);
      }
      CommandServerSerializationStreamReader streamReader = new CommandServerSerializationStreamReader();
      if (decoder.getThrownValue() != null) {
        streamReader.prepareToRead(Collections.singletonList(decoder.getThrownValue()));
        try {
          throw new RemoteException((Throwable) streamReader.readObject());
        } catch (ClassCastException e) {
          throw new SerializationException(
              "The remote end threw something other than a Throwable", e);
        } catch (SerializationException e) {
          throw new IncompatibleRemoteServiceException(
              "The remote end threw an exception which could not be deserialized",
              e);
        }
      } else {
        streamReader.prepareToRead(decoder.getValues());
      }

      // Read the name of the RemoteService interface
      String serviceIntfName = 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");
        }
      }

      Class<?> serviceIntf;
      try {
        serviceIntf = getClassFromSerializedName(null, serviceIntfName,
            classLoader);
        if (!RemoteService.class.isAssignableFrom(serviceIntf)) {
          // The requested interface is not a RpcService interface
          throw new IncompatibleRemoteServiceException(
              "Blocked attempt to access interface '"
                  + printTypeName(serviceIntf)
                  + "', which doesn't extend RpcService; "
                  + "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();
      Class<?>[] parameterTypes = new Class[paramCount];

      for (int i = 0; i < parameterTypes.length; i++) {
        String paramClassName = streamReader.readString();

        try {
          parameterTypes[i] = getClassFromSerializedName(clientOracle,
              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++) {
          Object o = CommandSerializationUtil.getAccessor(parameterTypes[i]).readNext(
              streamReader);
          parameterValues[i] = o;
        }

        return new RPCRequest(method, parameterValues, null, 0);

      } catch (NoSuchMethodException e) {
        throw new IncompatibleRemoteServiceException(
            formatMethodNotFoundErrorMessage(serviceIntf, serviceMethodName,
                parameterTypes));
      }
    } catch (SerializationException ex) {
      throw new IncompatibleRemoteServiceException(ex.getMessage(), ex);
    }
  }

  public static void invokeAndStreamResponse(Object target,
      Method serviceMethod, Object[] args, ClientOracle clientOracle,
      OutputStream stream) throws SerializationException {
    if (serviceMethod == null) {
      throw new NullPointerException("serviceMethod");
    }

    if (clientOracle == null) {
      throw new NullPointerException("clientOracle");
    }

    CommandSink sink;
    try {
      sink = clientOracle.createCommandSink(stream);
    } catch (IOException e) {
      throw new SerializationException("Unable to initialize output", e);
    }

    try {
      Object result = serviceMethod.invoke(target, args);
      try {
        streamResponse(clientOracle, result, sink, false);
      } catch (SerializationException e) {
        streamResponse(clientOracle, e, sink, true);
      }

    } 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();

      streamResponse(clientOracle, cause, sink, true);
    }
    sink.finish();
  }

  public static void streamResponseForFailure(ClientOracle clientOracle,
      OutputStream out, Throwable payload) throws SerializationException {
    CommandSink sink;
    try {
      sink = clientOracle.createCommandSink(out);
    } catch (IOException e) {
      throw new SerializationException("Unable to initialize output", e);
    }
    streamResponse(clientOracle, payload, sink, true);
    sink.finish();
  }

  public static void streamResponseForSuccess(ClientOracle clientOracle,
      OutputStream out, Object payload) throws SerializationException {
    CommandSink sink;
    try {
      sink = clientOracle.createCommandSink(out);
    } catch (IOException e) {
      throw new SerializationException("Unable to initialize output", e);
    }
    streamResponse(clientOracle, payload, sink, false);
    sink.finish();
  }

  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(ClientOracle clientOracle,
      String serializedName, ClassLoader classLoader)
      throws ClassNotFoundException {
    Class<?> value = TYPE_NAMES.get(serializedName);
    if (value != null) {
      return value;
    }

    // Interfaces don't exist in the client, so we use unobfuscated names
    if (serializedName.charAt(0) == ' ') {
      serializedName = serializedName.substring(1);
    } else if (clientOracle != null) {
      serializedName = clientOracle.getTypeName(serializedName);
    }
    assert serializedName != null;

    return Class.forName(serializedName, false, classLoader);
  }

  /**
   * 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<String> interfaceSet = serviceToImplementedInterfacesMap.get(service);
      if (interfaceSet != null) {
        if (interfaceSet.contains(intfName)) {
          return true;
        }
      } else {
        interfaceSet = new HashSet<String>();
        serviceToImplementedInterfacesMap.put(service, interfaceSet);
      }

      if (!service.isInterface()) {
        while ((service != null) && !RpcServlet.class.equals(service)) {
          Class<?>[] intfs = service.getInterfaces();
          for (Class<?> intf : intfs) {
            if (implementsInterfaceRecursive(intf, intfName)) {
              interfaceSet.add(intfName);
              return true;
            }
          }

          // did not find the interface in this class so we look in the
          // superclass
          //
          service = service.getSuperclass();
        }
      } else {
        if (implementsInterfaceRecursive(service, intfName)) {
          interfaceSet.add(intfName);
          return true;
        }
      }

      return false;
    }
  }

  /**
   * Recursive helper for implementsInterface().
   */
  private static boolean implementsInterfaceRecursive(Class<?> clazz,
      String intfName) {
    assert (clazz.isInterface());

    if (clazz.getName().equals(intfName)) {
      return true;
    }

    // search implemented interfaces
    Class<?>[] intfs = clazz.getInterfaces();
    for (Class<?> intf : intfs) {
      if (implementsInterfaceRecursive(intf, intfName)) {
        return true;
      }
    }

    return false;
  }

  /**
   * Straight copy from
   * {@link com.google.gwt.dev.util.TypeInfo#getSourceRepresentation(Class)} to
   * avoid runtime dependency on gwt-dev.
   */
  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('$', '.');
  }

  private static void streamResponse(ClientOracle clientOracle, Object payload,
      CommandSink sink, boolean asThrow) throws SerializationException {
    HasValues command;
    if (asThrow) {
      command = new ThrowCommand();
      assert payload instanceof Throwable : "Trying to throw something other than a Throwable";
      // payload = new RemoteException((Throwable) payload);
    } else {
      command = new ReturnCommand();
    }

    CommandServerSerializationStreamWriter out = new CommandServerSerializationStreamWriter(
        clientOracle, new HasValuesCommandSink(command));

    out.writeObject(payload);

    sink.accept((RpcCommand) command);
  }

  private RPC() {
  }
}
TOP

Related Classes of com.google.gwt.rpc.server.RPC

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.