/** * Copyright 2005-2014 Restlet * * The contents of this file are subject to the terms of one of the following * open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can * select the license that you prefer but you may not use this file except in * compliance with one of these Licenses. * * You can obtain a copy of the Apache 2.0 license at * http://www.opensource.org/licenses/apache-2.0 * * You can obtain a copy of the EPL 1.0 license at * http://www.opensource.org/licenses/eclipse-1.0 * * See the Licenses for the specific language governing permissions and * limitations under the Licenses. * * Alternatively, you can obtain a royalty free commercial license with less * limitations, transferable or non-transferable, directly at * http://restlet.com/products/restlet-framework * * Restlet is a registered trademark of Restlet S.A.S. */ package org.restlet.ext.apispark.internal.introspection.jaxrs; import java.beans.BeanInfo; import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import javax.ws.rs.ApplicationPath; import javax.ws.rs.Consumes; import javax.ws.rs.CookieParam; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.HEAD; import javax.ws.rs.HeaderParam; import javax.ws.rs.HttpMethod; import javax.ws.rs.MatrixParam; import javax.ws.rs.OPTIONS; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Application; import javax.ws.rs.core.Context; import org.restlet.data.Reference; import org.restlet.data.Status; import org.restlet.engine.util.BeanInfoUtils; import org.restlet.engine.util.StringUtils; import org.restlet.ext.apispark.internal.introspection.DocumentedApplication; import org.restlet.ext.apispark.internal.introspection.IntrospectionHelper; import org.restlet.ext.apispark.internal.introspection.util.TypeInfo; import org.restlet.ext.apispark.internal.introspection.util.Types; import org.restlet.ext.apispark.internal.introspection.util.UnsupportedTypeException; import org.restlet.ext.apispark.internal.model.Contract; import org.restlet.ext.apispark.internal.model.Definition; import org.restlet.ext.apispark.internal.model.Endpoint; import org.restlet.ext.apispark.internal.model.Header; import org.restlet.ext.apispark.internal.model.Operation; import org.restlet.ext.apispark.internal.model.PathVariable; import org.restlet.ext.apispark.internal.model.PayLoad; import org.restlet.ext.apispark.internal.model.Property; import org.restlet.ext.apispark.internal.model.QueryParameter; import org.restlet.ext.apispark.internal.model.Representation; import org.restlet.ext.apispark.internal.model.Resource; import org.restlet.ext.apispark.internal.model.Response; import org.restlet.ext.apispark.internal.model.Section; import org.restlet.ext.apispark.internal.reflect.ReflectUtils; import org.restlet.ext.apispark.internal.utils.IntrospectionUtils; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.databind.introspect.AnnotatedClass; import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; /** * Publish the documentation of a Jaxrs-based Application to the APISpark * console. * * @author Thierry Boileau */ public class JaxRsIntrospector extends IntrospectionUtils { private static class ClazzInfo { private Class<?> clazz; private Consumes consumes; // List of common annotations, defined at the level of the class, or // at the level of the fields. private Map<String, Header> headers = new LinkedHashMap<>(); private Path path; private Map<String, PathVariable> pathVariables = new LinkedHashMap<>(); private Produces produces; private Map<String, QueryParameter> queryParameters = new LinkedHashMap<>(); private Resource resource; public void addHeader(Header header) { headers.put(header.getName(), header); } public void addPathVariable(PathVariable pathVariable) { pathVariables.put(pathVariable.getName(), pathVariable); } public void addQueryParameter(QueryParameter queryParameter) { queryParameters.put(queryParameter.getName(), queryParameter); } public Class<?> getClazz() { return clazz; } public Consumes getConsumes() { return consumes; } public Map<String, Header> getHeadersCopy() { return new LinkedHashMap<>(headers); } public Path getPath() { return path; } public Map<String, PathVariable> getPathVariablesCopy() { return new LinkedHashMap<>(pathVariables); } public Produces getProduces() { return produces; } public Map<String, QueryParameter> getQueryParametersCopy() { return new LinkedHashMap<>(queryParameters); } @SuppressWarnings("unused") public Resource getResource() { return resource; } public void setClazz(Class<?> clazz) { this.clazz = clazz; } public void setConsumes(Consumes consumes) { this.consumes = consumes; } public void setPath(Path path) { this.path = path; } public void setProduces(Produces produces) { this.produces = produces; } @SuppressWarnings("unused") public void setResource(Resource resource) { this.resource = resource; } } public static class CollectInfo { private String applicationName; private String applicationPath; private Map<String, Representation> representations = new HashMap<>(); private Map<String, Resource> resourcesByPath = new LinkedHashMap<>(); private Map<String, Section> sections = new HashMap<>(); private boolean useSectionNamingPackageStrategy; public void addRepresentation(Representation representation) { representations.put(representation.getName(), representation); } public void addResource(Resource resource) { resourcesByPath.put(resource.getResourcePath(), resource); } public void addSection(Section section) { sections.put(section.getName(), section); } public String getApplicationName() { return applicationName; } public void setApplicationName(String applicationName) { this.applicationName = applicationName; } public String getApplicationPath() { return applicationPath; } public Representation getRepresentation(String identifier) { return representations.get(identifier); } public List<Representation> getRepresentations() { return new ArrayList<>(representations.values()); } public Resource getResource(String operationPath) { return resourcesByPath.get(operationPath); } public List<Resource> getResources() { return new ArrayList<>(resourcesByPath.values()); } public Section getSection(String identifier) { return sections.get(identifier); } public List<Section> getSections() { return new ArrayList<>(sections.values()); } public boolean isUseSectionNamingPackageStrategy() { return useSectionNamingPackageStrategy; } public void setApplicationPath(String applicationPath) { this.applicationPath = applicationPath; } public void setSections(Map<String, Section> sections) { this.sections = sections; } public void setUseSectionNamingPackageStrategy( boolean useSectionNamingPackageStrategy) { this.useSectionNamingPackageStrategy = useSectionNamingPackageStrategy; } } /** Internal logger. */ protected static Logger LOGGER = Logger.getLogger(JaxRsIntrospector.class .getName()); private static final String SUFFIX_RESOURCE = "Resource"; private static final String SUFFIX_SERVER_RESOURCE = "ServerResource"; private static void addEndpoints(String applicationPath, Definition definition) { if (applicationPath != null) { Endpoint endpoint = new Endpoint(applicationPath); definition.getEndpoints().add(endpoint); } } private static void addRepresentation(CollectInfo collectInfo, TypeInfo typeInfo, List<? extends IntrospectionHelper> introspectionHelper) { // Introspect the java class Representation representation = new Representation(); representation.setDescription(""); if (typeInfo.isList()) { // Collect generic type addRepresentation(collectInfo, typeInfo.getComponentTypeInfo(), introspectionHelper); return; } if (typeInfo.isPrimitive() || typeInfo.isFile()) { // primitives and files are not collected return; } // Example: "java.util.Contact" or "String" representation.setDescription("Java type: " + typeInfo.getRepresentationClazz().getName()); // Sections String packageName = typeInfo.getClazz().getPackage().getName(); representation.getSections().add(packageName); if (collectInfo.getSection(packageName) == null) { collectInfo.addSection(new Section(packageName)); } // Example: "Contact" JsonRootName jsonType = typeInfo.getClazz().getAnnotation( JsonRootName.class); String typeName = jsonType == null ? typeInfo.getRepresentationClazz() .getSimpleName() : jsonType.value(); representation.setName(typeName); representation.setRaw(false); // at this point, identifier is known - we check if it exists in cache boolean notInCache = collectInfo.getRepresentation(representation .getName()) == null; if (notInCache) { // add representation in cache before complete it to avoid infinite // loop collectInfo.addRepresentation(representation); if (typeInfo.isPojo()) { // add properties definition BeanInfo beanInfo = BeanInfoUtils.getBeanInfo(typeInfo .getRepresentationClazz()); JsonIgnoreProperties jsonIgnorePropertiesAnnotation = AnnotatedClass .construct(typeInfo.getRepresentationClazz(), new JacksonAnnotationIntrospector(), null) .getAnnotation(JsonIgnoreProperties.class); List<String> jsonIgnoreProperties = jsonIgnorePropertiesAnnotation == null ? null : Arrays.asList(jsonIgnorePropertiesAnnotation.value()); for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) { if (jsonIgnoreProperties != null && jsonIgnoreProperties.contains(pd.getName())) { // ignore this field continue; } JsonIgnore jsonIgnore = pd.getReadMethod().getAnnotation( JsonIgnore.class); if (jsonIgnore != null && jsonIgnore.value()) { // ignore this field continue; } TypeInfo propertyTypeInfo; try { propertyTypeInfo = Types.getTypeInfo(pd.getReadMethod() .getReturnType(), pd.getReadMethod() .getGenericReturnType()); } catch (UnsupportedTypeException e) { LOGGER.warning("Could not add property " + pd.getName() + " of representation " + typeInfo.getRepresentationClazz().getName() + ". " + e.getMessage()); continue; } JsonProperty jsonProperty = pd.getReadMethod() .getAnnotation(JsonProperty.class); String propertyName = jsonProperty != null && !StringUtils.isNullOrEmpty(jsonProperty.value()) ? jsonProperty .value() : pd.getName(); JsonPropertyDescription jsonPropertyDescription = pd .getReadMethod().getAnnotation( JsonPropertyDescription.class); Property property = new Property(); property.setName(propertyName); property.setDescription(jsonPropertyDescription != null ? jsonPropertyDescription .value() : ""); property.setType(propertyTypeInfo.getRepresentationName()); property.setMinOccurs(jsonProperty != null && jsonProperty.required() ? 1 : 0); property.setMaxOccurs(propertyTypeInfo.isList() ? -1 : 1); addRepresentation(collectInfo, propertyTypeInfo, introspectionHelper); for (IntrospectionHelper helper : introspectionHelper) { helper.processProperty(property, pd.getReadMethod()); } representation.getProperties().add(property); } } for (IntrospectionHelper helper : introspectionHelper) { helper.processRepresentation(representation, typeInfo.getRepresentationClazz()); } } } /** * Returns a clean path (especially variables are cleaned from routing * regexp). * * @param path * The path to clean. * @return The cleand path. */ private static String cleanPath(String path) { if (path != null) { StringBuilder sb = new StringBuilder(); char next; boolean inVariable = false; boolean endVariable = false; StringBuilder varBuffer = null; for (int i = 0; i < path.length(); i++) { next = path.charAt(i); if (inVariable) { if (next == '}') { // End of variable detected if (varBuffer.length() == 0) { LOGGER.warning("Empty pattern variables are not allowed : " + path); } else { sb.append(varBuffer.toString()); // Reset the variable name buffer varBuffer = new StringBuilder(); } endVariable = false; inVariable = false; sb.append(next); } else if (endVariable) { continue; } else if (Reference.isUnreserved(next)) { // Append to the variable name varBuffer.append(next); } else if (next == ':') { // In this case, the following is the regexp that helps // routing requests // TODO in the future, use the following as roles for // controlling the values of the variables. endVariable = true; } } else { sb.append(next); if (next == '{') { inVariable = true; varBuffer = new StringBuilder(); } else if (next == '}') { LOGGER.warning("An invalid character was detected inside a pattern variable : " + path); } } } return sb.toString(); } return null; } /** * Returns an instance of what must be a subclass of {@link Application}. * Returns null in case of errors. * * @param className * The name of the application class. * @return An instance of what must be a subclass of {@link Application}. */ public static Application getApplication(String className) { return ReflectUtils.newInstance(className, Application.class); } /** * Constructor. * * @param application * An application to introspect. */ public static Definition getDefinition(Application application, Reference baseRef, boolean useSectionNamingPackageStrategy) { // method kept for retro compatibility return getDefinition(application, null, null, baseRef, useSectionNamingPackageStrategy); } /** * Constructor. * * @param application * An application to introspect. */ public static Definition getDefinition(Application application, String applicationName, List<Class> resources, Reference baseRef, boolean useSectionNamingPackageStrategy) { List<IntrospectionHelper> introspectionHelpers = IntrospectionUtils .getIntrospectionHelpers(); Definition definition = new Definition(); CollectInfo collectInfo = new CollectInfo(); collectInfo .setUseSectionNamingPackageStrategy(useSectionNamingPackageStrategy); if (baseRef != null) { collectInfo.setApplicationPath(baseRef.getPath()); } else if (application != null) { ApplicationPath applicationPath = application.getClass() .getAnnotation(ApplicationPath.class); if (applicationPath != null) { collectInfo.setApplicationPath(applicationPath.value()); } } List<Class> allResources = getAllResources(application, resources); scanResources(collectInfo, allResources, introspectionHelpers); if (applicationName != null) { collectInfo.setApplicationName(applicationName); } else if (application != null) { collectInfo.setApplicationName(application.getClass().getName()); } else { collectInfo.setApplicationName("JAXRS-Application"); } updateDefinitionContract(collectInfo, application, definition); Contract contract = definition.getContract(); // add resources contract.setResources(collectInfo.getResources()); // add representations contract.setRepresentations(collectInfo.getRepresentations()); // add sections contract.setSections(collectInfo.getSections()); addEndpoints(collectInfo.getApplicationPath(), definition); sortDefinition(definition); updateRepresentationsSectionsFromResources(definition); if (application != null) { for (IntrospectionHelper helper : introspectionHelpers) { helper.processDefinition(definition, application.getClass()); } } return definition; } public static List<Class> getAllResources(Application application, List<Class> resources) { List<Class> allResources = new ArrayList<>(); if (application != null) { if (application.getClasses() != null) { allResources.addAll(application.getClasses()); } if (application.getSingletons() != null) { for (Object singleton : application.getSingletons()) { if (singleton != null) { allResources.add(singleton.getClass()); } } } } if (resources != null) { allResources.addAll(resources); } return allResources; } private static Header getHeader(TypeInfo typeInfo, String defaultValue, HeaderParam headerParam) { Header header = new Header(); header.setName(headerParam.value()); header.setType(typeInfo.getRepresentationName()); header.setAllowMultiple(typeInfo.isList()); header.setRequired(false); header.setDescription(StringUtils.isNullOrEmpty(defaultValue) ? "" : "Value: " + defaultValue); header.setDefaultValue(defaultValue); return header; } private static String getPathOrNull(Path path) { if (path != null) { return path.value(); } else { return null; } } private static PathVariable getPathVariable(TypeInfo typeInfo, PathParam pathParam) { PathVariable pathVariable = new PathVariable(); pathVariable.setName(pathParam.value()); pathVariable.setType(typeInfo.getRepresentationName()); return pathVariable; } private static QueryParameter getQueryParameter(TypeInfo typeInfo, String defaultValue, QueryParam queryParam) { QueryParameter queryParameter = new QueryParameter(); queryParameter.setName(queryParam.value()); queryParameter.setType(typeInfo.getRepresentationName()); queryParameter.setAllowMultiple(typeInfo.isList()); queryParameter.setRequired(false); queryParameter .setDescription(StringUtils.isNullOrEmpty(defaultValue) ? "" : "Value: " + defaultValue); queryParameter.setDefaultValue(defaultValue); return queryParameter; } private static String getResourceMethod(Method method) { if (method.getAnnotation(HEAD.class) != null) { return org.restlet.data.Method.HEAD.getName(); } if (method.getAnnotation(OPTIONS.class) != null) { return org.restlet.data.Method.OPTIONS.getName(); } if (method.getAnnotation(GET.class) != null) { return org.restlet.data.Method.GET.getName(); } if (method.getAnnotation(PUT.class) != null) { return org.restlet.data.Method.PUT.getName(); } if (method.getAnnotation(POST.class) != null) { return org.restlet.data.Method.POST.getName(); } if (method.getAnnotation(DELETE.class) != null) { return org.restlet.data.Method.DELETE.getName(); } if (method.getAnnotation(HttpMethod.class) != null) { return method.getAnnotation(HttpMethod.class).value(); } // not a resource method return null; } private static boolean isResourceMethod(Method method) { return (method.getAnnotation(HEAD.class) != null || method.getAnnotation(OPTIONS.class) != null || method.getAnnotation(GET.class) != null || method.getAnnotation(PUT.class) != null || method.getAnnotation(POST.class) != null || method.getAnnotation(DELETE.class) != null || method .getAnnotation(HttpMethod.class) != null); } private static String joinPaths(String... nullablePaths) { StringBuilder result = new StringBuilder(); // keep only not null paths List<String> paths = new ArrayList<>(); for (String path : nullablePaths) { if (!StringUtils.isNullOrEmpty(path)) { paths.add(path); } } // clean "/" and append paths int lastPathIndex = paths.size() - 1; for (int i = 0; i < paths.size(); i++) { String path = paths.get(i); if (!path.startsWith("/")) { result.append("/"); } if (i != lastPathIndex && path.endsWith("/")) { // remove last "/" if path is not the last one result.append(path.substring(0, path.length() - 1)); } else { result.append(path); } } if (result.length() == 0) { result.append("/"); } return result.toString(); } private static void scanClazz(CollectInfo collectInfo, Class<?> clazz, List<? extends IntrospectionHelper> introspectionHelper) { ClazzInfo clazzInfo = new ClazzInfo(); // Introduced by Jax-rs 2.0 // ConstrainedTo ct = clazz.getAnnotation(ConstrainedTo.class); // value = RuntimeType.SERVER clazzInfo.setClazz(clazz); Path path = clazz.getAnnotation(Path.class); clazzInfo.setPath(path); Consumes consumes = clazz.getAnnotation(Consumes.class); clazzInfo.setConsumes(consumes); Produces produces = clazz.getAnnotation(Produces.class); clazzInfo.setProduces(produces); // TODO Do we support encoded annotation? // Encoded e = clazz.getAnnotation(Encoded.class); // Scan constructor Constructor<?>[] constructors = clazz.getConstructors(); if (constructors.length == 1) { scanConstructor(constructors[0], clazzInfo); } else if (constructors.length > 1) { Constructor<?> selectedConstructor = null; int fieldsCount = -1; // should select the constructor with the most fields (jaxrs // specification) for (Constructor<?> constructor : constructors) { if (constructor.getParameterTypes().length > fieldsCount) { selectedConstructor = constructor; fieldsCount = constructor.getParameterTypes().length; } } scanConstructor(selectedConstructor, clazzInfo); } // Scan Fields Field[] fields = ReflectUtils.getAllDeclaredFields(clazz); if (fields != null) { for (Field field : fields) { scanField(field, clazzInfo); } } // todo authentication protocol // First scan bean properties methods ("simple"), then scan resource // methods List<Method> resourceMethods = new ArrayList<>(); Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { if (Modifier.isPublic(method.getModifiers())) { if (isResourceMethod(method)) { resourceMethods.add(method); } else { scanSimpleMethod(collectInfo, method, clazzInfo); } } } for (Method resourceMethod : resourceMethods) { scanResourceMethod(collectInfo, clazzInfo, resourceMethod, introspectionHelper); } } private static void scanConstructor(Constructor<?> constructor, ClazzInfo clazzInfo) { // Scan parameters Annotation[][] parameterAnnotations = constructor .getParameterAnnotations(); Class<?>[] parameterTypes = constructor.getParameterTypes(); Type[] genericParameterTypes = constructor.getGenericParameterTypes(); scanParameters(clazzInfo, parameterAnnotations, parameterTypes, genericParameterTypes); } private static void scanField(Field field, ClazzInfo clazzInfo) { TypeInfo typeInfo; try { typeInfo = Types.getTypeInfo(field.getType(), field.getGenericType()); } catch (UnsupportedTypeException e) { LOGGER.warning("Could not add field " + field + ". " + e.getMessage()); return; } // Introduced by Jax-rs 2.0 // BeanParam beanparam = field.getAnnotation(BeanParam.class); DefaultValue defaultvalue = field.getAnnotation(DefaultValue.class); String defaultValueString = defaultvalue != null ? defaultvalue.value() : null; // TODO Do we support encoded annotation? // Encoded encoded = field.getAnnotation(Encoded.class); // TODO Do we support cookie params? // CookieParam cookieParam = field.getAnnotation(CookieParam.class); // TODO Do we support matrix params? // MatrixParam matrixParam = field.getAnnotation(MatrixParam.class); HeaderParam headerParam = field.getAnnotation(HeaderParam.class); if (headerParam != null) { Header header = getHeader(typeInfo, defaultValueString, headerParam); clazzInfo.addHeader(header); } PathParam pathParam = field.getAnnotation(PathParam.class); if (pathParam != null) { PathVariable pathVariable = getPathVariable(typeInfo, pathParam); clazzInfo.addPathVariable(pathVariable); } QueryParam queryParam = field.getAnnotation(QueryParam.class); if (queryParam != null) { QueryParameter queryParameter = getQueryParameter(typeInfo, defaultValueString, queryParam); clazzInfo.addQueryParameter(queryParameter); } } private static void scanParameters(ClazzInfo clazzInfo, Annotation[][] parameterAnnotations, Class<?>[] parameterTypes, Type[] genericParameterTypes) { for (int i = 0; i < parameterTypes.length; i++) { Annotation[] annotations = parameterAnnotations[i]; TypeInfo typeInfo; try { typeInfo = Types.getTypeInfo(parameterTypes[i], genericParameterTypes[i]); } catch (UnsupportedTypeException e) { LOGGER.warning("Could not scan parameter " + Types.toString(parameterTypes[i], genericParameterTypes[i]) + ". " + e.getMessage()); continue; } for (Annotation annotation : annotations) { String defaultValue = null; if (annotation instanceof DefaultValue) { defaultValue = ((DefaultValue) annotation).value(); } if (annotation instanceof HeaderParam) { Header header = getHeader(typeInfo, defaultValue, (HeaderParam) annotation); clazzInfo.addHeader(header); } if (annotation instanceof PathParam) { PathVariable pathVariable = getPathVariable(typeInfo, (PathParam) annotation); clazzInfo.addPathVariable(pathVariable); } if (annotation instanceof QueryParam) { QueryParameter queryParameter = getQueryParameter(typeInfo, defaultValue, (QueryParam) annotation); clazzInfo.addQueryParameter(queryParameter); } } } } private static void scanResourceMethod(CollectInfo collectInfo, ClazzInfo clazzInfo, Method method, List<? extends IntrospectionHelper> introspectionHelper) { // "Path" decides on which resource to put this method Path path = method.getAnnotation(Path.class); String fullPath = joinPaths(collectInfo.getApplicationPath(), getPathOrNull(clazzInfo.getPath()), getPathOrNull(path)); String cleanPath = cleanPath(fullPath); // add operation Operation operation = new Operation(); operation.setMethod(getResourceMethod(method)); if (StringUtils.isNullOrEmpty(operation.getName())) { LOGGER.warning("Java method " + method.getName() + " has no Method name."); operation.setName(method.getName()); } Consumes consumes = method.getAnnotation(Consumes.class); if (consumes != null) { operation.setConsumes(Arrays.asList(consumes.value())); } else if (clazzInfo.getConsumes() != null) { operation.setConsumes(Arrays .asList(clazzInfo.getConsumes().value())); } Produces produces = method.getAnnotation(Produces.class); if (produces != null) { operation.setProduces(Arrays.asList(produces.value())); } else if (clazzInfo.getProduces() != null) { operation.setProduces(Arrays .asList(clazzInfo.getProduces().value())); } // Retrieve a copy of header parameters declared at class level before // adding header parameters declared at method level Map<String, Header> headers = clazzInfo.getHeadersCopy(); // Retrieve a copy of path variables declared at class level before // adding path variables declared at method level Map<String, PathVariable> pathVariables = clazzInfo .getPathVariablesCopy(); // Retrieve a copy of query parameters declared at class level before // adding query parameters declared at method level Map<String, QueryParameter> queryParameters = clazzInfo .getQueryParametersCopy(); // Scan method parameters Annotation[][] parameterAnnotations = method.getParameterAnnotations(); Class<?>[] parameterTypes = method.getParameterTypes(); Type[] genericParameterTypes = method.getGenericParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { Annotation[] annotations = parameterAnnotations[i]; TypeInfo typeInfo; try { typeInfo = Types.getTypeInfo(parameterTypes[i], genericParameterTypes[i]); } catch (UnsupportedTypeException e) { LOGGER.warning("Could not scan parameter " + Types.toString(parameterTypes[i], genericParameterTypes[i]) + " of method " + method + ". " + e.getMessage()); continue; } for (Annotation annotation : annotations) { String defaultValue = null; boolean isEntity = true; if (annotation instanceof DefaultValue) { defaultValue = ((DefaultValue) annotation).value(); } if (annotation instanceof FormParam) { isEntity = false; addRepresentation(collectInfo, typeInfo, introspectionHelper); } if (annotation instanceof HeaderParam) { isEntity = false; Header header = getHeader(typeInfo, defaultValue, (HeaderParam) annotation); headers.put(header.getName(), header); } if (annotation instanceof PathParam) { isEntity = false; PathVariable pathVariable = getPathVariable(typeInfo, (PathParam) annotation); pathVariables.put(pathVariable.getName(), pathVariable); } if (annotation instanceof QueryParam) { isEntity = false; QueryParameter queryParameter = getQueryParameter(typeInfo, defaultValue, (QueryParam) annotation); queryParameters.put(queryParameter.getName(), queryParameter); } if (annotation instanceof MatrixParam) { // not supported isEntity = false; } if (annotation instanceof CookieParam) { // not supported isEntity = false; } if (annotation instanceof Context) { // not supported isEntity = false; } // check if the parameter is an entity (no annotation) if (isEntity) { addRepresentation(collectInfo, typeInfo, introspectionHelper); PayLoad inputEntity = new PayLoad(); inputEntity.setType(typeInfo.getRepresentationName()); inputEntity.setArray(ReflectUtils .isListType(parameterTypes[i])); operation.setInputPayLoad(inputEntity); } } } operation.getQueryParameters().addAll(queryParameters.values()); // Describe the success response Response response = new Response(); if (method.getReturnType() != Void.TYPE) { TypeInfo outputTypeInfo = Types.getTypeInfo(method.getReturnType(), method.getGenericReturnType()); // Output representation addRepresentation(collectInfo, outputTypeInfo, introspectionHelper); PayLoad outputEntity = new PayLoad(); if (javax.ws.rs.core.Response.class.isAssignableFrom(outputTypeInfo .getRepresentationClazz())) { outputEntity.setType("file"); } else { outputEntity.setType(outputTypeInfo.getRepresentationName()); } outputEntity.setArray(outputTypeInfo.isList()); response.setOutputPayLoad(outputEntity); } response.setCode(Status.SUCCESS_OK.getCode()); response.setName("Success"); response.setDescription(""); response.setMessage(Status.SUCCESS_OK.getDescription()); operation.getResponses().add(response); Resource resource = collectInfo.getResource(cleanPath); if (resource == null) { resource = new Resource(); resource.setResourcePath(cleanPath); // set name from class String name = clazzInfo.getClazz().getSimpleName(); if (name.endsWith(SUFFIX_SERVER_RESOURCE) && name.length() > SUFFIX_SERVER_RESOURCE.length()) { name = name.substring(0, name.length() - SUFFIX_SERVER_RESOURCE.length()); } if (name.endsWith(SUFFIX_RESOURCE) && name.length() > SUFFIX_RESOURCE.length()) { name = name.substring(0, name.length() - SUFFIX_RESOURCE.length()); } resource.setName(name); resource.getPathVariables().addAll(pathVariables.values()); // set section from package if (collectInfo.isUseSectionNamingPackageStrategy()) { String sectionName = clazzInfo.getClazz().getPackage() .getName(); resource.getSections().add(sectionName); } collectInfo.addResource(resource); for (IntrospectionHelper helper : introspectionHelper) { helper.processResource(resource, clazzInfo.getClazz()); } } resource.getOperations().add(operation); for (IntrospectionHelper helper : introspectionHelper) { helper.processOperation(resource, operation, clazzInfo.getClazz(), method); } } /** * Returns a APISpark description of the current application. By default, * this method discovers all the resources attached to this application. It * can be overridden to add documentation, list of representations, etc. * * * @param collectInfo * The collect info bean * @param resources * The resources. * @param introspectionHelper * Optional list of introspection helpers */ public static void scanResources(CollectInfo collectInfo, List<Class> resources, List<? extends IntrospectionHelper> introspectionHelper) { for (Class<?> clazz : resources) { scanClazz(collectInfo, clazz, introspectionHelper); } } private static void scanSimpleMethod(CollectInfo collectInfo, Method method, ClazzInfo clazzInfo) { // Scan parameters Annotation[][] parameterAnnotations = method.getParameterAnnotations(); Class<?>[] parameterTypes = method.getParameterTypes(); Type[] genericParameterTypes = method.getGenericParameterTypes(); scanParameters(clazzInfo, parameterAnnotations, parameterTypes, genericParameterTypes); } private static void updateDefinitionContract(CollectInfo collectInfo, Application application, Definition definition) { // Contract Contract contract = new Contract(); contract.setName(collectInfo.getApplicationName()); // Sections if (application instanceof DocumentedApplication) { DocumentedApplication documentedApplication = (DocumentedApplication) application; if (documentedApplication.getSections() != null) { collectInfo.setSections(documentedApplication.getSections()); } } definition.setContract(contract); } }