/**
* 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.facets.Facet;
import com.webcohesion.enunciate.facets.HasFacets;
import com.webcohesion.enunciate.javac.decorations.element.DecoratedTypeElement;
import com.webcohesion.enunciate.javac.decorations.element.PropertyElement;
import com.webcohesion.enunciate.javac.decorations.type.TypeVariableContext;
import com.webcohesion.enunciate.modules.jaxrs.EnunciateJaxrsContext;
import com.webcohesion.enunciate.modules.jaxrs.model.util.JaxrsUtil;
import com.webcohesion.enunciate.util.IgnoreUtils;
import javax.annotation.security.RolesAllowed;
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.lang.model.util.Elements;
import javax.ws.rs.Consumes;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import java.util.*;
/**
* A JAX-RS resource.
*
* @author Ryan Heaton
*/
public abstract class Resource extends DecoratedTypeElement implements HasFacets, PathContext {
private final EnunciateJaxrsContext context;
private final String path;
private final List<PathSegment> pathComponents;
private final Set<com.webcohesion.enunciate.modules.jaxrs.model.util.MediaType> consumesMime;
private final Set<com.webcohesion.enunciate.modules.jaxrs.model.util.MediaType> producesMime;
private final Set<ResourceParameter> resourceParameters;
private final List<ResourceMethod> resourceMethods;
private final List<SubResourceLocator> resourceLocators;
private final Set<Facet> facets = new TreeSet<Facet>();
protected Resource(TypeElement delegate, String path, EnunciateJaxrsContext context) {
super(delegate, context.getContext().getProcessingEnvironment());
this.context = context;
if (path == null) {
throw new NullPointerException();
}
this.path = path;
this.pathComponents = extractPathComponents(path);
Set<com.webcohesion.enunciate.modules.jaxrs.model.util.MediaType> consumes = new TreeSet<com.webcohesion.enunciate.modules.jaxrs.model.util.MediaType>();
Consumes consumesInfo = delegate.getAnnotation(Consumes.class);
if (consumesInfo != null) {
consumes.addAll(JaxrsUtil.value(consumesInfo));
}
else {
consumes.add(new com.webcohesion.enunciate.modules.jaxrs.model.util.MediaType("*/*", 1.0F));
}
this.consumesMime = Collections.unmodifiableSet(consumes);
Set<com.webcohesion.enunciate.modules.jaxrs.model.util.MediaType> produces = new TreeSet<com.webcohesion.enunciate.modules.jaxrs.model.util.MediaType>();
Produces producesInfo = delegate.getAnnotation(Produces.class);
if (producesInfo != null) {
produces.addAll(JaxrsUtil.value(producesInfo));
}
else {
produces.add(new com.webcohesion.enunciate.modules.jaxrs.model.util.MediaType("*/*", 1.0F));
}
this.producesMime = Collections.unmodifiableSet(produces);
this.facets.addAll(Facet.gatherFacets(delegate, context.getContext()));
this.resourceParameters = Collections.unmodifiableSet(getResourceParameters(delegate, context));
this.resourceMethods = Collections.unmodifiableList(getResourceMethods(delegate, new TypeVariableContext(), context));
this.resourceLocators = Collections.unmodifiableList(getSubresourceLocators(delegate, context));
}
/**
* Get the sub-resource locators for the specified type.
*
* @param delegate The type.
* @param context The context
* @return The sub-resource locators.
*/
protected List<SubResourceLocator> getSubresourceLocators(TypeElement delegate, EnunciateJaxrsContext context) {
if (delegate == null || delegate.getQualifiedName().toString().equals(Object.class.getName())) {
return Collections.emptyList();
}
ArrayList<SubResourceLocator> resourceLocators = new ArrayList<SubResourceLocator>();
METHOD_LOOP : for (ExecutableElement methodElement : ElementFilter.methodsIn(delegate.getEnclosedElements())) {
if (methodElement.getAnnotation(Path.class) != null) { //sub-resource locators are annotated with @Path AND they have no resource method designator.
for (AnnotationMirror annotation : methodElement.getAnnotationMirrors()) {
Element annotationElement = annotation.getAnnotationType().asElement();
if (annotationElement != null) {
if (annotationElement.getAnnotation(HttpMethod.class) != null) {
continue METHOD_LOOP;
}
}
}
resourceLocators.add(new SubResourceLocator(methodElement, this, this.context));
}
}
//some methods may be specified by a superclass and/or implemented interface. But the annotations on the current class take precedence.
for (TypeMirror superType : delegate.getInterfaces()) {
if (superType instanceof DeclaredType) {
List<SubResourceLocator> interfaceMethods = getSubresourceLocators((TypeElement) ((DeclaredType)superType).asElement(), context);
for (SubResourceLocator interfaceMethod : interfaceMethods) {
if (!isOverridden(interfaceMethod, resourceLocators)) {
resourceLocators.add(interfaceMethod);
}
}
}
}
if (delegate.getKind() == ElementKind.CLASS) {
TypeMirror superclass = delegate.getSuperclass();
if (superclass instanceof DeclaredType && ((DeclaredType)superclass).asElement() != null) {
List<SubResourceLocator> superMethods = getSubresourceLocators((TypeElement) ((DeclaredType) superclass).asElement(), context);
for (SubResourceLocator superMethod : superMethods) {
if (!isOverridden(superMethod, resourceLocators)) {
resourceLocators.add(superMethod);
}
}
}
}
return resourceLocators;
}
/**
* Get all the resource methods for the specified type.
*
* @param delegate The type.
* @param context The context
* @return The resource methods.
*/
protected List<ResourceMethod> getResourceMethods(final TypeElement delegate, TypeVariableContext variableContext, EnunciateJaxrsContext context) {
if (delegate == null || delegate.getQualifiedName().toString().equals(Object.class.getName())) {
return Collections.emptyList();
}
ArrayList<ResourceMethod> resourceMethods = new ArrayList<ResourceMethod>();
for (ExecutableElement method : ElementFilter.methodsIn(delegate.getEnclosedElements())) {
if (IgnoreUtils.isIgnored(method)) {
continue;
}
if (method.getModifiers().contains(Modifier.PUBLIC)) {
for (AnnotationMirror annotation : method.getAnnotationMirrors()) {
Element annotationElement = annotation.getAnnotationType().asElement();
if (annotationElement != null) {
if (annotationElement.getAnnotation(HttpMethod.class) != null) {
resourceMethods.add(new ResourceMethod(method, this, variableContext, context));
break;
}
}
}
}
}
//some methods may be specified by a superclass and/or implemented interface. But the annotations on the current class take precedence.
for (TypeMirror interfaceType : delegate.getInterfaces()) {
if (interfaceType instanceof DeclaredType) {
DeclaredType declared = (DeclaredType) interfaceType;
TypeElement element = (TypeElement) declared.asElement();
List<ResourceMethod> interfaceMethods = getResourceMethods(element, variableContext.push(element.getTypeParameters(), declared.getTypeArguments()), context);
for (ResourceMethod interfaceMethod : interfaceMethods) {
if (!isOverridden(interfaceMethod, resourceMethods)) {
resourceMethods.add(interfaceMethod);
}
}
}
}
if (delegate.getKind() == ElementKind.CLASS) {
TypeMirror superclass = delegate.getSuperclass();
if (superclass instanceof DeclaredType && ((DeclaredType)superclass).asElement() != null) {
DeclaredType declared = (DeclaredType) superclass;
TypeElement element = (TypeElement) declared.asElement();
List<ResourceMethod> superMethods = getResourceMethods(element, variableContext.push(element.getTypeParameters(), declared.getTypeArguments()), context);
for (ResourceMethod superMethod : superMethods) {
if (!isOverridden(superMethod, resourceMethods)) {
resourceMethods.add(superMethod);
}
}
}
}
return resourceMethods;
}
/**
* Get the resource parameters for the specified delegate.
*
* @param delegate The delegate.
* @param context The context
* @return The resource parameters.
*/
protected Set<ResourceParameter> getResourceParameters(TypeElement delegate, EnunciateJaxrsContext context) {
if (delegate == null || delegate.getQualifiedName().toString().equals(Object.class.getName())) {
return Collections.emptySet();
}
Set<ResourceParameter> resourceParameters = new TreeSet<ResourceParameter>();
for (VariableElement field : ElementFilter.fieldsIn(delegate.getEnclosedElements())) {
if (ResourceParameter.isResourceParameter(field, this.context)) {
resourceParameters.add(new ResourceParameter(field, this));
}
}
for (PropertyElement property : ((DecoratedTypeElement)delegate).getProperties()) {
if (ResourceParameter.isResourceParameter(property, this.context)) {
resourceParameters.add(new ResourceParameter(property, this));
}
}
if (delegate.getKind() == ElementKind.CLASS && delegate.getSuperclass() instanceof DeclaredType) {
Set<ResourceParameter> superParams = getResourceParameters((TypeElement) ((DeclaredType) delegate.getSuperclass()).asElement(), context);
for (ResourceParameter superParam : superParams) {
if (!isHidden(superParam, resourceParameters)) {
resourceParameters.add(superParam);
}
}
}
return resourceParameters;
}
/**
* Whether the specified method is overridden by any of the methods in the specified list.
*
* @param method The method.
* @param resourceMethods The method list.
* @return If the methdo is overridden by any of the methods in the list.
*/
protected boolean isOverridden(ExecutableElement method, ArrayList<? extends ExecutableElement> resourceMethods) {
Elements decls = this.env.getElementUtils();
for (ExecutableElement resourceMethod : resourceMethods) {
if (decls.overrides(resourceMethod, method, (TypeElement) resourceMethod.getEnclosingElement())) {
return true;
}
}
return false;
}
/**
* Whether the specified resource parameter is hidden by any of the parameters in the specified list.
*
* @param param The param to test.
* @param resourceParameters The other parameters.
* @return If the parameter is hidden by any of the parameters in the specified list.
*/
private boolean isHidden(ResourceParameter param, Set<ResourceParameter> resourceParameters) {
Elements decls = this.env.getElementUtils();
for (ResourceParameter resourceParameter : resourceParameters) {
if (decls.hides(resourceParameter, param)) {
return true;
}
}
return false;
}
public EnunciateJaxrsContext getContext() {
return context;
}
/**
* Extracts out the components of a path.
*
* @param path The path.
*/
protected static List<PathSegment> extractPathComponents(String path) {
List<PathSegment> components = new ArrayList<PathSegment>();
if (path != null) {
int inBrace = 0;
boolean definingRegexp = false;
StringBuilder name = new StringBuilder();
StringBuilder regexp = new StringBuilder();
for (int i = 0; i < path.length(); i++) {
char ch = path.charAt(i);
if (ch == '{') {
inBrace++;
}
else if (ch == '}') {
inBrace--;
if (inBrace == 0) {
definingRegexp = false;
}
}
else if (inBrace == 1 && ch == ':') {
definingRegexp = true;
continue;
}
if (definingRegexp) {
regexp.append(ch);
}
else if (!Character.isWhitespace(ch) && ch != '/') {
name.append(ch);
}
if (i + 1 == path.length() || (ch == '/' && !definingRegexp)) {
String trimmed = name.toString().trim();
if (!trimmed.isEmpty()) {
components.add(new PathSegment(trimmed, regexp.length() > 0 ? regexp.toString().trim() : null));
}
name = new StringBuilder();
regexp = new StringBuilder();
}
}
}
return components;
}
/**
* The path to this resource.
*
* @return The path to this resource.
*/
public final String getPath() {
return this.path;
}
/**
* The path components for this resource.
*
* @return The path components for this resource.
*/
public List<PathSegment> getPathComponents() {
List<PathSegment> components = new ArrayList<PathSegment>();
Resource parent = getParent();
if (parent != null) {
components.addAll(parent.getPathComponents());
}
components.addAll(this.pathComponents);
return components;
}
/**
* The parent resource.
*
* @return The parent resource, or null if this is a root resource.
*/
public abstract Resource getParent();
/**
* The MIME types that the methods on this resource consumes (possibly overridden).
*
* @return The MIME types that the methods on this resource consumes.
*/
public Set<com.webcohesion.enunciate.modules.jaxrs.model.util.MediaType> getConsumesMediaTypes() {
return consumesMime;
}
/**
* The MIME types that the methods on this resource consumes (possibly overridden).
*
* @return The MIME types that the methods on this resource consumes.
*/
public Set<com.webcohesion.enunciate.modules.jaxrs.model.util.MediaType> getProducesMediaTypes() {
return producesMime;
}
/**
* The resource parameters.
*
* @return The resource parameters.
*/
public Set<ResourceParameter> getResourceParameters() {
return resourceParameters;
}
/**
* The resource methods.
*
* @return The resource methods.
*/
public List<ResourceMethod> getResourceMethods() {
return resourceMethods;
}
/**
* The resource methods.
*
* @param loadDescendants Whether to include the resource methods of all sub-resources.
* @return The resource methods.
*/
public List<ResourceMethod> getResourceMethods(boolean loadDescendants) {
if (!loadDescendants) {
return resourceMethods;
}
else {
List<ResourceMethod> resourceMethods = new ArrayList<ResourceMethod>();
LinkedList<Resource> resources = new LinkedList<Resource>();
Set<String> visited = new TreeSet<String>();
resources.add(this);
while (!resources.isEmpty()) {
Resource resource = resources.pop();
visited.add(resource.getQualifiedName().toString());
resourceMethods.addAll(resource.getResourceMethods());
for (SubResourceLocator locator : resource.getResourceLocators()) {
SubResource subresource = locator.getResource();
if (!visited.contains(subresource.getQualifiedName().toString())) {
resources.add(subresource);
}
}
}
return resourceMethods;
}
}
/**
* The resource locators.
*
* @return The resource locators.
*/
public List<SubResourceLocator> getResourceLocators() {
return resourceLocators;
}
/**
* The facets here applicable.
*
* @return The facets here applicable.
*/
public Set<Facet> getFacets() {
return facets;
}
/**
* The security roles for this resource.
*
* @return The security roles for this resource.
*/
public Set<String> getSecurityRoles() {
TreeSet<String> roles = new TreeSet<String>();
RolesAllowed rolesAllowed = getAnnotation(RolesAllowed.class);
if (rolesAllowed != null) {
Collections.addAll(roles, rolesAllowed.value());
}
Resource parent = getParent();
if (parent != null) {
roles.addAll(parent.getSecurityRoles());
}
return roles;
}
}