001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.user.rebind.rpc;
017:
018: import com.google.gwt.core.client.GWT;
019: import com.google.gwt.core.ext.GeneratorContext;
020: import com.google.gwt.core.ext.TreeLogger;
021: import com.google.gwt.core.ext.UnableToCompleteException;
022: import com.google.gwt.core.ext.typeinfo.JClassType;
023: import com.google.gwt.core.ext.typeinfo.JMethod;
024: import com.google.gwt.core.ext.typeinfo.JPackage;
025: import com.google.gwt.core.ext.typeinfo.JParameter;
026: import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
027: import com.google.gwt.core.ext.typeinfo.JType;
028: import com.google.gwt.core.ext.typeinfo.TypeOracle;
029: import com.google.gwt.dev.generator.NameFactory;
030: import com.google.gwt.dev.util.Util;
031: import com.google.gwt.user.client.rpc.SerializationException;
032: import com.google.gwt.user.client.rpc.impl.RemoteServiceProxy;
033: import com.google.gwt.user.client.rpc.impl.ClientSerializationStreamReader;
034: import com.google.gwt.user.client.rpc.impl.ClientSerializationStreamWriter;
035: import com.google.gwt.user.client.rpc.impl.RequestCallbackAdapter.ResponseReader;
036: import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
037: import com.google.gwt.user.rebind.SourceWriter;
038: import com.google.gwt.user.server.rpc.SerializationPolicyLoader;
039:
040: import java.io.ByteArrayOutputStream;
041: import java.io.IOException;
042: import java.io.OutputStream;
043: import java.io.OutputStreamWriter;
044: import java.io.PrintWriter;
045: import java.io.UnsupportedEncodingException;
046: import java.util.HashMap;
047: import java.util.Map;
048:
049: /**
050: * Creates a client-side proxy for a
051: * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService} interface
052: * as well as the necessary type and field serializers.
053: */
054: class ProxyCreator {
055: private static final String ENTRY_POINT_TAG = "gwt.defaultEntryPoint";
056:
057: private static final Map<JPrimitiveType, ResponseReader> JPRIMITIVETYPE_TO_RESPONSEREADER = new HashMap<JPrimitiveType, ResponseReader>();
058:
059: private static final String PROXY_SUFFIX = "_Proxy";
060:
061: private boolean enforceTypeVersioning;
062:
063: private JClassType serviceIntf;
064:
065: {
066: JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.BOOLEAN,
067: ResponseReader.BOOLEAN);
068: JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.BYTE,
069: ResponseReader.BYTE);
070: JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.CHAR,
071: ResponseReader.CHAR);
072: JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.DOUBLE,
073: ResponseReader.DOUBLE);
074: JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.FLOAT,
075: ResponseReader.FLOAT);
076: JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.INT,
077: ResponseReader.INT);
078: JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.LONG,
079: ResponseReader.LONG);
080: JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.SHORT,
081: ResponseReader.SHORT);
082: JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.VOID,
083: ResponseReader.VOID);
084: }
085:
086: public ProxyCreator(JClassType serviceIntf) {
087: assert (serviceIntf.isInterface() != null);
088: this .serviceIntf = serviceIntf;
089: }
090:
091: /**
092: * Creates the client-side proxy class.
093: *
094: * @throws UnableToCompleteException
095: */
096: public String create(TreeLogger logger, GeneratorContext context)
097: throws UnableToCompleteException {
098: TypeOracle typeOracle = context.getTypeOracle();
099:
100: JClassType serviceAsync = typeOracle.findType(serviceIntf
101: .getQualifiedSourceName()
102: + "Async");
103: if (serviceAsync == null) {
104: logger.branch(TreeLogger.ERROR,
105: "Could not find an asynchronous version for the service interface "
106: + serviceIntf.getQualifiedSourceName(),
107: null);
108: RemoteServiceAsyncValidator
109: .logValidAsyncInterfaceDeclaration(logger,
110: serviceIntf);
111: throw new UnableToCompleteException();
112: }
113:
114: SourceWriter srcWriter = getSourceWriter(logger, context,
115: serviceAsync);
116: if (srcWriter == null) {
117: return getProxyQualifiedName();
118: }
119:
120: // Make sure that the async and synchronous versions of the RemoteService
121: // agree with one another
122: //
123: RemoteServiceAsyncValidator rsav = new RemoteServiceAsyncValidator(
124: logger, typeOracle);
125: Map<JMethod, JMethod> syncMethToAsyncMethMap = rsav.validate(
126: logger, serviceIntf, serviceAsync);
127:
128: // Determine the set of serializable types
129: SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
130: logger, typeOracle);
131: SerializableTypeOracle sto = stob.build(context
132: .getPropertyOracle(), serviceIntf);
133:
134: TypeSerializerCreator tsc = new TypeSerializerCreator(logger,
135: sto, context, serviceIntf);
136: tsc.realize(logger);
137:
138: enforceTypeVersioning = Shared.shouldEnforceTypeVersioning(
139: logger, context.getPropertyOracle());
140:
141: String serializationPolicyStrongName = writeSerializationPolicyFile(
142: logger, context, sto);
143:
144: generateProxyFields(srcWriter, sto,
145: serializationPolicyStrongName);
146:
147: generateProxyContructor(srcWriter);
148:
149: generateProxyMethods(srcWriter, sto, syncMethToAsyncMethMap);
150:
151: srcWriter.commit(logger);
152:
153: return getProxyQualifiedName();
154: }
155:
156: /**
157: * Generate the proxy constructor and delegate to the superclass constructor
158: * using the default address for the
159: * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService}.
160: */
161: private void generateProxyContructor(SourceWriter srcWriter) {
162: srcWriter.println("public " + getProxySimpleName() + "() {");
163: srcWriter.indent();
164: srcWriter.println("super(GWT.getModuleBaseURL(),");
165: srcWriter.indent();
166: srcWriter.println(getDefaultServiceDefName() + ",");
167: srcWriter.println("SERIALIZATION_POLICY, ");
168: srcWriter.println("SERIALIZER);");
169: srcWriter.outdent();
170: srcWriter.outdent();
171: srcWriter.println("}");
172: }
173:
174: /**
175: * Generate any fields required by the proxy.
176: */
177: private void generateProxyFields(SourceWriter srcWriter,
178: SerializableTypeOracle serializableTypeOracle,
179: String serializationPolicyStrongName) {
180: // Initialize a field with binary name of the remote service interface
181: srcWriter
182: .println("private static final String REMOTE_SERVICE_INTERFACE_NAME = \""
183: + serializableTypeOracle
184: .getSerializedTypeName(serviceIntf)
185: + "\";");
186: srcWriter
187: .println("private static final String SERIALIZATION_POLICY =\""
188: + serializationPolicyStrongName + "\";");
189: String typeSerializerName = serializableTypeOracle
190: .getTypeSerializerQualifiedName(serviceIntf);
191: srcWriter.println("private static final " + typeSerializerName
192: + " SERIALIZER = new " + typeSerializerName + "();");
193: srcWriter.println();
194: }
195:
196: /**
197: * Generates the client's asynchronous proxy method.
198: */
199: private void generateProxyMethod(SourceWriter w,
200: SerializableTypeOracle serializableTypeOracle,
201: JMethod syncMethod, JMethod asyncMethod) {
202:
203: w.println();
204:
205: // Write the method signature
206: JType asyncReturnType = asyncMethod.getReturnType();
207: w.print("public ");
208: w.print(asyncReturnType.getQualifiedSourceName());
209: w.print(" ");
210: w.print(asyncMethod.getName() + "(");
211:
212: boolean needsComma = false;
213: boolean needsTryCatchBlock = false;
214: NameFactory nameFactory = new NameFactory();
215: JParameter[] asyncParams = asyncMethod.getParameters();
216: for (int i = 0; i < asyncParams.length; ++i) {
217: JParameter param = asyncParams[i];
218:
219: if (needsComma) {
220: w.print(", ");
221: } else {
222: needsComma = true;
223: }
224:
225: /*
226: * Ignoring the AsyncCallback parameter, if any method requires a call to
227: * SerializationStreamWriter.writeObject we need a try catch block
228: */
229: JType paramType = param.getType();
230: if (i < asyncParams.length - 1
231: && paramType.isPrimitive() == null
232: && !paramType.getQualifiedSourceName().equals(
233: String.class.getCanonicalName())) {
234: needsTryCatchBlock = true;
235: }
236:
237: w.print(paramType.getParameterizedQualifiedSourceName());
238: w.print(" ");
239:
240: String paramName = param.getName();
241: nameFactory.addName(paramName);
242: w.print(paramName);
243: }
244:
245: w.println(") {");
246:
247: w.indent();
248:
249: w.print(ClientSerializationStreamWriter.class.getSimpleName());
250: w.print(" ");
251: String streamWriterName = nameFactory
252: .createName("streamWriter");
253: w.println(streamWriterName + " = createStreamWriter();");
254: w.println("// createStreamWriter() prepared the stream");
255: w.println(streamWriterName
256: + ".writeString(REMOTE_SERVICE_INTERFACE_NAME);");
257: if (needsTryCatchBlock) {
258: w.println("try {");
259: w.indent();
260: }
261:
262: if (!shouldEnforceTypeVersioning()) {
263: w
264: .println(streamWriterName
265: + ".addFlags("
266: + ClientSerializationStreamReader.class
267: .getName()
268: + ".SERIALIZATION_STREAM_FLAGS_NO_TYPE_VERSIONING);");
269: }
270:
271: // Write the method name
272: w.println(streamWriterName + ".writeString(\""
273: + syncMethod.getName() + "\");");
274:
275: // Write the parameter count followed by the parameter values
276: JParameter[] syncParams = syncMethod.getParameters();
277: w.println(streamWriterName + ".writeInt(" + syncParams.length
278: + ");");
279: for (JParameter param : syncParams) {
280: w.println(streamWriterName
281: + ".writeString(\""
282: + serializableTypeOracle
283: .getSerializedTypeName(param.getType())
284: + "\");");
285: }
286:
287: // Encode all of the arguments to the asynchronous method, but exclude the
288: // last argument which is the callback instance.
289: //
290: for (int i = 0; i < asyncParams.length - 1; ++i) {
291: JParameter asyncParam = asyncParams[i];
292: w.print(streamWriterName + ".");
293: w.print(Shared.getStreamWriteMethodNameFor(asyncParam
294: .getType()));
295: w.println("(" + asyncParam.getName() + ");");
296: }
297:
298: JParameter callbackParam = asyncParams[asyncParams.length - 1];
299: String callbackName = callbackParam.getName();
300: if (needsTryCatchBlock) {
301: w.outdent();
302: w.print("} catch (SerializationException ");
303: String exceptionName = nameFactory.createName("ex");
304: w.println(exceptionName + ") {");
305: w.indent();
306: w.println(callbackName + ".onFailure(" + exceptionName
307: + ");");
308: w.outdent();
309: w.println("}");
310: }
311:
312: w.println();
313: if (asyncReturnType != JPrimitiveType.VOID) {
314: w.print("return ");
315: }
316:
317: // Call the doInvoke method to actually send the request.
318: w.print("doInvoke(");
319: JType returnType = syncMethod.getReturnType();
320: w.print("ResponseReader."
321: + getResponseReaderFor(returnType).name());
322: w.print(", " + streamWriterName + ".toString(), ");
323: w.println(callbackName + ");");
324: w.outdent();
325: w.println("}");
326: }
327:
328: private void generateProxyMethods(SourceWriter w,
329: SerializableTypeOracle serializableTypeOracle,
330: Map<JMethod, JMethod> syncMethToAsyncMethMap) {
331: JMethod[] syncMethods = serviceIntf.getOverridableMethods();
332: for (JMethod syncMethod : syncMethods) {
333: JMethod asyncMethod = syncMethToAsyncMethMap
334: .get(syncMethod);
335: assert (asyncMethod != null);
336:
337: generateProxyMethod(w, serializableTypeOracle, syncMethod,
338: asyncMethod);
339: }
340: }
341:
342: private String getDefaultServiceDefName() {
343: String[][] metaData = serviceIntf.getMetaData(ENTRY_POINT_TAG);
344: if (metaData.length == 0) {
345: return null;
346: }
347:
348: return serviceIntf.getMetaData(ENTRY_POINT_TAG)[0][0];
349: }
350:
351: private String getProxyQualifiedName() {
352: String[] name = Shared.synthesizeTopLevelClassName(serviceIntf,
353: PROXY_SUFFIX);
354: return name[0].length() == 0 ? name[1] : name[0] + "."
355: + name[1];
356: }
357:
358: private String getProxySimpleName() {
359: String[] name = Shared.synthesizeTopLevelClassName(serviceIntf,
360: PROXY_SUFFIX);
361: return name[1];
362: }
363:
364: private ResponseReader getResponseReaderFor(JType returnType) {
365: if (returnType.isPrimitive() != null) {
366: return JPRIMITIVETYPE_TO_RESPONSEREADER.get(returnType
367: .isPrimitive());
368: }
369:
370: if (returnType.getQualifiedSourceName().equals(
371: String.class.getCanonicalName())) {
372: return ResponseReader.STRING;
373: }
374:
375: return ResponseReader.OBJECT;
376: }
377:
378: private SourceWriter getSourceWriter(TreeLogger logger,
379: GeneratorContext ctx, JClassType serviceAsync) {
380: JPackage serviceIntfPkg = serviceAsync.getPackage();
381: String packageName = serviceIntfPkg == null ? ""
382: : serviceIntfPkg.getName();
383: PrintWriter printWriter = ctx.tryCreate(logger, packageName,
384: getProxySimpleName());
385: if (printWriter == null) {
386: return null;
387: }
388:
389: ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(
390: packageName, getProxySimpleName());
391:
392: String[] imports = new String[] {
393: RemoteServiceProxy.class.getCanonicalName(),
394: ClientSerializationStreamWriter.class
395: .getCanonicalName(),
396: GWT.class.getCanonicalName(),
397: ResponseReader.class.getCanonicalName(),
398: SerializationException.class.getCanonicalName() };
399: for (String imp : imports) {
400: composerFactory.addImport(imp);
401: }
402:
403: composerFactory.setSuperclass(RemoteServiceProxy.class
404: .getSimpleName());
405: composerFactory.addImplementedInterface(serviceAsync
406: .getParameterizedQualifiedSourceName());
407:
408: return composerFactory.createSourceWriter(ctx, printWriter);
409: }
410:
411: private boolean shouldEnforceTypeVersioning() {
412: return enforceTypeVersioning;
413: }
414:
415: private String writeSerializationPolicyFile(TreeLogger logger,
416: GeneratorContext ctx, SerializableTypeOracle sto)
417: throws UnableToCompleteException {
418: try {
419: ByteArrayOutputStream baos = new ByteArrayOutputStream();
420: OutputStreamWriter osw = new OutputStreamWriter(
421: baos,
422: SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING);
423: PrintWriter pw = new PrintWriter(osw);
424:
425: JType[] serializableTypes = sto.getSerializableTypes();
426: for (int i = 0; i < serializableTypes.length; ++i) {
427: JType serializableType = serializableTypes[i];
428: String binaryTypeName = sto
429: .getSerializedTypeName(serializableType);
430: boolean maybeInstantiated = sto
431: .maybeInstantiated(serializableType);
432: pw.println(binaryTypeName + ", "
433: + Boolean.toString(maybeInstantiated));
434: }
435:
436: // Closes the wrapped streams.
437: pw.close();
438:
439: byte[] serializationPolicyFileContents = baos.toByteArray();
440: String serializationPolicyName = Util
441: .computeStrongName(serializationPolicyFileContents);
442:
443: String serializationPolicyFileName = SerializationPolicyLoader
444: .getSerializationPolicyFileName(serializationPolicyName);
445: OutputStream os = ctx.tryCreateResource(logger,
446: serializationPolicyFileName);
447: if (os != null) {
448: os.write(serializationPolicyFileContents);
449: ctx.commitResource(logger, os);
450: } else {
451: logger
452: .log(
453: TreeLogger.TRACE,
454: "SerializationPolicy file for RemoteService '"
455: + serviceIntf
456: .getQualifiedSourceName()
457: + "' already exists; no need to rewrite it.",
458: null);
459: }
460:
461: return serializationPolicyName;
462: } catch (UnsupportedEncodingException e) {
463: logger
464: .log(
465: TreeLogger.ERROR,
466: SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING
467: + " is not supported", e);
468: throw new UnableToCompleteException();
469: } catch (IOException e) {
470: logger.log(TreeLogger.ERROR, null, e);
471: throw new UnableToCompleteException();
472: }
473: }
474: }
|