| /* |
| * 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.ConfigurationProperty; |
| import com.google.gwt.core.ext.GeneratorContext; |
| import com.google.gwt.core.ext.PropertyOracle; |
| import com.google.gwt.core.ext.TreeLogger; |
| import com.google.gwt.core.ext.UnableToCompleteException; |
| 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.javac.TypeOracleMediator; |
| 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.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.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 { |
| /** |
| * The directory within which RPC manifests are placed for individual |
| * permutations. |
| */ |
| public static final String MANIFEST_ARTIFACT_DIR = "rpcPolicyManifest/manifests"; |
| |
| 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 typesSentFromBrowser, |
| SerializableTypeOracleBuilder typesSentToBrowser, 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); |
| 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<JType> typesList = new HashSet<JType>(); |
| for (JType[] a : types) { |
| typesList.addAll(Arrays.asList(a)); |
| } |
| JType[] serializableTypes = typesList.toArray(new JType[0]); |
| 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<JType, String> typeStrings; |
| |
| { |
| 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(); |
| |
| 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); |
| |
| final PropertyOracle propertyOracle = context.getPropertyOracle(); |
| |
| // Load the blacklist/whitelist |
| TypeFilter blacklistTypeFilter = new BlacklistTypeFilter(logger, |
| propertyOracle); |
| |
| // Determine the set of serializable types |
| SerializableTypeOracleBuilder typesSentFromBrowserBuilder = new SerializableTypeOracleBuilder( |
| logger, propertyOracle, typeOracle); |
| typesSentFromBrowserBuilder.setTypeFilter(blacklistTypeFilter); |
| SerializableTypeOracleBuilder typesSentToBrowserBuilder = new SerializableTypeOracleBuilder( |
| logger, propertyOracle, typeOracle); |
| typesSentToBrowserBuilder.setTypeFilter(blacklistTypeFilter); |
| |
| addRoots(logger, typeOracle, typesSentFromBrowserBuilder, |
| typesSentToBrowserBuilder); |
| |
| 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(); |
| } |
| |
| // Decide what types to send in each direction. |
| // Log the decisions to a string that will be written later in this method |
| SerializableTypeOracle typesSentFromBrowser; |
| SerializableTypeOracle typesSentToBrowser; |
| String rpcLog; |
| { |
| 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(); |
| } |
| |
| generateTypeHandlers(logger, context, typesSentFromBrowser, |
| typesSentToBrowser); |
| |
| String serializationPolicyStrongName = writeSerializationPolicyFile(logger, |
| context, typesSentFromBrowser, typesSentToBrowser); |
| |
| String remoteServiceInterfaceName = elideTypeNames |
| ? TypeNameObfuscator.SERVICE_INTERFACE_ID |
| : TypeOracleMediator.computeBinaryClassName(serviceIntf); |
| generateProxyFields(srcWriter, typesSentFromBrowser, |
| serializationPolicyStrongName, remoteServiceInterfaceName); |
| |
| generateProxyContructor(srcWriter); |
| |
| generateProxyMethods(srcWriter, typesSentFromBrowser, |
| syncMethToAsyncMethMap); |
| |
| if (elideTypeNames) { |
| generateStreamWriterOverride(srcWriter); |
| } |
| |
| srcWriter.commit(logger); |
| |
| // Create an artifact explaining STOB's decisions. It will be emitted by |
| // RpcLogLinker |
| context.commitArtifact(logger, new RpcLogArtifact( |
| serviceIntf.getQualifiedSourceName(), serializationPolicyStrongName, |
| rpcLog)); |
| |
| return 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 = TypeOracleMediator.computeBinaryClassName(paramType); |
| } |
| return typeName == null ? null : ('"' + typeName + '"'); |
| } |
| |
| /** |
| * 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, 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 statsContextName = nameFactory.createName("statsContext"); |
| generateRpcStatsContext(w, syncMethod, asyncMethod, statsContextName); |
| |
| String statsMethodExpr = getProxySimpleName() + "." + syncMethod.getName(); |
| String tossName = nameFactory.createName("toss"); |
| w.println("boolean %s = %s.isStatsAvailable() && %s.stats(%s.timeStat(\"%s\", \"begin\"));", |
| tossName, statsContextName, statsContextName, statsContextName, statsMethodExpr); |
| w.print(SerializationStreamWriter.class.getSimpleName()); |
| w.print(" "); |
| String streamWriterName = nameFactory.createName("streamWriter"); |
| w.println(streamWriterName + " = createStreamWriter();"); |
| w.println("// createStreamWriter() prepared the stream"); |
| w.println("try {"); |
| w.indent(); |
| |
| w.println(streamWriterName + ".writeString(REMOTE_SERVICE_INTERFACE_NAME);"); |
| |
| // 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) { |
| 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() + ");"); |
| } |
| |
| String payloadName = nameFactory.createName("payload"); |
| w.println("String " + payloadName + " = " + streamWriterName |
| + ".toString();"); |
| |
| w.println(tossName + " = " + statsContextName + ".isStatsAvailable() && " |
| + statsContextName + ".stats(" + statsContextName + ".timeStat(\"" |
| + statsMethodExpr + "\", \"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()); |
| } |
| |
| JParameter callbackParam = asyncParams[asyncParams.length - 1]; |
| String callbackName = callbackParam.getName(); |
| JType returnType = syncMethod.getReturnType(); |
| w.print("ResponseReader." + getResponseReaderFor(returnType).name()); |
| w.println(", \"" + getProxySimpleName() + "." + syncMethod.getName() |
| + "\", " + statsContextName + ", " + payloadName + ", " + callbackName |
| + ");"); |
| |
| 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, |
| 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); |
| } |
| } |
| |
| /** |
| * @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();"); |
| srcWriter.println("toReturn.addFlags(ClientSerializationStreamWriter.FLAG_ELIDE_TYPE_NAMES);"); |
| srcWriter.println("return toReturn;"); |
| srcWriter.outdent(); |
| srcWriter.println("}"); |
| } |
| |
| protected void generateTypeHandlers(TreeLogger logger, |
| GeneratorContext context, SerializableTypeOracle typesSentFromBrowser, |
| SerializableTypeOracle typesSentToBrowser) |
| throws UnableToCompleteException { |
| TypeSerializerCreator tsc = new TypeSerializerCreator(logger, |
| typesSentFromBrowser, typesSentToBrowser, context, |
| SerializationUtils.getTypeSerializerQualifiedName(serviceIntf), |
| SerializationUtils.getTypeSerializerSimpleName(serviceIntf)); |
| tsc.realize(logger); |
| |
| typeStrings = new HashMap<JType, String>(tsc.getTypeStrings()); |
| typeStrings.put(serviceIntf, TypeNameObfuscator.SERVICE_INTERFACE_ID); |
| } |
| |
| 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); |
| TypeOracle oracle = ctx.getTypeOracle(); |
| PrintWriter pw = new PrintWriter(osw); |
| |
| JType[] serializableTypes = unionOfTypeArrays( |
| serializationSto.getSerializableTypes(), |
| deserializationSto.getSerializableTypes(), new JType[] {serviceIntf}); |
| |
| for (int i = 0; i < serializableTypes.length; ++i) { |
| JType type = serializableTypes[i]; |
| String binaryTypeName = TypeOracleMediator.computeBinaryClassName(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(oracle, 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 { |
| 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 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); |
| resource.setPrivate(true); |
| } 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(), |
| 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); |
| } |
| } |