/*
* Copyright 2015 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.processors.details;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.gwtplatform.dispatch.rest.processors.NameUtils;
import com.gwtplatform.dispatch.rest.processors.resolvers.HttpVerbResolver;
import com.gwtplatform.dispatch.rest.shared.ContentType;
import com.gwtplatform.dispatch.rest.shared.HttpParameter;
import com.gwtplatform.dispatch.rest.shared.RestAction;
import com.gwtplatform.processors.tools.domain.HasImports;
import com.gwtplatform.processors.tools.domain.Method;
import com.gwtplatform.processors.tools.domain.Type;
import com.gwtplatform.processors.tools.domain.Variable;
import com.gwtplatform.processors.tools.exceptions.UnableToProcessException;
import com.gwtplatform.processors.tools.logger.Logger;
import com.gwtplatform.processors.tools.utils.Primitives;
import com.gwtplatform.processors.tools.utils.Utils;
import static com.google.common.base.Predicates.and;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.not;
import static com.google.common.base.Predicates.notNull;
import static com.google.common.collect.ImmutableSet.copyOf;
import static com.gwtplatform.dispatch.rest.processors.resolvers.ContentTypeResolver.resolveConsumes;
import static com.gwtplatform.dispatch.rest.processors.resolvers.ContentTypeResolver.resolveProduces;
import static com.gwtplatform.processors.tools.utils.Primitives.findByPrimitive;
public class EndPointDetails implements HasImports {
public interface Factory {
EndPointDetails create(TypeElement element);
EndPointDetails create(EndPointDetails parentDetails, TypeElement element);
EndPointDetails create(EndPointDetails parentDetails, Method method);
}
private static final String MANY_POTENTIAL_BODY = "Method `%s` has more than one body parameter.";
private static final String FORM_AND_BODY_PARAM = "Method `%s` has both a @FormParam and a body parameter. "
+ "Specify one or the other.";
private static final String GET_WITH_BODY = "End-point method annotated with @GET or @HEAD contains illegal "
+ "parameters. Verify that `%s` contains no body or @FormParam parameters.";
private static final String BAD_REST_ACTION = "Method `%s` returns a RestAction<> without a type argument.";
private final Logger logger;
private final Utils utils;
private PathDetails path;
private Secured secured;
private Set<ContentType> consumes;
private Set<ContentType> produces;
private HttpVerb verb;
private Collection<HttpVariable> httpVariables = Collections.emptyList();
private Optional<HttpVariable> body = Optional.absent();
private Type resultType;
EndPointDetails(
Logger logger,
Utils utils) {
this.logger = logger;
this.utils = utils;
}
EndPointDetails(
Logger logger,
Utils utils,
EndPointDetails parentDetails) {
this.logger = logger;
this.utils = utils;
copyFields(parentDetails);
}
private void copyFields(EndPointDetails parentDetails) {
path = parentDetails.getPath();
secured = parentDetails.getSecured();
consumes = copyOf(parentDetails.getConsumes());
produces = copyOf(parentDetails.getProduces());
httpVariables = parentDetails.getHttpVariables();
body = parentDetails.getBody();
resultType = parentDetails.getResultType();
}
void processMethod(Method method) {
processElement(method.getElement());
processVerb(method);
processResult(method);
createVariables(method);
}
void processElement(Element element) {
path = path == null ? new PathDetails(element) : new PathDetails(element, path);
secured = secured == null ? new Secured(element) : new Secured(element, secured);
consumes = copyOf(consumes == null ? resolveConsumes(element) : resolveConsumes(element, consumes));
produces = copyOf(produces == null ? resolveProduces(element) : resolveProduces(element, produces));
}
private void processVerb(Method method) {
verb = new HttpVerbResolver(logger).resolve(method.getElement());
}
private void processResult(Method method) {
String restActionName = RestAction.class.getCanonicalName();
Type returnType = method.getReturnType();
if (restActionName.equals(returnType.getQualifiedName())) {
resolveRestActionResult(method);
} else {
Optional<Primitives> primitive = findByPrimitive(returnType.getQualifiedParameterizedName());
if (primitive.isPresent()) {
resultType = new Type(primitive.get().getBoxedClass().getCanonicalName());
} else {
resultType = returnType;
}
}
}
private void resolveRestActionResult(Method method) {
List<Type> typeArguments = method.getReturnType().getTypeArguments();
if (typeArguments.size() == 1) {
resultType = typeArguments.get(0);
} else {
ExecutableElement element = method.getElement();
logger.error().context(element).log(BAD_REST_ACTION, NameUtils.qualifiedMethodName(element));
throw new UnableToProcessException();
}
}
private void createVariables(Method method) {
httpVariables = new ArrayList<>(httpVariables);
for (Variable variable : method.getParameters()) {
resolveVariable(method, variable);
}
httpVariables = ImmutableList.copyOf(httpVariables);
logPotentialErrors(method);
}
private void resolveVariable(Method method, Variable variable) {
HttpVariable httpVariable = new HttpVariable(logger, utils, getPath(), variable);
if (body.isPresent() && httpVariable.isBody()) {
ExecutableElement methodElement = method.getElement();
logger.error()
.context(methodElement)
.log(MANY_POTENTIAL_BODY, NameUtils.qualifiedMethodName(methodElement));
throw new UnableToProcessException();
}
if (httpVariable.isBody()) {
body = Optional.of(httpVariable);
} else {
httpVariables.add(httpVariable);
}
}
private void logPotentialErrors(Method method) {
ExecutableElement methodElement = method.getElement();
String methodName = NameUtils.qualifiedMethodName(methodElement);
boolean containsFormVariables = containsFormVariables();
boolean containsBody = body.isPresent();
if (containsBody && containsFormVariables) {
logger.error().context(methodElement).log(FORM_AND_BODY_PARAM, methodName);
throw new UnableToProcessException();
}
if ((verb == HttpVerb.GET || verb == HttpVerb.HEAD) && (containsBody || containsFormVariables)) {
logger.error().context(methodElement).log(GET_WITH_BODY, methodName);
throw new UnableToProcessException();
}
}
private boolean containsFormVariables() {
return httpVariables.stream().anyMatch(
httpVariable -> httpVariable.getHttpAnnotation().get().getParameterType() == HttpParameter.Type.FORM);
}
public HttpVerb getVerb() {
return verb;
}
public PathDetails getPath() {
return path;
}
public Secured getSecured() {
return secured;
}
public Set<ContentType> getConsumes() {
return consumes;
}
public Set<ContentType> getProduces() {
return produces;
}
public Collection<HttpVariable> getHttpVariables() {
return httpVariables;
}
public Optional<HttpVariable> getBody() {
return body;
}
public Type getResultType() {
return resultType;
}
@Override
public Collection<String> getImports() {
FluentIterable<String> imports = FluentIterable.from(httpVariables)
.transformAndConcat(EXTRACT_IMPORTS_FUNCTION)
.append(resultType.getImports());
if (body.isPresent()) {
imports = imports.append(body.get().getImports());
}
if (!httpVariables.isEmpty()) {
imports = imports.append(HttpParameter.Type.class.getCanonicalName());
}
return imports.filter(and(notNull(), not(equalTo("")))).toList();
}
}