001 /**
002 * Copyright (C) 2009-2011 the original author or authors.
003 * See the notice.md file distributed with this work for additional
004 * information regarding copyright ownership.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package org.fusesource.restygwt.rebind;
020
021 import java.lang.annotation.Annotation;
022 import java.lang.reflect.InvocationTargetException;
023 import java.util.ArrayList;
024 import java.util.Arrays;
025 import java.util.HashMap;
026 import java.util.HashSet;
027 import java.util.LinkedList;
028 import java.util.List;
029 import java.util.Map;
030 import java.util.Set;
031 import java.util.regex.Pattern;
032
033 import javax.ws.rs.Consumes;
034 import javax.ws.rs.DELETE;
035 import javax.ws.rs.GET;
036 import javax.ws.rs.HEAD;
037 import javax.ws.rs.HeaderParam;
038 import javax.ws.rs.OPTIONS;
039 import javax.ws.rs.POST;
040 import javax.ws.rs.PUT;
041 import javax.ws.rs.Path;
042 import javax.ws.rs.PathParam;
043 import javax.ws.rs.Produces;
044 import javax.ws.rs.QueryParam;
045
046 import org.fusesource.restygwt.client.AbstractAsyncCallback;
047 import org.fusesource.restygwt.client.AbstractRequestCallback;
048 import org.fusesource.restygwt.client.Attribute;
049 import org.fusesource.restygwt.client.Defaults;
050 import org.fusesource.restygwt.client.Dispatcher;
051 import org.fusesource.restygwt.client.JSONP;
052 import org.fusesource.restygwt.client.Json;
053 import org.fusesource.restygwt.client.Json.Style;
054 import org.fusesource.restygwt.client.JsonCallback;
055 import org.fusesource.restygwt.client.JsonpMethod;
056 import org.fusesource.restygwt.client.Method;
057 import org.fusesource.restygwt.client.MethodCallback;
058 import org.fusesource.restygwt.client.Options;
059 import org.fusesource.restygwt.client.OverlayCallback;
060 import org.fusesource.restygwt.client.Resource;
061 import org.fusesource.restygwt.client.ResponseFormatException;
062 import org.fusesource.restygwt.client.RestService;
063 import org.fusesource.restygwt.client.RestServiceProxy;
064 import org.fusesource.restygwt.client.TextCallback;
065 import org.fusesource.restygwt.client.XmlCallback;
066
067 import com.google.gwt.core.client.JavaScriptObject;
068 import com.google.gwt.core.client.JsArray;
069 import com.google.gwt.core.client.JsArrayBoolean;
070 import com.google.gwt.core.client.JsArrayInteger;
071 import com.google.gwt.core.client.JsArrayNumber;
072 import com.google.gwt.core.client.JsArrayString;
073 import com.google.gwt.core.ext.GeneratorContext;
074 import com.google.gwt.core.ext.TreeLogger;
075 import com.google.gwt.core.ext.UnableToCompleteException;
076 import com.google.gwt.core.ext.typeinfo.JClassType;
077 import com.google.gwt.core.ext.typeinfo.JGenericType;
078 import com.google.gwt.core.ext.typeinfo.JMethod;
079 import com.google.gwt.core.ext.typeinfo.JParameter;
080 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
081 import com.google.gwt.core.ext.typeinfo.JType;
082 import com.google.gwt.core.ext.typeinfo.JTypeParameter;
083 import com.google.gwt.http.client.RequestException;
084 import com.google.gwt.json.client.JSONArray;
085 import com.google.gwt.json.client.JSONObject;
086 import com.google.gwt.json.client.JSONParser;
087 import com.google.gwt.json.client.JSONValue;
088 import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
089 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
090 import com.google.gwt.xml.client.Document;
091
092 /**
093 *
094 * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
095 *
096 * Updates: added automatically create resource from Path annotation,
097 * enhanced generics support
098 * @author <a href="http://www.acuedo.com">Dave Finch</a>
099 */
100 public class RestServiceClassCreator extends BaseSourceCreator {
101
102 private static final String REST_SERVICE_PROXY_SUFFIX = "_Generated_RestServiceProxy_";
103
104 private static final String METHOD_CLASS = Method.class.getName();
105 private static final String RESOURCE_CLASS = Resource.class.getName();
106 private static final String DISPATCHER_CLASS = Dispatcher.class.getName();
107 private static final String DEFAULTS_CLASS = Defaults.class.getName();
108 private static final String ABSTRACT_REQUEST_CALLBACK_CLASS = AbstractRequestCallback.class.getName();
109 private static final String ABSTRACT_ASYNC_CALLBACK_CLASS = AbstractAsyncCallback.class.getName();
110 private static final String JSON_PARSER_CLASS = JSONParser.class.getName();
111 private static final String JSON_ARRAY_CLASS = JSONArray.class.getName();
112 private static final String JSON_OBJECT_CLASS = JSONObject.class.getName();
113 private static final String REQUEST_EXCEPTION_CLASS = RequestException.class.getName();
114 private static final String RESPONSE_FORMAT_EXCEPTION_CLASS = ResponseFormatException.class.getName();
115 private static final String JSONP_METHOD_CLASS = JsonpMethod.class.getName();
116
117 /*
118 * static class in which are some compile-time relevant infos.
119 *
120 * TODO (andi): too much flexibility and overhead with reflection here?
121 */
122 private static final Class<BindingDefaults> BINDING_DEFAULTS = BindingDefaults.class;
123
124 private static final String METHOD_JSONP = "jsonp";
125 private static final String METHOD_PUT = "put";
126 private static final String METHOD_POST = "post";
127 private static final String METHOD_OPTIONS = "options";
128 private static final String METHOD_HEAD = "head";
129 private static final String METHOD_GET = "get";
130 private static final String METHOD_DELETE = "delete";
131
132 private static final HashSet<String> REST_METHODS = new HashSet<String>(8);
133 static {
134 REST_METHODS.add(METHOD_DELETE);
135 REST_METHODS.add(METHOD_GET);
136 REST_METHODS.add(METHOD_HEAD);
137 REST_METHODS.add(METHOD_OPTIONS);
138 REST_METHODS.add(METHOD_POST);
139 REST_METHODS.add(METHOD_PUT);
140 REST_METHODS.add(METHOD_JSONP);
141 }
142
143 private JClassType XML_CALLBACK_TYPE;
144 private JClassType METHOD_CALLBACK_TYPE;
145 private JClassType TEXT_CALLBACK_TYPE;
146 private JClassType JSON_CALLBACK_TYPE;
147 private JClassType OVERLAY_CALLBACK_TYPE;
148 private JClassType DOCUMENT_TYPE;
149 private JClassType METHOD_TYPE;
150 private JClassType STRING_TYPE;
151 private JClassType JSON_VALUE_TYPE;
152 private JClassType OVERLAY_VALUE_TYPE;
153 private Set<JClassType> OVERLAY_ARRAY_TYPES;
154 private Set<JClassType> QUERY_PARAM_LIST_TYPES;
155 private JClassType REST_SERVICE_TYPE;
156 private JsonEncoderDecoderInstanceLocator locator;
157
158 public RestServiceClassCreator(TreeLogger logger, GeneratorContext context, JClassType source) throws UnableToCompleteException {
159 super(logger, context, source, REST_SERVICE_PROXY_SUFFIX);
160 }
161
162 @Override
163 protected ClassSourceFileComposerFactory createComposerFactory() {
164 String parameters = "";
165 if(source instanceof JGenericType)
166 {
167 JGenericType gtype = (JGenericType)source;
168 StringBuilder builder = new StringBuilder();
169 builder.append("<");
170 boolean first = true;
171 for(JTypeParameter arg : gtype.getTypeParameters())
172 {
173 if(!first)
174 builder.append(",");
175 builder.append(arg.getName());
176 builder.append(" extends ");
177 builder.append(arg.getFirstBound().getParameterizedQualifiedSourceName());
178 first = false;
179 }
180 builder.append(">");
181 parameters = builder.toString();
182 }
183
184 ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName, shortName + parameters);
185 composerFactory.addImplementedInterface(source.getParameterizedQualifiedSourceName());
186 composerFactory.addImplementedInterface(RestServiceProxy.class.getName());
187 return composerFactory;
188 }
189
190 @Override
191 protected void generate() throws UnableToCompleteException {
192
193 if (source.isInterface() == null) {
194 error("Type is not an interface.");
195 }
196
197 locator = new JsonEncoderDecoderInstanceLocator(context, logger);
198
199 this.XML_CALLBACK_TYPE = find(XmlCallback.class);
200 this.METHOD_CALLBACK_TYPE = find(MethodCallback.class);
201 this.TEXT_CALLBACK_TYPE = find(TextCallback.class);
202 this.JSON_CALLBACK_TYPE = find(JsonCallback.class);
203 this.OVERLAY_CALLBACK_TYPE = find(OverlayCallback.class);
204 this.DOCUMENT_TYPE = find(Document.class);
205 this.METHOD_TYPE = find(Method.class);
206 this.STRING_TYPE = find(String.class);
207 this.JSON_VALUE_TYPE = find(JSONValue.class);
208 this.OVERLAY_VALUE_TYPE = find(JavaScriptObject.class);
209 this.OVERLAY_ARRAY_TYPES = new HashSet<JClassType>();
210 this.OVERLAY_ARRAY_TYPES.add(find(JsArray.class));
211 this.OVERLAY_ARRAY_TYPES.add(find(JsArrayBoolean.class));
212 this.OVERLAY_ARRAY_TYPES.add(find(JsArrayInteger.class));
213 this.OVERLAY_ARRAY_TYPES.add(find(JsArrayNumber.class));
214 this.OVERLAY_ARRAY_TYPES.add(find(JsArrayString.class));
215 this.QUERY_PARAM_LIST_TYPES = new HashSet<JClassType>();
216 this.QUERY_PARAM_LIST_TYPES.add(find(List.class));
217 this.QUERY_PARAM_LIST_TYPES.add(find(Set.class));
218 this.REST_SERVICE_TYPE = find(RestService.class);
219
220 String path = null;
221 Path pathAnnotation = source.getAnnotation(Path.class);
222 if (pathAnnotation != null) {
223 path = pathAnnotation.value();
224 }
225
226 RemoteServiceRelativePath relativePath = source.getAnnotation(RemoteServiceRelativePath.class);
227 if (relativePath != null) {
228 path = relativePath.value();
229 }
230
231 if (path == null) {
232 p("private " + RESOURCE_CLASS + " resource = new " + RESOURCE_CLASS + "(" + DEFAULTS_CLASS + ".getServiceRoot());");
233 } else {
234 p("private " + RESOURCE_CLASS + " resource = new " + RESOURCE_CLASS + "(" + DEFAULTS_CLASS + ".getServiceRoot()).resolve("+quote(path)+");");
235 }
236 p();
237
238 p("public void setResource(" + RESOURCE_CLASS + " resource) {").i(1);
239 {
240 p("this.resource = resource;");
241 }
242 i(-1).p("}");
243
244 p("public " + RESOURCE_CLASS + " getResource() {").i(1);
245 {
246 p("return this.resource;");
247 }
248 i(-1).p("}");
249
250
251 Options options = source.getAnnotation(Options.class);
252 if( options!=null && options.dispatcher()!=Dispatcher.class ) {
253 p("private " + DISPATCHER_CLASS + " dispatcher = "+options.dispatcher().getName()+".INSTANCE;");
254 } else {
255 p("private " + DISPATCHER_CLASS + " dispatcher = "+DEFAULTS_CLASS+".getDispatcher();");
256 }
257
258 p();
259 p("public void setDispatcher(" + DISPATCHER_CLASS + " dispatcher) {").i(1);
260 {
261 p("this.dispatcher = dispatcher;");
262 }
263 i(-1).p("}");
264
265 p();
266 p("public " + DISPATCHER_CLASS + " getDispatcher() {").i(1);
267 {
268 p("return this.dispatcher;");
269 }
270 i(-1).p("}");
271
272 for (JMethod method : source.getInheritableMethods()) {
273 JClassType iface = method.getReturnType().isInterface();
274 if(iface != null && REST_SERVICE_TYPE.isAssignableFrom(iface))
275 writeSubresourceLocatorImpl(method);
276 else
277 writeMethodImpl(method);
278 }
279 }
280
281 private String quote(String path) {
282 // TODO: unlikely to occur. but we should escape chars like newlines..
283 return "\"" + path + "\"";
284 }
285
286 private boolean isOverlayArrayType(JClassType type) {
287 for (JClassType arrayType : OVERLAY_ARRAY_TYPES) {
288 if (type.isAssignableTo(arrayType)) {
289 return true;
290 }
291 }
292 return false;
293 }
294
295 private boolean isQueryParamListType(JClassType type) {
296 if (type.isParameterized() == null) {
297 return false;
298 }
299 for (JClassType listType : QUERY_PARAM_LIST_TYPES) {
300 if (type.isAssignableTo(listType)) {
301 return true;
302 }
303 }
304 return false;
305 }
306
307 private void writeSubresourceLocatorImpl(JMethod method) throws UnableToCompleteException
308 {
309 JClassType iface = method.getReturnType().isInterface();
310 if(iface == null || !REST_SERVICE_TYPE.isAssignableFrom(iface)) {
311 error("Invalid subresource locator method. Method must have return type of an interface that extends RestService: " + method.getReadableDeclaration());
312 }
313
314 Path pathAnnotation = method.getAnnotation(Path.class);
315 if (pathAnnotation == null) {
316 error("Invalid subresource locator method. Method must have @Path annotation: " + method.getReadableDeclaration());
317 }
318 String pathExpression = wrap(pathAnnotation.value());
319
320 for (JParameter arg : method.getParameters()) {
321 PathParam paramPath = arg.getAnnotation(PathParam.class);
322 if (paramPath != null) {
323 pathExpression = pathExpression.replaceAll(Pattern.quote("{" + paramPath.value() + "}"), "\"+" + toStringExpression(arg) + "+\"");
324 if (arg.getAnnotation(Attribute.class) != null) {
325 error("Attribute annotations not allowed on subresource locators");
326 }
327 }
328 }
329
330
331 p(method.getReadableDeclaration(false, false, false, false, true) + " {").i(1);
332 {
333 JType type = method.getReturnType();
334 String name;
335 if(type instanceof JClassType)
336 {
337 JClassType restService = (JClassType)type;
338 RestServiceClassCreator generator = new RestServiceClassCreator(logger, context, restService);
339 name = generator.create();
340 }
341 else
342 {
343 throw new UnsupportedOperationException("Subresource method may not return: " + type);
344 }
345 p(method.getReturnType().getQualifiedSourceName() + " __subresource = new " + name + "();");
346 p("((" + RestServiceProxy.class.getName() + ")__subresource).setResource(getResource().resolve(" + pathExpression + "));");
347 p("return __subresource;");
348 }
349 i(-1).p("}");
350 }
351
352 private void writeMethodImpl(JMethod method) throws UnableToCompleteException {
353 if (method.getReturnType().isPrimitive() != JPrimitiveType.VOID) {
354 error("Invalid rest method. Method must have void return type: " + method.getReadableDeclaration());
355 }
356
357 Json jsonAnnotation = source.getAnnotation(Json.class);
358 final Style classStyle = jsonAnnotation != null ? jsonAnnotation.style() : Style.DEFAULT;
359
360 Options classOptions = source.getAnnotation(Options.class);
361 Options options = method.getAnnotation(Options.class);
362
363 p(method.getReadableDeclaration(false, false, false, false, true) + " {").i(1);
364 {
365 String restMethod = getRestMethod(method);
366 LinkedList<JParameter> args = new LinkedList<JParameter>(Arrays.asList(method.getParameters()));
367
368 // the last arg should be the callback.
369 if (args.isEmpty()) {
370 error("Invalid rest method. Method must declare at least a callback argument: " + method.getReadableDeclaration());
371 }
372 JParameter callbackArg = args.removeLast();
373 JClassType callbackType = callbackArg.getType().isClassOrInterface();
374 JClassType methodCallbackType = METHOD_CALLBACK_TYPE;
375 if (callbackType == null || !callbackType.isAssignableTo(methodCallbackType)) {
376 error("Invalid rest method. Last argument must be a " + methodCallbackType.getName() + " type: " + method.getReadableDeclaration());
377 }
378 JClassType resultType = getCallbackTypeGenericClass(callbackType);
379
380 String pathExpression = null;
381 Path pathAnnotation = method.getAnnotation(Path.class);
382 if (pathAnnotation != null) {
383 pathExpression = wrap(pathAnnotation.value());
384 }
385
386 JParameter contentArg = null;
387 HashMap<String, JParameter> queryParams = new HashMap<String, JParameter>();
388 HashMap<String, JParameter> headerParams = new HashMap<String, JParameter>();
389
390 for (JParameter arg : args) {
391 PathParam paramPath = arg.getAnnotation(PathParam.class);
392 if (paramPath != null) {
393 if (pathExpression == null) {
394 error("Invalid rest method. Invalid @PathParam annotation. Method is missing the @Path annotation: " + method.getReadableDeclaration());
395 }
396 pathExpression = pathExpression.replaceAll(Pattern.quote("{" + paramPath.value() + "}"), "\"+" + toStringExpression(arg) + "+\"");
397 if (arg.getAnnotation(Attribute.class) != null) {
398 // allow part of the arg-object participate in as PathParam and the object goes over the wire
399 contentArg = arg;
400 }
401 continue;
402 }
403
404 QueryParam queryParam = arg.getAnnotation(QueryParam.class);
405 if (queryParam != null) {
406 queryParams.put(queryParam.value(), arg);
407 continue;
408 }
409
410 HeaderParam headerParam = arg.getAnnotation(HeaderParam.class);
411 if (headerParam != null) {
412 headerParams.put(headerParam.value(), arg);
413 continue;
414 }
415
416 if (contentArg != null) {
417 error("Invalid rest method. Only one content parameter is supported: " + method.getReadableDeclaration());
418 }
419 contentArg = arg;
420 }
421
422 String acceptTypeBuiltIn = null;
423 if (callbackType.equals(TEXT_CALLBACK_TYPE)) {
424 acceptTypeBuiltIn = "CONTENT_TYPE_TEXT";
425 } else if (callbackType.equals(JSON_CALLBACK_TYPE)) {
426 acceptTypeBuiltIn = "CONTENT_TYPE_JSON";
427 } else if (callbackType.isAssignableTo(OVERLAY_CALLBACK_TYPE)) {
428 acceptTypeBuiltIn = "CONTENT_TYPE_JSON";
429 } else if (callbackType.equals(XML_CALLBACK_TYPE)) {
430 acceptTypeBuiltIn = "CONTENT_TYPE_XML";
431 }
432
433 p("final " + METHOD_CLASS + " __method =");
434
435 p("this.resource");
436 if (pathExpression != null) {
437 p(".resolve(" + pathExpression + ")");
438 }
439 for (Map.Entry<String, JParameter> entry : queryParams.entrySet()) {
440 String expr = entry.getValue().getName();
441 JClassType type = entry.getValue().getType().isClassOrInterface();
442 if (type != null && isQueryParamListType(type)) {
443 p(".addQueryParams(" + wrap(entry.getKey()) + ", " +
444 toIteratedStringExpression(entry.getValue()) + ")");
445 } else {
446 p(".addQueryParam(" + wrap(entry.getKey()) + ", " +
447 toStringExpression(entry.getValue().getType(), expr) + ")");
448 }
449 }
450 // example: .get()
451 p("." + restMethod + "();");
452
453 // Handle JSONP specific configuration...
454 JSONP jsonpAnnotation = method.getAnnotation(JSONP.class);
455
456 final boolean isJsonp = restMethod.equals(METHOD_JSONP) && jsonpAnnotation!=null;
457 if( isJsonp ) {
458 if( jsonpAnnotation.callbackParam().length() > 0 ) {
459 p("(("+JSONP_METHOD_CLASS+")__method).callbackParam("+wrap(jsonpAnnotation.callbackParam())+");");
460 }
461 if( jsonpAnnotation.failureCallbackParam().length() > 0 ) {
462 p("(("+JSONP_METHOD_CLASS+")__method).failureCallbackParam("+wrap(jsonpAnnotation.failureCallbackParam())+");");
463 }
464 }
465
466 // configure the dispatcher
467 if( options!=null && options.dispatcher()!=Dispatcher.class ) {
468 // use the dispatcher configured for the method.
469 p("__method.setDispatcher("+options.dispatcher().getName()+".INSTANCE);");
470 } else {
471 // use the default dispatcher configured for the service..
472 p("__method.setDispatcher(this.dispatcher);");
473 }
474
475 // configure the expected statuses..
476 if( options!=null && options.expect().length!=0 ) {
477 // Using method level defined expected status
478 p("__method.expect("+join(options.expect(), ", ")+");");
479 } else if( classOptions!=null && classOptions.expect().length!=0 ) {
480 // Using class level defined expected status
481 p("__method.expect("+join(classOptions.expect(), ", ")+");");
482 }
483
484 // configure the timeout
485 if( options!=null && options.timeout() >= 0 ) {
486 // Using method level defined value
487 p("__method.timeout("+options.timeout()+");");
488 } else if( classOptions!=null && classOptions.timeout() >= 0 ) {
489 // Using class level defined value
490 p("__method.timeout("+classOptions.timeout()+");");
491 }
492
493 if(jsonpAnnotation == null) {
494 Produces producesAnnotation = findAnnotationOnMethodOrEnclosingType(method, Produces.class);
495 if (producesAnnotation != null) {
496 p("__method.header(" + RESOURCE_CLASS + ".HEADER_ACCEPT, "+wrap(producesAnnotation.value()[0])+");");
497 } else {
498 // set the default accept header....
499 if (acceptTypeBuiltIn != null) {
500 p("__method.header(" + RESOURCE_CLASS + ".HEADER_ACCEPT, " + RESOURCE_CLASS + "." + acceptTypeBuiltIn + ");");
501 } else {
502 p("__method.header(" + RESOURCE_CLASS + ".HEADER_ACCEPT, " + RESOURCE_CLASS + ".CONTENT_TYPE_JSON);");
503 }
504 }
505
506 Consumes consumesAnnotation = findAnnotationOnMethodOrEnclosingType(method, Consumes.class);
507 if (consumesAnnotation != null) {
508 p("__method.header(" + RESOURCE_CLASS + ".HEADER_CONTENT_TYPE, "+wrap(consumesAnnotation.value()[0])+");");
509 }
510
511 // and set the explicit headers now (could override the accept header)
512 for (Map.Entry<String, JParameter> entry : headerParams.entrySet()) {
513 String expr = entry.getValue().getName();
514 p("__method.header(" + wrap(entry.getKey()) + ", " + toStringExpression(entry.getValue().getType(), expr) + ");");
515 }
516 }
517
518 if (contentArg != null) {
519 if (contentArg.getType() == STRING_TYPE) {
520 p("__method.text(" + contentArg.getName() + ");");
521 } else if (contentArg.getType() == JSON_VALUE_TYPE) {
522 p("__method.json(" + contentArg.getName() + ");");
523 } else if (contentArg.getType().isClass() != null &&
524 isOverlayArrayType(contentArg.getType().isClass())) {
525 p("__method.json(new " + JSON_ARRAY_CLASS + "(" + contentArg.getName() + "));");
526 } else if (contentArg.getType().isClass() != null &&
527 contentArg.getType().isClass().isAssignableTo(OVERLAY_VALUE_TYPE)) {
528 p("__method.json(new " + JSON_OBJECT_CLASS + "(" + contentArg.getName() + "));");
529 } else if (contentArg.getType() == DOCUMENT_TYPE) {
530 p("__method.xml(" + contentArg.getName() + ");");
531 } else {
532 JClassType contentClass = contentArg.getType().isClass();
533 if (contentClass == null) {
534 contentClass = contentArg.getType().isClassOrInterface();
535 if (!locator.isCollectionType(contentClass)) {
536 error("Content argument must be a class.");
537 }
538 }
539
540 jsonAnnotation = contentArg.getAnnotation(Json.class);
541 Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;
542
543 // example:
544 // .json(Listings$_Generated_JsonEncoder_$.INSTANCE.encode(arg0)
545 // )
546 p("__method.json(" + locator.encodeExpression(contentClass, contentArg.getName(), style) + ");");
547 }
548 }
549
550
551 List<AnnotationResolver> annotationResolvers = getAnnotationResolvers(context, logger);
552 logger.log(TreeLogger.DEBUG, "found " + annotationResolvers.size() + " additional AnnotationResolvers");
553
554 for (AnnotationResolver a : annotationResolvers) {
555 logger.log(TreeLogger.DEBUG, "(" + a.getClass().getName() + ") resolve `" + source.getName()
556 + "#" + method.getName() + "´ ...");
557 final Map<String, String[]> addDataParams = a.resolveAnnotation(logger, source, method, restMethod);
558
559 if (addDataParams != null) {
560 for (String s : addDataParams.keySet()) {
561 final StringBuilder sb = new StringBuilder();
562 final List<String> classList = Arrays.asList(addDataParams.get(s));
563
564 sb.append("[");
565 for (int i = 0; i < classList.size(); ++i) {
566 sb.append("\\\"").append(classList.get(i)).append("\\\"");
567
568 if ((i+1) < classList.size()) {
569 sb.append(",");
570 }
571 }
572 sb.append("]");
573
574 logger.log(TreeLogger.DEBUG, "add call with (\"" + s + "\", \"" +
575 sb.toString() + "\")");
576 p("__method.addData(\"" + s + "\", \"" + sb.toString() + "\");");
577 }
578 }
579 }
580
581
582 if (acceptTypeBuiltIn != null) {
583 p("__method.send(" + callbackArg.getName() + ");");
584 } else if ( isJsonp ){
585 p("((" + JSONP_METHOD_CLASS + ")__method).send(new " + ABSTRACT_ASYNC_CALLBACK_CLASS + "<" + resultType.getParameterizedQualifiedSourceName() + ">((" + JSONP_METHOD_CLASS + ")__method, "
586 + callbackArg.getName() + ") {").i(1);
587 {
588 p("protected " + resultType.getParameterizedQualifiedSourceName() + " parseResult(" + JSON_OBJECT_CLASS + " result) throws Exception {").i(1);
589 {
590 if(resultType.getParameterizedQualifiedSourceName().equals("java.lang.Void")) {
591 p("return (java.lang.Void) null;");
592 }
593 else {
594 p("try {").i(1);
595 {
596 jsonAnnotation = method.getAnnotation(Json.class);
597 Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;
598 p("return " + locator.decodeExpression(resultType, "result", style) + ";");
599 }
600 i(-1).p("} catch (Throwable __e) {").i(1);
601 {
602 p("throw new " + RESPONSE_FORMAT_EXCEPTION_CLASS + "(\"Response was NOT a valid JSON document\", __e);");
603 }
604 i(-1).p("}");
605 }
606 }
607 i(-1).p("}");
608 }
609 i(-1).p("});");
610 } else {
611 p("try {").i(1);
612 {
613 p("__method.send(new " + ABSTRACT_REQUEST_CALLBACK_CLASS + "<" + resultType.getParameterizedQualifiedSourceName() + ">(__method, "
614 + callbackArg.getName() + ") {").i(1);
615 {
616 p("protected " + resultType.getParameterizedQualifiedSourceName() + " parseResult() throws Exception {").i(1);
617 {
618 if(resultType.getParameterizedQualifiedSourceName().equals("java.lang.Void")) {
619 p("return (java.lang.Void) null;");
620 }
621 else {
622 p("try {").i(1);
623 {
624 jsonAnnotation = method.getAnnotation(Json.class);
625 Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;
626 p("return " + locator.decodeExpression(resultType, JSON_PARSER_CLASS + ".parse(__method.getResponse().getText())", style) + ";");
627 }
628 i(-1).p("} catch (Throwable __e) {").i(1);
629 {
630 p("throw new " + RESPONSE_FORMAT_EXCEPTION_CLASS + "(\"Response was NOT a valid JSON document\", __e);");
631 }
632 i(-1).p("}");
633 }
634 }
635 i(-1).p("}");
636 }
637 i(-1).p("});");
638 }
639 i(-1).p("} catch (" + REQUEST_EXCEPTION_CLASS + " __e) {").i(1);
640 {
641 p(callbackArg.getName() + ".onFailure(__method,__e);");
642 }
643 i(-1).p("}");
644 }
645 }
646 i(-1).p("}");
647 }
648
649 private <T extends Annotation> T findAnnotationOnMethodOrEnclosingType(final JMethod method, final Class<T> annotationType) {
650 T annotation = method.getAnnotation(annotationType);
651 if (annotation == null) {
652 annotation = method.getEnclosingType().getAnnotation(annotationType);
653 }
654 return annotation;
655 }
656
657 protected String toStringExpression(JParameter arg) {
658 Attribute attribute = arg.getAnnotation(Attribute.class);
659 if(attribute != null){
660 return arg.getName() + "." + attribute.value();
661 }
662 return toStringExpression(arg.getType(), arg.getName());
663 }
664
665 protected String toStringExpression(JType type, String expr) {
666 if (type.isPrimitive() != null) {
667 return "\"\"+" + expr;
668 }
669 if (STRING_TYPE == type) {
670 return expr;
671 }
672 if (type.isClass() != null &&
673 isOverlayArrayType(type.isClass())) {
674 return "(new " + JSON_ARRAY_CLASS + "(" + expr + ")).toString()";
675 }
676 if (type.isClass() != null &&
677 OVERLAY_VALUE_TYPE.isAssignableFrom(type.isClass())) {
678 return "(new " + JSON_OBJECT_CLASS + "(" + expr + ")).toString()";
679 }
680
681 return expr + ".toString()";
682 }
683
684 protected String toIteratedStringExpression(JParameter arg) {
685 StringBuilder result = new StringBuilder();
686 result.append("new org.fusesource.restygwt.client.StringIterable (")
687 .append(arg.getName()).append(")");
688
689 return result.toString();
690 }
691
692 private JClassType getCallbackTypeGenericClass(final JClassType callbackType) throws UnableToCompleteException {
693 return branch("getCallbackTypeGenericClass()", new Branch<JClassType>() {
694 public JClassType execute() throws UnableToCompleteException {
695
696 for (JMethod method : callbackType.getOverridableMethods()) {
697 debug("checking method: " + method.getName());
698 if (method.getName().equals("onSuccess")) {
699 JParameter[] parameters = method.getParameters();
700 debug("checking method params: " + parameters.length);
701 if (parameters.length == 2) {
702 debug("checking first param: " + parameters[0].getType());
703 if (parameters[0].getType() == METHOD_TYPE) {
704 debug("checking 2nd param: " + parameters[1].getType());
705 JType param2Type = parameters[1].getType();
706 JClassType type = param2Type.isClassOrInterface();
707 if (type == null) {
708 error("The type of the callback not supported: " + param2Type.getJNISignature());
709 }
710 debug("match: " + type);
711 return type;
712 }
713 }
714 }
715 }
716 error("The type of the callback could not be determined: " + callbackType.getParameterizedQualifiedSourceName());
717 return null;
718
719 }
720 });
721 }
722
723 private String getRestMethod(JMethod method) throws UnableToCompleteException {
724 String restMethod = null;
725 if (method.getAnnotation(DELETE.class) != null) {
726 restMethod = METHOD_DELETE;
727 } else if (method.getAnnotation(GET.class) != null) {
728 restMethod = METHOD_GET;
729 } else if (method.getAnnotation(HEAD.class) != null) {
730 restMethod = METHOD_HEAD;
731 } else if (method.getAnnotation(OPTIONS.class) != null) {
732 restMethod = METHOD_OPTIONS;
733 } else if (method.getAnnotation(POST.class) != null) {
734 restMethod = METHOD_POST;
735 } else if (method.getAnnotation(PUT.class) != null) {
736 restMethod = METHOD_PUT;
737 } else if (method.getAnnotation(JSONP.class) != null) {
738 restMethod = METHOD_JSONP;
739 } else {
740 restMethod = method.getName();
741 if (!REST_METHODS.contains(restMethod)) {
742 error("Invalid rest method. It must either have a lower case rest method name or have a javax rs method annotation: " + method.getReadableDeclaration());
743 }
744 }
745 return restMethod;
746 }
747
748 /**
749 * access additional AnnotationResolvers possibly added by
750 *
751 * {@link BindingDefaults#addAnnotationResolver(AnnotationResolver)}
752 * @return
753 */
754 @SuppressWarnings("unchecked")
755 private List<AnnotationResolver> getAnnotationResolvers(final GeneratorContext context, final TreeLogger logger) {
756 java.lang.reflect.Method m = null;
757 ArrayList args = new ArrayList();
758 ArrayList types = new ArrayList();
759
760 types.add(GeneratorContext.class);
761 args.add(context);
762 types.add(TreeLogger.class);
763 args.add(logger);
764
765 Object[] argValues = args.toArray();
766 Class[] argtypes = (Class[]) types.toArray(new Class[argValues.length]);
767
768 try {
769 m = BINDING_DEFAULTS.getMethod("getAnnotationResolvers", argtypes);
770 } catch (SecurityException e) {
771 throw new RuntimeException("could not call method `getAnnotationResolvers´ on "
772 + BINDING_DEFAULTS, e);
773 } catch (NoSuchMethodException e) {
774 throw new RuntimeException("could not resolve method `getAnnotationResolvers´ on "
775 + BINDING_DEFAULTS, e);
776 }
777
778 List<AnnotationResolver> l = new ArrayList<AnnotationResolver>();
779 try {
780 Object[] params = new Object[]{context};
781
782 l = (List<AnnotationResolver>) m.invoke(null, context, logger);
783 } catch (IllegalArgumentException e) {
784 throw new RuntimeException("could not call method `getAnnotationResolvers´ on "
785 + BINDING_DEFAULTS, e);
786 } catch (IllegalAccessException e) {
787 throw new RuntimeException("could not call method `getAnnotationResolvers´ on "
788 + BINDING_DEFAULTS, e);
789 } catch (InvocationTargetException e) {
790 throw new RuntimeException("could not call method `getAnnotationResolvers´ on "
791 + BINDING_DEFAULTS, e);
792 }
793
794 return l;
795 }
796 }