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

com.github.kongchen.swagger.docgen.AbstractDocumentSource Maven / Gradle / Ivy

Go to download

A maven build plugin which helps you generate API document during build phase

There is a newer version: 3.1.8
Show newest version
package com.github.kongchen.swagger.docgen;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule.Priority;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.Options;
import com.github.jknack.handlebars.Template;
import com.github.jknack.handlebars.helper.StringHelpers;
import com.github.jknack.handlebars.io.TemplateLoader;
import com.github.kongchen.swagger.docgen.mavenplugin.ApiSource;
import com.github.kongchen.swagger.docgen.mavenplugin.SecurityDefinition;
import com.github.kongchen.swagger.docgen.reader.AbstractReader;
import com.github.kongchen.swagger.docgen.reader.ClassSwaggerReader;
import com.github.kongchen.swagger.docgen.reader.ModelModifier;
import io.swagger.annotations.Api;
import io.swagger.config.FilterFactory;
import io.swagger.converter.ModelConverter;
import io.swagger.converter.ModelConverters;
import io.swagger.core.filter.SpecFilter;
import io.swagger.core.filter.SwaggerSpecFilter;
import io.swagger.jaxrs.ext.SwaggerExtension;
import io.swagger.jaxrs.ext.SwaggerExtensions;
import io.swagger.models.Scheme;
import io.swagger.models.Swagger;
import io.swagger.models.auth.SecuritySchemeDefinition;
import io.swagger.models.properties.Property;
import io.swagger.util.Json;
import io.swagger.util.Yaml;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.nio.charset.Charset;
import java.util.*;

/**
 * @author chekong 05/13/2013
 */
public abstract class AbstractDocumentSource {
    protected final ApiSource apiSource;
    protected final Log LOG;
    protected final List typesToSkip = new ArrayList();
    protected Swagger swagger;
    protected String swaggerSchemaConverter;
    private final String outputPath;
    private final String templatePath;
    private final String swaggerPath;
    private final String modelSubstitute;
    private final boolean jsonExampleValues;
    private ObjectMapper mapper = new ObjectMapper();
    private boolean isSorted = false;
    protected String encoding = "UTF-8";

    public AbstractDocumentSource(Log log, ApiSource apiSource) throws MojoFailureException {
        LOG = log;
        this.outputPath = apiSource.getOutputPath();
        this.templatePath = apiSource.getTemplatePath();
        this.swaggerPath = apiSource.getSwaggerDirectory();
        this.modelSubstitute = apiSource.getModelSubstitute();
        this.jsonExampleValues = apiSource.isJsonExampleValues();

        swagger = new Swagger();
        if (apiSource.getSchemes() != null) {
            for (String scheme : apiSource.getSchemes()) {
                swagger.scheme(Scheme.forValue(scheme));
            }
        }

        // read description from file
        if (apiSource.getDescriptionFile() != null) {
            try {
                InputStream is = new FileInputStream(apiSource.getDescriptionFile());
                apiSource.getInfo().setDescription(IOUtils.toString(is));
                is.close();
            } catch (IOException e) {
                throw new MojoFailureException(e.getMessage(), e);
            }
        }

        swagger.setHost(apiSource.getHost());
        swagger.setInfo(apiSource.getInfo());
        swagger.setBasePath(apiSource.getBasePath());

        this.apiSource = apiSource;
    }

    public void loadDocuments() throws GenerateException {
        ClassSwaggerReader reader = resolveApiReader();

        loadSwaggerExtensions(apiSource);

        swagger = reader.read(getValidClasses());

        swagger = addSecurityDefinitions(swagger, apiSource);

        swagger = doFilter(swagger);
    }

    private Swagger doFilter(Swagger swagger) throws GenerateException {
        String filterClassName = apiSource.getSwaggerInternalFilter();
        if (filterClassName != null) {
            try {
                LOG.debug(String.format("Setting filter configuration: %s", filterClassName));
                FilterFactory.setFilter((SwaggerSpecFilter) Class.forName(filterClassName).newInstance());
            } catch (Exception e) {
                throw new GenerateException("Cannot load: " + filterClassName, e);
            }
        }

        SwaggerSpecFilter filter = FilterFactory.getFilter();
        if (filter == null) {
            return swagger;
        }
        return new SpecFilter().filter(
                swagger,
                filter,
                new HashMap>(),
                new HashMap(),
                new HashMap>());
    }

    private Swagger addSecurityDefinitions(final Swagger swagger, ApiSource apiSource) throws GenerateException {
        Swagger result = swagger;
        if (apiSource.getSecurityDefinitions() == null) {
            return result;
        }
        Map definitions = new TreeMap();
        for (SecurityDefinition sd : apiSource.getSecurityDefinitions()) {
            for (Map.Entry entry : sd.generateSecuritySchemeDefinitions().entrySet()) {
                definitions.put(entry.getKey(), entry.getValue());
            }
        }
        result.setSecurityDefinitions(definitions);
        return result;
    }

    /**
     * The reader may modify the extensions list, therefore add the additional swagger extensions
     * after the instantiation of the reader
     */
    private void loadSwaggerExtensions(ApiSource apiSource) throws GenerateException {
        if (apiSource.getSwaggerExtensions() != null) {
            List extensions = SwaggerExtensions.getExtensions();
            extensions.addAll(resolveSwaggerExtensions());
        }
    }

    public void toSwaggerDocuments(String uiDocBasePath, String outputFormats, String encoding) throws GenerateException {
        toSwaggerDocuments(uiDocBasePath, outputFormats, null, encoding);
    }

    public void toSwaggerDocuments(String uiDocBasePath, String outputFormats, String fileName, String encoding) throws GenerateException {
        mapper.configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, false);
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        if (jsonExampleValues) {
            mapper.addMixInAnnotations(Property.class, PropertyExampleMixIn.class);
        }

        if (swaggerPath == null) {
            return;
        }
        if (!isSorted) {
            Utils.sortSwagger(swagger);
            isSorted = true;
        }
        File dir = new File(swaggerPath);
        if (dir.isFile()) {
            throw new GenerateException(String.format("Swagger-outputDirectory[%s] must be a directory!", swaggerPath));
        }

        if (!dir.exists()) {
            try {
                FileUtils.forceMkdir(dir);
            } catch (IOException e) {
                throw new GenerateException(String.format("Create Swagger-outputDirectory[%s] failed.", swaggerPath));
            }
        }

        if (fileName == null || "".equals(fileName.trim())) {
            fileName = "swagger";
        }
        try {
            if (outputFormats != null) {
                for (String format : outputFormats.split(",")) {
                    try {
                        Output output = Output.valueOf(format.toLowerCase());
                        switch (output) {
                            case json:
                                ObjectWriter jsonWriter = mapper.writer(new DefaultPrettyPrinter());
                                FileUtils.write(new File(dir, fileName + ".json"), jsonWriter.writeValueAsString(swagger), encoding);
                                break;
                            case yaml:
                                FileUtils.write(new File(dir, fileName + ".yaml"), Yaml.pretty().writeValueAsString(swagger), encoding);
                                break;
                        }
                    } catch (Exception e) {
                        throw new GenerateException(String.format("Declared output format [%s] is not supported.", format));
                    }
                }
            } else {
                // Default to json
                ObjectWriter jsonWriter = mapper.writer(new DefaultPrettyPrinter());
                FileUtils.write(new File(dir, fileName + ".json"), jsonWriter.writeValueAsString(swagger), encoding);
            }
        } catch (IOException e) {
            throw new GenerateException(e);
        }
    }

    public void loadModelModifier() throws GenerateException, IOException {
        ObjectMapper objectMapper = Json.mapper();
        if (apiSource.isUseJAXBAnnotationProcessor()) {
            JaxbAnnotationModule jaxbAnnotationModule = new JaxbAnnotationModule();
            if (apiSource.isUseJAXBAnnotationProcessorAsPrimary()) {
                jaxbAnnotationModule.setPriority(Priority.PRIMARY);
            } else {
                jaxbAnnotationModule.setPriority(Priority.SECONDARY);
            }
            objectMapper.registerModule(jaxbAnnotationModule);

            // to support @ApiModel on class level.
            // must be registered only if we use JaxbAnnotationModule before. Why?
            objectMapper.registerModule(new EnhancedSwaggerModule());
        }
        ModelModifier modelModifier = new ModelModifier(objectMapper);

        List apiModelPropertyAccessExclusions = apiSource.getApiModelPropertyAccessExclusions();
        if (apiModelPropertyAccessExclusions != null && !apiModelPropertyAccessExclusions.isEmpty()) {
            modelModifier.setApiModelPropertyAccessExclusions(apiModelPropertyAccessExclusions);
        }

        if (modelSubstitute != null) {
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(this.modelSubstitute)));
                String line = reader.readLine();
                while (line != null) {
                    String[] classes = line.split(":");
                    if (classes.length != 2) {
                        throw new GenerateException("Bad format of override model file, it should be ${actualClassName}:${expectClassName}");
                    }
                    modelModifier.addModelSubstitute(classes[0].trim(), classes[1].trim());
                    line = reader.readLine();
                }
            } catch (IOException e) {
                throw new GenerateException(e);
            } finally {
                if (reader != null) {
                    reader.close();
                }
            }
        }

        ModelConverters.getInstance().addConverter(modelModifier);
    }

    public void loadModelConverters() throws MojoExecutionException {
        final List modelConverters = apiSource.getModelConverters();
        if (modelConverters == null) {
            return;
        }

        for (String modelConverter : modelConverters) {
            try {
                final Class modelConverterClass = Class.forName(modelConverter);
                if (ModelConverter.class.isAssignableFrom(modelConverterClass)) {
                    final ModelConverter modelConverterInstance = (ModelConverter) modelConverterClass.newInstance();
                    ModelConverters.getInstance().addConverter(modelConverterInstance);
                } else {
                    throw new MojoExecutionException(String.format("Class %s has to be a subclass of %s", modelConverterClass.getName(), ModelConverter.class));
                }
            } catch (ClassNotFoundException e) {
                throw new MojoExecutionException(String.format("Could not find custom model converter %s", modelConverter), e);
            } catch (InstantiationException e) {
                throw new MojoExecutionException(String.format("Unable to instantiate custom model converter %s", modelConverter), e);
            } catch (IllegalAccessException e) {
                throw new MojoExecutionException(String.format("Unable to instantiate custom model converter %s", modelConverter), e);
            }
        }
    }

    public void loadTypesToSkip() throws GenerateException {
        List typesToSkip = apiSource.getTypesToSkip();
        if (typesToSkip == null) {
            return;
        }
        for (String typeToSkip : typesToSkip) {
            try {
                Type type = Class.forName(typeToSkip);
                this.typesToSkip.add(type);
            } catch (ClassNotFoundException e) {
                throw new GenerateException(e);
            }
        }
    }

    protected File createFile(File dir, String outputResourcePath) throws IOException {
        File serviceFile;
        int i = outputResourcePath.lastIndexOf("/");
        if (i != -1) {
            String fileName = outputResourcePath.substring(i + 1);
            String subDir = outputResourcePath.substring(0, i);
            File finalDirectory = new File(dir, subDir);
            finalDirectory.mkdirs();
            serviceFile = new File(finalDirectory, fileName);
        } else {
            serviceFile = new File(dir, outputResourcePath);
        }
        while (!serviceFile.createNewFile()) {
            serviceFile.delete();
        }
        LOG.info("Creating file " + serviceFile.getAbsolutePath());
        return serviceFile;
    }

    public void toDocuments() throws GenerateException {
        if (!isSorted) {
            Utils.sortSwagger(swagger);
            isSorted = true;
        }
        LOG.info("Writing doc to " + outputPath + "...");

        try {
            FileOutputStream fileOutputStream = new FileOutputStream(outputPath);
            OutputStreamWriter writer = new OutputStreamWriter(fileOutputStream, Charset.forName("UTF-8"));

            TemplatePath tp = Utils.parseTemplateUrl(templatePath);

            Handlebars handlebars = new Handlebars(tp.loader);
            initHandlebars(handlebars);

            Template template = handlebars.compile(tp.name);

            template.apply(swagger, writer);
            writer.close();
            LOG.info("Done!");
        } catch (MalformedURLException e) {
            throw new GenerateException(e);
        } catch (IOException e) {
            throw new GenerateException(e);
        }
    }

    private void initHandlebars(Handlebars handlebars) {
        handlebars.registerHelper("ifeq", new Helper() {
            @Override
            public CharSequence apply(String value, Options options) throws IOException {
                if (value == null || options.param(0) == null) {
                    return options.inverse();
                }
                if (value.equals(options.param(0))) {
                    return options.fn();
                }
                return options.inverse();
            }
        });

        handlebars.registerHelper("basename", new Helper() {
            @Override
            public CharSequence apply(String value, Options options) throws IOException {
                if (value == null) {
                    return null;
                }
                int lastSlash = value.lastIndexOf("/");
                if (lastSlash == -1) {
                    return value;
                } else {
                    return value.substring(lastSlash + 1);
                }
            }
        });

        handlebars.registerHelper(StringHelpers.join.name(), StringHelpers.join);
        handlebars.registerHelper(StringHelpers.lower.name(), StringHelpers.lower);
    }

    /**
     * Resolves the API reader which should be used to scan the classes.
     *
     * @return ClassSwaggerReader to use
     * @throws GenerateException if the reader cannot be created / resolved
     */
    protected abstract ClassSwaggerReader resolveApiReader() throws GenerateException;

    /**
     * Returns the set of classes which should be included in the scanning.
     *
     * @return Set> containing all valid classes
     */
    protected Set> getValidClasses() {
        return apiSource.getValidClasses(Api.class);
    }

    /**
     * Resolves all {@link SwaggerExtension} instances configured to be added to the Swagger configuration.
     *
     * @return Collection which should be added to the swagger configuration
     * @throws GenerateException if the swagger extensions could not be created / resolved
     */
    protected List resolveSwaggerExtensions() throws GenerateException {
        List clazzes = apiSource.getSwaggerExtensions();
        List resolved = new ArrayList();
        if (clazzes != null) {
            for (String clazz : clazzes) {
                SwaggerExtension extension;
                try {
                    extension = (SwaggerExtension) Class.forName(clazz).newInstance();
                } catch (Exception e) {
                    throw new GenerateException("Cannot load Swagger extension: " + clazz, e);
                }
                resolved.add(extension);
            }
        }
        return resolved;
    }

    protected ClassSwaggerReader getCustomApiReader(String customReaderClassName) throws GenerateException {
        try {
            LOG.info("Reading custom API reader: " + customReaderClassName);
            Class clazz = Class.forName(customReaderClassName);
            if (AbstractReader.class.isAssignableFrom(clazz)) {
                Constructor constructor = clazz.getConstructor(Swagger.class, Log.class);
                return (ClassSwaggerReader) constructor.newInstance(swagger, LOG);
            } else {
                return (ClassSwaggerReader) clazz.newInstance();
            }
        } catch (Exception e) {
            throw new GenerateException("Cannot load Swagger API reader: " + customReaderClassName, e);
        }
    }
}

enum Output {
    json,
    yaml
}

class TemplatePath {
    String prefix;
    String name;
    String suffix;
    public TemplateLoader loader;
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy