diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/ServiceModelGeneratorConstants.java b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/ServiceModelGeneratorConstants.java index 35fb17f71..e3b742330 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/ServiceModelGeneratorConstants.java +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/ServiceModelGeneratorConstants.java @@ -35,6 +35,9 @@ public class ServiceModelGeneratorConstants { public static final String SINGLE_SELECT_VALUE = "SINGLE_SELECT"; public static final String MULTIPLE_SELECT_VALUE = "MULTIPLE_SELECT"; + public static final String HTTP_DEFAULT_LISTENER_REF = "default:httpListener"; + public static final String HTTP_DEFAULT_MODULE = "http.default"; + public static final String KAFKA = "kafka"; public static final String HTTP = "http"; public static final String GRAPHQL = "graphql"; @@ -57,6 +60,7 @@ public class ServiceModelGeneratorConstants { public static final String VALUE_TYPE_EXPRESSION = "EXPRESSION"; public static final String VALUE_TYPE_IDENTIFIER = "IDENTIFIER"; public static final String VALUE_TYPE_TYPE = "TYPE"; + public static final String HTTP_PARAM_TYPE_QUERY = "QUERY"; public static final String TYPE_HTTP_SERVICE_CONFIG = "http:ServiceConfig"; diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/ServiceModelGeneratorService.java b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/ServiceModelGeneratorService.java index 776b25e90..5de627886 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/ServiceModelGeneratorService.java +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/ServiceModelGeneratorService.java @@ -95,10 +95,12 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -112,6 +114,7 @@ import static io.ballerina.servicemodelgenerator.extension.Utils.getResourceFunctionModel; import static io.ballerina.servicemodelgenerator.extension.Utils.getServiceDeclarationNode; import static io.ballerina.servicemodelgenerator.extension.Utils.importExists; +import static io.ballerina.servicemodelgenerator.extension.Utils.isHttpDefaultListenerAttached; import static io.ballerina.servicemodelgenerator.extension.Utils.isHttpServiceContractType; import static io.ballerina.servicemodelgenerator.extension.Utils.populateProperties; import static io.ballerina.servicemodelgenerator.extension.Utils.updateListenerModel; @@ -172,7 +175,7 @@ public CompletableFuture getListeners(ListenerDiscove Module module = currentPackage.module(ModuleName.from(currentPackage.packageName())); ModuleId moduleId = module.moduleId(); SemanticModel semanticModel = currentPackage.getCompilation().getSemanticModel(moduleId); - List listeners = getCompatibleListeners(request.moduleName(), semanticModel); + Set listeners = getCompatibleListeners(request.moduleName(), semanticModel); return new ListenerDiscoveryResponse(listeners); } catch (Throwable e) { return new ListenerDiscoveryResponse(e); @@ -180,15 +183,24 @@ public CompletableFuture getListeners(ListenerDiscove }); } - private static List getCompatibleListeners(String moduleName, SemanticModel semanticModel) { - return semanticModel.moduleSymbols().stream() - .filter(symbol -> symbol instanceof VariableSymbol) - .map(symbol -> (VariableSymbol) symbol) - .filter(variableSymbol -> variableSymbol.qualifiers().contains(Qualifier.LISTENER)) - .filter(variableSymbol -> variableSymbol.typeDescriptor().getModule().isPresent() && variableSymbol - .typeDescriptor().getModule().get().id().moduleName().equals(moduleName)) - .map(variableSymbol -> variableSymbol.getName().orElse("")) - .toList(); + private static Set getCompatibleListeners(String moduleName, SemanticModel semanticModel) { + Set listeners = new LinkedHashSet<>(); + if (ServiceModelGeneratorConstants.HTTP.equals(moduleName)) { + listeners.add(ServiceModelGeneratorConstants.HTTP_DEFAULT_LISTENER_REF); + } + + for (Symbol moduleSymbol : semanticModel.moduleSymbols()) { + if (!(moduleSymbol instanceof VariableSymbol variableSymbol) + || !variableSymbol.qualifiers().contains(Qualifier.LISTENER)) { + continue; + } + Optional module = variableSymbol.typeDescriptor().getModule(); + if (module.isEmpty() || !module.get().id().moduleName().equals(moduleName)) { + continue; + } + variableSymbol.getName().ifPresent(listeners::add); + } + return listeners; } /** @@ -273,7 +285,7 @@ public CompletableFuture getServiceModel(ServiceModelReque } SyntaxTree syntaxTree = document.get().syntaxTree(); ModulePartNode modulePartNode = syntaxTree.rootNode(); - List listenersList = getCompatibleListeners(request.moduleName(), semanticModel); + Set listenersList = getCompatibleListeners(request.moduleName(), semanticModel); if (Objects.nonNull(request.listenerName())) { listener.addValue(request.listenerName()); removeAlreadyDefinedServiceTypes(serviceModel, request.listenerName(), modulePartNode); @@ -284,7 +296,7 @@ public CompletableFuture getServiceModel(ServiceModelReque } else { listener.setValueType(ServiceModelGeneratorConstants.MULTIPLE_SELECT_VALUE); } - listener.setItems(listenersList); + listener.setItems(listenersList.stream().toList()); } return new ServiceModelResponse(serviceModel); } catch (Throwable e) { @@ -357,6 +369,14 @@ public CompletableFuture addService(ServiceSourceRequest r String importText = Utils.getImportStmt(service.getOrgName(), service.getModuleName()); edits.add(new TextEdit(Utils.toRange(lineRange.startLine()), importText)); } + if (ServiceModelGeneratorConstants.HTTP.equals(service.getModuleName()) + && isHttpDefaultListenerAttached(service.getListener()) && + !importExists(node, service.getOrgName(), ServiceModelGeneratorConstants.HTTP_DEFAULT_MODULE)) { + String importText = Utils.getImportStmt(service.getOrgName(), + ServiceModelGeneratorConstants.HTTP_DEFAULT_MODULE); + edits.add(new TextEdit(Utils.toRange(lineRange.startLine()), importText)); + } + edits.add(new TextEdit(Utils.toRange(lineRange.endLine()), ServiceModelGeneratorConstants.LINE_SEPARATOR + serviceDeclaration)); return new CommonSourceResponse(Map.of(request.filePath(), edits)); @@ -522,15 +542,21 @@ public CompletableFuture getServiceFromSource(CommonM } else { updateServiceModel(serviceModel, serviceNode, semanticModel); } - List listenersList = getCompatibleListeners(serviceName.get(), semanticModel); + Set listeners = getCompatibleListeners(serviceName.get(), semanticModel); + List allValues = serviceModel.getListener().getValues(); + if (allValues.isEmpty()) { + listeners.add(serviceModel.getListener().getValue()); + } else { + listeners.addAll(allValues); + } Value listener = serviceModel.getListener(); - if (!listenersList.isEmpty()) { + if (!listeners.isEmpty()) { if (serviceName.get().equals(ServiceModelGeneratorConstants.KAFKA)) { listener.setValueType(ServiceModelGeneratorConstants.SINGLE_SELECT_VALUE); } else { listener.setValueType(ServiceModelGeneratorConstants.MULTIPLE_SELECT_VALUE); } - listener.setItems(listenersList); + listener.setItems(listeners.stream().toList()); } return new ServiceFromSourceResponse(serviceModel); }); @@ -770,6 +796,14 @@ public CompletableFuture updateService(ServiceModifierRequ edits.add(listenerEdit); } } + if (ServiceModelGeneratorConstants.HTTP.equals(service.getModuleName()) && + isHttpDefaultListenerAttached(service.getListener()) && + !importExists(modulePartNode, service.getOrgName(), + ServiceModelGeneratorConstants.HTTP_DEFAULT_MODULE)) { + String importText = Utils.getImportStmt(service.getOrgName(), + ServiceModelGeneratorConstants.HTTP_DEFAULT_MODULE); + edits.add(new TextEdit(Utils.toRange(lineRange.startLine()), importText)); + } return new CommonSourceResponse(Map.of(request.filePath(), edits)); } catch (Throwable e) { return new CommonSourceResponse(e); diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/Utils.java b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/Utils.java index 6920aa6fd..3e99b8d6d 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/Utils.java +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/Utils.java @@ -462,7 +462,7 @@ public static Function getFunctionModel(MethodDeclarationNode functionDefinition SeparatedNodeList parameters = functionSignatureNode.parameters(); List parameterModels = new ArrayList<>(); parameters.forEach(parameterNode -> { - Optional parameterModel = getParameterModel(parameterNode); + Optional parameterModel = getParameterModel(parameterNode, isHttp); parameterModel.ifPresent(parameterModels::add); }); functionModel.setParameters(parameterModels); @@ -508,7 +508,7 @@ public static Function getFunctionModel(FunctionDefinitionNode functionDefinitio SeparatedNodeList parameters = functionSignatureNode.parameters(); List parameterModels = new ArrayList<>(); parameters.forEach(parameterNode -> { - Optional parameterModel = getParameterModel(parameterNode); + Optional parameterModel = getParameterModel(parameterNode, isHttp); parameterModel.ifPresent(parameterModels::add); }); functionModel.setParameters(parameterModels); @@ -700,18 +700,18 @@ public static Optional getHttpParameterType(NodeList ann return Optional.empty(); } - public static Optional getParameterModel(ParameterNode parameterNode) { + public static Optional getParameterModel(ParameterNode parameterNode, boolean isHttp) { if (parameterNode instanceof RequiredParameterNode parameter) { String paramName = parameter.paramName().get().toString().trim(); Parameter parameterModel = createParameter(paramName, ServiceModelGeneratorConstants.KIND_REQUIRED, ServiceModelGeneratorConstants.VALUE_TYPE_IDENTIFIER, parameter.typeName().toString().trim(), - parameter.annotations()); + parameter.annotations(), isHttp); return Optional.of(parameterModel); } else if (parameterNode instanceof DefaultableParameterNode parameter) { String paramName = parameter.paramName().get().toString().trim(); Parameter parameterModel = createParameter(paramName, ServiceModelGeneratorConstants.KIND_DEFAULTABLE, ServiceModelGeneratorConstants.VALUE_TYPE_EXPRESSION, parameter.typeName().toString().trim(), - parameter.annotations()); + parameter.annotations(), isHttp); Value defaultValue = parameterModel.getDefaultValue(); defaultValue.setValue(parameter.expression().toString().trim()); defaultValue.setValueType(ServiceModelGeneratorConstants.VALUE_TYPE_EXPRESSION); @@ -722,11 +722,18 @@ public static Optional getParameterModel(ParameterNode parameterNode) } private static Parameter createParameter(String paramName, String paramKind, String valueType, String typeName, - NodeList annotationNodes) { + NodeList annotationNodes, boolean isHttp) { Parameter parameterModel = Parameter.getNewParameter(); parameterModel.setMetadata(new MetaData(paramName, paramName)); parameterModel.setKind(paramKind); - getHttpParameterType(annotationNodes).ifPresent(parameterModel::setHttpParamType); + if (isHttp) { + Optional httpParameterType = getHttpParameterType(annotationNodes); + if (httpParameterType.isPresent()) { + parameterModel.setHttpParamType(httpParameterType.get()); + } else { + parameterModel.setHttpParamType(ServiceModelGeneratorConstants.HTTP_PARAM_TYPE_QUERY); + } + } Value type = parameterModel.getType(); type.setValue(typeName); type.setValueType(ServiceModelGeneratorConstants.VALUE_TYPE_TYPE); @@ -1233,4 +1240,17 @@ public static String getExprUri(String sourcePath) { String exprUriString = "expr" + Paths.get(sourcePath).toUri().toString().substring(4); return URI.create(exprUriString).toString(); } + + /** + * Check if the `default:httpListener` is attached to the service. + * + * @param value the Listener value + * @return true if the `default:httpListener` is attached, false otherwise + */ + public static boolean isHttpDefaultListenerAttached(Value value) { + if (value.getValues().isEmpty()) { + return ServiceModelGeneratorConstants.HTTP_DEFAULT_LISTENER_REF.equals(value.getValue().trim()); + } + return value.getValues().contains(ServiceModelGeneratorConstants.HTTP_DEFAULT_LISTENER_REF); + } } diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/response/ListenerDiscoveryResponse.java b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/response/ListenerDiscoveryResponse.java index a99dee65e..888910da9 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/response/ListenerDiscoveryResponse.java +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/response/ListenerDiscoveryResponse.java @@ -18,22 +18,22 @@ package io.ballerina.servicemodelgenerator.extension.response; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; +import java.util.HashSet; +import java.util.Set; -public record ListenerDiscoveryResponse(boolean hasListeners, List listeners, String errorMsg, +public record ListenerDiscoveryResponse(boolean hasListeners, Set listeners, String errorMsg, String stacktrace) { public ListenerDiscoveryResponse() { - this(false, new ArrayList<>(), null, null); + this(false, new HashSet<>(), null, null); } - public ListenerDiscoveryResponse(List listeners) { + public ListenerDiscoveryResponse(Set listeners) { this(!listeners.isEmpty(), listeners, null, null); } public ListenerDiscoveryResponse(Throwable e) { - this(false, new ArrayList<>(), e.toString(), Arrays.toString(e.getStackTrace())); + this(false, new HashSet<>(), e.toString(), Arrays.toString(e.getStackTrace())); } } diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/test/java/io/ballerina/servicemodelgenerator/extension/ServiceModelAPITests.java b/service-model-generator/modules/service-model-generator-ls-extension/src/test/java/io/ballerina/servicemodelgenerator/extension/ServiceModelAPITests.java index 1da82166e..760cc3eca 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/test/java/io/ballerina/servicemodelgenerator/extension/ServiceModelAPITests.java +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/test/java/io/ballerina/servicemodelgenerator/extension/ServiceModelAPITests.java @@ -80,8 +80,8 @@ public void testListenerDiscoveryWithEmptyFile() throws ExecutionException, Inte "ballerina", "http"); CompletableFuture result = serviceEndpoint.request("serviceDesign/getListeners", request); ListenerDiscoveryResponse response = (ListenerDiscoveryResponse) result.get(); - Assert.assertFalse(response.hasListeners()); - Assert.assertEquals(response.listeners().size(), 0); + Assert.assertTrue(response.hasListeners()); + Assert.assertEquals(response.listeners().size(), 1); } @Test(enabled = false) @@ -204,7 +204,7 @@ public void testAddHttpService() throws ExecutionException, InterruptedException ServiceModelResponse modelResponse = (ServiceModelResponse) modelResult.get(); Service service = modelResponse.service(); Assert.assertTrue(Objects.nonNull(service)); - service.getListener().setValues(List.of("httpTestListener", "httpsTestListener")); + service.getListener().setValues(List.of("default:httpListener", "httpsTestListener")); Value designApproach = service.getDesignApproach(); Value selectedApproach = designApproach.getChoices().getFirst(); Value basePath = selectedApproach.getProperty("basePath"); @@ -523,7 +523,7 @@ public void testUpdateHttpService() throws ExecutionException, InterruptedExcept ServiceFromSourceResponse sourceResponse = (ServiceFromSourceResponse) sourceResult.get(); Service service = sourceResponse.service(); Assert.assertTrue(Objects.nonNull(service)); - service.getListener().setValue("newHttpListener"); + service.getListener().setValues(List.of("newHttpListener", "default:httpListener")); service.getBasePath().setValue("/api/v1/test/new"); ServiceModifierRequest updateRequest = new ServiceModifierRequest(filePath.toAbsolutePath().toString(), diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/http_service_with_anonymous_listener.json b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/http_service_with_anonymous_listener.json index df8df5636..56cd5f34e 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/http_service_with_anonymous_listener.json +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/http_service_with_anonymous_listener.json @@ -39,6 +39,10 @@ "placeholder": "", "optional": false, "advanced": false, + "items": [ + "default:httpListener", + "new http:Listener(port = 8080)" + ], "codedata": { "inListenerInit": false, "isBasePath": false, @@ -371,7 +375,8 @@ "enabled": true, "editable": false, "optional": false, - "advanced": false + "advanced": false, + "httpParamType": "QUERY" } ], "schema": { diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/http_service_with_global_listener.json b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/http_service_with_global_listener.json index 84225cba2..f5c8ade9c 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/http_service_with_global_listener.json +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/http_service_with_global_listener.json @@ -45,7 +45,10 @@ "optional": false, "advanced": false, "items": [ + "default:globalListener", + "default:httpListener", "httpListener", + "new http:Listener(port = 8080)", "githubListener" ], "codedata": { diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/http_service_with_multiple_listeners.json b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/http_service_with_multiple_listeners.json index e0f02abbc..1d62a2e57 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/http_service_with_multiple_listeners.json +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/http_service_with_multiple_listeners.json @@ -44,7 +44,9 @@ "optional": false, "advanced": false, "items": [ + "default:httpListener", "httpListener", + "new http:Listener(port = 8080)", "githubListener" ], "codedata": { diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/http_service_with_single_listener.json b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/http_service_with_single_listener.json index 9935650f9..ba06fc45d 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/http_service_with_single_listener.json +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/get_sm_from_source/config/http_service_with_single_listener.json @@ -40,6 +40,7 @@ "optional": false, "advanced": false, "items": [ + "default:httpListener", "httpListener", "githubListener" ], @@ -375,7 +376,8 @@ "enabled": true, "editable": false, "optional": false, - "advanced": false + "advanced": false, + "httpParamType": "QUERY" } ], "schema": {