
com.phoenixnap.oss.ramlapisync.parser.ResourceParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of springmvc-raml-parser Show documentation
Show all versions of springmvc-raml-parser Show documentation
Components including the parsing of RAML documents and Spring MVC Annotations to create RAML models
/*
* Copyright 2002-2017 the original author or authors.
*
* 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.phoenixnap.oss.ramlapisync.parser;
import java.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.async.DeferredResult;
import com.phoenixnap.oss.ramlapisync.data.ApiParameterMetadata;
import com.phoenixnap.oss.ramlapisync.javadoc.JavaDocEntry;
import com.phoenixnap.oss.ramlapisync.javadoc.JavaDocExtractor;
import com.phoenixnap.oss.ramlapisync.javadoc.JavaDocStore;
import com.phoenixnap.oss.ramlapisync.naming.JavaTypeHelper;
import com.phoenixnap.oss.ramlapisync.naming.Pair;
import com.phoenixnap.oss.ramlapisync.naming.SchemaHelper;
import com.phoenixnap.oss.ramlapisync.raml.RamlAction;
import com.phoenixnap.oss.ramlapisync.raml.RamlActionType;
import com.phoenixnap.oss.ramlapisync.raml.RamlMimeType;
import com.phoenixnap.oss.ramlapisync.raml.RamlModelFactory;
import com.phoenixnap.oss.ramlapisync.raml.RamlModelFactoryOfFactories;
import com.phoenixnap.oss.ramlapisync.raml.RamlParamType;
import com.phoenixnap.oss.ramlapisync.raml.RamlQueryParameter;
import com.phoenixnap.oss.ramlapisync.raml.RamlResource;
import com.phoenixnap.oss.ramlapisync.raml.RamlResponse;
/**
* Common service scanning functionality
*
* @author Kurt Paris
* @since 0.0.1
*
*/
public abstract class ResourceParser {
protected static final Logger logger = LoggerFactory.getLogger(ResourceParser.class);
protected static final Pattern IGNORE_METHOD_REGEX = Pattern.compile(
"^(equals|hashCode|clone|finalize|getClass|notify|notifyAll|toString|wait)", Pattern.CASE_INSENSITIVE);
protected static final String CSRF_HEADER = "X-CSRF-TOKEN";
public static final String CATCH_ALL_MEDIA_TYPE = "application/everything";
protected JavaDocExtractor javaDocs;
protected String version;
protected String defaultMediaType;
public ResourceParser(File javaDocPath, String version, String defaultMediaType) {
this.version = version;
this.defaultMediaType = defaultMediaType;
this.javaDocs = new JavaDocExtractor(javaDocPath);
}
/**
* Loads the relevant methods from a service and extracts the information relevant to raml. Methods from the Object
* class are ignored
*
* @param clazz
* @return
*/
private void getMethodsFromService(Class> clazz, JavaDocStore javaDoc, RamlResource parentResource) {
try {
for (Method method : clazz.getMethods()) {
if (!IGNORE_METHOD_REGEX.matcher(method.getName()).matches() && shouldAddMethodToApi(method)) {
extractAndAppendResourceInfo(clazz, method, javaDoc.getJavaDoc(method), parentResource);
}
}
} catch (NoClassDefFoundError nEx) {
logger.error("Unable to get methods - skipping class " + clazz, nEx);
}
}
/**
* Update JavaDoc extractor
*
* @param javaDocs The java doc extractor to use (if any)
*/
public void setJavaDocs(JavaDocExtractor javaDocs) {
this.javaDocs = javaDocs;
}
/**
* Method to check if a specific action type supports payloads in the body of the request
*
* @param target The target Verb to check
* @return If true, the verb supports a payload in the request body
*/
public static boolean doesActionTypeSupportRequestBody(RamlActionType target) {
return target.equals(RamlActionType.POST) || target.equals(RamlActionType.PUT) || target.equals(RamlActionType.PATCH)
|| target.equals(RamlActionType.DELETE);
}
/**
* Method to check if a specific action type supports multipart mime request
*
* @param target The target Verb to check
* @return If true, the verb supports multipart mime request
*/
public static boolean doesActionTypeSupportMultipartMime(RamlActionType target) {
return target.equals(RamlActionType.POST);
}
/**
* Allows child Scanners to add their own logic on whether a method should be treated as an API or ignored
* @param method The method to inspect
* @return If true the method is annotated in such a way that it should be included in the raml
*/
protected abstract boolean shouldAddMethodToApi(Method method);
/**
* Extracts parameters from a method call and attaches these with the comments extracted from the javadoc
*
* @param apiAction The Verb of the action containing these parametes
* @param method The method to inspect
* @param parameterComments The parameter comments associated with these parameters
* @return A collection of parameters keyed by name
*/
protected Map extractQueryParameters(RamlActionType apiAction, Method method,
Map parameterComments) {
// Since POST requests have a body we choose to keep all request data in one place as much as possible
if (apiAction.equals(RamlActionType.POST) || method.getParameterCount() == 0) {
return Collections.emptyMap();
}
Map queryParams = new LinkedHashMap<>();
for (Parameter param : method.getParameters()) {
if (isQueryParameter(param)) { // Lets skip resourceIds since these are going to be going in the URL
RamlParamType simpleType = SchemaHelper.mapSimpleType(param.getType());
if (simpleType == null) {
queryParams.putAll(SchemaHelper.convertClassToQueryParameters(param,
javaDocs.getJavaDoc(param.getType())));
} else {
// Check if we have comments
String paramComment = parameterComments.get(param.getName());
queryParams.putAll(SchemaHelper.convertParameterToQueryParameter(param, paramComment));
}
}
}
return queryParams;
}
/**
* Allows children to specify whether a parameter should be included when generating query parameters for a method
* @param param The the Parameter to be checked
* @return IF true this is a parameter that shuold be appended in the query string
*/
protected abstract boolean isQueryParameter(Parameter param);
/**
* Allows children to specify which parameters within a method should be included in API generation
*
* @param method The method to inspect
* @param includeUrlParameters If true this will include URL parameters
* @param includeNonUrlParameters If true this will include query and body params
* @return A list of request parameters for this API
*/
protected abstract List getApiParameters(Method method, boolean includeUrlParameters,
boolean includeNonUrlParameters);
/**
* Extracts the TOs and other parameters from a method and will convert into JsonSchema for inclusion in the body
* TODO refactor this code structure
*
* @param apiAction The Verb of the action to be added
* @param method The method to be inspected
* @param parameterComments The parameter comments associated with these parameters
* @return A map of supported mime types for the request
*/
protected Map extractRequestBodyFromMethod(RamlActionType apiAction, Method method,
Map parameterComments) {
if (!(doesActionTypeSupportRequestBody(apiAction)) || method.getParameterCount() == 0) {
return Collections.emptyMap();
}
String comment = null;
List apiParameters = getApiParameters(method, false, true);
if (apiParameters.size() == 0) {
//We only have url params it seems
return Collections.emptyMap();
}
Pair schemaAndMime = extractRequestBody(method, parameterComments, comment, apiParameters);
return Collections.singletonMap(schemaAndMime.getFirst(), schemaAndMime.getSecond());
}
/**
* Converts a method body into a request json schema and a mime type.
* TODO refactor this code structure
*
* @param method The method to be used to get the request object
* @param parameterComments Associated JavaDoc for Parameters (if any)
* @param comment Main Method Javadoc Comment (if any)
* @param apiParameters The Parameters identifed from this method
* @return The Request Body as a schema and the associated mime type
*/
protected Pair extractRequestBody(Method method, Map parameterComments,
String comment, List apiParameters) {
String schema;
RamlMimeType mimeType = RamlModelFactoryOfFactories.createRamlModelFactoryV08().createRamlMimeType();
if (apiParameters.size() == 1) {
if (parameterComments != null && parameterComments.size() == 1) {
comment = parameterComments.values().iterator().next();
}
ApiParameterMetadata ajaxParameter = apiParameters.get(0);
schema = SchemaHelper.convertClassToJsonSchema(ajaxParameter, comment,
javaDocs.getJavaDoc(ajaxParameter.getType()));
if (StringUtils.hasText(ajaxParameter.getExample())) {
mimeType.setExample(ajaxParameter.getExample());
}
} else {
schema = "{ \"type\": \"object\", \n \"properties\": {\n"; // change to object where key = param name
boolean first = true;
for (ApiParameterMetadata param : apiParameters) {
if (!first) {
schema += ",\n";
} else {
first = false;
}
schema += "\"" + param.getName() + "\" : ";
comment = "";
if (parameterComments != null) {
if (StringUtils.hasText(comment) && StringUtils.hasText(parameterComments.get(param.getJavaName()))) {
comment = parameterComments.get(param.getJavaName());
}
}
schema += SchemaHelper.convertClassToJsonSchema(param, comment, javaDocs.getJavaDoc(param.getType()));
}
schema += "}\n}";
}
mimeType.setSchema(schema);
return new Pair<>(extractExpectedMimeTypeFromMethod(method), mimeType);
}
/**
* Allows children to add common headers to API methods (eg CSRF, Authorization)
*
* @param action The action to be modified
* @param actionType The verb of the Action
* @param method The method to inspect for headers
*/
protected abstract void addHeadersForMethod(RamlAction action, RamlActionType actionType, Method method);
/**
* Queries the parameters in the Method and checks for an AjaxParameter Annotation with the resource Id flag enabled
* @param method The Method to be inspected
* @return A list of parameters that are exposed in the API
*/
protected abstract ApiParameterMetadata[] extractResourceIdParameter(Method method);
/**
* Extracts the http method (verb) as well as the name of the api call
*
* @param clazz The controller class
* @param method The Method to inspect
* @return The Verb and Name (partial url if a resource needs to be created) of this method
*/
protected abstract Map getHttpMethodAndName(Class> clazz, Method method);
/**
* Extracts relevant info from a Java method and converts it into a RAML resource
*
* @param clazz The controller class
* @param method The Java method to introspect
* @param docEntry The associated JavaDoc (may be null)
* @param parentResource The Resource which contains this method
*/
protected abstract void extractAndAppendResourceInfo(Class> clazz, Method method, JavaDocEntry docEntry, RamlResource parentResource);
/**
* Checks is this api call is made directly on a resource without a trailing command in the URL. eg: POST on
* /myResource/
*
* @param method The method to check
* @return If true then this is an Action/Verb on a resource collection
*/
protected abstract boolean isActionOnResourceWithoutCommand(Method method);
/**
* Checks the annotations and return type that the method returns and the mime it corresponds to
*
* @param method The method to inspect
* @return The mime type as string
*/
protected String extractMimeTypeFromMethod(Method method) {
return defaultMediaType;
}
/**
* Checks the annotations and return type that the method returns and the mime it corresponds to
* @param method The method to inspect
* @return The mime type this method can work on
*/
protected String extractExpectedMimeTypeFromMethod(Method method) {
return defaultMediaType;
}
/**
* Extracts the Response Body from a method in JsonSchema Format and embeds it into a response object based on the
* defaultMediaType
* @param method The method to inspect
* @param responseComment The JavaDoc (if any) for this response
* @return The response RAML model for this method (success only)
*/
protected RamlResponse extractResponseFromMethod(Method method, String responseComment) {
RamlModelFactory ramlModelFactory = RamlModelFactoryOfFactories.createRamlModelFactoryV08();
RamlResponse response = ramlModelFactory.createRamlResponse();
String mime = extractMimeTypeFromMethod(method);
RamlMimeType jsonType = ramlModelFactory.createRamlMimeTypeWithMime(mime);// TODO this would be coolto annotate
// TO/VO/DO/weO with the mime type
// they represent and chuck it in here
Class> returnType = method.getReturnType();
Type genericReturnType = method.getGenericReturnType();
Type inferGenericType = JavaTypeHelper.inferGenericType(genericReturnType);
if (returnType != null && (returnType.equals(DeferredResult.class) || returnType.equals(ResponseEntity.class))) { //unwrap spring classes from response body
if (inferGenericType == null) {
inferGenericType = Object.class;
}
if( inferGenericType instanceof Class) {
returnType = (Class>) inferGenericType;
}
genericReturnType = inferGenericType;
}
jsonType.setSchema(SchemaHelper.convertClassToJsonSchema(genericReturnType, responseComment,
javaDocs.getJavaDoc(returnType)));
LinkedHashMap body = new LinkedHashMap<>();
body.put(mime, jsonType);
response.setBody(body);
if (StringUtils.hasText(responseComment)) {
response.setDescription(responseComment);
} else {
response.setDescription("Successful Response");
}
return response;
}
/**
*
* Extracts class information from a (believe it or not) java class as well as the contained methods.
*
* @param clazz The Class to be inspected
* @return The RAML Resource model for this class
*/
public RamlResource extractResourceInfo(Class> clazz) {
logger.info("Parsing resource: " + clazz.getSimpleName() + " ");
RamlResource resource = RamlModelFactoryOfFactories.createRamlModelFactoryV08().createRamlResource();
resource.setRelativeUri("/" + getResourceName(clazz));
resource.setDisplayName(clazz.getSimpleName()); // TODO allow the Api annotation to specify
// this stuff :)
JavaDocStore javaDoc = javaDocs.getJavaDoc(clazz);
String comment = javaDoc.getJavaDocComment(clazz);
if (comment != null) {
resource.setDescription(comment);
}
//Append stuff to the parent resource
getMethodsFromService(clazz, javaDoc, resource);
return resource;
}
/**
* Extracts the name of the resource that this class manages
*
* @param clazz The Class to inspect
* @return The name of the resource this class maps to
*/
protected abstract String getResourceName(Class> clazz);
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy