Package com.gwtplatform.dispatch.rest.rebind

Source Code of com.gwtplatform.dispatch.rest.rebind.ActionGenerator$AnnotatedMethodParameter

/**
* Copyright 2013 ArcBees Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/

package com.gwtplatform.dispatch.rest.rebind;

import java.lang.annotation.Annotation;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.HttpHeaders;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;

import com.google.common.collect.Lists;
import com.google.common.eventbus.EventBus;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.inject.assistedinject.Assisted;
import com.gwtplatform.dispatch.rest.client.MetadataType;
import com.gwtplatform.dispatch.rest.rebind.event.RegisterMetadataEvent;
import com.gwtplatform.dispatch.rest.rebind.event.RegisterSerializableTypeEvent;
import com.gwtplatform.dispatch.rest.rebind.type.ActionBinding;
import com.gwtplatform.dispatch.rest.rebind.type.MethodCall;
import com.gwtplatform.dispatch.rest.rebind.type.ResourceBinding;
import com.gwtplatform.dispatch.rest.rebind.util.AnnotationValueResolver;
import com.gwtplatform.dispatch.rest.rebind.util.FormParamValueResolver;
import com.gwtplatform.dispatch.rest.rebind.util.GeneratorUtil;
import com.gwtplatform.dispatch.rest.rebind.util.HeaderParamValueResolver;
import com.gwtplatform.dispatch.rest.rebind.util.PathParamValueResolver;
import com.gwtplatform.dispatch.rest.rebind.util.QueryParamValueResolver;
import com.gwtplatform.dispatch.rest.shared.DateFormat;
import com.gwtplatform.dispatch.rest.shared.HttpMethod;
import com.gwtplatform.dispatch.rest.shared.NoXsrfHeader;
import com.gwtplatform.dispatch.rest.shared.RestAction;

import static com.gwtplatform.dispatch.rest.client.MetadataType.BODY_TYPE;
import static com.gwtplatform.dispatch.rest.client.MetadataType.RESPONSE_TYPE;

public class ActionGenerator extends AbstractVelocityGenerator {
    private static class AnnotatedMethodParameter {
        private JParameter parameter;
        private String fieldName;

        private AnnotatedMethodParameter(JParameter parameter, String fieldName) {
            this.parameter = parameter;
            this.fieldName = fieldName;
        }
    }

    private static final String TEMPLATE = "com/gwtplatform/dispatch/rest/rebind/RestAction.vm";

    private static final String MANY_REST_ANNOTATIONS = "'%s' parameter's '%s' is annotated with more than one REST " +
            "annotations.";
    private static final String DATE_FORMAT_NOT_DATE = "'%s' parameter's '%s' is annotated with @DateFormat but its " +
            "type is not Date";
    private static final String MANY_POTENTIAL_BODY = "%s has more than one potential body parameter.";
    private static final String FORM_AND_BODY_PARAM = "%s has both @FormParam and a body parameter. You must specify " +
            "one or the other.";
    private static final String ADD_HEADER_PARAM = "addHeaderParam";
    private static final String ADD_PATH_PARAM = "addPathParam";
    private static final String ADD_QUERY_PARAM = "addQueryParam";
    private static final String ADD_FORM_PARAM = "addFormParam";
    private static final String SET_BODY_PARAM = "setBodyParam";
    private static final String ESCAPED_STRING = "\"%s\"";

    @SuppressWarnings("unchecked")
    private static final List<Class<? extends Annotation>> PARAM_ANNOTATIONS =
            Arrays.asList(HeaderParam.class, QueryParam.class, PathParam.class, FormParam.class);

    private final EventBus eventBus;

    private final JMethod actionMethod;
    private final ResourceBinding parent;
    private final String path;
    private final boolean secured;
    private final List<JParameter> parameters;
    private final List<AnnotatedMethodParameter> pathParams = new ArrayList<AnnotatedMethodParameter>();
    private final List<AnnotatedMethodParameter> headerParams = new ArrayList<AnnotatedMethodParameter>();
    private final List<AnnotatedMethodParameter> queryParams = new ArrayList<AnnotatedMethodParameter>();
    private final List<AnnotatedMethodParameter> formParams = new ArrayList<AnnotatedMethodParameter>();
    private final List<JParameter> potentialBodyParams = new ArrayList<JParameter>();

    private boolean returnsAction;
    private JClassType resultClass;
    private JPrimitiveType resultPrimitive;
    private HttpMethod httpMethod;
    private JParameter bodyParam;

    @Inject
    ActionGenerator(
            EventBus eventBus,
            TypeOracle typeOracle,
            Logger logger,
            Provider<VelocityContext> velocityContextProvider,
            VelocityEngine velocityEngine,
            GeneratorUtil generatorUtil,
            @Assisted JMethod actionMethod,
            @Assisted ResourceBinding parent) {
        super(typeOracle, logger, velocityContextProvider, velocityEngine, generatorUtil);

        this.eventBus = eventBus;
        this.actionMethod = actionMethod;
        this.parent = parent;

        path = concatenatePath(parent.getResourcePath(), extractPath(actionMethod));
        secured = parent.isSecured() && !actionMethod.isAnnotationPresent(NoXsrfHeader.class);
        parameters = Lists.newArrayList(parent.getCtorParameters());
    }

    public ActionBinding generate() throws UnableToCompleteException {
        String implName = getSuperTypeName() + SUFFIX;
        if (doGenerate(implName)) {
            registerMetadata();
        }
        return createActionBinding(implName);
    }

    @Override
    protected void populateVelocityContext(VelocityContext velocityContext) throws UnableToCompleteException {
        velocityContext.put("resultClass", resultClass);
        velocityContext.put("httpMethod", httpMethod);
        velocityContext.put("methodCalls", getMethodCallsToAdd());
        velocityContext.put("restPath", path);
        velocityContext.put("ctorParams", parameters);
        velocityContext.put("secured", secured);
    }

    @Override
    protected String getPackage() {
        return parent.getImplPackage().replace(SHARED_PACKAGE, CLIENT_PACKAGE);
    }

    private String getQualifiedImplName() {
        return getPackage() + "." + getSuperTypeName() + SUFFIX;
    }

    private String getSuperTypeName() {
        // The service may define overloads, in which case the method name is not unique.
        // The method index will ensure the generated class uniqueness.
        int methodIndex = Arrays.asList(actionMethod.getEnclosingType().getMethods()).indexOf(actionMethod);

        return parent.getSuperTypeName() + "_" + methodIndex + "_" + actionMethod.getName();
    }

    private List<MethodCall> getMethodCallsToAdd() {
        List<MethodCall> methodCalls = new ArrayList<MethodCall>();

        methodCalls.addAll(getMethodCallsToAdd(headerParams, ADD_HEADER_PARAM));
        methodCalls.addAll(getMethodCallsToAdd(pathParams, ADD_PATH_PARAM));
        methodCalls.addAll(getMethodCallsToAdd(queryParams, ADD_QUERY_PARAM));
        methodCalls.addAll(getMethodCallsToAdd(formParams, ADD_FORM_PARAM));

        addContentTypeHeaderMethodCall(methodCalls);

        if (bodyParam != null) {
            methodCalls.add(new MethodCall(SET_BODY_PARAM, bodyParam.getName()));
        }

        return methodCalls;
    }

    private List<MethodCall> getMethodCallsToAdd(List<AnnotatedMethodParameter> methodParameters, String methodName) {
        List<MethodCall> methodCalls = new ArrayList<MethodCall>();
        for (AnnotatedMethodParameter methodParameter : methodParameters) {
            List<String> arguments = Lists.newArrayList(String.format(ESCAPED_STRING, methodParameter.fieldName),
                    methodParameter.parameter.getName());

            maybeAddDateFormat(arguments, methodParameter);

            methodCalls.add(new MethodCall(methodName, arguments.toArray(new String[arguments.size()])));
        }

        return methodCalls;
    }

    private void maybeAddDateFormat(List<String> args, AnnotatedMethodParameter methodParameter) {
        if (methodParameter.parameter.isAnnotationPresent(DateFormat.class)) {
            String dateFormat = methodParameter.parameter.getAnnotation(DateFormat.class).value();
            args.add(String.format(ESCAPED_STRING, dateFormat));
        }
    }

    private void addContentTypeHeaderMethodCall(List<MethodCall> methodCalls) {
        Consumes consumes = actionMethod.getAnnotation(Consumes.class);

        if (consumes != null && consumes.value().length > 0) {
            MethodCall methodCall = new MethodCall(
                    ADD_HEADER_PARAM,
                    String.format(ESCAPED_STRING, HttpHeaders.CONTENT_TYPE),
                    String.format(ESCAPED_STRING, consumes.value()[0]));
            methodCalls.add(methodCall);
        }
    }

    private void registerMetadata() throws UnableToCompleteException {
        if (bodyParam != null) {
            registerMetadatum(BODY_TYPE, bodyParam.getType());
        }

        registerMetadatum(RESPONSE_TYPE, resultClass);
    }

    private void registerMetadatum(MetadataType metadataType, JType type) {
        String typeLiteral = "\"" + type.getParameterizedQualifiedSourceName() + "\"";

        eventBus.post(new RegisterMetadataEvent(getQualifiedImplName(), metadataType, typeLiteral));

        if (!Void.class.getCanonicalName().equals(type.getQualifiedSourceName()) && type.isPrimitive() == null) {
            eventBus.post(new RegisterSerializableTypeEvent(type));
        }
    }

    private boolean doGenerate(String implName) throws UnableToCompleteException {
        verifyReturnType();

        retrieveHttpMethod();
        retrieveParameterConfig();
        retrieveBodyConfig();

        return mergeTemplate(TEMPLATE, implName);
    }

    private void verifyReturnType() throws UnableToCompleteException {
        JClassType actionClass = getGeneratorUtil().getType(RestAction.class.getName());
        JClassType returnInterface = getReturnType().isInterface();

        returnsAction = returnInterface != null && returnInterface.isAssignableTo(actionClass);
        resultPrimitive = getReturnType().isPrimitive();
        resultClass = parseResultType();
    }

    private JClassType parseResultType() throws UnableToCompleteException {
        JClassType resultType;

        if (returnsAction) {
            resultType = parseActionResultType();
        } else {
            resultType = parseRawResultType();
        }

        return resultType;
    }

    private JClassType parseActionResultType() throws UnableToCompleteException {
        JClassType resultType = null;
        JParameterizedType parameterized = getReturnType().isParameterized();

        if (parameterized == null || parameterized.getTypeArgs().length != 1) {
            getLogger().die("RestAction must specify a result type argument.");
        } else {
            resultType = parameterized.getTypeArgs()[0];
        }

        return resultType;
    }

    private JClassType parseRawResultType() throws UnableToCompleteException {
        JClassType resultType;

        if (resultPrimitive != null) {
            resultType = getGeneratorUtil().convertPrimitiveToBoxed(resultPrimitive);
        } else {
            resultType = getReturnType().isClassOrInterface();
        }

        return resultType;
    }

    private void retrieveHttpMethod() throws UnableToCompleteException {
        Boolean moreThanOneAnnotation = false;

        for (SupportedHttpActions method : SupportedHttpActions.values()) {
            if (actionMethod.isAnnotationPresent(method.getAnnotationClass())) {
                moreThanOneAnnotation = moreThanOneAnnotation || httpMethod != null;
                httpMethod = method.getHttpMethod();
            }
        }

        if (httpMethod == null) {
            getLogger().die(actionMethod.getName() + " has no http method annotations.");
        } else if (moreThanOneAnnotation) {
            getLogger().warn(actionMethod.getName() + " has more than one http method annotation.");
        }
    }

    private void retrieveParameterConfig() throws UnableToCompleteException {
        Collections.addAll(parameters, actionMethod.getParameters());

        buildParamList(parameters, HeaderParam.class, new HeaderParamValueResolver(), headerParams);
        buildParamList(parameters, PathParam.class, new PathParamValueResolver(), pathParams);
        buildParamList(parameters, QueryParam.class, new QueryParamValueResolver(), queryParams);
        buildParamList(parameters, FormParam.class, new FormParamValueResolver(), formParams);

        buildPotentialBodyParams();
    }

    private <T extends Annotation> void buildParamList(List<JParameter> parameters, Class<T> annotationClass,
            AnnotationValueResolver<T> annotationValueResolver, List<AnnotatedMethodParameter> destination)
            throws UnableToCompleteException {
        List<Class<? extends Annotation>> restrictedAnnotations = getRestrictedAnnotations(annotationClass);

        for (JParameter parameter : parameters) {
            T parameterAnnotation = parameter.getAnnotation(annotationClass);

            if (parameterAnnotation != null) {
                if (hasAnnotationFrom(parameter, restrictedAnnotations)) {
                    getLogger().die(String.format(MANY_REST_ANNOTATIONS, actionMethod.getName(), parameter.getName()));
                }
                if (parameter.isAnnotationPresent(DateFormat.class) && !isDate(parameter)) {
                    getLogger().die(String.format(DATE_FORMAT_NOT_DATE, actionMethod.getName(), parameter.getName()));
                }

                String value = annotationValueResolver.resolve(parameterAnnotation);
                destination.add(new AnnotatedMethodParameter(parameter, value));
            }
        }
    }

    private boolean isDate(JParameter parameter) {
        return Date.class.getCanonicalName().equals(parameter.getType().getQualifiedSourceName());
    }

    private List<Class<? extends Annotation>> getRestrictedAnnotations(Class<? extends Annotation> allowedAnnotation) {
        List<Class<? extends Annotation>> restrictedAnnotations = new ArrayList<Class<? extends Annotation>>();
        restrictedAnnotations.addAll(PARAM_ANNOTATIONS);
        restrictedAnnotations.remove(allowedAnnotation);

        return restrictedAnnotations;
    }

    private Boolean hasAnnotationFrom(JParameter parameter, List<Class<? extends Annotation>> restrictedAnnotations) {
        for (Class<? extends Annotation> restrictedAnnotation : restrictedAnnotations) {
            if (parameter.isAnnotationPresent(restrictedAnnotation)) {
                return true;
            }
        }

        return false;
    }

    private void buildPotentialBodyParams() {
        potentialBodyParams.addAll(parameters);

        List<AnnotatedMethodParameter> annotatedParameters = new ArrayList<AnnotatedMethodParameter>();
        annotatedParameters.addAll(headerParams);
        annotatedParameters.addAll(pathParams);
        annotatedParameters.addAll(queryParams);
        annotatedParameters.addAll(formParams);

        for (AnnotatedMethodParameter annotatedParameter : annotatedParameters) {
            potentialBodyParams.remove(annotatedParameter.parameter);
        }
    }

    private void retrieveBodyConfig() throws UnableToCompleteException {
        if (potentialBodyParams.isEmpty()) {
            return;
        }

        if (potentialBodyParams.size() > 1) {
            getLogger().die(String.format(MANY_POTENTIAL_BODY, getMethodName()));
        }

        if (!formParams.isEmpty()) {
            getLogger().die(String.format(FORM_AND_BODY_PARAM, getMethodName()));
        }

        bodyParam = potentialBodyParams.get(0);
    }

    private ActionBinding createActionBinding(String implName) throws UnableToCompleteException {
        String resultClassName = resultClass.getParameterizedQualifiedSourceName();

        ActionBinding binding =
                new ActionBinding(path, getPackage(), implName, getMethodName(), resultClassName, parameters);
        binding.setSecured(secured);
        binding.setRestAction(returnsAction);
        binding.setResultPrimitive(resultPrimitive);

        return binding;
    }

    private JType getReturnType() {
        return actionMethod.getReturnType();
    }

    private String getMethodName() {
        return actionMethod.getName();
    }
}
TOP

Related Classes of com.gwtplatform.dispatch.rest.rebind.ActionGenerator$AnnotatedMethodParameter

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.