com.eharmony.services.swagger.SwaggerResourceServer.java Source code

Java tutorial

Introduction

Here is the source code for com.eharmony.services.swagger.SwaggerResourceServer.java

Source

/*
 * Copyright 2016 eHarmony, Inc
 *
 * 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 com.eharmony.services.swagger;

import com.google.common.io.ByteStreams;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLConnection;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Exposes the swagger UI at "/swagger-ui". The swagger libraries exposes the json at /swagger.json, and the
 * swagger UI is a set of static resources that are available in various ways in a maven jar; this class is provided
 * for convenience so the swagger UI is available from the service itself, and services have minimal configuration
 * to set up swagger.
 */
@Path("/swagger-ui")
public final class SwaggerResourceServer {
    protected final static Logger LOG = LoggerFactory.getLogger(SwaggerResourceServer.class);
    private PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    private ConcurrentMap<String, byte[]> cache = new ConcurrentHashMap<>(16, 0.9f, 1);
    private String contextPath;
    private String themePath;
    private String settings;
    private static final String SWAGGER_UI_PATH = "classpath*:META-INF/swagger-ui/%s";
    private static final String SWAGGER_THEME_PATH = "theme/%s";
    private static final String SWAGGER_SETTINGS_JS = "$(function () {\n" + "window.swaggerSettings = {\n"
            + "validationUrl: '%s'};\n" + "});";

    public SwaggerResourceServer() {
        Properties properties = new Properties();

        try {
            properties.load(getClass().getResourceAsStream("/eh-swagger.properties"));
            String validationUrl = properties.getProperty("swagger.validator.url",
                    "http://online.swagger.io/validator/debug");
            String theme = properties.getProperty("swagger.theme", "swagger");
            setTheme(theme);
            setValidationUrl(validationUrl);
        } catch (IOException e) {
            LOG.error("Unable to locate eh-swagger.properties in the classpath");
            throw new RuntimeException("Unable to load eh-swagger without eh-swagger.properties");
        }
    }

    /**
     * Provides initial entry point by redirecting Swagger js with desired target url as parameter.
     */
    @GET
    public Response getBase(@Context HttpServletRequest request) throws IOException, URISyntaxException {
        String cp = contextPath;
        if (StringUtils.isBlank(contextPath)) {
            cp = request.getContextPath();
        }

        if (!cp.endsWith("/")) {
            cp += "/";
        }

        String loc = "swagger-ui/index.html?url=" + cp + "swagger.json";

        String queryString = request.getQueryString();
        if (StringUtils.isNotBlank(queryString)) {
            loc += "&" + queryString;
        }

        return Response.seeOther(new URI(loc)).build();
    }

    @GET
    @Path("/settings.js")
    public Response getSettings() throws IOException, URISyntaxException {
        return Response.ok(settings, "text/javascript").build();
    }

    /**
     * Serves static resources. Would be better to just punt all this to Jetty, but couldn't figure
     * out how to make that happen without mucking with Jetty configurations separately for each service.
     * Doing it here compartmentalizes this from normal service handling flow.
     */
    @GET
    @Path("/{fileName:.*}")
    public Response getStatic(@PathParam("fileName") String file) throws IOException, URISyntaxException {
        int qp = file.indexOf('?');
        file = (qp > 0) ? file.substring(0, qp) : file;
        file = file.contains("theme") ? file.replaceFirst("theme", themePath) : file;
        if (!file.contains("..")) {
            byte[] data = cache.get(file);
            if (data == null) {
                Resource[] rz = resolver.getResources(String.format(SWAGGER_UI_PATH, file));
                if (rz.length > 0) {
                    try (InputStream result = rz[0].getInputStream()) {
                        data = ByteStreams.toByteArray(result);
                        cache.putIfAbsent(file, data);
                    }
                }
            }
            if (data != null) {
                String mimeType = URLConnection.guessContentTypeFromName(file);
                CacheControl cacheControl = new CacheControl();
                cacheControl.setMaxAge(600);
                return Response.ok(data, mimeType).cacheControl(cacheControl).build();
            }
        }
        throw new WebApplicationException(Response.Status.NOT_FOUND);
    }

    public void setContextPath(String contextPath) {
        this.contextPath = contextPath;
    }

    public void setTheme(String theme) {
        this.themePath = String.format(SWAGGER_THEME_PATH, theme);
    }

    public void setValidationUrl(String validationUrl) {
        this.settings = String.format(SWAGGER_SETTINGS_JS, validationUrl);
    }
}