com.google.gwt.user.rebind.rpc.ProxyCreator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vaadin-client Show documentation
Show all versions of vaadin-client Show documentation
Vaadin is a web application framework for Rich Internet Applications (RIA).
Vaadin enables easy development and maintenance of fast and
secure rich web
applications with a stunning look and feel and a wide browser support.
It features a server-side architecture with the majority of the logic
running
on the server. Ajax technology is used at the browser-side to ensure a
rich
and interactive user experience.
/*
* 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.client.impl.Impl;
import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.CachedGeneratorResult;
import com.google.gwt.core.ext.CachedPropertyInformation;
import com.google.gwt.core.ext.ConfigurationProperty;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.RebindMode;
import com.google.gwt.core.ext.RebindResult;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
import com.google.gwt.core.ext.linker.GeneratedResource;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JField;
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.NameFactory;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
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.RpcToken;
import com.google.gwt.user.client.rpc.RpcToken.RpcTokenImplementation;
import com.google.gwt.user.client.rpc.RpcTokenException;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.google.gwt.user.client.rpc.impl.ClientSerializationStreamWriter;
import com.google.gwt.user.client.rpc.impl.FailedRequest;
import com.google.gwt.user.client.rpc.impl.FailingRequestBuilder;
import com.google.gwt.user.client.rpc.impl.RemoteServiceProxy;
import com.google.gwt.user.client.rpc.impl.RequestCallbackAdapter.ResponseReader;
import com.google.gwt.user.client.rpc.impl.RpcStatsContext;
import com.google.gwt.user.linker.rpc.RpcLogArtifact;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.gwt.user.server.rpc.SerializationPolicyLoader;
import com.google.gwt.user.server.rpc.impl.TypeNameObfuscator;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* 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.
*/
public class ProxyCreator {
/**
* Some keys for storing cached information for use with generator result
* caching.
*/
public static final String CACHED_PROPERTY_INFO_KEY = "cached-property-info";
public static final String CACHED_TYPE_INFO_KEY = "cached-type-info";
/**
* The directory within which RPC manifests are placed for individual
* permutations.
*/
public static final String MANIFEST_ARTIFACT_DIR = "rpcPolicyManifest/manifests";
/**
* Properties which need to be checked to determine cache reusability.
*/
private static final Collection configPropsToCheck = Arrays.asList(
TypeSerializerCreator.GWT_ELIDE_TYPE_NAMES_FROM_RPC, Shared.RPC_ENHANCED_CLASSES);
private static final Collection selectionPropsToCheck = Arrays
.asList(Shared.RPC_PROP_SUPPRESS_NON_STATIC_FINAL_FIELD_WARNINGS);
private static final String PROXY_SUFFIX = "_Proxy";
private static final Map JPRIMITIVETYPE_TO_RESPONSEREADER =
new HashMap();
static {
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);
}
/**
* Adds a root type for each type that appears in the RemoteService interface
* methods.
*/
private static void addRemoteServiceRootTypes(TreeLogger logger, TypeOracle typeOracle,
SerializableTypeOracleBuilder typesSentFromBrowser,
SerializableTypeOracleBuilder typesSentToBrowser, JClassType remoteService)
throws NotFoundException, UnableToCompleteException {
logger =
logger.branch(TreeLogger.DEBUG, "Analyzing '"
+ remoteService.getParameterizedQualifiedSourceName() + "' for serializable types",
null);
JMethod[] methods = remoteService.getOverridableMethods();
JClassType exceptionClass = typeOracle.getType(Exception.class.getName());
JClassType rteType = typeOracle.getType(RpcTokenException.class.getName());
JClassType rpcTokenClass = typeOracle.getType(RpcToken.class.getName());
RpcTokenImplementation tokenClassToUse =
remoteService.findAnnotationInTypeHierarchy(RpcTokenImplementation.class);
if (tokenClassToUse != null) {
// only include serializer for the specified class literal
JClassType rpcTokenType = typeOracle.getType(tokenClassToUse.value());
if (rpcTokenType.isAssignableTo(rpcTokenClass)) {
typesSentFromBrowser.addRootType(logger, rpcTokenType);
typesSentToBrowser.addRootType(logger, rteType);
} else {
logger.branch(TreeLogger.ERROR, "RPC token class " + tokenClassToUse.value()
+ " must implement " + RpcToken.class.getName(), null);
throw new UnableToCompleteException();
}
} else {
JClassType[] rpcTokenSubclasses = rpcTokenClass.getSubtypes();
for (JClassType rpcTokenSubclass : rpcTokenSubclasses) {
typesSentFromBrowser.addRootType(logger, rpcTokenSubclass);
}
if (rpcTokenSubclasses.length > 0) {
typesSentToBrowser.addRootType(logger, rteType);
}
}
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);
typesSentToBrowser.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();
typesSentFromBrowser.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);
}
typesSentToBrowser.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);
}
/**
* Take the union of two type arrays, and then sort the results
* alphabetically.
*/
private static JType[] unionOfTypeArrays(JType[]... types) {
Set typesList = new HashSet();
for (JType[] a : types) {
typesList.addAll(Arrays.asList(a));
}
JType[] serializableTypes = typesList.toArray(new JType[typesList.size()]);
Arrays.sort(serializableTypes, SerializableTypeOracleBuilder.JTYPE_COMPARATOR);
return serializableTypes;
}
protected JClassType serviceIntf;
private boolean elideTypeNames;
/**
* The possibly obfuscated type signatures used to represent a type.
*/
private Map typeStrings;
private Set customSerializersUsed;
private Set typesNotUsingCustomSerializers;
public ProxyCreator(JClassType serviceIntf) {
assert (serviceIntf.isInterface() != null);
this.serviceIntf = serviceIntf;
}
/**
* Creates the client-side proxy class.
*
* @throws UnableToCompleteException
*/
public RebindResult create(TreeLogger logger, GeneratorContext context)
throws UnableToCompleteException {
TypeOracle typeOracle = context.getTypeOracle();
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();
}
if (checkAlreadyGenerated(typeOracle, serviceIntf)) {
return new RebindResult(RebindMode.USE_EXISTING, getProxyQualifiedName());
}
// Make sure that the async and synchronous versions of the RemoteService
// agree with one another
//
RemoteServiceAsyncValidator rsav = new RemoteServiceAsyncValidator(logger, typeOracle);
Map syncMethToAsyncMethMap = rsav.validate(logger, serviceIntf, serviceAsync);
final PropertyOracle propertyOracle = context.getPropertyOracle();
// Load the blacklist/whitelist
TypeFilter blacklistTypeFilter = new BlacklistTypeFilter(logger, propertyOracle);
// Determine the set of serializable types
Event event = SpeedTracerLogger.start(CompilerEventType.GENERATOR_RPC_STOB);
SerializableTypeOracle typesSentFromBrowser;
SerializableTypeOracle typesSentToBrowser;
String rpcLog = null;
try {
SerializableTypeOracleBuilder typesSentFromBrowserBuilder =
new SerializableTypeOracleBuilder(logger, context);
typesSentFromBrowserBuilder.setTypeFilter(blacklistTypeFilter);
SerializableTypeOracleBuilder typesSentToBrowserBuilder =
new SerializableTypeOracleBuilder(logger, context);
typesSentToBrowserBuilder.setTypeFilter(blacklistTypeFilter);
addRoots(logger, typeOracle, typesSentFromBrowserBuilder, typesSentToBrowserBuilder);
// Decide what types to send in each direction.
{
if (logger.isLoggable(TreeLogger.Type.DEBUG)) {
// Log the decisions to a string that will be written later in this method
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
typesSentFromBrowserBuilder.setLogOutputWriter(writer);
typesSentToBrowserBuilder.setLogOutputWriter(writer);
writer.write("====================================\n");
writer.write("Types potentially sent from browser:\n");
writer.write("====================================\n\n");
writer.flush();
typesSentFromBrowser = typesSentFromBrowserBuilder.build(logger);
writer.write("===================================\n");
writer.write("Types potentially sent from server:\n");
writer.write("===================================\n\n");
writer.flush();
typesSentToBrowser = typesSentToBrowserBuilder.build(logger);
writer.close();
rpcLog = stringWriter.toString();
} else {
typesSentFromBrowser = typesSentFromBrowserBuilder.build(logger);
typesSentToBrowser = typesSentToBrowserBuilder.build(logger);
}
}
} finally {
event.end();
}
// Check previous cached result, to see if we can return now
if (checkCachedGeneratorResultValid(logger, context, typesSentFromBrowser, typesSentToBrowser)) {
logger.log(TreeLogger.TRACE, "Reusing all cached artifacts for " + getProxyQualifiedName());
return new RebindResult(RebindMode.USE_ALL_CACHED, getProxyQualifiedName());
}
try {
ConfigurationProperty prop =
context.getPropertyOracle().getConfigurationProperty(
TypeSerializerCreator.GWT_ELIDE_TYPE_NAMES_FROM_RPC);
elideTypeNames = Boolean.parseBoolean(prop.getValues().get(0));
} catch (BadPropertyValueException e) {
logger.log(TreeLogger.ERROR, "Configuration property "
+ TypeSerializerCreator.GWT_ELIDE_TYPE_NAMES_FROM_RPC
+ " is not defined. Is RemoteService.gwt.xml inherited?");
throw new UnableToCompleteException();
}
SourceWriter srcWriter = getSourceWriter(logger, context, serviceAsync);
if (srcWriter == null) {
// don't expect this to occur, but could happen if an instance was
// recently generated but not yet committed
return new RebindResult(RebindMode.USE_EXISTING, getProxyQualifiedName());
}
generateTypeHandlers(logger, context, typesSentFromBrowser, typesSentToBrowser);
String serializationPolicyStrongName =
writeSerializationPolicyFile(logger, context, typesSentFromBrowser, typesSentToBrowser);
String remoteServiceInterfaceName =
elideTypeNames ? TypeNameObfuscator.SERVICE_INTERFACE_ID : SerializationUtils
.getRpcTypeName(serviceIntf);
generateProxyFields(srcWriter, typesSentFromBrowser, serializationPolicyStrongName,
remoteServiceInterfaceName);
generateProxyContructor(srcWriter);
generateProxyMethods(srcWriter, typesSentFromBrowser, typeOracle, syncMethToAsyncMethMap);
generateStreamWriterOverride(srcWriter);
generateCheckRpcTokenTypeOverride(srcWriter, typeOracle, typesSentFromBrowser);
srcWriter.commit(logger);
if (rpcLog != null) {
// Create an artifact explaining STOB's decisions. It will be emitted by
// RpcLogLinker
context.commitArtifact(logger, new RpcLogArtifact(serviceIntf.getQualifiedSourceName(),
serializationPolicyStrongName, rpcLog));
}
if (checkGeneratorResultCacheability(context)) {
/*
* Create a new cacheable result. The mode is set to
* RebindMode.USE_PARTIAL_CACHED, since we are allowing reuse of cached
* results for field serializers, when available, but all other types have
* been newly generated.
*/
RebindResult result =
new RebindResult(RebindMode.USE_PARTIAL_CACHED, getProxyQualifiedName());
// Remember the type info that we care about for cache reuse testing.
CachedRpcTypeInformation cti =
new CachedRpcTypeInformation(typesSentFromBrowser, typesSentToBrowser,
customSerializersUsed, typesNotUsingCustomSerializers);
CachedPropertyInformation cpi =
new CachedPropertyInformation(logger, context.getPropertyOracle(), selectionPropsToCheck,
configPropsToCheck);
result.putClientData(CACHED_TYPE_INFO_KEY, cti);
result.putClientData(CACHED_PROPERTY_INFO_KEY, cpi);
return result;
} else {
return new RebindResult(RebindMode.USE_ALL_NEW_WITH_NO_CACHING, getProxyQualifiedName());
}
}
protected void addRoots(TreeLogger logger, TypeOracle typeOracle,
SerializableTypeOracleBuilder typesSentFromBrowserBuilder,
SerializableTypeOracleBuilder typesSentToBrowserBuilder) throws UnableToCompleteException {
try {
addRequiredRoots(logger, typeOracle, typesSentFromBrowserBuilder);
addRequiredRoots(logger, typeOracle, typesSentToBrowserBuilder);
addRemoteServiceRootTypes(logger, typeOracle, typesSentFromBrowserBuilder,
typesSentToBrowserBuilder, serviceIntf);
} catch (NotFoundException e) {
logger.log(TreeLogger.ERROR, "Unable to find type referenced from remote service", e);
throw new UnableToCompleteException();
}
}
protected String computeTypeNameExpression(JType paramType) {
String typeName;
if (typeStrings.containsKey(paramType)) {
typeName = typeStrings.get(paramType);
} else {
typeName = SerializationUtils.getRpcTypeName(paramType);
}
return typeName == null ? null : ('"' + typeName + '"');
}
protected void generateCheckRpcTokenTypeOverride(SourceWriter srcWriter, TypeOracle typeOracle,
SerializableTypeOracle typesSentFromBrowser) {
JClassType rpcTokenType = typeOracle.findType(RpcToken.class.getName());
JClassType[] rpcTokenSubtypes = rpcTokenType.getSubtypes();
String rpcTokenImplementation = "";
for (JClassType rpcTokenSubtype : rpcTokenSubtypes) {
if (typesSentFromBrowser.isSerializable(rpcTokenSubtype)) {
if (rpcTokenImplementation.length() > 0) {
// >1 implematation of RpcToken, bail
rpcTokenImplementation = "";
break;
} else {
rpcTokenImplementation = rpcTokenSubtype.getQualifiedSourceName();
}
}
}
if (rpcTokenImplementation.length() > 0) {
srcWriter.println("@Override");
srcWriter.println("protected void checkRpcTokenType(RpcToken token) {");
srcWriter.indent();
srcWriter.println("if (!(token instanceof " + rpcTokenImplementation + ")) {");
srcWriter.indent();
srcWriter.println("throw new RpcTokenException(\"Invalid RpcToken type: " + "expected '"
+ rpcTokenImplementation + "' but got '\" + " + "token.getClass() + \"'\");");
srcWriter.outdent();
srcWriter.println("}");
srcWriter.outdent();
srcWriter.println("}");
}
}
/**
* 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}.
*/
protected void generateProxyContructor(SourceWriter srcWriter) {
srcWriter.println("public " + getProxySimpleName() + "() {");
srcWriter.indent();
srcWriter.println("super(GWT.getModuleBaseURL(),");
srcWriter.indent();
srcWriter.println(getRemoteServiceRelativePath() + ", ");
srcWriter.println("SERIALIZATION_POLICY, ");
srcWriter.println("SERIALIZER);");
srcWriter.outdent();
srcWriter.outdent();
srcWriter.println("}");
}
/**
* Generate any fields required by the proxy.
*
* @param serializableTypeOracle the type oracle
*/
protected void generateProxyFields(SourceWriter srcWriter,
SerializableTypeOracle serializableTypeOracle, String serializationPolicyStrongName,
String remoteServiceInterfaceName) {
// Initialize a field with binary name of the remote service interface
srcWriter.println("private static final String REMOTE_SERVICE_INTERFACE_NAME = " + "\""
+ remoteServiceInterfaceName + "\";");
srcWriter.println("private static final String SERIALIZATION_POLICY =\""
+ serializationPolicyStrongName + "\";");
String typeSerializerName = SerializationUtils.getTypeSerializerQualifiedName(serviceIntf);
srcWriter.println("private static final " + typeSerializerName + " SERIALIZER = new "
+ typeSerializerName + "();");
srcWriter.println();
}
/**
* Generates the client's asynchronous proxy method.
*
* @param serializableTypeOracle the type oracle
*/
protected void generateProxyMethod(SourceWriter w, SerializableTypeOracle serializableTypeOracle,
TypeOracle typeOracle, 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;
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();
w.print(paramType.getQualifiedSourceName());
w.print(" ");
String paramName = param.getName();
nameFactory.addName(paramName);
w.print(paramName);
}
w.println(") {");
w.indent();
String helperName = nameFactory.createName("helper");
String helperClassName = RemoteServiceProxy.ServiceHelper.class.getCanonicalName();
w.println("%s %s = new %s(\"%s\", \"%s\");", helperClassName, helperName, helperClassName,
getProxySimpleName(), syncMethod.getName());
w.println("try {");
w.indent();
// Write the parameter count followed by the parameter values
JParameter[] syncParams = syncMethod.getParameters();
String streamWriterName = nameFactory.createName("streamWriter");
w.println("%s %s = %s.start(REMOTE_SERVICE_INTERFACE_NAME, %s);",
SerializationStreamWriter.class.getSimpleName(), streamWriterName, helperName,
syncParams.length);
for (JParameter param : syncParams) {
JType paramType = param.getType().getErasedType();
String typeNameExpression = computeTypeNameExpression(paramType);
assert typeNameExpression != null : "Could not compute a type name for "
+ paramType.getQualifiedSourceName();
w.println(streamWriterName + ".writeString(" + typeNameExpression + ");");
}
// 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() + ");");
}
/*
* Depending on the return type for the async method, return a
* RequestBuilder, a Request, or nothing at all.
*/
JParameter callbackParam = asyncParams[asyncParams.length - 1];
JType returnType = syncMethod.getReturnType();
String callbackName = callbackParam.getName();
if (asyncReturnType == JPrimitiveType.VOID) {
w.println("%s.finish(%s, ResponseReader.%s);", helperName, callbackName,
getResponseReaderFor(returnType).name());
} else if (asyncReturnType.getQualifiedSourceName().equals(RequestBuilder.class.getName())) {
w.println("return %s.finishForRequestBuilder(%s, ResponseReader.%s);", helperName,
callbackName, getResponseReaderFor(returnType).name());
} else if (asyncReturnType.getQualifiedSourceName().equals(Request.class.getName())) {
w.println("return %s.finish(%s, ResponseReader.%s);", helperName, callbackName,
getResponseReaderFor(returnType).name());
} else {
// This method should have been caught by RemoteServiceAsyncValidator
throw new RuntimeException("Unhandled return type "
+ asyncReturnType.getQualifiedSourceName());
}
w.outdent();
w.print("} catch (SerializationException ");
String exceptionName = nameFactory.createName("ex");
w.println(exceptionName + ") {");
w.indent();
if (!asyncReturnType.getQualifiedSourceName().equals(RequestBuilder.class.getName())) {
/*
* If the method returns void or Request, signal the serialization error
* immediately. If the method returns RequestBuilder, the error will be
* signaled whenever RequestBuilder.send() is invoked.
*/
w.println(callbackName + ".onFailure(" + exceptionName + ");");
}
if (asyncReturnType.getQualifiedSourceName().equals(RequestBuilder.class.getName())) {
w.println("return new " + FailingRequestBuilder.class.getName() + "(" + exceptionName + ", "
+ callbackName + ");");
} else if (asyncReturnType.getQualifiedSourceName().equals(Request.class.getName())) {
w.println("return new " + FailedRequest.class.getName() + "();");
} else {
assert asyncReturnType == JPrimitiveType.VOID;
}
w.outdent();
w.println("}");
w.outdent();
w.println("}");
}
protected void generateProxyMethods(SourceWriter w,
SerializableTypeOracle serializableTypeOracle, TypeOracle typeOracle,
Map 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, typeOracle, syncMethod, asyncMethod);
}
}
/**
* @param syncMethod
* @param asyncMethod
* @param statsContextName
*/
protected void generateRpcStatsContext(SourceWriter w, JMethod syncMethod, JMethod asyncMethod,
String statsContextName) {
w.println("RpcStatsContext " + statsContextName + " = new RpcStatsContext();");
}
protected void generateStreamWriterOverride(SourceWriter srcWriter) {
srcWriter.println("@Override");
srcWriter.println("public SerializationStreamWriter createStreamWriter() {");
srcWriter.indent();
/*
* Need an explicit cast since we've widened the declaration of the method
* in RemoteServiceProxy.
*/
srcWriter.println("ClientSerializationStreamWriter toReturn =");
srcWriter.indentln("(ClientSerializationStreamWriter) super.createStreamWriter();");
if (elideTypeNames) {
srcWriter.println("toReturn.addFlags(ClientSerializationStreamWriter."
+ "FLAG_ELIDE_TYPE_NAMES);");
}
srcWriter.println("if (getRpcToken() != null) {");
srcWriter.indent();
srcWriter.println("toReturn.addFlags(ClientSerializationStreamWriter."
+ "FLAG_RPC_TOKEN_INCLUDED);");
srcWriter.outdent();
srcWriter.println("}");
srcWriter.println("return toReturn;");
srcWriter.outdent();
srcWriter.println("}");
}
protected void generateTypeHandlers(TreeLogger logger, GeneratorContext context,
SerializableTypeOracle typesSentFromBrowser, SerializableTypeOracle typesSentToBrowser)
throws UnableToCompleteException {
Event event = SpeedTracerLogger.start(CompilerEventType.GENERATOR_RPC_TYPE_SERIALIZER);
TypeSerializerCreator tsc =
new TypeSerializerCreator(logger, typesSentFromBrowser, typesSentToBrowser, context,
SerializationUtils.getTypeSerializerQualifiedName(serviceIntf), SerializationUtils
.getTypeSerializerSimpleName(serviceIntf));
tsc.realize(logger);
event.end();
typeStrings = new HashMap(tsc.getTypeStrings());
typeStrings.put(serviceIntf, TypeNameObfuscator.SERVICE_INTERFACE_ID);
customSerializersUsed = tsc.getCustomSerializersUsed();
typesNotUsingCustomSerializers = tsc.getTypesNotUsingCustomSerializers();
}
protected String getProxySimpleName() {
String[] name = Shared.synthesizeTopLevelClassName(serviceIntf, PROXY_SUFFIX);
return name[1];
}
protected Class extends RemoteServiceProxy> getProxySupertype() {
return RemoteServiceProxy.class;
}
protected String getRemoteServiceRelativePath() {
RemoteServiceRelativePath moduleRelativeURL =
serviceIntf.getAnnotation(RemoteServiceRelativePath.class);
if (moduleRelativeURL != null) {
return "\"" + moduleRelativeURL.value() + "\"";
}
return null;
}
protected Class extends SerializationStreamWriter> getStreamWriterClass() {
return ClientSerializationStreamWriter.class;
}
protected String writeSerializationPolicyFile(TreeLogger logger, GeneratorContext ctx,
SerializableTypeOracle serializationSto, SerializableTypeOracle deserializationSto)
throws UnableToCompleteException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStreamWriter osw =
new OutputStreamWriter(baos, SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING);
PrintWriter pw = new PrintWriter(osw);
JType[] serializableTypes =
unionOfTypeArrays(serializationSto.getSerializableTypes(), deserializationSto
.getSerializableTypes(), new JType[] {serviceIntf});
pw.print(SerializationPolicyLoader.FINAL_FIELDS_KEYWORD);
pw.print(", ");
pw.print(Shared.shouldSerializeFinalFields(logger, ctx));
pw.print('\n');
for (int i = 0; i < serializableTypes.length; ++i) {
JType type = serializableTypes[i];
String binaryTypeName = SerializationUtils.getRpcTypeName(type);
pw.print(binaryTypeName);
pw.print(", " + Boolean.toString(deserializationSto.isSerializable(type)));
pw.print(", " + Boolean.toString(deserializationSto.maybeInstantiated(type)));
pw.print(", " + Boolean.toString(serializationSto.isSerializable(type)));
pw.print(", " + Boolean.toString(serializationSto.maybeInstantiated(type)));
pw.print(", " + typeStrings.get(type));
/*
* Include the serialization signature to bump the RPC file name if
* obfuscated identifiers are used.
*/
pw.print(", " + SerializationUtils.getSerializationSignature(ctx, type));
pw.print('\n');
/*
* Emit client-side field information for classes that may be enhanced
* on the server. Each line consists of a comma-separated list
* containing the keyword '@ClientFields', the class name, and a list of
* all potentially serializable client-visible fields.
*/
if ((type instanceof JClassType) && ((JClassType) type).isEnhanced()) {
JField[] fields = ((JClassType) type).getFields();
JField[] rpcFields = new JField[fields.length];
int numRpcFields = 0;
for (JField f : fields) {
if (f.isTransient() || f.isStatic() || f.isFinal()) {
continue;
}
rpcFields[numRpcFields++] = f;
}
pw.print(SerializationPolicyLoader.CLIENT_FIELDS_KEYWORD);
pw.print(',');
pw.print(binaryTypeName);
for (int idx = 0; idx < numRpcFields; idx++) {
pw.print(',');
pw.print(rpcFields[idx].getName());
}
pw.print('\n');
}
}
// 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);
GeneratedResource resource = ctx.commitResource(logger, os);
/*
* Record which proxy class created the resource. A manifest will be
* emitted by the RpcPolicyManifestLinker.
*/
emitPolicyFileArtifact(logger, ctx, resource.getPartialPath());
} else {
if (logger.isLoggable(TreeLogger.TRACE)) {
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();
}
}
private boolean checkAlreadyGenerated(TypeOracle typeOracle, JClassType serviceAsync) {
JPackage serviceIntfPkg = serviceAsync.getPackage();
String packageName = serviceIntfPkg == null ? "" : serviceIntfPkg.getName();
return typeOracle.findType(packageName, getProxySimpleName()) != null;
}
private boolean checkCachedGeneratorResultValid(TreeLogger logger, GeneratorContext ctx,
SerializableTypeOracle typesSentFromBrowser, SerializableTypeOracle typesSentToBrowser) {
CachedGeneratorResult lastResult = ctx.getCachedGeneratorResult();
if (lastResult == null || !ctx.isGeneratorResultCachingEnabled()) {
return false;
}
CachedPropertyInformation cpi =
(CachedPropertyInformation) lastResult.getClientData(CACHED_PROPERTY_INFO_KEY);
if (cpi == null) {
return false;
}
CachedRpcTypeInformation cti =
(CachedRpcTypeInformation) lastResult.getClientData(CACHED_TYPE_INFO_KEY);
if (cti == null) {
return false;
}
if (!cti.checkTypeInformation(logger, ctx.getTypeOracle(), typesSentFromBrowser,
typesSentToBrowser)) {
return false;
}
if (!cpi.checkPropertiesWithPropertyOracle(logger, ctx.getPropertyOracle())) {
return false;
}
return true;
}
private boolean checkGeneratorResultCacheability(GeneratorContext context) {
/*
* Currently not supporting caching for implementations which sub-class this
* class, such as {@link RpcProxyCreator}, which implements deRPC.
*/
if (!this.getClass().equals(ProxyCreator.class)) {
return false;
}
return context.isGeneratorResultCachingEnabled();
}
private void emitPolicyFileArtifact(TreeLogger logger, GeneratorContext context,
String partialPath) throws UnableToCompleteException {
try {
String qualifiedSourceName = serviceIntf.getQualifiedSourceName();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Writer writer;
writer =
new OutputStreamWriter(baos, SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING);
writer.write("serviceClass: " + qualifiedSourceName + "\n");
writer.write("path: " + partialPath + "\n");
writer.close();
byte[] manifestBytes = baos.toByteArray();
String md5 = Util.computeStrongName(manifestBytes);
OutputStream os =
context.tryCreateResource(logger, MANIFEST_ARTIFACT_DIR + "/" + md5 + ".txt");
os.write(manifestBytes);
GeneratedResource resource = context.commitResource(logger, os);
// TODO: change to Deploy when possible
resource.setVisibility(Visibility.LegacyDeploy);
} 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();
}
}
private String getProxyQualifiedName() {
String[] name = Shared.synthesizeTopLevelClassName(serviceIntf, PROXY_SUFFIX);
return name[0].length() == 0 ? name[1] : name[0] + "." + name[1];
}
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[] {
getProxySupertype().getCanonicalName(), getStreamWriterClass().getCanonicalName(),
SerializationStreamWriter.class.getCanonicalName(), GWT.class.getCanonicalName(),
ResponseReader.class.getCanonicalName(),
SerializationException.class.getCanonicalName(), RpcToken.class.getCanonicalName(),
RpcTokenException.class.getCanonicalName(), Impl.class.getCanonicalName(),
RpcStatsContext.class.getCanonicalName()};
for (String imp : imports) {
composerFactory.addImport(imp);
}
composerFactory.setSuperclass(getProxySupertype().getSimpleName());
composerFactory.addImplementedInterface(serviceAsync.getErasedType().getQualifiedSourceName());
return composerFactory.createSourceWriter(ctx, printWriter);
}
}