/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.apidocs.processing; import com.emc.apidocs.AnnotationUtils; import com.emc.apidocs.DocReporter; import com.emc.apidocs.KnownAnnotations; import com.emc.apidocs.Utils; import com.emc.apidocs.generating.ExampleLoader; import com.emc.apidocs.model.ApiField; import com.emc.apidocs.model.ApiMethod; import com.emc.apidocs.model.ApiService; import com.google.common.collect.Lists; import com.sun.javadoc.*; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** */ public class MethodProcessor { private static final Pattern DATA_SERVICE_PARAM_PATTERN = Pattern .compile("(request|response)\\s*(Header|Payload|Query)?\\s*([^0-1]*)([0-1]*)\\s*([\\-0-1]*)\\s*([^ ]*)\\s*([\\s\\S]*)"); private static final String S3_URL_FORMAT = "Host Style: http://bucketname.ns1.emc.com"; private static final String ATMOS_URL_FORMAT = "Host Style: http://emc.com"; /** Create an APIMethod from a standard Method */ public static ApiMethod processMethod(ApiService apiService, MethodDoc method, String baseURL, boolean isDataService) { try { ApiMethod apiMethodDesc = new ApiMethod(); apiMethodDesc.javaMethodName = method.name(); apiMethodDesc.apiService = apiService; apiMethodDesc.isDataService = isDataService; addPath(method, apiMethodDesc, baseURL); addHttpMethod(method, apiMethodDesc); addDescription(method, apiMethodDesc); addBriefDescription(method, apiMethodDesc); addResponseDescription(method, apiMethodDesc); addPrerequisites(method, apiMethodDesc); addSecurity(method, apiMethodDesc); if (isDataService) { addDataServiceInformation(method, apiMethodDesc); } else { addInputs(method, apiMethodDesc); } addOutput(method, apiMethodDesc); addQueryParameters(method, apiMethodDesc); addPathParameters(method, apiMethodDesc); addExamples(apiMethodDesc); addDeprecated(method, apiMethodDesc); return apiMethodDesc; } catch (Exception e) { throw new RuntimeException("Error processing " + apiService.getFqJavaClassName() + "::" + method.name(), e); } } private static void addPath(MethodDoc method, ApiMethod apiMethod, String baseUrl) { String methodPath = AnnotationUtils .getAnnotationValue(method, KnownAnnotations.Path_Annotation, KnownAnnotations.Value_Element, ""); // TODO : Change this for RegEx apiMethod.path = Utils.mergePaths(baseUrl, methodPath.replace("{ignore: .+}", "").replace("{ignore:.*}", "")); } public static void addHttpMethod(MethodDoc method, ApiMethod apiMethod) { if (AnnotationUtils.hasAnnotation(method, "javax.ws.rs.POST")) { apiMethod.httpMethod = "POST"; } else if (AnnotationUtils.hasAnnotation(method, "javax.ws.rs.GET")) { apiMethod.httpMethod = "GET"; } else if (AnnotationUtils.hasAnnotation(method, "javax.ws.rs.DELETE")) { apiMethod.httpMethod = "DELETE"; } else if (AnnotationUtils.hasAnnotation(method, "javax.ws.rs.PUT")) { apiMethod.httpMethod = "PUT"; } else { apiMethod.httpMethod = "KNOWN"; } } public static void addDescription(MethodDoc method, ApiMethod apiMethod) { apiMethod.description = method.commentText(); } public static void addResponseDescription(MethodDoc method, ApiMethod apiMethod) { for (Tag tag : method.tags("@return")) { apiMethod.responseDescription = Utils.upperCaseFirstChar(tag.text()); return; } apiMethod.responseDescription = ""; } public static void addBriefDescription(MethodDoc method, ApiMethod apiMethod) { String brief = ""; for (Tag tag : method.tags("@brief")) { brief = tag.text(); } apiMethod.brief = brief; // Fix the issue where @Brief is before the main comment and thus picks up both the brief and the main comment if (brief.contains("\n")) { int briefEnd = brief.indexOf("\n"); apiMethod.brief = Utils.upperCaseFirstChar(brief.substring(0, briefEnd)); apiMethod.description = brief.substring(briefEnd + 1); } // Use brief as the comment if we have nothing else if (apiMethod.description.equals("")) { apiMethod.description = apiMethod.brief; } } public static void addPrerequisites(MethodDoc method, ApiMethod apiMethod) { for (Tag tag : method.tags("@prereq")) { if (!tag.text().toLowerCase().equals("none")) { apiMethod.addPrerequisite(tag.text()); } } } public static void addInputs(MethodDoc method, ApiMethod apiMethod) { for (Parameter parameter : method.parameters()) { if (!AnnotationUtils.hasAnnotation(parameter, "javax.ws.rs.PathParam") && !AnnotationUtils.hasAnnotation(parameter, "javax.ws.rs.QueryParam")) { apiMethod.input = JaxbClassProcessor.convertToApiClass(parameter.type().asClassDoc()); } } } public static void addDeprecated(MethodDoc method, ApiMethod apiMethod) { if (AnnotationUtils.hasAnnotation(method, KnownAnnotations.Deprecated_Annotation)) { apiMethod.isDeprecated = true; Tag[] deprecatedTags = method.tags("@deprecated"); if (deprecatedTags.length > 0) { apiMethod.deprecatedMessage = deprecatedTags[0].text(); } } } /** Data services mainly use headers, and thus the @param comments contain more information than usual */ public static void addDataServiceInformation(MethodDoc method, ApiMethod apiMethod) { Tag[] urlFormat = method.tags("@UrlFormat"); if (urlFormat.length > 0) { apiMethod.urlFormat = urlFormat[0].text(); // Update method path as Data service paths can't be picked up from the code if (apiMethod.urlFormat.startsWith(S3_URL_FORMAT)) { // S3 services have a URL format that's more comment style, so need to extract it int hostStyleEnd = apiMethod.urlFormat.indexOf("\n"); apiMethod.path = apiMethod.urlFormat.substring(S3_URL_FORMAT.length(), hostStyleEnd); } else if (apiMethod.urlFormat.startsWith(ATMOS_URL_FORMAT)) { // S3 services have a URL format that's more comment style, so need to extract it int hostStyleEnd = apiMethod.urlFormat.indexOf("\n"); apiMethod.path = apiMethod.urlFormat.substring(ATMOS_URL_FORMAT.length(), hostStyleEnd); } else { apiMethod.path = Utils.mergePaths(apiMethod.path, apiMethod.urlFormat); } } for (Tag tag : method.tags("@param")) { if (tag.text().startsWith("request") || tag.text().startsWith("response")) { try { Matcher param = DATA_SERVICE_PARAM_PATTERN.matcher(tag.text()); if (param.find()) { if (!param.group(4).equals("")) { ApiField desc = new ApiField(); desc.name = param.group(3); desc.primitiveType = param.group(6); desc.min = Integer.valueOf(param.group(4)); desc.max = Integer.valueOf(param.group(5)); desc.description = param.group(7); // Now find out where it goes if (param.group(1).equals("request")) { if (param.group(2) == null || param.group(2).equals("Header")) { apiMethod.headerParameters.add(desc); } } else { if (param.group(2) == null || param.group(2).equals("Header")) { apiMethod.responseHeaders.add(desc); } } } else { DocReporter.printWarning("Ignoring :" + tag.text()); } } else { DocReporter.printWarning("Data Services parameter did not match RegEx pattern"); DocReporter.printWarning(tag.text()); } } catch (Exception e) { DocReporter.printError(tag.text()); throw new RuntimeException(e); } } } } public static void addOutput(MethodDoc method, ApiMethod apiMethod) { if (method.returnType() != null && !method.returnType().typeName().equals("void") && !TypeUtils.isPrimitiveType(method.returnType())) { if (AnnotationUtils.hasAnnotation(method.returnType().asClassDoc(), KnownAnnotations.XMLRoot_Annotation)) { apiMethod.output = JaxbClassProcessor.convertToApiClass(method.returnType().asClassDoc()); apiMethod.isTaskResponse = method.returnType().asClassDoc().name().equals("TaskResourceRep") || method.returnType().asClassDoc().name().equals("TaskList"); } } } /** * NOTE : Needs some work to work out inherited roles/acls */ public static void addSecurity(MethodDoc method, ApiMethod apiMethod) { AnnotationDesc checkPermission = AnnotationUtils.getAnnotation(method, KnownAnnotations.CheckPermission_Annotation); if (checkPermission != null) { // CheckPermission signifies that this method should use the explicit set of permissions for (AnnotationDesc.ElementValuePair pair : checkPermission.elementValues()) { if (pair.element().name().equals("roles")) { for (AnnotationValue value : (AnnotationValue[]) pair.value().value()) { apiMethod.addRole(((FieldDoc) value.value()).name()); } } else if (pair.element().name().equals("acls")) { for (AnnotationValue value : (AnnotationValue[]) pair.value().value()) { apiMethod.addAcl(((FieldDoc) value.value()).name()); } } } } else if (AnnotationUtils.hasAnnotation(method, KnownAnnotations.InheritCheckPermission_Annotation)) { // InheritCheckPermission signifies that the method should inherit from teh DefaultPermission read or write lists boolean inheritWrite = AnnotationUtils.getAnnotationValue(method, KnownAnnotations.InheritCheckPermission_Annotation, "writeAccess", false); if (inheritWrite) { apiMethod.acls.addAll(apiMethod.apiService.writeAcls); apiMethod.roles.addAll(apiMethod.apiService.writeRoles); } else { apiMethod.acls.addAll(apiMethod.apiService.readAcls); apiMethod.roles.addAll(apiMethod.apiService.readRoles); } } } public static void addQueryParameters(MethodDoc method, ApiMethod apiMethod) { apiMethod.queryParameters = getApiParameters(method, "javax.ws.rs.QueryParam"); } public static void addPathParameters(MethodDoc method, ApiMethod apiMethod) { apiMethod.pathParameters = getApiParameters(method, "javax.ws.rs.PathParam"); } public static void addExamples(ApiMethod apiMethod) { apiMethod.xmlExample = ExampleLoader.loadExample(apiMethod.getXmlExampleFilename()); apiMethod.jsonExample = ExampleLoader.loadExample(apiMethod.getJsonExampleFilename()); } public static List<ApiField> getApiParameters(MethodDoc method, String parameterAnnotation) { List<ApiField> apiParams = Lists.newArrayList(); for (Parameter parameter : method.parameters()) { if (AnnotationUtils.hasAnnotation(parameter, parameterAnnotation)) { ApiField apiParam = new ApiField(); apiParam.name = AnnotationUtils.getAnnotationValue(parameter, parameterAnnotation, KnownAnnotations.Value_Element, parameter.name()); if (parameter.type().asClassDoc() != null) { apiParam.type = JaxbClassProcessor.convertToApiClass(parameter.type().asClassDoc()); } for (ParamTag paramTag : method.paramTags()) { if (paramTag.parameterName().equals(parameter.name())) { apiParam.description = paramTag.parameterComment(); } } apiParams.add(apiParam); } } return apiParams; } }