All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.rpc.server.SimplePayloadDecoder Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * 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 static com.google.gwt.rpc.client.impl.SimplePayloadSink.ARRAY_TYPE;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.BACKREF_TYPE;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.BOOLEAN_TYPE;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.BYTE_TYPE;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.CHAR_TYPE;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.DOUBLE_TYPE;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.ENUM_TYPE;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.FLOAT_TYPE;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.INT_TYPE;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.INVOKE_TYPE;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.LONG_TYPE;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.NL_CHAR;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.OBJECT_TYPE;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.RETURN_TYPE;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.RPC_SEPARATOR_CHAR;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.SHORT_TYPE;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.STRING_TYPE;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.THROW_TYPE;
import static com.google.gwt.rpc.client.impl.SimplePayloadSink.VOID_TYPE;

import com.google.gwt.rpc.client.ast.ArrayValueCommand;
import com.google.gwt.rpc.client.ast.BooleanValueCommand;
import com.google.gwt.rpc.client.ast.ByteValueCommand;
import com.google.gwt.rpc.client.ast.CharValueCommand;
import com.google.gwt.rpc.client.ast.DoubleValueCommand;
import com.google.gwt.rpc.client.ast.EnumValueCommand;
import com.google.gwt.rpc.client.ast.FloatValueCommand;
import com.google.gwt.rpc.client.ast.HasSetters;
import com.google.gwt.rpc.client.ast.IdentityValueCommand;
import com.google.gwt.rpc.client.ast.InstantiateCommand;
import com.google.gwt.rpc.client.ast.IntValueCommand;
import com.google.gwt.rpc.client.ast.InvokeCustomFieldSerializerCommand;
import com.google.gwt.rpc.client.ast.LongValueCommand;
import com.google.gwt.rpc.client.ast.NullValueCommand;
import com.google.gwt.rpc.client.ast.ReturnCommand;
import com.google.gwt.rpc.client.ast.RpcCommand;
import com.google.gwt.rpc.client.ast.ScalarValueCommand;
import com.google.gwt.rpc.client.ast.ShortValueCommand;
import com.google.gwt.rpc.client.ast.StringValueCommand;
import com.google.gwt.rpc.client.ast.ValueCommand;
import com.google.gwt.user.server.rpc.impl.SerializabilityUtil;

import java.lang.reflect.Array;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

/**
 * Decodes the simple payload.
 */
public class SimplePayloadDecoder {
  private static final String OBFUSCATED_CLASS_PREFIX = "Class$ ";
  private static final Map> PRIMITIVE_TYPES = new HashMap>();

  static {
    // Obfuscated when class metadata is disabled
    PRIMITIVE_TYPES.put(OBFUSCATED_CLASS_PREFIX + BOOLEAN_TYPE, boolean.class);
    PRIMITIVE_TYPES.put(OBFUSCATED_CLASS_PREFIX + BYTE_TYPE, byte.class);
    PRIMITIVE_TYPES.put(OBFUSCATED_CLASS_PREFIX + CHAR_TYPE, char.class);
    PRIMITIVE_TYPES.put(OBFUSCATED_CLASS_PREFIX + DOUBLE_TYPE, double.class);
    PRIMITIVE_TYPES.put(OBFUSCATED_CLASS_PREFIX + FLOAT_TYPE, float.class);
    PRIMITIVE_TYPES.put(OBFUSCATED_CLASS_PREFIX + INT_TYPE, int.class);
    PRIMITIVE_TYPES.put(OBFUSCATED_CLASS_PREFIX + LONG_TYPE, long.class);
    PRIMITIVE_TYPES.put(OBFUSCATED_CLASS_PREFIX + SHORT_TYPE, short.class);
    PRIMITIVE_TYPES.put(OBFUSCATED_CLASS_PREFIX + VOID_TYPE, void.class);

    // Regular
    PRIMITIVE_TYPES.put(boolean.class.getName(), boolean.class);
    PRIMITIVE_TYPES.put(byte.class.getName(), byte.class);
    PRIMITIVE_TYPES.put(char.class.getName(), char.class);
    PRIMITIVE_TYPES.put(double.class.getName(), double.class);
    PRIMITIVE_TYPES.put(float.class.getName(), float.class);
    PRIMITIVE_TYPES.put(int.class.getName(), int.class);
    PRIMITIVE_TYPES.put(long.class.getName(), long.class);
    PRIMITIVE_TYPES.put(short.class.getName(), short.class);
    PRIMITIVE_TYPES.put(void.class.getName(), void.class);
  }

  private final Map backRefs = new HashMap();
  private final Map> classCache = new HashMap>(
      PRIMITIVE_TYPES);
  private final ClientOracle clientOracle;
  private final Stack commands = new Stack();
  private int idx;
  private final CharSequence payload;
  private ReturnCommand toReturn;
  private ValueCommand toThrow;

  /**
   * Construct a new SimplePayloadDecoder. This will consume the entire payload
   * which will be made available through {@link #getValues}. If the payload
   * stream contains an embedded exception, processing will end early and the
   * Throwable will be available via {@link #getThrownValue()}.
   * 
   * @throws ClassNotFoundException
   */
  public SimplePayloadDecoder(ClientOracle clientOracle, CharSequence payload)
      throws ClassNotFoundException {
    this.clientOracle = clientOracle;
    this.payload = payload;
    while (toReturn == null && idx < payload.length()) {
      decodeCommand();

      // We hit an error in the stream; stop now
      if (toThrow != null) {
        return;
      }
    }
  }

  /**
   * Returns the thrown value, if any.
   */
  public ValueCommand getThrownValue() {
    return toThrow;
  }

  /**
   * Returns the values encoded in the payload.
   */
  public List getValues() {
    return toReturn == null ? Collections. emptyList()
        : toReturn.getValues();
  }

  private void decodeCommand() throws ClassNotFoundException {
    char command = next();
    if (command == NL_CHAR) {
      // Pretty mode payload
      command = next();
    }
    String token = token();
    switch (command) {
      case BOOLEAN_TYPE: {
        push(new BooleanValueCommand(token.equals("1")));
        break;
      }
      case BYTE_TYPE: {
        push(new ByteValueCommand(Byte.valueOf(token)));
        break;
      }
      case CHAR_TYPE: {
        push(new CharValueCommand(Character.valueOf((char) Integer.valueOf(
            token).intValue())));
        break;
      }
      case DOUBLE_TYPE: {
        push(new DoubleValueCommand(Double.valueOf(token)));
        break;
      }
      case FLOAT_TYPE: {
        push(new FloatValueCommand(Float.valueOf(token)));
        break;
      }
      case INT_TYPE: {
        push(new IntValueCommand(Integer.valueOf(token)));
        break;
      }
      case LONG_TYPE: {
        push(new LongValueCommand(Long.valueOf(token)));
        break;
      }
      case VOID_TYPE: {
        push(NullValueCommand.INSTANCE);
        break;
      }
      case SHORT_TYPE: {
        push(new ShortValueCommand(Short.valueOf(token)));
        break;
      }
      case STRING_TYPE: {
        // "4~abcd
        int length = Integer.valueOf(token);
        String value = next(length);
        if (next() != RPC_SEPARATOR_CHAR) {
          throw new RuntimeException("Overran string");
        }
        push(new StringValueCommand(value));
        break;
      }
      case ENUM_TYPE: {
        // ETypeSeedName~IOrdinal~
        EnumValueCommand x = new EnumValueCommand();
        push(x);
        
        // use ordinal (and not name), since name might have been obfuscated
        int ordinal = readCommand(IntValueCommand.class).getValue();

        @SuppressWarnings("unchecked")
        Class clazz = findClass(token).asSubclass(Enum.class);
        
        /*
         * TODO: Note this approach could be prone to subtle corruption or
         * an ArrayOutOfBoundsException if the client and server have drifted.
         */
        Enum enumConstants[] = clazz.getEnumConstants();
        x.setValue(enumConstants[ordinal]);
        break;
      }
      case ARRAY_TYPE: {
        // Encoded as (leafType, dimensions, length, .... )
        Class leaf = findClass(token);

        Integer numDims = readCommand(IntValueCommand.class).getValue();
        Class clazz;
        if (numDims > 1) {
          int[] dims = new int[numDims - 1];
          clazz = Array.newInstance(leaf, dims).getClass();
        } else {
          clazz = leaf;
        }

        ArrayValueCommand x = new ArrayValueCommand(clazz);
        push(x);
        int length = readCommand(IntValueCommand.class).getValue();
        for (int i = 0; i < length; i++) {
          x.add(readCommand(ValueCommand.class));
        }
        break;
      }
      case OBJECT_TYPE: {
        // @TypeSeedName~3~... N-many setters ...
        Class clazz = findClass(token);
        InstantiateCommand x = new InstantiateCommand(clazz);
        push(x);
        readSetters(clazz, x);
        break;
      }
      case INVOKE_TYPE: {
        // !TypeSeedName~Number of objects written by CFS~...CFS objects...~
        // Number of extra fields~...N-many setters...
        Class clazz = findClass(token);
        Class serializerClass = null;

        // The custom serializer type might be for a supertype
        Class manualType = clazz;
        while (manualType != null) {
          serializerClass = SerializabilityUtil.hasCustomFieldSerializer(manualType);
          if (serializerClass != null) {
            break;
          }
          manualType = manualType.getSuperclass();
        }

        InvokeCustomFieldSerializerCommand x = new InvokeCustomFieldSerializerCommand(
            clazz, serializerClass, manualType);
        push(x);

        readFields(x);
        readSetters(clazz, x);
        break;
      }
      case RETURN_TYPE: {
        // R4~...values...
        toReturn = new ReturnCommand();
        int toRead = Integer.valueOf(token);
        for (int i = 0; i < toRead; i++) {
          toReturn.addValue(readCommand(ValueCommand.class));
        }
        break;
      }
      case THROW_TYPE: {
        // T...value...
        toThrow = readCommand(ValueCommand.class);
        break;
      }
      case BACKREF_TYPE: {
        // @backrefNumber~
        ValueCommand x = backRefs.get(Integer.valueOf(token));
        assert x != null : "Could not find backref";
        commands.push(x);
        break;
      }
      case RPC_SEPARATOR_CHAR: {
        /*
         * Not strictly necessary, but it makes an off-by-one easier to
         * distinguish.
         */
        throw new RuntimeException("Segmentation overrun at " + idx);
      }
      default:
        throw new RuntimeException("Unknown command " + command);
    }
  }

  /**
   * Uses the ClientOracle to decode a type name.
   */
  private Class findClass(String token) throws ClassNotFoundException {
    /*
     * NB: This is the only method in SimplePayloadDecoder which would require
     * any real adaptation to be made to run in Production Mode.
     */

    Class clazz = classCache.get(token);
    if (clazz != null) {
      return clazz;
    }

    String className = clientOracle.getTypeName(token);
    if (className == null) {
      // Probably a regular class name
      className = token;
    }

    if (className.contains("[]")) {
      // Array types are annoying to construct
      int firstIndex = -1;
      int j = -1;
      int dims = 0;
      while ((j = className.indexOf("[", j + 1)) != -1) {
        if (dims++ == 0) {
          firstIndex = j;
        }
      }
      Class componentType = findClass(className.substring(0, firstIndex));
      assert componentType != null : "Could not determine component type with "
          + className.substring(0, firstIndex);
      clazz = Array.newInstance(componentType, new int[dims]).getClass();
    } else {
      // Ensure that we use the bridge classloader in CCL
      ClassLoader myCCL = getClass().getClassLoader();
      clazz = Class.forName(className, false, myCCL);
    }
    classCache.put(token, clazz);
    return clazz;
  }

  /**
   * Reads the next character in the input, possibly evaluating escape
   * sequences.
   */
  private char next() {
    char c = payload.charAt(idx++);

    if (c == '\\') {
      switch (payload.charAt(idx++)) {
        case '0':
          c = '\0';
          break;
        case '!':
          // Compatibility since we're using the legacy escaping code
          c = '|';
          break;
        case 'b':
          c = '\b';
          break;
        case 't':
          c = '\t';
          break;
        case 'n':
          c = '\n';
          break;
        case 'f':
          c = '\f';
          break;
        case 'r':
          c = '\r';
          break;
        case '\\':
          c = '\\';
          break;
        case '"':
          c = '"';
          break;
        case 'u':
          c = (char) Integer.parseInt(
              payload.subSequence(idx, idx += 4).toString(), 16);
          break;
        case 'x':
          c = (char) Integer.parseInt(
              payload.subSequence(idx, idx += 2).toString(), 16);
          break;
        default:
          throw new RuntimeException("Unhandled escape " + payload.charAt(idx));
      }
    }
    return c;
  }

  /**
   * Reads count many characters and returns them as a string.
   */
  private String next(int count) {
    StringBuilder sb = new StringBuilder();
    while (count-- > 0) {
      sb.append(next());
    }
    return sb.toString();
  }

  /**
   * Retains the object value and establishes a backreference.
   */
  private void push(IdentityValueCommand x) {
    commands.push(x);
    backRefs.put(backRefs.size(), x);
  }

  /**
   * Retains the scalar value, but does not establish a backreference.
   */
  private void push(ScalarValueCommand x) {
    commands.push(x);
  }

  /**
   * Retains the string value and establishes a backreference.
   */
  private void push(StringValueCommand x) {
    commands.push(x);
    backRefs.put(backRefs.size(), x);
  }

  /**
   * Read one command from the stream.
   * 
   * @param  the expected type of RpcCommand to read
   * @param clazz the expected type of RpcCommand to read
   * @throws ClassCastException if a command was successfully read, but could
   *           not be assigned to clazz
   */
  private  T readCommand(Class clazz)
      throws ClassNotFoundException {
    decodeCommand();
    RpcCommand value = commands.pop();
    assert clazz.isInstance(value) : "Cannot assign a "
        + value.getClass().getName() + " to " + clazz.getName();
    return clazz.cast(value);
  }

  /**
   * Format is (int, value...).
   */
  private void readFields(InvokeCustomFieldSerializerCommand x)
      throws ClassNotFoundException {
    int length = readCommand(IntValueCommand.class).getValue();
    for (int i = 0; i < length; i++) {
      x.addValue(readCommand(ValueCommand.class));
    }
  }

  /**
   * Format is (fieldDeclClassName, fieldId, value). fieldDeclClassName may be
   * null.
   */
  private void readSetter(Class clazz, HasSetters x)
      throws ClassNotFoundException {
    // Only used by Development Mode to handle shadowing
    if (!clientOracle.isScript()) {
      String fieldDeclClassName = readCommand(StringValueCommand.class).getValue();
      if (fieldDeclClassName != null) {
        clazz = findClass(fieldDeclClassName);
      }
    }
    String fieldId = readCommand(StringValueCommand.class).getValue();

    Pair, String> data = clientOracle.getFieldName(clazz, fieldId);
    Class fieldDeclClass = data.getA();
    String fieldName = data.getB();
    ValueCommand value = readCommand(ValueCommand.class);
    x.set(fieldDeclClass, fieldName, value);
  }

  /**
   * Format is (int, setter...).
   */
  private void readSetters(Class clazz, HasSetters x)
      throws ClassNotFoundException {
    int length = readCommand(IntValueCommand.class).getValue();
    for (int i = 0; i < length; i++) {
      readSetter(clazz, x);
    }
  }

  /**
   * Read through the next separator character.
   */
  private String token() {
    StringBuilder sb = new StringBuilder();
    char n = next();
    while (n != RPC_SEPARATOR_CHAR) {
      sb.append(n);
      n = next();
    }
    return sb.toString();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy