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

com.predic8.membrane.core.openapi.serviceproxy.APIProxy Maven / Gradle / Ivy

There is a newer version: 5.7.3
Show newest version
/*
 *  Copyright 2022 predic8 GmbH, www.predic8.com
 *
 *    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.predic8.membrane.core.openapi.serviceproxy;

import com.predic8.membrane.annot.*;
import com.predic8.membrane.core.openapi.util.*;
import com.predic8.membrane.core.rules.*;
import com.predic8.membrane.core.util.*;
import com.predic8.membrane.core.util.URI;
import io.swagger.v3.oas.models.servers.*;
import org.slf4j.*;

import java.io.*;
import java.net.*;
import java.util.*;

/**
 * @description The api proxy extends the serviceProxy with API related functions like OpenAPI support.
 *
 * @topic 2. Proxies
 */
@MCElement(name = "api")
public class APIProxy extends ServiceProxy {

    private static final Logger log = LoggerFactory.getLogger(APIProxy.class.getName());

    public static final String X_MEMBRANE_VALIDATION = "x-membrane-validation";
    public static final String X_MEMBRANE_ID = "x-membrane-id";
    public static final String REQUESTS = "requests";
    public static final String RESPONSES = "responses";
    public static final String VALIDATION_DETAILS = "details";

    protected Map apiRecords = new LinkedHashMap<>();

    protected Map basePaths;

    protected ValidationStatisticsCollector statisticCollector = new ValidationStatisticsCollector();

    public APIProxy() {}

    @Override
    protected AbstractProxy getNewInstance() {
        return new APIProxy();
    }


    protected List specs = new ArrayList<>();

    public List getSpecs() {
        return specs;
    }

    /**
     * @description Deploys an API from an OpenAPI document.
     */
    @MCChildElement(order = 25)
    public void setSpecs(List specs) {
        this.specs = specs;
    }

    @Override
    public void init() throws Exception {
        if (specs.size() > 0)
            key = new OpenAPIProxyServiceKey(getIp(),getHost(),getPort()); // Must come before super.  init()
        super.init();
        initOpenAPI();
    }

    private void initOpenAPI() throws IOException, ClassNotFoundException {
        if (specs.size() == 0)
            return;

        apiRecords = new OpenAPIRecordFactory(router).create(specs);

        checkForDuplicatePaths();

        basePaths = getOpenAPIMap();
        configureBasePaths();

        interceptors.add(new OpenAPIPublisherInterceptor(apiRecords));
        interceptors.add(new OpenAPIInterceptor(this));
    }

    /**
     * One API should not have multiple OpenAPI specs sharing the same path. The interceptor needs the path to check against
     * the right OpenAPI. Therefor the path must be unique.
     * The check does not consider variables. Maybe this is needed in the future?
     */
    void checkForDuplicatePaths() {

        Map> paths = new HashMap<>();

        apiRecords.values().forEach(rec -> {
            for (Server server : rec.api.getServers()) {
                URI uri;
                try {
                    uri = new URIFactory(true).create(server.getUrl());
                } catch (URISyntaxException e) {
                    throw new RuntimeException(e);
                }

                String path = uri.getPath();

                if (paths.containsKey(path)) {
                    List l = paths.get(path);
                    // Check if the path is not from the same API. One OpenAPI can have several server.urls with the same path.
                    if (!l.contains(rec)) {
                        log.error("Several OpenAPI documents of the API {} share the same path {}. Make sure that the values of info.servers.url in the OpenAPI documents are unique.", name, path);
                        throw new DuplicatePathException(path);
                    }
                }

                paths.computeIfAbsent(path, s -> {
                    List apis = new ArrayList<>();
                    apis.add(rec);
                    return apis;
                });
            }
        });
    }

    private void configureBasePaths() {
        ((OpenAPIProxyServiceKey) key).addBasePaths(new ArrayList<>(basePaths.keySet()));
    }

    private Map getOpenAPIMap() {
        Map basePaths = new HashMap<>();
        apiRecords.forEach((id,record) -> record.api.getServers().forEach(server -> {
            try {
                basePaths.put(UriUtil.getPathFromURL(router.getUriFactory(),server.getUrl()), record);
            } catch (URISyntaxException e) {
                e.printStackTrace();
                // @TODO
                throw new RuntimeException();
            }
        }));
        return basePaths;
    }

    public ValidationStatisticsCollector getValidationStatisticCollector() {
        return statisticCollector;
    }

    public Map getBasePaths() {
        return basePaths;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy