From bec76254eac128c274b103e292a983c6f894815c Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 15 Jul 2024 10:05:38 +0530 Subject: [PATCH] Refactor service contract implementation with static resource --- ballerina/http_annotation.bal | 13 +- compiler-plugin/spotbugs-exclude.xml | 10 + .../http/compiler/HttpCompilerPlugin.java | 7 +- .../http/compiler/HttpCompilerPluginUtil.java | 78 +++++ .../http/compiler/HttpServiceAnalyzer.java | 19 +- .../compiler/HttpServiceObjTypeAnalyzer.java | 36 +-- .../http/compiler/HttpServiceValidator.java | 82 ++---- .../compiler/ServiceContractOasGenerator.java | 71 +++++ .../http/compiler/ServiceOasGenerator.java | 184 ++++++++++++ .../codemodifier/HttpServiceModifier.java | 10 +- .../ContractInfoModifierTask.java} | 57 +++- .../compiler/codemodifier/oas/Constants.java | 40 --- .../codemodifier/oas/DocGenerationUtils.java | 36 --- .../oas/HttpServiceAnalysisTask.java | 47 --- .../oas/OpenApiInfoUpdaterTask.java | 268 ------------------ .../oas/context/OpenApiDocContext.java | 83 ------ .../oas/context/OpenApiDocContextHandler.java | 91 ------ .../context/ServiceNodeAnalysisContext.java | 87 ------ .../oas/gen/AbstractOpenApiDocGenerator.java | 182 ------------ .../gen/BalProjectOpenApiDocGenerator.java | 30 -- .../oas/gen/DocGeneratorManager.java | 42 --- .../oas/gen/OpenApiContractResolver.java | 128 --------- .../oas/gen/OpenApiDocConfig.java | 41 --- .../oas/gen/OpenApiDocGenerator.java | 31 -- .../gen/SingleFileOpenApiDocGenerator.java | 54 ---- .../payload/HttpPayloadParamIdentifier.java | 4 +- .../PayloadAnnotationModifierTask.java | 2 +- .../stdlib/http/api/HttpService.java | 35 +++ .../http/api/HttpServiceFromContract.java | 1 + 29 files changed, 490 insertions(+), 1279 deletions(-) create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceContractOasGenerator.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceOasGenerator.java rename compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/{ServiceTypeModifierTask.java => contract/ContractInfoModifierTask.java} (72%) delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/Constants.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/DocGenerationUtils.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/HttpServiceAnalysisTask.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/OpenApiInfoUpdaterTask.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContext.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContextHandler.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/ServiceNodeAnalysisContext.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/BalProjectOpenApiDocGenerator.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/DocGeneratorManager.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiContractResolver.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocConfig.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocGenerator.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/SingleFileOpenApiDocGenerator.java diff --git a/ballerina/http_annotation.bal b/ballerina/http_annotation.bal index a24729de03..d22a63c7f2 100644 --- a/ballerina/http_annotation.bal +++ b/ballerina/http_annotation.bal @@ -35,6 +35,7 @@ public type HttpServiceConfig record {| ListenerAuthConfig[] auth?; string mediaTypeSubtypePrefix?; boolean treatNilableAsOptional = true; + @deprecated byte[] openApiDefinition = []; boolean validation = true; typedesc serviceType?; @@ -156,15 +157,3 @@ public type HttpCacheConfig record {| # Success(2XX) `StatusCodeResponses` return types. Default annotation adds `must-revalidate,public,max-age=3600` as # `cache-control` header in addition to `etag` and `last-modified` headers. public annotation HttpCacheConfig Cache on return; - -# Defines the information about the service contract. -# -# + openApiDefinition - The generated OpenAPI definition for the HTTP service. This is auto-generated at -# compile-time by default -public type ServiceContractInformation record {| - string openApiDefinition; -|}; - -# The annotation which is used to define the information about the service contract. This annotation is auto-generated -# at compile-time by default. Adding this annotation manually is not recommended. -public const annotation ServiceContractInformation ServiceContractInfo on type; diff --git a/compiler-plugin/spotbugs-exclude.xml b/compiler-plugin/spotbugs-exclude.xml index 5ec731368d..0bdc8fcdd7 100644 --- a/compiler-plugin/spotbugs-exclude.xml +++ b/compiler-plugin/spotbugs-exclude.xml @@ -81,4 +81,14 @@ + + + + + + + + + + diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPlugin.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPlugin.java index b6f829aa90..5936bfa6a1 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPlugin.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPlugin.java @@ -37,6 +37,7 @@ import io.ballerina.stdlib.http.compiler.completion.HttpServiceBodyContextProvider; import java.util.List; +import java.util.Map; /** * The compiler plugin implementation for Ballerina Http package. @@ -45,8 +46,10 @@ public class HttpCompilerPlugin extends CompilerPlugin { @Override public void init(CompilerPluginContext context) { - context.addCodeModifier(new HttpServiceModifier()); - context.addCodeAnalyzer(new HttpServiceAnalyzer()); + Map ctxData = context.userData(); + ctxData.put("HTTP_CODE_MODIFIER_EXECUTED", false); + context.addCodeModifier(new HttpServiceModifier(ctxData)); + context.addCodeAnalyzer(new HttpServiceAnalyzer(ctxData)); getCodeActions().forEach(context::addCodeAction); getCompletionProviders().forEach(context::addCompletionProvider); } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java index 291ade6422..2db9d66682 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java @@ -18,24 +18,33 @@ package io.ballerina.stdlib.http.compiler; +import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.api.Types; import io.ballerina.compiler.api.symbols.FunctionSymbol; import io.ballerina.compiler.api.symbols.FunctionTypeSymbol; import io.ballerina.compiler.api.symbols.IntersectionTypeSymbol; import io.ballerina.compiler.api.symbols.ModuleSymbol; +import io.ballerina.compiler.api.symbols.ServiceDeclarationSymbol; import io.ballerina.compiler.api.symbols.Symbol; import io.ballerina.compiler.api.symbols.TypeDefinitionSymbol; import io.ballerina.compiler.api.symbols.TypeDescKind; +import io.ballerina.compiler.api.symbols.TypeReferenceTypeSymbol; import io.ballerina.compiler.api.symbols.TypeSymbol; +import io.ballerina.compiler.api.symbols.UnionTypeSymbol; import io.ballerina.compiler.syntax.tree.AnnotationNode; import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.ObjectTypeDescriptorNode; import io.ballerina.compiler.syntax.tree.ReturnTypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; +import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; +import io.ballerina.tools.diagnostics.Diagnostic; import io.ballerina.tools.diagnostics.DiagnosticFactory; import io.ballerina.tools.diagnostics.DiagnosticInfo; import io.ballerina.tools.diagnostics.DiagnosticProperty; +import io.ballerina.tools.diagnostics.DiagnosticSeverity; import io.ballerina.tools.diagnostics.Location; import java.util.HashMap; @@ -59,6 +68,7 @@ import static io.ballerina.stdlib.http.compiler.Constants.FLOAT_ARRAY; import static io.ballerina.stdlib.http.compiler.Constants.HEADER_OBJ_NAME; import static io.ballerina.stdlib.http.compiler.Constants.HTTP; +import static io.ballerina.stdlib.http.compiler.Constants.HTTP_SERVICE_TYPE; import static io.ballerina.stdlib.http.compiler.Constants.INT; import static io.ballerina.stdlib.http.compiler.Constants.INTERCEPTOR_RESOURCE_RETURN_TYPE; import static io.ballerina.stdlib.http.compiler.Constants.INT_ARRAY; @@ -309,4 +319,72 @@ private static void populateNilableBasicArrayTypes(Map typeS typeSymbols.put(NILABLE_MAP_OF_ANYDATA_ARRAY, types.builder().UNION_TYPE.withMemberTypes( typeSymbols.get(ARRAY_OF_MAP_OF_ANYDATA), types.NIL).build()); } + + public static boolean diagnosticContainsErrors(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext) { + List diagnostics = syntaxNodeAnalysisContext.semanticModel().diagnostics(); + return diagnostics.stream() + .anyMatch(d -> DiagnosticSeverity.ERROR.equals(d.diagnosticInfo().severity())); + } + + public static ServiceDeclarationNode getServiceDeclarationNode(SyntaxNodeAnalysisContext context) { + if (!(context.node() instanceof ServiceDeclarationNode serviceDeclarationNode)) { + return null; + } + return getServiceDeclarationNode(serviceDeclarationNode, context.semanticModel()); + } + + public static ServiceDeclarationNode getServiceDeclarationNode(Node node, SemanticModel semanticModel) { + if (!(node instanceof ServiceDeclarationNode serviceDeclarationNode)) { + return null; + } + + Optional serviceSymOptional = semanticModel.symbol(node); + if (serviceSymOptional.isPresent()) { + List listenerTypes = ((ServiceDeclarationSymbol) serviceSymOptional.get()).listenerTypes(); + if (listenerTypes.stream().noneMatch(HttpCompilerPluginUtil::isListenerBelongsToHttpModule)) { + return null; + } + } + return serviceDeclarationNode; + } + + private static boolean isListenerBelongsToHttpModule(TypeSymbol listenerType) { + if (listenerType.typeKind() == TypeDescKind.UNION) { + return ((UnionTypeSymbol) listenerType).memberTypeDescriptors().stream() + .filter(typeDescriptor -> typeDescriptor instanceof TypeReferenceTypeSymbol) + .map(typeReferenceTypeSymbol -> (TypeReferenceTypeSymbol) typeReferenceTypeSymbol) + .anyMatch(typeReferenceTypeSymbol -> isHttpModule(typeReferenceTypeSymbol.getModule().get())); + } + + if (listenerType.typeKind() == TypeDescKind.TYPE_REFERENCE) { + return isHttpModule(((TypeReferenceTypeSymbol) listenerType).typeDescriptor().getModule().get()); + } + return false; + } + + public static boolean isServiceObjectType(ObjectTypeDescriptorNode typeNode) { + return typeNode.objectTypeQualifiers().stream().anyMatch( + qualifier -> qualifier.kind().equals(SyntaxKind.SERVICE_KEYWORD)); + } + + public static boolean isHttpServiceType(SemanticModel semanticModel, Node typeNode) { + if (!(typeNode instanceof ObjectTypeDescriptorNode serviceObjType) || !isServiceObjectType(serviceObjType)) { + return false; + } + + Optional serviceObjSymbol = semanticModel.symbol(serviceObjType.parent()); + if (serviceObjSymbol.isEmpty() || + (!(serviceObjSymbol.get() instanceof TypeDefinitionSymbol serviceObjTypeDef))) { + return false; + } + + Optional serviceContractType = semanticModel.types().getTypeByName(BALLERINA, HTTP, EMPTY, + HTTP_SERVICE_TYPE); + if (serviceContractType.isEmpty() || + !(serviceContractType.get() instanceof TypeDefinitionSymbol serviceContractTypeDef)) { + return false; + } + + return serviceObjTypeDef.typeDescriptor().subtypeOf(serviceContractTypeDef.typeDescriptor()); + } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceAnalyzer.java index 4bf17a52ab..83504cb8f5 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceAnalyzer.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceAnalyzer.java @@ -22,15 +22,32 @@ import io.ballerina.projects.plugins.CodeAnalysisContext; import io.ballerina.projects.plugins.CodeAnalyzer; +import java.util.Map; + /** * The {@code CodeAnalyzer} for Ballerina Http services. */ public class HttpServiceAnalyzer extends CodeAnalyzer { + private final Map ctxData; + + public HttpServiceAnalyzer(Map ctxData) { + this.ctxData = ctxData; + } + @Override public void init(CodeAnalysisContext codeAnalysisContext) { codeAnalysisContext.addSyntaxNodeAnalysisTask(new HttpServiceObjTypeAnalyzer(), SyntaxKind.OBJECT_TYPE_DESC); codeAnalysisContext.addSyntaxNodeAnalysisTask(new HttpServiceValidator(), SyntaxKind.SERVICE_DECLARATION); + + boolean httpCodeModifierExecuted = (boolean) ctxData.getOrDefault("HTTP_CODE_MODIFIER_EXECUTED", false); + if (httpCodeModifierExecuted) { + codeAnalysisContext.addSyntaxNodeAnalysisTask(new ServiceContractOasGenerator(), + SyntaxKind.OBJECT_TYPE_DESC); + codeAnalysisContext.addSyntaxNodeAnalysisTask(new ServiceOasGenerator(), + SyntaxKind.SERVICE_DECLARATION); + } + codeAnalysisContext.addSyntaxNodeAnalysisTask(new HttpInterceptorServiceValidator(), - SyntaxKind.CLASS_DEFINITION); + SyntaxKind.CLASS_DEFINITION); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceObjTypeAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceObjTypeAnalyzer.java index b34236dde3..38f611dd7b 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceObjTypeAnalyzer.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceObjTypeAnalyzer.java @@ -24,20 +24,17 @@ import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; import io.ballerina.compiler.syntax.tree.ObjectTypeDescriptorNode; -import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; -import io.ballerina.tools.diagnostics.Diagnostic; -import io.ballerina.tools.diagnostics.DiagnosticSeverity; -import java.util.List; import java.util.Optional; import static io.ballerina.stdlib.http.compiler.Constants.BALLERINA; import static io.ballerina.stdlib.http.compiler.Constants.EMPTY; import static io.ballerina.stdlib.http.compiler.Constants.HTTP; -import static io.ballerina.stdlib.http.compiler.Constants.HTTP_SERVICE_TYPE; import static io.ballerina.stdlib.http.compiler.Constants.SERVICE_CONTRACT_TYPE; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.diagnosticContainsErrors; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.isHttpServiceType; /** * Validates the HTTP service object type. @@ -48,8 +45,7 @@ public class HttpServiceObjTypeAnalyzer extends HttpServiceValidator { @Override public void perform(SyntaxNodeAnalysisContext context) { - List diagnostics = context.semanticModel().diagnostics(); - if (diagnostics.stream().anyMatch(d -> DiagnosticSeverity.ERROR.equals(d.diagnosticInfo().severity()))) { + if (diagnosticContainsErrors(context)) { return; } @@ -67,32 +63,6 @@ public void perform(SyntaxNodeAnalysisContext context) { validateResources(context, members); } - public static boolean isServiceObjectType(ObjectTypeDescriptorNode typeNode) { - return typeNode.objectTypeQualifiers().stream().anyMatch( - qualifier -> qualifier.kind().equals(SyntaxKind.SERVICE_KEYWORD)); - } - - public static boolean isHttpServiceType(SemanticModel semanticModel, Node typeNode) { - if (!(typeNode instanceof ObjectTypeDescriptorNode serviceObjType) || !isServiceObjectType(serviceObjType)) { - return false; - } - - Optional serviceObjSymbol = semanticModel.symbol(serviceObjType.parent()); - if (serviceObjSymbol.isEmpty() || - (!(serviceObjSymbol.get() instanceof TypeDefinitionSymbol serviceObjTypeDef))) { - return false; - } - - Optional serviceContractType = semanticModel.types().getTypeByName(BALLERINA, HTTP, EMPTY, - HTTP_SERVICE_TYPE); - if (serviceContractType.isEmpty() || - !(serviceContractType.get() instanceof TypeDefinitionSymbol serviceContractTypeDef)) { - return false; - } - - return serviceObjTypeDef.typeDescriptor().subtypeOf(serviceContractTypeDef.typeDescriptor()); - } - private static boolean isServiceContractType(SemanticModel semanticModel, ObjectTypeDescriptorNode serviceObjType) { Optional serviceObjSymbol = semanticModel.symbol(serviceObjType.parent()); diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceValidator.java index 72cf3c89e5..de39ea5810 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceValidator.java @@ -20,13 +20,10 @@ import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.api.symbols.ObjectTypeSymbol; -import io.ballerina.compiler.api.symbols.ServiceDeclarationSymbol; import io.ballerina.compiler.api.symbols.Symbol; import io.ballerina.compiler.api.symbols.TypeDefinitionSymbol; -import io.ballerina.compiler.api.symbols.TypeDescKind; import io.ballerina.compiler.api.symbols.TypeReferenceTypeSymbol; import io.ballerina.compiler.api.symbols.TypeSymbol; -import io.ballerina.compiler.api.symbols.UnionTypeSymbol; import io.ballerina.compiler.syntax.tree.AnnotationNode; import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; @@ -45,7 +42,6 @@ import io.ballerina.tools.diagnostics.Diagnostic; import io.ballerina.tools.diagnostics.DiagnosticFactory; import io.ballerina.tools.diagnostics.DiagnosticInfo; -import io.ballerina.tools.diagnostics.DiagnosticSeverity; import io.ballerina.tools.diagnostics.Location; import org.wso2.ballerinalang.compiler.diagnostic.BLangDiagnosticLocation; @@ -71,8 +67,9 @@ import static io.ballerina.stdlib.http.compiler.Constants.SERVICE_TYPE; import static io.ballerina.stdlib.http.compiler.Constants.SUFFIX_SEPARATOR_REGEX; import static io.ballerina.stdlib.http.compiler.Constants.UNNECESSARY_CHARS_REGEX; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.diagnosticContainsErrors; import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.getCtxTypes; -import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.isHttpModule; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.getServiceDeclarationNode; import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.updateDiagnostic; import static io.ballerina.stdlib.http.compiler.HttpDiagnostic.HTTP_101; import static io.ballerina.stdlib.http.compiler.HttpDiagnostic.HTTP_119; @@ -255,48 +252,6 @@ private static void checkForServiceImplementationErrors(SyntaxNodeAnalysisContex } } - public static boolean diagnosticContainsErrors(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext) { - List diagnostics = syntaxNodeAnalysisContext.semanticModel().diagnostics(); - return diagnostics.stream() - .anyMatch(d -> DiagnosticSeverity.ERROR.equals(d.diagnosticInfo().severity())); - } - - public static ServiceDeclarationNode getServiceDeclarationNode(SyntaxNodeAnalysisContext context) { - if (!(context.node() instanceof ServiceDeclarationNode serviceDeclarationNode)) { - return null; - } - return getServiceDeclarationNode(serviceDeclarationNode, context.semanticModel()); - } - - public static ServiceDeclarationNode getServiceDeclarationNode(Node node, SemanticModel semanticModel) { - if (!(node instanceof ServiceDeclarationNode serviceDeclarationNode)) { - return null; - } - - Optional serviceSymOptional = semanticModel.symbol(node); - if (serviceSymOptional.isPresent()) { - List listenerTypes = ((ServiceDeclarationSymbol) serviceSymOptional.get()).listenerTypes(); - if (listenerTypes.stream().noneMatch(HttpServiceValidator::isListenerBelongsToHttpModule)) { - return null; - } - } - return serviceDeclarationNode; - } - - private static boolean isListenerBelongsToHttpModule(TypeSymbol listenerType) { - if (listenerType.typeKind() == TypeDescKind.UNION) { - return ((UnionTypeSymbol) listenerType).memberTypeDescriptors().stream() - .filter(typeDescriptor -> typeDescriptor instanceof TypeReferenceTypeSymbol) - .map(typeReferenceTypeSymbol -> (TypeReferenceTypeSymbol) typeReferenceTypeSymbol) - .anyMatch(typeReferenceTypeSymbol -> isHttpModule(typeReferenceTypeSymbol.getModule().get())); - } - - if (listenerType.typeKind() == TypeDescKind.TYPE_REFERENCE) { - return isHttpModule(((TypeReferenceTypeSymbol) listenerType).typeDescriptor().getModule().get()); - } - return false; - } - private static void validateResourceLinks(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, LinksMetaData linksMetaData) { if (!linksMetaData.hasNameReferenceObjects()) { @@ -375,28 +330,31 @@ private static void validateAnnotationUsageForServiceContractType(SyntaxNodeAnal AnnotationNode annotation, MappingConstructorExpressionNode annotValue, TypeDescriptorNode typeDescriptorNode) { - if (Objects.isNull(annotValue) || annotValue.fields().isEmpty() || annotValue.fields().size() > 1) { + // TODO: Change annotValue.fields().size() > 1 after resource migration + if (Objects.isNull(annotValue) || annotValue.fields().isEmpty() || annotValue.fields().size() > 2) { reportInvalidServiceConfigAnnotationUsage(ctx, annotation.location()); return; } - MappingFieldNode field = annotValue.fields().get(0); - String fieldString = field.toString(); - fieldString = fieldString.trim().replaceAll(UNNECESSARY_CHARS_REGEX, ""); - if (field.kind().equals(SyntaxKind.SPECIFIC_FIELD)) { - String[] strings = fieldString.split(COLON, 2); - if (SERVICE_TYPE.equals(strings[0].trim())) { - String expectedServiceType = typeDescriptorNode.toString().trim(); - String actualServiceType = strings[1].trim(); - if (!actualServiceType.equals(expectedServiceType)) { - reportInvalidServiceContractType(ctx, expectedServiceType, actualServiceType, - annotation.location()); + for (MappingFieldNode field : annotValue.fields()) { + String fieldString = field.toString(); + fieldString = fieldString.trim().replaceAll(UNNECESSARY_CHARS_REGEX, ""); + if (field.kind().equals(SyntaxKind.SPECIFIC_FIELD)) { + String[] strings = fieldString.split(COLON, 2); + if (SERVICE_TYPE.equals(strings[0].trim())) { + String expectedServiceType = typeDescriptorNode.toString().trim(); + String actualServiceType = strings[1].trim(); + if (!actualServiceType.equals(expectedServiceType)) { + reportInvalidServiceContractType(ctx, expectedServiceType, actualServiceType, + annotation.location()); + return; + } + } else if (!("openApiDefinition".equals(strings[0].trim()))) { + reportInvalidServiceConfigAnnotationUsage(ctx, annotation.location()); + return; } - return; } } - - reportInvalidServiceConfigAnnotationUsage(ctx, annotation.location()); } protected static void validateServiceConfigAnnotation(SyntaxNodeAnalysisContext ctx, diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceContractOasGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceContractOasGenerator.java new file mode 100644 index 0000000000..a833d8228a --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceContractOasGenerator.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.http.compiler; + +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.ObjectTypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; +import io.ballerina.openapi.service.mapper.model.ServiceContractType; +import io.ballerina.openapi.service.mapper.model.ServiceNode; +import io.ballerina.projects.ProjectKind; +import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; + +import java.util.Optional; + +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.diagnosticContainsErrors; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.isHttpServiceType; + +/** + * This class generates the OpenAPI definition resource for the service contract type node. + * + * @since 2.12.0 + */ +public class ServiceContractOasGenerator extends ServiceOasGenerator { + + @Override + public void perform(SyntaxNodeAnalysisContext context) { + if (diagnosticContainsErrors(context) || + !context.currentPackage().project().kind().equals(ProjectKind.BUILD_PROJECT)) { + return; + } + + Node typeNode = context.node(); + if (!isHttpServiceType(context.semanticModel(), typeNode)) { + return; + } + + ObjectTypeDescriptorNode serviceObjectType = (ObjectTypeDescriptorNode) typeNode; + TypeDefinitionNode serviceTypeNode = (TypeDefinitionNode) serviceObjectType.parent(); + + if (!serviceTypeNode.visibilityQualifier() + .map(qualifier -> qualifier.text().equals("public")) + .orElse(false)) { + return; + } + + String fileName = String.format("%s.json", serviceTypeNode.typeName().text()); + ServiceNode serviceNode = new ServiceContractType(serviceTypeNode); + Optional openApi = generateOpenApi(fileName, context.currentPackage().project(), + context.semanticModel(), serviceNode); + if (openApi.isEmpty()) { + return; + } + + writeOpenApiAsTargetResource(context.currentPackage().project(), fileName, openApi.get()); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceOasGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceOasGenerator.java new file mode 100644 index 0000000000..701bf8e44a --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceOasGenerator.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.http.compiler; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.syntax.tree.AnnotationNode; +import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; +import io.ballerina.compiler.syntax.tree.MetadataNode; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; +import io.ballerina.compiler.syntax.tree.SpecificFieldNode; +import io.ballerina.openapi.service.mapper.ServiceToOpenAPIMapper; +import io.ballerina.openapi.service.mapper.model.OASGenerationMetaInfo; +import io.ballerina.openapi.service.mapper.model.OASResult; +import io.ballerina.openapi.service.mapper.model.ServiceDeclaration; +import io.ballerina.openapi.service.mapper.model.ServiceNode; +import io.ballerina.projects.Project; +import io.ballerina.projects.plugins.AnalysisTask; +import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; +import io.ballerina.tools.diagnostics.DiagnosticSeverity; +import io.swagger.v3.core.util.Json; +import io.swagger.v3.oas.models.OpenAPI; + +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.normalizeTitle; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.diagnosticContainsErrors; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.getServiceDeclarationNode; + +/** + * This class generates the OpenAPI definition resource for the service declaration node. + * + * @since 2.12.0 + */ +public class ServiceOasGenerator implements AnalysisTask { + + @Override + public void perform(SyntaxNodeAnalysisContext context) { + if (diagnosticContainsErrors(context)) { + return; + } + + ServiceDeclarationNode serviceDeclarationNode = getServiceDeclarationNode(context); + if (serviceDeclarationNode == null) { + return; + } + + Optional serviceInfoAnnotation = getServiceInfoAnnotation(serviceDeclarationNode); + if (serviceInfoAnnotation.isEmpty()) { + return; + } + + boolean embedOpenAPI = retrieveValueFromAnnotation(serviceInfoAnnotation.get(), "embed") + .map(Boolean::parseBoolean) + .orElse(false); + if (!embedOpenAPI) { + return; + } + + Optional contractPath = retrieveValueFromAnnotation(serviceInfoAnnotation.get(), "contract"); + if (contractPath.isPresent()) { + return; + } + + Optional symbol = context.semanticModel().symbol(serviceDeclarationNode); + if (symbol.isEmpty()) { + // Add warning diagnostic + return; + } + String fileName = getFileName(symbol.get().hashCode()); + + + ServiceNode serviceNode = new ServiceDeclaration(serviceDeclarationNode, context.semanticModel()); + Optional openApi = generateOpenApi(fileName, context.currentPackage().project(), + context.semanticModel(), serviceNode); + if (openApi.isEmpty()) { + return; + } + + writeOpenApiAsTargetResource(context.currentPackage().project(), fileName, openApi.get()); + } + + protected static String getFileName(int hashCode) { + String hashString = Integer.toString(hashCode); + return String.format("openapi_%s.json", + hashString.startsWith("-") ? "0" + hashString.substring(1) : hashString); + } + + protected static void writeOpenApiAsTargetResource(Project project, String fileName, String openApi) { + Path targetPath = project.targetDir(); + // Create a folder resources if not exists + Path resourcesPath = targetPath.resolve("resources"); + if (!resourcesPath.toFile().exists()) { + try { + Files.createDirectory(resourcesPath); + } catch (IOException e) { + // Add warning diagnostic + return; + } + } + writeFile(fileName, openApi, resourcesPath); + } + + protected static void writeFile(String fileName, String content, Path dirPath) { + Path openApiPath = dirPath.resolve(fileName); + try (FileWriter writer = new FileWriter(openApiPath.toString(), StandardCharsets.UTF_8)) { + writer.write(content); + } catch (IOException e) { + // Add warning diagnostic + } + } + + private Optional getServiceInfoAnnotation(ServiceDeclarationNode serviceDeclarationNode) { + Optional metadata = serviceDeclarationNode.metadata(); + if (metadata.isEmpty()) { + return Optional.empty(); + } + MetadataNode metaData = metadata.get(); + NodeList annotations = metaData.annotations(); + String serviceInfoAnnotation = String.format("%s:%s", "openapi", "ServiceInfo"); + return annotations.stream() + .filter(ann -> serviceInfoAnnotation.equals(ann.annotReference().toString().trim())) + .findFirst(); + } + + private Optional retrieveValueFromAnnotation(AnnotationNode annotation, String fieldName) { + return annotation + .annotValue() + .map(MappingConstructorExpressionNode::fields) + .flatMap(fields -> + fields.stream() + .filter(fld -> fld instanceof SpecificFieldNode) + .map(fld -> (SpecificFieldNode) fld) + .filter(fld -> fieldName.equals(fld.fieldName().toString().trim())) + .findFirst() + ).flatMap(SpecificFieldNode::valueExpr) + .map(en -> en.toString().trim()); + } + + protected Optional generateOpenApi(String fileName, Project project, SemanticModel semanticModel, + ServiceNode serviceNode) { + OASGenerationMetaInfo.OASGenerationMetaInfoBuilder builder = new + OASGenerationMetaInfo.OASGenerationMetaInfoBuilder(); + builder.setServiceNode(serviceNode) + .setSemanticModel(semanticModel) + .setOpenApiFileName(fileName) + .setBallerinaFilePath(null) + .setProject(project); + OASResult oasResult = ServiceToOpenAPIMapper.generateOAS(builder.build()); + Optional openApiOpt = oasResult.getOpenAPI(); + if (oasResult.getDiagnostics().stream().anyMatch( + diagnostic -> diagnostic.getDiagnosticSeverity().equals(DiagnosticSeverity.ERROR)) + || openApiOpt.isEmpty()) { + // Add warning diagnostic + return Optional.empty(); + } + OpenAPI openApi = openApiOpt.get(); + if (openApi.getInfo().getTitle() == null || openApi.getInfo().getTitle().equals("/")) { + openApi.getInfo().setTitle(normalizeTitle(fileName)); + } + return Optional.of(Json.pretty(openApi)); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpServiceModifier.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpServiceModifier.java index bc548e22e7..0aac4114c2 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpServiceModifier.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpServiceModifier.java @@ -22,7 +22,7 @@ import io.ballerina.projects.DocumentId; import io.ballerina.projects.plugins.CodeModifier; import io.ballerina.projects.plugins.CodeModifierContext; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.OpenApiInfoUpdaterTask; +import io.ballerina.stdlib.http.compiler.codemodifier.contract.ContractInfoModifierTask; import io.ballerina.stdlib.http.compiler.codemodifier.payload.HttpPayloadParamIdentifier; import io.ballerina.stdlib.http.compiler.codemodifier.payload.PayloadAnnotationModifierTask; import io.ballerina.stdlib.http.compiler.codemodifier.payload.context.PayloadParamContext; @@ -38,19 +38,21 @@ */ public class HttpServiceModifier extends CodeModifier { private final Map payloadParamContextMap; + private final Map ctxData; - public HttpServiceModifier() { + public HttpServiceModifier(Map ctxData) { this.payloadParamContextMap = new HashMap<>(); + this.ctxData = ctxData; } @Override public void init(CodeModifierContext codeModifierContext) { + ctxData.put("HTTP_CODE_MODIFIER_EXECUTED", true); codeModifierContext.addSyntaxNodeAnalysisTask( new HttpPayloadParamIdentifier(this.payloadParamContextMap), List.of(SyntaxKind.SERVICE_DECLARATION, SyntaxKind.CLASS_DEFINITION, SyntaxKind.OBJECT_TYPE_DESC)); codeModifierContext.addSourceModifierTask(new PayloadAnnotationModifierTask(this.payloadParamContextMap)); - codeModifierContext.addSourceModifierTask(new OpenApiInfoUpdaterTask()); - codeModifierContext.addSourceModifierTask(new ServiceTypeModifierTask()); + codeModifierContext.addSourceModifierTask(new ContractInfoModifierTask()); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/ServiceTypeModifierTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/contract/ContractInfoModifierTask.java similarity index 72% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/ServiceTypeModifierTask.java rename to compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/contract/ContractInfoModifierTask.java index c3411523f8..04acbb807e 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/ServiceTypeModifierTask.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/contract/ContractInfoModifierTask.java @@ -15,17 +15,19 @@ * specific language governing permissions and limitations * under the License. */ -package io.ballerina.stdlib.http.compiler.codemodifier; +package io.ballerina.stdlib.http.compiler.codemodifier.contract; import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.syntax.tree.AbstractNodeFactory; import io.ballerina.compiler.syntax.tree.AnnotationNode; import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; +import io.ballerina.compiler.syntax.tree.MappingFieldNode; import io.ballerina.compiler.syntax.tree.MetadataNode; import io.ballerina.compiler.syntax.tree.ModuleMemberDeclarationNode; import io.ballerina.compiler.syntax.tree.ModulePartNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.SeparatedNodeList; import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; import io.ballerina.compiler.syntax.tree.SpecificFieldNode; import io.ballerina.compiler.syntax.tree.SyntaxKind; @@ -44,6 +46,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import static io.ballerina.compiler.syntax.tree.AbstractNodeFactory.createIdentifierToken; import static io.ballerina.compiler.syntax.tree.AbstractNodeFactory.createNodeList; @@ -66,7 +69,7 @@ * * @since 2.12.0 */ -public class ServiceTypeModifierTask implements ModifierTask { +public class ContractInfoModifierTask implements ModifierTask { @Override public void modify(SourceModifierContext modifierContext) { @@ -147,13 +150,61 @@ private ServiceDeclarationNode updateServiceDeclarationNode(ServiceDeclarationNo String[] annotStrings = annotName.split(COLON); if (SERVICE_CONFIG_ANNOTATION.equals(annotStrings[annotStrings.length - 1].trim()) && HTTP.equals(annotStrings[0].trim())) { - return serviceDeclarationNode; + return updateServiceConfigAnnotation(serviceTypeDesc.get(), serviceDeclarationNode, annotation); } } return addServiceConfigAnnotation(serviceTypeDesc.get(), serviceDeclarationNode); } + private ServiceDeclarationNode updateServiceConfigAnnotation(TypeDescriptorNode serviceTypeDesc, + ServiceDeclarationNode serviceDeclarationNode, + AnnotationNode serviceConfigAnnotation) { + SpecificFieldNode serviceTypeField = createSpecificFieldNode(null, createIdentifierToken("serviceType"), + createToken(COLON_TOKEN), serviceTypeDesc); + Optional serviceConfigConstruct = serviceConfigAnnotation.annotValue(); + MappingConstructorExpressionNode newServiceConfigConstruct; + if (serviceConfigConstruct.isEmpty() || serviceConfigConstruct.get().fields().isEmpty()) { + newServiceConfigConstruct = createMappingConstructorExpressionNode( + createToken(SyntaxKind.OPEN_BRACE_TOKEN), createSeparatedNodeList(serviceTypeField), + createToken(SyntaxKind.CLOSE_BRACE_TOKEN)); + } else { + MappingConstructorExpressionNode existingServiceConfigConstruct = serviceConfigConstruct.get(); + SeparatedNodeList fields = existingServiceConfigConstruct.fields(); + boolean hasServiceType = fields.stream().anyMatch(field -> { + if (field.kind().equals(SyntaxKind.SPECIFIC_FIELD)) { + SpecificFieldNode specificField = (SpecificFieldNode) field; + return specificField.fieldName().toString().equals("serviceType"); + } + return false; + }); + if (hasServiceType) { + return serviceDeclarationNode; + } + List fieldList = fields.stream().collect(Collectors.toList()); + fieldList.add(createToken(SyntaxKind.COMMA_TOKEN)); + fieldList.add(serviceTypeField); + SeparatedNodeList updatedFields = createSeparatedNodeList(fieldList); + newServiceConfigConstruct = createMappingConstructorExpressionNode( + createToken(SyntaxKind.OPEN_BRACE_TOKEN), updatedFields, + createToken(SyntaxKind.CLOSE_BRACE_TOKEN)); + } + AnnotationNode newServiceConfigAnnotation = serviceConfigAnnotation.modify() + .withAnnotValue(newServiceConfigConstruct).apply(); + Optional metadata = serviceDeclarationNode.metadata(); + if (metadata.isEmpty()) { + MetadataNode metadataNode = createMetadataNode(null, + createNodeList(newServiceConfigAnnotation)); + return serviceDeclarationNode.modify().withMetadata(metadataNode).apply(); + } + + NodeList updatedAnnotations = metadata.get().annotations() + .remove(serviceConfigAnnotation) + .add(newServiceConfigAnnotation); + MetadataNode metadataNode = metadata.get().modify().withAnnotations(updatedAnnotations).apply(); + return serviceDeclarationNode.modify().withMetadata(metadataNode).apply(); + } + private ServiceDeclarationNode addServiceConfigAnnotation(TypeDescriptorNode serviceTypeDesc, ServiceDeclarationNode serviceDeclarationNode) { SpecificFieldNode serviceTypeField = createSpecificFieldNode(null, createIdentifierToken("serviceType"), diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/Constants.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/Constants.java deleted file mode 100644 index 9c860b84d0..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/Constants.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.http.compiler.codemodifier.oas; - -/** - * {@code Constants} contains the common constants. - */ -public interface Constants { - // package details related constants - String PACKAGE_ORG = "ballerina"; - String PACKAGE_NAME = "openapi"; - - // open-api module related constants - String SERVICE_INFO_ANNOTATION_IDENTIFIER = "ServiceInfo"; - String CONTRACT = "contract"; - String EMBED = "embed"; - - // http module related constants - String HTTP_PACKAGE_NAME = "http"; - String SERVICE_CONFIG = "ServiceConfig"; - String SERVICE_CONTRACT_INFO = "ServiceContractInfo"; - String OPEN_API_DEFINITION_FIELD = "openApiDefinition"; - - String SLASH = "/"; -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/DocGenerationUtils.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/DocGenerationUtils.java deleted file mode 100644 index 85c2ecb543..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/DocGenerationUtils.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.http.compiler.codemodifier.oas; - -import io.ballerina.stdlib.http.compiler.HttpDiagnostic; -import io.ballerina.tools.diagnostics.Diagnostic; -import io.ballerina.tools.diagnostics.DiagnosticFactory; -import io.ballerina.tools.diagnostics.DiagnosticInfo; -import io.ballerina.tools.diagnostics.Location; - -/** - * {@code DocGenerationUtils} contains common utilities related to doc generation and resource packaging. - */ -public final class DocGenerationUtils { - public static Diagnostic getDiagnostics(HttpDiagnostic errorCode, - Location location, Object... args) { - DiagnosticInfo diagnosticInfo = new DiagnosticInfo( - errorCode.getCode(), errorCode.getMessage(), errorCode.getSeverity()); - return DiagnosticFactory.createDiagnostic(diagnosticInfo, location, args); - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/HttpServiceAnalysisTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/HttpServiceAnalysisTask.java deleted file mode 100644 index 2cfe6776cf..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/HttpServiceAnalysisTask.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.http.compiler.codemodifier.oas; - -import io.ballerina.compiler.api.SemanticModel; -import io.ballerina.compiler.syntax.tree.SyntaxTree; -import io.ballerina.openapi.service.mapper.model.ServiceNode; -import io.ballerina.projects.Project; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.gen.DocGeneratorManager; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.gen.OpenApiDocConfig; - -/** - * {@code HttpServiceAnalysisTask} analyses the HTTP service for which the OpenApi doc is generated. - */ -public class HttpServiceAnalysisTask { - private final DocGeneratorManager docGenerator; - - public HttpServiceAnalysisTask() { - this.docGenerator = new DocGeneratorManager(); - } - - public void perform(ServiceNodeAnalysisContext context) { - Project currentProject = context.currentPackage().project(); - ServiceNode serviceNode = context.node(); - SemanticModel semanticModel = context.semanticModel(); - SyntaxTree syntaxTree = context.syntaxTree(); - OpenApiDocConfig docConfig = new OpenApiDocConfig(context.currentPackage(), - semanticModel, syntaxTree, serviceNode, currentProject.kind()); - this.docGenerator.generate(docConfig, context, serviceNode.location()); - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/OpenApiInfoUpdaterTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/OpenApiInfoUpdaterTask.java deleted file mode 100644 index 9ff96b3279..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/OpenApiInfoUpdaterTask.java +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.http.compiler.codemodifier.oas; - -import io.ballerina.compiler.api.SemanticModel; -import io.ballerina.compiler.syntax.tree.AbstractNodeFactory; -import io.ballerina.compiler.syntax.tree.AnnotationNode; -import io.ballerina.compiler.syntax.tree.ExpressionNode; -import io.ballerina.compiler.syntax.tree.IdentifierToken; -import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; -import io.ballerina.compiler.syntax.tree.MappingFieldNode; -import io.ballerina.compiler.syntax.tree.MetadataNode; -import io.ballerina.compiler.syntax.tree.ModuleMemberDeclarationNode; -import io.ballerina.compiler.syntax.tree.ModulePartNode; -import io.ballerina.compiler.syntax.tree.Node; -import io.ballerina.compiler.syntax.tree.NodeFactory; -import io.ballerina.compiler.syntax.tree.NodeList; -import io.ballerina.compiler.syntax.tree.NodeParser; -import io.ballerina.compiler.syntax.tree.QualifiedNameReferenceNode; -import io.ballerina.compiler.syntax.tree.SeparatedNodeList; -import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; -import io.ballerina.compiler.syntax.tree.SpecificFieldNode; -import io.ballerina.compiler.syntax.tree.SyntaxKind; -import io.ballerina.compiler.syntax.tree.SyntaxTree; -import io.ballerina.compiler.syntax.tree.Token; -import io.ballerina.openapi.service.mapper.model.ServiceNode; -import io.ballerina.projects.Document; -import io.ballerina.projects.DocumentId; -import io.ballerina.projects.Module; -import io.ballerina.projects.ModuleId; -import io.ballerina.projects.Package; -import io.ballerina.projects.plugins.ModifierTask; -import io.ballerina.projects.plugins.SourceModifierContext; -import io.ballerina.stdlib.http.compiler.HttpDiagnostic; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.OpenApiDocContext; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; -import io.ballerina.tools.diagnostics.DiagnosticFactory; -import io.ballerina.tools.diagnostics.DiagnosticSeverity; -import io.ballerina.tools.text.TextDocument; - -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Base64; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -import static io.ballerina.openapi.service.mapper.ServiceToOpenAPIMapper.getServiceNode; -import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.getDiagnosticInfo; -import static io.ballerina.stdlib.http.compiler.codemodifier.oas.Constants.SERVICE_CONFIG; -import static io.ballerina.stdlib.http.compiler.codemodifier.oas.Constants.SERVICE_CONTRACT_INFO; -import static io.ballerina.stdlib.http.compiler.codemodifier.oas.context.OpenApiDocContextHandler.getContextHandler; - -/** - * {@code OpenApiInfoUpdaterTask} modifies the source by including generated open-api spec for http-service - * declarations. - */ -public class OpenApiInfoUpdaterTask implements ModifierTask { - @Override - public void modify(SourceModifierContext context) { - boolean erroneousCompilation = context.compilation().diagnosticResult() - .diagnostics().stream() - .anyMatch(d -> DiagnosticSeverity.ERROR.equals(d.diagnosticInfo().severity())); - // if the compilation already contains any error, do not proceed - if (erroneousCompilation) { - return; - } - - // Mocking the code analysis task - mockServiceAnalyzerExecution(context); - - for (OpenApiDocContext openApiContext: getContextHandler().retrieveAvailableContexts()) { - ModuleId moduleId = openApiContext.getModuleId(); - Module currentModule = context.currentPackage().module(moduleId); - DocumentId documentId = openApiContext.getDocumentId(); - Document currentDoc = currentModule.document(documentId); - SemanticModel semanticModel = context.compilation().getSemanticModel(moduleId); - ModulePartNode rootNode = currentDoc.syntaxTree().rootNode(); - NodeList newMembers = updateMemberNodes( - rootNode.members(), openApiContext.getOpenApiDetails(), context, semanticModel); - ModulePartNode newModulePart = rootNode.modify(rootNode.imports(), newMembers, rootNode.eofToken()); - SyntaxTree updatedSyntaxTree = currentDoc.syntaxTree().modifyWith(newModulePart); - TextDocument textDocument = updatedSyntaxTree.textDocument(); - if (currentModule.documentIds().contains(documentId)) { - context.modifySourceFile(textDocument, documentId); - } else { - context.modifyTestSourceFile(textDocument, documentId); - } - } - } - - private static void mockServiceAnalyzerExecution(SourceModifierContext context) { - Package currentPackage = context.currentPackage(); - HttpServiceAnalysisTask serviceAnalysisTask = new HttpServiceAnalysisTask(); - for (ModuleId moduleId : currentPackage.moduleIds()) { - Module currentModule = currentPackage.module(moduleId); - SemanticModel semanticModel = context.compilation().getSemanticModel(moduleId); - for (DocumentId documentId : currentModule.documentIds()) { - Document currentDoc = currentModule.document(documentId); - SyntaxTree syntaxTree = currentDoc.syntaxTree(); - ModulePartNode rootNode = syntaxTree.rootNode(); - NodeList members = rootNode.members(); - for (ModuleMemberDeclarationNode member: members) { - Optional serviceNode = getServiceNode(member, semanticModel); - if (serviceNode.isEmpty()) { - continue; - } - ServiceNodeAnalysisContext serviceNodeAnalysisContext = new ServiceNodeAnalysisContext( - currentPackage, moduleId, documentId, syntaxTree, semanticModel, - serviceNode.get()); - serviceAnalysisTask.perform(serviceNodeAnalysisContext); - serviceNodeAnalysisContext.diagnostics().forEach(context::reportDiagnostic); - } - } - } - } - - private NodeList updateMemberNodes(NodeList oldMembers, - List openApi, - SourceModifierContext context, - SemanticModel semanticModel) { - List updatedMembers = new LinkedList<>(); - for (ModuleMemberDeclarationNode memberNode : oldMembers) { - Optional serviceNode = getServiceNode(memberNode, semanticModel); - if (serviceNode.isEmpty()) { - updatedMembers.add(memberNode); - continue; - } - updateServiceDeclarationNode(openApi, context, serviceNode.get(), updatedMembers); - } - return AbstractNodeFactory.createNodeList(updatedMembers); - } - - private void updateServiceDeclarationNode(List openApi, - SourceModifierContext context, ServiceNode serviceNode, - List updatedMembers) { - ModuleMemberDeclarationNode memberNode = serviceNode.getInternalNode(); - Optional openApiDefOpt = openApi.stream() - .filter(service -> service.getServiceId() == serviceNode.getServiceId()) - .findFirst(); - if (openApiDefOpt.isEmpty()) { - updatedMembers.add(memberNode); - return; - } - OpenApiDocContext.OpenApiDefinition openApiDef = openApiDefOpt.get(); - if (!openApiDef.isAutoEmbedToService()) { - updatedMembers.add(memberNode); - return; - } - NodeList existingAnnotations = serviceNode.metadata().map(MetadataNode::annotations) - .orElseGet(NodeFactory::createEmptyNodeList); - NodeList updatedAnnotations = updateAnnotations(existingAnnotations, - openApiDef.getDefinition(), context, serviceNode.kind().equals(ServiceNode.Kind.SERVICE_OBJECT_TYPE)); - serviceNode.updateAnnotations(updatedAnnotations); - updatedMembers.add(serviceNode.getInternalNode()); - } - - private NodeList updateAnnotations(NodeList currentAnnotations, - String openApiDef, SourceModifierContext context, - boolean isServiceContract) { - NodeList updatedAnnotations = NodeFactory.createNodeList(); - String annotationToBeUpdated = isServiceContract ? SERVICE_CONTRACT_INFO : SERVICE_CONFIG; - boolean annotationAlreadyExists = false; - for (AnnotationNode annotation: currentAnnotations) { - if (isHttpAnnotation(annotation, annotationToBeUpdated)) { - annotationAlreadyExists = true; - SeparatedNodeList updatedFields = getUpdatedFields(annotation, openApiDef, context); - MappingConstructorExpressionNode annotationValue = - NodeFactory.createMappingConstructorExpressionNode( - NodeFactory.createToken(SyntaxKind.OPEN_BRACE_TOKEN), updatedFields, - NodeFactory.createToken(SyntaxKind.CLOSE_BRACE_TOKEN)); - annotation = annotation.modify().withAnnotValue(annotationValue).apply(); - } - updatedAnnotations = updatedAnnotations.add(annotation); - } - if (!annotationAlreadyExists) { - AnnotationNode openApiAnnotation = getHttpAnnotationWithOpenApi(annotationToBeUpdated, openApiDef); - updatedAnnotations = updatedAnnotations.add(openApiAnnotation); - } - return updatedAnnotations; - } - - private SeparatedNodeList getUpdatedFields(AnnotationNode annotation, String servicePath, - SourceModifierContext context) { - Optional annotationValueOpt = annotation.annotValue(); - if (annotationValueOpt.isEmpty()) { - return NodeFactory.createSeparatedNodeList(createOpenApiDefinitionField(servicePath, false)); - } - List fields = new ArrayList<>(); - MappingConstructorExpressionNode annotationValue = annotationValueOpt.get(); - SeparatedNodeList existingFields = annotationValue.fields(); - Token separator = NodeFactory.createToken(SyntaxKind.COMMA_TOKEN); - MappingFieldNode openApiDefNode = null; - for (MappingFieldNode field : existingFields) { - if (field instanceof SpecificFieldNode) { - String fieldName = ((SpecificFieldNode) field).fieldName().toString(); - if (Constants.OPEN_API_DEFINITION_FIELD.equals(fieldName.trim())) { - openApiDefNode = field; - continue; - } - } - fields.add(field); - fields.add(separator); - } - fields.add(createOpenApiDefinitionField(servicePath, false)); - if (Objects.nonNull(openApiDefNode)) { - context.reportDiagnostic(DiagnosticFactory.createDiagnostic( - getDiagnosticInfo(HttpDiagnostic.HTTP_WARNING_102), openApiDefNode.location())); - } - return NodeFactory.createSeparatedNodeList(fields); - } - - private AnnotationNode getHttpAnnotationWithOpenApi(String annotationName, String openApiDefinition) { - String configIdentifierString = Constants.HTTP_PACKAGE_NAME + SyntaxKind.COLON_TOKEN.stringValue() + - annotationName; - IdentifierToken identifierToken = NodeFactory.createIdentifierToken(configIdentifierString); - Token atToken = NodeFactory.createToken(SyntaxKind.AT_TOKEN); - SimpleNameReferenceNode nameReferenceNode = NodeFactory.createSimpleNameReferenceNode(identifierToken); - MappingConstructorExpressionNode annotValue = getAnnotationExpression(openApiDefinition, - annotationName.equals(SERVICE_CONTRACT_INFO)); - return NodeFactory.createAnnotationNode(atToken, nameReferenceNode, annotValue); - } - - private MappingConstructorExpressionNode getAnnotationExpression(String openApiDefinition, - boolean isServiceContract) { - Token openBraceToken = NodeFactory.createToken(SyntaxKind.OPEN_BRACE_TOKEN); - Token closeBraceToken = NodeFactory.createToken(SyntaxKind.CLOSE_BRACE_TOKEN); - SpecificFieldNode specificFieldNode = createOpenApiDefinitionField(openApiDefinition, isServiceContract); - SeparatedNodeList separatedNodeList = NodeFactory.createSeparatedNodeList(specificFieldNode); - return NodeFactory.createMappingConstructorExpressionNode(openBraceToken, separatedNodeList, closeBraceToken); - } - - private static SpecificFieldNode createOpenApiDefinitionField(String openApiDefinition, - boolean isServiceContract) { - IdentifierToken fieldName = AbstractNodeFactory.createIdentifierToken(Constants.OPEN_API_DEFINITION_FIELD); - Token colonToken = AbstractNodeFactory.createToken(SyntaxKind.COLON_TOKEN); - String encodedValue = Base64.getEncoder().encodeToString(openApiDefinition.getBytes(Charset.defaultCharset())); - String format = isServiceContract ? "\"%s\"" : "base64 `%s`.cloneReadOnly()"; - ExpressionNode expressionNode = NodeParser.parseExpression(String.format(format, encodedValue)); - return NodeFactory.createSpecificFieldNode(null, fieldName, colonToken, expressionNode); - } - - private boolean isHttpAnnotation(AnnotationNode annotationNode, String annotationName) { - if (!(annotationNode.annotReference() instanceof QualifiedNameReferenceNode referenceNode)) { - return false; - } - if (!Constants.HTTP_PACKAGE_NAME.equals(referenceNode.modulePrefix().text())) { - return false; - } - return annotationName.equals(referenceNode.identifier().text()); - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContext.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContext.java deleted file mode 100644 index fb6b908107..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContext.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.http.compiler.codemodifier.oas.context; - -import io.ballerina.projects.DocumentId; -import io.ballerina.projects.ModuleId; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - - -/** - * {@code OpenApiDocContext} contains details related to open-api doc generation. - */ -public class OpenApiDocContext { - private final ModuleId moduleId; - private final DocumentId documentId; - private final List definitions = new ArrayList<>(); - - OpenApiDocContext(ModuleId moduleId, DocumentId documentId) { - this.moduleId = moduleId; - this.documentId = documentId; - } - - public ModuleId getModuleId() { - return moduleId; - } - - public DocumentId getDocumentId() { - return documentId; - } - - public List getOpenApiDetails() { - return Collections.unmodifiableList(definitions); - } - - void updateOpenApiDetails(OpenApiDefinition definition) { - this.definitions.add(definition); - } - - /** - * {@code OpenApiDefinition} contains details related to generated open-api definition. - */ - public static class OpenApiDefinition { - private final int serviceId; - private final String definition; - private final boolean embed; - - public OpenApiDefinition(int serviceId, String definition, boolean embed) { - this.serviceId = serviceId; - this.definition = definition; - this.embed = embed; - } - - public int getServiceId() { - return serviceId; - } - - public String getDefinition() { - return definition; - } - - public boolean isAutoEmbedToService() { - return embed; - } - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContextHandler.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContextHandler.java deleted file mode 100644 index 52dca294f4..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContextHandler.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.http.compiler.codemodifier.oas.context; - -import io.ballerina.projects.DocumentId; -import io.ballerina.projects.ModuleId; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -/** - * {@code OpenApiDocContextHandler} will manage the shared context among compiler plugin tasks. - */ -public final class OpenApiDocContextHandler { - private static OpenApiDocContextHandler instance; - - private final List contexts; - - private OpenApiDocContextHandler(List contexts) { - this.contexts = contexts; - } - - public static OpenApiDocContextHandler getContextHandler() { - synchronized (OpenApiDocContextHandler.class) { - if (Objects.isNull(instance)) { - instance = new OpenApiDocContextHandler(new ArrayList<>()); - } - } - return instance; - } - - private void addContext(OpenApiDocContext context) { - synchronized (this.contexts) { - this.contexts.add(context); - } - } - - /** - * Update the shared context for open-api doc generation. - * @param moduleId of the current module - * @param documentId of the current file - * @param definition to be added to the context - */ - public void updateContext(ModuleId moduleId, DocumentId documentId, - OpenApiDocContext.OpenApiDefinition definition) { - Optional contextOpt = retrieveContext(moduleId, documentId); - if (contextOpt.isPresent()) { - OpenApiDocContext context = contextOpt.get(); - synchronized (context) { - context.updateOpenApiDetails(definition); - } - return; - } - OpenApiDocContext context = new OpenApiDocContext(moduleId, documentId); - context.updateOpenApiDetails(definition); - addContext(context); - } - - private Optional retrieveContext(ModuleId moduleId, DocumentId documentId) { - return this.contexts.stream() - .filter(ctx -> equals(ctx, moduleId, documentId)) - .findFirst(); - } - - public List retrieveAvailableContexts() { - return Collections.unmodifiableList(contexts); - } - - private boolean equals(OpenApiDocContext context, ModuleId moduleId, DocumentId documentId) { - int hashCodeForCurrentContext = Objects.hash(context.getModuleId(), context.getDocumentId()); - return hashCodeForCurrentContext == Objects.hash(moduleId, documentId); - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/ServiceNodeAnalysisContext.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/ServiceNodeAnalysisContext.java deleted file mode 100644 index 3b3a02cb4d..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/ServiceNodeAnalysisContext.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.http.compiler.codemodifier.oas.context; - -import io.ballerina.compiler.api.SemanticModel; -import io.ballerina.compiler.syntax.tree.SyntaxTree; -import io.ballerina.openapi.service.mapper.model.ServiceNode; -import io.ballerina.projects.DocumentId; -import io.ballerina.projects.ModuleId; -import io.ballerina.projects.Package; -import io.ballerina.tools.diagnostics.Diagnostic; - -import java.util.ArrayList; -import java.util.List; - -/** - * {@code ServiceNodeAnalysisContext} will store service node analysis context. - */ -public class ServiceNodeAnalysisContext { - - private final ServiceNode node; - private final ModuleId moduleId; - private final DocumentId documentId; - private final SyntaxTree syntaxTree; - private final SemanticModel semanticModel; - private final Package currentPackage; - private final List diagnostics; - - public ServiceNodeAnalysisContext(Package currentPackage, ModuleId moduleId, DocumentId documentId, - SyntaxTree syntaxTree, SemanticModel semanticModel, - ServiceNode node) { - this.moduleId = moduleId; - this.documentId = documentId; - this.syntaxTree = syntaxTree; - this.semanticModel = semanticModel; - this.currentPackage = currentPackage; - this.diagnostics = new ArrayList<>(); - this.node = node; - } - - public ServiceNode node() { - return node; - } - - public ModuleId moduleId() { - return moduleId; - } - - public DocumentId documentId() { - return documentId; - } - - public SyntaxTree syntaxTree() { - return syntaxTree; - } - - public SemanticModel semanticModel() { - return semanticModel; - } - - public Package currentPackage() { - return currentPackage; - } - - public void reportDiagnostic(Diagnostic diagnosticCode) { - diagnostics.add(diagnosticCode); - } - - public List diagnostics() { - return diagnostics; - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java deleted file mode 100644 index 016ad4f7cd..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.http.compiler.codemodifier.oas.gen; - -import io.ballerina.compiler.syntax.tree.AnnotationNode; -import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; -import io.ballerina.compiler.syntax.tree.MetadataNode; -import io.ballerina.compiler.syntax.tree.NodeList; -import io.ballerina.compiler.syntax.tree.SpecificFieldNode; -import io.ballerina.openapi.service.mapper.ServiceToOpenAPIMapper; -import io.ballerina.openapi.service.mapper.model.OASGenerationMetaInfo; -import io.ballerina.openapi.service.mapper.model.OASResult; -import io.ballerina.openapi.service.mapper.model.ServiceNode; -import io.ballerina.projects.Package; -import io.ballerina.projects.Project; -import io.ballerina.stdlib.http.compiler.HttpDiagnostic; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.Constants; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.OpenApiDocContext; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; -import io.ballerina.tools.diagnostics.Diagnostic; -import io.ballerina.tools.diagnostics.DiagnosticSeverity; -import io.ballerina.tools.diagnostics.Location; -import io.swagger.v3.core.util.Json; -import io.swagger.v3.oas.models.OpenAPI; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Optional; - -import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.normalizeTitle; -import static io.ballerina.stdlib.http.compiler.codemodifier.oas.context.OpenApiDocContextHandler.getContextHandler; -import static io.ballerina.stdlib.http.compiler.codemodifier.oas.DocGenerationUtils.getDiagnostics; - -/** - * {@code AbstractOpenApiDocGenerator} contains the basic utilities required for OpenAPI doc generation. - */ -public abstract class AbstractOpenApiDocGenerator implements OpenApiDocGenerator { - private static final String FILE_NAME_FORMAT = "%d.json"; - - private final OpenApiContractResolver contractResolver; - - public AbstractOpenApiDocGenerator() { - this.contractResolver = new OpenApiContractResolver(); - } - - @Override - public void generate(OpenApiDocConfig config, ServiceNodeAnalysisContext context, Location location) { - try { - int serviceId = config.getServiceId(); - Package currentPackage = config.currentPackage(); - Path srcRoot = currentPackage.project().sourceRoot(); - - // find the project root path - Path projectRoot = retrieveProjectRoot(srcRoot); - - ServiceNode serviceNode = config.serviceNode(); - Optional serviceInfoAnnotationOpt = getServiceInfoAnnotation(serviceNode); - if (serviceInfoAnnotationOpt.isPresent()) { - AnnotationNode serviceInfoAnnotation = serviceInfoAnnotationOpt.get(); - - boolean embed = retrieveValueForAnnotationFields(serviceInfoAnnotation, Constants.EMBED) - .map(Boolean::parseBoolean) - .orElse(false); - - // use the available open-api doc and update the context - OpenApiContractResolver.ResolverResponse resolverResponse = this.contractResolver - .resolve(serviceInfoAnnotation, projectRoot); - if (resolverResponse.isContractAvailable()) { - // could not find the open-api contract file, hence will not proceed - if (resolverResponse.getContractPath().isEmpty()) { - return; - } - String openApiDefinition = Files.readString(resolverResponse.getContractPath().get()); - updateOpenApiContext(context, serviceId, openApiDefinition, embed); - } else { - // generate open-api doc and update the context if the `contract` configuration is not available - generateOpenApiDoc(currentPackage.project(), config, context, location, embed); - } - } else if (serviceNode.kind().equals(ServiceNode.Kind.SERVICE_OBJECT_TYPE)) { - // generate open-api doc and update the context if the service is a service contract type - generateOpenApiDoc(currentPackage.project(), config, context, location, true); - } - } catch (IOException | RuntimeException e) { - // currently, we do not have open-api doc generation logic for following scenarios: - // 1. default resources and for scenarios - // 2. returning http-response from a resource - // hence logs are disabled for now - } - } - - private void updateOpenApiContext(ServiceNodeAnalysisContext context, int serviceId, String openApiDefinition, - boolean embed) { - OpenApiDocContext.OpenApiDefinition openApiDef = new OpenApiDocContext.OpenApiDefinition(serviceId, - openApiDefinition, embed); - getContextHandler().updateContext(context.moduleId(), context.documentId(), openApiDef); - } - - private void updateCompilerContext(ServiceNodeAnalysisContext context, Location location, - HttpDiagnostic errorCode) { - Diagnostic diagnostic = getDiagnostics(errorCode, location); - context.reportDiagnostic(diagnostic); - } - - private Optional getServiceInfoAnnotation(ServiceNode serviceNode) { - Optional metadata = serviceNode.metadata(); - if (metadata.isEmpty()) { - return Optional.empty(); - } - MetadataNode metaData = metadata.get(); - NodeList annotations = metaData.annotations(); - String serviceInfoAnnotation = String.format("%s:%s", - Constants.PACKAGE_NAME, Constants.SERVICE_INFO_ANNOTATION_IDENTIFIER); - return annotations.stream() - .filter(ann -> serviceInfoAnnotation.equals(ann.annotReference().toString().trim())) - .findFirst(); - } - - private Optional retrieveValueForAnnotationFields(AnnotationNode serviceInfoAnnotation, String fieldName) { - return serviceInfoAnnotation - .annotValue() - .map(MappingConstructorExpressionNode::fields) - .flatMap(fields -> - fields.stream() - .filter(fld -> fld instanceof SpecificFieldNode) - .map(fld -> (SpecificFieldNode) fld) - .filter(fld -> fieldName.equals(fld.fieldName().toString().trim())) - .findFirst() - ).flatMap(SpecificFieldNode::valueExpr) - .map(en -> en.toString().trim()); - } - - private void generateOpenApiDoc(Project project, OpenApiDocConfig config, ServiceNodeAnalysisContext context, - Location location, boolean embed) { - if (!embed) { - return; - } - int serviceId = config.getServiceId(); - String targetFile = String.format(FILE_NAME_FORMAT, serviceId); - OASGenerationMetaInfo.OASGenerationMetaInfoBuilder builder = new - OASGenerationMetaInfo.OASGenerationMetaInfoBuilder(); - builder.setServiceNode(config.serviceNode()) - .setSemanticModel(config.semanticModel()) - .setOpenApiFileName(targetFile) - .setBallerinaFilePath(null) - .setProject(project); - OASResult oasResult = ServiceToOpenAPIMapper.generateOAS(builder.build()); - Optional openApiOpt = oasResult.getOpenAPI(); - if (oasResult.getDiagnostics().stream().anyMatch( - diagnostic -> diagnostic.getDiagnosticSeverity().equals(DiagnosticSeverity.ERROR)) - || openApiOpt.isEmpty()) { - HttpDiagnostic errorCode = HttpDiagnostic.HTTP_WARNING_101; - updateCompilerContext(context, location, errorCode); - return; - } - OpenAPI openApi = openApiOpt.get(); - if (openApi.getInfo().getTitle() == null || openApi.getInfo().getTitle().equals(Constants.SLASH)) { - openApi.getInfo().setTitle(normalizeTitle(targetFile)); - } - String openApiDefinition = Json.pretty(openApi); - updateOpenApiContext(context, serviceId, openApiDefinition, true); - } - - protected Path retrieveProjectRoot(Path projectRoot) { - return projectRoot; - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/BalProjectOpenApiDocGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/BalProjectOpenApiDocGenerator.java deleted file mode 100644 index a31c71cdf1..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/BalProjectOpenApiDocGenerator.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.http.compiler.codemodifier.oas.gen; - -import io.ballerina.projects.ProjectKind; - -/** - * {@code BalProjectOpenApiDocGenerator} generates open-api related docs for HTTP service defined in ballerina projects. - */ -public class BalProjectOpenApiDocGenerator extends AbstractOpenApiDocGenerator { - @Override - public boolean isSupported(ProjectKind projectType) { - return ProjectKind.BUILD_PROJECT.equals(projectType); - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/DocGeneratorManager.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/DocGeneratorManager.java deleted file mode 100644 index 43b385171c..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/DocGeneratorManager.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.http.compiler.codemodifier.oas.gen; - -import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; -import io.ballerina.tools.diagnostics.Location; - -import java.util.List; - -/** - * {@code DocGeneratorManager} manages OpenAPI doc generation for HTTP services depending on whether the current project - * is a ballerina-project or a single ballerina file. - */ -public final class DocGeneratorManager { - private final List docGenerators; - - public DocGeneratorManager() { - this.docGenerators = List.of(new SingleFileOpenApiDocGenerator(), new BalProjectOpenApiDocGenerator()); - } - - public void generate(OpenApiDocConfig config, ServiceNodeAnalysisContext context, Location location) { - docGenerators.stream() - .filter(dg -> dg.isSupported(config.projectType())) - .findFirst() - .ifPresent(dg -> dg.generate(config, context, location)); - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiContractResolver.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiContractResolver.java deleted file mode 100644 index 164e4909ea..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiContractResolver.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.http.compiler.codemodifier.oas.gen; - -import io.ballerina.compiler.syntax.tree.AnnotationNode; -import io.ballerina.compiler.syntax.tree.ExpressionNode; -import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; -import io.ballerina.compiler.syntax.tree.MappingFieldNode; -import io.ballerina.compiler.syntax.tree.SeparatedNodeList; -import io.ballerina.compiler.syntax.tree.SpecificFieldNode; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.Constants; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Optional; - -/** - * {@code OpenApiContractResolver} resolves the provided OpenAPI doc via `openapi:ServiceInfo` annotation. - */ -public final class OpenApiContractResolver { - public ResolverResponse resolve(AnnotationNode serviceInfoAnnotation, Path projectRoot) { - Optional mappingConstructorExpressionNode = - serviceInfoAnnotation.annotValue(); - if (mappingConstructorExpressionNode.isEmpty()) { - // if details not available do not proceed - return new ResolverResponse(false); - } - - MappingConstructorExpressionNode exprNode = mappingConstructorExpressionNode.get(); - SeparatedNodeList fieldsOpt = exprNode.fields(); - if (fieldsOpt.isEmpty()) { - // if details not available do not proceed - return new ResolverResponse(false); - } - - Optional> annotationFieldsOpt = serviceInfoAnnotation - .annotValue().map(MappingConstructorExpressionNode::fields); - if (annotationFieldsOpt.isEmpty()) { - // annotation fields are not available, hence will not proceed - return new ResolverResponse(false); - } - - SeparatedNodeList annotationFields = annotationFieldsOpt.get(); - Optional contractFieldOpt = annotationFields.stream() - .filter(fld -> fld instanceof SpecificFieldNode) - .map(fld -> (SpecificFieldNode) fld) - .filter(fld -> Constants.CONTRACT.equals(fld.fieldName().toString().trim())) - .findFirst(); - if (contractFieldOpt.isEmpty()) { - // could not find the `contract` field in the service-info annotation, hence will not proceed - return new ResolverResponse(false); - } - - SpecificFieldNode openApiContract = contractFieldOpt.get(); - Optional openApiContractValueOpt = openApiContract.valueExpr(); - if (openApiContractValueOpt.isEmpty()) { - // could not find the value for `contract` field in the service-info annotation, - // hence will not proceed - return new ResolverResponse(true); - } - - ExpressionNode openApiContractValue = openApiContractValueOpt.get(); - String openApiContractPath = openApiContractValue.toString() - .replaceAll("\"", "").trim(); - if (openApiContractPath.isBlank()) { - // `contract` value is empty, hence will not proceed - return new ResolverResponse(true); - } - - Path pathToOpenApiContract = getPathToOpenApiContract(openApiContractPath, projectRoot); - if (!Files.exists(pathToOpenApiContract)) { - // could not find open-api contract file, hence will not proceed - return new ResolverResponse(true); - } - - return new ResolverResponse(pathToOpenApiContract); - } - - private Path getPathToOpenApiContract(String openApiPath, Path projectRoot) { - Path openApiDocPath = Paths.get(openApiPath); - if (openApiDocPath.isAbsolute()) { - return openApiDocPath; - } else { - return projectRoot.resolve(openApiDocPath); - } - } - - /** - * {@code ResolverResponse} contains the response for OpenAPI doc retrieval via `openapi:ServiceInfo` annotation. - */ - public static class ResolverResponse { - private final boolean contractAvailable; - private Path contractPath; - - ResolverResponse(boolean contractAvailable) { - this.contractAvailable = contractAvailable; - } - - ResolverResponse(Path contractPath) { - this.contractAvailable = true; - this.contractPath = contractPath; - } - - public boolean isContractAvailable() { - return contractAvailable; - } - - public Optional getContractPath() { - return Optional.ofNullable(this.contractPath); - } - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocConfig.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocConfig.java deleted file mode 100644 index 7fa6ddf1a2..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocConfig.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.http.compiler.codemodifier.oas.gen; - -import io.ballerina.compiler.api.SemanticModel; -import io.ballerina.compiler.syntax.tree.SyntaxTree; -import io.ballerina.openapi.service.mapper.model.ServiceNode; -import io.ballerina.projects.Package; -import io.ballerina.projects.ProjectKind; - -/** - * {@code OpenApiDocConfig} contains the configurations related to generate OpenAPI doc. - * - * @param currentPackage current package - * @param semanticModel semantic model - * @param syntaxTree syntax tree - * @param serviceNode service node - * @param projectType project type - */ -public record OpenApiDocConfig(Package currentPackage, SemanticModel semanticModel, SyntaxTree syntaxTree, - ServiceNode serviceNode, ProjectKind projectType) { - - public int getServiceId() { - return serviceNode.getServiceId(); - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocGenerator.java deleted file mode 100644 index 6313ac807d..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocGenerator.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.http.compiler.codemodifier.oas.gen; - -import io.ballerina.projects.ProjectKind; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; -import io.ballerina.tools.diagnostics.Location; - -/** - * {@code Generator} generates open-api related docs for HTTP service. - */ -public interface OpenApiDocGenerator { - void generate(OpenApiDocConfig config, ServiceNodeAnalysisContext context, Location location); - - boolean isSupported(ProjectKind projectType); -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/SingleFileOpenApiDocGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/SingleFileOpenApiDocGenerator.java deleted file mode 100644 index a771d3c7c8..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/SingleFileOpenApiDocGenerator.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you 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 io.ballerina.stdlib.http.compiler.codemodifier.oas.gen; - -import io.ballerina.projects.ProjectKind; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Objects; - -/** - * {@code SingleFileOpenApiDocGenerator} generates open-api related docs for HTTP service defined in single ballerina - * file. - */ -public class SingleFileOpenApiDocGenerator extends AbstractOpenApiDocGenerator { - @Override - public boolean isSupported(ProjectKind projectType) { - return ProjectKind.SINGLE_FILE_PROJECT.equals(projectType); - } - - @Override - protected Path retrieveProjectRoot(Path projectRoot) { - // For single ballerina file, project root will be the absolute path for that particular ballerina file - // hence project root should be updated to the directory which contains the ballerina file - Path parentDirectory = retrieveParentDirectory(projectRoot); - if (Objects.nonNull(parentDirectory) && Files.exists(parentDirectory)) { - return parentDirectory; - } - return projectRoot.isAbsolute() ? projectRoot : projectRoot.toAbsolutePath(); - } - - private Path retrieveParentDirectory(Path projectRoot) { - if (projectRoot.isAbsolute()) { - return projectRoot.getParent(); - } else { - return projectRoot.toAbsolutePath().getParent(); - } - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/HttpPayloadParamIdentifier.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/HttpPayloadParamIdentifier.java index 21ed4d453d..6995a88aac 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/HttpPayloadParamIdentifier.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/HttpPayloadParamIdentifier.java @@ -68,13 +68,15 @@ import static io.ballerina.stdlib.http.compiler.Constants.TABLE_OF_ANYDATA_MAP; import static io.ballerina.stdlib.http.compiler.Constants.TUPLE_OF_ANYDATA; import static io.ballerina.stdlib.http.compiler.Constants.XML; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.diagnosticContainsErrors; import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.getCtxTypes; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.getServiceDeclarationNode; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.isHttpServiceType; import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.subtypeOf; import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.updateDiagnostic; import static io.ballerina.stdlib.http.compiler.HttpResourceValidator.getEffectiveType; import static io.ballerina.stdlib.http.compiler.HttpResourceValidator.isValidBasicParamType; import static io.ballerina.stdlib.http.compiler.HttpResourceValidator.isValidNilableBasicParamType; -import static io.ballerina.stdlib.http.compiler.HttpServiceObjTypeAnalyzer.isHttpServiceType; /** diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/PayloadAnnotationModifierTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/PayloadAnnotationModifierTask.java index 24d4befcc7..d22d33faf2 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/PayloadAnnotationModifierTask.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/PayloadAnnotationModifierTask.java @@ -60,7 +60,7 @@ import java.util.List; import java.util.Map; -import static io.ballerina.stdlib.http.compiler.HttpServiceObjTypeAnalyzer.isHttpServiceType; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.isHttpServiceType; import static io.ballerina.stdlib.http.compiler.HttpServiceValidator.isServiceContractImplementation; /** diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java index 5270c8105f..ccdf7bcd84 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java @@ -44,11 +44,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.stream.Collectors; @@ -238,6 +241,7 @@ public static HttpService buildHttpService(BObject service, String basePath) { HttpService httpService = new HttpService(service, basePath); BMap serviceConfig = getHttpServiceConfigAnnotation(service); httpService.populateServiceConfig(serviceConfig); + httpService.populateIntrospectionPayload(); return httpService; } @@ -248,6 +252,7 @@ protected void populateServiceConfig(BMap serviceConfig) { this.setChunkingConfig(serviceConfig.get(HttpConstants.ANN_CONFIG_ATTR_CHUNKING).toString()); this.setCorsHeaders(CorsHeaders.buildCorsHeaders(serviceConfig.getMapValue(CORS_FIELD))); this.setHostName(serviceConfig.getStringValue(HOST_FIELD).getValue().trim()); + // TODO: Remove once the field is removed from the annotation this.setIntrospectionPayload(serviceConfig.getArrayValue(OPENAPI_DEF_FIELD).getByteArray()); if (serviceConfig.containsKey(MEDIA_TYPE_SUBTYPE_PREFIX)) { this.setMediaTypeSubtypePrefix(serviceConfig.getStringValue(MEDIA_TYPE_SUBTYPE_PREFIX) @@ -413,6 +418,36 @@ protected static BMap getServiceConfigAnnotation(BObject service, String package fromString(key + ":" + annotationName)); } + private static Optional getOpenApiDocFileName(BObject service) { + BMap openApiDocMap = (BMap) ((ObjectType) TypeUtils.getReferredType(TypeUtils.getType(service))).getAnnotation( + fromString("ballerina/lang.annotations:0:IntrospectionDocConfig")); + if (Objects.isNull(openApiDocMap)) { + return Optional.empty(); + } + BString name = openApiDocMap.getStringValue(fromString("name")); + return Objects.isNull(name) ? Optional.empty() : Optional.of(name.getValue()); + } + + protected void populateIntrospectionPayload() { + Optional openApiFileNameOpt = getOpenApiDocFileName(balService); + if (openApiFileNameOpt.isEmpty()) { + return; + } + // Load from resources + String openApiFileName = openApiFileNameOpt.get(); + String openApiDocPath = String.format("resources/openapi_%s.json", + openApiFileName.startsWith("-") ? "0" + openApiFileName.substring(1) : openApiFileName); + try (InputStream is = HttpService.class.getClassLoader().getResourceAsStream(openApiDocPath)) { + if (Objects.isNull(is)) { + log.debug("OpenAPI definition is not available in the resources"); + return; + } + this.setIntrospectionPayload(is.readAllBytes()); + } catch (IOException e) { + log.debug("Error while loading OpenAPI definition from resources", e); + } + } + @Override public String getOasResourceLink() { if (this.oasResourceLinks.isEmpty()) { diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpServiceFromContract.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpServiceFromContract.java index e687cf069f..531521ac73 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpServiceFromContract.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpServiceFromContract.java @@ -48,6 +48,7 @@ protected HttpServiceFromContract(BObject service, String basePath, ReferenceTyp public static HttpService buildHttpService(BObject service, String basePath, ReferenceType serviceContractType) { HttpService httpService = new HttpServiceFromContract(service, basePath, serviceContractType); + httpService.populateIntrospectionPayload(); BMap serviceConfig = getHttpServiceConfigAnnotation(serviceContractType); httpService.populateServiceConfig(serviceConfig); return httpService;