All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.mvbonline.tools.restapidoc.RestApiDocGeneratorMojo Maven / Gradle / Ivy

Go to download

Creates documentation for a spring mvc RESTful api. Output is reStructuredText for sphinx using sphinxcontrib.httpdomain

The newest version!
package de.mvbonline.tools.restapidoc;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.codehaus.classworlds.ClassRealm;
import org.codehaus.classworlds.ClassWorld;
import org.codehaus.classworlds.DuplicateRealmException;
import org.reflections.Reflections;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Template;
import com.github.jknack.handlebars.helper.StringHelpers;

import de.mvbonline.tools.restapidoc.doclet.model.ClassDescription;
import de.mvbonline.tools.restapidoc.doclet.model.MethodDescription;
import de.mvbonline.tools.restapidoc.model.ApiBodyObjectDoc;
import de.mvbonline.tools.restapidoc.model.ApiDoc;
import de.mvbonline.tools.restapidoc.model.ApiMethodDoc;
import de.mvbonline.tools.restapidoc.model.ApiObjectDoc;
import de.mvbonline.tools.restapidoc.model.ApiParamDoc;
import de.mvbonline.tools.restapidoc.model.ApiResponseObjectDoc;

/**
 * Generates documentation of spring controllers in sphinx doc (rst Files) format.
 * 
 * @goal generatedocs
 * @requiresProject true
 * @requiresDependencyResolution compile
 */
public class RestApiDocGeneratorMojo extends AbstractMojo {
    /**
     * @parameter expression="${project}"
     * @required
     * @readonly
     */
    private MavenProject project;

    /**
     * @parameter
     * @required
     */
    private String packageName;

    /**
     * @parameter
     */
    private String jacksonObjectMapper;

    /**
     * @parameter expression="${basedir}/src/site/sphinx"
     */
    private String sourceDocFolder;

    private Handlebars handlebars = new Handlebars();

    private Reflections reflections;

    private ApiDescriptionFinder apiDescriptionsFinder;

    private File outputtFolder;

    private Set modelObjects = new HashSet();

    public void execute() throws MojoExecutionException, MojoFailureException {
        try {
            outputtFolder = new File(new File(sourceDocFolder), "restapi");
            outputtFolder.mkdirs();
            setupClasspath();
            setupHandlerbars();

            reflections = new Reflections(packageName);
            apiDescriptionsFinder = new ApiDescriptionFinder();
            Set> controllers = reflections.getTypesAnnotatedWith(Controller.class);
            for (Class controller : controllers) {
                getLog().info("Found Controller: " + controller.getName());
                processRestApiController(controller);
            }

            fillModelFields();

            generateFile(modelObjects, "api_models", "models");
        }
        catch (Exception e) {
            throw new MojoExecutionException("Failed", e);
        }
    }

    private void fillModelFields() {
        ObjectMapper objectMapper = null;
        if (!StringUtils.isBlank(jacksonObjectMapper)) {
            try {
                Class mapperClass = Class.forName(jacksonObjectMapper, true, Thread.currentThread().getContextClassLoader());
                objectMapper = (ObjectMapper) mapperClass.newInstance();
            }
            catch (ClassNotFoundException e) {
                getLog().warn("Specified ObjectMapper not found " + jacksonObjectMapper);
            }
            catch (InstantiationException e) {
                getLog().warn("Specified ObjectMapper could not be created " + jacksonObjectMapper);
            }
            catch (IllegalAccessException e) {
                getLog().warn("Specified ObjectMapper could not be created " + jacksonObjectMapper);
            }
        }
        if(objectMapper == null) {
            objectMapper= new ObjectMapper();
            getLog().info("Using default ObjectMapper");
        }
        
        Jackson2ApiObjectDoc converter = new Jackson2ApiObjectDoc(objectMapper, apiDescriptionsFinder);
        for (ApiObjectDoc model : modelObjects) {
            try {
                converter.enrich(model);
            }
            catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

    }

    private void generateFile(Object doc, String templateName, String fileName) throws IOException {
        Template template = handlebars.compile(templateName);
        File f = new File(outputtFolder, fileName + ".rst");

        FileWriter fw = new FileWriter(f);
        fw.write(template.apply(doc));
        fw.close();
    }

    /**
     * build doc for all endpoint methods of the controller
     * 
     * @param classDescription
     *            the classDescription for the controller
     * @param controller
     *            the controller class
     * @return a List of all endpoint methods or an empty list if there are none
     */
    private List getEndpointMethods(ClassDescription classDescription, Class controller) {
        List result = new LinkedList();
        String basePath = "";

        if (controller.isAnnotationPresent(RequestMapping.class)) {
            basePath = controller.getAnnotation(RequestMapping.class).value()[0];
        }

        for (Method m : controller.getMethods()) {
            if (m.isAnnotationPresent(RequestMapping.class) && m.isAnnotationPresent(ResponseBody.class)) {
                RequestMapping rMappingAnno = m.getAnnotation(RequestMapping.class);
                ApiMethodDoc apiMethod = new ApiMethodDoc();
                apiMethod.setConsumes(Arrays.asList(rMappingAnno.consumes()));
                apiMethod.setProduces(Arrays.asList(rMappingAnno.produces()));
                apiMethod.setPath(basePath + rMappingAnno.value()[0]);
                apiMethod.setRequestMethod(rMappingAnno.method()[0]);

                MethodDescription methodDescription = apiDescriptionsFinder.getMethodDescription(classDescription, m.getName());
                apiMethod.setDescription(methodDescription != null ? methodDescription.getDescription() : "");

                Class[] paramTypes = m.getParameterTypes();
                Annotation[][] paramAnnotations = m.getParameterAnnotations();
                ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
                String[] paramNames = parameterNameDiscoverer.getParameterNames(m);
                for (int i = 0; i < paramAnnotations.length; i++) {
                    for (Annotation annotation : paramAnnotations[i]) {
                        if (annotation instanceof RequestParam) {
                            Boolean required = ((RequestParam) annotation).required();
                            String name = ((RequestParam) annotation).value();
                            apiMethod.getQueryParameters().add(
                                    new ApiParamDoc(name, apiDescriptionsFinder.getElementDescription(methodDescription, paramNames[i]), paramTypes[i].getSimpleName(),
                                            required));
                        }
                        else if (annotation instanceof PathVariable) {
                            apiMethod.getUrlparameters().add(
                                    new ApiParamDoc(((PathVariable) annotation).value(), apiDescriptionsFinder.getElementDescription(methodDescription, paramNames[i]),
                                            paramTypes[i].getSimpleName(), true));
                        }
                        else if (annotation instanceof RequestBody) {
                            apiMethod.setBodyobject(new ApiBodyObjectDoc(paramTypes[i].getSimpleName()));

                            ClassDescription modelObjectDescription = apiDescriptionsFinder.getClassDescription(paramTypes[i].getName());
                            modelObjects.add(new ApiObjectDoc(paramTypes[i].getSimpleName(), modelObjectDescription != null ? modelObjectDescription
                                    .getDescription() : "",paramTypes[i].getName()));
                        }
                    }
                }

                ApiResponseObjectDoc apiResponseObjectDoc = new ApiResponseObjectDoc(m.getReturnType().getSimpleName());
                apiMethod.setResponse(apiResponseObjectDoc);
                result.add(apiMethod);
                ClassDescription modelObjectDescription = apiDescriptionsFinder.getClassDescription(m.getReturnType().getName());
                modelObjects.add(new ApiObjectDoc(m.getReturnType().getSimpleName(), modelObjectDescription != null ? modelObjectDescription
                        .getDescription() : "",m.getReturnType().getName()));
            }
        }

        return result;
    }




    /**
     * Process a single controller class
     * 
     * @param controller
     *            the controller
     * @throws IOException
     */
    private void processRestApiController(Class controller) throws IOException {
        ApiDoc doc = new ApiDoc();
        doc.setName(controller.getSimpleName());

        ClassDescription classDescription = apiDescriptionsFinder.getClassDescription(controller.getName());
        doc.setDescription(classDescription != null ? classDescription.getDescription() : "");

        doc.setMethods(getEndpointMethods(classDescription, controller));

        if (doc.getMethods().isEmpty()) {
            getLog().info("No RESTful methods found for controller " + controller.getName());
            return;
        }
        generateFile(doc, "apidoc", doc.getName());
    }

    private void setupClasspath() throws DependencyResolutionRequiredException, DuplicateRealmException, MalformedURLException {
        List elements = project.getCompileClasspathElements();
        ClassWorld world = new ClassWorld();
        ClassRealm realm;
        realm = world.newRealm("maven.plugin." + getClass().getSimpleName(), Thread.currentThread().getContextClassLoader());
        for (String e : elements) {
            File elementFile = new File(e);
            URL url = new URL("file:///" + elementFile.getPath() + (elementFile.isDirectory() ? "/" : ""));
            realm.addConstituent(url);
        }

        Thread.currentThread().setContextClassLoader(realm.getClassLoader());
    }

    private void setupHandlerbars() {
        handlebars.registerHelper(StringHelpers.lower.name(), StringHelpers.lower);
        handlebars.registerHelper("header", new HeaderHelper());
        handlebars.prettyPrint(true);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy