From 719d62c70d284e8f5514aec2c9d712b95cacea16 Mon Sep 17 00:00:00 2001 From: Hasitha Athukorala Date: Tue, 21 Sep 2021 20:16:36 +0530 Subject: [PATCH] Add service types generation --- .../application/CodeGenerator.java | 9 ++- .../configuration/Constants.java | 3 + .../controller/SchemaController.java | 1 - .../controller/ServiceTypesController.java | 75 ++++++++++++++++++ .../usecase/ExtractServiceTypesFromSpec.java | 76 +++++++++++++++++++ .../usecase/GenerateRecordNode.java | 6 +- .../usecase/GenerateServiceTypeNode.java | 65 ++++++++++++++++ .../usecase/utils/CodegenUtils.java | 4 +- src/main/resources/types.bal | 7 -- 9 files changed, 229 insertions(+), 17 deletions(-) create mode 100644 src/main/java/io/ballerina/asyncapi/codegenerator/controller/ServiceTypesController.java create mode 100644 src/main/java/io/ballerina/asyncapi/codegenerator/usecase/ExtractServiceTypesFromSpec.java create mode 100644 src/main/java/io/ballerina/asyncapi/codegenerator/usecase/GenerateServiceTypeNode.java delete mode 100644 src/main/resources/types.bal diff --git a/src/main/java/io/ballerina/asyncapi/codegenerator/application/CodeGenerator.java b/src/main/java/io/ballerina/asyncapi/codegenerator/application/CodeGenerator.java index 27521708d..64ff5e687 100644 --- a/src/main/java/io/ballerina/asyncapi/codegenerator/application/CodeGenerator.java +++ b/src/main/java/io/ballerina/asyncapi/codegenerator/application/CodeGenerator.java @@ -25,6 +25,7 @@ import io.ballerina.asyncapi.codegenerator.configuration.Constants; import io.ballerina.asyncapi.codegenerator.controller.Controller; import io.ballerina.asyncapi.codegenerator.controller.SchemaController; +import io.ballerina.asyncapi.codegenerator.controller.ServiceTypesController; import io.ballerina.asyncapi.codegenerator.repository.FileRepository; import io.ballerina.asyncapi.codegenerator.repository.FileRepositoryImpl; @@ -42,7 +43,7 @@ public void generate() throws BallerinaAsyncApiException { String asyncApiSpecJson; if (specPath.endsWith(".json")) { asyncApiSpecJson = asyncApiSpecYaml; - } else if (specPath.endsWith("yaml")) { + } else if (specPath.endsWith("yaml") || specPath.endsWith("yml")) { try { asyncApiSpecJson = convertYamlToJson(asyncApiSpecYaml); } catch (JsonProcessingException e) { @@ -51,10 +52,12 @@ public void generate() throws BallerinaAsyncApiException { } else { throw new BallerinaAsyncApiException("Unknown file type: ".concat(specPath)); } - String balTemplate = fileRepository.getFileContentFromResources(Constants.DATA_TYPES_BAL_TEMPLATE_FILE_NAME); Controller schemaController = new SchemaController(); - schemaController.generateBalCode(asyncApiSpecJson, balTemplate); + schemaController.generateBalCode(asyncApiSpecJson, ""); + + Controller serviceTypesController = new ServiceTypesController(); + serviceTypesController.generateBalCode(asyncApiSpecJson, ""); } String convertYamlToJson(String yaml) throws JsonProcessingException { diff --git a/src/main/java/io/ballerina/asyncapi/codegenerator/configuration/Constants.java b/src/main/java/io/ballerina/asyncapi/codegenerator/configuration/Constants.java index 9e583df65..051acd321 100644 --- a/src/main/java/io/ballerina/asyncapi/codegenerator/configuration/Constants.java +++ b/src/main/java/io/ballerina/asyncapi/codegenerator/configuration/Constants.java @@ -57,6 +57,9 @@ public final class Constants { public static final String FLOAT = "float"; public static final String DOUBLE = "double"; + public static final String X_BALLERINA_EVENT_TYPE = "x-ballerina-event-type"; + public static final String X_BALLERINA_SERVICE_TYPE = "x-ballerina-service-type"; + private Constants() { } } diff --git a/src/main/java/io/ballerina/asyncapi/codegenerator/controller/SchemaController.java b/src/main/java/io/ballerina/asyncapi/codegenerator/controller/SchemaController.java index 6e6df1959..fb00ddcf0 100644 --- a/src/main/java/io/ballerina/asyncapi/codegenerator/controller/SchemaController.java +++ b/src/main/java/io/ballerina/asyncapi/codegenerator/controller/SchemaController.java @@ -28,7 +28,6 @@ import io.ballerina.compiler.syntax.tree.ModuleMemberDeclarationNode; import io.ballerina.compiler.syntax.tree.ModulePartNode; import io.ballerina.compiler.syntax.tree.SyntaxTree; -import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; import io.ballerina.tools.text.TextDocuments; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/src/main/java/io/ballerina/asyncapi/codegenerator/controller/ServiceTypesController.java b/src/main/java/io/ballerina/asyncapi/codegenerator/controller/ServiceTypesController.java new file mode 100644 index 000000000..a58cdc9c9 --- /dev/null +++ b/src/main/java/io/ballerina/asyncapi/codegenerator/controller/ServiceTypesController.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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.asyncapi.codegenerator.controller; + +import io.apicurio.datamodels.Library; +import io.apicurio.datamodels.asyncapi.models.AaiChannelItem; +import io.apicurio.datamodels.asyncapi.models.AaiDocument; +import io.apicurio.datamodels.asyncapi.models.AaiMessage; +import io.apicurio.datamodels.asyncapi.v2.models.Aai20Document; +import io.ballerina.asyncapi.codegenerator.configuration.BallerinaAsyncApiException; +import io.ballerina.asyncapi.codegenerator.configuration.Constants; +import io.ballerina.asyncapi.codegenerator.usecase.ExtractServiceTypesFromSpec; +import io.ballerina.asyncapi.codegenerator.usecase.GenerateServiceTypeNode; +import io.ballerina.asyncapi.codegenerator.usecase.UseCase; +import io.ballerina.compiler.syntax.tree.ModuleMemberDeclarationNode; +import io.ballerina.compiler.syntax.tree.ModulePartNode; +import io.ballerina.compiler.syntax.tree.SyntaxTree; +import io.ballerina.tools.text.TextDocuments; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.ballerinalang.formatter.core.Formatter; +import org.ballerinalang.formatter.core.FormatterException; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ServiceTypesController implements Controller { + private static final Logger logger = LogManager.getLogger(ServiceTypesController.class); + + @Override + public void generateBalCode(String spec, String balTemplate) throws BallerinaAsyncApiException { + AaiDocument asyncApiSpec = (Aai20Document) Library.readDocumentFromJSONString(spec); + + UseCase extractServiceTypes = new ExtractServiceTypesFromSpec(asyncApiSpec); + Map> serviceTypes = extractServiceTypes.execute(); + + List serviceNodes = new ArrayList<>(); + for (Map.Entry> service : serviceTypes.entrySet()) { + UseCase generateServiceTypeNode = new GenerateServiceTypeNode(service.getKey(), service.getValue()); + serviceNodes.add(generateServiceTypeNode.execute()); + } + + var textDocument = TextDocuments.from(balTemplate); + var syntaxTree = SyntaxTree.from(textDocument); + ModulePartNode oldRoot = syntaxTree.rootNode(); + ModulePartNode newRoot = oldRoot.modify().withMembers(oldRoot.members().addAll(serviceNodes)).apply(); + var modifiedTree = syntaxTree.replaceNode(oldRoot, newRoot); + + try { + var formattedSourceCode = Formatter.format(modifiedTree).toSourceCode(); + logger.debug("Generated the source code for the service types: {}", formattedSourceCode); + } catch (FormatterException e) { + logger.error("Could not format the generated code, may be syntax issue in the generated code. " + + "Generated code: {}", modifiedTree.toSourceCode()); + } + } +} diff --git a/src/main/java/io/ballerina/asyncapi/codegenerator/usecase/ExtractServiceTypesFromSpec.java b/src/main/java/io/ballerina/asyncapi/codegenerator/usecase/ExtractServiceTypesFromSpec.java new file mode 100644 index 000000000..768be3fcb --- /dev/null +++ b/src/main/java/io/ballerina/asyncapi/codegenerator/usecase/ExtractServiceTypesFromSpec.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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.asyncapi.codegenerator.usecase; + +import io.apicurio.datamodels.asyncapi.models.AaiChannelItem; +import io.apicurio.datamodels.asyncapi.models.AaiDocument; +import io.apicurio.datamodels.asyncapi.models.AaiMessage; +import io.ballerina.asyncapi.codegenerator.configuration.BallerinaAsyncApiException; +import io.ballerina.asyncapi.codegenerator.configuration.Constants; +import io.ballerina.asyncapi.codegenerator.usecase.utils.CodegenUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ExtractServiceTypesFromSpec implements UseCase { + private final AaiDocument asyncApiSpec; + private final CodegenUtils codegenUtils = new CodegenUtils(); + + public ExtractServiceTypesFromSpec(AaiDocument asyncApiSpec) { + this.asyncApiSpec = asyncApiSpec; + } + + @Override + public Map> execute() throws BallerinaAsyncApiException { + Map> serviceTypes = new HashMap<>(); + for (Map.Entry channel : asyncApiSpec.channels.entrySet()) { + List remoteFunctions = new ArrayList<>(); + String serviceType; + if (channel.getValue().getExtension(Constants.X_BALLERINA_SERVICE_TYPE) == null) { + serviceType = codegenUtils.getValidName(channel.getKey(), true); + } else { + serviceType = channel.getValue().getExtension(Constants.X_BALLERINA_SERVICE_TYPE).value.toString(); + } + AaiMessage mainMessage = channel.getValue().subscribe.message; + if (mainMessage.oneOf != null) { + for(AaiMessage message: mainMessage.oneOf) { + if (message.getExtension(Constants.X_BALLERINA_EVENT_TYPE) == null) { + throw new BallerinaAsyncApiException( + "Could not find the ".concat(Constants.X_BALLERINA_EVENT_TYPE) + .concat(" attribute in the message of the channel ").concat(channel.getKey())); + } + remoteFunctions.add( + message.getExtension(Constants.X_BALLERINA_EVENT_TYPE).value.toString()); + } + } else { + if (mainMessage.getExtension(Constants.X_BALLERINA_EVENT_TYPE) == null) { + throw new BallerinaAsyncApiException( + "Could not find the ".concat(Constants.X_BALLERINA_EVENT_TYPE) + .concat(" attribute in the message of the channel ").concat(channel.getKey())); + } + remoteFunctions.add(channel.getValue() + .subscribe.message.getExtension(Constants.X_BALLERINA_EVENT_TYPE).value.toString()); + } + serviceTypes.put(serviceType, remoteFunctions); + } + return serviceTypes; + } +} diff --git a/src/main/java/io/ballerina/asyncapi/codegenerator/usecase/GenerateRecordNode.java b/src/main/java/io/ballerina/asyncapi/codegenerator/usecase/GenerateRecordNode.java index bcfb126f1..1b6bd1820 100644 --- a/src/main/java/io/ballerina/asyncapi/codegenerator/usecase/GenerateRecordNode.java +++ b/src/main/java/io/ballerina/asyncapi/codegenerator/usecase/GenerateRecordNode.java @@ -32,8 +32,7 @@ import static io.ballerina.compiler.syntax.tree.AbstractNodeFactory.*; import static io.ballerina.compiler.syntax.tree.NodeFactory.*; -import static io.ballerina.compiler.syntax.tree.SyntaxKind.OPEN_BRACE_TOKEN; -import static io.ballerina.compiler.syntax.tree.SyntaxKind.SEMICOLON_TOKEN; +import static io.ballerina.compiler.syntax.tree.SyntaxKind.*; public class GenerateRecordNode implements UseCase { private final AaiDocument asyncApiSpec; @@ -51,7 +50,6 @@ public GenerateRecordNode(AaiDocument asyncApiSpec, Map.Entry public TypeDefinitionNode execute() throws BallerinaAsyncApiException { var typeName = AbstractNodeFactory .createIdentifierToken(codegenUtils.escapeIdentifier(recordFields.getKey().trim())); - Token typeKeyWord = AbstractNodeFactory.createIdentifierToken("public type"); TypeDefinitionNode typeDefinitionNode; List schemaDoc = new ArrayList<>(); List recordFieldList = new ArrayList<>(); @@ -67,7 +65,7 @@ public TypeDefinitionNode execute() throws BallerinaAsyncApiException { createMarkdownDocumentationNode(createNodeList(schemaDoc)); var metadataNode = createMetadataNode(documentationNode, createEmptyNodeList()); typeDefinitionNode = NodeFactory.createTypeDefinitionNode(metadataNode, - null, typeKeyWord, typeName, recordTypeDescriptorNode, createToken(SEMICOLON_TOKEN)); + createToken(PUBLIC_KEYWORD), createToken(TYPE_KEYWORD), typeName, recordTypeDescriptorNode, createToken(SEMICOLON_TOKEN)); return typeDefinitionNode; } diff --git a/src/main/java/io/ballerina/asyncapi/codegenerator/usecase/GenerateServiceTypeNode.java b/src/main/java/io/ballerina/asyncapi/codegenerator/usecase/GenerateServiceTypeNode.java new file mode 100644 index 000000000..427fbe899 --- /dev/null +++ b/src/main/java/io/ballerina/asyncapi/codegenerator/usecase/GenerateServiceTypeNode.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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.asyncapi.codegenerator.usecase; + +import io.ballerina.asyncapi.codegenerator.configuration.BallerinaAsyncApiException; +import io.ballerina.asyncapi.codegenerator.usecase.utils.CodegenUtils; +import io.ballerina.compiler.syntax.tree.*; + +import java.util.ArrayList; +import java.util.List; + +import static io.ballerina.compiler.syntax.tree.AbstractNodeFactory.*; +import static io.ballerina.compiler.syntax.tree.AbstractNodeFactory.createToken; +import static io.ballerina.compiler.syntax.tree.NodeFactory.*; +import static io.ballerina.compiler.syntax.tree.SyntaxKind.*; + +public class GenerateServiceTypeNode implements UseCase { + private final String serviceTypeName; + private final List remoteFunctionNames; + private final CodegenUtils codegenUtils = new CodegenUtils(); + + public GenerateServiceTypeNode(String serviceTypeName, List remoteFunctionNames) { + this.serviceTypeName = serviceTypeName; + this.remoteFunctionNames = remoteFunctionNames; + } + + @Override + public TypeDefinitionNode execute() throws BallerinaAsyncApiException { + List remoteFunctions = new ArrayList<>(); + remoteFunctionNames.forEach(remoteFunction -> { + var methodDeclarationNode = createMethodDeclarationNode( + SyntaxKind.METHOD_DECLARATION, null, createNodeList(createToken(REMOTE_KEYWORD)), + createToken(SyntaxKind.FUNCTION_KEYWORD), createIdentifierToken(remoteFunction), createEmptyNodeList(), + createFunctionSignatureNode( + createToken(OPEN_PAREN_TOKEN), createSeparatedNodeList(), + createToken(CLOSE_PAREN_TOKEN), null), + createToken(SyntaxKind.SEMICOLON_TOKEN)); + remoteFunctions.add(methodDeclarationNode); + }); + var serviceTypeToken = AbstractNodeFactory + .createIdentifierToken(serviceTypeName); + var recordTypeDescriptorNode = + NodeFactory.createRecordTypeDescriptorNode(createIdentifierToken("service object"), + createToken(OPEN_BRACE_TOKEN), createNodeList(remoteFunctions), null, + createToken(SyntaxKind.CLOSE_BRACE_TOKEN)); + return createTypeDefinitionNode(null, createToken(PUBLIC_KEYWORD), + createToken(TYPE_KEYWORD), serviceTypeToken, recordTypeDescriptorNode, createToken(SEMICOLON_TOKEN)); + } +} diff --git a/src/main/java/io/ballerina/asyncapi/codegenerator/usecase/utils/CodegenUtils.java b/src/main/java/io/ballerina/asyncapi/codegenerator/usecase/utils/CodegenUtils.java index 7246932c1..adbd82dc4 100644 --- a/src/main/java/io/ballerina/asyncapi/codegenerator/usecase/utils/CodegenUtils.java +++ b/src/main/java/io/ballerina/asyncapi/codegenerator/usecase/utils/CodegenUtils.java @@ -68,7 +68,7 @@ public String escapeIdentifier(String identifier) { * @param identifier input function name, record name or operation Id * @return string with new generated name */ - public String getValidName(String identifier, boolean isSchema) { + public String getValidName(String identifier, boolean capitalizeFirstChar) { //For the flatten enable we need to remove first Part of valid name check // this - > !identifier.matches("\\b[a-zA-Z][a-zA-Z0-9]*\\b") && if (!identifier.matches("\\b[0-9]*\\b")) { @@ -85,7 +85,7 @@ public String getValidName(String identifier, boolean isSchema) { } identifier = validName.toString(); } - if (isSchema) { + if (capitalizeFirstChar) { return identifier.substring(0, 1).toUpperCase(Locale.ENGLISH) + identifier.substring(1); } else { return identifier.substring(0, 1).toLowerCase(Locale.ENGLISH) + identifier.substring(1); diff --git a/src/main/resources/types.bal b/src/main/resources/types.bal deleted file mode 100644 index 53cc0c08f..000000000 --- a/src/main/resources/types.bal +++ /dev/null @@ -1,7 +0,0 @@ -@display {label: "Connection Config"} -public type ListenerConfiguration record { - @display {label: "Listener Port"} - int port; - @display {label: "Verification Token"} - string verificationToken; -};