view services/schema/src/main/java/com/redhat/thermostat/gateway/service/schema/SwaggerBuilder.java @ 292:1738c834713c

Add schema service to serve combined API swagger file This patch serves an uber-api Swagger file from URL https://127.0.0.1:30000/schema/fullapi-swagger.yaml (or ... .json) Rviewed-by: sgehwolf Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-October/025369.html
author Simon Tooke <stooke@redhat.com>
date Tue, 24 Oct 2017 14:46:18 -0400
parents
children
line wrap: on
line source

/*
 * Copyright 2012-2017 Red Hat, Inc.
 *
 * This file is part of Thermostat.
 *
 * Thermostat is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2, or (at your
 * option) any later version.
 *
 * Thermostat is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Thermostat; see the file COPYING.  If not see
 * <http://www.gnu.org/licenses/>.
 *
 * Linking this code with other modules is making a combined work
 * based on this code.  Thus, the terms and conditions of the GNU
 * General Public License cover the whole combination.
 *
 * As a special exception, the copyright holders of this code give
 * you permission to link this code with independent modules to
 * produce an executable, regardless of the license terms of these
 * independent modules, and to copy and distribute the resulting
 * executable under terms of your choice, provided that you also
 * meet, for each linked independent module, the terms and conditions
 * of the license of that module.  An independent module is a module
 * which is not derived from or based on this code.  If you modify
 * this code, you may extend this exception to your version of the
 * library, but you are not obligated to do so.  If you do not wish
 * to do so, delete this exception statement from your version.
 */

package com.redhat.thermostat.gateway.service.schema;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import com.redhat.thermostat.common.swaggercombine.SwaggerCombine;
import com.redhat.thermostat.common.swaggercombine.SwaggerCombineContext;

import com.redhat.thermostat.gateway.common.core.config.Configuration;
import com.redhat.thermostat.gateway.common.core.config.ConfigurationFactory;
import com.redhat.thermostat.gateway.common.core.config.GlobalConfiguration;
import com.redhat.thermostat.gateway.common.core.servlet.GlobalConstants;
import com.redhat.thermostat.gateway.common.util.LoggingUtil;

import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.util.ssl.SslContextFactory;

import javax.servlet.ServletContext;

import javax.ws.rs.HttpMethod;

import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;

class SwaggerBuilder {

    private static final int HTTP_OKAY = 200;
    private static final int HTTP_UNAUTHORIZED = 401;
    private static final int HTTP_NOT_FOUND = 404;

    private static final Logger logger = LoggingUtil.getLogger(SwaggerBuilder.class);

    private final String baseUrl;
    private final boolean tlsIsEnabled;

    SwaggerBuilder() {
        final ConfigurationFactory configurationFactory = new ConfigurationFactory();
        final Map<String, Object> cfgmap = configurationFactory.createGlobalConfiguration().asMap();
        tlsIsEnabled = Boolean.parseBoolean((String)cfgmap.get(GlobalConfiguration.ConfigurationKey.WITH_TLS.name()));
        final String scheme = tlsIsEnabled ? "https" : "http";
        final String port = (String)cfgmap.get(GlobalConfiguration.ConfigurationKey.PORT.name());
        String ip = (String)cfgmap.get(GlobalConfiguration.ConfigurationKey.IP.name());
        baseUrl = scheme + "://" + ip + ':' + port;
    }

    JsonObject createCombinedSwagger(ServletContext context) throws Exception {

        final SwaggerCombineContext swaggerContext = new SwaggerCombineContext();

        // add a template to define all common definitions
        final String template = readFully(context.getResourceAsStream("/WEB-INF/swagger-template.json"), "UTF-8");
        swaggerContext.addMicroAPI(template, false);

        // create an HTTP client to call other services
        final HttpClient client = createAndStartHttpClient();

        // grab the list of services from the context
        final Map<String, Object> ctxmap = ((Configuration)context.getAttribute(GlobalConstants.SERVICE_CONFIG_KEY)).asMap();
        final Map<String, String> services = (Map<String, String>)(ctxmap.get(GlobalConfiguration.ConfigurationKey.SERVICES.name()));

        // fetch all swagger files from every service
        for (final String servicePrefix : services.keySet()) {

            // get the version JSON
            final String version = fetchVersion(client, servicePrefix);

            // some services have no version or no available YAML
            if (version != null) {
                // get the YAML
                final String swaggerYaml = fetchYaml(client, servicePrefix, version);
                if (swaggerYaml != null) {
                    swaggerContext.addMicroAPI(swaggerYaml, true);
                }
            }
        }

        // perform the combine operation
        final SwaggerCombine sc = new SwaggerCombine();
        return sc.run(swaggerContext);
    }

    private String fetchVersion(HttpClient client, final String servicePrefix) throws InterruptedException, ExecutionException, TimeoutException {
        final String urlStr = baseUrl + servicePrefix + "/version";
        final ContentResponse response = client.newRequest(urlStr).method(HttpMethod.GET).send();
        if (response.getStatus() == HTTP_OKAY) {
            final String jsonStr = response.getContentAsString();
            final JsonParser parser = new JsonParser();
            final JsonElement json = parser.parse(jsonStr);
            return json.getAsJsonObject().getAsJsonPrimitive("version").getAsString();
        } else if (response.getStatus() == HTTP_NOT_FOUND) {
            logger.warning("Unexpected 404 (Not Found) from service " + urlStr);
            return null;
        } else if (response.getStatus() == HTTP_UNAUTHORIZED) {
            logger.warning("Unexpected 401 (Unauthorized) from service " + urlStr);
            return null;
        } else {
            return null;
        }
    }

    private String fetchYaml(HttpClient client, final String servicePrefix, final String version) throws InterruptedException, ExecutionException, TimeoutException {
        final String urlStr = baseUrl + servicePrefix + '/' + version + "/doc" + servicePrefix + "-swagger.yaml";
        final ContentResponse response = client.newRequest(urlStr).method(HttpMethod.GET).send();
        if (response.getStatus() == HTTP_OKAY) {
            return response.getContentAsString();
        } else if (response.getStatus() == HTTP_NOT_FOUND) {
            logger.warning("Unexpected 404 (Not Found) from service " + urlStr);
            return null;
        } else if (response.getStatus() == HTTP_UNAUTHORIZED) {
            logger.warning("Unexpected 401 (Unauthorized) from service " + urlStr);
            return null;
        } else {
            return null;
        }
    }

    private HttpClient createAndStartHttpClient() throws Exception {
        final HttpClient theclient;
        if (tlsIsEnabled) {
            SslContextFactory sslFactory = new SslContextFactory();
            sslFactory.setTrustAll(true);
            theclient = new HttpClient(sslFactory);
        } else {
            theclient = new HttpClient();
        }
        theclient.start();
        return theclient;
    }

    private String readFully(InputStream in, final String encoding) throws IOException {
        try (ByteArrayOutputStream result = new ByteArrayOutputStream()) {
            final byte[] buffer = new byte[1024];
            int length;
            while ((length = in.read(buffer)) != -1) {
                result.write(buffer, 0, length);
            }
            in.close();
            return result.toString(encoding);
        }
    }
}