Skip to content
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,17 @@ 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")
);

/**
* Mapping of module names to annotation field names for services that derive their name from annotations. Each
* module can have multiple field names to try in order of preference.
*/
private static final Map<String, String[]> moduleAnnotationFields = Map.of(
"solace", new String[]{"queueName", "topicName"}
"solace", new String[]{"queueName", "topicName"},
"gcloud.pubsub", new String[]{"subscription"}
);

public static String getCategory(String type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,9 @@ public Optional<ServiceInitInfo> 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 = ?");

Expand Down Expand Up @@ -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")
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ParameterMemberTypeData> memberTypes) {
String selections, List<ParameterMemberTypeData> memberTypes, boolean optional,
boolean advanced) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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<Function> getFunctionBuilder(String protocol) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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
*/
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, List<TextEdit>> updateModel(UpdateModelContext context) {
Map<String, List<TextEdit>> databindEdits = DatabindUtil.processDatabindingUpdate(
context, TYPE_PREFIX, REQUIRED_PARAM_TYPE, PAYLOAD_FIELD_NAME, false);

Map<String, List<TextEdit>> mainFileEdits = super.updateModel(context);
Map<String, List<TextEdit>> allEdits = new HashMap<>(mainFileEdits);
allEdits.putAll(databindEdits);
return allEdits;
}

@Override
public Map<String, List<TextEdit>> addModel(AddModelContext context) throws Exception {
Map<String, List<TextEdit>> databindEdits = DatabindUtil.processDatabindingForAdd(
context, TYPE_PREFIX, REQUIRED_PARAM_TYPE, PAYLOAD_FIELD_NAME, false);

Map<String, List<TextEdit>> mainFileEdits = super.addModel(context);
Map<String, List<TextEdit>> allEdits = new HashMap<>(mainFileEdits);
allEdits.putAll(databindEdits);
return allEdits;
}

@Override
public String kind() {
return GCLOUD_PUBSUB;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, Value> properties = serviceInitModel.getProperties();
Set<String> listeners = ListenerUtil.getCompatibleListeners(context.moduleName(),
context.semanticModel(), context.project());

if (!listeners.isEmpty()) {
Map<String, Value> 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<String, List<TextEdit>> addServiceInitSource(AddServiceInitModelContext context) {
ServiceInitModel serviceInitModel = context.serviceInitModel();
Map<String, Value> 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<String, List<TextEdit>> addServiceWithNewListener(AddServiceInitModelContext context) {
ListenerDTO listenerDTO = buildListenerDTO(context);
String serviceCode = buildPubsubServiceCode(context.serviceInitModel(), listenerDTO);
return buildServiceCodeEdits(context, serviceCode, null);
}

private void applyCredentialsProperty(Map<String, Value> 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<String, Value> properties = serviceInitModel.getProperties();

String serviceAnnotation = buildServiceAnnotation(TYPE_PUBSUB_SERVICE_CONFIG, properties);

List<Function> functions = getRequiredFunctionsForServiceType(serviceInitModel);
List<String> 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;
}
}
Loading
Loading