/** * 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.EnunciateException; import com.webcohesion.enunciate.api.ApiRegistry; import com.webcohesion.enunciate.module.*; import com.webcohesion.enunciate.modules.jaxrs.model.*; import com.webcohesion.enunciate.modules.jaxrs.model.util.MediaType; import com.webcohesion.enunciate.util.IgnoreUtils; import com.webcohesion.enunciate.util.PathSortStrategy; import org.reflections.adapters.MetadataAdapter; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.ws.rs.ApplicationPath; import javax.ws.rs.Path; import javax.ws.rs.ext.Provider; import java.util.*; import static com.webcohesion.enunciate.util.IgnoreUtils.isIgnored; /** * @author Ryan Heaton */ @SuppressWarnings ( "unchecked" ) public class JaxrsModule extends BasicProviderModule implements TypeDetectingModule, ApiRegistryProviderModule, ApiFeatureProviderModule { private DataTypeDetectionStrategy defaultDataTypeDetectionStrategy; private final List<MediaTypeDefinitionModule> mediaTypeModules = new ArrayList<MediaTypeDefinitionModule>(); private EnunciateJaxrsContext jaxrsContext; static final String NAME = "jaxrs"; private PathSortStrategy defaultSortStrategy = PathSortStrategy.breadth_first; @Override public String getName() { return NAME; } @Override public List<DependencySpec> getDependencySpecifications() { return Collections.singletonList((DependencySpec) new MediaTypeDependencySpec()); } public DataTypeDetectionStrategy getDataTypeDetectionStrategy() { String dataTypeDetection = this.config.getString("[@datatype-detection]", null); if (dataTypeDetection != null) { try { return DataTypeDetectionStrategy.valueOf(dataTypeDetection); } catch (IllegalArgumentException e) { //fall through... } } if (this.defaultDataTypeDetectionStrategy != null) { return this.defaultDataTypeDetectionStrategy; } if (this.enunciate.getIncludePatterns().isEmpty()) { //if there are no configured include patterns, then we'll just stick with "local" detection so we don't include too much. return DataTypeDetectionStrategy.local; } else { //otherwise, we'll assume the user knows what (s)he's doing and aggressively include everything. return DataTypeDetectionStrategy.aggressive; } } public void setDefaultDataTypeDetectionStrategy(DataTypeDetectionStrategy strategy) { this.defaultDataTypeDetectionStrategy = strategy; } public PathSortStrategy getPathSortStrategy() { PathSortStrategy strategy = defaultSortStrategy; try { strategy = PathSortStrategy.valueOf(this.config.getString("[@path-sort-strategy]", this.defaultSortStrategy.name())); } catch (IllegalArgumentException e) { // Ignore? Log? } return strategy; } public boolean isDisableExamples() { return this.config.getBoolean("[@disableExamples]", false); } public void setDefaultSortStrategy(PathSortStrategy defaultSortStrategy) { this.defaultSortStrategy = defaultSortStrategy; } public EnunciateJaxrsContext getJaxrsContext() { return jaxrsContext; } @Override public ApiRegistry getApiRegistry() { return new JaxrsApiRegistry(this.jaxrsContext); } @Override public void call(EnunciateContext context) { jaxrsContext = new EnunciateJaxrsContext(context, isDisableExamples()); DataTypeDetectionStrategy detectionStrategy = getDataTypeDetectionStrategy(); String relativeContextPath = ""; if (detectionStrategy != DataTypeDetectionStrategy.passive) { Set<? extends Element> elements = detectionStrategy == DataTypeDetectionStrategy.local ? context.getLocalApiElements() : context.getApiElements(); for (Element declaration : elements) { if (declaration instanceof TypeElement) { TypeElement element = (TypeElement) declaration; if ("org.glassfish.jersey.server.wadl.internal.WadlResource".equals(element.getQualifiedName().toString())) { //known internal wadl resource not to be documented. continue; } if (isIgnored(declaration)) { continue; } Path pathInfo = declaration.getAnnotation(Path.class); if (pathInfo != null) { //add root resource. RootResource rootResource = new RootResource(element, jaxrsContext); jaxrsContext.add(rootResource); LinkedList<Element> contextStack = new LinkedList<Element>(); contextStack.push(rootResource); try { for (ResourceMethod resourceMethod : rootResource.getResourceMethods(true)) { addReferencedDataTypeDefinitions(resourceMethod, contextStack); } } finally { contextStack.pop(); } } Provider providerInfo = declaration.getAnnotation(Provider.class); if (providerInfo != null) { //add jax-rs provider jaxrsContext.addJAXRSProvider(element); } ApplicationPath applicationPathInfo = declaration.getAnnotation(ApplicationPath.class); if (applicationPathInfo != null) { relativeContextPath = applicationPathInfo.value(); } } } } //tidy up the application path. relativeContextPath = this.config.getString("application[@path]", relativeContextPath); relativeContextPath = sanitizeContextPath(relativeContextPath); jaxrsContext.setRelativeContextPath(relativeContextPath); jaxrsContext.setGroupingStrategy(getGroupingStrategy()); jaxrsContext.setPathSortStrategy(getPathSortStrategy()); if (jaxrsContext.getRootResources().size() > 0) { this.enunciate.addArtifact(new JaxrsRootResourceClassListArtifact(this.jaxrsContext)); } if (this.jaxrsContext.getProviders().size() > 0) { this.enunciate.addArtifact(new JaxrsProviderClassListArtifact(this.jaxrsContext)); } } public static String sanitizeContextPath(String relativeContextPath) { while (relativeContextPath.startsWith("/")) { relativeContextPath = relativeContextPath.substring(1); } while (relativeContextPath.endsWith("/")) { //trim off any leading slashes relativeContextPath = relativeContextPath.substring(0, relativeContextPath.length() - 1); } return relativeContextPath; } /** * Add the referenced type definitions for the specified resource method. * * @param resourceMethod The resource method. */ protected void addReferencedDataTypeDefinitions(ResourceMethod resourceMethod, LinkedList<Element> contextStack) { if (IgnoreUtils.isIgnored(resourceMethod)) { return; } ResourceEntityParameter ep = resourceMethod.getEntityParameter(); if (ep != null) { Set<com.webcohesion.enunciate.modules.jaxrs.model.util.MediaType> consumesMt = resourceMethod.getConsumesMediaTypes(); Set<String> consumes = new TreeSet<String>(); for (MediaType mediaType : consumesMt) { consumes.add(mediaType.getMediaType()); } contextStack.push(ep.getDelegate()); TypeMirror type = ep.getType(); contextStack.push(resourceMethod); try { for (MediaTypeDefinitionModule mediaTypeModule : this.mediaTypeModules) { mediaTypeModule.addDataTypeDefinitions(type, consumes, contextStack); } } finally { contextStack.pop(); } } ResourceRepresentationMetadata outputPayload = resourceMethod.getRepresentationMetadata(); if (outputPayload != null) { TypeMirror type = outputPayload.getDelegate(); Set<com.webcohesion.enunciate.modules.jaxrs.model.util.MediaType> producesMt = resourceMethod.getProducesMediaTypes(); Set<String> produces = new TreeSet<String>(); for (MediaType mediaType : producesMt) { produces.add(mediaType.getMediaType()); } contextStack.push(resourceMethod); try { for (MediaTypeDefinitionModule mediaTypeModule : this.mediaTypeModules) { mediaTypeModule.addDataTypeDefinitions(type, produces, contextStack); } } finally { contextStack.pop(); } } List<? extends ResponseCode> statusCodes = resourceMethod.getStatusCodes(); if (statusCodes != null) { for (ResponseCode statusCode : statusCodes) { TypeMirror type = statusCode.getType(); if (type != null) { Set<com.webcohesion.enunciate.modules.jaxrs.model.util.MediaType> producesMt = resourceMethod.getProducesMediaTypes(); Set<String> produces = new TreeSet<String>(); for (MediaType mediaType : producesMt) { produces.add(mediaType.getMediaType()); } contextStack.push(resourceMethod); try { for (MediaTypeDefinitionModule mediaTypeModule : this.mediaTypeModules) { mediaTypeModule.addDataTypeDefinitions(type, produces, contextStack); } } finally { contextStack.pop(); } } } } } public EnunciateJaxrsContext.GroupingStrategy getGroupingStrategy() { String groupBy = this.config.getString("[@groupBy]", "class"); if ("class".equals(groupBy)) { return EnunciateJaxrsContext.GroupingStrategy.resource_class; } else if ("path".equals(groupBy)) { return EnunciateJaxrsContext.GroupingStrategy.path; } else if ("annotation".equals(groupBy)) { return EnunciateJaxrsContext.GroupingStrategy.annotation; } else { throw new EnunciateException("Unknown grouping strategy: " + groupBy); } } @Override public boolean typeDetected(Object type, MetadataAdapter metadata) { String classname = metadata.getClassName(type); if (classname.startsWith("org.glassfish.jersey") || classname.startsWith("com.sun.jersey") || classname.startsWith("org.jboss.resteasy") || classname.startsWith("org.apache.cxf")) { //don't accept jax-rs implementation-specific types return false; } List<String> classAnnotations = metadata.getClassAnnotationNames(type); if (classAnnotations != null) { for (String classAnnotation : classAnnotations) { if ((Path.class.getName().equals(classAnnotation)) || (Provider.class.getName().equals(classAnnotation)) || (ApplicationPath.class.getName().equals(classAnnotation))) { return true; } } } return false; } public class MediaTypeDependencySpec implements DependencySpec { @Override public boolean accept(EnunciateModule module) { if (module instanceof MediaTypeDefinitionModule) { MediaTypeDefinitionModule definitionModule = (MediaTypeDefinitionModule) module; mediaTypeModules.add(definitionModule); // suggest to the media type definition module that it should take a passive approach to detecting data types // because this module will be aggressively adding the data type definitions to it. definitionModule.setDefaultDataTypeDetectionStrategy(DataTypeDetectionStrategy.passive); return true; } return false; } @Override public boolean isFulfilled() { // this spec is always fulfilled. return true; } @Override public String toString() { return "media type definition modules"; } } }