/**
* 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;
import com.webcohesion.enunciate.EnunciateContext;
import com.webcohesion.enunciate.api.ApiRegistrationContext;
import com.webcohesion.enunciate.api.InterfaceDescriptionFile;
import com.webcohesion.enunciate.api.resources.Resource;
import com.webcohesion.enunciate.api.resources.ResourceGroup;
import com.webcohesion.enunciate.facets.FacetFilter;
import com.webcohesion.enunciate.javac.TypeElementComparator;
import com.webcohesion.enunciate.javac.decorations.type.DecoratedTypeMirror;
import com.webcohesion.enunciate.module.EnunciateModuleContext;
import com.webcohesion.enunciate.modules.jaxrs.api.impl.AnnotationBasedResourceGroupImpl;
import com.webcohesion.enunciate.modules.jaxrs.api.impl.PathBasedResourceGroupImpl;
import com.webcohesion.enunciate.modules.jaxrs.api.impl.ResourceClassResourceGroupImpl;
import com.webcohesion.enunciate.modules.jaxrs.api.impl.ResourceImpl;
import com.webcohesion.enunciate.modules.jaxrs.model.ResourceMethod;
import com.webcohesion.enunciate.modules.jaxrs.model.RootResource;
import com.webcohesion.enunciate.modules.jaxrs.model.util.JaxrsUtil;
import com.webcohesion.enunciate.util.*;
import org.apache.commons.configuration.HierarchicalConfiguration;
import javax.lang.model.element.TypeElement;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.*;
/**
* @author Ryan Heaton
*/
@SuppressWarnings ( "unchecked" )
public class EnunciateJaxrsContext extends EnunciateModuleContext {
public enum GroupingStrategy {
path,
annotation,
resource_class
}
private final Map<String, String> mediaTypeIds;
private final Set<RootResource> rootResources;
private final Set<TypeElement> providers;
private final Set<String> customResourceParameterAnnotations;
private final Set<String> systemResourceParameterAnnotations;
private String relativeContextPath = "";
private GroupingStrategy groupingStrategy = GroupingStrategy.resource_class;
private PathSortStrategy pathSortStrategy = PathSortStrategy.breadth_first;
private InterfaceDescriptionFile wadlFile = null;
private final boolean disableExamples;
public EnunciateJaxrsContext(EnunciateContext context, boolean disableExamples) {
super(context);
this.disableExamples = disableExamples;
this.mediaTypeIds = loadKnownMediaTypes();
this.rootResources = new TreeSet<RootResource>(new RootResourceComparator());
this.providers = new TreeSet<TypeElement>(new TypeElementComparator());
this.customResourceParameterAnnotations = loadKnownCustomResourceParameterAnnotations(context);
this.systemResourceParameterAnnotations = loadKnownSystemResourceParameterAnnotations(context);
}
protected Map<String, String> loadKnownMediaTypes() {
HashMap<String, String> mediaTypes = new HashMap<String, String>();
mediaTypes.put(MediaType.APPLICATION_ATOM_XML, "atom");
mediaTypes.put(MediaType.APPLICATION_FORM_URLENCODED, "form");
mediaTypes.put(MediaType.APPLICATION_JSON, "json");
mediaTypes.put(MediaType.APPLICATION_OCTET_STREAM, "bin");
mediaTypes.put(MediaType.APPLICATION_SVG_XML, "svg");
mediaTypes.put(MediaType.APPLICATION_XHTML_XML, "xhtml");
mediaTypes.put(MediaType.APPLICATION_XML, "xml");
mediaTypes.put(MediaType.MULTIPART_FORM_DATA, "multipart");
mediaTypes.put(MediaType.TEXT_HTML, "html");
mediaTypes.put(MediaType.TEXT_PLAIN, "text");
return mediaTypes;
}
protected Set<String> loadKnownCustomResourceParameterAnnotations(EnunciateContext context) {
TreeSet<String> customResourceParameterAnnotations = new TreeSet<String>();
//Jersey 1
customResourceParameterAnnotations.add("com.sun.jersey.multipart.FormDataParam");
//Jersey 2
customResourceParameterAnnotations.add("org.glassfish.jersey.media.multipart.FormDataParam");
//CXF
customResourceParameterAnnotations.add("org.apache.cxf.jaxrs.ext.multipart.Multipart");
//RESTEasy
//(none?)
//load the configured ones.
List<HierarchicalConfiguration> configuredParameterAnnotations = context.getConfiguration().getSource().configurationsAt("modules.jaxrs.custom-resource-parameter-annotation");
for (HierarchicalConfiguration configuredParameterAnnotation : configuredParameterAnnotations) {
String fqn = configuredParameterAnnotation.getString("[@qualifiedName]", null);
if (fqn != null) {
customResourceParameterAnnotations.add(fqn);
}
}
return customResourceParameterAnnotations;
}
protected Set<String> loadKnownSystemResourceParameterAnnotations(EnunciateContext context) {
TreeSet<String> systemResourceParameterAnnotations = new TreeSet<String>();
//JDK
systemResourceParameterAnnotations.add("javax.inject.Inject");
//Jersey
systemResourceParameterAnnotations.add("com.sun.jersey.api.core.InjectParam");
//CXF
//(none?)
//RESTEasy
//(none?)
//Spring
systemResourceParameterAnnotations.add("org.springframework.beans.factory.annotation.Autowired");
//load the configured ones.
List<HierarchicalConfiguration> configuredSystemAnnotations = context.getConfiguration().getSource().configurationsAt("modules.jaxrs.custom-system-parameter-annotation");
for (HierarchicalConfiguration configuredSystemAnnotation : configuredSystemAnnotations) {
String fqn = configuredSystemAnnotation.getString("[@qualifiedName]", null);
if (fqn != null) {
systemResourceParameterAnnotations.add(fqn);
}
}
return systemResourceParameterAnnotations;
}
public EnunciateContext getContext() {
return context;
}
public Map<String, String> getMediaTypeIds() {
//todo: configure media type ids?
return mediaTypeIds;
}
public boolean isDisableExamples() {
return disableExamples;
}
/**
* Add a content type.
*
* @param mediaType The content type to add.
*/
public void addMediaType(com.webcohesion.enunciate.modules.jaxrs.model.util.MediaType mediaType) {
if (!mediaTypeIds.containsKey(mediaType.getMediaType())) {
String id = getDefaultContentTypeId(mediaType.getMediaType());
if (id != null) {
mediaTypeIds.put(mediaType.getMediaType(), id);
}
}
}
/**
* Get the default content type id for the specified content type.
*
* @param contentType The content type.
* @return The default content type id, or null if the content type is a wildcard type.
*/
protected String getDefaultContentTypeId(String contentType) {
String id = contentType;
if (id.endsWith("/")) {
throw new IllegalArgumentException("Illegal content type: " + id);
}
int semiColon = id.indexOf(';');
if (semiColon > -1) {
id = id.substring(0, semiColon);
}
int lastSlash = id.lastIndexOf('/');
if (lastSlash > -1) {
id = id.substring(lastSlash + 1);
}
int plus = id.indexOf('+');
if (plus > -1) {
id = id.substring(0, plus);
}
if (id.contains("*")) {
//wildcard types have no ids.
return null;
}
else {
return id;
}
}
public Set<RootResource> getRootResources() {
return rootResources;
}
public Set<TypeElement> getProviders() {
return providers;
}
public Set<String> getCustomResourceParameterAnnotations() {
return this.customResourceParameterAnnotations;
}
public Set<String> getSystemResourceParameterAnnotations() {
return this.systemResourceParameterAnnotations;
}
/**
* Add a root resource to the model.
*
* @param rootResource The root resource to add to the model.
*/
public void add(RootResource rootResource) {
if (rootResource.isInterface()) {
//if the root resource is an interface, don't add it if its implementation has already been added (avoid duplication).
for (RootResource resource : this.rootResources) {
if (((DecoratedTypeMirror)(resource.asType())).isInstanceOf(rootResource)) {
debug("%s was identified as a JAX-RS root resource, but will be ignored because root resource %s implements it.", rootResource.getQualifiedName(), resource.getQualifiedName());
return;
}
}
}
else {
//remove any interfaces of this root resource that have been identified as root resources (avoid duplication)
DecoratedTypeMirror rootResourceType = (DecoratedTypeMirror) rootResource.asType();
Iterator<RootResource> it = this.rootResources.iterator();
while (it.hasNext()) {
RootResource resource = it.next();
if (rootResourceType.isInstanceOf(resource)) {
debug("%s was identified as a JAX-RS root resource, but will be ignored because root resource %s implements it.", resource.getQualifiedName(), rootResource.getQualifiedName());
it.remove();
}
}
}
this.rootResources.add(rootResource);
debug("Added %s as a JAX-RS root resource.", rootResource.getQualifiedName());
if (getContext().getProcessingEnvironment().findSourcePosition(rootResource) == null) {
OneTimeLogMessage.SOURCE_FILES_NOT_FOUND.log(getContext());
debug("Unable to find source file for %s.", rootResource.getQualifiedName());
}
}
/**
* Add a JAX-RS provider to the model.
*
* @param declaration The declaration of the provider.
*/
public void addJAXRSProvider(TypeElement declaration) {
this.providers.add(declaration);
debug("Added %s as a JAX-RS provider.", declaration.getQualifiedName());
Produces produces = declaration.getAnnotation(Produces.class);
if (produces != null) {
for (com.webcohesion.enunciate.modules.jaxrs.model.util.MediaType contentType : JaxrsUtil.value(produces)) {
addMediaType(contentType);
}
}
Consumes consumes = declaration.getAnnotation(Consumes.class);
if (consumes != null) {
for (com.webcohesion.enunciate.modules.jaxrs.model.util.MediaType contentType : JaxrsUtil.value(consumes)) {
addMediaType(contentType);
}
}
}
public boolean isIncludeResourceGroupName() {
return this.groupingStrategy != GroupingStrategy.path;
}
public void setRelativeContextPath(String relativeContextPath) {
this.relativeContextPath = relativeContextPath;
}
public void setGroupingStrategy(GroupingStrategy groupingStrategy) {
this.groupingStrategy = groupingStrategy;
}
public PathSortStrategy getPathSortStrategy() {
return pathSortStrategy;
}
public void setPathSortStrategy(PathSortStrategy pathSortStrategy) {
this.pathSortStrategy = pathSortStrategy;
}
public InterfaceDescriptionFile getWadlFile() {
return wadlFile;
}
public void setWadlFile(InterfaceDescriptionFile wadlFile) {
this.wadlFile = wadlFile;
}
public List<ResourceGroup> getResourceGroups(ApiRegistrationContext registrationContext) {
List<ResourceGroup> resourceGroups;
if (this.groupingStrategy == GroupingStrategy.path) {
//group resources by path.
resourceGroups = getResourceGroupsByPath(registrationContext);
}
else if (this.groupingStrategy == GroupingStrategy.annotation) {
resourceGroups = getResourceGroupsByAnnotation(registrationContext);
}
else {
resourceGroups = getResourceGroupsByClass(registrationContext);
}
Collections.sort(resourceGroups, new Comparator<ResourceGroup>() {
@Override
public int compare(ResourceGroup o1, ResourceGroup o2) {
return o1.getLabel().compareTo(o2.getLabel());
}
});
return resourceGroups;
}
public List<ResourceGroup> getResourceGroupsByClass(ApiRegistrationContext registrationContext) {
List<ResourceGroup> resourceGroups = new ArrayList<ResourceGroup>();
Set<String> slugs = new TreeSet<String>();
FacetFilter facetFilter = context.getConfiguration().getFacetFilter();
for (RootResource rootResource : rootResources) {
if (!facetFilter.accept(rootResource)) {
continue;
}
String slug = rootResource.getSimpleName().toString();
if (slugs.contains(slug)) {
slug = "";
String[] qualifiedNameTokens = rootResource.getQualifiedName().toString().split("\\.");
for (int i = qualifiedNameTokens.length - 1; i >= 0; i--) {
slug = slug.isEmpty() ? qualifiedNameTokens[i] : slug + "_" + qualifiedNameTokens[i];
if (!slugs.contains(slug)) {
break;
}
}
}
slugs.add(slug);
com.webcohesion.enunciate.metadata.rs.ServiceContextRoot context = rootResource.getAnnotation(com.webcohesion.enunciate.metadata.rs.ServiceContextRoot.class);
com.webcohesion.enunciate.modules.jaxrs.model.Resource resource = rootResource.getParent();
while (context == null && resource != null) {
context = resource.getAnnotation(com.webcohesion.enunciate.metadata.rs.ServiceContextRoot.class);
resource = resource.getParent();
}
String contextPath = context != null ? JaxrsModule.sanitizeContextPath(context.value()) : this.relativeContextPath;
ResourceGroup group = new ResourceClassResourceGroupImpl(rootResource, slug, contextPath, registrationContext);
if (!group.getResources().isEmpty()) {
resourceGroups.add(group);
}
}
Collections.sort(resourceGroups, new ResourceGroupComparator(this.pathSortStrategy));
return resourceGroups;
}
public List<ResourceGroup> getResourceGroupsByPath(ApiRegistrationContext registrationContext) {
Map<String, PathBasedResourceGroupImpl> resourcesByPath = new HashMap<String, PathBasedResourceGroupImpl>();
FacetFilter facetFilter = context.getConfiguration().getFacetFilter();
for (RootResource rootResource : rootResources) {
if (!facetFilter.accept(rootResource)) {
continue;
}
for (ResourceMethod method : rootResource.getResourceMethods(true)) {
if (facetFilter.accept(method)) {
com.webcohesion.enunciate.metadata.rs.ServiceContextRoot context = method.getAnnotation(com.webcohesion.enunciate.metadata.rs.ServiceContextRoot.class);
com.webcohesion.enunciate.modules.jaxrs.model.Resource resource = method.getParent();
while (context == null && resource != null) {
context = resource.getAnnotation(com.webcohesion.enunciate.metadata.rs.ServiceContextRoot.class);
resource = resource.getParent();
}
String path = method.getFullpath();
PathBasedResourceGroupImpl resourceGroup = resourcesByPath.get(path);
if (resourceGroup == null) {
String contextPath = context != null ? JaxrsModule.sanitizeContextPath(context.value()) : this.relativeContextPath;
resourceGroup = new PathBasedResourceGroupImpl(contextPath, path, new ArrayList<Resource>());
resourcesByPath.put(path, resourceGroup);
}
resourceGroup.getResources().add(new ResourceImpl(method, resourceGroup, registrationContext));
}
}
}
ArrayList<ResourceGroup> resourceGroups = new ArrayList<ResourceGroup>(resourcesByPath.values());
Collections.sort(resourceGroups, new ResourceGroupComparator(this.pathSortStrategy));
return resourceGroups;
}
public List<ResourceGroup> getResourceGroupsByAnnotation(ApiRegistrationContext registrationContext) {
Map<String, AnnotationBasedResourceGroupImpl> resourcesByAnnotation = new HashMap<String, AnnotationBasedResourceGroupImpl>();
FacetFilter facetFilter = context.getConfiguration().getFacetFilter();
for (RootResource rootResource : rootResources) {
if (!facetFilter.accept(rootResource)) {
continue;
}
for (ResourceMethod method : rootResource.getResourceMethods(true)) {
if (facetFilter.accept(method)) {
com.webcohesion.enunciate.metadata.rs.ResourceGroup annotation = method.getAnnotation(com.webcohesion.enunciate.metadata.rs.ResourceGroup.class);
com.webcohesion.enunciate.modules.jaxrs.model.Resource resource = method.getParent();
while (annotation == null && resource != null) {
annotation = resource.getAnnotation(com.webcohesion.enunciate.metadata.rs.ResourceGroup.class);
resource = resource.getParent();
}
com.webcohesion.enunciate.metadata.rs.ServiceContextRoot context = method.getAnnotation(com.webcohesion.enunciate.metadata.rs.ServiceContextRoot.class);
resource = method.getParent();
while (context == null && resource != null) {
context = resource.getAnnotation(com.webcohesion.enunciate.metadata.rs.ServiceContextRoot.class);
resource = resource.getParent();
}
String label = annotation == null ? "Other" : annotation.value();
AnnotationBasedResourceGroupImpl resourceGroup = resourcesByAnnotation.get(label);
if (resourceGroup == null) {
String contextPath = context != null ? JaxrsModule.sanitizeContextPath(context.value()) : this.relativeContextPath;
resourceGroup = new AnnotationBasedResourceGroupImpl(contextPath, label, new SortedList<Resource>(new ResourceComparator(this.pathSortStrategy)), this.pathSortStrategy);
resourcesByAnnotation.put(label, resourceGroup);
}
resourceGroup.getResources().add(new ResourceImpl(method, resourceGroup, registrationContext));
}
}
}
ArrayList<ResourceGroup> resourceGroups = new ArrayList<ResourceGroup>(resourcesByAnnotation.values());
Collections.sort(resourceGroups, new ResourceGroupComparator(this.pathSortStrategy));
return resourceGroups;
}
private static class RootResourceComparator implements Comparator<RootResource> {
@Override
public int compare(RootResource r1, RootResource r2) {
String key1 = r1.getPath() + r1.getQualifiedName();
String key2 = r2.getPath() + r2.getQualifiedName();
return key1.compareTo(key2);
}
}
}