Package org.fusesource.restygwt.rebind

Source Code of org.fusesource.restygwt.rebind.RestServiceClassCreator

/**
* Copyright (C) 2009-2012 the original author or authors.
* See the notice.md file distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.fusesource.restygwt.rebind;

import static org.fusesource.restygwt.rebind.util.AnnotationUtils.getAnnotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;

import org.fusesource.restygwt.client.AbstractAsyncCallback;
import org.fusesource.restygwt.client.AbstractRequestCallback;
import org.fusesource.restygwt.client.Attribute;
import org.fusesource.restygwt.client.Defaults;
import org.fusesource.restygwt.client.Dispatcher;
import org.fusesource.restygwt.client.FormPostContent;
import org.fusesource.restygwt.client.JSONP;
import org.fusesource.restygwt.client.Json;
import org.fusesource.restygwt.client.Json.Style;
import org.fusesource.restygwt.client.JsonCallback;
import org.fusesource.restygwt.client.JsonpMethod;
import org.fusesource.restygwt.client.Method;
import org.fusesource.restygwt.client.MethodCallback;
import org.fusesource.restygwt.client.Options;
import org.fusesource.restygwt.client.OverlayCallback;
import org.fusesource.restygwt.client.Resource;
import org.fusesource.restygwt.client.ResponseFormatException;
import org.fusesource.restygwt.client.RestService;
import org.fusesource.restygwt.client.RestServiceProxy;
import org.fusesource.restygwt.client.ServiceRoots;
import org.fusesource.restygwt.client.TextCallback;
import org.fusesource.restygwt.client.XmlCallback;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayBoolean;
import com.google.gwt.core.client.JsArrayInteger;
import com.google.gwt.core.client.JsArrayNumber;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JGenericType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.JTypeParameter;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.jsonp.client.JsonpRequest;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.xml.client.Document;
/**
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*
*         Updates: added automatically create resource from Path annotation,
*         enhanced generics support
* @author <a href="http://www.acuedo.com">Dave Finch</a>
*/
public class RestServiceClassCreator extends BaseSourceCreator {

    private static final String REST_SERVICE_PROXY_SUFFIX = "_Generated_RestServiceProxy_";

    private static final String METHOD_CLASS = Method.class.getName();
    private static final String RESOURCE_CLASS = Resource.class.getName();
    private static final String DISPATCHER_CLASS = Dispatcher.class.getName();
    private static final String DEFAULTS_CLASS = Defaults.class.getName();
    private static final String ABSTRACT_REQUEST_CALLBACK_CLASS = AbstractRequestCallback.class.getName();
    private static final String ABSTRACT_ASYNC_CALLBACK_CLASS = AbstractAsyncCallback.class.getName();
    private static final String JSON_PARSER_CLASS = JSONParser.class.getName();
    private static final String JSON_ARRAY_CLASS = JSONArray.class.getName();
    private static final String JSON_OBJECT_CLASS = JSONObject.class.getName();
    private static final String JSON_VALUE_CLASS = JSONValue.class.getName();
    private static final String REQUEST_EXCEPTION_CLASS = RequestException.class.getName();
    private static final String RESPONSE_FORMAT_EXCEPTION_CLASS = ResponseFormatException.class.getName();
    private static final String JSONP_METHOD_CLASS = JsonpMethod.class.getName();
    private static final String FORM_POST_CONTENT_CLASS = FormPostContent.class.getName();
    private static final String SERVICE_ROOTS_CLASS = ServiceRoots.class.getName();

    /*
     * static class in which are some compile-time relevant infos.
     *
     * TODO (andi): too much flexibility and overhead with reflection here?
     */
    private static final Class<BindingDefaults> BINDING_DEFAULTS = BindingDefaults.class;

    private static final String METHOD_JSONP = "jsonp";
    private static final String METHOD_PUT = "put";
    private static final String METHOD_POST = "post";
    private static final String METHOD_OPTIONS = "options";
    private static final String METHOD_HEAD = "head";
    private static final String METHOD_GET = "get";
    private static final String METHOD_DELETE = "delete";

    private static final HashSet<String> REST_METHODS = new HashSet<String>(8);
    static {
        REST_METHODS.add(METHOD_DELETE);
        REST_METHODS.add(METHOD_GET);
        REST_METHODS.add(METHOD_HEAD);
        REST_METHODS.add(METHOD_OPTIONS);
        REST_METHODS.add(METHOD_POST);
        REST_METHODS.add(METHOD_PUT);
        REST_METHODS.add(METHOD_JSONP);
    }

    private JClassType XML_CALLBACK_TYPE;
    private JClassType METHOD_CALLBACK_TYPE;
    private JClassType TEXT_CALLBACK_TYPE;
    private JClassType JSON_CALLBACK_TYPE;
    private JClassType OVERLAY_CALLBACK_TYPE;
    private JClassType DOCUMENT_TYPE;
    private JClassType METHOD_TYPE;
    private JClassType STRING_TYPE;
    private JClassType JSON_VALUE_TYPE;
    private JClassType OVERLAY_VALUE_TYPE;
    private Set<JClassType> OVERLAY_ARRAY_TYPES;
    private Set<JClassType> QUERY_PARAM_LIST_TYPES;
    private JClassType REST_SERVICE_TYPE;
    private JsonEncoderDecoderInstanceLocator locator;

    public RestServiceClassCreator(TreeLogger logger, GeneratorContext context, JClassType source) {
        super(logger, context, source, REST_SERVICE_PROXY_SUFFIX);
    }

    @Override
    protected ClassSourceFileComposerFactory createComposerFactory() {
      String parameters = "";
      if(source instanceof JGenericType)
      {
        JGenericType gtype = (JGenericType)source;
      StringBuilder builder = new StringBuilder();
      builder.append("<");
      boolean first = true;
         for(JTypeParameter arg : gtype.getTypeParameters())
         {
           if(!first)
             builder.append(",");
           builder.append(arg.getName());
           builder.append(" extends ");
           builder.append(arg.getFirstBound().getParameterizedQualifiedSourceName());
           first = false;
         }
         builder.append(">");
         parameters = builder.toString();
       }
     
        ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName, shortName + parameters);
        composerFactory.addImplementedInterface(source.getParameterizedQualifiedSourceName());
        composerFactory.addImplementedInterface(RestServiceProxy.class.getName());
        return composerFactory;
    }

    @Override
    protected void generate() throws UnableToCompleteException {

        if (source.isInterface() == null) {
            getLogger().log(ERROR, "Type is not an interface.");
            throw new UnableToCompleteException();
        }

        locator = new JsonEncoderDecoderInstanceLocator(context, getLogger());

        this.XML_CALLBACK_TYPE = find(XmlCallback.class, getLogger(), context);
        this.METHOD_CALLBACK_TYPE = find(MethodCallback.class, getLogger(), context);
        this.TEXT_CALLBACK_TYPE = find(TextCallback.class, getLogger(), context);
        this.JSON_CALLBACK_TYPE = find(JsonCallback.class, getLogger(), context);
        this.OVERLAY_CALLBACK_TYPE = find(OverlayCallback.class, getLogger(), context);
        this.DOCUMENT_TYPE = find(Document.class, getLogger(), context);
        this.METHOD_TYPE = find(Method.class, getLogger(), context);
        this.STRING_TYPE = find(String.class, getLogger(), context);
        this.JSON_VALUE_TYPE = find(JSONValue.class, getLogger(), context);
        this.OVERLAY_VALUE_TYPE = find(JavaScriptObject.class, getLogger(), context);
        this.OVERLAY_ARRAY_TYPES = new HashSet<JClassType>();
        this.OVERLAY_ARRAY_TYPES.add(find(JsArray.class, getLogger(), context));
        this.OVERLAY_ARRAY_TYPES.add(find(JsArrayBoolean.class, getLogger(), context));
        this.OVERLAY_ARRAY_TYPES.add(find(JsArrayInteger.class, getLogger(), context));
        this.OVERLAY_ARRAY_TYPES.add(find(JsArrayNumber.class, getLogger(), context));
        this.OVERLAY_ARRAY_TYPES.add(find(JsArrayString.class, getLogger(), context));
        this.QUERY_PARAM_LIST_TYPES = new HashSet<JClassType>();
        this.QUERY_PARAM_LIST_TYPES.add(find(Collection.class, getLogger(), context));
        this.QUERY_PARAM_LIST_TYPES.add(find(List.class, getLogger(), context));
        this.QUERY_PARAM_LIST_TYPES.add(find(Set.class, getLogger(), context));
    this.REST_SERVICE_TYPE = find(RestService.class, getLogger(), context);
   
        String path = null;
        Path pathAnnotation = getAnnotation(source, Path.class);
        if (pathAnnotation != null) {
            path = pathAnnotation.value();
        }

        RemoteServiceRelativePath relativePath = getAnnotation(source, RemoteServiceRelativePath.class);
        if (relativePath != null) {
            path = relativePath.value();
        }

        p("private " + RESOURCE_CLASS + " resource = null;");
        p();

        p("public void setResource(" + RESOURCE_CLASS + " resource) {").i(1);
        {
            p("this.resource = resource;");
        }
        i(-1).p("}");

        Options options = getAnnotation(source, Options.class);
       
        p("public " + RESOURCE_CLASS + " getResource() {").i(1);
        {
            p("if (this.resource == null) {").i(1);
           
            if (options != null && options.serviceRootKey() != null && !options.serviceRootKey().isEmpty()) {
              p("String serviceRoot = " + SERVICE_ROOTS_CLASS + ".get(\"" + options.serviceRootKey() + "\");");
            } else {
              p("String serviceRoot = " + DEFAULTS_CLASS + ".getServiceRoot();");
            }
           
            if (path == null) {
                p("this.resource = new " + RESOURCE_CLASS + "(serviceRoot);");
            } else {
                p("this.resource = new " + RESOURCE_CLASS + "(serviceRoot).resolve("+quote(path)+");");
            }
           
            i(-1).p("}");
            p("return this.resource;");
        }
        i(-1).p("}");


        if( options!=null && options.dispatcher()!=Dispatcher.class ) {
            p("private " + DISPATCHER_CLASS + " dispatcher = "+options.dispatcher().getName()+".INSTANCE;");
        } else {
            p("private " + DISPATCHER_CLASS + " dispatcher = null;");
        }

        p();
        p("public void setDispatcher(" + DISPATCHER_CLASS + " dispatcher) {").i(1);
        {
            p("this.dispatcher = dispatcher;");
        }
        i(-1).p("}");

        p();
        p("public " + DISPATCHER_CLASS + " getDispatcher() {").i(1);
        {
            p("return this.dispatcher;");
        }
        i(-1).p("}");

        for (JMethod method : source.getInheritableMethods()) {
          JClassType iface = method.getReturnType().isInterface();
          if(iface != null && REST_SERVICE_TYPE.isAssignableFrom(iface))
            writeSubresourceLocatorImpl(method);
          else
              writeMethodImpl(method);
        }
    }

    private String quote(String path) {
        // TODO: unlikely to occur. but we should escape chars like newlines..
        return "\"" + path + "\"";
    }

    private boolean isOverlayArrayType(JClassType type) {
        for (JClassType arrayType : OVERLAY_ARRAY_TYPES) {
            if (type.isAssignableTo(arrayType)) {
                return true;
            }
        }
        return false;
    }

    private boolean isQueryParamListType(JClassType type) {
        if (type.isParameterized() == null) {
            return false;
        }
        for (JClassType listType : QUERY_PARAM_LIST_TYPES) {
            if (type.isAssignableTo(listType)) {
                return true;
            }
        }
        return false;
    }
 
    private void writeSubresourceLocatorImpl(JMethod method) throws UnableToCompleteException
    {
      JClassType iface = method.getReturnType().isInterface();
      if(iface == null || !REST_SERVICE_TYPE.isAssignableFrom(iface)) {
        getLogger().log(ERROR, "Invalid subresource locator method. Method must have return type of an interface that extends RestService: " + method.getReadableDeclaration());
            throw new UnableToCompleteException();
      }
     
        Path pathAnnotation = getAnnotation(method, Path.class);
        if (pathAnnotation == null) {
          getLogger().log(ERROR, "Invalid subresource locator method. Method must have @Path annotation: " + method.getReadableDeclaration());
            throw new UnableToCompleteException();
        }
        String pathExpression = wrap(pathAnnotation.value());

        for (JParameter arg : method.getParameters()) {
            PathParam paramPath = getAnnotation(arg, PathParam.class);
            if (paramPath != null) {
                pathExpression = pathExpression(pathExpression, arg, paramPath);
            }
        }


        p(method.getReadableDeclaration(false, false, false, false, true) + " {").i(1);
        {
          JType type = method.getReturnType();
          String name;
          if(type instanceof JClassType)
          {
                JClassType restService = (JClassType)type;
                RestServiceClassCreator generator = new RestServiceClassCreator(getLogger(), context, restService);
                name = generator.create();
          }
          else
          {
            throw new UnsupportedOperationException("Subresource method may not return: " + type);
          }
          p(method.getReturnType().getQualifiedSourceName() + " __subresource = new " + name + "();");
                p("((" + RestServiceProxy.class.getName() + ")__subresource).setResource(getResource().resolve(" + pathExpression + "));");
                p("((" + RestServiceProxy.class.getName() + ")__subresource).setDispatcher(getDispatcher());");
          p("return __subresource;");
        }
        i(-1).p("}");
    }

    private String pathExpression(String pathExpression, JParameter arg, PathParam paramPath) {
        String expr = toStringExpression(arg);
        return pathExpression.replaceAll(Pattern.quote("{" + paramPath.value() + "}"),
               "\"+(" + expr + "== null? null : ((\"\" + " + expr +").startsWith(\"http\") ? " + expr +
               " : com.google.gwt.http.client.URL.encodePathSegment(" + expr + ")))+\"");
    }
   
    private void writeMethodImpl(JMethod method) throws UnableToCompleteException {
        boolean returnRequest = false;
        if (method.getReturnType() != JPrimitiveType.VOID) {
            if (!method.getReturnType().getQualifiedSourceName().equals(Request.class.getName()) &&
                !method.getReturnType().getQualifiedSourceName().equals(JsonpRequest.class.getName())) {
                getLogger().log(ERROR, "Invalid rest method. Method must have void, Request or JsonpRequest return types: " + method.getReadableDeclaration());
                throw new UnableToCompleteException();
            }
            returnRequest = true;
        }

        Json jsonAnnotation = getAnnotation(source, Json.class);
        final Style classStyle = jsonAnnotation != null ? jsonAnnotation.style() : Style.DEFAULT;

        Options classOptions = getAnnotation(source, Options.class);
        Options options = getAnnotation(method, Options.class);

        p(method.getReadableDeclaration(false, false, false, false, true) + " {").i(1);
        {
            String restMethod = getRestMethod(method);
            LinkedList<JParameter> args = new LinkedList<JParameter>(Arrays.asList(method.getParameters()));
            for (final JParameter arg : args.subList(0, args.size() - 1)) {
                p("final "
                        + arg.getType().getParameterizedQualifiedSourceName()
                        + " final_" + arg.getName() + " = " + arg.getName()
                        + ";");
            }

            // the last arg should be the callback.
            if (args.isEmpty()) {
                getLogger().log(ERROR, "Invalid rest method. Method must declare at least a callback argument: " + method.getReadableDeclaration());
                throw new UnableToCompleteException();
            }
            JParameter callbackArg = args.removeLast();
            JClassType callbackType = callbackArg.getType().isClassOrInterface();
            JClassType methodCallbackType = METHOD_CALLBACK_TYPE;
            if (callbackType == null || !callbackType.isAssignableTo(methodCallbackType)) {
                getLogger().log(ERROR, "Invalid rest method. Last argument must be a " + methodCallbackType.getName() + " type: " + method.getReadableDeclaration());
                throw new UnableToCompleteException();
            }
            JClassType resultType = getCallbackTypeGenericClass(callbackType);

            String pathExpression = null;
            Path pathAnnotation = getAnnotation(method, Path.class);
            if (pathAnnotation != null) {
                pathExpression = wrap(pathAnnotation.value());
            }

            JParameter contentArg = null;
            HashMap<String, JParameter> queryParams = new HashMap<String, JParameter>();
            HashMap<String, JParameter> formParams = new HashMap<String, JParameter>();
            HashMap<String, JParameter> headerParams = new HashMap<String, JParameter>();

            for (JParameter arg : args) {
                PathParam paramPath = getAnnotation(arg, PathParam.class);
                if (paramPath != null) {
                    if (pathExpression == null) {
                        getLogger().log(ERROR, "Invalid rest method.  Invalid @PathParam annotation. Method is missing the @Path annotation: " + method.getReadableDeclaration());
                        throw new UnableToCompleteException();
                    }
                    pathExpression = pathExpression(pathExpression, arg, paramPath);
                    //.replaceAll(Pattern.quote("{" + paramPath.value() + "}"), "\"+com.google.gwt.http.client.URL.encodePathSegment(" + toStringExpression(arg) + ")+\"");
                    if (getAnnotation(arg, Attribute.class) != null) {
                        // allow part of the arg-object participate in as PathParam and the object goes over the wire
                        contentArg = arg;
                    }
                    continue;
                }

                QueryParam queryParam = getAnnotation(arg, QueryParam.class);
                if (queryParam != null) {
                    queryParams.put(queryParam.value(), arg);
                    continue;
                }

                FormParam formParam = getAnnotation(arg, FormParam.class);
                if (formParam != null) {
                    formParams.put(formParam.value(), arg);
                    continue;
                }

                HeaderParam headerParam = getAnnotation(arg, HeaderParam.class);
                if (headerParam != null) {
                    headerParams.put(headerParam.value(), arg);
                    continue;
                }

                if (!formParams.isEmpty()) {
                    getLogger().log(ERROR, "You can not have both @FormParam parameters and a content parameter: " +
                                                method.getReadableDeclaration());
                    throw new UnableToCompleteException();
                }

                if (contentArg != null) {
                    getLogger().log(ERROR, "Invalid rest method. Only one content parameter is supported: " + method.getReadableDeclaration());
                    throw new UnableToCompleteException();
                }
                contentArg = arg;
            }

            String acceptTypeBuiltIn = null;
            if (callbackType.equals(TEXT_CALLBACK_TYPE)) {
                acceptTypeBuiltIn = "CONTENT_TYPE_TEXT";
            } else if (callbackType.equals(JSON_CALLBACK_TYPE)) {
                acceptTypeBuiltIn = "CONTENT_TYPE_JSON";
            } else if (callbackType.isAssignableTo(OVERLAY_CALLBACK_TYPE)) {
                acceptTypeBuiltIn = "CONTENT_TYPE_JSON";
            } else if (callbackType.equals(XML_CALLBACK_TYPE)) {
                acceptTypeBuiltIn = "CONTENT_TYPE_XML";
            }

            p("final " + METHOD_CLASS + " __method =");

            p("getResource()");
            if (pathExpression != null) {
                p(".resolve(" + pathExpression + ")");
            }
            for (Map.Entry<String, JParameter> entry : queryParams.entrySet()) {
                String expr = entry.getValue().getName();
                JClassType type = entry.getValue().getType().isClassOrInterface();
                if (type != null && isQueryParamListType(type)) {
                    p(".addQueryParams(" + wrap(entry.getKey()) + ", " +
                      toIteratedStringExpression(entry.getValue()) + ")");
                } else {
                    p(".addQueryParam(" + wrap(entry.getKey()) + ", " +
                      toStringExpression(entry.getValue().getType(), expr) + ")");
                }
            }
            // example: .get()
            p("." + restMethod + "();");

            // Handle JSONP specific configuration...
            JSONP jsonpAnnotation = getAnnotation(method, JSONP.class);

            final boolean isJsonp = restMethod.equals(METHOD_JSONP) && jsonpAnnotation!=null;
            if( isJsonp ) {
                if (returnRequest && !method.getReturnType().getQualifiedSourceName().equals(JsonpRequest.class.getName())) {
                    getLogger().log(ERROR, "Invalid rest method. JSONP method must have void or JsonpRequest return types: " + method.getReadableDeclaration());
                    throw new UnableToCompleteException();
                }
                if( jsonpAnnotation.callbackParam().length() > 0 ) {
                    p("(("+JSONP_METHOD_CLASS+")__method).callbackParam("+wrap(jsonpAnnotation.callbackParam())+");");
                }
                if( jsonpAnnotation.failureCallbackParam().length() > 0 ) {
                    p("(("+JSONP_METHOD_CLASS+")__method).failureCallbackParam("+wrap(jsonpAnnotation.failureCallbackParam())+");");
                }
            } else {
                if (returnRequest && !method.getReturnType().getQualifiedSourceName().equals(Request.class.getName())) {
                    getLogger().log(ERROR, "Invalid rest method. Non JSONP method must have void or Request return types: " + method.getReadableDeclaration());
                    throw new UnableToCompleteException();
                }
            }

            // configure the dispatcher
            if( options!=null && options.dispatcher()!=Dispatcher.class ) {
                // use the dispatcher configured for the method.
                p("__method.setDispatcher("+options.dispatcher().getName()+".INSTANCE);");
            } else {
                // use the default dispatcher configured for the service..
                p("__method.setDispatcher(this.dispatcher);");
            }

            // configure the expected statuses..
            if( options!=null && options.expect().length!=0 ) {
                // Using method level defined expected status
                p("__method.expect("+join(options.expect(), ", ")+");");
            } else if( classOptions!=null && classOptions.expect().length!=0 ) {
                // Using class level defined expected status
                p("__method.expect("+join(classOptions.expect(), ", ")+");");
            }

            // configure the timeout
            if( options!=null && options.timeout() >= 0 ) {
                // Using method level defined value
                p("__method.timeout("+options.timeout()+");");
            } else if( classOptions!=null && classOptions.timeout() >= 0 ) {
                // Using class level defined value
                p("__method.timeout("+classOptions.timeout()+");");
            }

            if(jsonpAnnotation == null) {
                Produces producesAnnotation = findAnnotationOnMethodOrEnclosingType(method, Produces.class);
                if (producesAnnotation != null) {
                    p("__method.header(" + RESOURCE_CLASS + ".HEADER_ACCEPT, "+wrap(producesAnnotation.value()[0])+");");
                } else {
                    // set the default accept header....
                    if (acceptTypeBuiltIn != null) {
                        p("__method.header(" + RESOURCE_CLASS + ".HEADER_ACCEPT, " + RESOURCE_CLASS + "." + acceptTypeBuiltIn + ");");
                    } else {
                        p("__method.header(" + RESOURCE_CLASS + ".HEADER_ACCEPT, " + RESOURCE_CLASS + ".CONTENT_TYPE_JSON);");
                    }
                }

                Consumes consumesAnnotation = findAnnotationOnMethodOrEnclosingType(method, Consumes.class);
                if (consumesAnnotation != null) {
                    p("__method.header(" + RESOURCE_CLASS + ".HEADER_CONTENT_TYPE, "+wrap(consumesAnnotation.value()[0])+");");
                }

                // and set the explicit headers now (could override the accept header)
                for (Map.Entry<String, JParameter> entry : headerParams.entrySet()) {
                    String expr = entry.getValue().getName();
                    p("__method.header(" + wrap(entry.getKey()) + ", " + toStringExpression(entry.getValue().getType(), expr) + ");");
                }
            }

            if (! formParams.isEmpty()) {
                p(FORM_POST_CONTENT_CLASS + " __formPostContent = new " + FORM_POST_CONTENT_CLASS + "();");

                for (Map.Entry<String, JParameter> entry : formParams.entrySet()) {
                    JClassType type = entry.getValue().getType()
                            .isClassOrInterface();
                    if (type != null && isQueryParamListType(type)) {
                        p("__formPostContent.addParameters(" +
                                wrap(entry.getKey()) + ", " +
                                toIteratedFormStringExpression(entry.getValue(), classStyle) +
                                ");");
                    } else {
                        p("__formPostContent.addParameter(" +
                                wrap(entry.getKey()) + ", " +
                                toFormStringExpression(entry.getValue(), classStyle) +
                                ");");
                    }
                }

                p("__method.form(__formPostContent.getTextContent());");
            }

            if (contentArg != null) {
                if (contentArg.getType() == STRING_TYPE) {
                    p("__method.text(" + contentArg.getName() + ");");
                } else if (contentArg.getType() == JSON_VALUE_TYPE) {
                    p("__method.json(" + contentArg.getName() + ");");
                } else if (contentArg.getType().isClass() != null &&
                           isOverlayArrayType(contentArg.getType().isClass())) {
                    p("__method.json(new " + JSON_ARRAY_CLASS + "(" + contentArg.getName() + "));");
                } else if (contentArg.getType().isClass() != null &&
                           contentArg.getType().isClass().isAssignableTo(OVERLAY_VALUE_TYPE)) {
                    p("__method.json(new " + JSON_OBJECT_CLASS + "(" + contentArg.getName() + "));");
                } else if (contentArg.getType() == DOCUMENT_TYPE) {
                    p("__method.xml(" + contentArg.getName() + ");");
                } else {
                    JClassType contentClass = contentArg.getType().isClass();
                    if (contentClass == null) {
                        contentClass = contentArg.getType().isClassOrInterface();
                        if (!locator.isCollectionType(contentClass)) {
                            getLogger().log(ERROR, "Content argument must be a class.");
                            throw new UnableToCompleteException();
                        }
                    }

                    jsonAnnotation = getAnnotation(contentArg, Json.class);
                    Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;

                    // example:
                    // .json(Listings$_Generated_JsonEncoder_$.INSTANCE.encode(arg0)
                    // )
                    p("__method.json(" + locator.encodeExpression(contentClass, contentArg.getName(), style) + ");");
                }
            }


            List<AnnotationResolver> annotationResolvers = getAnnotationResolvers(context, getLogger());
            getLogger().log(TreeLogger.DEBUG, "found " + annotationResolvers.size() + " additional AnnotationResolvers");

            for (AnnotationResolver a : annotationResolvers) {
                getLogger().log(TreeLogger.DEBUG, "(" + a.getClass().getName() + ") resolve `" + source.getName()
                        + "#" + method.getName() + "´ ...");
                final Map<String, String[]> addDataParams = a.resolveAnnotation(getLogger(), source, method, restMethod);

                if (addDataParams != null) {
                    for (String s : addDataParams.keySet()) {
                        final StringBuilder sb = new StringBuilder();
                        final List<String> classList = Arrays.asList(addDataParams.get(s));

                        sb.append("[");
                        for (int i = 0; i < classList.size(); ++i) {
                            sb.append("\\\"").append(classList.get(i)).append("\\\"");

                            if ((i+1) <  classList.size()) {
                                sb.append(",");
                            }
                        }
                        sb.append("]");

                        getLogger().log(TreeLogger.DEBUG, "add call with (\"" + s + "\", \"" +
                                sb.toString() + "\")");
                        p("__method.addData(\"" + s + "\", \"" + sb.toString() + "\");");
                    }
                }
            }


            if (acceptTypeBuiltIn != null) {
                // TODO: shouldn't we also have a cach in here?
                p(returnRequest(returnRequest,isJsonp) + "__method.send(" + callbackArg.getName() + ");");
            } else if ( isJsonp ){
                    p(returnRequest(returnRequest,isJsonp) + "((" + JSONP_METHOD_CLASS + ")__method).send(new " + ABSTRACT_ASYNC_CALLBACK_CLASS + "<" + resultType.getParameterizedQualifiedSourceName() + ">((" + JSONP_METHOD_CLASS + ")__method, "
                                    + callbackArg.getName() + ") {").i(1);
                    {
                        p("protected " + resultType.getParameterizedQualifiedSourceName() + " parseResult(" + JSON_VALUE_CLASS + " result) throws Exception {").i(1);
                        {
                            if(resultType.getParameterizedQualifiedSourceName().equals("java.lang.Void")) {
                                p("return (java.lang.Void) null;");
                            }
                            else {
                                p("try {").i(1);
                                {
                                    if(resultType.isAssignableTo(locator.LIST_TYPE)){
                                        p("result = new " + JSON_ARRAY_CLASS + "(result.getJavaScriptObject());");
                                    }
                                    jsonAnnotation = getAnnotation(method, Json.class);
                                    Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;
                                    p("return " + locator.decodeExpression(resultType, "result", style) + ";");
                                }
                                i(-1).p("} catch (Throwable __e) {").i(1);
                                {
                                    p("throw new " + RESPONSE_FORMAT_EXCEPTION_CLASS + "(\"Response was NOT a valid JSON document\", __e);");
                                }
                                i(-1).p("}");
                            }
                        }
                        i(-1).p("}");
                    }
                    i(-1).p("});");
            } else {
                p("try {").i(1);
                {
                    p(returnRequest(returnRequest,isJsonp) + "__method.send(new " + ABSTRACT_REQUEST_CALLBACK_CLASS + "<" + resultType.getParameterizedQualifiedSourceName() + ">(__method, "
                                    + callbackArg.getName() + ") {").i(1);
                    {
                        p("protected " + resultType.getParameterizedQualifiedSourceName() + " parseResult() throws Exception {").i(1);
                        {
                            if(resultType.getParameterizedQualifiedSourceName().equals("java.lang.Void")) {
                                p("return (java.lang.Void) null;");
                            }
                            else {
                                p("try {").i(1);
                                {
                                    jsonAnnotation = getAnnotation(method, Json.class);
                                    Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;
                                    p("return " + locator.decodeExpression(resultType, JSON_PARSER_CLASS + ".parse(__method.getResponse().getText())", style) + ";");
                                }
                                i(-1).p("} catch (Throwable __e) {").i(1);
                                {
                                    p("throw new " + RESPONSE_FORMAT_EXCEPTION_CLASS + "(\"Response was NOT a valid JSON document\", __e);");
                                }
                                i(-1).p("}");
                            }
                        }
                        i(-1).p("}");
                    }
                    i(-1).p("});");
                }
                i(-1).p("} catch (" + REQUEST_EXCEPTION_CLASS + " __e) {").i(1);
                {
                    p(callbackArg.getName() + ".onFailure(__method,__e);");
                    if (returnRequest) {
                        p("return null;");
                    }                   
                }
                i(-1).p("}");
            }
        }
        i(-1).p("}");
    }

    private <T extends Annotation> T findAnnotationOnMethodOrEnclosingType(final JMethod method, final Class<T> annotationType) {
        T annotation = getAnnotation(method, annotationType);
        if (annotation == null) {
            annotation = getAnnotation(method.getEnclosingType(), annotationType);
        }
        return annotation;
    }

    protected String toStringExpression(JParameter arg) {
        Attribute attribute = getAnnotation(arg, Attribute.class);
        if(attribute != null){
            return "(" + arg.getName() + "." + attribute.value() + "+ \"\")";
        }
        return toStringExpression(arg.getType(), arg.getName());
    }

    protected String toFormStringExpression(JParameter argument, Style classStyle) throws UnableToCompleteException {
        JType type = argument.getType();
        String expr = argument.getName();

        if (type.isPrimitive() != null) {
            return "\"\"+" + expr;
        }
        if (STRING_TYPE == type) {
            return expr;
        }
        if (type.isClass() != null &&
                isOverlayArrayType(type.isClass())) {
            return "(new " + JSON_ARRAY_CLASS + "(" + expr + ")).toString()";
        }
        if (type.isClass() != null &&
                OVERLAY_VALUE_TYPE.isAssignableFrom(type.isClass())) {
            return "(new " + JSON_OBJECT_CLASS + "(" + expr + ")).toString()";
        }
        if (type.getQualifiedBinaryName().startsWith("java.lang.") || type.isEnum() != null) {
            return String.format("(%s != null ? %s.toString() : null)", expr, expr);
        }

        Json jsonAnnotation = getAnnotation(argument, Json.class);
        final Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;

        return locator.encodeExpression(type, expr, style) + ".toString()";
    }

    protected String toIteratedFormStringExpression(JParameter argument, Style classStyle) throws UnableToCompleteException {
        assert isQueryParamListType(argument.getType().isClassOrInterface());
        final JClassType[] type_args = argument.getType().isParameterized().getTypeArgs();
        assert (type_args.length == 1);
        final JClassType class_type = type_args[0];
        final String argument_expr = "final_"
                + argument.getName();

        final StringBuilder result = new StringBuilder();
        result.append(argument_expr + " == null ? null : ");
        result.append("new java.lang.Iterable<String> () {\n");
        result.append(" @Override\n");
        result.append(" public java.util.Iterator<String> iterator() {\n");
        result.append("     final java.util.Iterator<"
                + class_type.getParameterizedQualifiedSourceName()
                + "> baseIterator =  " + argument_expr + ".iterator();\n");
        result.append("     return new java.util.Iterator<String>() {\n");
        result.append("         @Override\n");
        result.append("         public boolean hasNext() {\n");
        result.append("           return baseIterator.hasNext();\n");
        result.append("         }\n");
        result.append("         @Override\n");
        result.append("         public String next() {\n");
        final String expr = "baseIterator.next()";
        if (class_type.isPrimitive() != null) {
            result.append("             return \"\"+ expr;\n");
        }
        if (STRING_TYPE == class_type) {
            result.append("             return expr;\n");
        }
        if (class_type.isClass() != null &&
            isOverlayArrayType(class_type.isClass())) {
            result.append("             return (new " + JSON_ARRAY_CLASS + "(" + expr + ")).toString();\n");
        }
        if (class_type.isClass() != null &&
            OVERLAY_VALUE_TYPE.isAssignableFrom(class_type.isClass())) {
            result.append("             return (new " + JSON_OBJECT_CLASS + "(" + expr + ")).toString();\n");
        }
        if (class_type.getQualifiedBinaryName().startsWith("java.lang.")) {
            result.append("             return " + String.format("%s != null ? %s.toString() : null;\n", expr, expr));
        }
        Json jsonAnnotation = getAnnotation(argument, Json.class);
        final Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;
        result.append("             return " + locator.encodeExpression(class_type, expr, style) + ".toString();\n");
        result.append("         }\n");
        result.append("         @Override\n");
        result.append("         public void remove() {\n");
        result.append("             throw new UnsupportedOperationException();\n");
        result.append("         }\n");
        result.append("     };\n");
        result.append(" }\n");
        result.append("}\n");

        return result.toString();
    }

    protected String toStringExpression(JType type, String expr) {
        if (type.isPrimitive() != null) {
            return "\"\"+" + expr;
        }
        if (STRING_TYPE == type) {
            return expr;
        }
        if (type.isClass() != null &&
            isOverlayArrayType(type.isClass())) {
          return "(new " + JSON_ARRAY_CLASS + "(" + expr + ")).toString()";
        }
        if (type.isClass() != null &&
            OVERLAY_VALUE_TYPE.isAssignableFrom(type.isClass())) {
          return "(new " + JSON_OBJECT_CLASS + "(" + expr + ")).toString()";
        }

        return String.format("(%s != null ? %s.toString() : null)", expr, expr);
    }

    protected String toIteratedStringExpression(JParameter arg) {
        StringBuilder result = new StringBuilder();
        result.append("new org.fusesource.restygwt.client.StringIterable (")
            .append(arg.getName()).append(")");

        return result.toString();
    }

    private JClassType getCallbackTypeGenericClass(final JClassType callbackType) throws UnableToCompleteException {
        return branch("getCallbackTypeGenericClass()", new Branch<JClassType>() {
            @Override
            public JClassType execute() throws UnableToCompleteException {

                for (JMethod method : callbackType.getOverridableMethods()) {
                    getLogger().log(DEBUG, "checking method: " + method.getName());
                    if (method.getName().equals("onSuccess")) {
                        JParameter[] parameters = method.getParameters();
                        getLogger().log(DEBUG, "checking method params: " + parameters.length);
                        if (parameters.length == 2) {
                            getLogger().log(DEBUG, "checking first param: " + parameters[0].getType());
                            if (parameters[0].getType() == METHOD_TYPE) {
                                getLogger().log(DEBUG, "checking 2nd param: " + parameters[1].getType());
                                JType param2Type = parameters[1].getType();
                                JClassType type = param2Type.isClassOrInterface();
                                if (type == null) {
                                    getLogger().log(ERROR, "The type of the callback not supported: " + param2Type.getJNISignature());
                                    throw new UnableToCompleteException();
                                }
                                getLogger().log(DEBUG, "match: " + type);
                                return type;
                            }
                        }
                    }
                }
                getLogger().log(ERROR, "The type of the callback could not be determined: " + callbackType.getParameterizedQualifiedSourceName());
                throw new UnableToCompleteException();
            }
        });
    }

    private String getRestMethod(JMethod method) throws UnableToCompleteException {
        String restMethod = null;
        if (getAnnotation(method, DELETE.class) != null) {
            restMethod = METHOD_DELETE;
        } else if (getAnnotation(method, GET.class) != null) {
            restMethod = METHOD_GET;
        } else if (getAnnotation(method, HEAD.class) != null) {
            restMethod = METHOD_HEAD;
        } else if (getAnnotation(method, OPTIONS.class) != null) {
            restMethod = METHOD_OPTIONS;
        } else if (getAnnotation(method, POST.class) != null) {
            restMethod = METHOD_POST;
        } else if (getAnnotation(method, PUT.class) != null) {
            restMethod = METHOD_PUT;
        } else if (getAnnotation(method, JSONP.class) != null) {
            restMethod = METHOD_JSONP;
        } else {
            restMethod = method.getName();
            if (!REST_METHODS.contains(restMethod)) {
                getLogger().log(ERROR, "Invalid rest method. It must either have a lower case rest method name or have a javax rs method annotation: " + method.getReadableDeclaration());
                throw new UnableToCompleteException();
            }
        }
        return restMethod;
    }

    /**
     * access additional AnnotationResolvers possibly added by
     *
     * {@link BindingDefaults#addAnnotationResolver(AnnotationResolver)}
     * @return
     */
    // TODO remove suppression
    @SuppressWarnings({"unchecked", "rawtypes"})
    private List<AnnotationResolver> getAnnotationResolvers(final GeneratorContext context, final TreeLogger logger) {
        java.lang.reflect.Method m = null;
        ArrayList args = new ArrayList();
        ArrayList types = new ArrayList();

        types.add(GeneratorContext.class);
        args.add(context);
        types.add(TreeLogger.class);
        args.add(logger);

        Object[] argValues = args.toArray();
        Class[] argtypes = (Class[]) types.toArray(new Class[argValues.length]);

        try {
             m = BINDING_DEFAULTS.getMethod("getAnnotationResolvers", argtypes);
        } catch (SecurityException e) {
            throw new RuntimeException("could not call method `getAnnotationResolvers´ on "
                    + BINDING_DEFAULTS, e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("could not resolve method `getAnnotationResolvers´ on "
                    + BINDING_DEFAULTS, e);
        }

        List<AnnotationResolver> l = new ArrayList<AnnotationResolver>();
        try {
            l = (List<AnnotationResolver>) m.invoke(null, context, logger);
        } catch (IllegalArgumentException e) {
            throw new RuntimeException("could not call method `getAnnotationResolvers´ on "
                    + BINDING_DEFAULTS, e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("could not call method `getAnnotationResolvers´ on "
                    + BINDING_DEFAULTS, e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("could not call method `getAnnotationResolvers´ on "
                    + BINDING_DEFAULTS, e);
        }

        return l;
    }
   
    private String returnRequest(boolean returnRequest, boolean isJsonp) {
        String type = isJsonp ? JsonpRequest.class.getName() : Request.class.getName();
        return returnRequest ? "return ("+type+")" : "";
    }   
}
TOP

Related Classes of org.fusesource.restygwt.rebind.RestServiceClassCreator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.