org.calrissian.restdoclet.writer.swagger.SwaggerWriter.java Source code

Java tutorial

Introduction

Here is the source code for org.calrissian.restdoclet.writer.swagger.SwaggerWriter.java

Source

/*******************************************************************************
 * Copyright (C) 2014 The Calrissian 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 org.calrissian.restdoclet.writer.swagger;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.calrissian.restdoclet.Configuration;
import org.calrissian.restdoclet.model.*;
import org.calrissian.restdoclet.writer.Writer;
import org.calrissian.restdoclet.writer.swagger.model.*;

import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import static java.util.Map.Entry;
import static org.calrissian.restdoclet.util.CommonUtils.*;
import static org.calrissian.restdoclet.writer.swagger.TypeUtils.*;

public class SwaggerWriter implements Writer {
    public static final String OUTPUT_OPTION_NAME = "swagger";

    private static final String SWAGGER_DEFAULT_HTML = "swagger/index.html";
    private static final String SWAGGER_CALLABLE_HTML = "swagger/index-callable.html";
    private static final String SWAGGER_UI_ARTIFACT = "swagger/swagger-ui.zip";
    private static final String SWAGGER_VERSION = "1.2";
    private static final String RESOURCE_DOC = "./api-docs";
    private static final String API_DOC_DIR = "apis";
    private static ObjectMapper mapper = new ObjectMapper()
            .configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false)
            .setSerializationInclusion(JsonInclude.Include.NON_NULL);

    @Override
    public void write(Collection<ClassDescriptor> classDescriptors, Configuration config) throws IOException {

        Map<String, Collection<Endpoint>> resources = new LinkedHashMap<String, Collection<Endpoint>>();
        for (ClassDescriptor classDescriptor : classDescriptors) {
            for (Endpoint endpoint : classDescriptor.getEndpoints()) {
                String resourceName = getResource(classDescriptor.getContextPath(), endpoint);
                if (resources.containsKey(resourceName)) {
                    resources.get(resourceName).add(endpoint);
                } else {
                    Collection<Endpoint> tmp = new ArrayList<Endpoint>();
                    tmp.add(endpoint);
                    resources.put(resourceName, tmp);
                }
            }
        }

        writeResource(resources, config);
        copyIndex(config);
        copySwagger();
    }

    private static void writeResource(Map<String, Collection<Endpoint>> resources, Configuration config)
            throws IOException {

        ResourceListing resourceListing = new ResourceListing(SWAGGER_VERSION, config.getApiVersion(),
                config.getDocumentTitle());
        for (Entry<String, Collection<Endpoint>> entry : resources.entrySet()) {
            resourceListing.addApi("/../" + API_DOC_DIR + entry.getKey(), "");
            writeApi(entry.getKey(), entry.getValue(), config);
        }

        mapper.writerWithDefaultPrettyPrinter().writeValue(new FileOutputStream(RESOURCE_DOC), resourceListing);

    }

    private static void writeApi(String resource, Collection<Endpoint> endpoints, Configuration config)
            throws IOException {
        Map<String, Collection<Endpoint>> pathGroups = groupPaths(endpoints);

        File apiFile = new File("./" + API_DOC_DIR, resource);
        apiFile.getParentFile().mkdirs();

        Collection<Api> apis = new ArrayList<Api>(pathGroups.size());
        for (Entry<String, Collection<Endpoint>> entry : pathGroups.entrySet())
            apis.add(new Api(entry.getKey(), "", getOperations(entry.getValue())));

        mapper.writerWithDefaultPrettyPrinter().writeValue(new FileOutputStream(apiFile),
                new ApiListing(SWAGGER_VERSION, config.getUrl(), resource, config.getApiVersion(), apis));
    }

    private static Collection<Operation> getOperations(Collection<Endpoint> endpoints) {
        Collection<Operation> operations = new ArrayList<Operation>(endpoints.size());

        for (Endpoint endpoint : endpoints) {
            Collection<Parameter> params = new ArrayList<Parameter>();

            for (PathVar pathVar : endpoint.getPathVars())
                params.add(getParameter(pathVar));

            for (QueryParam queryParam : endpoint.getQueryParams())
                params.add(getParameter(queryParam));

            if (endpoint.getRequestBody() != null)
                params.add(getParameter(endpoint.getRequestBody()));

            operations.add(new Operation(endpoint.getHttpMethod(), "nickname", endpoint.getShortDescription(),
                    endpoint.getDescription(), dataType(endpoint.getType()), endpoint.getProduces(),
                    endpoint.getConsumes(), params));
        }

        return operations;
    }

    private static Parameter getParameter(PathVar pathVar) {
        return new Parameter("path", pathVar.getName(), pathVar.getDescription(), basicType(pathVar.getType()),
                null, true, false, allowableValues(pathVar.getType()));
    }

    private static Parameter getParameter(QueryParam queryParam) {
        //If it is a container type then allow multiple but use the underlying type.
        boolean container = isContainer(queryParam.getType());

        return new Parameter("query", queryParam.getName(), queryParam.getDescription(),
                (container ? internalContainerType(queryParam.getType()) : basicType(queryParam.getType())), null,
                queryParam.isRequired(), container, allowableValues(queryParam.getType()));
    }

    private static Parameter getParameter(RequestBody requestBody) {
        return new Parameter("body", requestBody.getName(), requestBody.getDescription(),
                dataType(requestBody.getType()), null, true, false, allowableValues(requestBody.getType()));
    }

    private static Map<String, Collection<Endpoint>> groupPaths(Collection<Endpoint> endpoints) {
        Map<String, Collection<Endpoint>> paths = new LinkedHashMap<String, Collection<Endpoint>>();
        for (Endpoint endpoint : endpoints) {
            if (paths.containsKey(endpoint.getPath())) {
                paths.get(endpoint.getPath()).add(endpoint);
            } else {
                Collection<Endpoint> tmp = new ArrayList<Endpoint>();
                tmp.add(endpoint);
                paths.put(endpoint.getPath(), tmp);
            }
        }

        return paths;
    }

    /**
     * Will get the first path segment that follows the context path.  Will return the partial path as the resource id.
     */
    private static String getResource(String contextPath, Endpoint endpoint) {
        if (endpoint == null || isEmpty(endpoint.getPath()))
            return "/";

        //Shouldn't need to do this, but being safe.
        String tmp = fixPath(endpoint.getPath());

        //First normalize the path then, if not part of the path then simply ignore it.
        contextPath = fixPath(contextPath);
        contextPath = (!tmp.startsWith(contextPath) ? "" : contextPath);

        //remove the context path for evaluation
        tmp = tmp.substring(contextPath.length());

        if (tmp.indexOf("/", 1) > 0)
            tmp = tmp.substring(0, tmp.indexOf("/", 1));

        return contextPath + tmp;
    }

    private static void copyIndex(Configuration config) throws IOException {
        InputStream in = null;
        OutputStream out = null;
        try {

            if (config.hasUrl())
                in = Thread.currentThread().getContextClassLoader().getResourceAsStream(SWAGGER_CALLABLE_HTML);
            else
                in = Thread.currentThread().getContextClassLoader().getResourceAsStream(SWAGGER_DEFAULT_HTML);

            out = new FileOutputStream(new File(".", "index.html"));
            copy(in, out);

        } finally {
            close(in, out);
        }
    }

    private static void copySwagger() throws IOException {
        ZipInputStream swaggerZip = null;
        FileOutputStream out = null;
        try {
            swaggerZip = new ZipInputStream(
                    Thread.currentThread().getContextClassLoader().getResourceAsStream(SWAGGER_UI_ARTIFACT));
            ZipEntry entry;
            while ((entry = swaggerZip.getNextEntry()) != null) {
                final File swaggerFile = new File(".", entry.getName());
                if (entry.isDirectory()) {
                    if (!swaggerFile.isDirectory() && !swaggerFile.mkdirs()) {
                        throw new RuntimeException("Unable to create directory: " + swaggerFile);
                    }
                } else {
                    copy(swaggerZip, new FileOutputStream(swaggerFile));
                }
            }
        } finally {
            close(swaggerZip, out);
        }
    }
}