package com.revolsys.doclet.rest;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import com.revolsys.collection.set.Sets;
import com.revolsys.doclet.BaseDoclet;
import com.revolsys.doclet.DocletUtil;
import com.revolsys.util.CaseConverter;
import com.revolsys.util.HtmlAttr;
import com.revolsys.util.HtmlElem;
import com.sun.javadoc.AnnotationDesc;
import com.sun.javadoc.AnnotationDesc.ElementValuePair;
import com.sun.javadoc.AnnotationValue;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.DocErrorReporter;
import com.sun.javadoc.ExecutableMemberDoc;
import com.sun.javadoc.FieldDoc;
import com.sun.javadoc.LanguageVersion;
import com.sun.javadoc.MethodDoc;
import com.sun.javadoc.PackageDoc;
import com.sun.javadoc.Parameter;
import com.sun.javadoc.RootDoc;
import com.sun.javadoc.Tag;
public class RestDoclet extends BaseDoclet {
private static Set<String> PARAMETER_IGNORE_CLASS_NAMES = Sets
.newHash("javax.servlet.http.HttpServletRequest", "javax.servlet.http.HttpServletResponse");
public static LanguageVersion languageVersion() {
return LanguageVersion.JAVA_1_5;
}
public static int optionLength(final String optionName) {
return DocletUtil.optionLength(optionName);
}
public static boolean start(final RootDoc root) {
new RestDoclet(root).start();
return true;
}
public static boolean validOptions(final String options[][],
final DocErrorReporter docerrorreporter) {
return DocletUtil.validOptions(options, docerrorreporter);
}
public RestDoclet(final RootDoc root) {
super(root);
}
public void addResponseStatusDescription(final Map<String, List<String>> responseCodes,
final String code, final String description) {
List<String> descriptions = responseCodes.get(code);
if (descriptions == null) {
descriptions = new ArrayList<>();
responseCodes.put(code, descriptions);
}
descriptions.add(description);
}
@Override
public void documentation() {
DocletUtil.contentContainer(this.writer, "col-md-12");
this.writer.element(HtmlElem.H1, this.docTitle);
DocletUtil.description(this.writer, null, this.root);
for (final PackageDoc packageDoc : this.root.specifiedPackages()) {
final Map<String, ClassDoc> classes = new TreeMap<>();
for (final ClassDoc classDoc : packageDoc.ordinaryClasses()) {
classes.put(classDoc.name(), classDoc);
}
for (final ClassDoc classDoc : classes.values()) {
documentationClass(classDoc);
}
}
DocletUtil.endContentContainer(this.writer);
}
public void documentationClass(final ClassDoc classDoc) {
if (DocletUtil.hasAnnotation(classDoc, "org.springframework.stereotype.Controller")) {
final String id = getClassId(classDoc);
final String name = classDoc.name();
final String title = CaseConverter.toCapitalizedWords(name);
DocletUtil.panelStart(this.writer, "panel-default", HtmlElem.H2, id, null, title, null);
DocletUtil.description(this.writer, classDoc, classDoc);
for (final MethodDoc methodDoc : classDoc.methods()) {
documentationMethod(classDoc, methodDoc);
}
DocletUtil.panelEnd(this.writer);
}
}
public void documentationMethod(final ClassDoc classDoc, final MethodDoc methodDoc) {
final AnnotationDesc requestMapping = DocletUtil.getAnnotation(methodDoc,
"com.revolsys.ui.web.annotation.RequestMapping");
if (requestMapping != null) {
final String id = getMethodId(methodDoc);
final String methodName = methodDoc.name();
final String title = CaseConverter.toCapitalizedWords(methodName);
DocletUtil.panelStart(this.writer, "panel-primary", HtmlElem.H3, id, null, title, null);
DocletUtil.description(this.writer, methodDoc.containingClass(), methodDoc);
requestMethods(requestMapping);
uriTemplates(requestMapping);
uriTemplateParameters(methodDoc);
parameters(methodDoc);
responseStatus(methodDoc);
DocletUtil.panelEnd(this.writer);
}
}
@SuppressWarnings("unchecked")
private <T> T getElementValue(final AnnotationDesc annotation, final String name) {
for (final ElementValuePair pair : annotation.elementValues()) {
if (pair.element().name().equals(name)) {
return (T)pair.value().value();
}
}
return null;
}
protected String getMethodId(final ExecutableMemberDoc member) {
final ClassDoc classDoc = member.containingClass();
final String methodName = member.name();
final String classId = getClassId(classDoc);
return classId + "." + methodName;
}
@Override
public void navbar() {
DocletUtil.navbarStart(this.writer, this.docTitle);
for (final PackageDoc packageDoc : this.root.specifiedPackages()) {
final Map<String, ClassDoc> classes = new TreeMap<>();
for (final ClassDoc classDoc : packageDoc.ordinaryClasses()) {
classes.put(classDoc.name(), classDoc);
}
for (final ClassDoc classDoc : classes.values()) {
navMenu(classDoc);
}
}
DocletUtil.navbarEnd(this.writer);
}
public void navMenu(final ClassDoc classDoc) {
final String id = getClassId(classDoc);
final String name = classDoc.name();
final String title = CaseConverter.toCapitalizedWords(name);
DocletUtil.navDropdownStart(this.writer, title, "#" + id, false);
for (final MethodDoc methodDoc : classDoc.methods()) {
final AnnotationDesc requestMapping = DocletUtil.getAnnotation(methodDoc,
"com.revolsys.ui.web.annotation.RequestMapping");
if (requestMapping != null) {
navMenu(classDoc, methodDoc);
}
}
DocletUtil.navDropdownEnd(this.writer);
}
public void navMenu(final ClassDoc classDoc, final MethodDoc methodDoc) {
final String name = methodDoc.name();
final String id = getMethodId(methodDoc);
final String title = CaseConverter.toCapitalizedWords(name);
DocletUtil.navMenuItem(this.writer, title, "#" + id);
}
private void parameters(final MethodDoc method) {
final List<Parameter> parameters = new ArrayList<>();
for (final Parameter parameter : method.parameters()) {
final AnnotationDesc[] annotations = parameter.annotations();
if (DocletUtil.hasAnnotation(annotations,
"org.springframework.web.bind.annotation.RequestParam")
|| DocletUtil.hasAnnotation(annotations,
"org.springframework.web.bind.annotation.RequestBody")) {
parameters.add(parameter);
}
}
if (!parameters.isEmpty()) {
final Map<String, Tag[]> descriptions = DocletUtil.getParameterDescriptions(method);
DocletUtil.panelStart(this.writer, "panel-info", HtmlElem.H4, null, null, "Parameters", null);
this.writer.element(HtmlElem.P, "The resource supports the following parameters. "
+ "For HTTP get requests these must be specified using query string parameters. "
+ "For HTTP POST requests these can be specified using query string, application/x-www-form-urlencoded parameters or multipart/form-data unless otherwise specified. "
+ "Array values [] can be specified by including the parameter multiple times in the request.");
this.writer.startTag(HtmlElem.DIV);
this.writer.attribute(HtmlAttr.CLASS, "table-responsive");
this.writer.startTag(HtmlElem.TABLE);
this.writer.attribute(HtmlAttr.CLASS, "table table-striped table-bordered table-condensed");
this.writer.startTag(HtmlElem.THEAD);
this.writer.startTag(HtmlElem.TR);
this.writer.element(HtmlElem.TH, "Parameter");
this.writer.element(HtmlElem.TH, "Type");
this.writer.element(HtmlElem.TH, "Default");
this.writer.element(HtmlElem.TH, "Required");
this.writer.element(HtmlElem.TH, "Description");
this.writer.endTag(HtmlElem.TR);
this.writer.endTag(HtmlElem.THEAD);
this.writer.startTag(HtmlElem.TBODY);
for (final Parameter parameter : parameters) {
String typeName = parameter.typeName();
if (PARAMETER_IGNORE_CLASS_NAMES.contains(typeName)) {
typeName = typeName.replaceAll("java.util.List<([^>]+)>", "$1\\[\\]");
typeName = typeName.replaceFirst("^java.lang.", "");
typeName = typeName.replaceAll("org.springframework.web.multipart.MultipartFile", "File");
this.writer.startTag(HtmlElem.TR);
final String name = parameter.name();
final AnnotationDesc requestParam = DocletUtil.getAnnotation(parameter.annotations(),
"org.springframework.web.bind.annotation.RequestParam");
final AnnotationDesc requestBody = DocletUtil.getAnnotation(parameter.annotations(),
"org.springframework.web.bind.annotation.RequestBody");
String paramName = name;
String defaultValue = "-";
boolean required = true;
if (requestParam != null) {
final String value = getElementValue(requestParam, "value");
if (value != null && !value.trim().equals("")) {
paramName = value;
}
defaultValue = getElementValue(requestParam, "defaultValue");
if (defaultValue == null) {
defaultValue = "-";
}
required = Boolean.FALSE != (Boolean)getElementValue(requestParam, "required");
}
if (requestBody != null) {
required = true;
paramName = "HTTP Request body or 'body' parameter";
typeName = "binary/character data";
}
this.writer.startTag(HtmlElem.TD);
this.writer.startTag(HtmlElem.CODE);
this.writer.text(paramName);
this.writer.endTag(HtmlElem.CODE);
this.writer.endTag(HtmlElem.TD);
this.writer.startTag(HtmlElem.TD);
this.writer.startTag(HtmlElem.CODE);
this.writer.text(typeName);
this.writer.endTag(HtmlElem.CODE);
this.writer.endTag(HtmlElem.TD);
this.writer.element(HtmlElem.TD, defaultValue);
if (required) {
this.writer.element(HtmlElem.TD, "Yes");
} else {
this.writer.element(HtmlElem.TD, "No");
}
DocletUtil.descriptionTd(this.writer, method.containingClass(), descriptions, name);
this.writer.endTag(HtmlElem.TR);
}
}
this.writer.endTag(HtmlElem.TBODY);
this.writer.endTag(HtmlElem.TABLE);
this.writer.endTag(HtmlElem.DIV);
DocletUtil.panelEnd(this.writer);
}
}
private void requestMethods(final AnnotationDesc requestMapping) {
final AnnotationValue[] methods = getElementValue(requestMapping, "method");
if (methods != null && methods.length > 0) {
DocletUtil.panelStart(this.writer, "panel-info", HtmlElem.H4, null, null,
"HTTP Request Methods", null);
this.writer.element(HtmlElem.P,
"The resource can be accessed using the following HTTP request methods.");
this.writer.startTag(HtmlElem.UL);
for (final AnnotationValue value : methods) {
final FieldDoc method = (FieldDoc)value.value();
this.writer.element(HtmlElem.LI, method.name());
}
this.writer.endTag(HtmlElem.UL);
DocletUtil.panelEnd(this.writer);
}
}
private void responseStatus(final MethodDoc method) {
final Map<String, List<String>> responseStatusDescriptions = new TreeMap<>();
for (final Tag tag : method.tags()) {
if (tag.name().equals("@web.response.status")) {
final String text = DocletUtil.description(method.containingClass(), tag);
final int index = text.indexOf(" ");
if (index != -1) {
final String status = text.substring(0, index);
final String description = text.substring(index + 1).trim();
addResponseStatusDescription(responseStatusDescriptions, status, description);
}
}
}
addResponseStatusDescription(responseStatusDescriptions, "500",
"<p><b>Internal Server Error</b></p>"
+ "<p>This error indicates that there was an unexpected error on the server. "
+ "This is sometimes temporary so try again after a few minutes. "
+ "The problem could also be caused by bad input data so verify all input parameters and files. "
+ "If the problem persists contact the support desk with exact details of the parameters you were using.</p>");
if (!responseStatusDescriptions.isEmpty()) {
DocletUtil.panelStart(this.writer, "panel-info", HtmlElem.H4, null, null, "HTTP Status Codes",
null);
this.writer.element(HtmlElem.P,
"The resource will return one of the following status codes. The HTML error page may include an error message. The descriptions of the messages and the cause are described below.");
this.writer.startTag(HtmlElem.DIV);
this.writer.attribute(HtmlAttr.CLASS, "table-responsive");
this.writer.startTag(HtmlElem.TABLE);
this.writer.attribute(HtmlAttr.CLASS, "table table-striped table-bordered table-condensed");
this.writer.startTag(HtmlElem.THEAD);
this.writer.startTag(HtmlElem.TR);
this.writer.element(HtmlElem.TH, "HTTP Status Code");
this.writer.element(HtmlElem.TH, "Description");
this.writer.endTag(HtmlElem.TR);
this.writer.endTag(HtmlElem.THEAD);
this.writer.startTag(HtmlElem.TBODY);
for (final Entry<String, List<String>> entry : responseStatusDescriptions.entrySet()) {
final String code = entry.getKey();
for (final String message : entry.getValue()) {
this.writer.startTag(HtmlElem.TR);
this.writer.element(HtmlElem.TD, code);
this.writer.startTag(HtmlElem.TD);
this.writer.write(message);
this.writer.endTag(HtmlElem.TD);
this.writer.endTag(HtmlElem.TR);
}
}
this.writer.endTag(HtmlElem.TBODY);
this.writer.endTag(HtmlElem.TABLE);
this.writer.endTag(HtmlElem.DIV);
DocletUtil.panelEnd(this.writer);
}
}
@Override
protected void setOptions(final String[][] options) {
super.setOptions(options);
}
private void uriTemplateParameters(final MethodDoc method) {
final List<Parameter> parameters = new ArrayList<>();
for (final Parameter parameter : method.parameters()) {
if (DocletUtil.hasAnnotation(parameter.annotations(),
"org.springframework.web.bind.annotation.PathVariable")) {
parameters.add(parameter);
}
}
if (!parameters.isEmpty()) {
final Map<String, Tag[]> descriptions = DocletUtil.getParameterDescriptions(method);
DocletUtil.panelStart(this.writer, "panel-info", HtmlElem.H4, null, null,
"URI Template Parameters", null);
this.writer.element(HtmlElem.P,
"The URI templates support the following parameters which must be replaced with values as described below.");
this.writer.startTag(HtmlElem.DIV);
this.writer.attribute(HtmlAttr.CLASS, "table-responsive");
this.writer.startTag(HtmlElem.TABLE);
this.writer.attribute(HtmlAttr.CLASS, "table table-striped table-bordered table-condensed");
this.writer.startTag(HtmlElem.THEAD);
this.writer.startTag(HtmlElem.TR);
this.writer.element(HtmlElem.TH, "Parameter");
this.writer.element(HtmlElem.TH, "Type");
this.writer.element(HtmlElem.TH, "Description");
this.writer.endTag(HtmlElem.TR);
this.writer.endTag(HtmlElem.THEAD);
this.writer.startTag(HtmlElem.TBODY);
for (final Parameter parameter : parameters) {
this.writer.startTag(HtmlElem.TR);
final String name = parameter.name();
this.writer.element(HtmlElem.TD, "{" + name + "}");
this.writer.element(HtmlElem.TD, parameter.typeName());
DocletUtil.descriptionTd(this.writer, method.containingClass(), descriptions, name);
this.writer.endTag(HtmlElem.TR);
}
this.writer.endTag(HtmlElem.TBODY);
this.writer.endTag(HtmlElem.TABLE);
this.writer.endTag(HtmlElem.DIV);
DocletUtil.panelEnd(this.writer);
}
}
private void uriTemplates(final AnnotationDesc requestMapping) {
final AnnotationValue[] uriTemplates = getElementValue(requestMapping, "value");
if (uriTemplates.length > 0) {
DocletUtil.panelStart(this.writer, "panel-info", HtmlElem.H4, null, null, "URI Templates",
null);
this.writer.element(HtmlElem.P,
"The URI templates define the paths that can be appended to the base URL of the service to access this resource.");
for (final AnnotationValue uriTemplate : uriTemplates) {
this.writer.element(HtmlElem.PRE, uriTemplate.value());
}
DocletUtil.panelEnd(this.writer);
}
}
}