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

org.openl.rules.openapi.impl.OpenAPIJavaClassGenerator Maven / Gradle / Ivy

There is a newer version: 5.27.9
Show newest version
package org.openl.rules.openapi.impl;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import javax.ws.rs.Consumes;
import javax.ws.rs.CookieParam;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.PATCH;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;

import org.openl.gen.AnnotationDescriptionBuilder;
import org.openl.gen.InterfaceByteCodeBuilder;
import org.openl.gen.InterfaceImplBuilder;
import org.openl.gen.MethodDescriptionBuilder;
import org.openl.gen.MethodParameterBuilder;
import org.openl.gen.TypeDescription;
import org.openl.rules.model.scaffolding.InputParameter;
import org.openl.rules.model.scaffolding.MethodModel;
import org.openl.rules.model.scaffolding.PathInfo;
import org.openl.rules.model.scaffolding.ProjectModel;
import org.openl.rules.model.scaffolding.TypeInfo;
import org.openl.rules.ruleservice.core.annotations.Name;
import org.openl.rules.ruleservice.core.annotations.NoTypeConversion;
import org.openl.rules.ruleservice.core.annotations.ServiceExtraMethod;
import org.openl.rules.ruleservice.core.annotations.ServiceExtraMethodHandler;
import org.openl.rules.ruleservice.core.interceptors.RulesType;
import org.openl.rules.ruleservice.publish.jaxrs.JAXRSOpenLServiceEnhancerHelper;
import org.openl.util.StringUtils;

public class OpenAPIJavaClassGenerator {

    private static final String DEFAULT_JSON_TYPE = "application/json";
    private static final String DEFAULT_SIMPLE_TYPE = "text/plain";
    private static final Class DEFAULT_DATATYPE_CLASS = Object.class;
    public static final String VALUE = "value";
    public static final String DEFAULT_OPEN_API_PATH = "org.openl.generated.services";
    public static final String DEFAULT_RUNTIME_CTX_PARAM_NAME = "runtimeContext";

    private final ProjectModel projectModel;

    public OpenAPIJavaClassGenerator(ProjectModel projectModel) {
        this.projectModel = projectModel;
    }

    /**
     * Make decision whatever if we need to decorate this method or not
     *
     * @param method candidate
     * @return {@code true} if require decoration
     */
    private boolean generateDecision(MethodModel method) {
        if (!method.isInclude()) {
            return false;
        }
        for (InputParameter inputParameter : method.getParameters()) {
            if (inputParameter.getType().getType() == TypeInfo.Type.SPREADSHEET || inputParameter.getType()
                    .getType() == TypeInfo.Type.SPREADSHEET_ARRAY) {
                return true;
            }
        }
        final PathInfo pathInfo = method.getPathInfo();
        StringBuilder sb = new StringBuilder("/" + pathInfo.getFormattedPath());
        final List parameters = method.getParameters();
        parameters.stream()
                .filter(p -> p.getIn() == InputParameter.In.PATH)
                .map(InputParameter::getFormattedName)
                .forEach(name -> sb.append("/{").append(name).append('}'));
        if (!pathInfo.getOriginalPath().contentEquals(sb)) {
            // if method name doesn't match expected path
            return true;
        }
        if (StringUtils.isNotBlank(pathInfo.getProduces())) {
            final TypeInfo typeInfo = pathInfo.getReturnType();
            if (typeInfo.isReference() || typeInfo.getDimension() > 0) {
                if (!DEFAULT_JSON_TYPE.equals(pathInfo.getProduces())) {
                    // if return type is not simple, application/json by default
                    return true;
                }
            } else if (!DEFAULT_SIMPLE_TYPE.equals(pathInfo.getProduces())) {
                // if return type is simple, text/plain by default
                return true;
            }
        }
        final boolean requestBodyIsPresented = parameters.stream().map(InputParameter::getIn).anyMatch(Objects::isNull);
        final boolean otherParamsArePresented = parameters.stream()
                .map(InputParameter::getIn)
                .anyMatch(Objects::nonNull);

        if (requestBodyIsPresented && otherParamsArePresented) {
            return true;
        }

        if (parameters.stream()
                .map(InputParameter::getIn)
                .filter(Objects::nonNull)
                .anyMatch(in -> !InputParameter.In.PATH.equals(in))) {
            // Only @PathParam annotation is generated by default by Rule Services
            return true;
        }
        if (parameters.stream().anyMatch(p -> !p.getFormattedName().equalsIgnoreCase(p.getOriginalName()))) {
            return true;
        }
        if (StringUtils.isNotBlank(pathInfo.getConsumes())) {
            if (projectModel.isRuntimeContextProvided()) {
                if (!DEFAULT_JSON_TYPE.equals(pathInfo.getConsumes())) {
                    // if context, application/json by default
                    return true;
                }
                // runtime context param may be null when it's inside the request model
                if (!parameters.isEmpty() && pathInfo
                        .getRuntimeContextParameter() != null && !DEFAULT_RUNTIME_CTX_PARAM_NAME
                        .equals(pathInfo.getRuntimeContextParameter().getFormattedName())) {
                    // if runtimeContext param name is not default
                    return true;
                }
            } else if (parameters.isEmpty()) {
                if (!DEFAULT_SIMPLE_TYPE.equals(pathInfo.getConsumes())) {
                    // if no prams, text/plan by default
                    return true;
                }
            } else {
                if (parameters.size() == 1) {
                    if (parameters.get(0).getType().isReference() || parameters.get(0).getType().getDimension() > 0) {
                        if (!DEFAULT_JSON_TYPE.equals(pathInfo.getConsumes())) {
                            // if one not simple param, application/json by default
                            return true;
                        }
                    } else if (!DEFAULT_SIMPLE_TYPE.equals(pathInfo.getConsumes())) {
                        // if one simple param, text/plain by default
                        return true;
                    }
                } else if (!DEFAULT_JSON_TYPE.equals(pathInfo.getConsumes())) {
                    // if more than one param, application/json by default
                    return true;
                }
            }
        }
        switch (pathInfo.getOperation()) {
            case GET:
                if (projectModel.isRuntimeContextProvided()) {
                    // if RuntimeContext is provided, POST by default.
                    return true;
                }
                if (parameters.size() > JAXRSOpenLServiceEnhancerHelper.MAX_PARAMETERS_COUNT_FOR_GET) {
                    // if more than 3 parameters, POST by default.
                    return true;
                } else if (!parameters.stream().allMatch(p -> p.getType().getType() == TypeInfo.Type.PRIMITIVE)) {
                    // if there is at least one non-primitive parameter, POST by default.
                    return true;
                }
                break;
            case POST:
                if (!projectModel.isRuntimeContextProvided()) {
                    if (parameters.isEmpty()) {
                        // if no context and empty params, GET by default.
                        return true;
                    } else if (parameters
                            .size() <= JAXRSOpenLServiceEnhancerHelper.MAX_PARAMETERS_COUNT_FOR_GET && parameters.stream()
                            .allMatch(p -> p.getType().getType() == TypeInfo.Type.PRIMITIVE)) {
                        // if no context and if there are less than 3 parameters, and they are all primitive, GET by
                        // default.
                        return true;
                    }
                }
                break;
            default:
                // if not POST and not GET
                return true;
        }
        return false;
    }

    public OpenAPIGeneratedClasses generate() {
        String interfaceName = DEFAULT_OPEN_API_PATH + ".Service";
        InterfaceByteCodeBuilder interfaceBuilder = InterfaceByteCodeBuilder.create(interfaceName);

        Stream.concat(projectModel.getSpreadsheetResultModels().stream(), projectModel.getDataModels().stream())
                .filter(this::generateDecision)
                .map(method -> visitInterfaceMethod(method, false).build())
                .forEach(interfaceBuilder::addAbstractMethod);

        OpenAPIGeneratedClasses.Builder builder = OpenAPIGeneratedClasses.Builder.initialize();
        for (MethodModel extraMethod : projectModel.getNotOpenLModels()) {
            InterfaceImplBuilder extraMethodBuilder = new InterfaceImplBuilder(ServiceExtraMethodHandler.class,
                    DEFAULT_OPEN_API_PATH);
            GroovyScriptFile groovyScriptFile = new GroovyScriptFile(extraMethodBuilder.getScriptName(),
                    extraMethodBuilder.scriptText());
            builder.addGroovyCommonScript(groovyScriptFile);
            MethodDescriptionBuilder methodDesc = visitInterfaceMethod(extraMethod, true);
            methodDesc.addAnnotation(AnnotationDescriptionBuilder.create(ServiceExtraMethod.class)
                    .withProperty(VALUE, new TypeDescription(groovyScriptFile.getNameWithPackage()))
                    .build());
            interfaceBuilder.addAbstractMethod(methodDesc.build());
        }

        if (!interfaceBuilder.isEmpty()) {
            builder.setGroovyScriptFile(new GroovyScriptFile(interfaceName,
                    interfaceBuilder.buildGroovy().generatedText()));
        }
        return builder.build();
    }

    private MethodDescriptionBuilder visitInterfaceMethod(MethodModel sprModel, boolean extraMethod) {
        final PathInfo pathInfo = sprModel.getPathInfo();
        final TypeInfo returnTypeInfo = pathInfo.getReturnType();
        MethodDescriptionBuilder methodBuilder = MethodDescriptionBuilder.create(pathInfo.getFormattedPath(),
                resolveType(returnTypeInfo));

        InputParameter runtimeCtxParam = sprModel.getPathInfo().getRuntimeContextParameter();
        if (runtimeCtxParam != null) {
            MethodParameterBuilder ctxBuilder = MethodParameterBuilder.create(runtimeCtxParam.getType().getJavaName());
            final String paramName = runtimeCtxParam.getFormattedName();
            if (sprModel.getParameters().size() > 0 && !DEFAULT_RUNTIME_CTX_PARAM_NAME.equals(paramName)) {
                ctxBuilder.addAnnotation(
                        AnnotationDescriptionBuilder.create(Name.class).withProperty(VALUE, paramName).build());
            }
            methodBuilder.addParameter(ctxBuilder.build());
        }

        for (InputParameter parameter : sprModel.getParameters()) {
            methodBuilder.addParameter(visitMethodParameter(parameter, extraMethod));
        }

        if (returnTypeInfo.getType() == TypeInfo.Type.DATATYPE) {
            methodBuilder.addAnnotation(AnnotationDescriptionBuilder.create(RulesType.class)
                    .withProperty(VALUE, OpenAPITypeUtils.removeArrayBrackets(returnTypeInfo.getSimpleName()))
                    .build());
        }

        writeWebServiceAnnotations(methodBuilder, pathInfo);

        return methodBuilder;
    }

    private TypeDescription visitMethodParameter(InputParameter parameter, boolean extraMethod) {
        final TypeInfo paramType = parameter.getType();
        MethodParameterBuilder methodParamBuilder = MethodParameterBuilder.create(resolveType(paramType));
        if (paramType.getType() == TypeInfo.Type.DATATYPE) {
            methodParamBuilder.addAnnotation(AnnotationDescriptionBuilder.create(RulesType.class)
                    .withProperty(VALUE, OpenAPITypeUtils.removeArrayBrackets(paramType.getSimpleName()))
                    .build());
        } else if (paramType.getType() == TypeInfo.Type.SPREADSHEET || paramType
                .getType() == TypeInfo.Type.SPREADSHEET_ARRAY) {
            methodParamBuilder.addAnnotation(AnnotationDescriptionBuilder.create(NoTypeConversion.class).build());
        }
        final String originalParameterName = parameter.getOriginalName();
        final String formattedParameterName = parameter.getFormattedName();
        final String parameterName = originalParameterName
                .equalsIgnoreCase(formattedParameterName) ? formattedParameterName : originalParameterName;
        if (extraMethod) {
            methodParamBuilder.addAnnotation(
                    AnnotationDescriptionBuilder.create(Name.class).withProperty(VALUE, parameterName).build());
        }
        if (parameter.getIn() != null) {
            methodParamBuilder
                    .addAnnotation(AnnotationDescriptionBuilder.create(chooseParamAnnotation(parameter.getIn()))
                            .withProperty(VALUE, parameterName)
                            .build());
        }
        return methodParamBuilder.build();
    }

    private void writeWebServiceAnnotations(MethodDescriptionBuilder methodBuilder, PathInfo pathInfo) {
        methodBuilder.addAnnotation(
                AnnotationDescriptionBuilder.create(chooseOperationAnnotation(pathInfo.getOperation())).build());
        methodBuilder.addAnnotation(
                AnnotationDescriptionBuilder.create(Path.class).withProperty(VALUE, pathInfo.getOriginalPath()).build());
        if (StringUtils.isNotBlank(pathInfo.getConsumes())) {
            methodBuilder.addAnnotation(AnnotationDescriptionBuilder.create(Consumes.class)
                    .withProperty(VALUE, pathInfo.getConsumes(), true)
                    .build());
        }
        if (StringUtils.isNotBlank(pathInfo.getProduces())) {
            methodBuilder.addAnnotation(AnnotationDescriptionBuilder.create(Produces.class)
                    .withProperty(VALUE, pathInfo.getProduces(), true)
                    .build());
        }
    }

    static String resolveType(TypeInfo typeInfo) {
        if (typeInfo.getType() == TypeInfo.Type.DATATYPE) {
            Class type = DEFAULT_DATATYPE_CLASS;
            if (typeInfo.getDimension() > 0) {
                int[] dimensions = new int[typeInfo.getDimension()];
                type = Array.newInstance(type, dimensions).getClass();
            }
            return type.getName();
        } else {
            return typeInfo.getJavaName();
        }
    }

    private Class chooseOperationAnnotation(PathInfo.Operation operation) {
        switch (operation) {
            case GET:
                return GET.class;
            case POST:
                return POST.class;
            case PUT:
                return PUT.class;
            case DELETE:
                return DELETE.class;
            case PATCH:
                return PATCH.class;
            case HEAD:
                return HEAD.class;
            case OPTIONS:
                return OPTIONS.class;
            default:
                throw new IllegalStateException("Unable to find operation annotation.");
        }
    }

    private Class chooseParamAnnotation(InputParameter.In in) {
        switch (in) {
            case PATH:
                return PathParam.class;
            case QUERY:
                return QueryParam.class;
            case COOKIE:
                return CookieParam.class;
            case HEADER:
                return HeaderParam.class;
            default:
                throw new IllegalStateException("Unable to find param annotation.");
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy