diff --git a/architecture-model-generator/modules/architecture-model-generator-core/src/main/java/io/ballerina/artifactsgenerator/Artifact.java b/architecture-model-generator/modules/architecture-model-generator-core/src/main/java/io/ballerina/artifactsgenerator/Artifact.java index 70db717ed0..764fb1cc5a 100644 --- a/architecture-model-generator/modules/architecture-model-generator-core/src/main/java/io/ballerina/artifactsgenerator/Artifact.java +++ b/architecture-model-generator/modules/architecture-model-generator-core/src/main/java/io/ballerina/artifactsgenerator/Artifact.java @@ -91,7 +91,8 @@ public record Artifact(String id, LineRange location, String type, String name, Map.entry("github", "GitHub Event Integration"), Map.entry("twilio", "Twilio Event Integration"), Map.entry("ai", "AI Agent Services"), - Map.entry("solace", "Solace Event Integration") + Map.entry("solace", "Solace Event Integration"), + Map.entry("gcloud.pubsub", "Google Cloud Pub/Sub Event Integration") ); /** @@ -99,7 +100,8 @@ public record Artifact(String id, LineRange location, String type, String name, * module can have multiple field names to try in order of preference. */ private static final Map moduleAnnotationFields = Map.of( - "solace", new String[]{"queueName", "topicName"} + "solace", new String[]{"queueName", "topicName"}, + "gcloud.pubsub", new String[]{"subscription"} ); public static String getCategory(String type) { diff --git a/model-generator-commons/src/main/java/io/ballerina/modelgenerator/commons/ServiceDatabaseManager.java b/model-generator-commons/src/main/java/io/ballerina/modelgenerator/commons/ServiceDatabaseManager.java index 62df806805..712fe1e2f9 100644 --- a/model-generator-commons/src/main/java/io/ballerina/modelgenerator/commons/ServiceDatabaseManager.java +++ b/model-generator-commons/src/main/java/io/ballerina/modelgenerator/commons/ServiceDatabaseManager.java @@ -330,7 +330,9 @@ public Optional getServiceInitInfo(String orgName, String modul sql2.append("sip.value_type as valueType, "); sql2.append("sip.type_constraint as typeConstraint, "); sql2.append("sip.source_kind as sourceKind, "); - sql2.append("sip.selections "); + sql2.append("sip.selections, "); + sql2.append("sip.optional, "); + sql2.append("sip.advanced "); sql2.append("FROM ServiceInitializerProperty sip "); sql2.append("WHERE sip.package_id = ?"); @@ -368,7 +370,9 @@ private ServiceInitProperty getServiceInitProperty(ResultSet rs) throws SQLExcep rs.getString("typeConstraint"), rs.getString("sourceKind"), rs.getString("selections"), - getServiceInitPropertyMemberTypes(initializerId) + getServiceInitPropertyMemberTypes(initializerId), + rs.getBoolean("optional"), + rs.getBoolean("advanced") ); } diff --git a/model-generator-commons/src/main/java/io/ballerina/modelgenerator/commons/ServiceInitProperty.java b/model-generator-commons/src/main/java/io/ballerina/modelgenerator/commons/ServiceInitProperty.java index 88ec61bd00..15e8dc2c1b 100644 --- a/model-generator-commons/src/main/java/io/ballerina/modelgenerator/commons/ServiceInitProperty.java +++ b/model-generator-commons/src/main/java/io/ballerina/modelgenerator/commons/ServiceInitProperty.java @@ -22,5 +22,6 @@ public record ServiceInitProperty(String keyName, String label, String description, String defaultValue, String placeholder, String valueType, String typeConstraint, String sourceKind, - String selections, List memberTypes) { + String selections, List memberTypes, boolean optional, + boolean advanced) { } diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/FunctionBuilderRouter.java b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/FunctionBuilderRouter.java index 26352a487b..6a3b15fb29 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/FunctionBuilderRouter.java +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/FunctionBuilderRouter.java @@ -27,6 +27,7 @@ import io.ballerina.projects.Document; import io.ballerina.projects.Project; import io.ballerina.servicemodelgenerator.extension.builder.function.DefaultFunctionBuilder; +import io.ballerina.servicemodelgenerator.extension.builder.function.GcloudPubSubFunctionBuilder; import io.ballerina.servicemodelgenerator.extension.builder.function.GraphqlFunctionBuilder; import io.ballerina.servicemodelgenerator.extension.builder.function.HttpFunctionBuilder; import io.ballerina.servicemodelgenerator.extension.builder.function.KafkaFunctionBuilder; @@ -50,6 +51,7 @@ import java.util.function.Supplier; import static io.ballerina.servicemodelgenerator.extension.util.Constants.DEFAULT; +import static io.ballerina.servicemodelgenerator.extension.util.Constants.GCLOUD_PUBSUB; import static io.ballerina.servicemodelgenerator.extension.util.Constants.GRAPHQL; import static io.ballerina.servicemodelgenerator.extension.util.Constants.HTTP; import static io.ballerina.servicemodelgenerator.extension.util.Constants.KAFKA; @@ -72,6 +74,7 @@ public class FunctionBuilderRouter { put(MCP, McpFunctionBuilder::new); put(KAFKA, KafkaFunctionBuilder::new); put(SOLACE, SolaceFunctionBuilder::new); + put(GCLOUD_PUBSUB, GcloudPubSubFunctionBuilder::new); }}; private static NodeBuilder getFunctionBuilder(String protocol) { diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/ServiceBuilderRouter.java b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/ServiceBuilderRouter.java index b28906f3af..48c298a68c 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/ServiceBuilderRouter.java +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/ServiceBuilderRouter.java @@ -27,6 +27,7 @@ import io.ballerina.servicemodelgenerator.extension.builder.service.AiChatServiceBuilder; import io.ballerina.servicemodelgenerator.extension.builder.service.AsbServiceBuilder; import io.ballerina.servicemodelgenerator.extension.builder.service.DefaultServiceBuilder; +import io.ballerina.servicemodelgenerator.extension.builder.service.GcloudPubSubServiceBuilder; import io.ballerina.servicemodelgenerator.extension.builder.service.GraphqlServiceBuilder; import io.ballerina.servicemodelgenerator.extension.builder.service.HttpServiceBuilder; import io.ballerina.servicemodelgenerator.extension.builder.service.KafkaServiceBuilder; @@ -57,6 +58,7 @@ import static io.ballerina.servicemodelgenerator.extension.util.Constants.AI; import static io.ballerina.servicemodelgenerator.extension.util.Constants.ASB; +import static io.ballerina.servicemodelgenerator.extension.util.Constants.GCLOUD_PUBSUB; import static io.ballerina.servicemodelgenerator.extension.util.Constants.GRAPHQL; import static io.ballerina.servicemodelgenerator.extension.util.Constants.HTTP; import static io.ballerina.servicemodelgenerator.extension.util.Constants.KAFKA; @@ -66,8 +68,8 @@ import static io.ballerina.servicemodelgenerator.extension.util.Constants.TCP; /** - * ServiceBuilderRouter is responsible for routing service building requests to the appropriate service builder - * based on the protocol type. + * ServiceBuilderRouter is responsible for routing service building requests to the appropriate service builder based on + * the protocol type. * * @since 1.2.0 */ @@ -83,6 +85,7 @@ public class ServiceBuilderRouter { put(KAFKA, KafkaServiceBuilder::new); put(ASB, AsbServiceBuilder::new); put(SOLACE, SolaceServiceBuilder::new); + put(GCLOUD_PUBSUB, GcloudPubSubServiceBuilder::new); }}; public static ServiceNodeBuilder getServiceBuilder(String protocol) { diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/function/GcloudPubSubFunctionBuilder.java b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/function/GcloudPubSubFunctionBuilder.java new file mode 100644 index 0000000000..57483e1b03 --- /dev/null +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/function/GcloudPubSubFunctionBuilder.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025, 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.servicemodelgenerator.extension.builder.function; + +import io.ballerina.servicemodelgenerator.extension.model.context.AddModelContext; +import io.ballerina.servicemodelgenerator.extension.model.context.UpdateModelContext; +import io.ballerina.servicemodelgenerator.extension.util.DatabindUtil; +import org.eclipse.lsp4j.TextEdit; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static io.ballerina.servicemodelgenerator.extension.builder.service.GcloudPubSubServiceBuilder.PAYLOAD_FIELD_NAME; +import static io.ballerina.servicemodelgenerator.extension.builder.service.GcloudPubSubServiceBuilder.TYPE_PREFIX; +import static io.ballerina.servicemodelgenerator.extension.util.Constants.GCLOUD_PUBSUB; + +/** + * Represents the Google Cloud Pub/Sub function builder of the service model generator. + * + * @since 1.4.0 + */ +public final class GcloudPubSubFunctionBuilder extends AbstractFunctionBuilder { + + private static final String REQUIRED_PARAM_TYPE = "pubsub:Message"; + + @Override + public Map> updateModel(UpdateModelContext context) { + Map> databindEdits = DatabindUtil.processDatabindingUpdate( + context, TYPE_PREFIX, REQUIRED_PARAM_TYPE, PAYLOAD_FIELD_NAME, false); + + Map> mainFileEdits = super.updateModel(context); + Map> allEdits = new HashMap<>(mainFileEdits); + allEdits.putAll(databindEdits); + return allEdits; + } + + @Override + public Map> addModel(AddModelContext context) throws Exception { + Map> databindEdits = DatabindUtil.processDatabindingForAdd( + context, TYPE_PREFIX, REQUIRED_PARAM_TYPE, PAYLOAD_FIELD_NAME, false); + + Map> mainFileEdits = super.addModel(context); + Map> allEdits = new HashMap<>(mainFileEdits); + allEdits.putAll(databindEdits); + return allEdits; + } + + @Override + public String kind() { + return GCLOUD_PUBSUB; + } +} diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/service/AbstractServiceBuilder.java b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/service/AbstractServiceBuilder.java index 878038d165..1fec47254b 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/service/AbstractServiceBuilder.java +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/service/AbstractServiceBuilder.java @@ -321,7 +321,9 @@ public ServiceInitModel getServiceInitModel(GetServiceInitModelContext context) .setItems(items) .setTypeMembers(property.memberTypes()) .enabled(true) - .editable(true); + .editable(true) + .optional(property.optional()) + .setAdvanced(property.advanced()); serviceInitModel.addProperty(property.keyName(), builder.build()); } diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/service/GcloudPubSubServiceBuilder.java b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/service/GcloudPubSubServiceBuilder.java new file mode 100644 index 0000000000..09b09147ac --- /dev/null +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/service/GcloudPubSubServiceBuilder.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2025, 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.servicemodelgenerator.extension.builder.service; + +import io.ballerina.servicemodelgenerator.extension.model.Codedata; +import io.ballerina.servicemodelgenerator.extension.model.Function; +import io.ballerina.servicemodelgenerator.extension.model.Service; +import io.ballerina.servicemodelgenerator.extension.model.ServiceInitModel; +import io.ballerina.servicemodelgenerator.extension.model.Value; +import io.ballerina.servicemodelgenerator.extension.model.context.AddServiceInitModelContext; +import io.ballerina.servicemodelgenerator.extension.model.context.GetServiceInitModelContext; +import io.ballerina.servicemodelgenerator.extension.model.context.ModelFromSourceContext; +import io.ballerina.servicemodelgenerator.extension.util.ListenerUtil; +import org.eclipse.lsp4j.TextEdit; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static io.ballerina.servicemodelgenerator.extension.model.ServiceInitModel.KEY_CONFIGURE_LISTENER; +import static io.ballerina.servicemodelgenerator.extension.model.ServiceInitModel.KEY_EXISTING_LISTENER; +import static io.ballerina.servicemodelgenerator.extension.model.ServiceInitModel.KEY_LISTENER_VAR_NAME; +import static io.ballerina.servicemodelgenerator.extension.util.Constants.ARG_TYPE_LISTENER_PARAM_INCLUDED_FIELD; +import static io.ballerina.servicemodelgenerator.extension.util.Constants.CLOSE_BRACE; +import static io.ballerina.servicemodelgenerator.extension.util.Constants.GCLOUD_PUBSUB; +import static io.ballerina.servicemodelgenerator.extension.util.Constants.NEW_LINE; +import static io.ballerina.servicemodelgenerator.extension.util.Constants.ON; +import static io.ballerina.servicemodelgenerator.extension.util.Constants.OPEN_BRACE; +import static io.ballerina.servicemodelgenerator.extension.util.Constants.SERVICE; +import static io.ballerina.servicemodelgenerator.extension.util.Constants.SPACE; +import static io.ballerina.servicemodelgenerator.extension.util.Constants.TWO_NEW_LINES; +import static io.ballerina.servicemodelgenerator.extension.util.Constants.VALUE_TYPE_EXPRESSION; +import static io.ballerina.servicemodelgenerator.extension.util.DatabindUtil.addDataBindingParam; +import static io.ballerina.servicemodelgenerator.extension.util.JmsUtil.ON_MESSAGE_FUNCTION_NAME; +import static io.ballerina.servicemodelgenerator.extension.util.JmsUtil.buildListenerChoice; +import static io.ballerina.servicemodelgenerator.extension.util.JmsUtil.buildServiceCodeEdits; +import static io.ballerina.servicemodelgenerator.extension.util.ServiceModelUtils.getRequiredFunctionsForServiceType; +import static io.ballerina.servicemodelgenerator.extension.util.Utils.FunctionAddContext.TRIGGER_ADD; +import static io.ballerina.servicemodelgenerator.extension.util.Utils.applyEnabledChoiceProperty; +import static io.ballerina.servicemodelgenerator.extension.util.Utils.buildServiceAnnotation; + +/** + * Builder class for Google Cloud Pub/Sub service. + * + * @since 1.4.0 + */ +public final class GcloudPubSubServiceBuilder extends AbstractServiceBuilder { + + private static final String TYPE_PUBSUB_SERVICE_CONFIG = "pubsub:ServiceConfig"; + private static final String SERVICE_TYPE = "pubsub:Service"; + private static final String PROPERTY_CREDENTIALS = "credentials"; + private static final String PROPERTY_PROJECT_ID = "project"; + private static final String PROPERTY_AUTH = "auth"; + private static final String AUTH_CONFIG_TEMPLATE = "{path: \"%s\"}"; + public static final String PAYLOAD_FIELD_NAME = "data"; + public static final String TYPE_PREFIX = "PubSubMessage"; + private static final String LABEL_GCLOUD_PUBSUB = "Google Cloud Pub/Sub"; + + // Listener configuration property keys + private static final String[] LISTENER_CONFIG_KEYS = { + PROPERTY_PROJECT_ID, PROPERTY_CREDENTIALS, KEY_LISTENER_VAR_NAME + }; + + @Override + public ServiceInitModel getServiceInitModel(GetServiceInitModelContext context) { + ServiceInitModel serviceInitModel = super.getServiceInitModel(context); + if (serviceInitModel == null) { + return null; + } + + Map properties = serviceInitModel.getProperties(); + Set listeners = ListenerUtil.getCompatibleListeners(context.moduleName(), + context.semanticModel(), context.project()); + + if (!listeners.isEmpty()) { + Map listenerProps = new LinkedHashMap<>(); + for (String key : LISTENER_CONFIG_KEYS) { + listenerProps.put(key, properties.remove(key)); + } + Value choicesProperty = buildListenerChoice(listenerProps, listeners, LABEL_GCLOUD_PUBSUB); + properties.put(KEY_CONFIGURE_LISTENER, choicesProperty); + } + + return serviceInitModel; + } + + @Override + public Map> addServiceInitSource(AddServiceInitModelContext context) { + ServiceInitModel serviceInitModel = context.serviceInitModel(); + Map properties = serviceInitModel.getProperties(); + + applyCredentialsProperty(properties); + + if (!properties.containsKey(KEY_CONFIGURE_LISTENER)) { + return addServiceWithNewListener(context); + } + + applyEnabledChoiceProperty(serviceInitModel, KEY_CONFIGURE_LISTENER); + + ListenerDTO listenerDTO; + applyCredentialsProperty(properties); + if (properties.containsKey(KEY_EXISTING_LISTENER)) { + listenerDTO = new ListenerDTO(context.serviceInitModel().getModuleName(), + properties.get(KEY_EXISTING_LISTENER).getValue(), ""); + } else { + listenerDTO = buildListenerDTO(context); + } + + String serviceCode = buildPubsubServiceCode(serviceInitModel, listenerDTO); + return buildServiceCodeEdits(context, serviceCode, null); + } + + private Map> addServiceWithNewListener(AddServiceInitModelContext context) { + ListenerDTO listenerDTO = buildListenerDTO(context); + String serviceCode = buildPubsubServiceCode(context.serviceInitModel(), listenerDTO); + return buildServiceCodeEdits(context, serviceCode, null); + } + + private void applyCredentialsProperty(Map properties) { + if (!properties.containsKey(PROPERTY_CREDENTIALS)) { + return; + } + + Value credentialsValue = properties.get(PROPERTY_CREDENTIALS); + if (credentialsValue == null || credentialsValue.getValue() == null + || credentialsValue.getValue().isEmpty()) { + properties.remove(PROPERTY_CREDENTIALS); + return; + } + + String credentialsPath = credentialsValue.getValue(); + String authConfig = String.format(AUTH_CONFIG_TEMPLATE, credentialsPath); + + Value authValue = new Value.ValueBuilder() + .value(authConfig) + .valueType(VALUE_TYPE_EXPRESSION) + .enabled(true) + .editable(false) + .setCodedata(new Codedata(null, ARG_TYPE_LISTENER_PARAM_INCLUDED_FIELD)) + .build(); + properties.put(PROPERTY_AUTH, authValue); + properties.remove(PROPERTY_CREDENTIALS); + } + + private String buildPubsubServiceCode(ServiceInitModel serviceInitModel, ListenerDTO listenerDTO) { + Map properties = serviceInitModel.getProperties(); + + String serviceAnnotation = buildServiceAnnotation(TYPE_PUBSUB_SERVICE_CONFIG, properties); + + List functions = getRequiredFunctionsForServiceType(serviceInitModel); + List functionsStr = AbstractServiceBuilder.buildMethodDefinitions( + functions, TRIGGER_ADD, new HashMap<>()); + + String code = NEW_LINE; + if (!listenerDTO.listenerDeclaration().isEmpty()) { + code += listenerDTO.listenerDeclaration() + NEW_LINE; + } + code += serviceAnnotation + + SERVICE + SPACE + SERVICE_TYPE + SPACE + + ON + SPACE + listenerDTO.listenerVarName() + SPACE + + OPEN_BRACE + + NEW_LINE + + String.join(TWO_NEW_LINES, functionsStr) + NEW_LINE + + CLOSE_BRACE + NEW_LINE; + + return code; + } + + @Override + public Service getModelFromSource(ModelFromSourceContext context) { + Service service = super.getModelFromSource(context); + addDataBindingParam(service, ON_MESSAGE_FUNCTION_NAME, context, PAYLOAD_FIELD_NAME, TYPE_PREFIX); + return service; + } + + @Override + public String kind() { + return GCLOUD_PUBSUB; + } +} diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/util/Constants.java b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/util/Constants.java index df99e7d2bd..ae03000b85 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/util/Constants.java +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/util/Constants.java @@ -66,6 +66,7 @@ public class Constants { public static final String ASB = "asb"; public static final String SF = "salesforce"; public static final String TRIGGER_GITHUB = "trigger.github"; + public static final String GCLOUD_PUBSUB = "gcloud.pubsub"; public static final String FTP = "ftp"; public static final String FILE = "file"; @@ -125,6 +126,7 @@ public class Constants { "LISTENER_PARAM_INCLUDED_DEFAULTABLE_FIELD"; public static final String ARG_TYPE_SERVICE_BASE_PATH = "SERVICE_BASE_PATH"; public static final String ARG_TYPE_SERVICE_TYPE_DESCRIPTOR = "SERVICE_TYPE_DESCRIPTOR"; + public static final String ARG_TYPE_SERVICE_ANNOTATION = "SOURCE_ANNOTATION"; public static final String TYPE_SERVICE = "Service"; diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/util/DatabindUtil.java b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/util/DatabindUtil.java index 8bf70f1c2e..ed996f89ef 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/util/DatabindUtil.java +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/util/DatabindUtil.java @@ -62,7 +62,6 @@ import java.util.Optional; import java.util.Set; -import static io.ballerina.servicemodelgenerator.extension.util.Constants.COLON; import static io.ballerina.servicemodelgenerator.extension.util.Constants.DATA_BINDING; import static io.ballerina.servicemodelgenerator.extension.util.Constants.DATA_BINDING_PROPERTY; import static io.ballerina.servicemodelgenerator.extension.util.Constants.DATA_BINDING_TEMPLATE; @@ -433,7 +432,8 @@ public static Map> processDatabindingForAdd(AddModelConte ); return createTypeDefinitionEdits(context.project(), typeName, baseType, - newDataBindingType, payloadFieldName, context.filePath(), context.workspaceManager()); + context.function().getCodedata().getModuleName(), newDataBindingType, payloadFieldName, + context.filePath(), context.workspaceManager()); } /** @@ -552,7 +552,8 @@ public static Map> processDatabindingUpdate(UpdateModelCo } else { typesEdits = createTypeDefinitionEdits(context.project(), customWrapperTypeName, baseType, - newDataBindingType, payloadFieldName, context.filePath(), context.workspaceManager()); + context.function().getCodedata().getModuleName(), newDataBindingType, payloadFieldName, + context.filePath(), context.workspaceManager()); } } else if (existingTypeName != null) { typeName = existingTypeName; @@ -563,8 +564,9 @@ public static Map> processDatabindingUpdate(UpdateModelCo generateNewDataBindTypeName(context.filePath(), context.workspaceManager(), context.semanticModel(), context.functionNode(), prefix); - typesEdits = createTypeDefinitionEdits(context.project(), typeName, baseType, newDataBindingType, - payloadFieldName, context.filePath(), context.workspaceManager()); + typesEdits = createTypeDefinitionEdits(context.project(), typeName, baseType, + context.function().getCodedata().getModuleName(), newDataBindingType, payloadFieldName, + context.filePath(), context.workspaceManager()); } updateFunctionParameters(function, dataBindingParam, typeName, isArray); @@ -799,24 +801,20 @@ private static String generateNewDataBindTypeName(String contextFilePath, Worksp } /** - * Extracts import statements needed for the given baseType. For example, "kafka:AnydataConsumerRecord" returns - * org/kafka imports. + * Generates the required import statements for the base type if they do not already exist in the module part node. * - * @param baseType The base record type (e.g., "kafka:AnydataConsumerRecord") - * @param modulePartNode The module part node to check existing imports + * @param baseTypeModuleName The module name of the base type + * @param modulePartNode The module part node to check existing imports * @return Set of import statements to add */ - private static Set extractRequiredImports(String baseType, ModulePartNode modulePartNode) { + private static Set extractRequiredImports(String baseTypeModuleName, + ModulePartNode modulePartNode) { Set imports = new HashSet<>(); - if (baseType.contains(COLON)) { - String moduleName = baseType.substring(0, baseType.indexOf(COLON)); - String org = "ballerinax"; - String importModule = moduleName.toLowerCase(java.util.Locale.ENGLISH); + String org = "ballerinax"; - if (!importExists(modulePartNode, org, importModule)) { - imports.add(getImportStmt(org, importModule)); - } + if (!importExists(modulePartNode, org, baseTypeModuleName)) { + imports.add(getImportStmt(org, baseTypeModuleName)); } return imports; @@ -826,13 +824,15 @@ private static Set extractRequiredImports(String baseType, ModulePartNod * Prepares the context for type definition edit operations by loading the types document and extracting required * imports. * - * @param project The Ballerina project - * @param baseType The base record type (e.g., "kafka:AnydataConsumerRecord") - * @param contextFilePath The context file path for locating types.bal - * @param workspaceManager The workspace manager for document retrieval + * @param project The Ballerina project + * @param baseType The base record type (e.g., "kafka:AnydataConsumerRecord") + * @param baseTypeModuleName The module name of the base type + * @param contextFilePath The context file path for locating types.bal + * @param workspaceManager The workspace manager for document retrieval * @return TypeDefinitionEditContext containing types document, module part node, required imports, and project */ private static TypeDefinitionEditContext prepareTypeDefinitionEditContext(Project project, String baseType, + String baseTypeModuleName, String contextFilePath, WorkspaceManager workspaceManager) { Document typesDocument = getTypesDocument(contextFilePath, workspaceManager); @@ -841,7 +841,7 @@ private static TypeDefinitionEditContext prepareTypeDefinitionEditContext(Projec } ModulePartNode modulePartNode = typesDocument.syntaxTree().rootNode(); - Set requiredImports = extractRequiredImports(baseType, modulePartNode); + Set requiredImports = extractRequiredImports(baseTypeModuleName, modulePartNode); return new TypeDefinitionEditContext(typesDocument, modulePartNode, requiredImports, project); } @@ -857,12 +857,14 @@ private static TypeDefinitionEditContext prepareTypeDefinitionEditContext(Projec * @return Map of file paths to TextEdit lists */ private static Map> createTypeDefinitionEdits(Project project, String typeName, - String baseType, String dataBindingType, + String baseType, String baseTypeModuleName, + String dataBindingType, String payloadFieldName, String contextFilePath, WorkspaceManager workspaceManager) { TypeDefinitionEditContext context = - prepareTypeDefinitionEditContext(project, baseType, contextFilePath, workspaceManager); + prepareTypeDefinitionEditContext(project, baseType, baseTypeModuleName, contextFilePath, + workspaceManager); if (context == null) { return Map.of(); } @@ -1101,7 +1103,8 @@ private static Map> updateTypeDefinitionEdits(UpdateModel String newTypeName) { Project project = context.project() != null ? context.project() : context.document().module().project(); TypeDefinitionEditContext editContext = - prepareTypeDefinitionEditContext(project, baseType, context.filePath(), context.workspaceManager()); + prepareTypeDefinitionEditContext(project, baseType, context.function().getCodedata().getModuleName(), + context.filePath(), context.workspaceManager()); if (editContext == null) { return Map.of(); } @@ -1121,8 +1124,9 @@ private static Map> updateTypeDefinitionEdits(UpdateModel if (existingTypeDef == null) { // Type doesn't exist, create it instead - return createTypeDefinitionEdits(context.project(), existingTypeName, baseType, newDataBindingType, - payloadFieldName, context.filePath(), context.workspaceManager()); + return createTypeDefinitionEdits(context.project(), existingTypeName, baseType, + context.function().getCodedata().getModuleName(), newDataBindingType, payloadFieldName, + context.filePath(), context.workspaceManager()); } // Use newTypeName if provided for renaming, otherwise use existingTypeName diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/util/Utils.java b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/util/Utils.java index 5a4f1f8e3e..e960dd7066 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/util/Utils.java +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/util/Utils.java @@ -106,10 +106,13 @@ import java.util.stream.Collectors; import static io.ballerina.servicemodelgenerator.extension.util.Constants.ANNOT_PREFIX; +import static io.ballerina.servicemodelgenerator.extension.util.Constants.AT; import static io.ballerina.servicemodelgenerator.extension.util.Constants.BALLERINA; import static io.ballerina.servicemodelgenerator.extension.util.Constants.CD_TYPE_ANNOTATION_ATTACHMENT; +import static io.ballerina.servicemodelgenerator.extension.util.Constants.CLOSE_BRACE; import static io.ballerina.servicemodelgenerator.extension.util.Constants.CLOSE_PAREN; import static io.ballerina.servicemodelgenerator.extension.util.Constants.COLON; +import static io.ballerina.servicemodelgenerator.extension.util.Constants.COMMA; import static io.ballerina.servicemodelgenerator.extension.util.Constants.GET; import static io.ballerina.servicemodelgenerator.extension.util.Constants.GRAPHQL_CONTEXT; import static io.ballerina.servicemodelgenerator.extension.util.Constants.GRAPHQL_FIELD; @@ -122,6 +125,7 @@ import static io.ballerina.servicemodelgenerator.extension.util.Constants.KIND_RESOURCE; import static io.ballerina.servicemodelgenerator.extension.util.Constants.KIND_SUBSCRIPTION; import static io.ballerina.servicemodelgenerator.extension.util.Constants.NEW_LINE; +import static io.ballerina.servicemodelgenerator.extension.util.Constants.OPEN_BRACE; import static io.ballerina.servicemodelgenerator.extension.util.Constants.OPEN_PAREN; import static io.ballerina.servicemodelgenerator.extension.util.Constants.PROPERTY_DESIGN_APPROACH; import static io.ballerina.servicemodelgenerator.extension.util.Constants.REMOTE; @@ -221,8 +225,8 @@ public static void populateDesignApproach(Service service) { } /** - * Applies the properties of the enabled choice from the specified choice property key in the service init model. - * If an enabled choice exists, its properties are added to the service and the choice property is removed. + * Applies the properties of the enabled choice from the specified choice property key in the service init model. If + * an enabled choice exists, its properties are added to the service and the choice property is removed. * * @param service the service initialization model to update * @param key the key of the choice property to process @@ -263,9 +267,9 @@ private static Optional getServiceByServiceType(String serviceType) { * Extracts the line range that encompasses all listener expressions in a service declaration. * * @param serviceNode the service declaration node containing listener expressions - * @return an {@link Optional} containing the {@link LineRange} that spans from the start - * of the first listener expression to the end of the last listener expression, - * or {@link Optional#empty()} if no listener expressions are found + * @return an {@link Optional} containing the {@link LineRange} that spans from the start of the first listener + * expression to the end of the last listener expression, or {@link Optional#empty()} if no listener expressions are + * found */ public static Optional getListenerExpressionsLineRange(ServiceDeclarationNode serviceNode) { SeparatedNodeList expressions = serviceNode.expressions(); @@ -419,7 +423,6 @@ public static Optional getParameterModel(ParameterNode parameterNode) return Optional.empty(); } - private static Parameter createParameter(String paramName, String paramKind, String typeName) { Parameter parameterModel = Parameter.getNewFunctionParameter(); parameterModel.setMetadata(new MetaData(paramName, paramName)); @@ -613,8 +616,8 @@ public static NodeList getParamAnnotations(ParameterNode paramet } /** - * This function will add the annotations of a parameter as properties to the parameter model. - * We can pass a skip list to skip certain annotations. + * This function will add the annotations of a parameter as properties to the parameter model. We can pass a skip + * list to skip certain annotations. * * @param parameterModel Parameter model we need to add the annotations as properties * @param annotations Annotations of the parameter @@ -818,6 +821,42 @@ private static boolean isAnnotationProperty(Value value) { && (value.isEnabledWithValue() || !value.isEnabled() && !value.isEditable()); } + /** + * Builds a generic service annotation string from properties. This method iterates through properties, filters + * those marked as service annotations, and constructs a properly formatted annotation string. + * + * @param annotationType The annotation type identifier (e.g., "pubsub:ServiceConfig") + * @param properties The service properties containing configuration values + * @return The formatted annotation string + */ + public static String buildServiceAnnotation(String annotationType, Map properties) { + StringBuilder annotation = new StringBuilder(); + annotation.append(AT).append(annotationType).append(SPACE).append(OPEN_BRACE).append(NEW_LINE); + + List annotationParams = new ArrayList<>(); + + for (Map.Entry entry : properties.entrySet()) { + String propertyName = entry.getKey(); + Value propertyValue = entry.getValue(); + + if (propertyValue != null && propertyValue.getCodedata() != null) { + String argType = propertyValue.getCodedata().getArgType(); + if (Constants.ARG_TYPE_SERVICE_ANNOTATION.equals(argType)) { + if (propertyValue.getValue() != null && !propertyValue.getValue().isBlank()) { + annotationParams.add(propertyName + COLON + SPACE + propertyValue.getValue()); + } + } + } + } + + if (!annotationParams.isEmpty()) { + annotation.append(String.join(COMMA + NEW_LINE, annotationParams)).append(NEW_LINE); + } + + annotation.append(CLOSE_BRACE).append(NEW_LINE); + return annotation.toString(); + } + public static String getDocumentationEdits(Service service) { String docs = ""; if (Objects.nonNull(service.getDocumentation()) && service.getDocumentation().getValue() != null) { @@ -1077,7 +1116,6 @@ public static String generateFunctionDefSource(Function function, List s hasErrorInReturn = returnParts.contains("error") || returnParts.contains("error?"); } - // function body builder.append("{").append(NEW_LINE); if (hasErrorInReturn) { @@ -1295,9 +1333,8 @@ public static List deserializeSelections(String jsonString) { } /** - * Resolves a Ballerina module by organization, package, and module name. - * If the module is not found locally, attempts to pull it from the central repository, - * notifies the client about the process. + * Resolves a Ballerina module by organization, package, and module name. If the module is not found locally, + * attempts to pull it from the central repository, notifies the client about the process. * * @param orgName the organization name * @param packageName the package name diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/main/resources/service-index.sqlite b/service-model-generator/modules/service-model-generator-ls-extension/src/main/resources/service-index.sqlite index 26989dc445..c2b9e73909 100644 Binary files a/service-model-generator/modules/service-model-generator-ls-extension/src/main/resources/service-index.sqlite and b/service-model-generator/modules/service-model-generator-ls-extension/src/main/resources/service-index.sqlite differ diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/main/resources/trigger_properties.json b/service-model-generator/modules/service-model-generator-ls-extension/src/main/resources/trigger_properties.json index 95c103dc32..20723ac0c2 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/main/resources/trigger_properties.json +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/main/resources/trigger_properties.json @@ -109,5 +109,18 @@ "messaging", "event" ] + }, + "12": { + "name": "gcloud.pubsub", + "orgName": "ballerinax", + "packageName": "gcloud.pubsub", + "keywords": [ + "gcloud", + "google cloud", + "pub/sub", + "pubsub", + "messaging", + "event" + ] } } diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_service_init_model/config/gcp_service_model_1.json b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_service_init_model/config/gcp_service_model_1.json new file mode 100644 index 0000000000..56a1eeb4c8 --- /dev/null +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_service_init_model/config/gcp_service_model_1.json @@ -0,0 +1,95 @@ +{ + "description": "Test getting gcloud.pubsub service init model", + "filePath": "sample1/main.bal", + "orgName": "ballerinax", + "pkgName": "gcloud.pubsub", + "moduleName": "gcloud.pubsub", + "response": { + "serviceInitModel": { + "id": "17", + "displayName": "Google Cloud Pub/Sub Event Integration", + "description": "Create a service to handle messages from a Google Cloud Pub/Sub subscription", + "orgName": "ballerinax", + "packageName": "gcloud.pubsub", + "moduleName": "gcloud.pubsub", + "version": "1.0.0", + "type": "gcloud.pubsub", + "icon": "https://bcentral-packageicons.azureedge.net/images/ballerinax_gcloud.pubsub_1.0.0.png", + "properties": { + "project": { + "metadata": { + "label": "Project ID", + "description": "The Google Cloud project ID" + }, + "codedata": { + "argType": "LISTENER_PARAM_REQUIRED" + }, + "placeholder": "\"my-gcp-project\"", + "valueType": "EXPRESSION", + "valueTypeConstraint": "string", + "value": "", + "items": [], + "typeMembers": [], + "enabled": true, + "editable": true, + "optional": false, + "advanced": false + }, + "subscription": { + "metadata": { + "label": "Subscription", + "description": "The name of the Pub/Sub subscription to pull messages from" + }, + "codedata": { + "argType": "SOURCE_ANNOTATION" + }, + "placeholder": "", + "valueType": "EXPRESSION", + "valueTypeConstraint": "string", + "value": "", + "items": [], + "typeMembers": [], + "enabled": true, + "editable": true, + "optional": false, + "advanced": false + }, + "credentials": { + "metadata": { + "label": "User Credentials JSON", + "description": "The service account credentials JSON file path" + }, + "codedata": { + "argType": "LISTENER_PARAM_INCLUDED_FIELD" + }, + "placeholder": "\"/path/to/credentials.json\"", + "valueType": "FILE_SELECT", + "valueTypeConstraint": "string", + "value": "", + "items": [], + "typeMembers": [], + "enabled": true, + "editable": true, + "optional": true, + "advanced": true + }, + "listenerVarName": { + "metadata": { + "label": "Listener Name", + "description": "Provide a name for the listener being created" + }, + "codedata": { + "type": "LISTENER_VAR_NAME" + }, + "valueType": "IDENTIFIER", + "valueTypeConstraint": "Global", + "value": "pubsubListener", + "enabled": true, + "editable": true, + "optional": false, + "advanced": true + } + } + } + } +} diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_service_init_model/config/gcp_service_model_2.json b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_service_init_model/config/gcp_service_model_2.json new file mode 100644 index 0000000000..e6b822b4c6 --- /dev/null +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_service_init_model/config/gcp_service_model_2.json @@ -0,0 +1,152 @@ +{ + "description": "Test getting gcloud.pubsub service init model with existing listeners", + "filePath": "sample2/main.bal", + "orgName": "ballerinax", + "pkgName": "gcloud.pubsub", + "moduleName": "gcloud.pubsub", + "response": { + "serviceInitModel": { + "id": "17", + "displayName": "Google Cloud Pub/Sub Event Integration", + "description": "Create a service to handle messages from a Google Cloud Pub/Sub subscription", + "orgName": "ballerinax", + "packageName": "gcloud.pubsub", + "moduleName": "gcloud.pubsub", + "version": "1.0.0", + "type": "gcloud.pubsub", + "icon": "https://bcentral-packageicons.azureedge.net/images/ballerinax_gcloud.pubsub_1.0.0.png", + "properties": { + "subscription": { + "metadata": { + "label": "Subscription", + "description": "The name of the Pub/Sub subscription to pull messages from" + }, + "codedata": { + "argType": "SOURCE_ANNOTATION" + }, + "placeholder": "", + "valueType": "EXPRESSION", + "valueTypeConstraint": "string", + "value": "", + "items": [], + "typeMembers": [], + "enabled": true, + "editable": true, + "optional": false, + "advanced": false + }, + "configureListener": { + "metadata": { + "label": "Use Existing Listener", + "description": "Use Existing Listener or Create New Listener" + }, + "valueType": "CHOICE", + "value": true, + "choices": [ + { + "metadata": { + "label": "Use Existing Listener", + "description": "Use Existing Listener" + }, + "valueType": "FORM", + "value": "true", + "properties": { + "existingListener": { + "metadata": { + "label": "Select Listener", + "description": "Select from the existing Google Cloud Pub/Sub listeners" + }, + "valueType": "SINGLE_SELECT", + "value": "pubsubListener", + "items": [ + "pubsubListener" + ], + "enabled": true, + "editable": true, + "optional": false, + "advanced": false + } + }, + "enabled": false, + "editable": false, + "optional": false, + "advanced": false + }, + { + "metadata": { + "label": "Create New Listener", + "description": "Create a new Google Cloud Pub/Sub listener" + }, + "valueType": "FORM", + "value": "true", + "properties": { + "project": { + "metadata": { + "label": "Project ID", + "description": "The Google Cloud project ID" + }, + "codedata": { + "argType": "LISTENER_PARAM_REQUIRED" + }, + "placeholder": "\"my-gcp-project\"", + "valueType": "EXPRESSION", + "valueTypeConstraint": "string", + "value": "", + "items": [], + "typeMembers": [], + "enabled": true, + "editable": true, + "optional": false, + "advanced": false + }, + "credentials": { + "metadata": { + "label": "User Credentials JSON", + "description": "The service account credentials JSON file path" + }, + "codedata": { + "argType": "LISTENER_PARAM_INCLUDED_FIELD" + }, + "placeholder": "\"/path/to/credentials.json\"", + "valueType": "FILE_SELECT", + "valueTypeConstraint": "string", + "value": "", + "items": [], + "typeMembers": [], + "enabled": true, + "editable": true, + "optional": true, + "advanced": true + }, + "listenerVarName": { + "metadata": { + "label": "Listener Name", + "description": "Provide a name for the listener being created" + }, + "codedata": { + "type": "LISTENER_VAR_NAME" + }, + "valueType": "IDENTIFIER", + "valueTypeConstraint": "Global", + "value": "pubsubListenerResult", + "enabled": true, + "editable": true, + "optional": false, + "advanced": true + } + }, + "enabled": false, + "editable": false, + "optional": false, + "advanced": false + } + ], + "enabled": true, + "editable": true, + "optional": false, + "advanced": true + } + } + } + } +} diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_service_init_model/source/sample2/main.bal b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_service_init_model/source/sample2/main.bal index 32719957a6..7a3963260a 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_service_init_model/source/sample2/main.bal +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_service_init_model/source/sample2/main.bal @@ -1,6 +1,8 @@ import ballerinax/rabbitmq; import ballerinax/solace; +import ballerinax/gcloud.pubsub; listener rabbitmq:Listener orderListener = new (rabbitmq:DEFAULT_HOST, 5671); listener rabbitmq:Listener deliveryListener = new (rabbitmq:DEFAULT_HOST, 5671); listener solace:Listener solaceListener = new ("smf://localhost:55554", messageVpn = "default"); +listener pubsub:Listener pubsubListener = new ("project1", auth = {path: "path/to/auth.json"}); diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/gcp_service_model.json b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/gcp_service_model.json new file mode 100644 index 0000000000..37f9de7d89 --- /dev/null +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/gcp_service_model.json @@ -0,0 +1,469 @@ +{ + "filePath": "sample14/main.bal", + "description": "Test the generation of service model for a gcloud.pubsub service", + "start": { + "line": 4, + "offset": 0 + }, + "end": { + "line": 16, + "offset": 1 + }, + "response": { + "id": "17", + "name": "Google Cloud Pub/Sub Event Integration", + "type": "gcloud.pubsub", + "displayName": "Google Cloud Pub/Sub Event Integration", + "moduleName": "gcloud.pubsub", + "orgName": "ballerinax", + "version": "1.0.0", + "packageName": "gcloud.pubsub", + "listenerProtocol": "pubsub", + "icon": "https://bcentral-packageicons.azureedge.net/images/ballerinax_gcloud.pubsub_1.0.0.png", + "documentation": { + "metadata": { + "label": "Description", + "description": "The description of the class" + }, + "codedata": { + "type": "DOCUMENTATION" + }, + "valueType": "STRING", + "valueTypeConstraint": "string", + "enabled": true, + "editable": true, + "optional": true, + "advanced": false + }, + "properties": { + "listener": { + "metadata": { + "label": "Listener", + "description": "The Listener to be bound with the service" + }, + "codedata": { + "type": "LISTENER" + }, + "placeholder": "", + "valueType": "SINGLE_SELECT_LISTENER", + "valueTypeConstraint": "pubsub:Listener", + "value": "pubsubListener", + "values": [], + "items": [ + "pubsubListener" + ], + "properties": { + "pubsubListener": { + "codedata": { + "lineRange": { + "fileName": "main.bal", + "startLine": { + "line": 7, + "offset": 26 + }, + "endLine": { + "line": 7, + "offset": 40 + } + } + }, + "enabled": false, + "editable": false, + "optional": false, + "advanced": false + } + }, + "enabled": true, + "editable": true, + "optional": false, + "advanced": false + }, + "serviceType": { + "metadata": { + "label": "Service Type", + "description": "The name of the service contract type" + }, + "codedata": { + "type": "SERVICE_TYPE" + }, + "placeholder": "Service", + "valueType": "SINGLE_SELECT", + "valueTypeConstraint": "string", + "value": "Service", + "items": [ + "", + "Service" + ], + "enabled": false, + "editable": false, + "optional": false, + "advanced": false + }, + "readOnlyMetadata": { + "codedata": { + "type": "READONLY" + }, + "placeholder": "false", + "valueType": "SINGLE_SELECT", + "value": { + "Subscription": [ + "sub1" + ] + }, + "enabled": true, + "editable": true, + "optional": false, + "advanced": false + }, + "annotServiceConfig": { + "metadata": { + "label": "Service Configuration", + "description": "Define advanced subscription configurations" + }, + "codedata": { + "type": "ANNOTATION_ATTACHMENT", + "originalName": "ServiceConfig" + }, + "placeholder": "{}", + "valueType": "EXPRESSION", + "valueTypeConstraint": "pubsub:ServiceConfiguration", + "value": "{\n subscription: \"sub1\"\n}", + "values": [], + "typeMembers": [ + { + "type": "ServiceConfiguration", + "packageInfo": "ballerinax:gcloud.pubsub:1.0.0", + "kind": "RECORD_TYPE", + "selected": true + } + ], + "enabled": true, + "editable": true, + "optional": true, + "advanced": false + } + }, + "codedata": { + "lineRange": { + "fileName": "main.bal", + "startLine": { + "line": 4, + "offset": 0 + }, + "endLine": { + "line": 16, + "offset": 1 + } + }, + "orgName": "ballerinax", + "packageName": "gcloud.pubsub", + "moduleName": "gcloud.pubsub" + }, + "functions": [ + { + "metadata": { + "label": "onMessage", + "description": "The `onMessage` remote method will be triggered when a message is received from a Google Cloud Pub/Sub subscription" + }, + "kind": "REMOTE", + "name": { + "metadata": { + "label": "onMessage", + "description": "The `onMessage` remote method will be triggered when a message is received from a Google Cloud Pub/Sub subscription" + }, + "placeholder": "onMessage", + "valueType": "IDENTIFIER", + "value": "onMessage", + "enabled": true, + "editable": false, + "optional": false, + "advanced": false + }, + "parameters": [ + { + "metadata": { + "label": "message", + "description": "The received Pub/Sub message" + }, + "kind": "REQUIRED", + "type": { + "metadata": { + "label": "Parameter Type", + "description": "The type of the parameter" + }, + "placeholder": "pubsub:Message", + "valueType": "TYPE", + "value": "pubsub:Message", + "enabled": true, + "editable": true, + "optional": true, + "advanced": false + }, + "name": { + "metadata": { + "label": "message", + "description": "The received Pub/Sub message" + }, + "placeholder": "message", + "valueType": "IDENTIFIER", + "value": "message", + "enabled": true, + "editable": true, + "optional": false, + "advanced": false + }, + "defaultValue": { + "metadata": { + "label": "Default Value", + "description": "The default value of the parameter" + }, + "placeholder": "", + "valueType": "EXPRESSION", + "value": "", + "enabled": true, + "editable": true, + "optional": true, + "advanced": false + }, + "enabled": false, + "editable": true, + "optional": false, + "advanced": false, + "hidden": false, + "isGraphqlId": false + }, + { + "metadata": { + "label": "caller", + "description": "The Pub/Sub caller to acknowledge the message" + }, + "kind": "REQUIRED", + "type": { + "metadata": { + "label": "Parameter Type", + "description": "The type of the parameter" + }, + "placeholder": "pubsub:Caller", + "valueType": "TYPE", + "value": "pubsub:Caller", + "enabled": true, + "editable": false, + "optional": true, + "advanced": false + }, + "name": { + "metadata": { + "label": "caller", + "description": "The Pub/Sub caller to acknowledge the message" + }, + "placeholder": "caller", + "valueType": "IDENTIFIER", + "value": "caller", + "enabled": true, + "editable": false, + "optional": false, + "advanced": false + }, + "defaultValue": { + "metadata": { + "label": "Default Value", + "description": "The default value of the parameter" + }, + "placeholder": "", + "valueType": "EXPRESSION", + "value": "", + "enabled": true, + "editable": true, + "optional": true, + "advanced": false + }, + "enabled": true, + "editable": true, + "optional": false, + "advanced": false, + "hidden": false, + "isGraphqlId": false + }, + { + "metadata": { + "label": "Data Binding", + "description": "Data binding parameter" + }, + "kind": "DATA_BINDING", + "type": { + "valueType": "TYPE", + "value": "", + "enabled": true, + "editable": false, + "optional": false, + "advanced": false + }, + "name": { + "valueType": "IDENTIFIER", + "value": "message", + "enabled": true, + "editable": false, + "optional": false, + "advanced": false + }, + "enabled": false, + "editable": true, + "optional": false, + "advanced": false, + "hidden": false, + "isGraphqlId": false + } + ], + "returnType": { + "hasError": true, + "isGraphqlId": false, + "metadata": { + "label": "Return Type", + "description": "The return type of the function" + }, + "placeholder": "error?", + "valueType": "TYPE", + "value": "error?", + "enabled": true, + "editable": false, + "optional": true, + "advanced": false + }, + "enabled": true, + "optional": false, + "editable": false, + "canAddParameters": false, + "codedata": { + "lineRange": { + "fileName": "main.bal", + "startLine": { + "line": 8, + "offset": 4 + }, + "endLine": { + "line": 14, + "offset": 5 + } + }, + "moduleName": "gcloud.pubsub" + }, + "properties": { + "wrapperTypeName": { + "value": "PubSubMessage", + "enabled": false, + "editable": false, + "optional": false, + "advanced": false + }, + "payloadFieldName": { + "value": "data", + "enabled": false, + "editable": false, + "optional": false, + "advanced": false + }, + "canDataBind": { + "value": "true", + "enabled": false, + "editable": false, + "optional": false, + "advanced": false + } + } + }, + { + "metadata": { + "label": "onError", + "description": "Triggers when an error occurs during message processing" + }, + "kind": "REMOTE", + "name": { + "metadata": { + "label": "onError", + "description": "Triggers when an error occurs during message processing" + }, + "placeholder": "onError", + "valueType": "IDENTIFIER", + "value": "onError", + "enabled": true, + "editable": false, + "optional": false, + "advanced": false + }, + "parameters": [ + { + "metadata": { + "label": "error", + "description": "The error occurred during message processing" + }, + "kind": "REQUIRED", + "type": { + "metadata": { + "label": "Parameter Type", + "description": "The type of the parameter" + }, + "placeholder": "pubsub:Error", + "valueType": "TYPE", + "value": "pubsub:Error", + "enabled": true, + "editable": false, + "optional": true, + "advanced": false + }, + "name": { + "metadata": { + "label": "error", + "description": "The error occurred during message processing" + }, + "placeholder": "error", + "valueType": "IDENTIFIER", + "value": "error", + "enabled": true, + "editable": true, + "optional": false, + "advanced": false + }, + "defaultValue": { + "metadata": { + "label": "Default Value", + "description": "The default value of the parameter" + }, + "placeholder": "", + "valueType": "EXPRESSION", + "value": "", + "enabled": true, + "editable": true, + "optional": true, + "advanced": false + }, + "enabled": true, + "editable": true, + "optional": false, + "advanced": false, + "hidden": false, + "isGraphqlId": false + } + ], + "returnType": { + "hasError": true, + "isGraphqlId": false, + "metadata": { + "label": "Return Type", + "description": "The return type of the function" + }, + "placeholder": "error?", + "valueType": "TYPE", + "value": "error?", + "enabled": true, + "editable": false, + "optional": true, + "advanced": false + }, + "enabled": false, + "optional": true, + "editable": false, + "canAddParameters": false + } + ] + } +} diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/source/sample14/Ballerina.toml b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/source/sample14/Ballerina.toml new file mode 100644 index 0000000000..4c600c3a38 --- /dev/null +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/source/sample14/Ballerina.toml @@ -0,0 +1,6 @@ +[package] +org = "ballerina" +name = "service_designer" +version = "0.1.0" + +bi = true diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/source/sample14/main.bal b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/source/sample14/main.bal new file mode 100644 index 0000000000..a266f29b67 --- /dev/null +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/source/sample14/main.bal @@ -0,0 +1,17 @@ +import ballerinax/gcloud.pubsub; + +listener pubsub:Listener pubsubListener = new ("project1", auth = {path: "/Users/radith/Desktop/auth.json"}); + +@pubsub:ServiceConfig { + subscription: "sub1" +} +service pubsub:Service on pubsubListener { + remote function onMessage(PubSubMessage message, pubsub:Caller caller) returns error? { + do { + } on fail error err { + // handle error + return error("unhandled error", err); + } + } + +} diff --git a/service-model-generator/modules/service-model-index-generator/src/main/java/io/ballerina/indexgenerator/DatabaseManager.java b/service-model-generator/modules/service-model-index-generator/src/main/java/io/ballerina/indexgenerator/DatabaseManager.java index 26b32253be..86bbe12f0d 100644 --- a/service-model-generator/modules/service-model-index-generator/src/main/java/io/ballerina/indexgenerator/DatabaseManager.java +++ b/service-model-generator/modules/service-model-index-generator/src/main/java/io/ballerina/indexgenerator/DatabaseManager.java @@ -176,12 +176,13 @@ public static void insertAnnotation(int packageId, String annotName, String atta public static int insertServiceInitializerProperty(int packageId, String keyName, String label, String description, String defaultValue, String placeholder, String valueType, - String typeConstraint, String sourceKind, String selections) { + String typeConstraint, String sourceKind, String selections, + boolean optional, boolean advanced) { String sql = "INSERT INTO ServiceInitializerProperty (package_id, key_name, label, description, " + - "default_value, placeholder, value_type, type_constraint, source_kind, selections) " + - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + "default_value, placeholder, value_type, type_constraint, source_kind, selections, optional, advanced) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; return insertEntry(sql, new Object[]{packageId, keyName, label, description, defaultValue, placeholder, - valueType, typeConstraint, sourceKind, selections}); + valueType, typeConstraint, sourceKind, selections, optional ? 1 : 0, advanced ? 1 : 0}); } public static void insertServiceInitializerPropertyMemberType(int initializerId, String type, String kind, diff --git a/service-model-generator/modules/service-model-index-generator/src/main/java/io/ballerina/indexgenerator/ServiceIndexGenerator.java b/service-model-generator/modules/service-model-index-generator/src/main/java/io/ballerina/indexgenerator/ServiceIndexGenerator.java index 238a92e299..5e121e4e8d 100644 --- a/service-model-generator/modules/service-model-index-generator/src/main/java/io/ballerina/indexgenerator/ServiceIndexGenerator.java +++ b/service-model-generator/modules/service-model-index-generator/src/main/java/io/ballerina/indexgenerator/ServiceIndexGenerator.java @@ -240,7 +240,7 @@ private static void resolvePackage(BuildProject buildProject, String org, int id = DatabaseManager.insertServiceInitializerProperty(packageId, propertyName, property.label(), property.description(), property.defaultValue(), property.placeholder(), property.valueType(), property.typeConstraint(), property.sourceKind(), - GSON.toJson(property.selections())); + GSON.toJson(property.selections()), property.optional(), property.advanced()); for (ServiceInitializerPropertyMemberType memberType : property.typeMembers()) { DatabaseManager.insertServiceInitializerPropertyMemberType(id, memberType.type(), memberType.kind(), memberType.packageInfo()); @@ -698,7 +698,7 @@ record ServiceDeclaration(int optionalTypeDescriptor, String displayName, String record ServiceInitializerProperty(String label, String description, String defaultValue, String placeholder, String valueType, String typeConstraint, List typeMembers, String sourceKind, - List selections) { + List selections, boolean optional, boolean advanced) { } record ServiceInitializerPropertyMemberType(String type, String packageInfo, String kind) { diff --git a/service-model-generator/modules/service-model-index-generator/src/main/resources/service-index.sql b/service-model-generator/modules/service-model-index-generator/src/main/resources/service-index.sql index 0973c18022..776203dbc9 100644 --- a/service-model-generator/modules/service-model-index-generator/src/main/resources/service-index.sql +++ b/service-model-generator/modules/service-model-index-generator/src/main/resources/service-index.sql @@ -142,11 +142,13 @@ CREATE TABLE ServiceInitializerProperty ( description TEXT NOT NULL, default_value TEXT, placeholder TEXT, - value_type TEXT CHECK(value_type IN ('TYPE', 'FLAG', 'EXPRESSION', 'SINGLE_SELECT')), + value_type TEXT CHECK(value_type IN ('TYPE', 'FLAG', 'EXPRESSION', 'SINGLE_SELECT', 'FILE_SELECT')), type_constraint TEXT, source_kind TEXT CHECK(source_kind IN ('SERVICE_TYPE_DESCRIPTOR', 'SERVICE_BASE_PATH', 'LISTENER_PARAM_REQUIRED', 'LISTENER_PARAM_INCLUDED_DEFAULTABLE_FIELD', 'LISTENER_PARAM_INCLUDED_FIELD', 'SOURCE_ANNOTATION')), selections TEXT, -- Comma-separated values for selection options + optional INTEGER DEFAULT 0, -- Whether the property is optional + advanced INTEGER DEFAULT 0, -- Whether the property is advanced/hidden by default FOREIGN KEY (package_id) REFERENCES Package(package_id) ON DELETE CASCADE ); diff --git a/service-model-generator/modules/service-model-index-generator/src/main/resources/service_artifacts.json b/service-model-generator/modules/service-model-index-generator/src/main/resources/service_artifacts.json index d6599409b4..bbe40fd7d3 100644 --- a/service-model-generator/modules/service-model-index-generator/src/main/resources/service_artifacts.json +++ b/service-model-generator/modules/service-model-index-generator/src/main/resources/service_artifacts.json @@ -1452,6 +1452,151 @@ "kind": "ANNOTATION" } ] + }, + { + "name": "gcloud.pubsub", + "version": "1.0.0", + "serviceTypeSkipList": [], + "serviceDeclaration": { + "displayName": "Google Cloud Pub/Sub Event Integration", + "description": "Create a service to handle messages from a Google Cloud Pub/Sub subscription", + "optionalTypeDescriptor": 1, + "typeDescriptorLabel": "Service Type", + "typeDescriptorDescription": "The name of the service contract type", + "typeDescriptorDefaultValue": "Service", + "addDefaultTypeDescriptor": 0, + "optionalAbsoluteResourcePath": 1, + "absoluteResourcePathLabel": "Service Base Path", + "absoluteResourcePathDescription": "", + "absoluteResourcePathDefaultValue": "", + "optionalStringLiteral": 1, + "stringLiteralLabel": "Service Base Path", + "stringLiteralDescription": "", + "stringLiteralDefaultValue": "", + "listenerKind": "SINGLE_SELECT", + "kind": "event" + }, + "serviceTypes": { + "Service": { + "name": "Service", + "description": "", + "functions": [ + { + "name": "onMessage", + "description": "The `onMessage` remote method will be triggered when a message is received from a Google Cloud Pub/Sub subscription", + "accessor": "", + "kind": "REMOTE", + "returnType": "error?", + "returnError": 1, + "returnTypeEditable": 0, + "enable": 0, + "optional": 0, + "parameters": [ + { + "name": "message", + "label": "Message", + "description": "The received Pub/Sub message", + "kind": "REQUIRED", + "type": "pubsub:Message", + "defaultValue": "", + "importStatements": "", + "nameEditable": 1, + "typeEditable": 1 + }, + { + "name": "caller", + "label": "Caller", + "description": "The Pub/Sub caller to acknowledge the message", + "kind": "REQUIRED", + "type": "pubsub:Caller", + "defaultValue": "", + "importStatements": "", + "nameEditable": 0, + "typeEditable": 0 + } + ] + }, + { + "name": "onError", + "description": "Triggers when an error occurs during message processing", + "accessor": "", + "kind": "REMOTE", + "returnType": "error?", + "returnError": 1, + "returnTypeEditable": 0, + "enable": 0, + "optional": 1, + "parameters": [ + { + "name": "error", + "label": "Error", + "description": "The error occurred during message processing", + "kind": "REQUIRED", + "type": "pubsub:Error", + "defaultValue": "", + "importStatements": "", + "nameEditable": 1, + "typeEditable": 0 + } + ] + } + ] + } + }, + "annotations": { + "ServiceConfig": { + "attachmentPoints": [ + "SERVICE" + ], + "displayName": "Service Configuration", + "description": "Define advanced subscription configurations", + "typeConstraint": "pubsub:ServiceConfiguration" + } + }, + "initForm": { + "project": { + "label": "Project ID", + "description": "The Google Cloud project ID", + "defaultValue": "", + "placeholder": "\"my-gcp-project\"", + "valueType": "EXPRESSION", + "typeConstraint": "string", + "typeMembers": [], + "sourceKind": "LISTENER_PARAM_REQUIRED", + "selections": [] + }, + "subscription": { + "label": "Subscription", + "description": "The name of the Pub/Sub subscription to pull messages from", + "defaultValue": "", + "placeholder": "", + "valueType": "EXPRESSION", + "typeConstraint": "string", + "typeMembers": [], + "sourceKind": "SOURCE_ANNOTATION", + "selections": [] + }, + "credentials": { + "label": "User Credentials JSON", + "description": "The service account credentials JSON file path", + "defaultValue": "", + "placeholder": "\"/path/to/credentials.json\"", + "valueType": "FILE_SELECT", + "typeConstraint": "string", + "optional": true, + "advanced": true, + "typeMembers": [], + "sourceKind": "LISTENER_PARAM_INCLUDED_FIELD", + "selections": [] + } + }, + "readOnlyMetadata": [ + { + "key": "subscription", + "displayName": "Subscription", + "kind": "ANNOTATION" + } + ] } ] }