Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.gwtplatform.dispatch.rest.rebind.ActionGenerator Maven / Gradle / Ivy
/**
* 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.io.PrintWriter;
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.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.POST;
import javax.ws.rs.PUT;
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.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.inject.assistedinject.Assisted;
import com.gwtplatform.dispatch.rest.client.DateFormat;
import com.gwtplatform.dispatch.rest.client.NoXsrfHeader;
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.HttpMethod;
import com.gwtplatform.dispatch.rest.shared.MetadataType;
import com.gwtplatform.dispatch.rest.shared.RestAction;
import static com.gwtplatform.dispatch.rest.shared.MetadataType.BODY_TYPE;
import static com.gwtplatform.dispatch.rest.shared.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> 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 parameters;
private final List pathParams = new ArrayList();
private final List headerParams = new ArrayList();
private final List queryParams = new ArrayList();
private final List formParams = new ArrayList();
private final List potentialBodyParams = new ArrayList();
private HttpMethod httpMethod;
private JParameter bodyParam;
@Inject
ActionGenerator(
EventBus eventBus,
TypeOracle typeOracle,
Logger logger,
Provider 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;
PrintWriter printWriter = getGeneratorUtil().tryCreatePrintWriter(getPackage(), implName);
if (printWriter != null) {
doGenerate(implName, printWriter);
registerMetadata();
} else {
getLogger().debug("Action already generated. Returning.");
}
return createActionBinding(implName);
}
@Override
protected void populateVelocityContext(VelocityContext velocityContext) throws UnableToCompleteException {
velocityContext.put("resultClass", getResultType());
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 getMethodCallsToAdd() {
List methodCalls = new ArrayList();
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 getMethodCallsToAdd(List methodParameters, String methodName) {
List methodCalls = new ArrayList();
for (AnnotatedMethodParameter methodParameter : methodParameters) {
List 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 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 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 doGenerate(String implName, PrintWriter printWriter) throws UnableToCompleteException {
verifyIsAction();
retrieveHttpMethod();
retrieveParameterConfig();
retrieveBodyConfig();
mergeTemplate(printWriter, TEMPLATE, implName);
}
private void registerMetadata() throws UnableToCompleteException {
if (bodyParam != null) {
registerMetadatum(BODY_TYPE, bodyParam.getType());
}
registerMetadatum(RESPONSE_TYPE, getResultType());
}
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 void verifyIsAction() throws UnableToCompleteException {
JClassType actionClass = null;
try {
actionClass = getTypeOracle().getType(RestAction.class.getName());
} catch (NotFoundException e) {
getLogger().die("Unable to find interface Action.");
}
JClassType returnClass = getReturnType().isClassOrInterface();
if (!returnClass.isAssignableTo(actionClass)) {
String typeName = returnClass.getQualifiedSourceName();
getLogger().die(typeName + " must implement RestAction.");
}
}
private void retrieveHttpMethod() throws UnableToCompleteException {
Boolean moreThanOneAnnotation = false;
if (actionMethod.isAnnotationPresent(GET.class)) {
httpMethod = HttpMethod.GET;
}
if (actionMethod.isAnnotationPresent(POST.class)) {
moreThanOneAnnotation = httpMethod != null;
httpMethod = HttpMethod.POST;
}
if (actionMethod.isAnnotationPresent(PUT.class)) {
moreThanOneAnnotation = moreThanOneAnnotation || httpMethod != null;
httpMethod = HttpMethod.PUT;
}
if (actionMethod.isAnnotationPresent(DELETE.class)) {
moreThanOneAnnotation = moreThanOneAnnotation || httpMethod != null;
httpMethod = HttpMethod.DELETE;
}
if (actionMethod.isAnnotationPresent(HEAD.class)) {
moreThanOneAnnotation = moreThanOneAnnotation || httpMethod != null;
httpMethod = HttpMethod.HEAD;
}
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 void buildParamList(List parameters, Class annotationClass,
AnnotationValueResolver annotationValueResolver, List destination)
throws UnableToCompleteException {
List> 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> getRestrictedAnnotations(Class extends Annotation> allowedAnnotation) {
List> restrictedAnnotations = new ArrayList>();
restrictedAnnotations.addAll(PARAM_ANNOTATIONS);
restrictedAnnotations.remove(allowedAnnotation);
return restrictedAnnotations;
}
private Boolean hasAnnotationFrom(JParameter parameter, List> restrictedAnnotations) {
for (Class extends Annotation> restrictedAnnotation : restrictedAnnotations) {
if (parameter.isAnnotationPresent(restrictedAnnotation)) {
return true;
}
}
return false;
}
private void buildPotentialBodyParams() {
potentialBodyParams.addAll(parameters);
List annotatedParameters = new ArrayList();
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, actionMethod.getName()));
}
if (!formParams.isEmpty()) {
getLogger().die(String.format(FORM_AND_BODY_PARAM, actionMethod.getName()));
}
bodyParam = potentialBodyParams.get(0);
}
private ActionBinding createActionBinding(String implName) throws UnableToCompleteException {
String resultClass = getResultType().getParameterizedQualifiedSourceName();
ActionBinding binding = new ActionBinding(path, getPackage(), implName, actionMethod.getName(), resultClass,
parameters);
binding.setSecured(secured);
return binding;
}
@SuppressWarnings("ConstantConditions")
private JClassType getResultType() throws UnableToCompleteException {
JParameterizedType parameterized = getReturnType().isParameterized();
if (parameterized == null || parameterized.getTypeArgs().length != 1) {
getLogger().die("The action must specify a result type argument.");
}
return parameterized.getTypeArgs()[0];
}
private JType getReturnType() {
return actionMethod.getReturnType();
}
}