/** * Copyright © 2006-2016 Web Cohesion (info@webcohesion.com) * * 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.webcohesion.enunciate.modules.jaxrs.model; import com.webcohesion.enunciate.javac.decorations.DecoratedProcessingEnvironment; import com.webcohesion.enunciate.javac.decorations.ElementDecorator; import com.webcohesion.enunciate.javac.decorations.element.*; import com.webcohesion.enunciate.javac.decorations.type.DecoratedTypeMirror; import com.webcohesion.enunciate.javac.decorations.type.TypeMirrorUtils; import com.webcohesion.enunciate.metadata.rs.TypeHint; import com.webcohesion.enunciate.modules.jaxrs.EnunciateJaxrsContext; import com.webcohesion.enunciate.util.IgnoreUtils; import com.webcohesion.enunciate.util.TypeHintUtils; import javax.lang.model.element.*; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import javax.ws.rs.*; import javax.ws.rs.container.Suspended; import javax.ws.rs.core.Context; import java.util.*; /** * Parameter for a JAX-RS resource. * * @author Ryan Heaton */ public class ResourceParameter extends DecoratedElement<Element> implements Comparable<ResourceParameter> { public static final List<String> FORM_BEAN_ANNOTATIONS = Arrays.asList("org.jboss.resteasy.annotations.Form", "javax.ws.rs.BeanParam", "com.sun.jersey.api.core.InjectParam"); private final PathContext context; private final String parameterName; private final String defaultValue; private final String typeName; private final boolean matrixParam; private final boolean queryParam; private final boolean pathParam; private final boolean cookieParam; private final boolean headerParam; private final boolean formParam; private final boolean multivalued; private ResourceParameterConstraints constraints; private ResourceParameterDataType dataType; public ResourceParameter(Element declaration, PathContext context) { super(declaration, context.getContext().getContext().getProcessingEnvironment()); this.context = context; String parameterName = null; String typeName = null; boolean matrix = false; boolean query = false; boolean path = false; boolean cookie = false; boolean header = false; boolean form = false; MatrixParam matrixParam = declaration.getAnnotation(MatrixParam.class); if (matrixParam != null) { parameterName = matrixParam.value(); typeName = "matrix"; matrix = true; } QueryParam queryParam = declaration.getAnnotation(QueryParam.class); if (queryParam != null) { parameterName = queryParam.value(); typeName = "query"; query = true; } PathParam pathParam = declaration.getAnnotation(PathParam.class); if (pathParam != null) { parameterName = pathParam.value(); typeName = "path"; path = true; } CookieParam cookieParam = declaration.getAnnotation(CookieParam.class); if (cookieParam != null) { parameterName = cookieParam.value(); typeName = "cookie"; cookie = true; } HeaderParam headerParam = declaration.getAnnotation(HeaderParam.class); if (headerParam != null) { parameterName = headerParam.value(); typeName = "header"; header = true; } FormParam formParam = declaration.getAnnotation(FormParam.class); if (formParam != null) { parameterName = formParam.value(); typeName = "form"; form = true; } if (typeName == null) { for (AnnotationMirror annotation : declaration.getAnnotationMirrors()) { TypeElement decl = (TypeElement) annotation.getAnnotationType().asElement(); if (decl != null) { String fqn = decl.getQualifiedName().toString(); if (this.context.getContext().getCustomResourceParameterAnnotations().contains(fqn)) { parameterName = declaration.getSimpleName().toString(); typeName = decl.getSimpleName().toString().toLowerCase().replaceAll("param", ""); break; } } } } if (typeName == null) { typeName = "custom"; } DecoratedTypeMirror parameterType = loadType(); this.multivalued = parameterType.isArray() || parameterType.isCollection(); this.parameterName = parameterName; this.matrixParam = matrix; this.queryParam = query; this.pathParam = path; this.cookieParam = cookie; this.headerParam = header; this.formParam = form; this.typeName = typeName; DefaultValue defaultValue = declaration.getAnnotation(DefaultValue.class); if (defaultValue != null) { this.defaultValue = defaultValue.value(); } else { this.defaultValue = null; } if (delegate instanceof DecoratedVariableElement) { getJavaDoc().setValue(((DecoratedVariableElement) delegate).getDocComment()); } } public DecoratedTypeMirror loadType() { TypeHint hint = getAnnotation(TypeHint.class); if (hint != null) { return (DecoratedTypeMirror) TypeHintUtils.getTypeHint(hint, getContext().getContext().getContext().getProcessingEnvironment(), asType()); } return (DecoratedTypeMirror) asType(); } public static boolean isResourceParameter(Element candidate, EnunciateJaxrsContext context) { if (IgnoreUtils.isIgnored(candidate)) { return false; } if (!isSystemParameter(candidate, context)) { for (AnnotationMirror annotation : candidate.getAnnotationMirrors()) { TypeElement declaration = (TypeElement) annotation.getAnnotationType().asElement(); if (declaration != null) { String fqn = declaration.getQualifiedName().toString(); if ((MatrixParam.class.getName().equals(fqn)) || QueryParam.class.getName().equals(fqn) || PathParam.class.getName().equals(fqn) || CookieParam.class.getName().equals(fqn) || HeaderParam.class.getName().equals(fqn) || FormParam.class.getName().equals(fqn)) { return true; } if (context.getSystemResourceParameterAnnotations().contains(fqn)) { return false; } if (context.getCustomResourceParameterAnnotations().contains(fqn)) { return true; } } } } return false; } public static boolean isSystemParameter(Element candidate, EnunciateJaxrsContext context) { for (AnnotationMirror annotation : candidate.getAnnotationMirrors()) { TypeElement declaration = (TypeElement) annotation.getAnnotationType().asElement(); if (declaration != null) { String fqn = declaration.getQualifiedName().toString(); if (Context.class.getName().equals(fqn) && candidate.getAnnotation(TypeHint.class) == null) { return true; } if (Suspended.class.getName().equals(fqn) && candidate.getAnnotation(TypeHint.class) == null) { return true; } if (context.getSystemResourceParameterAnnotations().contains(fqn)) { return true; } } } return false; } public static boolean isBeanParameter(Element candidate) { for (AnnotationMirror annotation : candidate.getAnnotationMirrors()) { TypeElement declaration = (TypeElement) annotation.getAnnotationType().asElement(); if (declaration != null) { String fqn = declaration.getQualifiedName().toString(); if (FORM_BEAN_ANNOTATIONS.contains(fqn)) { return true; } } } return false; } public static List<ResourceParameter> getFormBeanParameters(VariableElement parameterDeclaration, PathContext context) { ArrayList<ResourceParameter> formBeanParameters = new ArrayList<ResourceParameter>(); gatherFormBeanParameters(parameterDeclaration.asType(), formBeanParameters, context); return formBeanParameters; } private static void gatherFormBeanParameters(TypeMirror type, ArrayList<ResourceParameter> beanParams, PathContext context) { if (type instanceof DeclaredType) { DecoratedTypeElement typeDeclaration = (DecoratedTypeElement) ElementDecorator.decorate(((DeclaredType) type).asElement(), context.getContext().getContext().getProcessingEnvironment()); for (VariableElement field : ElementFilter.fieldsIn(typeDeclaration.getEnclosedElements())) { if (isResourceParameter(field, context.getContext())) { beanParams.add(new ResourceParameter(field, context)); } else if (isBeanParameter(field)) { gatherFormBeanParameters(field.asType(), beanParams, context); } } List<PropertyElement> properties = new ArrayList<PropertyElement>(typeDeclaration.getProperties(new JaxRsResourceParameterPropertySpec(context.getContext().getContext().getProcessingEnvironment()))); for (PropertyElement property : properties) { if (isResourceParameter(property, context.getContext())) { beanParams.add(new ResourceParameter(property, context)); } else if (isBeanParameter(property)) { gatherFormBeanParameters(property.getPropertyType(), beanParams, context); } } if (typeDeclaration.getKind() == ElementKind.CLASS) { gatherFormBeanParameters(typeDeclaration.getSuperclass(), beanParams, context); } } } /** * The parameter name. * * @return The parameter name. */ public String getParameterName() { return parameterName; } /** * The default value. * * @return The default value. */ public String getDefaultValue() { return defaultValue; } /** * Whether this is a matrix parameter. * * @return Whether this is a matrix parameter. */ public boolean isMatrixParam() { return matrixParam; } /** * Whether this is a query parameter. * * @return Whether this is a query parameter. */ public boolean isQueryParam() { return queryParam; } /** * Whether this is a path parameter. * * @return Whether this is a path parameter. */ public boolean isPathParam() { return pathParam; } /** * Whether this is a cookie parameter. * * @return Whether this is a cookie parameter. */ public boolean isCookieParam() { return cookieParam; } /** * Whether this is a header parameter. * * @return Whether this is a header parameter. */ public boolean isHeaderParam() { return headerParam; } /** * Whether this is a form parameter. * * @return Whether this is a form parameter. */ public boolean isFormParam() { return formParam; } /** * The type of the parameter. * * @return The type of the parameter. */ public String getTypeName() { return this.typeName; } /** * Whether this parameter is multi-valued. * * @return Whether this parameter is multi-valued. */ public boolean isMultivalued() { return multivalued; } /** * The constraints of the resource parameter. * * @return The constraints of the resource parameter. */ public ResourceParameterConstraints getConstraints() { if (this.constraints == null) { this.constraints = loadConstraints(); } return this.constraints; } public ResourceParameterConstraints loadConstraints() { String regex = null; String componentName = "{" + getParameterName() + "}"; for (PathSegment component : this.context.getPathComponents()) { if (componentName.equals(component.getValue())) { regex = component.getRegex(); break; } } if (regex != null) { return new ResourceParameterConstraints.Regex(regex); } DecoratedTypeMirror type = loadType(); //unwrap it, if possible. DecoratedTypeMirror componentType = TypeMirrorUtils.getComponentType(type, this.context.getContext().getContext().getProcessingEnvironment()); if (componentType != null) { type = componentType; } //unbox it, if possible. try { type = (DecoratedTypeMirror) this.context.getContext().getContext().getProcessingEnvironment().getTypeUtils().unboxedType(type); } catch (Exception e) { //no-op; not unboxable. } if (type.isPrimitive()) { return new ResourceParameterConstraints.Primitive(type.getKind()); } else if (type.isEnum()) { List<VariableElement> enumConstants = ((DecoratedTypeElement) ((DeclaredType) type).asElement()).enumValues(); Set<String> values = new TreeSet<String>(); for (VariableElement enumConstant : enumConstants) { values.add(enumConstant.getSimpleName().toString()); } return new ResourceParameterConstraints.Enumeration(values); } return new ResourceParameterConstraints.UnboundString(); } public ResourceParameterDataType getDataType() { if (this.dataType == null) { this.dataType = loadDataType(); } return this.dataType; } private ResourceParameterDataType loadDataType() { DecoratedTypeMirror type = loadType(); //unwrap it, if possible. DecoratedTypeMirror componentType = TypeMirrorUtils.getComponentType(type, this.context.getContext().getContext().getProcessingEnvironment()); if (componentType != null) { type = componentType; } //unbox it, if possible. try { type = (DecoratedTypeMirror) this.context.getContext().getContext().getProcessingEnvironment().getTypeUtils().unboxedType(type); } catch (Exception e) { //no-op; not unboxable. } if (type.isPrimitive()) { switch (type.getKind()) { case BOOLEAN: return ResourceParameterDataType.BOOLEAN; case SHORT: case INT: return ResourceParameterDataType.INT32; case LONG: return ResourceParameterDataType.INT64; case DOUBLE: return ResourceParameterDataType.DOUBLE; case FLOAT: return ResourceParameterDataType.FLOAT; default: return ResourceParameterDataType.STRING; } } else if (type.isEnum()) { return ResourceParameterDataType.STRING; } else if (isFormParam()) { return ResourceParameterDataType.STRING; } else { //some _other_ kind of form; probably a file upload? if (getTypeName().contains("form")) { if (type.isInstanceOf(String.class)) { return ResourceParameterDataType.STRING; } else { return ResourceParameterDataType.FILE; } } else { return ResourceParameterDataType.STRING; } } } public PathContext getContext() { return context; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof ResourceParameter)) { return false; } if (!super.equals(o)) { return false; } ResourceParameter that = (ResourceParameter) o; return !(getParameterName() != null ? !getParameterName().equals(that.getParameterName()) : that.getParameterName() != null) && !(getTypeName() != null ? !getTypeName().equals(that.getTypeName()) : that.getTypeName() != null); } @Override public int hashCode() { String pName = getParameterName(); String tName = getTypeName(); int result = pName != null ? pName.hashCode() : 0; result = 31 * result + (tName != null ? tName.hashCode() : 0); return result; } @Override public int compareTo(ResourceParameter other) { return (this.getTypeName() + this.getParameterName()).compareTo(other.getTypeName() + other.getParameterName()); } private static class JaxRsResourceParameterPropertySpec extends ElementUtils.DefaultPropertySpec { JaxRsResourceParameterPropertySpec(DecoratedProcessingEnvironment env) { super(env); } @Override public boolean isGetter(DecoratedExecutableElement executable) { //JAX-RS considers non-public methods as potential properties, too: return executable.isGetter(); } @Override public boolean isSetter(DecoratedExecutableElement executable) { //JAX-RS considers non-public methods as potential properties, too: return executable.isSetter(); } } }