Package com.google.gwt.user.rebind.rpc

Source Code of com.google.gwt.user.rebind.rpc.ProxyCreator

/*
* Copyright 2008 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.user.rebind.rpc;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JPackage;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.generator.GenUtil;
import com.google.gwt.dev.generator.NameFactory;
import com.google.gwt.dev.util.Util;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.impl.ClientSerializationStreamWriter;
import com.google.gwt.user.client.rpc.impl.RemoteServiceProxy;
import com.google.gwt.user.client.rpc.impl.RequestCallbackAdapter.ResponseReader;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.gwt.user.server.rpc.SerializationPolicyLoader;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

/**
* Creates a client-side proxy for a
* {@link com.google.gwt.user.client.rpc.RemoteService RemoteService} interface
* as well as the necessary type and field serializers.
*/
class ProxyCreator {
  private static final String ENTRY_POINT_TAG = "gwt.defaultEntryPoint";

  private static final Map<JPrimitiveType, ResponseReader> JPRIMITIVETYPE_TO_RESPONSEREADER = new HashMap<JPrimitiveType, ResponseReader>();

  private static final String PROXY_SUFFIX = "_Proxy";

  /**
   * Adds a root type for each type that appears in the RemoteService interface
   * methods.
   */
  private static void addRemoteServiceRootTypes(TreeLogger logger,
      TypeOracle typeOracle, SerializableTypeOracleBuilder stob,
      JClassType remoteService) throws NotFoundException {
    logger = logger.branch(TreeLogger.DEBUG, "Analyzing '"
        + remoteService.getParameterizedQualifiedSourceName()
        + "' for serializable types", null);

    JMethod[] methods = remoteService.getOverridableMethods();

    JClassType exceptionClass = typeOracle.getType(Exception.class.getName());

    TreeLogger validationLogger = logger.branch(TreeLogger.DEBUG,
        "Analyzing methods:", null);
    for (JMethod method : methods) {
      TreeLogger methodLogger = validationLogger.branch(TreeLogger.DEBUG,
          method.toString(), null);
      JType returnType = method.getReturnType();
      if (returnType != JPrimitiveType.VOID) {
        TreeLogger returnTypeLogger = methodLogger.branch(TreeLogger.DEBUG,
            "Return type: " + returnType.getParameterizedQualifiedSourceName(),
            null);
        stob.addRootType(returnTypeLogger, returnType);
      }

      JParameter[] params = method.getParameters();
      for (JParameter param : params) {
        TreeLogger paramLogger = methodLogger.branch(TreeLogger.DEBUG,
            "Parameter: " + param.toString(), null);
        JType paramType = param.getType();
        stob.addRootType(paramLogger, paramType);
      }

      JType[] exs = method.getThrows();
      if (exs.length > 0) {
        TreeLogger throwsLogger = methodLogger.branch(TreeLogger.DEBUG,
            "Throws:", null);
        for (JType ex : exs) {
          if (!exceptionClass.isAssignableFrom(ex.isClass())) {
            throwsLogger = throwsLogger.branch(
                TreeLogger.WARN,
                "'"
                    + ex.getQualifiedSourceName()
                    + "' is not a checked exception; only checked exceptions may be used",
                null);
          }

          stob.addRootType(throwsLogger, ex);
        }
      }
    }
  }

  /**
   * Add the implicit root types that are needed to make RPC work. These would
   * be {@link String} and {@link IncompatibleRemoteServiceException}.
   */
  private static void addRequiredRoots(TreeLogger logger,
      TypeOracle typeOracle, SerializableTypeOracleBuilder stob)
      throws NotFoundException {
    logger = logger.branch(TreeLogger.DEBUG, "Analyzing implicit types");

    // String is always instantiable.
    JClassType stringType = typeOracle.getType(String.class.getName());
    stob.addRootType(logger, stringType);

    // IncompatibleRemoteServiceException is always serializable
    JClassType icseType = typeOracle.getType(IncompatibleRemoteServiceException.class.getName());
    stob.addRootType(logger, icseType);
  }

  private JClassType serviceIntf;

  {
    JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.BOOLEAN,
        ResponseReader.BOOLEAN);
    JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.BYTE,
        ResponseReader.BYTE);
    JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.CHAR,
        ResponseReader.CHAR);
    JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.DOUBLE,
        ResponseReader.DOUBLE);
    JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.FLOAT,
        ResponseReader.FLOAT);
    JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.INT, ResponseReader.INT);
    JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.LONG,
        ResponseReader.LONG);
    JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.SHORT,
        ResponseReader.SHORT);
    JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.VOID,
        ResponseReader.VOID);
  }

  public ProxyCreator(JClassType serviceIntf) {
    assert (serviceIntf.isInterface() != null);
    this.serviceIntf = serviceIntf;
  }

  /**
   * Creates the client-side proxy class.
   *
   * @throws UnableToCompleteException
   */
  public String create(TreeLogger logger, GeneratorContext context)
      throws UnableToCompleteException {
    TypeOracle typeOracle = context.getTypeOracle();

    TreeLogger javadocAnnotationDeprecationBranch = null;
    if (GenUtil.warnAboutMetadata()) {
      javadocAnnotationDeprecationBranch = logger.branch(TreeLogger.TRACE,
          "Scanning this RemoteService for deprecated annotations; "
              + "Please see " + RemoteServiceRelativePath.class.getName()
              + " for more information.", null);
    }

    JClassType serviceAsync = typeOracle.findType(serviceIntf.getQualifiedSourceName()
        + "Async");
    if (serviceAsync == null) {
      logger.branch(TreeLogger.ERROR,
          "Could not find an asynchronous version for the service interface "
              + serviceIntf.getQualifiedSourceName(), null);
      RemoteServiceAsyncValidator.logValidAsyncInterfaceDeclaration(logger,
          serviceIntf);
      throw new UnableToCompleteException();
    }

    SourceWriter srcWriter = getSourceWriter(logger, context, serviceAsync);
    if (srcWriter == null) {
      return getProxyQualifiedName();
    }

    // Make sure that the async and synchronous versions of the RemoteService
    // agree with one another
    //
    RemoteServiceAsyncValidator rsav = new RemoteServiceAsyncValidator(logger,
        typeOracle);
    Map<JMethod, JMethod> syncMethToAsyncMethMap = rsav.validate(logger,
        serviceIntf, serviceAsync);

    // Determine the set of serializable types
    SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
        logger, context.getPropertyOracle(), typeOracle);
    try {
      addRequiredRoots(logger, typeOracle, stob);

      addRemoteServiceRootTypes(logger, typeOracle, stob, serviceIntf);
    } catch (NotFoundException e) {
      logger.log(TreeLogger.ERROR, "", e);
      throw new UnableToCompleteException();
    }

    // Create a resource file to receive all of the serialization information
    // computed by STOB and mark it as private so it does not end up in the
    // output.
    OutputStream pathInfo = context.tryCreateResource(logger,
        serviceIntf.getQualifiedSourceName() + ".rpc.log");
    stob.setLogOutputStream(pathInfo);
    SerializableTypeOracle sto = stob.build(logger);
    if (pathInfo != null) {
      context.commitResource(logger, pathInfo).setPrivate(true);
    }

    TypeSerializerCreator tsc = new TypeSerializerCreator(logger, sto, context,
        serviceIntf);
    tsc.realize(logger);

    String serializationPolicyStrongName = writeSerializationPolicyFile(logger,
        context, sto);

    generateProxyFields(srcWriter, sto, serializationPolicyStrongName);

    generateProxyContructor(javadocAnnotationDeprecationBranch, srcWriter);

    generateProxyMethods(srcWriter, sto, syncMethToAsyncMethMap);

    srcWriter.commit(logger);

    return getProxyQualifiedName();
  }

  /**
   * Generate the proxy constructor and delegate to the superclass constructor
   * using the default address for the
   * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService}.
   */
  private void generateProxyContructor(
      TreeLogger javadocAnnotationDeprecationBranch, SourceWriter srcWriter) {
    srcWriter.println("public " + getProxySimpleName() + "() {");
    srcWriter.indent();
    srcWriter.println("super(GWT.getModuleBaseURL(),");
    srcWriter.indent();
    srcWriter.println(getRemoteServiceRelativePath(javadocAnnotationDeprecationBranch)
        + ", ");
    srcWriter.println("SERIALIZATION_POLICY, ");
    srcWriter.println("SERIALIZER);");
    srcWriter.outdent();
    srcWriter.outdent();
    srcWriter.println("}");
  }

  /**
   * Generate any fields required by the proxy.
   */
  private void generateProxyFields(SourceWriter srcWriter,
      SerializableTypeOracle serializableTypeOracle,
      String serializationPolicyStrongName) {
    // Initialize a field with binary name of the remote service interface
    srcWriter.println("private static final String REMOTE_SERVICE_INTERFACE_NAME = \""
        + serializableTypeOracle.getSerializedTypeName(serviceIntf) + "\";");
    srcWriter.println("private static final String SERIALIZATION_POLICY =\""
        + serializationPolicyStrongName + "\";");
    String typeSerializerName = serializableTypeOracle.getTypeSerializerQualifiedName(serviceIntf);
    srcWriter.println("private static final " + typeSerializerName
        + " SERIALIZER = new " + typeSerializerName + "();");
    srcWriter.println();
  }

  /**
   * Generates the client's asynchronous proxy method.
   */
  private void generateProxyMethod(SourceWriter w,
      SerializableTypeOracle serializableTypeOracle, JMethod syncMethod,
      JMethod asyncMethod) {

    w.println();

    // Write the method signature
    JType asyncReturnType = asyncMethod.getReturnType().getErasedType();
    w.print("public ");
    w.print(asyncReturnType.getQualifiedSourceName());
    w.print(" ");
    w.print(asyncMethod.getName() + "(");

    boolean needsComma = false;
    boolean needsTryCatchBlock = false;
    NameFactory nameFactory = new NameFactory();
    JParameter[] asyncParams = asyncMethod.getParameters();
    for (int i = 0; i < asyncParams.length; ++i) {
      JParameter param = asyncParams[i];

      if (needsComma) {
        w.print(", ");
      } else {
        needsComma = true;
      }

      /*
       * Ignoring the AsyncCallback parameter, if any method requires a call to
       * SerializationStreamWriter.writeObject we need a try catch block
       */
      JType paramType = param.getType();
      paramType = paramType.getErasedType();
      if (i < asyncParams.length - 1
          && paramType.isPrimitive() == null
          && !paramType.getQualifiedSourceName().equals(
              String.class.getCanonicalName())) {
        needsTryCatchBlock = true;
      }

      w.print(paramType.getQualifiedSourceName());
      w.print(" ");

      String paramName = param.getName();
      nameFactory.addName(paramName);
      w.print(paramName);
    }

    w.println(") {");
    w.indent();

    String requestIdName = nameFactory.createName("requestId");
    w.println("int " + requestIdName + " = getNextRequestId();");

    String statsMethodExpr = getProxySimpleName() + "." + syncMethod.getName();
    String tossName = nameFactory.createName("toss");
    w.println("boolean " + tossName + " = isStatsAvailable() && stats("
        + "timeStat(\"" + statsMethodExpr + "\", getRequestId(), \"begin\"));");

    w.print(ClientSerializationStreamWriter.class.getSimpleName());
    w.print(" ");
    String streamWriterName = nameFactory.createName("streamWriter");
    w.println(streamWriterName + " = createStreamWriter();");
    w.println("// createStreamWriter() prepared the stream");
    w.println(streamWriterName + ".writeString(REMOTE_SERVICE_INTERFACE_NAME);");
    if (needsTryCatchBlock) {
      w.println("try {");
      w.indent();
    }

    // Write the method name
    w.println(streamWriterName + ".writeString(\"" + syncMethod.getName()
        + "\");");

    // Write the parameter count followed by the parameter values
    JParameter[] syncParams = syncMethod.getParameters();
    w.println(streamWriterName + ".writeInt(" + syncParams.length + ");");
    for (JParameter param : syncParams) {
      w.println(streamWriterName
          + ".writeString(\""
          + serializableTypeOracle.getSerializedTypeName(param.getType().getErasedType())
          + "\");");
    }

    // Encode all of the arguments to the asynchronous method, but exclude the
    // last argument which is the callback instance.
    //
    for (int i = 0; i < asyncParams.length - 1; ++i) {
      JParameter asyncParam = asyncParams[i];
      w.print(streamWriterName + ".");
      w.print(Shared.getStreamWriteMethodNameFor(asyncParam.getType()));
      w.println("(" + asyncParam.getName() + ");");
    }

    JParameter callbackParam = asyncParams[asyncParams.length - 1];
    String callbackName = callbackParam.getName();
    if (needsTryCatchBlock) {
      w.outdent();
      w.print("} catch (SerializationException ");
      String exceptionName = nameFactory.createName("ex");
      w.println(exceptionName + ") {");
      w.indent();
      w.println(callbackName + ".onFailure(" + exceptionName + ");");
      w.outdent();
      w.println("}");
    }

    w.println();

    String payloadName = nameFactory.createName("payload");
    w.println("String " + payloadName + " = " + streamWriterName
        + ".toString();");

    w.println(tossName + " = isStatsAvailable() && stats(" + "timeStat(\""
        + statsMethodExpr + "\", getRequestId(), \"requestSerialized\"));");

    /*
     * Depending on the return type for the async method, return a
     * RequestBuilder, a Request, or nothing at all.
     */
    if (asyncReturnType == JPrimitiveType.VOID) {
      w.print("doInvoke(");
    } else if (asyncReturnType.getQualifiedSourceName().equals(
        RequestBuilder.class.getName())) {
      w.print("return doPrepareRequestBuilder(");
    } else if (asyncReturnType.getQualifiedSourceName().equals(
        Request.class.getName())) {
      w.print("return doInvoke(");
    } else {
      // This method should have been caught by RemoteServiceAsyncValidator
      throw new RuntimeException("Unhandled return type "
          + asyncReturnType.getQualifiedSourceName());
    }

    JType returnType = syncMethod.getReturnType();
    w.print("ResponseReader." + getResponseReaderFor(returnType).name());
    w.println(", \"" + getProxySimpleName() + "." + syncMethod.getName()
        + "\", getRequestId(), " + payloadName + ", " + callbackName + ");");
    w.outdent();
    w.println("}");
  }

  private void generateProxyMethods(SourceWriter w,
      SerializableTypeOracle serializableTypeOracle,
      Map<JMethod, JMethod> syncMethToAsyncMethMap) {
    JMethod[] syncMethods = serviceIntf.getOverridableMethods();
    for (JMethod syncMethod : syncMethods) {

      JMethod asyncMethod = syncMethToAsyncMethMap.get(syncMethod);
      assert (asyncMethod != null);

      JClassType enclosingType = syncMethod.getEnclosingType();
      JParameterizedType isParameterizedType = enclosingType.isParameterized();
      if (isParameterizedType != null) {
        JMethod[] methods = isParameterizedType.getMethods();
        for (int i = 0; i < methods.length; ++i) {
          if (methods[i] == syncMethod) {
            /*
             * Use the generic version of the method to ensure that the server
             * can find the method using the erasure of the generic signature.
             */
            syncMethod = isParameterizedType.getBaseType().getMethods()[i];
          }
        }
      }

      generateProxyMethod(w, serializableTypeOracle, syncMethod, asyncMethod);
    }
  }

  private String getProxyQualifiedName() {
    String[] name = Shared.synthesizeTopLevelClassName(serviceIntf,
        PROXY_SUFFIX);
    return name[0].length() == 0 ? name[1] : name[0] + "." + name[1];
  }

  private String getProxySimpleName() {
    String[] name = Shared.synthesizeTopLevelClassName(serviceIntf,
        PROXY_SUFFIX);
    return name[1];
  }

  private String getRemoteServiceRelativePath(
      TreeLogger javadocAnnotationDeprecationBranch) {
    String[][] metaData = serviceIntf.getMetaData(ENTRY_POINT_TAG);
    if (metaData.length != 0) {
      if (javadocAnnotationDeprecationBranch != null) {
        javadocAnnotationDeprecationBranch.log(TreeLogger.WARN,
            "Deprecated use of " + ENTRY_POINT_TAG + "; Please use "
                + RemoteServiceRelativePath.class.getName() + " instead", null);
      }
      return metaData[0][0];
    } else {
      RemoteServiceRelativePath moduleRelativeURL = serviceIntf.getAnnotation(RemoteServiceRelativePath.class);
      if (moduleRelativeURL != null) {
        return "\"" + moduleRelativeURL.value() + "\"";
      }
    }

    return null;
  }

  private ResponseReader getResponseReaderFor(JType returnType) {
    if (returnType.isPrimitive() != null) {
      return JPRIMITIVETYPE_TO_RESPONSEREADER.get(returnType.isPrimitive());
    }

    if (returnType.getQualifiedSourceName().equals(
        String.class.getCanonicalName())) {
      return ResponseReader.STRING;
    }

    return ResponseReader.OBJECT;
  }

  private SourceWriter getSourceWriter(TreeLogger logger, GeneratorContext ctx,
      JClassType serviceAsync) {
    JPackage serviceIntfPkg = serviceAsync.getPackage();
    String packageName = serviceIntfPkg == null ? "" : serviceIntfPkg.getName();
    PrintWriter printWriter = ctx.tryCreate(logger, packageName,
        getProxySimpleName());
    if (printWriter == null) {
      return null;
    }

    ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(
        packageName, getProxySimpleName());

    String[] imports = new String[] {
        RemoteServiceProxy.class.getCanonicalName(),
        ClientSerializationStreamWriter.class.getCanonicalName(),
        GWT.class.getCanonicalName(), ResponseReader.class.getCanonicalName(),
        SerializationException.class.getCanonicalName()};
    for (String imp : imports) {
      composerFactory.addImport(imp);
    }

    composerFactory.setSuperclass(RemoteServiceProxy.class.getSimpleName());
    composerFactory.addImplementedInterface(serviceAsync.getErasedType().getQualifiedSourceName());

    return composerFactory.createSourceWriter(ctx, printWriter);
  }

  private String writeSerializationPolicyFile(TreeLogger logger,
      GeneratorContext ctx, SerializableTypeOracle sto)
      throws UnableToCompleteException {
    try {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      OutputStreamWriter osw = new OutputStreamWriter(baos,
          SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING);
      PrintWriter pw = new PrintWriter(osw);

      JType[] serializableTypes = sto.getSerializableTypes();
      for (int i = 0; i < serializableTypes.length; ++i) {
        JType serializableType = serializableTypes[i];
        String binaryTypeName = sto.getSerializedTypeName(serializableType);
        boolean maybeInstantiated = sto.maybeInstantiated(serializableType);
        pw.println(binaryTypeName + ", " + Boolean.toString(maybeInstantiated));
      }

      // Closes the wrapped streams.
      pw.close();

      byte[] serializationPolicyFileContents = baos.toByteArray();
      String serializationPolicyName = Util.computeStrongName(serializationPolicyFileContents);

      String serializationPolicyFileName = SerializationPolicyLoader.getSerializationPolicyFileName(serializationPolicyName);
      OutputStream os = ctx.tryCreateResource(logger,
          serializationPolicyFileName);
      if (os != null) {
        os.write(serializationPolicyFileContents);
        ctx.commitResource(logger, os);
      } else {
        logger.log(TreeLogger.TRACE,
            "SerializationPolicy file for RemoteService '"
                + serviceIntf.getQualifiedSourceName()
                + "' already exists; no need to rewrite it.", null);
      }

      return serializationPolicyName;
    } catch (UnsupportedEncodingException e) {
      logger.log(TreeLogger.ERROR,
          SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING
              + " is not supported", e);
      throw new UnableToCompleteException();
    } catch (IOException e) {
      logger.log(TreeLogger.ERROR, null, e);
      throw new UnableToCompleteException();
    }
  }
}
TOP

Related Classes of com.google.gwt.user.rebind.rpc.ProxyCreator

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.