org.ballerinalang.ballerina.openapi.convertor.service.OpenApiConverterUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.ballerinalang.ballerina.openapi.convertor.service.OpenApiConverterUtils.java

Source

/*
 * Copyright (c) 2017, WSO2 Inc. (http://wso2.com) All Rights Reserved.
 *
 * 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.ballerinalang.ballerina.openapi.convertor.service;

import io.swagger.models.Swagger;
import io.swagger.parser.SwaggerParser;
import io.swagger.parser.util.SwaggerDeserializationResult;
import io.swagger.v3.core.util.Yaml;
import io.swagger.v3.parser.converter.SwaggerConverter;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.ballerinalang.ballerina.openapi.convertor.Constants;
import org.ballerinalang.ballerina.openapi.convertor.OpenApiConverterException;
import org.ballerinalang.compiler.CompilerPhase;
import org.ballerinalang.langserver.compiler.LSCompiler;
import org.ballerinalang.langserver.compiler.LSCompilerException;
import org.ballerinalang.langserver.compiler.common.modal.BallerinaFile;
import org.ballerinalang.model.elements.Flag;
import org.ballerinalang.model.tree.TopLevelNode;
import org.wso2.ballerinalang.compiler.tree.BLangCompilationUnit;
import org.wso2.ballerinalang.compiler.tree.BLangIdentifier;
import org.wso2.ballerinalang.compiler.tree.BLangImportPackage;
import org.wso2.ballerinalang.compiler.tree.BLangService;
import org.wso2.ballerinalang.compiler.tree.BLangSimpleVariable;

import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * OpenApi related utility classes.
 */

public class OpenApiConverterUtils {
    /**
     * This method will generate ballerina string from openapi definition. Since ballerina service definition is super
     * set of openapi definition we will take both openapi and ballerina definition and merge openapi changes to
     * ballerina definition selectively to prevent data loss
     *
     * @param ballerinaSource ballerina definition to be process as ballerina definition
     * @param serviceName     service name
     * @return String representation of converted ballerina source
     * @throws IOException when error occur while processing input openapi and ballerina definitions.
     */
    public static String generateOpenApiDefinitions(String ballerinaSource, String serviceName) throws IOException {
        try {
            //Create empty openapi object.
            BallerinaFile ballerinaFile = LSCompiler.compileContent(ballerinaSource, CompilerPhase.DEFINE);
            BLangCompilationUnit topCompilationUnit = ballerinaFile.getBLangPackage()
                    .map(bLangPackage -> bLangPackage.getCompilationUnits().get(0)).orElse(null);

            if (topCompilationUnit == null) {
                return "Error";
            }
            String httpAlias = getAlias(topCompilationUnit, Constants.BALLERINA_HTTP_PACKAGE_NAME);
            String openApiAlias = getAlias(topCompilationUnit, Constants.OPENAPI_PACKAGE_NAME);
            OpenApiServiceMapper openApiServiceMapper = new OpenApiServiceMapper(httpAlias, openApiAlias);
            List<BLangSimpleVariable> endpoints = new ArrayList<>();

            Swagger openapi = getOpenApiDefinition(new Swagger(), openApiServiceMapper, serviceName,
                    topCompilationUnit, endpoints);
            return openApiServiceMapper.generateOpenApiString(openapi);
        } catch (LSCompilerException e) {
            return "Error";
        }
    }

    /**
     * This method will generate open API 3.X specification for given ballerina service. Since we will need to
     * support both OAS 2.0 and OAS 3.0 it was implemented to convert to openapi by default and convert it
     * to OAS on demand.
     *
     * @param ballerinaSource ballerina source to be converted to OpenApi definition
     * @param serviceName     specific service name within ballerina source that need to map OAS
     * @return Generated OAS3 string output.
     * @throws OpenApiConverterException when error occurs while converting, parsing generated openapi source.
     */
    public static String generateOAS3Definitions(String ballerinaSource, String serviceName)
            throws OpenApiConverterException {
        try {
            BallerinaFile ballerinaFile = LSCompiler.compileContent(ballerinaSource, CompilerPhase.DEFINE);
            BLangCompilationUnit topCompilationUnit = ballerinaFile.getBLangPackage()
                    .map(bLangPackage -> bLangPackage.getCompilationUnits().get(0)).orElse(null);

            if (topCompilationUnit == null) {
                return "Error";
            }
            String httpAlias = getAlias(topCompilationUnit, Constants.BALLERINA_HTTP_PACKAGE_NAME);
            String openApiAlias = getAlias(topCompilationUnit, Constants.OPENAPI_PACKAGE_NAME);
            OpenApiServiceMapper openApiServiceMapper = new OpenApiServiceMapper(httpAlias, openApiAlias);
            List<BLangSimpleVariable> endpoints = new ArrayList<>();
            Swagger openapi = getOpenApiDefinition(new Swagger(), openApiServiceMapper, serviceName,
                    topCompilationUnit, endpoints);
            String openApiSource = openApiServiceMapper.generateOpenApiString(openapi);
            SwaggerConverter converter = new SwaggerConverter();
            SwaggerDeserializationResult result = new SwaggerParser().readWithInfo(openApiSource);

            if (result.getMessages().size() > 0) {
                throw new OpenApiConverterException("Please check if input source is valid and complete");
            }

            return Yaml.pretty(converter.convert(result).getOpenAPI());
        } catch (LSCompilerException e) {
            return "Error";
        }
    }

    private static Swagger getOpenApiDefinition(Swagger openapi, OpenApiServiceMapper openApiServiceMapper,
            String serviceName, BLangCompilationUnit topCompilationUnit, List<BLangSimpleVariable> endpoints) {
        for (TopLevelNode topLevelNode : topCompilationUnit.getTopLevelNodes()) {
            if (topLevelNode instanceof BLangSimpleVariable
                    && ((BLangSimpleVariable) topLevelNode).getFlags().contains(Flag.LISTENER)) {
                endpoints.add((BLangSimpleVariable) topLevelNode);
            }

            if (topLevelNode instanceof BLangService) {
                BLangService serviceDefinition = (BLangService) topLevelNode;
                openapi = new OpenApiEndpointMapper().convertBoundEndpointsToOpenApi(endpoints, serviceDefinition,
                        openapi);

                // Generate openApi string for the mentioned service name.
                if (StringUtils.isNotBlank(serviceName)) {
                    if (serviceDefinition.getName().getValue().equals(serviceName)) {
                        openapi = openApiServiceMapper.convertServiceToOpenApi(serviceDefinition, openapi);
                        break;
                    }
                } else {
                    // If no service name mentioned, then generate openApi definition for the first service.
                    openapi = openApiServiceMapper.convertServiceToOpenApi(serviceDefinition, openapi);
                    break;
                }
            }
        }
        return openapi;
    }

    /**
     * This method will read the contents of ballerina service in {@code servicePath} and write output to
     * {@code outPath} in OAS3 format.
     *
     * @param servicePath path to ballerina service
     * @param outPath     output path to write generated openapi file
     * @param serviceName if bal file contain multiple services, name of a specific service to build
     * @throws IOException               when file operations fail
     * @throws OpenApiConverterException when converting openapi definition fails
     * @see #generateOAS3Definitions(String, String)
     */
    public static void generateOAS3Definitions(Path servicePath, Path outPath, String serviceName)
            throws IOException, OpenApiConverterException {
        String balSource = readFromFile(servicePath);
        String openApiName = getOpenApiFileName(servicePath, serviceName);

        String openApiSource = generateOAS3Definitions(balSource, serviceName);
        writeFile(outPath.resolve(openApiName), openApiSource);
    }

    /**
     * This method will read the contents of ballerina service in {@code servicePath} and write output to
     * {@code outPath} in OpenApi format.
     *
     * @param servicePath path to ballerina service
     * @param outPath     output path to write generated openapi file
     * @param serviceName if bal file contain multiple services, name of a specific service to build
     * @throws IOException when file operations fail
     * @see #generateOpenApiDefinitions(String, String)
     */
    public static void generateOpenApiDefinitions(Path servicePath, Path outPath, String serviceName)
            throws IOException {
        String balSource = readFromFile(servicePath);
        String openApiName = getOpenApiFileName(servicePath, serviceName);

        String openApiSource = generateOpenApiDefinitions(balSource, serviceName);
        writeFile(outPath.resolve(openApiName), openApiSource);
    }

    private static String readFromFile(Path servicePath) throws IOException {
        String source = FileUtils.readFileToString(servicePath.toFile(), "UTF-8");
        return source;
    }

    /**
     * Write content to a file.
     *
     * @param path    Path of the file.
     * @param content The content.
     * @throws IOException Error when creating or writing the file.
     */
    private static void writeFile(Path path, String content) throws IOException {
        Path parentPath = path.getParent();
        if (null != parentPath && Files.exists(parentPath)) {
            Files.createDirectories(parentPath);
        }
        Files.deleteIfExists(path);
        Files.createFile(path);
        try (PrintWriter writer = new PrintWriter(path.toString(), "UTF-8")) {
            writer.print(content);
        }
    }

    private static String getOpenApiFileName(Path servicePath, String serviceName) {
        Path file = servicePath.getFileName();
        String openApiFile;

        if (StringUtils.isNotBlank(serviceName)) {
            openApiFile = serviceName + ConverterConstants.OPENAPI_SUFFIX;
        } else {
            openApiFile = file != null
                    ? FilenameUtils.removeExtension(file.toString()) + ConverterConstants.OPENAPI_SUFFIX
                    : null;
        }

        return openApiFile + ConverterConstants.YAML_EXTENSION;
    }

    /**
     * Gets the alias for a given module from a bLang file root node.
     *
     * @param topCompilationUnit The root node.
     * @param packageName        The module name.
     * @return The alias.
     */
    private static String getAlias(BLangCompilationUnit topCompilationUnit, String packageName) {
        for (TopLevelNode topLevelNode : topCompilationUnit.getTopLevelNodes()) {
            if (topLevelNode instanceof BLangImportPackage) {
                BLangImportPackage importPackage = (BLangImportPackage) topLevelNode;
                String packagePath = importPackage.getPackageName().stream().map(BLangIdentifier::getValue)
                        .collect(Collectors.joining("."));
                packagePath = importPackage.getOrgName().toString() + '/' + packagePath;
                if (packageName.equals(packagePath)) {
                    return importPackage.getAlias().getValue();
                }
            }
        }

        return null;
    }
}