From db1342153951802e0d1320c63df629f3531fbc8c Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Tue, 26 May 2026 15:25:37 +0530 Subject: [PATCH 01/34] Generate PR to sync with the main branch --- .github/workflows/release.yml | 46 +++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 60f3c67c2..938259be1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,11 +47,43 @@ jobs: name: ${{ steps.ls.outputs.fileName }}.zip - name: Create a release in repo run: | - createResponse=`curl -X POST -H "Accept: application/vnd.github.v3+json" \ - -H "Authorization:token ${{ secrets.GIT_BOT_TOKEN }}" -d '{"tag_name":"v${{ steps.ls.outputs.version }}", \ - "draft":false, "name": "Release v${{ steps.ls.outputs.version }}", "prerelease":true}' \ - https://api.github.com/repos/${{ github.repository }}/releases` \ - && id=`echo "$createResponse" | sed -n -e 's/"id":\ \([0-9]\+\),/\1/p' | head -n 1 | sed 's/[[:blank:]]//g'` && \ - uploadResponse=`curl -X POST -H "Authorization:token ${{ secrets.GIT_BOT_TOKEN }}" -H "Content-Type:application/octet-stream" \ + set -euo pipefail + createResponse=$(curl --fail-with-body -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization:token ${{ secrets.GIT_BOT_TOKEN }}" \ + -d '{"tag_name":"v${{ steps.ls.outputs.version }}", "draft":false, "name": "Release v${{ steps.ls.outputs.version }}", "prerelease":true}' \ + https://api.github.com/repos/${{ github.repository }}/releases) + id=$(echo "$createResponse" | jq -r '.id // empty') + if [ -z "$id" ]; then + echo "Failed to parse release id from response:" + echo "$createResponse" + exit 1 + fi + curl --fail-with-body -X POST \ + -H "Authorization:token ${{ secrets.GIT_BOT_TOKEN }}" \ + -H "Content-Type:application/octet-stream" \ --data-binary @${{ steps.ls.outputs.fileName }}.zip \ - https://uploads.github.com/repos/${{ github.repository }}/releases/$id/assets?name=${{ steps.ls.outputs.fileName }}.zip` + "https://uploads.github.com/repos/${{ github.repository }}/releases/$id/assets?name=${{ steps.ls.outputs.fileName }}.zip" + + - name: Create sync PR from stable/mi to main + if: github.ref == 'refs/heads/stable/mi' + env: + GH_TOKEN: ${{ secrets.GIT_BOT_TOKEN }} + run: | + git fetch --unshallow origin main stable/mi 2>/dev/null || git fetch origin main stable/mi + commits=$(git rev-list --count origin/main..origin/stable/mi) + if [ "$commits" -eq 0 ]; then + echo "No new commits on stable/mi compared to main — skipping PR creation." + exit 0 + fi + existing=$(gh pr list --repo ${{ github.repository }} --base main --head stable/mi --state open --json number --jq '.[0].number // empty') + if [ -n "$existing" ]; then + echo "An open PR from stable/mi to main already exists: #$existing" + exit 0 + fi + gh pr create \ + --repo ${{ github.repository }} \ + --base main \ + --head stable/mi \ + --title "Sync stable/mi to main after release v${{ steps.ls.outputs.version }}" \ + --body "Automated PR to sync \`stable/mi\` into \`main\` following the successful release of v${{ steps.ls.outputs.version }}." From a3149e5ba4140c846c04d3b1abf60699a4b255b2 Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Fri, 29 May 2026 10:28:03 +0530 Subject: [PATCH 02/34] Support the startOnLoad attribute for tasks in MI 4.1.0 --- .../src/main/resources/org/eclipse/lemminx/schemas/430/task.xsd | 1 + 1 file changed, 1 insertion(+) diff --git a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/task.xsd b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/task.xsd index 4c5482d16..830cc1f11 100644 --- a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/task.xsd +++ b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/task.xsd @@ -49,6 +49,7 @@ + From 4d8782453bbe88926af40af3950b6ba768d526e3 Mon Sep 17 00:00:00 2001 From: dulavinya Date: Mon, 4 May 2026 13:30:57 +0530 Subject: [PATCH 03/34] Added mcp server section to the directory tree builder --- .../directoryTree/DirectoryTreeBuilder.java | 104 +++++++++++++++++- 1 file changed, 102 insertions(+), 2 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java index ab3ea82c4..2c2b20064 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java @@ -55,7 +55,9 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; @@ -69,6 +71,10 @@ public class DirectoryTreeBuilder { private static final String WSO2MI = "wso2mi"; private static final String RESOURCES = "resources"; private static final String JAVA = "java"; + private static final String MCP_CONFIG_SUFFIX = "-mcp-config"; + private static final String MCP_ENDPOINT_SUFFIX = "-endpoint"; + private static final String MCP_SERVERS_SECTION = "MCP Servers"; + private static final String MCP_SERVERS_KEY = "mcpServers"; private static String projectPath; private static String mainSequence; private static List artifactResourcePaths = new ArrayList<>(); @@ -104,9 +110,37 @@ public static DirectoryMapResponse buildDirectoryTree(WorkspaceFolder projectFol } DirectoryMapResponse directoryMapResponse = new DirectoryMapResponse(directoryTree); + if (directoryTree instanceof IntegrationDirectoryTree) { + applyMcpClassification(directoryMapResponse); + } return directoryMapResponse; } + /** + * Classifies MCP server artifacts in the raw directory tree response. + */ + private static void applyMcpClassification(DirectoryMapResponse response) { + + if (response.getDirectoryMap() == null) return; + try { + ObjectMapper mapper = new ObjectMapper(); + JsonNode root = mapper.readTree(response.getDirectoryMap().getAsJsonObject().toString()); + JsonNode artifactsNode = root.path(Constant.SRC).path(MAIN).path(WSO2MI).path(Constant.ARTIFACTS); + if (artifactsNode.isMissingNode() || !artifactsNode.isObject()) return; + + ArrayNode[] result = extractMcpServers(mapper, artifactsNode); + ObjectNode artifacts = (ObjectNode) artifactsNode; + artifacts.set(Constant.INBOUNDENDPOINTS, result[1]); + artifacts.set(Constant.LOCALENTRIES, result[2]); + artifacts.set(MCP_SERVERS_KEY, result[0]); + + String updated = mapper.writeValueAsString(root); + response.setDirectoryMap(JsonParser.parseString(updated)); + } catch (JsonProcessingException e) { + LOGGER.log(Level.SEVERE, "Error occurred while applying MCP classification.", e); + } + } + /** * Generate model for the project explorer * @@ -128,10 +162,18 @@ public static DirectoryMapResponse getProjectExplorerModel(WorkspaceFolder proje JsonNode resources = root.path(Constant.SRC).path(MAIN).path(WSO2MI).path(Constant.RESOURCES); ObjectNode newArtifacts = mapper.createObjectNode(); + ArrayNode mcpServersArray = artifacts.path(MCP_SERVERS_KEY).isArray() + ? (ArrayNode) artifacts.path(MCP_SERVERS_KEY) : mapper.createArrayNode(); + ArrayNode filteredInboundEndpoints = artifacts.path(Constant.INBOUNDENDPOINTS).isArray() + ? (ArrayNode) artifacts.path(Constant.INBOUNDENDPOINTS) : mapper.createArrayNode(); + ArrayNode filteredLocalEntries = artifacts.path(Constant.LOCALENTRIES).isArray() + ? (ArrayNode) artifacts.path(Constant.LOCALENTRIES) : mapper.createArrayNode(); + newArtifacts.set("APIs", artifacts.path(Constant.APIS)); - newArtifacts.set("Event Integrations", artifacts.path(Constant.INBOUNDENDPOINTS)); + newArtifacts.set("Event Integrations", filteredInboundEndpoints); newArtifacts.set("Automations", artifacts.path(Constant.TASKS)); newArtifacts.set("Data Services", artifacts.path(Constant.DATA_SERVICES)); + newArtifacts.set(MCP_SERVERS_SECTION, mcpServersArray); ObjectNode otherArtifacts = newArtifacts.putObject("Other Artifacts"); otherArtifacts.set("Sequences", artifacts.path(Constant.SEQUENCES)); @@ -152,7 +194,7 @@ public static DirectoryMapResponse getProjectExplorerModel(WorkspaceFolder proje otherArtifacts.set("Proxy Services", artifacts.path(Constant.PROXYSERVICES)); otherArtifacts.set("Message Stores", artifacts.path(Constant.MESSAGE_STORES)); otherArtifacts.set("Message Processors", artifacts.path(Constant.MESSAGE_PROCESSORS)); - otherArtifacts.set("Local Entries", artifacts.path(Constant.LOCALENTRIES)); + otherArtifacts.set("Local Entries", filteredLocalEntries); otherArtifacts.set("Templates", artifacts.path(Constant.TEMPLATES)); JsonNode registryFolders = root.path(Constant.SRC).path(MAIN).path(WSO2MI).path(Constant.RESOURCES) @@ -822,6 +864,64 @@ private static void addResources(DOMElement rootElement, AdvancedNode advancedNo } } + /** + * Separates MCP server artifacts from the regular inbound endpoints and local entries. + */ + private static ArrayNode[] extractMcpServers(ObjectMapper mapper, JsonNode artifacts) { + + JsonNode inboundEndpointsNode = artifacts.path(Constant.INBOUNDENDPOINTS); + JsonNode localEntriesNode = artifacts.path(Constant.LOCALENTRIES); + + Map mcpLocalEntries = new LinkedHashMap<>(); + ArrayNode filteredLocalEntries = mapper.createArrayNode(); + for (JsonNode localEntry : localEntriesNode) { + if (!localEntry.has(Constant.NAME)) { + // Connector group object (e.g. {"HTTP": [...]}) — keep as-is + filteredLocalEntries.add(localEntry); + continue; + } + String entryName = localEntry.path(Constant.NAME).asText(); + if (entryName.endsWith(MCP_CONFIG_SUFFIX)) { + String serverName = entryName.substring(0, entryName.length() - MCP_CONFIG_SUFFIX.length()); + mcpLocalEntries.put(serverName, localEntry); + } else { + filteredLocalEntries.add(localEntry); + } + } + + Map mcpInboundEndpoints = new LinkedHashMap<>(); + ArrayNode filteredInboundEndpoints = mapper.createArrayNode(); + for (JsonNode inboundEndpoint : inboundEndpointsNode) { + String endpointName = inboundEndpoint.path(Constant.NAME).asText(); + if (endpointName.endsWith(MCP_ENDPOINT_SUFFIX)) { + String serverName = endpointName.substring(0, endpointName.length() - MCP_ENDPOINT_SUFFIX.length()); + if (mcpLocalEntries.containsKey(serverName)) { + mcpInboundEndpoints.put(serverName, inboundEndpoint); + continue; + } + } + filteredInboundEndpoints.add(inboundEndpoint); + } + + // Restore any -mcp-config local entries that have no matching -endpoint + for (Map.Entry entry : mcpLocalEntries.entrySet()) { + if (!mcpInboundEndpoints.containsKey(entry.getKey())) { + filteredLocalEntries.add(entry.getValue()); + } + } + + ArrayNode mcpServersArray = mapper.createArrayNode(); + for (String serverName : mcpInboundEndpoints.keySet()) { + ObjectNode mcpServer = mapper.createObjectNode(); + mcpServer.put(Constant.NAME, serverName); + mcpServer.set(Constant.LOCAL_ENTRY, mcpLocalEntries.get(serverName)); + mcpServer.set(Constant.INBOUND_ENDPOINT, mcpInboundEndpoints.get(serverName)); + mcpServersArray.add(mcpServer); + } + + return new ArrayNode[]{mcpServersArray, filteredInboundEndpoints, filteredLocalEntries}; + } + private static void extractClassMediators(JsonNode mediatorFolders, ArrayNode classMediatorArray) { for (JsonNode classMediatorFolder : mediatorFolders) { if (classMediatorFolder.has(Constant.FILES)) { From bc0bc6686e99f59df12294fa2dd8a700a4e748ac Mon Sep 17 00:00:00 2001 From: dulavinya Date: Sat, 9 May 2026 11:26:49 +0530 Subject: [PATCH 04/34] Preserve mcpServers in directory tree format conversion --- .../synapse/directoryTree/DirectoryMapResponse.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryMapResponse.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryMapResponse.java index 8e707d589..ccaae0846 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryMapResponse.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryMapResponse.java @@ -70,7 +70,7 @@ private JsonElement convertFormat(JsonElement jsonTree) { String[] artifactNames = {"apis", "endpoints", "sequences", "proxyServices", "inboundEndpoints", "messageStores", "messageProcessors", "tasks", "localEntries", "connections", "templates", - "dataServices", "dataSources"}; + "dataServices", "dataSources", "mcpServers"}; processLocalEntries(jsonObject); for (String element : artifactNames) { artifacts.add(element, jsonObject.get(element)); From b5f761770415e21447e71fcf049b93dac612e34e Mon Sep 17 00:00:00 2001 From: dulavinya Date: Sat, 9 May 2026 11:39:05 +0530 Subject: [PATCH 05/34] Update golden test files --- .../synapse/directorytree.builder/generated-directory-tree.json | 2 +- .../directorytree.builder/generated-project-explorer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-directory-tree.json b/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-directory-tree.json index a792654f1..0dbfba822 100644 --- a/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-directory-tree.json +++ b/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-directory-tree.json @@ -1 +1 @@ -{"src":{"main":{"wso2mi":{"artifacts":{"apis":[{"context":"/test","resources":[{"methods":"GET","uriTemplate":"/","urlMapping":null}],"sequences":[],"endpoints":[],"type":"API","subType":null,"name":"testApi","isFaulty":false}],"endpoints":[{"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint3","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint4","isFaulty":false}],"sequences":[{"isMainSequence":true,"sequences":[],"endpoints":[],"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence2","isFaulty":false}],"proxyServices":[{"sequences":[],"endpoints":[],"type":"PROXY_SERVICE","subType":null,"name":"testProxy1","isFaulty":false}],"inboundEndpoints":[{"sequences":[],"endpoints":[],"type":"INBOUND_ENDPOINT","subType":"HTTPS","name":"HttpsInboundEndpoint","isFaulty":false}],"messageStores":[{"type":"MESSAGE_STORE","subType":"IN_MEMORY","name":"testMessageStore","isFaulty":false}],"messageProcessors":[{"type":"MESSAGE_PROCESSOR","subType":"MESSAGE_SAMPLING","name":"testMessageProcessor","isFaulty":false}],"tasks":[{"type":"TASK","subType":null,"name":"testTask","isFaulty":false}],"localEntries":[{"type":"LOCAL_ENTRY","subType":null,"name":"testLocalEntry","isFaulty":false}],"connections":[{"connectorName":"http","connectionType":"HTTPS","type":"localEntry","subType":null,"name":"HttpsCon","isFaulty":false}],"templates":[{"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate2","isFaulty":false},{"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate2","isFaulty":false}],"dataServices":[{"type":"DATA_SERVICE","subType":null,"name":"RDBMSDataservice","isFaulty":false}],"dataSources":[{"type":"DATA_SOURCE","subType":null,"name":"RDBMSDatasource","isFaulty":false}]},"resources":{"registry":{"conf":null,"gov":{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}},"connectors":[],"metadata":[],"newResources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}}},"java":null,"ballerina":null},"tests":{"wso2mi":null,"java":null}}} \ No newline at end of file +{"src":{"main":{"wso2mi":{"artifacts":{"apis":[{"context":"/test","resources":[{"methods":"GET","uriTemplate":"/","urlMapping":null}],"sequences":[],"endpoints":[],"type":"API","subType":null,"name":"testApi","isFaulty":false}],"endpoints":[{"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint3","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint4","isFaulty":false}],"sequences":[{"isMainSequence":true,"sequences":[],"endpoints":[],"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence2","isFaulty":false}],"proxyServices":[{"sequences":[],"endpoints":[],"type":"PROXY_SERVICE","subType":null,"name":"testProxy1","isFaulty":false}],"inboundEndpoints":[{"sequences":[],"endpoints":[],"type":"INBOUND_ENDPOINT","subType":"HTTPS","name":"HttpsInboundEndpoint","isFaulty":false}],"messageStores":[{"type":"MESSAGE_STORE","subType":"IN_MEMORY","name":"testMessageStore","isFaulty":false}],"messageProcessors":[{"type":"MESSAGE_PROCESSOR","subType":"MESSAGE_SAMPLING","name":"testMessageProcessor","isFaulty":false}],"tasks":[{"type":"TASK","subType":null,"name":"testTask","isFaulty":false}],"localEntries":[{"type":"LOCAL_ENTRY","subType":null,"name":"testLocalEntry","isFaulty":false}],"connections":[{"connectorName":"http","connectionType":"HTTPS","type":"localEntry","subType":null,"name":"HttpsCon","isFaulty":false}],"templates":[{"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate2","isFaulty":false},{"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate2","isFaulty":false}],"dataServices":[{"type":"DATA_SERVICE","subType":null,"name":"RDBMSDataservice","isFaulty":false}],"dataSources":[{"type":"DATA_SOURCE","subType":null,"name":"RDBMSDatasource","isFaulty":false}],"mcpServers":[]},"resources":{"registry":{"conf":null,"gov":{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}},"connectors":[],"metadata":[],"newResources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}}},"java":null,"ballerina":null},"tests":{"wso2mi":null,"java":null}}} \ No newline at end of file diff --git a/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-project-explorer.json b/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-project-explorer.json index 8e94951bb..2497ab589 100644 --- a/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-project-explorer.json +++ b/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-project-explorer.json @@ -1 +1 @@ -{"src":{"main":{"wso2mi":{"artifacts":{"APIs":[{"context":"/test","resources":[{"methods":"GET","uriTemplate":"/","urlMapping":null}],"sequences":[],"endpoints":[],"type":"API","subType":null,"name":"testApi","isFaulty":false}],"Event Integrations":[{"sequences":[],"endpoints":[],"type":"INBOUND_ENDPOINT","subType":"HTTPS","name":"HttpsInboundEndpoint","isFaulty":false}],"Automations":[{"type":"TASK","subType":null,"name":"testTask","isFaulty":false}],"Data Services":[{"type":"DATA_SERVICE","subType":null,"name":"RDBMSDataservice","isFaulty":false}],"Other Artifacts":{"Sequences":[{"isMainSequence":true,"sequences":[],"endpoints":[],"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence2","isFaulty":false}],"Connections":[{"connectorName":"http","connectionType":"HTTPS","type":"localEntry","subType":null,"name":"HttpsCon","isFaulty":false}],"Data Sources":[{"type":"DATA_SOURCE","subType":null,"name":"RDBMSDatasource","isFaulty":false}],"Class Mediators":[],"Ballerina Modules":[],"Endpoints":[{"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint3","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint4","isFaulty":false}],"Proxy Services":[{"sequences":[],"endpoints":[],"type":"PROXY_SERVICE","subType":null,"name":"testProxy1","isFaulty":false}],"Message Stores":[{"type":"MESSAGE_STORE","subType":"IN_MEMORY","name":"testMessageStore","isFaulty":false}],"Message Processors":[{"type":"MESSAGE_PROCESSOR","subType":"MESSAGE_SAMPLING","name":"testMessageProcessor","isFaulty":false}],"Local Entries":[{"type":"LOCAL_ENTRY","subType":null,"name":"testLocalEntry","isFaulty":false}],"Templates":[{"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate2","isFaulty":false},{"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate2","isFaulty":false}],"Data Mappers":[]},"Resources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}},"resources":{"registry":{"conf":null,"gov":{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}},"connectors":[],"metadata":[],"newResources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}}},"java":null,"ballerina":null},"tests":{"wso2mi":null,"java":null}}} \ No newline at end of file +{"src":{"main":{"wso2mi":{"artifacts":{"APIs":[{"context":"/test","resources":[{"methods":"GET","uriTemplate":"/","urlMapping":null}],"sequences":[],"endpoints":[],"type":"API","subType":null,"name":"testApi","isFaulty":false}],"Event Integrations":[{"sequences":[],"endpoints":[],"type":"INBOUND_ENDPOINT","subType":"HTTPS","name":"HttpsInboundEndpoint","isFaulty":false}],"Automations":[{"type":"TASK","subType":null,"name":"testTask","isFaulty":false}],"Data Services":[{"type":"DATA_SERVICE","subType":null,"name":"RDBMSDataservice","isFaulty":false}],"MCP Servers":[],"Other Artifacts":{"Sequences":[{"isMainSequence":true,"sequences":[],"endpoints":[],"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence2","isFaulty":false}],"Connections":[{"connectorName":"http","connectionType":"HTTPS","type":"localEntry","subType":null,"name":"HttpsCon","isFaulty":false}],"Data Sources":[{"type":"DATA_SOURCE","subType":null,"name":"RDBMSDatasource","isFaulty":false}],"Class Mediators":[],"Ballerina Modules":[],"Endpoints":[{"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint3","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint4","isFaulty":false}],"Proxy Services":[{"sequences":[],"endpoints":[],"type":"PROXY_SERVICE","subType":null,"name":"testProxy1","isFaulty":false}],"Message Stores":[{"type":"MESSAGE_STORE","subType":"IN_MEMORY","name":"testMessageStore","isFaulty":false}],"Message Processors":[{"type":"MESSAGE_PROCESSOR","subType":"MESSAGE_SAMPLING","name":"testMessageProcessor","isFaulty":false}],"Local Entries":[{"type":"LOCAL_ENTRY","subType":null,"name":"testLocalEntry","isFaulty":false}],"Templates":[{"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate2","isFaulty":false},{"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate2","isFaulty":false}],"Data Mappers":[]},"Resources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}},"resources":{"registry":{"conf":null,"gov":{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}},"connectors":[],"metadata":[],"newResources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}}},"java":null,"ballerina":null},"tests":{"wso2mi":null,"java":null}}} \ No newline at end of file From e4f83a2ca95950985332d16d0bd4a391a50c3d27 Mon Sep 17 00:00:00 2001 From: dulavinya Date: Sat, 9 May 2026 12:30:02 +0530 Subject: [PATCH 06/34] Refactor Mcp Classification --- .../directoryTree/DirectoryTreeBuilder.java | 85 +++++++++++-------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java index 2c2b20064..ccae97cae 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java @@ -19,6 +19,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.JsonParser; import org.eclipse.lemminx.customservice.synapse.directoryTree.node.APINode; import org.eclipse.lemminx.customservice.synapse.directoryTree.node.APIResource; @@ -122,23 +125,23 @@ public static DirectoryMapResponse buildDirectoryTree(WorkspaceFolder projectFol private static void applyMcpClassification(DirectoryMapResponse response) { if (response.getDirectoryMap() == null) return; - try { - ObjectMapper mapper = new ObjectMapper(); - JsonNode root = mapper.readTree(response.getDirectoryMap().getAsJsonObject().toString()); - JsonNode artifactsNode = root.path(Constant.SRC).path(MAIN).path(WSO2MI).path(Constant.ARTIFACTS); - if (artifactsNode.isMissingNode() || !artifactsNode.isObject()) return; - - ArrayNode[] result = extractMcpServers(mapper, artifactsNode); - ObjectNode artifacts = (ObjectNode) artifactsNode; - artifacts.set(Constant.INBOUNDENDPOINTS, result[1]); - artifacts.set(Constant.LOCALENTRIES, result[2]); - artifacts.set(MCP_SERVERS_KEY, result[0]); - - String updated = mapper.writeValueAsString(root); - response.setDirectoryMap(JsonParser.parseString(updated)); - } catch (JsonProcessingException e) { - LOGGER.log(Level.SEVERE, "Error occurred while applying MCP classification.", e); - } + JsonObject root = response.getDirectoryMap().getAsJsonObject(); + JsonObject src = root.getAsJsonObject(Constant.SRC); + if (src == null) return; + + JsonObject main = src.getAsJsonObject(MAIN); + if (main == null) return; + + JsonObject wso2mi = main.getAsJsonObject(WSO2MI); + if (wso2mi == null) return; + + JsonObject artifacts = wso2mi.getAsJsonObject(Constant.ARTIFACTS); + if (artifacts == null) return; + + JsonArray[] result = extractMcpServers(artifacts); + artifacts.add(Constant.INBOUNDENDPOINTS, result[1]); + artifacts.add(Constant.LOCALENTRIES, result[2]); + artifacts.add(MCP_SERVERS_KEY, result[0]); } /** @@ -867,20 +870,24 @@ private static void addResources(DOMElement rootElement, AdvancedNode advancedNo /** * Separates MCP server artifacts from the regular inbound endpoints and local entries. */ - private static ArrayNode[] extractMcpServers(ObjectMapper mapper, JsonNode artifacts) { + private static JsonArray[] extractMcpServers(JsonObject artifacts) { + + JsonElement inboundEndpointsElem = artifacts.get(Constant.INBOUNDENDPOINTS); + JsonElement localEntriesElem = artifacts.get(Constant.LOCALENTRIES); - JsonNode inboundEndpointsNode = artifacts.path(Constant.INBOUNDENDPOINTS); - JsonNode localEntriesNode = artifacts.path(Constant.LOCALENTRIES); + JsonArray inboundEndpointsNode = (inboundEndpointsElem != null && inboundEndpointsElem.isJsonArray()) + ? inboundEndpointsElem.getAsJsonArray() : new JsonArray(); + JsonArray localEntriesNode = (localEntriesElem != null && localEntriesElem.isJsonArray()) + ? localEntriesElem.getAsJsonArray() : new JsonArray(); - Map mcpLocalEntries = new LinkedHashMap<>(); - ArrayNode filteredLocalEntries = mapper.createArrayNode(); - for (JsonNode localEntry : localEntriesNode) { - if (!localEntry.has(Constant.NAME)) { - // Connector group object (e.g. {"HTTP": [...]}) — keep as-is + Map mcpLocalEntries = new LinkedHashMap<>(); + JsonArray filteredLocalEntries = new JsonArray(); + for (JsonElement localEntry : localEntriesNode) { + if (!localEntry.isJsonObject() || !localEntry.getAsJsonObject().has(Constant.NAME)) { filteredLocalEntries.add(localEntry); continue; } - String entryName = localEntry.path(Constant.NAME).asText(); + String entryName = localEntry.getAsJsonObject().get(Constant.NAME).getAsString(); if (entryName.endsWith(MCP_CONFIG_SUFFIX)) { String serverName = entryName.substring(0, entryName.length() - MCP_CONFIG_SUFFIX.length()); mcpLocalEntries.put(serverName, localEntry); @@ -889,10 +896,14 @@ private static ArrayNode[] extractMcpServers(ObjectMapper mapper, JsonNode artif } } - Map mcpInboundEndpoints = new LinkedHashMap<>(); - ArrayNode filteredInboundEndpoints = mapper.createArrayNode(); - for (JsonNode inboundEndpoint : inboundEndpointsNode) { - String endpointName = inboundEndpoint.path(Constant.NAME).asText(); + Map mcpInboundEndpoints = new LinkedHashMap<>(); + JsonArray filteredInboundEndpoints = new JsonArray(); + for (JsonElement inboundEndpoint : inboundEndpointsNode) { + if (!inboundEndpoint.isJsonObject()) { + filteredInboundEndpoints.add(inboundEndpoint); + continue; + } + String endpointName = inboundEndpoint.getAsJsonObject().get(Constant.NAME).getAsString(); if (endpointName.endsWith(MCP_ENDPOINT_SUFFIX)) { String serverName = endpointName.substring(0, endpointName.length() - MCP_ENDPOINT_SUFFIX.length()); if (mcpLocalEntries.containsKey(serverName)) { @@ -904,22 +915,22 @@ private static ArrayNode[] extractMcpServers(ObjectMapper mapper, JsonNode artif } // Restore any -mcp-config local entries that have no matching -endpoint - for (Map.Entry entry : mcpLocalEntries.entrySet()) { + for (Map.Entry entry : mcpLocalEntries.entrySet()) { if (!mcpInboundEndpoints.containsKey(entry.getKey())) { filteredLocalEntries.add(entry.getValue()); } } - ArrayNode mcpServersArray = mapper.createArrayNode(); + JsonArray mcpServersArray = new JsonArray(); for (String serverName : mcpInboundEndpoints.keySet()) { - ObjectNode mcpServer = mapper.createObjectNode(); - mcpServer.put(Constant.NAME, serverName); - mcpServer.set(Constant.LOCAL_ENTRY, mcpLocalEntries.get(serverName)); - mcpServer.set(Constant.INBOUND_ENDPOINT, mcpInboundEndpoints.get(serverName)); + JsonObject mcpServer = new JsonObject(); + mcpServer.addProperty(Constant.NAME, serverName); + mcpServer.add(Constant.LOCAL_ENTRY, mcpLocalEntries.get(serverName)); + mcpServer.add(Constant.INBOUND_ENDPOINT, mcpInboundEndpoints.get(serverName)); mcpServersArray.add(mcpServer); } - return new ArrayNode[]{mcpServersArray, filteredInboundEndpoints, filteredLocalEntries}; + return new JsonArray[]{mcpServersArray, filteredInboundEndpoints, filteredLocalEntries}; } private static void extractClassMediators(JsonNode mediatorFolders, ArrayNode classMediatorArray) { From 9597c4ec4f836ca48546ed34459a5bb4f3aceec7 Mon Sep 17 00:00:00 2001 From: dulavinya Date: Sat, 9 May 2026 15:36:06 +0530 Subject: [PATCH 07/34] Replace suffix-based MCP identification with content-based parsing --- .../directoryTree/DirectoryTreeBuilder.java | 91 ++++++++++++++----- .../synapse/directoryTree/node/Node.java | 22 +++++ .../builder/DirectoryTreeBuilderTest.java | 2 +- 3 files changed, 91 insertions(+), 24 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java index ccae97cae..9f99853a5 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java @@ -74,8 +74,6 @@ public class DirectoryTreeBuilder { private static final String WSO2MI = "wso2mi"; private static final String RESOURCES = "resources"; private static final String JAVA = "java"; - private static final String MCP_CONFIG_SUFFIX = "-mcp-config"; - private static final String MCP_ENDPOINT_SUFFIX = "-endpoint"; private static final String MCP_SERVERS_SECTION = "MCP Servers"; private static final String MCP_SERVERS_KEY = "mcpServers"; private static String projectPath; @@ -735,6 +733,12 @@ private static AdvancedNode createAdvancedEsbComponent(Node component, String ty if (Constant.API.equalsIgnoreCase(type)) { addResources(rootElement, advancedNode); } + if (Constant.INBOUND_ENDPOINT.equalsIgnoreCase(type)) { + String mcpConfigRef = getMcpConfigReference(rootElement); + if (mcpConfigRef != null) { + component.setMcpConfigReference(mcpConfigRef); + } + } } return advancedNode; } @@ -747,6 +751,12 @@ private static Node createLocalEntry(Node component, String path) { if (domDocument != null) { DOMElement rootElement = domDocument.getDocumentElement(); String key = rootElement.getAttribute(Constant.KEY); + + if (isMcpConfig(rootElement)) { + component.setIsMcpConfig(true); + return component; + } + DOMElement childElement = Utils.getFirstElement(rootElement); if (childElement != null) { String entryTag = childElement.getNodeName(); @@ -781,6 +791,39 @@ private static String getConnectionType(DOMElement element) { return null; } + private static boolean isMcpConfig(DOMElement rootElement) { + + List children = rootElement.getChildren(); + if (children != null) { + for (DOMNode child : children) { + if ("mcptools".equals(child.getNodeName())) { + return true; + } + } + } + return false; + } + + private static String getMcpConfigReference(DOMElement inboundElement) { + + DOMNode parametersNode = Utils.getChildNodeByName(inboundElement, "parameters"); + if (parametersNode != null) { + List children = parametersNode.getChildren(); + if (children != null) { + for (DOMNode child : children) { + if ("parameter".equals(child.getNodeName())) { + String paramName = ((DOMElement) child).getAttribute("name"); + if ("mcp.tools.localentry".equals(paramName)) { + String paramValue = Utils.getInlineString(child.getFirstChild()); + return paramValue; + } + } + } + } + } + return null; + } + private static String getApiContext(String path) { File file = new File(path); @@ -887,10 +930,11 @@ private static JsonArray[] extractMcpServers(JsonObject artifacts) { filteredLocalEntries.add(localEntry); continue; } - String entryName = localEntry.getAsJsonObject().get(Constant.NAME).getAsString(); - if (entryName.endsWith(MCP_CONFIG_SUFFIX)) { - String serverName = entryName.substring(0, entryName.length() - MCP_CONFIG_SUFFIX.length()); - mcpLocalEntries.put(serverName, localEntry); + JsonObject entryObj = localEntry.getAsJsonObject(); + String entryName = entryObj.get(Constant.NAME).getAsString(); + + if (entryObj.has("isMcpConfig") && entryObj.get("isMcpConfig").getAsBoolean()) { + mcpLocalEntries.put(entryName, localEntry); } else { filteredLocalEntries.add(localEntry); } @@ -903,30 +947,31 @@ private static JsonArray[] extractMcpServers(JsonObject artifacts) { filteredInboundEndpoints.add(inboundEndpoint); continue; } - String endpointName = inboundEndpoint.getAsJsonObject().get(Constant.NAME).getAsString(); - if (endpointName.endsWith(MCP_ENDPOINT_SUFFIX)) { - String serverName = endpointName.substring(0, endpointName.length() - MCP_ENDPOINT_SUFFIX.length()); - if (mcpLocalEntries.containsKey(serverName)) { - mcpInboundEndpoints.put(serverName, inboundEndpoint); - continue; - } + JsonObject endpointObj = inboundEndpoint.getAsJsonObject(); + String mcpConfigRef = null; + + if (endpointObj.has("mcpConfigReference") && !endpointObj.get("mcpConfigReference").isJsonNull()) { + mcpConfigRef = endpointObj.get("mcpConfigReference").getAsString(); } - filteredInboundEndpoints.add(inboundEndpoint); - } - // Restore any -mcp-config local entries that have no matching -endpoint - for (Map.Entry entry : mcpLocalEntries.entrySet()) { - if (!mcpInboundEndpoints.containsKey(entry.getKey())) { - filteredLocalEntries.add(entry.getValue()); + if (mcpConfigRef != null && mcpLocalEntries.containsKey(mcpConfigRef)) { + mcpInboundEndpoints.put(mcpConfigRef, inboundEndpoint); + } else { + filteredInboundEndpoints.add(inboundEndpoint); } } JsonArray mcpServersArray = new JsonArray(); - for (String serverName : mcpInboundEndpoints.keySet()) { + for (String mcpConfigKey : mcpInboundEndpoints.keySet()) { + JsonElement localEntry = mcpLocalEntries.get(mcpConfigKey); + if (localEntry == null) { + filteredInboundEndpoints.add(mcpInboundEndpoints.get(mcpConfigKey)); + continue; + } JsonObject mcpServer = new JsonObject(); - mcpServer.addProperty(Constant.NAME, serverName); - mcpServer.add(Constant.LOCAL_ENTRY, mcpLocalEntries.get(serverName)); - mcpServer.add(Constant.INBOUND_ENDPOINT, mcpInboundEndpoints.get(serverName)); + mcpServer.addProperty(Constant.NAME, mcpConfigKey); + mcpServer.add(Constant.LOCAL_ENTRY, localEntry); + mcpServer.add(Constant.INBOUND_ENDPOINT, mcpInboundEndpoints.get(mcpConfigKey)); mcpServersArray.add(mcpServer); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/node/Node.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/node/Node.java index 4947867b3..4ebe9aa39 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/node/Node.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/node/Node.java @@ -21,6 +21,8 @@ public class Node { String name; String path; Boolean isFaulty = false; + String mcpConfigReference; + Boolean isMcpConfig = false; public Node(String type, String name, String path) { @@ -87,6 +89,26 @@ public void setFaulty(Boolean faulty) { isFaulty = faulty; } + public String getMcpConfigReference() { + + return mcpConfigReference; + } + + public void setMcpConfigReference(String mcpConfigReference) { + + this.mcpConfigReference = mcpConfigReference; + } + + public Boolean getIsMcpConfig() { + + return isMcpConfig; + } + + public void setIsMcpConfig(Boolean isMcpConfig) { + + this.isMcpConfig = isMcpConfig; + } + protected Boolean equals(Node component) { return this.type.equals(component.type) && this.name.equals(component.name) && this.path.equals(component.path); diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/synapse/directorytree/builder/DirectoryTreeBuilderTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/synapse/directorytree/builder/DirectoryTreeBuilderTest.java index 7927b3cff..2babf94f4 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/synapse/directorytree/builder/DirectoryTreeBuilderTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/synapse/directorytree/builder/DirectoryTreeBuilderTest.java @@ -129,7 +129,7 @@ void getProjectIdentifiersWithEmptyArtifactList() { private static JsonObject sanitizeJson(JsonObject jsonObject) { JsonObject sanitizedJson = new JsonObject(); for (String key : jsonObject.keySet()) { - if (!(key.equals("path") || key.equals("registryPath"))) { + if (!(key.equals("path") || key.equals("registryPath") || key.equals("mcpConfigReference") || key.equals("isMcpConfig"))) { JsonElement value = jsonObject.get(key); if (value.isJsonObject()) { sanitizedJson.add(key, sanitizeJson(value.getAsJsonObject())); From b0e83893c83377a68bbb4e59ec5984e421c2a13e Mon Sep 17 00:00:00 2001 From: dulavinya Date: Sat, 9 May 2026 16:24:11 +0530 Subject: [PATCH 08/34] Restore MCP local entries without matching endpoints to local entries list. --- .../synapse/directoryTree/DirectoryTreeBuilder.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java index 9f99853a5..efbf3781f 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java @@ -975,6 +975,13 @@ private static JsonArray[] extractMcpServers(JsonObject artifacts) { mcpServersArray.add(mcpServer); } + // Restore MCP local entries (with ) that have no matching endpoint + for (Map.Entry entry : mcpLocalEntries.entrySet()) { + if (!mcpInboundEndpoints.containsKey(entry.getKey())) { + filteredLocalEntries.add(entry.getValue()); + } + } + return new JsonArray[]{mcpServersArray, filteredInboundEndpoints, filteredLocalEntries}; } From 47feedd3c3924f277af7a5586781c37aba5fdc49 Mon Sep 17 00:00:00 2001 From: Arunan Sugunakumar Date: Fri, 5 Jun 2026 11:50:01 +0530 Subject: [PATCH 09/34] Improve MCP server inbound implementation --- .../tree/OverviewModelGenerator.java | 25 ++++++++++++++++++- .../directoryTree/DirectoryTreeBuilder.java | 10 +++++--- .../customservice/synapse/utils/Constant.java | 1 + .../customservice/synapse/utils/Utils.java | 4 +++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/dependency/tree/OverviewModelGenerator.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/dependency/tree/OverviewModelGenerator.java index 406ab1eb1..fccd37d02 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/dependency/tree/OverviewModelGenerator.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/dependency/tree/OverviewModelGenerator.java @@ -25,8 +25,14 @@ import org.eclipse.lemminx.customservice.synapse.resourceFinder.pojo.Resource; import org.eclipse.lemminx.customservice.synapse.resourceFinder.pojo.ResourceResponse; import org.eclipse.lemminx.customservice.synapse.utils.Constant; +import org.eclipse.lemminx.customservice.synapse.utils.Utils; +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMElement; +import org.eclipse.lemminx.dom.DOMNode; import org.eclipse.lsp4j.jsonrpc.messages.Either; +import java.io.File; +import java.io.IOException; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; @@ -55,8 +61,12 @@ public static OverviewModel getOverviewModel(String projectPath) { NewProjectResourceFinder newProjectResourceFinder = new NewProjectResourceFinder(); ResourceResponse response = newProjectResourceFinder.getAvailableResources(projectPath, Either.forRight(requiredResources)); for (Resource resource : response.getResources()) { + String absolutePath = ((ArtifactResource) resource).getAbsolutePath(); + if (Constant.INBOUND_DASH_ENDPOINT.equals(resource.getType()) && isMcpInboundEndpoint(absolutePath)) { + continue; + } DependencyScanner dependencyScanner = new DependencyScanner(projectPath); - DependencyTree dependencyTree = dependencyScanner.analyzeArtifact(((ArtifactResource) resource).getAbsolutePath()); + DependencyTree dependencyTree = dependencyScanner.analyzeArtifact(absolutePath); dependencyTreeList.add(dependencyTree); } return convertDataToOverviewModel(Paths.get(projectPath).getFileName().toString(), dependencyTreeList); @@ -138,4 +148,17 @@ private static String getEntrypointType(String type) { return Constant.OTHER; } } + + private static boolean isMcpInboundEndpoint(String artifactPath) { + try { + DOMDocument document = Utils.getDOMDocument(new File(artifactPath)); + if (document == null) return false; + DOMNode inboundElement = Utils.getChildNodeByName(document, Constant.INBOUND_ENDPOINT); + if (inboundElement == null) return false; + return Utils.isMcpInboundEndpoint((DOMElement) inboundElement); + } catch (IOException e) { + // ignore unreadable files + } + return false; + } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java index efbf3781f..0235060d3 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java @@ -733,10 +733,10 @@ private static AdvancedNode createAdvancedEsbComponent(Node component, String ty if (Constant.API.equalsIgnoreCase(type)) { addResources(rootElement, advancedNode); } - if (Constant.INBOUND_ENDPOINT.equalsIgnoreCase(type)) { + if (Constant.INBOUND_ENDPOINT.equalsIgnoreCase(type) && Utils.isMcpInboundEndpoint(rootElement)) { String mcpConfigRef = getMcpConfigReference(rootElement); if (mcpConfigRef != null) { - component.setMcpConfigReference(mcpConfigRef); + advancedNode.setMcpConfigReference(mcpConfigRef); } } } @@ -969,7 +969,11 @@ private static JsonArray[] extractMcpServers(JsonObject artifacts) { continue; } JsonObject mcpServer = new JsonObject(); - mcpServer.addProperty(Constant.NAME, mcpConfigKey); + JsonObject inboundEndpointObj = mcpInboundEndpoints.get(mcpConfigKey).getAsJsonObject(); + String serverName = (inboundEndpointObj.has(Constant.NAME) && !inboundEndpointObj.get(Constant.NAME).isJsonNull()) + ? inboundEndpointObj.get(Constant.NAME).getAsString() + : mcpConfigKey; + mcpServer.addProperty(Constant.NAME, serverName); mcpServer.add(Constant.LOCAL_ENTRY, localEntry); mcpServer.add(Constant.INBOUND_ENDPOINT, mcpInboundEndpoints.get(mcpConfigKey)); mcpServersArray.add(mcpServer); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Constant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Constant.java index 020618ba9..7772cf0ba 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Constant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Constant.java @@ -496,6 +496,7 @@ public class Constant { public static final String TASK_UPPERCASE = "TASK"; public static final String INBOUND_UPPERCASE = "INBOUND_ENDPOINT"; public static final String INBOUND_DASH_ENDPOINT = "inbound-endpoint"; + public static final String MCP_INBOUND_LISTENER_CLASS = "org.wso2.carbon.inbound.sse.McpInboundListener"; public static final String CONNECTION_UPPERCASE = "CONNECTION"; public static final String OTHER = "other"; public static final String SCHEDULED_TASK = "schedule-task"; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Utils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Utils.java index ae34d043e..4a7dc4aa1 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Utils.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Utils.java @@ -575,6 +575,10 @@ public static DOMNode getChildNodeByName(DOMNode node, String name) { return foundNode; } + public static boolean isMcpInboundEndpoint(DOMElement element) { + return Constant.MCP_INBOUND_LISTENER_CLASS.equals(element.getAttribute("class")); + } + public static String addUnderscoreBetweenWords(String input) { StringBuilder result = new StringBuilder(); From 9094568a3c0983a84773d82ba86f2aa4cc553179 Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Fri, 5 Jun 2026 20:13:37 +0530 Subject: [PATCH 10/34] Fix issues in the MI Extension --- .../lemminx/SynapseLanguageService.java | 6 ++++++ .../ISynapseLanguageService.java | 3 +++ .../synapse/api/generator/RestApiAdmin.java | 8 +++++++- .../generator/pojo/GenerateAPIResponse.java | 8 ++++++++ .../directoryTree/DirectoryTreeBuilder.java | 20 +++++++++++++++++++ .../synapse/directoryTree/node/Resource.java | 7 +++++++ .../conector/InboundConnectorHolder.java | 5 ++++- .../generated-directory-tree.json | 2 +- .../generated-project-explorer.json | 2 +- 9 files changed, 57 insertions(+), 4 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/SynapseLanguageService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/SynapseLanguageService.java index 1da9fb4fd..4c50e8fde 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/SynapseLanguageService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/SynapseLanguageService.java @@ -1202,6 +1202,12 @@ public CompletableFuture> resolveConnector }); } + @Override + public CompletableFuture fetchInboundConnectors() { + + return CompletableFuture.supplyAsync(() -> inboundConnectorHolder.getCustomInboundConnectors()); + } + public String getProjectUri() { return projectUri; } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/ISynapseLanguageService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/ISynapseLanguageService.java index c8c8da214..72933ee1f 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/ISynapseLanguageService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/ISynapseLanguageService.java @@ -359,4 +359,7 @@ CompletableFuture getDriverMavenCoordinates( @JsonRequest CompletableFuture> getInboundInfo(InboundInfoRequest request); + + @JsonRequest + CompletableFuture fetchInboundConnectors(); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/RestApiAdmin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/RestApiAdmin.java index 805fd5980..24c096083 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/RestApiAdmin.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/RestApiAdmin.java @@ -125,17 +125,23 @@ public GenerateAPIResponse createAPI(String apiName, String sourcePath, String e return createAPIFromSwagger(apiName, sourcePath, publishSwaggerPath); } catch (JsonProcessingException e) { LOGGER.log(Level.SEVERE, "Exception occurred while creating API from Swagger", e); - return null; + return new GenerateAPIResponse(null, null, + "Exception occurred while creating API from Swagger: " + e.getMessage()); } } else if (CREATE_FROM_WSDL.equalsIgnoreCase(mode)) { try { return createAPIFromWSDL(apiName, endpoint, sourcePath); } catch (SOAPToRESTException e) { LOGGER.log(Level.SEVERE, "Exception occurred while converting SOAP to REST", e); + return new GenerateAPIResponse(null, null, + "Exception occurred while converting SOAP to REST: " + e.getMessage()); } catch (MalformedURLException e) { LOGGER.log(Level.SEVERE, "Invalid WSDL URL", e); + return new GenerateAPIResponse(null, null, "Invalid WSDL URL: " + e.getMessage()); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Exception occurred while creating API from WSDL", e); + return new GenerateAPIResponse(null, null, + "Exception occurred while creating API from WSDL: " + e.getMessage()); } } return null; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/pojo/GenerateAPIResponse.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/pojo/GenerateAPIResponse.java index 1e95d1590..721dccd18 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/pojo/GenerateAPIResponse.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/pojo/GenerateAPIResponse.java @@ -18,6 +18,7 @@ public class GenerateAPIResponse { public String apiXml; public String endpointXml; + public String error; private final String XML_TAG = "\n"; public GenerateAPIResponse(String apiXml) { @@ -30,4 +31,11 @@ public GenerateAPIResponse(String apiXml, String endpointXml) { this.apiXml = XML_TAG + apiXml; this.endpointXml = XML_TAG + endpointXml; } + + public GenerateAPIResponse(String apiXml, String endpointXml, String error) { + + this.apiXml = apiXml != null ? XML_TAG + apiXml : null; + this.endpointXml = endpointXml != null ? XML_TAG + endpointXml : null; + this.error = error; + } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java index ab3ea82c4..cb6fd4efb 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java @@ -387,6 +387,7 @@ private static void analyzeResources(IntegrationDirectoryTree directoryTree) { analyzeRegistryResources(directoryTree); analyzeConnectorResources(directoryTree); + analyzeInboundConnectorResources(directoryTree); analyzeMetadataResources(directoryTree); analyzeNewResources(directoryTree); } @@ -450,6 +451,25 @@ private static void analyzeConnectorResources(IntegrationDirectoryTree directory } } + private static void analyzeInboundConnectorResources(IntegrationDirectoryTree directoryTree) { + + String inboundConnectorPath = projectPath + File.separator + Constant.SRC + File.separator + MAIN + + File.separator + WSO2MI + File.separator + RESOURCES + File.separator + + Constant.INBOUND_CONNECTORS_DIR; + File folder = new File(inboundConnectorPath); + File[] listOfFiles = folder.listFiles(); + if (listOfFiles != null) { + for (File file : listOfFiles) { + if (Utils.isZipFile(file) && !file.isHidden()) { + String name = file.getName(); + String path = file.getAbsolutePath(); + Node resource = new Node("inboundConnector", name, path); + directoryTree.getResources().addInboundConnector(resource); + } + } + } + } + private static void analyzeMetadataResources(IntegrationDirectoryTree directoryTree) { String metadataPath = projectPath + File.separator + Constant.SRC + File.separator + MAIN + diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/node/Resource.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/node/Resource.java index f1c364379..edd682360 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/node/Resource.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/node/Resource.java @@ -21,6 +21,7 @@ public class Resource { private RegistryResource registry; private List connectors; + private List inboundConnectors; private List metadata; private FolderNode newResources; @@ -28,6 +29,7 @@ public Resource() { registry = new RegistryResource(); connectors = new ArrayList<>(); + inboundConnectors = new ArrayList<>(); metadata = new ArrayList<>(); } @@ -46,6 +48,11 @@ public void addConnector(Node connector) { connectors.add(connector); } + public void addInboundConnector(Node inboundConnector) { + + inboundConnectors.add(inboundConnector); + } + public void addMetadata(Node meta) { metadata.add(meta); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java index 38801b406..558b84367 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java @@ -123,8 +123,9 @@ private void loadInboundConnectors() { } } - public void getCustomInboundConnectors() { + public synchronized String getCustomInboundConnectors() { + boolean isInboundConnectorAdded = false; File extractFolder = new File(Path.of(this.projectPath, Constant.SRC, Constant.MAIN, Constant.WSO2MI, Constant.RESOURCES, Constant.INBOUND_CONNECTORS_DIR).toString()); InputStream inputStream = JsonLoader.class @@ -152,6 +153,7 @@ public void getCustomInboundConnectors() { newConnector.addProperty(Constant.TYPE, Constant.INBOUND_DASH_ENDPOINT); JsonArray connectorArray = this.inboundConnectorListJson.getAsJsonArray(Constant.INBOUND_CONNECTOR_DATA); connectorArray.add(newConnector); + isInboundConnectorAdded = true; } catch (Exception e) { LOGGER.log(Level.SEVERE, "Failed to import custom inbound-connector:" + zipName, e); } @@ -163,6 +165,7 @@ public void getCustomInboundConnectors() { } } } + return isInboundConnectorAdded ? "success" : "Failed to import the inbound-connector"; } private List getInboundConnectorZips(File extractFolder) { diff --git a/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-directory-tree.json b/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-directory-tree.json index a792654f1..d25bf8ddc 100644 --- a/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-directory-tree.json +++ b/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-directory-tree.json @@ -1 +1 @@ -{"src":{"main":{"wso2mi":{"artifacts":{"apis":[{"context":"/test","resources":[{"methods":"GET","uriTemplate":"/","urlMapping":null}],"sequences":[],"endpoints":[],"type":"API","subType":null,"name":"testApi","isFaulty":false}],"endpoints":[{"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint3","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint4","isFaulty":false}],"sequences":[{"isMainSequence":true,"sequences":[],"endpoints":[],"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence2","isFaulty":false}],"proxyServices":[{"sequences":[],"endpoints":[],"type":"PROXY_SERVICE","subType":null,"name":"testProxy1","isFaulty":false}],"inboundEndpoints":[{"sequences":[],"endpoints":[],"type":"INBOUND_ENDPOINT","subType":"HTTPS","name":"HttpsInboundEndpoint","isFaulty":false}],"messageStores":[{"type":"MESSAGE_STORE","subType":"IN_MEMORY","name":"testMessageStore","isFaulty":false}],"messageProcessors":[{"type":"MESSAGE_PROCESSOR","subType":"MESSAGE_SAMPLING","name":"testMessageProcessor","isFaulty":false}],"tasks":[{"type":"TASK","subType":null,"name":"testTask","isFaulty":false}],"localEntries":[{"type":"LOCAL_ENTRY","subType":null,"name":"testLocalEntry","isFaulty":false}],"connections":[{"connectorName":"http","connectionType":"HTTPS","type":"localEntry","subType":null,"name":"HttpsCon","isFaulty":false}],"templates":[{"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate2","isFaulty":false},{"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate2","isFaulty":false}],"dataServices":[{"type":"DATA_SERVICE","subType":null,"name":"RDBMSDataservice","isFaulty":false}],"dataSources":[{"type":"DATA_SOURCE","subType":null,"name":"RDBMSDatasource","isFaulty":false}]},"resources":{"registry":{"conf":null,"gov":{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}},"connectors":[],"metadata":[],"newResources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}}},"java":null,"ballerina":null},"tests":{"wso2mi":null,"java":null}}} \ No newline at end of file +{"src":{"main":{"wso2mi":{"artifacts":{"apis":[{"context":"/test","resources":[{"methods":"GET","uriTemplate":"/","urlMapping":null}],"sequences":[],"endpoints":[],"type":"API","subType":null,"name":"testApi","isFaulty":false}],"endpoints":[{"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint3","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint4","isFaulty":false}],"sequences":[{"isMainSequence":true,"sequences":[],"endpoints":[],"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence2","isFaulty":false}],"proxyServices":[{"sequences":[],"endpoints":[],"type":"PROXY_SERVICE","subType":null,"name":"testProxy1","isFaulty":false}],"inboundEndpoints":[{"sequences":[],"endpoints":[],"type":"INBOUND_ENDPOINT","subType":"HTTPS","name":"HttpsInboundEndpoint","isFaulty":false}],"messageStores":[{"type":"MESSAGE_STORE","subType":"IN_MEMORY","name":"testMessageStore","isFaulty":false}],"messageProcessors":[{"type":"MESSAGE_PROCESSOR","subType":"MESSAGE_SAMPLING","name":"testMessageProcessor","isFaulty":false}],"tasks":[{"type":"TASK","subType":null,"name":"testTask","isFaulty":false}],"localEntries":[{"type":"LOCAL_ENTRY","subType":null,"name":"testLocalEntry","isFaulty":false}],"connections":[{"connectorName":"http","connectionType":"HTTPS","type":"localEntry","subType":null,"name":"HttpsCon","isFaulty":false}],"templates":[{"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate2","isFaulty":false},{"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate2","isFaulty":false}],"dataServices":[{"type":"DATA_SERVICE","subType":null,"name":"RDBMSDataservice","isFaulty":false}],"dataSources":[{"type":"DATA_SOURCE","subType":null,"name":"RDBMSDatasource","isFaulty":false}]},"resources":{"registry":{"conf":null,"gov":{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}},"connectors":[],"inboundConnectors":[],"metadata":[],"newResources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}}},"java":null,"ballerina":null},"tests":{"wso2mi":null,"java":null}}} \ No newline at end of file diff --git a/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-project-explorer.json b/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-project-explorer.json index 8e94951bb..0a584b28a 100644 --- a/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-project-explorer.json +++ b/org.eclipse.lemminx/src/test/resources/synapse/directorytree.builder/generated-project-explorer.json @@ -1 +1 @@ -{"src":{"main":{"wso2mi":{"artifacts":{"APIs":[{"context":"/test","resources":[{"methods":"GET","uriTemplate":"/","urlMapping":null}],"sequences":[],"endpoints":[],"type":"API","subType":null,"name":"testApi","isFaulty":false}],"Event Integrations":[{"sequences":[],"endpoints":[],"type":"INBOUND_ENDPOINT","subType":"HTTPS","name":"HttpsInboundEndpoint","isFaulty":false}],"Automations":[{"type":"TASK","subType":null,"name":"testTask","isFaulty":false}],"Data Services":[{"type":"DATA_SERVICE","subType":null,"name":"RDBMSDataservice","isFaulty":false}],"Other Artifacts":{"Sequences":[{"isMainSequence":true,"sequences":[],"endpoints":[],"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence2","isFaulty":false}],"Connections":[{"connectorName":"http","connectionType":"HTTPS","type":"localEntry","subType":null,"name":"HttpsCon","isFaulty":false}],"Data Sources":[{"type":"DATA_SOURCE","subType":null,"name":"RDBMSDatasource","isFaulty":false}],"Class Mediators":[],"Ballerina Modules":[],"Endpoints":[{"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint3","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint4","isFaulty":false}],"Proxy Services":[{"sequences":[],"endpoints":[],"type":"PROXY_SERVICE","subType":null,"name":"testProxy1","isFaulty":false}],"Message Stores":[{"type":"MESSAGE_STORE","subType":"IN_MEMORY","name":"testMessageStore","isFaulty":false}],"Message Processors":[{"type":"MESSAGE_PROCESSOR","subType":"MESSAGE_SAMPLING","name":"testMessageProcessor","isFaulty":false}],"Local Entries":[{"type":"LOCAL_ENTRY","subType":null,"name":"testLocalEntry","isFaulty":false}],"Templates":[{"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate2","isFaulty":false},{"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate2","isFaulty":false}],"Data Mappers":[]},"Resources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}},"resources":{"registry":{"conf":null,"gov":{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}},"connectors":[],"metadata":[],"newResources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}}},"java":null,"ballerina":null},"tests":{"wso2mi":null,"java":null}}} \ No newline at end of file +{"src":{"main":{"wso2mi":{"artifacts":{"APIs":[{"context":"/test","resources":[{"methods":"GET","uriTemplate":"/","urlMapping":null}],"sequences":[],"endpoints":[],"type":"API","subType":null,"name":"testApi","isFaulty":false}],"Event Integrations":[{"sequences":[],"endpoints":[],"type":"INBOUND_ENDPOINT","subType":"HTTPS","name":"HttpsInboundEndpoint","isFaulty":false}],"Automations":[{"type":"TASK","subType":null,"name":"testTask","isFaulty":false}],"Data Services":[{"type":"DATA_SERVICE","subType":null,"name":"RDBMSDataservice","isFaulty":false}],"Other Artifacts":{"Sequences":[{"isMainSequence":true,"sequences":[],"endpoints":[],"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence1","isFaulty":false},{"isRegistryResource":true,"type":"SEQUENCE","subType":null,"name":"testSequence2","isFaulty":false}],"Connections":[{"connectorName":"http","connectionType":"HTTPS","type":"localEntry","subType":null,"name":"HttpsCon","isFaulty":false}],"Data Sources":[{"type":"DATA_SOURCE","subType":null,"name":"RDBMSDatasource","isFaulty":false}],"Class Mediators":[],"Ballerina Modules":[],"Endpoints":[{"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint1","isFaulty":false},{"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint2","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"DEFAULT_ENDPOINT","name":"testEndpoint3","isFaulty":false},{"isRegistryResource":true,"type":"ENDPOINT","subType":"HTTP_ENDPOINT","name":"testEndpoint4","isFaulty":false}],"Proxy Services":[{"sequences":[],"endpoints":[],"type":"PROXY_SERVICE","subType":null,"name":"testProxy1","isFaulty":false}],"Message Stores":[{"type":"MESSAGE_STORE","subType":"IN_MEMORY","name":"testMessageStore","isFaulty":false}],"Message Processors":[{"type":"MESSAGE_PROCESSOR","subType":"MESSAGE_SAMPLING","name":"testMessageProcessor","isFaulty":false}],"Local Entries":[{"type":"LOCAL_ENTRY","subType":null,"name":"testLocalEntry","isFaulty":false}],"Templates":[{"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"DEFAULT_ENDPOINT","name":"testEndpointTemplate2","isFaulty":false},{"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate1","isFaulty":false},{"isRegistryResource":true,"type":"TEMPLATE","subType":"SEQUENCE","name":"testSequenceTemplate2","isFaulty":false}],"Data Mappers":[]},"Resources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}},"resources":{"registry":{"conf":null,"gov":{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}},"connectors":[],"inboundConnectors":[],"metadata":[],"newResources":{"name":"resources","files":[],"folders":[{"name":"conf","files":[{"name":"config.properties"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint3.xml"},{"name":"testEndpoint4.xml"}],"folders":[]},{"name":"js","files":[{"name":"test1.js"}],"folders":[]},{"name":"json","files":[{"name":"test1.json"}],"folders":[]},{"name":"registry","files":[],"folders":[{"name":"gov","files":[],"folders":[{"name":"datamapper","files":[{"name":"sample.dmc"}],"folders":[]},{"name":"endpoints","files":[{"name":"testEndpoint1.xml"},{"name":"testEndpoint2.xml"}],"folders":[]},{"name":"js","files":[{"name":"test.js"}],"folders":[]},{"name":"json","files":[{"name":"test.json"}],"folders":[]},{"name":"sequences","files":[{"name":"testSequence1.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger.json"},{"name":"swagger.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate1.xml"},{"name":"testSequenceTemplate1.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample.yaml"}],"folders":[]}]}]},{"name":"sequences","files":[{"name":"testSequence2.xml"}],"folders":[]},{"name":"smooks","files":[{"name":"test_smooks_config1.xml"}],"folders":[]},{"name":"swagger","files":[{"name":"swagger1.json"},{"name":"swagger1.yaml"}],"folders":[]},{"name":"templates","files":[{"name":"testEndpointTemplate2.xml"},{"name":"testSequenceTemplate2.xml"}],"folders":[]},{"name":"ws_policy","files":[{"name":"ws_policy1.xml"}],"folders":[]},{"name":"wsdl","files":[{"name":"wsdlfile1.wsdl"}],"folders":[]},{"name":"xsd","files":[{"name":"sample1.xsd"}],"folders":[]},{"name":"xsl","files":[{"name":"sample1.xsl"}],"folders":[]},{"name":"xslt","files":[{"name":"sample1.xslt"}],"folders":[]},{"name":"yaml","files":[{"name":"sample1.yaml"}],"folders":[]}]}}},"java":null,"ballerina":null},"tests":{"wso2mi":null,"java":null}}} \ No newline at end of file From a0d15b22c04d4620a9647cd7b723dff6b163627d Mon Sep 17 00:00:00 2001 From: Arunan Sugunakumar Date: Tue, 9 Jun 2026 14:47:11 +0530 Subject: [PATCH 11/34] Refactor code to avoid duplicate dom parsing for mcp inbound --- .../tree/OverviewModelGenerator.java | 23 ++----------------- .../AbstractResourceFinder.java | 1 + .../resourceFinder/pojo/ArtifactResource.java | 11 +++++++++ 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/dependency/tree/OverviewModelGenerator.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/dependency/tree/OverviewModelGenerator.java index fccd37d02..19f575c37 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/dependency/tree/OverviewModelGenerator.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/dependency/tree/OverviewModelGenerator.java @@ -25,14 +25,8 @@ import org.eclipse.lemminx.customservice.synapse.resourceFinder.pojo.Resource; import org.eclipse.lemminx.customservice.synapse.resourceFinder.pojo.ResourceResponse; import org.eclipse.lemminx.customservice.synapse.utils.Constant; -import org.eclipse.lemminx.customservice.synapse.utils.Utils; -import org.eclipse.lemminx.dom.DOMDocument; -import org.eclipse.lemminx.dom.DOMElement; -import org.eclipse.lemminx.dom.DOMNode; import org.eclipse.lsp4j.jsonrpc.messages.Either; -import java.io.File; -import java.io.IOException; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; @@ -61,10 +55,10 @@ public static OverviewModel getOverviewModel(String projectPath) { NewProjectResourceFinder newProjectResourceFinder = new NewProjectResourceFinder(); ResourceResponse response = newProjectResourceFinder.getAvailableResources(projectPath, Either.forRight(requiredResources)); for (Resource resource : response.getResources()) { - String absolutePath = ((ArtifactResource) resource).getAbsolutePath(); - if (Constant.INBOUND_DASH_ENDPOINT.equals(resource.getType()) && isMcpInboundEndpoint(absolutePath)) { + if (((ArtifactResource) resource).isMcpInbound()) { continue; } + String absolutePath = ((ArtifactResource) resource).getAbsolutePath(); DependencyScanner dependencyScanner = new DependencyScanner(projectPath); DependencyTree dependencyTree = dependencyScanner.analyzeArtifact(absolutePath); dependencyTreeList.add(dependencyTree); @@ -148,17 +142,4 @@ private static String getEntrypointType(String type) { return Constant.OTHER; } } - - private static boolean isMcpInboundEndpoint(String artifactPath) { - try { - DOMDocument document = Utils.getDOMDocument(new File(artifactPath)); - if (document == null) return false; - DOMNode inboundElement = Utils.getChildNodeByName(document, Constant.INBOUND_ENDPOINT); - if (inboundElement == null) return false; - return Utils.isMcpInboundEndpoint((DOMElement) inboundElement); - } catch (IOException e) { - // ignore unreadable files - } - return false; - } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/resourceFinder/AbstractResourceFinder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/resourceFinder/AbstractResourceFinder.java index 3696540f2..0a81af63e 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/resourceFinder/AbstractResourceFinder.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/resourceFinder/AbstractResourceFinder.java @@ -1024,6 +1024,7 @@ private Resource createArtifactResource(File file, DOMElement rootElement, Strin artifact.setType(type); artifact.setFrom(ARTIFACTS); ((ArtifactResource) artifact).setLocalEntry(isLocalEntry); + ((ArtifactResource) artifact).setMcpInbound(Utils.isMcpInboundEndpoint(rootElement)); ((ArtifactResource) artifact).setArtifactPath(file.getName()); ((ArtifactResource) artifact).setAbsolutePath(file.getAbsolutePath()); return artifact; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/resourceFinder/pojo/ArtifactResource.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/resourceFinder/pojo/ArtifactResource.java index 72a2df3c3..d5c571cd0 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/resourceFinder/pojo/ArtifactResource.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/resourceFinder/pojo/ArtifactResource.java @@ -19,6 +19,7 @@ public class ArtifactResource extends Resource { private String artifactPath; private String absolutePath; private boolean isLocalEntry; + private boolean isMcpInbound; public String getArtifactPath() { @@ -49,4 +50,14 @@ public void setLocalEntry(boolean localEntry) { isLocalEntry = localEntry; } + + public boolean isMcpInbound() { + + return isMcpInbound; + } + + public void setMcpInbound(boolean mcpInbound) { + + isMcpInbound = mcpInbound; + } } From efbec66048a8735f06f3dd4b190649dc823b0f6a Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Wed, 10 Jun 2026 18:12:46 +0530 Subject: [PATCH 12/34] Fix mcp tool response parsing issue --- .../mediatorService/AIConnectorHandler.java | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/AIConnectorHandler.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/AIConnectorHandler.java index 46aece6d1..d895dc264 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/AIConnectorHandler.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/AIConnectorHandler.java @@ -1408,19 +1408,37 @@ public MCPToolResponse fetchMcpTools(String documentUri, Range range, List 0) { + break; + } + continue; + } + if (line.startsWith("data:")) { + dataBuffer.append(line.substring(5).trim()); + } } + responseJson = dataBuffer.toString(); + } else { + responseJson = responseBody; + } + + if (StringUtils.isBlank(responseJson)) { + response.error = "Empty MCP response"; + return response; } - String responseJson = dataBuffer.toString(); ObjectMapper mapper = new ObjectMapper(); JsonNode dataJson = mapper.readTree(responseJson); From 78b3ea5198be73f50bf24e7bd62afb47d1434533 Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Wed, 10 Jun 2026 20:45:04 +0530 Subject: [PATCH 13/34] Update LS version --- org.eclipse.lemminx/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org.eclipse.lemminx/pom.xml b/org.eclipse.lemminx/pom.xml index 4c0922b97..5d5b176c0 100644 --- a/org.eclipse.lemminx/pom.xml +++ b/org.eclipse.lemminx/pom.xml @@ -3,7 +3,7 @@ org.wso2.language.server mi-language-server-parent - 0.24.0-wso2v90 + 0.24.0-wso2v91 ../pom.xml MI Language Server diff --git a/pom.xml b/pom.xml index 44c33afda..35087195d 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.wso2.language.server mi-language-server-parent - 0.24.0-wso2v90 + 0.24.0-wso2v91 pom MI Language Server - Parent LemMinX is a XML Language Server Protocol (LSP), and can be used with any editor that supports LSP, to offer an outstanding XML editing experience From 699d638aea38c05e37b528df65e2511261c29a54 Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Mon, 22 Jun 2026 11:39:09 +0530 Subject: [PATCH 14/34] Fix issues in the MI Extension --- .../lemminx/SynapseLanguageService.java | 8 +++-- .../synapse/connectors/SchemaGenerate.java | 1 + .../generate/ConnectorGeneratorResponse.java | 8 +++++ .../mediatorService/AIConnectorHandler.java | 10 ++++++- .../mediatorService/MediatorHandler.java | 6 ++++ .../factory/mediators/ConnectorFactory.java | 4 +++ .../syntaxTree/pojo/connector/Connector.java | 11 +++++++ .../customservice/synapse/utils/Constant.java | 2 ++ .../customservice/synapse/utils/Utils.java | 30 +++++++++++++++++++ .../430/templates/connector.mustache | 4 +-- .../440/templates/connector.mustache | 4 +-- 11 files changed, 81 insertions(+), 7 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/SynapseLanguageService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/SynapseLanguageService.java index 4c50e8fde..dae35912d 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/SynapseLanguageService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/SynapseLanguageService.java @@ -1044,7 +1044,10 @@ public CompletableFuture generateConnector(Connector connectorGenReq.connectorProjectPath, projectServerVersion, projectUri); } } catch (Exception e) { - log.log(Level.SEVERE, "Error occurred while generating the connector", e); + String errorMsg = "Error occurred while generating the connector: " + e.getMessage(); + log.log(Level.SEVERE, errorMsg, e); + ConnectorGeneratorResponse errorResponse = new ConnectorGeneratorResponse(false, null, errorMsg); + return CompletableFuture.supplyAsync(() -> errorResponse); } ConnectorGeneratorResponse response = new ConnectorGeneratorResponse(filePath != null, filePath); return CompletableFuture.supplyAsync(() -> response); @@ -1239,7 +1242,8 @@ public void dispose() { private void packHttpConnector() { - if (Utils.compareVersions(projectServerVersion, Constant.MI_440_VERSION) >= 0) { + if (Utils.compareVersions(projectServerVersion, Constant.MI_440_VERSION) >= 0 + && Utils.hasDependency(projectUri, Constant.HTTP_CONNECTOR_ARTIFACT_ID)) { String projectId = new File(projectUri).getName() + "_" + Utils.getHash(projectUri); String connectorDownloadPath = Path.of(System.getProperty(Constant.USER_HOME), Constant.WSO2_MI, Constant.CONNECTORS, projectId, Constant.DOWNLOADED).toString(); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/SchemaGenerate.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/SchemaGenerate.java index 06d32ca88..4cf2b5ed4 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/SchemaGenerate.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/SchemaGenerate.java @@ -108,6 +108,7 @@ private static String getConnectorSchema(ConnectorHolder holder) { } sb.append(" \n"); sb.append(" \n"); + sb.append(" \n"); sb.append(" \n" + " \n"); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/generate/ConnectorGeneratorResponse.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/generate/ConnectorGeneratorResponse.java index ac8e63754..67af55bd3 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/generate/ConnectorGeneratorResponse.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/generate/ConnectorGeneratorResponse.java @@ -18,10 +18,18 @@ public class ConnectorGeneratorResponse { public boolean buildStatus; public String connectorPath; + public String errorMessage; public ConnectorGeneratorResponse(boolean buildStatus, String connectorPath) { this.buildStatus = buildStatus; this.connectorPath = connectorPath; } + + public ConnectorGeneratorResponse(boolean buildStatus, String connectorPath, String errorMessage) { + + this.buildStatus = buildStatus; + this.connectorPath = connectorPath; + this.errorMessage = errorMessage; + } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/AIConnectorHandler.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/AIConnectorHandler.java index d895dc264..ec30f49fe 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/AIConnectorHandler.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/AIConnectorHandler.java @@ -555,6 +555,14 @@ private Map processToolData(Map data, String seq return toolData; } + private static String unwrapExpression(String value) { + + if (StringUtils.isNotBlank(value) && value.startsWith("${") && value.endsWith("}")) { + return value.substring(2, value.length() - 1); + } + return value; + } + /** * Generates a unique sequence template name for the newly added tool. */ @@ -931,7 +939,7 @@ private void addToolConfigurations(JsonObject schema, DOMNode node, Mediator med JsonObject expression = new JsonObject(); expression.addProperty(Constant.IS_EXPRESSION, true); - expression.addProperty(Constant.VALUE, node.getAttribute(RESULT_EXPRESSION)); + expression.addProperty(Constant.VALUE, unwrapExpression(node.getAttribute(RESULT_EXPRESSION))); toolData.put(TOOL_RESULT_EXPRESSION, expression); toolData.put(TOOL_DESCRIPTION, node.getAttribute(Constant.DESCRIPTION)); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/MediatorHandler.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/MediatorHandler.java index 6094bfdd9..9a94631c1 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/MediatorHandler.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediatorService/MediatorHandler.java @@ -176,6 +176,11 @@ private SynapseConfigResponse generateConnectorSynapseConfig(STNode node, String Map connectorData = new HashMap<>(); connectorData.put(Constant.TAG, operation.getTag()); connectorData.put(Constant.CONFIG_KEY, data.get(Constant.CONFIG_KEY)); + Object description = data.get(Constant.DESCRIPTION); + if (description == null && node instanceof Connector) { + description = ((Connector) node).getDescription(); + } + connectorData.put(Constant.DESCRIPTION, description); List parameterData = new ArrayList<>(); for (OperationParameter parameter : parameters) { if (data.containsKey(parameter.getName())) { @@ -197,6 +202,7 @@ private SynapseConfigResponse generateConnectorSynapseConfig(STNode node, String } connectorData.put(Constant.PARAMETERS, parameterData); + connectorData.put(Constant.HAS_PARAMETERS, !parameterData.isEmpty()); StringWriter writer = new StringWriter(); String edit = templateMap.get(Constant.CONNECTOR).execute(writer, connectorData).toString(); TextEdit textEdit = new TextEdit(range, edit); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/factory/mediators/ConnectorFactory.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/factory/mediators/ConnectorFactory.java index a1d9849d5..73ff0606b 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/factory/mediators/ConnectorFactory.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/factory/mediators/ConnectorFactory.java @@ -38,6 +38,10 @@ public void populateAttributes(STNode node, DOMElement element) { if (configKey != null) { ((Connector) node).setConfigKey(configKey); } + String description = element.getAttribute(Constant.DESCRIPTION); + if (description != null) { + ((Connector) node).setDescription(description); + } addConnectorParameters((Connector) node, element); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/pojo/connector/Connector.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/pojo/connector/Connector.java index 1de889081..9565749eb 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/pojo/connector/Connector.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/pojo/connector/Connector.java @@ -24,6 +24,7 @@ public class Connector extends Mediator { String connectorName; String method; String configKey; + String description; List parameters; public Connector() { @@ -86,6 +87,16 @@ public void setConfigKey(String configKey) { this.configKey = configKey; } + public String getDescription() { + + return description; + } + + public void setDescription(String description) { + + this.description = description; + } + public void removeParameter(String name) { for (ConnectorParameter parameter : parameters) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Constant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Constant.java index 7772cf0ba..72ed7bb87 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Constant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Constant.java @@ -173,6 +173,7 @@ public class Constant { public static final String STATISTICS = "statistics"; public static final String TRACE = "trace"; public static final String PARAMETERS = "parameters"; + public static final String HAS_PARAMETERS = "hasParameters"; public static final String PARAMETER = "parameter"; public static final String LOCKED = "locked"; public static final String SRC = "src"; @@ -550,6 +551,7 @@ public class Constant { public static final String WSO2_MI = ".wso2-mi"; public static final String M2 = ".m2"; public static final String DOWNLOADED = "Downloaded"; + public static final String HTTP_CONNECTOR_ARTIFACT_ID = "mi-connector-http"; public static final String EXTRACTED = "Extracted"; public static final String PARALLEL_EXECUTION = "parallelExecution"; public static final String CONTINUE_WITHOUT_AGGREGATION = "continueWithoutAggregation"; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Utils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Utils.java index 4a7dc4aa1..98f2d8049 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Utils.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Utils.java @@ -1066,6 +1066,36 @@ public static String getServerVersion(String projectPath, String defaultVersion) return mapped != null ? mapped : Constant.DEFAULT_MI_VERSION; } + public static boolean hasDependency(String projectPath, String artifactId) { + try { + Path pomPath = Path.of(projectPath, "pom.xml"); + File pomFile = pomPath.toFile(); + if (!pomFile.exists()) { + return false; + } + DOMDocument document = getDOMDocument(pomFile); + DOMNode dependencies = getChildNodeByName(document.getDocumentElement(), Constant.DEPENDENCIES); + if (dependencies == null) { + return false; + } + for (DOMNode dependency : dependencies.getChildren()) { + if (!Constant.DEPENDENCY.equalsIgnoreCase(dependency.getNodeName())) { + continue; + } + DOMNode artifactNode = getChildNodeByName(dependency, Constant.ARTIFACT_ID); + if (artifactNode != null) { + String pomArtifactId = getInlineString(artifactNode.getFirstChild()); + if (pomArtifactId != null && artifactId.equals(pomArtifactId.trim())) { + return true; + } + } + } + } catch (Exception e) { + logger.log(Level.SEVERE, "Error occurred while checking pom dependency: " + artifactId, e); + } + return false; + } + public static Map getTemplateMap(String resourceFolderName) { Map templateMap = new HashMap<>(); try { diff --git a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/mediators/430/templates/connector.mustache b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/mediators/430/templates/connector.mustache index 5afa03a56..fbb9f685e 100644 --- a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/mediators/430/templates/connector.mustache +++ b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/mediators/430/templates/connector.mustache @@ -1,5 +1,5 @@ -<{{tag}} {{#configKey}}configKey="{{{configKey}}}"{{/configKey}}> +<{{tag}}{{#configKey}} configKey="{{{configKey}}}"{{/configKey}}{{#description}} description="{{description}}"{{/description}}{{^hasParameters}}/>{{/hasParameters}}{{#hasParameters}}> {{#parameters}} <{{{name}}} {{#value}}{{#namespaces}} xmlns:{{{prefix}}}="{{{uri}}}"{{/namespaces}}>{{#isCDATA}}{{/isCDATA}}{{^isCDATA}}{{#isExpression}}{{value}}{{/isExpression}}{{^isExpression}}{{{value}}}{{/isExpression}}{{/isCDATA}}{{/value}} {{/parameters}} - +{{/hasParameters}} diff --git a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/mediators/440/templates/connector.mustache b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/mediators/440/templates/connector.mustache index 5afa03a56..fbb9f685e 100644 --- a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/mediators/440/templates/connector.mustache +++ b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/mediators/440/templates/connector.mustache @@ -1,5 +1,5 @@ -<{{tag}} {{#configKey}}configKey="{{{configKey}}}"{{/configKey}}> +<{{tag}}{{#configKey}} configKey="{{{configKey}}}"{{/configKey}}{{#description}} description="{{description}}"{{/description}}{{^hasParameters}}/>{{/hasParameters}}{{#hasParameters}}> {{#parameters}} <{{{name}}} {{#value}}{{#namespaces}} xmlns:{{{prefix}}}="{{{uri}}}"{{/namespaces}}>{{#isCDATA}}{{/isCDATA}}{{^isCDATA}}{{#isExpression}}{{value}}{{/isExpression}}{{^isExpression}}{{{value}}}{{/isExpression}}{{/isCDATA}}{{/value}} {{/parameters}} - +{{/hasParameters}} From afc745e27167ab26489d55873fb18a52bc458aaa Mon Sep 17 00:00:00 2001 From: Isuru Wijesiri Date: Fri, 19 Jun 2026 16:26:42 +0530 Subject: [PATCH 15/34] Flag operator-precedence pitfalls and undefined vars in element text Two validation gaps that let semantically-wrong but syntactically-valid Synapse configs reach runtime unflagged: - SemanticExpressionValidator: warn when a logical operator (and/or) is an unparenthesized operand of a comparison. In the Synapse expression grammar and/or bind TIGHTER than the comparison operators, so `a <= 0 or b > 10` parses as `a <= (0 or b) > 10`. The warning points at the and/or token and recommends parentheses; the corrected `(a <= 0) or (b > 10)` form is a single top-level logical expression and is never flagged (no false positives). - SynapseDiagnosticsParticipant: the UndefinedVariable check only scanned attribute values, so a reference in element text (e.g. {${vars.soqlQuery1}}) was never flagged. Extend it to scan leaf element text content, sharing the same detection logic; skip raw-code bodies (script) and containers to avoid false positives. Adds tests for both in ExpressionValidatorTest and SynapseDiagnosticsParticipantTest. --- .../SemanticExpressionValidator.java | 58 ++++++++ .../SynapseDiagnosticsParticipant.java | 132 ++++++++++++------ .../SynapseDiagnosticsParticipantTest.java | 70 ++++++++++ .../expression/ExpressionValidatorTest.java | 64 +++++++++ 4 files changed, 284 insertions(+), 40 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/expression/SemanticExpressionValidator.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/expression/SemanticExpressionValidator.java index fc8a729d4..11f46a5dc 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/expression/SemanticExpressionValidator.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/expression/SemanticExpressionValidator.java @@ -198,6 +198,64 @@ private boolean isOverloadCompatible(FunctionSignature sig, String[] literalType return true; } + /** + * Detects the operator-precedence pitfall where a logical operator ('and'/'or') is used as a + * direct, unparenthesized operand of a comparison operator. In the Synapse expression grammar, + * 'and'/'or' bind TIGHTER than the comparison operators (<, >, <=, >=, ==, !=), so an + * expression such as {@code a <= 0 or b > 10} parses as {@code a <= (0 or b) > 10} instead of + * the usually-intended {@code (a <= 0) or (b > 10)}. A warning is emitted recommending explicit + * parentheses. The correctly-parenthesized form parses as a single top-level logical expression + * with no comparison operator at this node, so it is never flagged (no false positives). + */ + @Override + public Void visitComparisonExpression(ExpressionParser.ComparisonExpressionContext ctx) { + if (hasComparisonOperator(ctx)) { + for (ExpressionParser.LogicalExpressionContext operand : ctx.logicalExpression()) { + TerminalNode logicalOp = operand.AND() != null ? operand.AND() : operand.OR(); + if (logicalOp != null) { + Token opToken = logicalOp.getSymbol(); + String op = opToken.getText(); + String message = "Operator precedence: '" + op + "' binds tighter than the comparison " + + "operators (<, >, <=, >=, ==, !=) in Synapse expressions, so '" + op + + "' is evaluated before the comparison. This is likely not the intended " + + "behavior. Add parentheses around each comparison to make the intent " + + "explicit, e.g. (a <= 0) " + op + " (b > 10)."; + ExpressionError error = new ExpressionError(opToken.getLine(), + opToken.getCharPositionInLine(), message, opToken, null); + error.setWarning(true); + errors.add(error); + break; // One warning per comparison expression is sufficient. + } + } + } + return visitChildren(ctx); + } + + /** + * Returns true if this comparison expression actually applies a comparison operator, as opposed + * to being a pass-through to a single logical expression. Checks the direct children for a + * comparison operator token rather than relying on a specific generated accessor. + */ + private boolean hasComparisonOperator(ExpressionParser.ComparisonExpressionContext ctx) { + for (int i = 0; i < ctx.getChildCount(); i++) { + if (ctx.getChild(i) instanceof TerminalNode) { + int type = ((TerminalNode) ctx.getChild(i)).getSymbol().getType(); + switch (type) { + case ExpressionLexer.GT: + case ExpressionLexer.LT: + case ExpressionLexer.GTE: + case ExpressionLexer.LTE: + case ExpressionLexer.EQ: + case ExpressionLexer.NEQ: + return true; + default: + break; + } + } + } + return false; + } + @Override public Void visitArithmeticExpression(ExpressionParser.ArithmeticExpressionContext ctx) { List terms = ctx.term(); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipant.java index 4202f22a0..31023eb74 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipant.java @@ -130,6 +130,15 @@ public class SynapseDiagnosticsParticipant implements IDiagnosticsParticipant { "|\\{\\$\\{(.+?)\\}\\}" // {${...}} — wrapped form ); + /** + * Leaf elements whose text content is raw code (not a Synapse expression) and must not be + * scanned for vars.X references — e.g. a JS/Groovy script body could legitimately contain a + * ${...} template literal that is not a Synapse variable reference. + */ + private static final Set RAW_TEXT_ELEMENTS = new HashSet<>(Arrays.asList( + "script" + )); + /** * Regex to find $N placeholders in PayloadFactory format strings. */ @@ -1146,53 +1155,96 @@ private void collectVariableDefinition(DOMElement element, Set definedVa */ private void validateVariableReferences(DOMElement element, List diagnostics, DOMDocument document, Set definedVariables) { - // Check all attributes for ${...} expressions containing vars.X + // Check all attribute values for ${...} expressions containing vars.X List attrs = element.getAttributeNodes(); - if (attrs == null) { + if (attrs != null) { + for (DOMAttr attr : attrs) { + String attrValue = attr.getValue(); + if (attrValue == null || (!attrValue.contains("vars.") && !attrValue.contains("vars["))) { + continue; + } + checkContentForUndefinedVariables(attrValue, XMLPositionUtility.selectAttributeValue(attr), + diagnostics, definedVariables); + } + } + + // Check element text content too — e.g. connector operation parameters such as + // {${vars.x}} reference variables in text, not attributes. + validateVariableReferencesInText(element, diagnostics, document, definedVariables); + } + + /** + * Scans the text content of a leaf element for ${...} expressions referencing undefined vars.X. + * Only leaf elements are inspected (containers expose their text via their leaf children), and + * raw-code elements (see {@link #RAW_TEXT_ELEMENTS}) are skipped to avoid false positives. + */ + private void validateVariableReferencesInText(DOMElement element, List diagnostics, + DOMDocument document, Set definedVariables) { + if (hasChildElements(element)) { + return; + } + String localName = element.getLocalName(); + if (localName != null && RAW_TEXT_ELEMENTS.contains(localName.toLowerCase())) { return; } - for (DOMAttr attr : attrs) { - String attrValue = attr.getValue(); - if (attrValue == null || (!attrValue.contains("vars.") && !attrValue.contains("vars["))) { + List children = element.getChildren(); + if (children == null) { + return; + } + for (DOMNode child : children) { + if (!child.isText()) { continue; } + String text = child.getTextContent(); + if (text == null || (!text.contains("vars.") && !text.contains("vars["))) { + continue; + } + Range range = XMLPositionUtility.createRange(child.getStart(), child.getEnd(), document); + checkContentForUndefinedVariables(text, range, diagnostics, definedVariables); + } + } - // Extract ${...} or {${...}} expressions from the attribute value - Matcher exprMatcher = EXPRESSION_PATTERN.matcher(attrValue); - while (exprMatcher.find()) { - String exprContent = exprMatcher.group(1); - if (exprContent == null) { - exprContent = exprMatcher.group(2); // {${...}} form - } - if (exprContent == null) { - continue; - } - - // Find vars.X references within the expression - Matcher varsMatcher = VARS_REF_PATTERN.matcher(exprContent); - while (varsMatcher.find()) { - // Get the variable name from whichever group matched - String varName = varsMatcher.group(1); - if (varName == null) varName = varsMatcher.group(2); - if (varName == null) varName = varsMatcher.group(3); + /** + * Extracts every ${...}/{${...}} expression from {@code content}, and for each vars.X reference + * to a name not in {@code definedVariables}, adds an UndefinedVariable warning anchored at + * {@code range}. Shared by the attribute-value and text-content reference checks. + */ + private void checkContentForUndefinedVariables(String content, Range range, + List diagnostics, Set definedVariables) { + if (range == null) { + return; + } + Matcher exprMatcher = EXPRESSION_PATTERN.matcher(content); + while (exprMatcher.find()) { + String exprContent = exprMatcher.group(1); + if (exprContent == null) { + exprContent = exprMatcher.group(2); // {${...}} form + } + if (exprContent == null) { + continue; + } - if (varName != null && !definedVariables.contains(varName)) { - Range range = XMLPositionUtility.selectAttributeValue(attr); - if (range != null) { - Diagnostic d = new Diagnostic(); - d.setRange(range); - d.setMessage( - "Variable '" + varName + "' is referenced but not defined in this file. " + - "If it is defined in a calling sequence, this warning can be ignored. " + - "Otherwise, define it using before this point."); - d.setSeverity(DiagnosticSeverity.Warning); - d.setSource(SOURCE); - d.setCode("UndefinedVariable"); - d.setData(varName); - diagnostics.add(d); - } - } + // Find vars.X references within the expression + Matcher varsMatcher = VARS_REF_PATTERN.matcher(exprContent); + while (varsMatcher.find()) { + // Get the variable name from whichever group matched + String varName = varsMatcher.group(1); + if (varName == null) varName = varsMatcher.group(2); + if (varName == null) varName = varsMatcher.group(3); + + if (varName != null && !definedVariables.contains(varName)) { + Diagnostic d = new Diagnostic(); + d.setRange(range); + d.setMessage( + "Variable '" + varName + "' is referenced but not defined in this file. " + + "If it is defined in a calling sequence, this warning can be ignored. " + + "Otherwise, define it using before this point."); + d.setSeverity(DiagnosticSeverity.Warning); + d.setSource(SOURCE); + d.setCode("UndefinedVariable"); + d.setData(varName); + diagnostics.add(d); } } } diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipantTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipantTest.java index 6ba9e5610..9fb188398 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipantTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipantTest.java @@ -468,6 +468,76 @@ public void testResponseVariableDefinesVariable() { assertTrue(diags.isEmpty(), "responseVariable child element should define the variable"); } + // ===== Variable references in element text content (Issue #4) ===== + + @Test + public void testUndefinedVariableInElementText() { + // Connector operation parameter references vars.X in element text, not in an attribute. + // 'soqlQuery1' is a typo for the defined 'soqlQuery' — this is the exact MI Copilot case. + String xml = "" + + "" + + "" + + "{${vars.soqlQuery1}}" + + "sfResponse" + + "false" + + "" + + ""; + List diags = diagnosticsWithCode(diagnose(xml), "UndefinedVariable"); + assertEquals(1, diags.size(), "Undefined variable referenced in element text should warn"); + assertEquals(DiagnosticSeverity.Warning, diags.get(0).getSeverity()); + assertTrue(diags.get(0).getMessage().contains("soqlQuery1")); + } + + @Test + public void testDefinedVariableInElementTextNoWarning() { + // The correctly-spelled variable referenced in element text should not warn. + String xml = "" + + "" + + "" + + "{${vars.soqlQuery}}" + + "sfResponse" + + "" + + ""; + List diags = diagnosticsWithCode(diagnose(xml), "UndefinedVariable"); + assertTrue(diags.isEmpty(), "Defined variable referenced in element text should not warn"); + } + + @Test + public void testUndefinedVariableInElementTextPlainExpressionForm() { + // The plain ${...} form (not {${...}}) inside element text should also be detected. + String xml = "" + + "" + + "{\"id\": \"${vars.missingId}\"}" + + "" + + ""; + List diags = diagnosticsWithCode(diagnose(xml), "UndefinedVariable"); + assertEquals(1, diags.size(), "Undefined variable in ${...} text form should warn"); + assertTrue(diags.get(0).getMessage().contains("missingId")); + } + + @Test + public void testScriptBodyNotScannedForVariables() { + // Raw-code (script) bodies must not be treated as Synapse expressions (no false positives). + String xml = "" + + "" + + ""; + List diags = diagnosticsWithCode(diagnose(xml), "UndefinedVariable"); + assertTrue(diags.isEmpty(), "Script body should not be scanned for variable references"); + } + + @Test + public void testPlainTextWithoutExpressionNoWarning() { + // Element text that merely contains the literal 'vars.' but no ${...} expression must not warn. + String xml = "" + + "" + + "SELECT vars.field FROM Account" + + "sfResponse" + + "" + + ""; + List diags = diagnosticsWithCode(diagnose(xml), "UndefinedVariable"); + assertTrue(diags.isEmpty(), "Plain text without a ${...} expression should not warn"); + } + // ===== Non-Synapse document skipping ===== @Test diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/synapse/expression/ExpressionValidatorTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/synapse/expression/ExpressionValidatorTest.java index fe49955d7..d97c1dc59 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/synapse/expression/ExpressionValidatorTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/synapse/expression/ExpressionValidatorTest.java @@ -441,4 +441,68 @@ public void testZeroArrayIndexNoWarning() { List errors = ExpressionValidator.validate("payload[0]"); assertTrue(errors.isEmpty(), "Zero array index should produce no warnings"); } + + // ===== Operator precedence: logical operator (and/or) as a comparison operand ===== + // In the Synapse expression grammar 'and'/'or' bind TIGHTER than the comparison operators, + // so 'a <= 0 or b > 10' parses as 'a <= (0 or b) > 10' rather than '(a <= 0) or (b > 10)'. + + @Test + public void testOrBindsTighterThanComparisonWarns() { + // Parses as payload.id <= (0 or payload.id) > 10 — almost certainly not intended. + List errors = ExpressionValidator.validate("payload.id <= 0 or payload.id > 10"); + assertEquals(1, errors.size(), "Mixing 'or' with comparisons without parentheses should warn once"); + assertTrue(errors.get(0).getMessage().contains("Operator precedence"), + "Should warn about operator precedence: " + errors.get(0).getMessage()); + assertTrue(errors.get(0).isWarning(), "Precedence issue should be a warning, not an error"); + } + + @Test + public void testCopilotPrecedenceExampleWarns() { + // The exact expression MI Copilot generated (Issue #1). + List errors = ExpressionValidator.validate( + "integer(params.queryParams.id) <= 0 or integer(params.queryParams.id) > 10"); + assertTrue(errors.stream().anyMatch(e -> e.getMessage().contains("Operator precedence")), + "Copilot precedence example should produce a precedence warning"); + assertTrue(errors.stream().filter(e -> e.getMessage().contains("Operator precedence")) + .allMatch(ExpressionError::isWarning), + "Precedence diagnostic should be a warning"); + } + + @Test + public void testAndBindsTighterThanComparisonWarns() { + // Parses as (payload.a and payload.b) <= 5. + List errors = ExpressionValidator.validate("payload.a and payload.b <= 5"); + assertEquals(1, errors.size(), "Mixing 'and' with a comparison without parentheses should warn once"); + assertTrue(errors.get(0).getMessage().contains("Operator precedence"), + "Should warn about operator precedence: " + errors.get(0).getMessage()); + assertTrue(errors.get(0).isWarning(), "Precedence issue should be a warning, not an error"); + } + + @Test + public void testParenthesizedComparisonsNoWarning() { + // The corrected form: each comparison wrapped in parentheses. No precedence ambiguity. + List errors = ExpressionValidator.validate( + "(payload.id <= 0) or (payload.id > 10)"); + assertTrue(errors.isEmpty(), "Parenthesized comparisons should produce no precedence warning"); + } + + @Test + public void testPureLogicalExpressionNoWarning() { + // No comparison operator present — pure logical expression, nothing to flag. + List errors = ExpressionValidator.validate("payload.a and payload.b"); + assertTrue(errors.isEmpty(), "Pure logical expression without a comparison should not warn"); + } + + @Test + public void testPureComparisonNoWarning() { + // No logical operator present — pure comparison, nothing to flag. + List errors = ExpressionValidator.validate("payload.x <= 10"); + assertTrue(errors.isEmpty(), "Pure comparison without a logical operator should not warn"); + } + + @Test + public void testComparisonBetweenTwoAccessesNoWarning() { + List errors = ExpressionValidator.validate("payload.x < payload.y"); + assertTrue(errors.isEmpty(), "Comparison between two accesses should not warn"); + } } From 35153742748df0db19a38cd4d9a4d7d45453c0de Mon Sep 17 00:00:00 2001 From: Isuru Wijesiri Date: Fri, 19 Jun 2026 16:26:52 +0530 Subject: [PATCH 16/34] Report expression diagnostics on the synapse/codeDiagnostic path MI Copilot validates in-memory (unsaved) generated code via the synapse/codeDiagnostic RPC. That handler parsed the code with the literal URI "temp", which fails SynapseExpressionValidator's activation gate (it only runs for documents whose URI is under src/main/wso2mi/artifacts). As a result every expression diagnostic -- operator-precedence warnings, syntax errors, unknown-function/arg-count errors -- was silently dropped for the agent, even though the editor's didOpen -> publishDiagnostics flow reported them correctly. - CodeDiagnosticRequest: add a fileName field (the extension already sends it alongside code; it was being discarded). - Utils.getDOMDocument(content, uri, resolver): new overload that assigns the given URI. The existing 2-arg overload now delegates with uri="temp", so no other caller changes behavior. - SynapseLanguageService.codeDiagnostic(): use the request's fileName as the document URI when present, falling back to "temp" otherwise. This also unblocks the other URI-gated checks on this path (cross-file reference validation and the 4.4.0+ hints via deriveProjectPath). Adds CodeDiagnosticFileNameTest covering the artifacts-path, parenthesized, and "temp" fallback cases end-to-end through the diagnostics pipeline. --- .../lemminx/SynapseLanguageService.java | 6 +- .../synapse/CodeDiagnosticRequest.java | 11 ++ .../customservice/synapse/utils/Utils.java | 19 ++- .../synapse/CodeDiagnosticFileNameTest.java | 147 ++++++++++++++++++ 4 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/CodeDiagnosticFileNameTest.java diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/SynapseLanguageService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/SynapseLanguageService.java index dae35912d..80741016c 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/SynapseLanguageService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/SynapseLanguageService.java @@ -346,7 +346,11 @@ private PublishDiagnosticsParams doDiagnostics(DOMDocument xmlDocument, CancelCh public CompletableFuture codeDiagnostic(CodeDiagnosticRequest param) { return CompletableFuture.supplyAsync(() -> { - DOMDocument xmlDocument = Utils.getDOMDocument(param.getCode(), uriResolverExtensionManager); + // Use the real file path (when supplied) as the document URI. Several diagnostics are + // gated on the document path — e.g. SynapseExpressionValidator only runs for files under + // src/main/wso2mi/artifacts — so the literal "temp" fallback would silently drop them. + String uri = param.getFileName() != null ? param.getFileName() : "temp"; + DOMDocument xmlDocument = Utils.getDOMDocument(param.getCode(), uri, uriResolverExtensionManager); return doDiagnostics(xmlDocument, NULL_CANCEL_CHECKER); }); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/CodeDiagnosticRequest.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/CodeDiagnosticRequest.java index 029d7027a..fe934de43 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/CodeDiagnosticRequest.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/CodeDiagnosticRequest.java @@ -17,6 +17,7 @@ public class CodeDiagnosticRequest { private String code; + private String fileName; public String getCode() { @@ -27,4 +28,14 @@ public void setCode(String code) { this.code = code; } + + public String getFileName() { + + return fileName; + } + + public void setFileName(String fileName) { + + this.fileName = fileName; + } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Utils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Utils.java index 98f2d8049..bafb8e2d3 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Utils.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Utils.java @@ -270,7 +270,24 @@ public static DOMDocument getDOMDocument(String content) { public static DOMDocument getDOMDocument(String content, URIResolverExtensionManager resolverExtensionManager) { - TextDocument document = new TextDocument(content, "temp"); + return getDOMDocument(content, "temp", resolverExtensionManager); + } + + /** + * Get the DOM document from the given xml content, using the provided URI as the document's + * system id. The URI matters for diagnostics that are gated on the document path (e.g. + * SynapseExpressionValidator only runs for files under src/main/wso2mi/artifacts), so callers + * that have a real file path should pass it instead of relying on the "temp" fallback. + * + * @param content the xml content + * @param uri the URI to assign to the parsed document + * @param resolverExtensionManager the URI resolver extension manager + * @return the DOM document for the given xml content + */ + public static DOMDocument getDOMDocument(String content, String uri, + URIResolverExtensionManager resolverExtensionManager) { + + TextDocument document = new TextDocument(content, uri); return DOMParser.getInstance().parse(document, resolverExtensionManager); } diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/CodeDiagnosticFileNameTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/CodeDiagnosticFileNameTest.java new file mode 100644 index 000000000..552aa38a0 --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/CodeDiagnosticFileNameTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com). + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * WSO2 LLC - support for WSO2 Micro Integrator Configuration + */ + +package org.eclipse.lemminx.extensions.synapse; + +import org.eclipse.lemminx.AbstractCacheBasedTest; +import org.eclipse.lemminx.XMLAssert; +import org.eclipse.lemminx.customservice.synapse.utils.Utils; +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.extensions.contentmodel.settings.ContentModelSettings; +import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationRootSettings; +import org.eclipse.lemminx.services.XMLLanguageService; +import org.eclipse.lsp4j.Diagnostic; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests for the {@code synapse/codeDiagnostic} document-URI plumbing. + * + *

The MI Copilot agent validates in-memory (unsaved) code via {@code synapse/codeDiagnostic}. + * Before the fix this path parsed the code with the literal URI {@code "temp"}, which made the + * URI-gated {@code SynapseExpressionValidator} (it only runs for files under + * {@code src/main/wso2mi/artifacts}) silently skip every expression diagnostic. The fix lets the + * caller supply the real file name as the document URI (with {@code "temp"} as the backward + * compatible fallback). + * + *

These tests exercise the exact pipeline {@code SynapseLanguageService.codeDiagnostic()} runs + * internally — {@code Utils.getDOMDocument(code, uri, resolver)} followed by + * {@code XMLLanguageService.doDiagnostics(...)} — and assert the operator-precedence warning is + * surfaced only when the URI is under the artifacts path. + */ +public class CodeDiagnosticFileNameTest extends AbstractCacheBasedTest { + + private static final String SYNAPSE_NS = "http://ws.apache.org/ns/synapse"; + private static final String SYNAPSE_CATALOG_440 = + "src/main/resources/org/eclipse/lemminx/schemas/440/catalog.xml"; + // An absolute path under the project artifacts directory, as the MI extension sends. + private static final String ARTIFACT_URI = + "/home/proj/src/main/wso2mi/artifacts/sequences/Test.xml"; + + // with an unparenthesized comparison/logical mix — the precedence pitfall. + private static final String UNPARENTHESIZED = "" + + "" + + ""; + // The corrected, explicitly parenthesized form. + private static final String PARENTHESIZED = "" + + "" + + ""; + + /** + * Reproduces the codeDiagnostic pipeline: build the DOM document with the given URI (via the + * fileName-aware overload the fix added), then run the full XML diagnostics pipeline. + */ + private List codeDiagnostic(String code, String fileName) { + XMLLanguageService ls = new XMLLanguageService(); + String uri = fileName != null ? fileName : "temp"; + DOMDocument document = Utils.getDOMDocument(code, uri, ls.getResolverExtensionManager()); + ls.setDocumentProvider(u -> document); + + ContentModelSettings settings = new ContentModelSettings(); + settings.setUseCache(false); + XMLValidationRootSettings validation = new XMLValidationRootSettings(); + validation.setNoGrammar("ignore"); + settings.setValidation(validation); + settings.setCatalogs(new String[]{SYNAPSE_CATALOG_440}); + ls.doSave(new XMLAssert.SettingsSaveContext(settings)); + + return ls.doDiagnostics(document, settings.getValidation(), Collections.emptyMap(), () -> {}); + } + + private List precedenceWarnings(List diagnostics) { + return diagnostics.stream() + .filter(d -> d.getMessage() != null && d.getMessage().contains("Operator precedence")) + .collect(Collectors.toList()); + } + + @Test + public void testPrecedenceWarningReturnedForArtifactsPath() { + // With a real artifacts file name, the expression validator runs and reports the warning. + List diags = codeDiagnostic(UNPARENTHESIZED, ARTIFACT_URI); + assertFalse(precedenceWarnings(diags).isEmpty(), + "codeDiagnostic with an artifacts file name should surface the operator-precedence warning"); + } + + @Test + public void testNoPrecedenceWarningForParenthesizedExpression() { + // The corrected, parenthesized form must not be flagged even on the artifacts path. + List diags = codeDiagnostic(PARENTHESIZED, ARTIFACT_URI); + assertTrue(precedenceWarnings(diags).isEmpty(), + "Parenthesized comparisons should produce no operator-precedence warning"); + } + + @Test + public void testTempFallbackDocumentsUnchangedBehavior() { + // Backward compatibility: a null file name falls back to "temp", which is not under the + // artifacts path, so the expression validator stays disabled (unchanged pre-fix behavior). + List diags = codeDiagnostic(UNPARENTHESIZED, null); + assertTrue(precedenceWarnings(diags).isEmpty(), + "The \"temp\" fallback keeps the expression validator disabled, as before the fix"); + } + + // ===== URI plumbing unit checks (deterministic, no validation pipeline) ===== + + @Test + public void testGetDOMDocumentUsesProvidedUri() { + DOMDocument document = Utils.getDOMDocument(UNPARENTHESIZED, ARTIFACT_URI, null); + assertEquals(ARTIFACT_URI, document.getDocumentURI(), + "The 3-arg overload should assign the supplied URI to the document"); + } + + @Test + public void testGetDOMDocumentDefaultStillUsesTemp() { + // The legacy overloads (which delegate with no URI) must keep the "temp" URI unchanged. + DOMDocument document = Utils.getDOMDocument(UNPARENTHESIZED); + assertEquals("temp", document.getDocumentURI(), + "The legacy overloads should keep the \"temp\" URI for backward compatibility"); + } + + @Test + public void testCodeDiagnosticRequestCarriesFileName() { + org.eclipse.lemminx.customservice.synapse.CodeDiagnosticRequest request = + new org.eclipse.lemminx.customservice.synapse.CodeDiagnosticRequest(); + request.setCode(UNPARENTHESIZED); + request.setFileName(ARTIFACT_URI); + assertEquals(UNPARENTHESIZED, request.getCode()); + assertEquals(ARTIFACT_URI, request.getFileName(), + "CodeDiagnosticRequest must carry the fileName sent by the extension"); + } +} From 27ecb313e0d647751e1239fa4436c8e25fbbe843 Mon Sep 17 00:00:00 2001 From: Isuru Wijesiri Date: Fri, 19 Jun 2026 16:48:07 +0530 Subject: [PATCH 17/34] Flag unclosed ${ expression delimiters An opening "${" with no matching "}" (e.g. expression="${payload.count > 0") was silently accepted: SynapseExpressionValidator only validates a value that both starts with "${" and ends with "}", so an unterminated expression is treated as a plain string and the malformed ${} boundary is never checked. Add an UnclosedExpression warning in SynapseDiagnosticsParticipant that scans attribute values and leaf element text for a "${" with no matching "}". It runs for any Synapse document (so both the editor's didOpen flow and the synapse/codeDiagnostic path are covered) and skips raw-code (script) bodies. The detector is false-positive-safe: the expression grammar has no {/} tokens of its own (indexing uses [ ]), so the only braces inside ${...} are within string literals -- the scan ignores those, so a valid expression such as ${concat('}', x)} is correctly recognized as closed. Adds tests covering unclosed in attributes and text, the closed and {${...}} forms, a brace inside a string literal, and the script-body exclusion. --- .../SynapseDiagnosticsParticipant.java | 101 ++++++++++++++++++ .../SynapseDiagnosticsParticipantTest.java | 68 ++++++++++++ 2 files changed, 169 insertions(+) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipant.java index 31023eb74..658793b3d 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipant.java @@ -236,6 +236,10 @@ private void validateElement(DOMNode node, List diagnostics, DOMDocu // Validate variable references in expression attributes validateVariableReferences(element, diagnostics, document, definedVariables); + // Flag an opening "${" with no matching "}" (the malformed expression is otherwise + // treated as a plain string and reaches runtime with no feedback) + validateUnclosedExpressions(element, diagnostics, document); + // Cross-file reference validation if (knownArtifacts != null) { validateCrossReferences(element, diagnostics, knownArtifacts); @@ -1250,6 +1254,103 @@ private void checkContentForUndefinedVariables(String content, Range range, } } + /** + * Flags an opening "${" that is never closed by a matching "}". Such a value is not recognized + * as a Synapse expression (it is treated as a plain string), so the malformed expression would + * otherwise reach runtime with no feedback. Scans attribute values and leaf element text, + * mirroring the variable-reference checks; raw-code elements (see {@link #RAW_TEXT_ELEMENTS}) + * are skipped to avoid false positives. + */ + private void validateUnclosedExpressions(DOMElement element, List diagnostics, + DOMDocument document) { + List attrs = element.getAttributeNodes(); + if (attrs != null) { + for (DOMAttr attr : attrs) { + String attrValue = attr.getValue(); + if (attrValue != null && attrValue.contains("${") && hasUnclosedExpression(attrValue)) { + reportUnclosedExpression(XMLPositionUtility.selectAttributeValue(attr), diagnostics); + } + } + } + + if (hasChildElements(element)) { + return; + } + String localName = element.getLocalName(); + if (localName != null && RAW_TEXT_ELEMENTS.contains(localName.toLowerCase())) { + return; + } + List children = element.getChildren(); + if (children == null) { + return; + } + for (DOMNode child : children) { + if (!child.isText()) { + continue; + } + String text = child.getTextContent(); + if (text != null && text.contains("${") && hasUnclosedExpression(text)) { + reportUnclosedExpression( + XMLPositionUtility.createRange(child.getStart(), child.getEnd(), document), diagnostics); + } + } + } + + private void reportUnclosedExpression(Range range, List diagnostics) { + if (range == null) { + return; + } + Diagnostic d = new Diagnostic(); + d.setRange(range); + d.setMessage("Unclosed expression: '${' is not terminated by a matching '}'. " + + "Synapse expressions must be written as ${...} — add the missing '}'."); + d.setSeverity(DiagnosticSeverity.Warning); + d.setSource(SOURCE); + d.setCode("UnclosedExpression"); + diagnostics.add(d); + } + + /** + * Returns true if {@code value} contains an opening "${" with no matching "}" closing it. + * Synapse expressions have no "{"/"}" tokens of their own (indexing uses "[" "]"), so the only + * braces that can appear inside ${...} are within string literals — which are skipped here, so a + * valid expression such as {@code ${concat('{', x)}} is not mistaken for unclosed. + */ + private boolean hasUnclosedExpression(String value) { + int open = value.indexOf("${"); + while (open >= 0) { + if (!hasClosingBrace(value, open + 2)) { + return true; + } + open = value.indexOf("${", open + 2); + } + return false; + } + + /** + * Returns true if there is a '}' at or after {@code from} that lies outside any string literal. + */ + private boolean hasClosingBrace(String value, int from) { + boolean inString = false; + char quote = 0; + for (int i = from; i < value.length(); i++) { + char c = value.charAt(i); + if (inString) { + if (c == '\\') { + i++; // skip the escaped character + } else if (c == quote) { + inString = false; + } + } else if (c == '"' || c == '\'') { + inString = true; + quote = c; + } else if (c == '}') { + return true; + } + } + return false; + } + /** * Validate cross-file references (key, target, onError attributes). */ diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipantTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipantTest.java index 9fb188398..e5fe5f91e 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipantTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipantTest.java @@ -538,6 +538,74 @@ public void testPlainTextWithoutExpressionNoWarning() { assertTrue(diags.isEmpty(), "Plain text without a ${...} expression should not warn"); } + // ===== Unclosed expression delimiters ===== + + @Test + public void testUnclosedExpressionInAttributeWarns() { + // '${' opened but never closed — previously treated as a plain string with no feedback. + String xml = "" + + " 0\"/>" + + ""; + List diags = diagnosticsWithCode(diagnose(xml), "UnclosedExpression"); + assertEquals(1, diags.size(), "An unclosed ${ in an attribute should warn"); + assertEquals(DiagnosticSeverity.Warning, diags.get(0).getSeverity()); + assertTrue(diags.get(0).getMessage().contains("Unclosed expression")); + } + + @Test + public void testClosedExpressionInAttributeNoWarning() { + String xml = "" + + " 0}\"/>" + + ""; + List diags = diagnosticsWithCode(diagnose(xml), "UnclosedExpression"); + assertTrue(diags.isEmpty(), "A properly closed ${...} must not be flagged"); + } + + @Test + public void testUnclosedExpressionInElementTextWarns() { + // Same gap in element text (e.g. a connector operation parameter). + String xml = "" + + "" + + "${payload.count" + + "" + + ""; + List diags = diagnosticsWithCode(diagnose(xml), "UnclosedExpression"); + assertEquals(1, diags.size(), "An unclosed ${ in element text should warn"); + assertTrue(diags.get(0).getMessage().contains("Unclosed expression")); + } + + @Test + public void testWrappedClosedExpressionNoWarning() { + // The {${...}} form, properly closed, must not be flagged as unclosed. + String xml = "" + + "" + + "{${vars.q}}" + + ""; + List diags = diagnosticsWithCode(diagnose(xml), "UnclosedExpression"); + assertTrue(diags.isEmpty(), "A closed {${...}} expression must not be flagged"); + } + + @Test + public void testExpressionWithBraceInStringLiteralNoWarning() { + // A '}' inside a string literal must not be mistaken for the closing delimiter, and the + // real closing '}' must still be recognized — so this valid expression is not flagged. + String xml = "" + + "" + + ""; + List diags = diagnosticsWithCode(diagnose(xml), "UnclosedExpression"); + assertTrue(diags.isEmpty(), "A brace inside a string literal must not cause a false positive"); + } + + @Test + public void testScriptBodyUnclosedExpressionNotFlagged() { + // Raw-code (script) bodies are excluded — a ${ in JS is not a Synapse expression. + String xml = "" + + "" + + ""; + List diags = diagnosticsWithCode(diagnose(xml), "UnclosedExpression"); + assertTrue(diags.isEmpty(), "Script bodies must not be scanned for unclosed expressions"); + } + // ===== Non-Synapse document skipping ===== @Test From d9d24154821694ded1a12e3226e9e08acc972006 Mon Sep 17 00:00:00 2001 From: Isuru Wijesiri Date: Tue, 23 Jun 2026 12:02:57 +0530 Subject: [PATCH 18/34] Add skipCrossFileValidation opt-out and refresh stale cross-file index Two related fixes for the agent's per-file validation via synapse/codeDiagnostic, where cross-file reference checks were firing spuriously. 1) Opt-in skipCrossFileValidation flag (default off) on synapse/codeDiagnostic. The agent validates a file right after writing it, before the sibling artifacts it references exist, so cross-file checks (UnresolvedArtifactReference, UnresolvedConfigKeyReference, DuplicateArtifactName, UnknownTemplateParameter, CircularArtifactReference, ...) misfire. The flag lets the agent suppress only those checks; the editor and the explicit "validate all" path never set it and are unchanged. - CodeDiagnosticRequest: add boolean skipCrossFileValidation (default false). - SynapseDiagnosticsParticipant: a thread-confined flag (set/cleared by codeDiagnostic around doDiagnostics). When set, the run skips buildArtifactNameIndex (so the knownArtifacts-gated checks and the filesystem scan are skipped) and the two cross-file checks not gated on that index (validateCallTemplateParams, validateDuplicateArtifactName). The skip path mutates no shared index state, so concurrent editor validations are unaffected; within-file checks (schema, expression/precedence, UndefinedVariable, ...) keep running. - SynapseLanguageService.codeDiagnostic(): set the flag from the request and clear it in a finally block. 2) Refresh the cached cross-file artifact index when project files change. The index is cached per project with a ~5s TTL, so just after a file is written/saved the cached index can omit it, wrongly flagging a sibling that exists on disk as unresolved (affects the editor and "validate all" too). - SynapseDiagnosticsParticipant: a static epoch counter and invalidateArtifactIndexCache(); each cache entry records its build epoch and is honored only while that epoch still matches, so a bump forces a rebuild on the next run even within the TTL. - XMLWorkspaceService.didChangeWatchedFiles and XMLTextDocumentService.didSave: bump the epoch when a changed/saved file under src/main/wso2mi is seen, covering external/agent writes (file watcher) and editor saves. Tests: skip=true drops UnresolvedArtifactReference while keeping the within-file UndefinedVariable; default (false) still flags it; the request flag defaults false; and a stale-index reference resolves after the sibling is written and the cache is invalidated. --- .../lemminx/SynapseLanguageService.java | 16 +- .../lemminx/XMLTextDocumentService.java | 9 +- .../eclipse/lemminx/XMLWorkspaceService.java | 7 + .../synapse/CodeDiagnosticRequest.java | 17 ++ .../SynapseDiagnosticsParticipant.java | 81 ++++++++- .../validator/SynapseExpressionValidator.java | 11 +- .../synapse/CodeDiagnosticFileNameTest.java | 13 +- .../SynapseDiagnosticsParticipantTest.java | 163 ++++++++++++++++++ 8 files changed, 304 insertions(+), 13 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/SynapseLanguageService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/SynapseLanguageService.java index 80741016c..328ba14ae 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/SynapseLanguageService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/SynapseLanguageService.java @@ -150,6 +150,7 @@ import org.eclipse.lemminx.customservice.synapse.idp.PdfToImagesRequest; import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationSettings; +import org.eclipse.lemminx.extensions.synapse.SynapseDiagnosticsParticipant; import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse; import org.eclipse.lemminx.settings.SharedSettings; import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager; @@ -349,9 +350,18 @@ public CompletableFuture codeDiagnostic(CodeDiagnostic // Use the real file path (when supplied) as the document URI. Several diagnostics are // gated on the document path — e.g. SynapseExpressionValidator only runs for files under // src/main/wso2mi/artifacts — so the literal "temp" fallback would silently drop them. - String uri = param.getFileName() != null ? param.getFileName() : "temp"; - DOMDocument xmlDocument = Utils.getDOMDocument(param.getCode(), uri, uriResolverExtensionManager); - return doDiagnostics(xmlDocument, NULL_CANCEL_CHECKER); + // Treat a blank fileName as missing, otherwise an unusable URI would skip those checks. + String uri = StringUtils.isBlank(param.getFileName()) ? "temp" : param.getFileName(); + // Opt-out (default off) for cross-file reference checks: the agent validates a file + // before its referenced siblings are written, so those checks would fire spuriously. + // Set/clear around doDiagnostics on this thread; the editor never sets it. + try { + SynapseDiagnosticsParticipant.setSkipCrossFileValidation(param.isSkipCrossFileValidation()); + DOMDocument xmlDocument = Utils.getDOMDocument(param.getCode(), uri, uriResolverExtensionManager); + return doDiagnostics(xmlDocument, NULL_CANCEL_CHECKER); + } finally { + SynapseDiagnosticsParticipant.clearSkipCrossFileValidation(); + } }); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java index 4c742b6ab..f75e3e65e 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java @@ -39,6 +39,7 @@ import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.dom.DOMParser; import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationRootSettings; +import org.eclipse.lemminx.extensions.synapse.SynapseDiagnosticsParticipant; import org.eclipse.lemminx.services.DocumentSymbolsResult; import org.eclipse.lemminx.services.SymbolInformationResult; import org.eclipse.lemminx.services.XMLLanguageService; @@ -599,7 +600,13 @@ public CompletableFuture> colorPresentation(ColorPresent public void didSave(DidSaveTextDocumentParams params) { computeAsync((monitor) -> { // A document was saved, collect documents to revalidate - SaveContext context = new SaveContext(params.getTextDocument().getUri()); + String savedUri = params.getTextDocument().getUri(); + if (savedUri != null && savedUri.contains("src/main/wso2mi")) { + // An artifact/resource file was saved — drop the cached cross-file index so the + // revalidation below (and sibling files) sees the updated set instead of stale data. + SynapseDiagnosticsParticipant.invalidateArtifactIndexCache(); + } + SaveContext context = new SaveContext(savedUri); doSave(context); // Manage didSave document lifecycle participants diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLWorkspaceService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLWorkspaceService.java index f866168c8..e9d07c133 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLWorkspaceService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLWorkspaceService.java @@ -21,6 +21,7 @@ import org.eclipse.lemminx.commons.WorkspaceFolders; import org.eclipse.lemminx.customservice.synapse.utils.Constant; +import org.eclipse.lemminx.extensions.synapse.SynapseDiagnosticsParticipant; import org.eclipse.lemminx.services.extensions.commands.IXMLCommandService; import org.eclipse.lsp4j.DidChangeConfigurationParams; import org.eclipse.lsp4j.DidChangeWatchedFilesParams; @@ -99,6 +100,12 @@ public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) { } else if (change.getUri().contains(Constant.CONNECTORS) && change.getUri().contains(".zip")) { ((SynapseLanguageService) xmlLanguageServer.getSynapseLanguageService()).updateConnectors(); } else { + if (change.getUri().contains("src/main/wso2mi")) { + // An artifact/resource file changed on disk — drop the cached cross-file index so + // the next diagnostics run rebuilds it (otherwise a just-written sibling stays + // "unresolved" for up to the cache TTL). + SynapseDiagnosticsParticipant.invalidateArtifactIndexCache(); + } if (!xmlTextDocumentService.documentIsOpen(change.getUri())) { xmlTextDocumentService.doSave(change.getUri()); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/CodeDiagnosticRequest.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/CodeDiagnosticRequest.java index fe934de43..189eddd65 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/CodeDiagnosticRequest.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/CodeDiagnosticRequest.java @@ -18,6 +18,7 @@ public class CodeDiagnosticRequest { private String code; private String fileName; + private boolean skipCrossFileValidation; public String getCode() { @@ -38,4 +39,20 @@ public void setFileName(String fileName) { this.fileName = fileName; } + + /** + * When true, cross-file reference checks (which depend on other artifact files existing) are + * skipped for this request. Defaults to false, so the editor and the explicit "validate all" + * path are unaffected. The MI Copilot agent sets it for per-file auto-validation after a write, + * where a referenced sibling artifact may not exist on disk yet. + */ + public boolean isSkipCrossFileValidation() { + + return skipCrossFileValidation; + } + + public void setSkipCrossFileValidation(boolean skipCrossFileValidation) { + + this.skipCrossFileValidation = skipCrossFileValidation; + } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipant.java index 658793b3d..fd2fd9afa 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipant.java @@ -47,6 +47,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; @@ -72,6 +73,20 @@ public class SynapseDiagnosticsParticipant implements IDiagnosticsParticipant { private static final long ARTIFACT_CACHE_TTL_MS = 5000; // 5 seconds private final Map artifactIndexCache = new ConcurrentHashMap<>(); + /** + * Invalidation signal for {@link #artifactIndexCache}. The cache is keyed per project with a + * short TTL; when an artifact/resource file under {@code src/main/wso2mi} changes, the language + * server bumps this epoch so the next diagnostics run rebuilds the index instead of serving a + * stale entry (which would wrongly flag a just-written sibling as unresolved for up to the TTL). + * A cache entry is only honored while its stored epoch matches the current one. + */ + private static final AtomicLong artifactCacheEpoch = new AtomicLong(); + + /** Invalidate the cross-file artifact index cache (call when project artifact files change). */ + public static void invalidateArtifactIndexCache() { + artifactCacheEpoch.incrementAndGet(); + } + /** Template name -> absolute file path, populated during artifact index building. */ private volatile Map templateFilePaths = java.util.Collections.emptyMap(); /** Artifact names that appear in multiple files (duplicates). */ @@ -79,6 +94,28 @@ public class SynapseDiagnosticsParticipant implements IDiagnosticsParticipant { /** Artifact names that participate in direct circular references (A->B->A). */ private volatile Set cyclicArtifacts = java.util.Collections.emptySet(); + /** + * Request-scoped opt-out for cross-file (other-artifact-dependent) checks. The MI Copilot agent + * sets this for per-file auto-validation after a write, when sibling artifacts it references may + * not exist on disk yet, to suppress transient false positives. It is thread-confined and set by + * {@code SynapseLanguageService.codeDiagnostic()} around the {@code doDiagnostics} call; the + * editor/manual flows never set it, so cross-file validation stays on by default. + */ + private static final ThreadLocal SKIP_CROSS_FILE_VALIDATION = + ThreadLocal.withInitial(() -> Boolean.FALSE); + + public static void setSkipCrossFileValidation(boolean skip) { + SKIP_CROSS_FILE_VALIDATION.set(skip); + } + + public static void clearSkipCrossFileValidation() { + SKIP_CROSS_FILE_VALIDATION.remove(); + } + + private static boolean isSkipCrossFileValidation() { + return Boolean.TRUE.equals(SKIP_CROSS_FILE_VALIDATION.get()); + } + private static final Set SYNAPSE_ROOT_ELEMENTS = new HashSet<>(Arrays.asList( "api", "proxy", "endpoint", "sequence", "inboundEndpoint", "template", "task", "localEntry", "messageStore", "messageProcessor", "registry" @@ -158,8 +195,13 @@ public class SynapseDiagnosticsParticipant implements IDiagnosticsParticipant { "api", "proxy", "sequence", "inboundEndpoint", "resource" )); + // Synchronized because a single participant instance is registered in SynapsePlugin and + // diagnostics can run concurrently (editor validation and codeDiagnostic both execute async). + // The cross-file index is derived into shared instance fields per run, so serializing here keeps + // one request from clearing/overwriting that state while another is still validating. Runs are + // short (the project scan is cached), so the contention cost is minimal. @Override - public void doDiagnostics(DOMDocument xmlDocument, List diagnostics, + public synchronized void doDiagnostics(DOMDocument xmlDocument, List diagnostics, XMLValidationSettings validationSettings, CancelChecker cancelChecker) { DOMElement root = xmlDocument.getDocumentElement(); if (root == null) { @@ -173,7 +215,18 @@ public void doDiagnostics(DOMDocument xmlDocument, List diagnostics, if (SYNAPSE_NS.equals(namespace)) { // Valid Synapse file — run all validations Set definedVariables = new HashSet<>(); - Set knownArtifacts = buildArtifactNameIndex(xmlDocument, cancelChecker); + // Cross-file reference checks depend on the project-wide artifact index. When the caller + // opts out (agent per-file validation) the index is not built, avoiding the filesystem + // scan; it is also null when the project path is not derivable. In either case the + // index is unavailable, so clear the derived cross-file state — otherwise a stale index + // from a prior request could surface template/duplicate/cycle diagnostics here. + boolean skipCrossFile = isSkipCrossFileValidation(); + Set knownArtifacts = skipCrossFile ? null : buildArtifactNameIndex(xmlDocument, cancelChecker); + if (knownArtifacts == null) { + this.templateFilePaths = java.util.Collections.emptyMap(); + this.duplicateArtifactNames = java.util.Collections.emptySet(); + this.cyclicArtifacts = java.util.Collections.emptySet(); + } // Detect MI runtime version for new-pattern hints String projectPath = deriveProjectPath(xmlDocument); @@ -965,6 +1018,8 @@ private void validateScriptMediator(DOMElement element, List diagnos * P1-14: Validate call-template with-param names against template parameter declarations. */ private void validateCallTemplateParams(DOMElement element, List diagnostics) { + // Cross-file check: depends on the referenced template file (cycles + parameter declarations) + if (isSkipCrossFileValidation()) return; String target = element.getAttribute("target"); if (StringUtils.isEmpty(target) || isExpression(target)) return; @@ -1071,6 +1126,7 @@ private Map parseTemplateParameters(String filePath) { * P1-19: Warn if the current document's root artifact name is duplicated in the project. */ private void validateDuplicateArtifactName(DOMElement root, List diagnostics) { + if (isSkipCrossFileValidation()) return; // cross-file check: needs the project-wide index if (duplicateArtifactNames.isEmpty()) return; String name = root.getAttribute("name"); if (name != null && duplicateArtifactNames.contains(name)) { @@ -1196,7 +1252,9 @@ private void validateVariableReferencesInText(DOMElement element, List) commonly wrap + // ${vars.x} references in . + if (!child.isText() && !child.isCDATA()) { continue; } String text = child.getTextContent(); @@ -1285,7 +1343,8 @@ private void validateUnclosedExpressions(DOMElement element, List di return; } for (DOMNode child : children) { - if (!child.isText()) { + // Include CDATA: ${...} can appear inside payloads too. + if (!child.isText() && !child.isCDATA()) { continue; } String text = child.getTextContent(); @@ -1521,9 +1580,14 @@ private Set buildArtifactNameIndex(DOMDocument document, CancelChecker c return null; } + // Read the invalidation epoch up front: a cache entry built before the latest file change + // is treated as a miss even within its TTL, so a just-written sibling is picked up at once. + long epoch = artifactCacheEpoch.get(); + // Check cache first CachedArtifactIndex cached = artifactIndexCache.get(projectPath); - if (cached != null && (System.currentTimeMillis() - cached.timestamp) < ARTIFACT_CACHE_TTL_MS) { + if (cached != null && cached.epoch == epoch + && (System.currentTimeMillis() - cached.timestamp) < ARTIFACT_CACHE_TTL_MS) { // Restore all derived state so cross-reference checks see the same // template paths, duplicates, and cycles as a fresh build would. this.templateFilePaths = cached.templateFilePaths; @@ -1568,7 +1632,7 @@ private Set buildArtifactNameIndex(DOMDocument document, CancelChecker c this.duplicateArtifactNames = duplicates; this.cyclicArtifacts = cycles; artifactIndexCache.put(projectPath, new CachedArtifactIndex( - artifactNames, templatePaths, duplicates, cycles, System.currentTimeMillis())); + artifactNames, templatePaths, duplicates, cycles, System.currentTimeMillis(), epoch)); return artifactNames; } @@ -2372,17 +2436,20 @@ private static class CachedArtifactIndex { final Set duplicateArtifactNames; final Set cyclicArtifacts; final long timestamp; + final long epoch; CachedArtifactIndex(Set artifactNames, Map templateFilePaths, Set duplicateArtifactNames, Set cyclicArtifacts, - long timestamp) { + long timestamp, + long epoch) { this.artifactNames = artifactNames; this.templateFilePaths = templateFilePaths; this.duplicateArtifactNames = duplicateArtifactNames; this.cyclicArtifacts = cyclicArtifacts; this.timestamp = timestamp; + this.epoch = epoch; } } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/synapse/validator/SynapseExpressionValidator.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/synapse/validator/SynapseExpressionValidator.java index 6f6af2573..1800df10a 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/synapse/validator/SynapseExpressionValidator.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/synapse/validator/SynapseExpressionValidator.java @@ -64,7 +64,16 @@ public void startDocument(XMLLocator locator, String encoding, NamespaceContext private boolean isFileInArtifacts(String baseSystemId) { - return baseSystemId.contains(TryOutConstants.PROJECT_ARTIFACT_PATH.toString()); + if (baseSystemId == null) { + return false; + } + // Compare with forward slashes so the check holds on every OS: the document system id is + // typically a file:// URI (always '/'), while PROJECT_ARTIFACT_PATH.toString() uses the + // platform separator ('\' on Windows) — without normalizing, the gate would never match on + // Windows and expression validation would silently not run there. + String normalizedId = baseSystemId.replace('\\', '/'); + String artifactsPath = TryOutConstants.PROJECT_ARTIFACT_PATH.toString().replace('\\', '/'); + return normalizedId.contains(artifactsPath); } @Override diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/CodeDiagnosticFileNameTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/CodeDiagnosticFileNameTest.java index 552aa38a0..51315537e 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/CodeDiagnosticFileNameTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/CodeDiagnosticFileNameTest.java @@ -52,7 +52,8 @@ public class CodeDiagnosticFileNameTest extends AbstractCacheBasedTest { private static final String SYNAPSE_NS = "http://ws.apache.org/ns/synapse"; private static final String SYNAPSE_CATALOG_440 = "src/main/resources/org/eclipse/lemminx/schemas/440/catalog.xml"; - // An absolute path under the project artifacts directory, as the MI extension sends. + // An absolute path under the project artifacts directory, as the MI extension sends. The + // SynapseExpressionValidator gate is separator-agnostic, so this forward-slash URI works on all OSes. private static final String ARTIFACT_URI = "/home/proj/src/main/wso2mi/artifacts/sequences/Test.xml"; @@ -144,4 +145,14 @@ public void testCodeDiagnosticRequestCarriesFileName() { assertEquals(ARTIFACT_URI, request.getFileName(), "CodeDiagnosticRequest must carry the fileName sent by the extension"); } + + @Test + public void testCodeDiagnosticRequestSkipCrossFileDefaultsFalse() { + org.eclipse.lemminx.customservice.synapse.CodeDiagnosticRequest request = + new org.eclipse.lemminx.customservice.synapse.CodeDiagnosticRequest(); + assertFalse(request.isSkipCrossFileValidation(), + "skipCrossFileValidation must default to false so the editor/validate-all paths are unchanged"); + request.setSkipCrossFileValidation(true); + assertTrue(request.isSkipCrossFileValidation()); + } } diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipantTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipantTest.java index e5fe5f91e..585b37d47 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipantTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/synapse/SynapseDiagnosticsParticipantTest.java @@ -606,6 +606,33 @@ public void testScriptBodyUnclosedExpressionNotFlagged() { assertTrue(diags.isEmpty(), "Script bodies must not be scanned for unclosed expressions"); } + // ===== CDATA payloads are scanned too ===== + + @Test + public void testUndefinedVariableInCdataWarns() { + // ${vars.x} inside a CDATA payload (e.g. payloadFactory format) must still be validated. + String xml = "" + + "" + + "" + + "" + + ""; + List diags = diagnosticsWithCode(diagnose(xml), "UndefinedVariable"); + assertEquals(1, diags.size(), "Undefined variable referenced inside CDATA should warn"); + assertTrue(diags.get(0).getMessage().contains("missingCdata")); + } + + @Test + public void testUnclosedExpressionInCdataWarns() { + // An unclosed ${ inside a CDATA payload must still be flagged. + String xml = "" + + "" + + "" + + "" + + ""; + List diags = diagnosticsWithCode(diagnose(xml), "UnclosedExpression"); + assertEquals(1, diags.size(), "Unclosed ${ inside CDATA should warn"); + } + // ===== Non-Synapse document skipping ===== @Test @@ -1656,6 +1683,7 @@ public void restoreUserHome() { originalUserHome = null; } SynapseLanguageService.setLoadedResourceFinder(null); + SynapseDiagnosticsParticipant.clearSkipCrossFileValidation(); } /** @@ -1744,4 +1772,139 @@ public void testUnknownSequenceStillFlaggedWithDependencies(@TempDir Path tempDi assertEquals(1, unresolved.size()); assertTrue(unresolved.get(0).getMessage().contains("reallyDoesNotExist")); } + + // ===== skipCrossFileValidation opt-out (Change 1) ===== + + /** As {@link #diagnoseAtPath(String, Path)} but with the request-scoped cross-file opt-out set. */ + private List diagnoseAtPath(String xml, Path xmlFilePath, boolean skipCrossFile) throws Exception { + try { + SynapseDiagnosticsParticipant.setSkipCrossFileValidation(skipCrossFile); + return diagnoseAtPath(xml, xmlFilePath); + } finally { + SynapseDiagnosticsParticipant.clearSkipCrossFileValidation(); + } + } + + @Test + public void testSkipCrossFileValidationSuppressesUnresolvedButKeepsWithinFileChecks(@TempDir Path tempDir) + throws Exception { + originalUserHome = System.getProperty("user.home"); + System.setProperty("user.home", tempDir.toString()); + + Path consumer = tempDir.resolve("consumer"); + Path apiXml = consumer.resolve("src/main/wso2mi/artifacts/apis/cvh.xml"); + // References a sequence that does not exist (cross-file) AND an undefined variable (within-file). + String xml = "" + + "" + + "" + + "" + + ""; + + List diags = diagnoseAtPath(xml, apiXml, true); + assertTrue(diagnosticsWithCode(diags, "UnresolvedArtifactReference").isEmpty(), + "skipCrossFileValidation must suppress the cross-file UnresolvedArtifactReference"); + assertEquals(1, diagnosticsWithCode(diags, "UndefinedVariable").size(), + "Within-file UndefinedVariable must still be reported when cross-file checks are skipped"); + } + + @Test + public void testCrossFileValidationDefaultStillFlagsUnresolved(@TempDir Path tempDir) throws Exception { + originalUserHome = System.getProperty("user.home"); + System.setProperty("user.home", tempDir.toString()); + + Path consumer = tempDir.resolve("consumer"); + Path apiXml = consumer.resolve("src/main/wso2mi/artifacts/apis/cvh.xml"); + String xml = "" + + "" + + "" + + ""; + + // Default (flag false) — cross-file validation runs and flags the unresolved reference. + List diags = diagnoseAtPath(xml, apiXml, false); + assertEquals(1, diagnosticsWithCode(diags, "UnresolvedArtifactReference").size(), + "With cross-file validation on (default), an unresolved reference must still be flagged"); + } + + // ===== Cached artifact index invalidation (Change 2) ===== + + /** Runs diagnostics with a caller-supplied participant so its artifact-index cache persists across calls. */ + private List diagnoseAtPathWith(SynapseDiagnosticsParticipant participant, String xml, + Path xmlFilePath) throws Exception { + Files.createDirectories(xmlFilePath.getParent()); + Files.writeString(xmlFilePath, xml); + TextDocument textDocument = new TextDocument(xml, xmlFilePath.toUri().toString()); + DOMDocument document = DOMParser.getInstance().parse(textDocument, null); + List diagnostics = new ArrayList<>(); + participant.doDiagnostics(document, diagnostics, null, () -> {}); + return diagnostics; + } + + @Test + public void testInvalidateArtifactIndexCacheRebuildsAfterFileChange(@TempDir Path tempDir) throws Exception { + originalUserHome = System.getProperty("user.home"); + System.setProperty("user.home", tempDir.toString()); + + Path consumer = tempDir.resolve("consumer"); + Path apiXml = consumer.resolve("src/main/wso2mi/artifacts/apis/cvh.xml"); + String api = "" + + "" + + ""; + + // Reuse one participant so its cross-file index cache survives across calls (as in production). + SynapseDiagnosticsParticipant participant = new SynapseDiagnosticsParticipant(); + + // 1. Sibling does not exist yet -> unresolved, and the index is now cached for this project. + List first = diagnoseAtPathWith(participant, api, apiXml); + assertEquals(1, diagnosticsWithCode(first, "UnresolvedArtifactReference").size(), + "Sibling 'sibling' does not exist yet -> should be flagged unresolved"); + + // 2. Write the sibling on disk. Within the TTL and without invalidation the cache is stale. + Path siblingXml = consumer.resolve("src/main/wso2mi/artifacts/sequences/sibling.xml"); + Files.createDirectories(siblingXml.getParent()); + Files.writeString(siblingXml, ""); + + List stale = diagnoseAtPathWith(participant, api, apiXml); + assertEquals(1, diagnosticsWithCode(stale, "UnresolvedArtifactReference").size(), + "Within the TTL and without invalidation, the stale cached index still flags it unresolved"); + + // 3. Invalidate -> the next run rebuilds the index and resolves the now-present sibling. + SynapseDiagnosticsParticipant.invalidateArtifactIndexCache(); + List fresh = diagnoseAtPathWith(participant, api, apiXml); + assertTrue(diagnosticsWithCode(fresh, "UnresolvedArtifactReference").isEmpty(), + "After invalidation the rebuilt index includes the new sibling -> no longer unresolved"); + } + + @Test + public void testStaleCrossFileStateNotLeakedWhenIndexUnavailable(@TempDir Path tempDir) throws Exception { + originalUserHome = System.getProperty("user.home"); + System.setProperty("user.home", tempDir.toString()); + + Path project = tempDir.resolve("proj"); + // Two artifacts sharing a name -> "DupSeq" becomes a known duplicate for this project. + Path seqA = project.resolve("src/main/wso2mi/artifacts/sequences/a.xml"); + Path seqB = project.resolve("src/main/wso2mi/artifacts/sequences/b.xml"); + Files.createDirectories(seqA.getParent()); + Files.writeString(seqA, ""); + Files.writeString(seqB, ""); + + // Reuse one participant so its instance-level cross-file state persists across requests. + SynapseDiagnosticsParticipant participant = new SynapseDiagnosticsParticipant(); + + // Request A: validate a doc inside the project so the duplicate index is built into the + // participant's instance state (duplicateArtifactNames = { "DupSeq" }). + Path apiXml = project.resolve("src/main/wso2mi/artifacts/apis/cvh.xml"); + diagnoseAtPathWith(participant, "" + + "", + apiXml); + + // Request B (same participant): a doc named "DupSeq" whose project path is not derivable, so + // the cross-file index is unavailable. The stale duplicate state must be cleared, not reused. + TextDocument textB = new TextDocument( + "", "test.xml"); + DOMDocument docB = DOMParser.getInstance().parse(textB, null); + List diagsB = new ArrayList<>(); + participant.doDiagnostics(docB, diagsB, null, () -> {}); + assertTrue(diagnosticsWithCode(diagsB, "DuplicateArtifactName").isEmpty(), + "Stale cross-file duplicate state must not leak to a request with no project index"); + } } From 8c411d2f1c2b581a87ab7f8a8b10845f0ea7b465 Mon Sep 17 00:00:00 2001 From: Isuru Wijesiri Date: Thu, 25 Jun 2026 17:12:37 +0530 Subject: [PATCH 19/34] Normalize path separators in artifact file-change cache invalidation The didSave and didChangeWatchedFiles handlers invalidate the cross-file artifact index when a changed file's URI contains "src/main/wso2mi". LSP document URIs use forward slashes on every OS, so this already works on Windows, but normalize the URI's separators before the check so a backslash path would also match. This is a small defensive hardening, consistent with the separator-agnostic artifacts-path gate in SynapseExpressionValidator. Follow-up to #552. --- .../main/java/org/eclipse/lemminx/XMLTextDocumentService.java | 3 ++- .../src/main/java/org/eclipse/lemminx/XMLWorkspaceService.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java index f75e3e65e..380748726 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java @@ -601,7 +601,8 @@ public void didSave(DidSaveTextDocumentParams params) { computeAsync((monitor) -> { // A document was saved, collect documents to revalidate String savedUri = params.getTextDocument().getUri(); - if (savedUri != null && savedUri.contains("src/main/wso2mi")) { + // LSP document URIs use '/', but normalize defensively so a backslash path also matches on Windows. + if (savedUri != null && savedUri.replace('\\', '/').contains("src/main/wso2mi")) { // An artifact/resource file was saved — drop the cached cross-file index so the // revalidation below (and sibling files) sees the updated set instead of stale data. SynapseDiagnosticsParticipant.invalidateArtifactIndexCache(); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLWorkspaceService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLWorkspaceService.java index e9d07c133..738703dc5 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLWorkspaceService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLWorkspaceService.java @@ -100,7 +100,8 @@ public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) { } else if (change.getUri().contains(Constant.CONNECTORS) && change.getUri().contains(".zip")) { ((SynapseLanguageService) xmlLanguageServer.getSynapseLanguageService()).updateConnectors(); } else { - if (change.getUri().contains("src/main/wso2mi")) { + // LSP URIs use '/', but normalize defensively so a backslash path also matches on Windows. + if (change.getUri().replace('\\', '/').contains("src/main/wso2mi")) { // An artifact/resource file changed on disk — drop the cached cross-file index so // the next diagnostics run rebuilds it (otherwise a just-written sibling stays // "unresolved" for up to the cache TTL). From 05a7114cfb9e4a33bd9efa9bbb40ee9f5dd12ed2 Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Thu, 25 Jun 2026 21:47:37 +0530 Subject: [PATCH 20/34] Add support to the binds-to attribute in APIs --- .../AbstractResourceFinder.java | 29 +++++++++++++++++++ .../NewProjectResourceFinder.java | 1 + .../resourceFinder/pojo/ArtifactResource.java | 11 +++++++ .../pojo/RequestedResource.java | 13 +++++++++ .../syntaxTree/factory/APIFactory.java | 4 +++ .../syntaxTree/factory/ResourceFactory.java | 4 +++ .../synapse/syntaxTree/pojo/api/API.java | 11 +++++++ .../syntaxTree/pojo/api/APIResource.java | 11 +++++++ .../serializer/api/APISerializer.java | 3 ++ .../serializer/api/ResourceSerializer.java | 4 +++ .../customservice/synapse/utils/Constant.java | 1 + .../org/eclipse/lemminx/schemas/430/api.xsd | 1 + .../lemminx/schemas/430/misc/resource.xsd | 2 ++ .../org/eclipse/lemminx/schemas/440/api.xsd | 1 + .../lemminx/schemas/440/misc/resource.xsd | 2 ++ 15 files changed, 98 insertions(+) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/resourceFinder/AbstractResourceFinder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/resourceFinder/AbstractResourceFinder.java index 0a81af63e..5e35441e9 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/resourceFinder/AbstractResourceFinder.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/resourceFinder/AbstractResourceFinder.java @@ -672,6 +672,34 @@ private void traverseRegistryFolder(File folder, Map a */ public abstract Map findAllResources(String projectPath); + /** + * For each requested resource that declares a protocols filter, drops resources of that + * type whose protocol attribute is not in the list. Unfiltered types are left untouched. + * + * @param response the response whose resource list is filtered in place + * @param requestedResources the requested resources and protocol filters if any + */ + protected void applyProtocolFilters(ResourceResponse response, List requestedResources) { + + if (response.getResources() == null) { + return; + } + for (RequestedResource requested : requestedResources) { + List protocols = requested.getProtocols(); + if (protocols == null || protocols.isEmpty()) { + continue; + } + response.getResources().removeIf(resource -> { + if (!requested.getType().equals(resource.getType())) { + return false; + } + String protocol = resource instanceof ArtifactResource + ? ((ArtifactResource) resource).getProtocol() : null; + return protocol == null || protocols.stream().noneMatch(p -> p.equalsIgnoreCase(protocol.trim())); + }); + } + } + protected List findResourceInArtifacts(Path artifactsPath, List types) { List resources = new ArrayList<>(); @@ -1025,6 +1053,7 @@ private Resource createArtifactResource(File file, DOMElement rootElement, Strin artifact.setFrom(ARTIFACTS); ((ArtifactResource) artifact).setLocalEntry(isLocalEntry); ((ArtifactResource) artifact).setMcpInbound(Utils.isMcpInboundEndpoint(rootElement)); + ((ArtifactResource) artifact).setProtocol(rootElement.getAttribute(Constant.PROTOCOL)); ((ArtifactResource) artifact).setArtifactPath(file.getName()); ((ArtifactResource) artifact).setAbsolutePath(file.getAbsolutePath()); return artifact; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/resourceFinder/NewProjectResourceFinder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/resourceFinder/NewProjectResourceFinder.java index 07c74dfc2..03e8c09bc 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/resourceFinder/NewProjectResourceFinder.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/resourceFinder/NewProjectResourceFinder.java @@ -54,6 +54,7 @@ protected ResourceResponse findResources(String projectPath, List protocols; public RequestedResource() { @@ -48,4 +51,14 @@ public void setNeedRegistry(boolean needRegistry) { this.needRegistry = needRegistry; } + + public List getProtocols() { + + return protocols; + } + + public void setProtocols(List protocols) { + + this.protocols = protocols; + } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/factory/APIFactory.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/factory/APIFactory.java index 8be5470bc..392869a71 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/factory/APIFactory.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/factory/APIFactory.java @@ -103,6 +103,10 @@ public void populateAttributes(STNode node, DOMElement element) { if (Objects.nonNull(traceEnum)) { api.setTrace(traceEnum); } + String bindsTo = element.getAttribute(Constant.BINDS_TO); + if (Objects.nonNull(bindsTo)) { + api.setBindsTo(bindsTo); + } } public STNode createAPIResource(DOMNode node, String apiName) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/factory/ResourceFactory.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/factory/ResourceFactory.java index 19f87f3e5..3a91d9d41 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/factory/ResourceFactory.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/factory/ResourceFactory.java @@ -95,6 +95,10 @@ public void populateAttributes(STNode node, DOMElement element) { if (Objects.nonNull(faultSequence)) { apiResource.setFaultSequenceAttribute(faultSequence); } + String bindsTo = element.getAttribute(Constant.BINDS_TO); + if (Objects.nonNull(bindsTo)) { + apiResource.setBindsTo(bindsTo); + } } private STNode createSequence(DOMNode node) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/pojo/api/API.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/pojo/api/API.java index eb35fb30e..fff7a90b1 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/pojo/api/API.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/pojo/api/API.java @@ -30,6 +30,7 @@ public class API extends STNode { String description; EnableDisable statistics; EnableDisable trace; + String bindsTo; public APIResource[] getResource() { @@ -150,4 +151,14 @@ public void setTrace(EnableDisable trace) { this.trace = trace; } + + public String getBindsTo() { + + return bindsTo; + } + + public void setBindsTo(String bindsTo) { + + this.bindsTo = bindsTo; + } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/pojo/api/APIResource.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/pojo/api/APIResource.java index afeeea14a..5ca39019e 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/pojo/api/APIResource.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/pojo/api/APIResource.java @@ -30,6 +30,7 @@ public class APIResource extends STNode { String faultSequenceAttribute; String uriTemplate; String urlMapping; + String bindsTo; public String getApi() { @@ -141,6 +142,16 @@ public void setUrlMapping(String urlMapping) { this.urlMapping = urlMapping; } + public String getBindsTo() { + + return bindsTo; + } + + public void setBindsTo(String bindsTo) { + + this.bindsTo = bindsTo; + } + public void addMethod(String method) { if (this.methods == null) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/serializer/api/APISerializer.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/serializer/api/APISerializer.java index 2f70276dd..8aa59a4e5 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/serializer/api/APISerializer.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/serializer/api/APISerializer.java @@ -59,6 +59,9 @@ private static void addAttributes(API api, OMElement apiElt) { if (api.getTrace() != null) { apiElt.addAttribute("trace", api.getTrace().name(), null); } + if (api.getBindsTo() != null) { + apiElt.addAttribute(Constant.BINDS_TO, api.getBindsTo(), null); + } } public static OMElement serializeVersioningStrategy(API api, OMElement apiElement) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/serializer/api/ResourceSerializer.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/serializer/api/ResourceSerializer.java index 919d005a4..18ebe0e8f 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/serializer/api/ResourceSerializer.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/syntaxTree/serializer/api/ResourceSerializer.java @@ -51,6 +51,10 @@ public static OMElement serializeResource(APIResource resource) { resourceElt.addAttribute("url-mapping", resource.getUrlMapping(), null); } + if (resource.getBindsTo() != null) { + resourceElt.addAttribute(Constant.BINDS_TO, resource.getBindsTo(), null); + } + if (resource.getInSequenceAttribute() != null) { resourceElt.addAttribute("inSequence", resource.getInSequenceAttribute(), null); } else { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Constant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Constant.java index 7772cf0ba..a1886b1dc 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Constant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Constant.java @@ -292,6 +292,7 @@ public class Constant { public static final String HOSTNAME = "hostname"; public static final String VERSION_TYPE = "version-type"; public static final String PUBLISH_SWAGGER = "publishSwagger"; + public static final String BINDS_TO = "binds-to"; public static final String URL_MAPPING = "url-mapping"; public static final String SPACE = " "; public static final String WSDL_IMPORT = "wsdl:import"; diff --git a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/api.xsd b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/api.xsd index bf49c6330..93d56092b 100644 --- a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/api.xsd +++ b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/api.xsd @@ -74,6 +74,7 @@ + diff --git a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/misc/resource.xsd b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/misc/resource.xsd index 2313b5360..5a10574af 100644 --- a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/misc/resource.xsd +++ b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/430/misc/resource.xsd @@ -75,6 +75,8 @@ + + diff --git a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/440/api.xsd b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/440/api.xsd index bf49c6330..93d56092b 100644 --- a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/440/api.xsd +++ b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/440/api.xsd @@ -74,6 +74,7 @@ + diff --git a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/440/misc/resource.xsd b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/440/misc/resource.xsd index 2313b5360..5a10574af 100644 --- a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/440/misc/resource.xsd +++ b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/schemas/440/misc/resource.xsd @@ -75,6 +75,8 @@ + + From fe9c36bfd56fe82b3374e8341eadbeb5429ab340 Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Mon, 29 Jun 2026 09:18:10 +0000 Subject: [PATCH 21/34] Update LS version --- org.eclipse.lemminx/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org.eclipse.lemminx/pom.xml b/org.eclipse.lemminx/pom.xml index 5d5b176c0..6fc2f64e0 100644 --- a/org.eclipse.lemminx/pom.xml +++ b/org.eclipse.lemminx/pom.xml @@ -3,7 +3,7 @@ org.wso2.language.server mi-language-server-parent - 0.24.0-wso2v91 + 0.24.0-wso2v92 ../pom.xml MI Language Server diff --git a/pom.xml b/pom.xml index 35087195d..e01908cac 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.wso2.language.server mi-language-server-parent - 0.24.0-wso2v91 + 0.24.0-wso2v92 pom MI Language Server - Parent LemMinX is a XML Language Server Protocol (LSP), and can be used with any editor that supports LSP, to offer an outstanding XML editing experience From 3370e9ef93339c23d8d1260720b8eb1face9111c Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Mon, 29 Jun 2026 17:28:03 +0530 Subject: [PATCH 22/34] Add Solace inbound-endpoint configuration --- .../lemminx/inbound-endpoints/inbound_endpoints_460.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_460.json b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_460.json index 7a40ee83a..c2b2d18ee 100644 --- a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_460.json +++ b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_460.json @@ -131,6 +131,11 @@ "name": "RabbitMQ (Inbound)", "id": "org.wso2.carbon.inbound.rabbitmq.RabbitMQListener", "type": "inbound-endpoint" + }, + { + "name": "Solace (Inbound)", + "id": "org.wso2.carbon.inbound.solace.SolaceEventListener", + "type": "inbound-endpoint" } ] } From 366340093e3bd788312f187d329965b9d5803edc Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Fri, 3 Jul 2026 09:45:37 +0530 Subject: [PATCH 23/34] Change custom inbound-endpoints directory --- .../eclipse/lemminx/XMLWorkspaceService.java | 3 +- .../directoryTree/DirectoryTreeBuilder.java | 25 +++++---- .../conector/InboundConnectorHolder.java | 56 ++++++++++++++----- 3 files changed, 58 insertions(+), 26 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLWorkspaceService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLWorkspaceService.java index 738703dc5..284c0fbea 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLWorkspaceService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLWorkspaceService.java @@ -95,7 +95,8 @@ public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) { .getTextDocumentService(); List changes = params.getChanges(); for (FileEvent change : changes) { - if (change.getUri().contains(Constant.INBOUND_CONNECTORS_DIR) && change.getUri().contains(".zip")) { + if ((change.getUri().contains(Constant.INBOUND_ENDPOINTS) + || change.getUri().contains(Constant.INBOUND_CONNECTORS_DIR)) && change.getUri().contains(".zip")) { ((SynapseLanguageService) xmlLanguageServer.getSynapseLanguageService()).updateInboundConnectors(); } else if (change.getUri().contains(Constant.CONNECTORS) && change.getUri().contains(".zip")) { ((SynapseLanguageService) xmlLanguageServer.getSynapseLanguageService()).updateConnectors(); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java index 48d2e8804..04b23b0a6 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/directoryTree/DirectoryTreeBuilder.java @@ -496,18 +496,19 @@ private static void analyzeConnectorResources(IntegrationDirectoryTree directory private static void analyzeInboundConnectorResources(IntegrationDirectoryTree directoryTree) { - String inboundConnectorPath = projectPath + File.separator + Constant.SRC + File.separator + MAIN - + File.separator + WSO2MI + File.separator + RESOURCES + File.separator - + Constant.INBOUND_CONNECTORS_DIR; - File folder = new File(inboundConnectorPath); - File[] listOfFiles = folder.listFiles(); - if (listOfFiles != null) { - for (File file : listOfFiles) { - if (Utils.isZipFile(file) && !file.isHidden()) { - String name = file.getName(); - String path = file.getAbsolutePath(); - Node resource = new Node("inboundConnector", name, path); - directoryTree.getResources().addInboundConnector(resource); + String resourcesPath = projectPath + File.separator + Constant.SRC + File.separator + MAIN + + File.separator + WSO2MI + File.separator + RESOURCES + File.separator; + for (String dirName : new String[]{Constant.INBOUND_ENDPOINTS, Constant.INBOUND_CONNECTORS_DIR}) { + File folder = new File(resourcesPath + dirName); + File[] listOfFiles = folder.listFiles(); + if (listOfFiles != null) { + for (File file : listOfFiles) { + if (Utils.isZipFile(file) && !file.isHidden()) { + String name = file.getName(); + String path = file.getAbsolutePath(); + Node resource = new Node("inboundConnector", name, path); + directoryTree.getResources().addInboundConnector(resource); + } } } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java index 558b84367..ce9ea81a1 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.fge.jackson.JsonLoader; import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import org.apache.commons.lang3.StringUtils; @@ -126,13 +127,25 @@ private void loadInboundConnectors() { public synchronized String getCustomInboundConnectors() { boolean isInboundConnectorAdded = false; - File extractFolder = new File(Path.of(this.projectPath, Constant.SRC, Constant.MAIN, Constant.WSO2MI, - Constant.RESOURCES, Constant.INBOUND_CONNECTORS_DIR).toString()); InputStream inputStream = JsonLoader.class .getResourceAsStream("/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_" + this.projectRuntimeVersion.replace(".", StringUtils.EMPTY) + Constant.JSON_FILE_EXT); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); this.inboundConnectorListJson = JsonParser.parseReader(reader).getAsJsonObject(); + Path resourcesPath = Path.of(this.projectPath, Constant.SRC, Constant.MAIN, Constant.WSO2MI, + Constant.RESOURCES); + for (String dirName : new String[]{Constant.INBOUND_ENDPOINTS, Constant.INBOUND_CONNECTORS_DIR}) { + File extractFolder = new File(resourcesPath.resolve(dirName).toString()); + if (importInboundConnectorsFromDirectory(extractFolder)) { + isInboundConnectorAdded = true; + } + } + return isInboundConnectorAdded ? "success" : "Failed to import the inbound-connector"; + } + + private boolean importInboundConnectorsFromDirectory(File extractFolder) { + + boolean isInboundConnectorAdded = false; List inboundConnectorZips = getInboundConnectorZips(extractFolder); for (File zip : inboundConnectorZips) { String zipName = zip.getName().replace(Constant.DOT + "zip", StringUtils.EMPTY); @@ -142,18 +155,21 @@ public synchronized String getCustomInboundConnectors() { String schema = Utils.readFile(extractToFolder.toPath().resolve(Constant.RESOURCES) .resolve(Constant.UI_SCHEMA_JSON).toFile()); saveInboundConnector(Utils.getJsonObject(schema).get(Constant.NAME).getAsString(), schema); - JsonObject newConnector = new JsonObject(); JsonObject connectorSchema = Utils.getJsonObject(schema); - newConnector.addProperty(Constant.NAME, connectorSchema.get(Constant.TITLE) != null ? - connectorSchema.get(Constant.TITLE).getAsString() : StringUtils.EMPTY); - newConnector.addProperty(Constant.ID, connectorSchema.get(Constant.ID) != null ? - connectorSchema.get(Constant.ID).getAsString() : StringUtils.EMPTY); - newConnector.addProperty(Constant.DESCRIPTION, connectorSchema.get(Constant.DESCRIPTION) != null ? - connectorSchema.get(Constant.DESCRIPTION).getAsString() : StringUtils.EMPTY); - newConnector.addProperty(Constant.TYPE, Constant.INBOUND_DASH_ENDPOINT); JsonArray connectorArray = this.inboundConnectorListJson.getAsJsonArray(Constant.INBOUND_CONNECTOR_DATA); - connectorArray.add(newConnector); - isInboundConnectorAdded = true; + String connectorId = connectorSchema.get(Constant.ID) != null ? + connectorSchema.get(Constant.ID).getAsString() : StringUtils.EMPTY; + if (!isConnectorAlreadyListed(connectorArray, connectorId)) { + JsonObject newConnector = new JsonObject(); + newConnector.addProperty(Constant.NAME, connectorSchema.get(Constant.TITLE) != null ? + connectorSchema.get(Constant.TITLE).getAsString() : StringUtils.EMPTY); + newConnector.addProperty(Constant.ID, connectorId); + newConnector.addProperty(Constant.DESCRIPTION, connectorSchema.get(Constant.DESCRIPTION) != null ? + connectorSchema.get(Constant.DESCRIPTION).getAsString() : StringUtils.EMPTY); + newConnector.addProperty(Constant.TYPE, Constant.INBOUND_DASH_ENDPOINT); + connectorArray.add(newConnector); + isInboundConnectorAdded = true; + } } catch (Exception e) { LOGGER.log(Level.SEVERE, "Failed to import custom inbound-connector:" + zipName, e); } @@ -165,7 +181,21 @@ public synchronized String getCustomInboundConnectors() { } } } - return isInboundConnectorAdded ? "success" : "Failed to import the inbound-connector"; + return isInboundConnectorAdded; + } + + private boolean isConnectorAlreadyListed(JsonArray connectorArray, String connectorId) { + + if (connectorId == null || connectorId.isEmpty()) { + return false; + } + for (JsonElement element : connectorArray) { + JsonObject connector = element.getAsJsonObject(); + if (connector.has(Constant.ID) && connectorId.equals(connector.get(Constant.ID).getAsString())) { + return true; + } + } + return false; } private List getInboundConnectorZips(File extractFolder) { From 7eab8b804b75ccd62867e4d5577218705e6c7f89 Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Fri, 3 Jul 2026 11:53:32 +0530 Subject: [PATCH 24/34] Fix solace description retrieving issue --- .../lemminx/customservice/synapse/utils/UISchemaMapper.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/UISchemaMapper.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/UISchemaMapper.java index 8d051aaa1..3f85aaf9d 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/UISchemaMapper.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/UISchemaMapper.java @@ -198,6 +198,9 @@ public static JsonObject mapInputToUISchemaForConnector(Connector connector, Jso } else { data.addProperty(Constant.CONFIG_REF, connector.getConfigKey()); } + if (StringUtils.isNotEmpty(connector.getDescription())) { + data.addProperty(Constant.DESCRIPTION, connector.getDescription()); + } return mapInputToUISchema(data, uiSchema); } From 84b6c7bfe802afbe4d551857f2de12efa539ca8d Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Fri, 3 Jul 2026 11:20:17 +0000 Subject: [PATCH 25/34] Update LS version --- org.eclipse.lemminx/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org.eclipse.lemminx/pom.xml b/org.eclipse.lemminx/pom.xml index 6fc2f64e0..07cd773ef 100644 --- a/org.eclipse.lemminx/pom.xml +++ b/org.eclipse.lemminx/pom.xml @@ -3,7 +3,7 @@ org.wso2.language.server mi-language-server-parent - 0.24.0-wso2v92 + 0.24.0-wso2v93 ../pom.xml MI Language Server diff --git a/pom.xml b/pom.xml index e01908cac..5771dfef9 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.wso2.language.server mi-language-server-parent - 0.24.0-wso2v92 + 0.24.0-wso2v93 pom MI Language Server - Parent LemMinX is a XML Language Server Protocol (LSP), and can be used with any editor that supports LSP, to offer an outstanding XML editing experience From f21ad7b2254dc7f6ff738f8a253fa897ceee0dd8 Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Fri, 3 Jul 2026 18:17:19 +0530 Subject: [PATCH 26/34] Handle inbound-endpoints with same ID --- .../conector/InboundConnectorHolder.java | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java index ce9ea81a1..cd1393f8c 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java @@ -154,22 +154,23 @@ private boolean importInboundConnectorsFromDirectory(File extractFolder) { Utils.extractZip(zip, extractToFolder); String schema = Utils.readFile(extractToFolder.toPath().resolve(Constant.RESOURCES) .resolve(Constant.UI_SCHEMA_JSON).toFile()); - saveInboundConnector(Utils.getJsonObject(schema).get(Constant.NAME).getAsString(), schema); - JsonObject connectorSchema = Utils.getJsonObject(schema); - JsonArray connectorArray = this.inboundConnectorListJson.getAsJsonArray(Constant.INBOUND_CONNECTOR_DATA); - String connectorId = connectorSchema.get(Constant.ID) != null ? - connectorSchema.get(Constant.ID).getAsString() : StringUtils.EMPTY; - if (!isConnectorAlreadyListed(connectorArray, connectorId)) { - JsonObject newConnector = new JsonObject(); - newConnector.addProperty(Constant.NAME, connectorSchema.get(Constant.TITLE) != null ? - connectorSchema.get(Constant.TITLE).getAsString() : StringUtils.EMPTY); - newConnector.addProperty(Constant.ID, connectorId); - newConnector.addProperty(Constant.DESCRIPTION, connectorSchema.get(Constant.DESCRIPTION) != null ? - connectorSchema.get(Constant.DESCRIPTION).getAsString() : StringUtils.EMPTY); - newConnector.addProperty(Constant.TYPE, Constant.INBOUND_DASH_ENDPOINT); - connectorArray.add(newConnector); - isInboundConnectorAdded = true; - } + if (saveInboundConnector(Utils.getJsonObject(schema).get(Constant.NAME).getAsString(), schema)) { + JsonObject connectorSchema = Utils.getJsonObject(schema); + JsonArray connectorArray = this.inboundConnectorListJson.getAsJsonArray(Constant.INBOUND_CONNECTOR_DATA); + String connectorId = connectorSchema.get(Constant.ID) != null ? + connectorSchema.get(Constant.ID).getAsString() : StringUtils.EMPTY; + if (!isConnectorAlreadyListed(connectorArray, connectorId)) { + JsonObject newConnector = new JsonObject(); + newConnector.addProperty(Constant.NAME, connectorSchema.get(Constant.TITLE) != null ? + connectorSchema.get(Constant.TITLE).getAsString() : StringUtils.EMPTY); + newConnector.addProperty(Constant.ID, connectorId); + newConnector.addProperty(Constant.DESCRIPTION, connectorSchema.get(Constant.DESCRIPTION) != null ? + connectorSchema.get(Constant.DESCRIPTION).getAsString() : StringUtils.EMPTY); + newConnector.addProperty(Constant.TYPE, Constant.INBOUND_DASH_ENDPOINT); + connectorArray.add(newConnector); + } + isInboundConnectorAdded = true; + } } catch (Exception e) { LOGGER.log(Level.SEVERE, "Failed to import custom inbound-connector:" + zipName, e); } From fcb77e71a616f7084e18332a768663e11cf9be57 Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:23:12 +0530 Subject: [PATCH 27/34] Update connector gen tool version --- org.eclipse.lemminx/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.eclipse.lemminx/pom.xml b/org.eclipse.lemminx/pom.xml index 07cd773ef..1b1ca4275 100644 --- a/org.eclipse.lemminx/pom.xml +++ b/org.eclipse.lemminx/pom.xml @@ -15,7 +15,7 @@ ${maven.build.timestamp} true 0.9.16 - 0.9.10 + 0.9.11 From 4d954ed03fd38c502e72a79a79d18a9e726615f1 Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Wed, 15 Jul 2026 15:07:07 +0530 Subject: [PATCH 28/34] Add ASB inbound-endpoint --- .../lemminx/inbound-endpoints/inbound_endpoints_440.json | 5 +++++ .../lemminx/inbound-endpoints/inbound_endpoints_450.json | 5 +++++ .../lemminx/inbound-endpoints/inbound_endpoints_460.json | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_440.json b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_440.json index c128089ec..1580524b6 100644 --- a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_440.json +++ b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_440.json @@ -143,6 +143,11 @@ "name": "Google PubSub (Inbound)", "id": "org.wso2.carbon.inbound.googlepubsub.GooglePubSubMessageConsumer", "type": "inbound-endpoint" + }, + { + "name": "Azure Service Bus (Inbound)", + "id": "org.wso2.carbon.inbound.asb.ASBEventConsumer", + "type": "inbound-endpoint" } ] } diff --git a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_450.json b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_450.json index 71f52eace..bdbf13668 100644 --- a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_450.json +++ b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_450.json @@ -137,6 +137,11 @@ "name": "Google PubSub (Inbound)", "id": "org.wso2.carbon.inbound.googlepubsub.GooglePubSubMessageConsumer", "type": "inbound-endpoint" + }, + { + "name": "Azure Service Bus (Inbound)", + "id": "org.wso2.carbon.inbound.asb.ASBEventConsumer", + "type": "inbound-endpoint" } ] } diff --git a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_460.json b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_460.json index c2b2d18ee..645d4f6e5 100644 --- a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_460.json +++ b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/inbound-endpoints/inbound_endpoints_460.json @@ -136,6 +136,11 @@ "name": "Solace (Inbound)", "id": "org.wso2.carbon.inbound.solace.SolaceEventListener", "type": "inbound-endpoint" + }, + { + "name": "Azure Service Bus (Inbound)", + "id": "org.wso2.carbon.inbound.asb.ASBEventConsumer", + "type": "inbound-endpoint" } ] } From dd8ff8d565146404d98b4a3b64f0f6f9651132c5 Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Wed, 15 Jul 2026 15:07:16 +0530 Subject: [PATCH 29/34] Update LS version --- org.eclipse.lemminx/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org.eclipse.lemminx/pom.xml b/org.eclipse.lemminx/pom.xml index 07cd773ef..ae6262e76 100644 --- a/org.eclipse.lemminx/pom.xml +++ b/org.eclipse.lemminx/pom.xml @@ -3,7 +3,7 @@ org.wso2.language.server mi-language-server-parent - 0.24.0-wso2v93 + 0.24.0-wso2v94 ../pom.xml MI Language Server diff --git a/pom.xml b/pom.xml index 5771dfef9..7fcb5c405 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.wso2.language.server mi-language-server-parent - 0.24.0-wso2v93 + 0.24.0-wso2v94 pom MI Language Server - Parent LemMinX is a XML Language Server Protocol (LSP), and can be used with any editor that supports LSP, to offer an outstanding XML editing experience From 13d0567960cc49367b9fdcda7cab4bb07b35d79b Mon Sep 17 00:00:00 2001 From: Thuvarakan Sritharan Date: Mon, 20 Jul 2026 11:46:20 +0530 Subject: [PATCH 30/34] Add inbound connector input variable suggestion support --- .../connectors/AbstractConnectorLoader.java | 12 +- .../connectors/NewProjectConnectorLoader.java | 16 ++- .../connectors/entity/ConnectorAction.java | 89 +----------- .../entity/ConnectorVariableSchemaUtils.java | 129 ++++++++++++++++++ .../conector/InboundConnectorHolder.java | 113 ++++++++++++--- .../generate/ServerLessTryoutHandler.java | 37 ++++- .../visitor/InboundEndpointVisitor.java | 61 ++++++++- .../customservice/synapse/utils/Constant.java | 2 + 8 files changed, 338 insertions(+), 121 deletions(-) create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorVariableSchemaUtils.java diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/AbstractConnectorLoader.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/AbstractConnectorLoader.java index 811c3190b..07c648405 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/AbstractConnectorLoader.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/AbstractConnectorLoader.java @@ -14,6 +14,7 @@ package org.eclipse.lemminx.customservice.synapse.connectors; +import com.google.gson.JsonObject; import org.eclipse.lemminx.customservice.SynapseLanguageClientAPI; import org.eclipse.lemminx.customservice.synapse.ConnectorStatusNotification; import org.eclipse.lemminx.customservice.synapse.connectors.entity.Connector; @@ -160,8 +161,15 @@ private void extractZips(List connectorZips, File extractFolder) { if (zipName.contains(INBOUND_CONNECTOR_PREFIX)) { String schema = Utils.readFile(extractToFolder.toPath().resolve(Constant.RESOURCES) .resolve(Constant.UI_SCHEMA_JSON).toFile()); - inboundConnectorHolder.saveInboundConnector(Utils.getJsonObject(schema) - .get(Constant.NAME).getAsString(), schema); + JsonObject uiSchemaJson = Utils.getJsonObject(schema); + String connectorName = uiSchemaJson.get(Constant.NAME).getAsString(); + inboundConnectorHolder.saveInboundConnector(connectorName, schema); + File inputSchemaFile = extractToFolder.toPath().resolve(Constant.RESOURCES) + .resolve(Constant.INPUT_SCHEMA_JSON).toFile(); + if (inputSchemaFile.exists() && uiSchemaJson.has(Constant.ID)) { + inboundConnectorHolder.saveInboundConnectorInputSchema(connectorName, + uiSchemaJson.get(Constant.ID).getAsString(), Utils.readFile(inputSchemaFile)); + } } } catch (IOException e) { log.log(Level.WARNING, "Failed to extract connector zip:" + zipName, e); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/NewProjectConnectorLoader.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/NewProjectConnectorLoader.java index 6a169eb59..97f0fa526 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/NewProjectConnectorLoader.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/NewProjectConnectorLoader.java @@ -119,11 +119,19 @@ protected void cleanOldConnectors(File connectorExtractFolder, List connec if (connectorFolder.getName().contains(Constant.INBOUND_CONNECTOR_PREFIX) ) { String schema = Utils.readFile(connectorFolder.toPath().resolve(Constant.RESOURCES) .resolve(Constant.UI_SCHEMA_JSON).toFile()); - String fileName = Utils.getJsonObject(schema).get(Constant.NAME).getAsString() + Constant.JSON_FILE_EXT; + String inboundName = Utils.getJsonObject(schema).get(Constant.NAME).getAsString(); String projectFolderName = connectorExtractFolder.getParentFile().getName(); - File schemaToRemove = Path.of(getUserHome(), Constant.WSO2_MI, - Constant.INBOUND_CONNECTORS).resolve(projectFolderName).resolve(fileName).toFile(); - FileUtils.delete(schemaToRemove); + Path inboundCacheDir = Path.of(getUserHome(), Constant.WSO2_MI, + Constant.INBOUND_CONNECTORS).resolve(projectFolderName); + File schemaToRemove = inboundCacheDir.resolve(inboundName + Constant.JSON_FILE_EXT).toFile(); + if (schemaToRemove.exists()) { + FileUtils.delete(schemaToRemove); + } + File inputSchemaToRemove = inboundCacheDir + .resolve(inboundName + InboundConnectorHolder.INPUT_SCHEMA_FILE_SUFFIX).toFile(); + if (inputSchemaToRemove.exists()) { + FileUtils.delete(inputSchemaToRemove); + } } FileUtils.deleteDirectory(connectorFolder); notifyRemoveConnector(connectorName, true, "Connector deleted successfully"); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorAction.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorAction.java index c9246be1d..858f988fa 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorAction.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorAction.java @@ -14,21 +14,15 @@ package org.eclipse.lemminx.customservice.synapse.connectors.entity; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import org.apache.commons.lang3.StringUtils; import org.eclipse.lemminx.customservice.synapse.mediator.tryout.pojo.Property; -import org.eclipse.lemminx.customservice.synapse.utils.Constant; import org.eclipse.lemminx.customservice.synapse.utils.Utils; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Set; public class ConnectorAction { @@ -151,91 +145,10 @@ private void loadOutputSchema() throws IOException { String outputSchemaString = Utils.readFile(new File(outputSchemaPath)); JsonObject outputSchemaJson = Utils.getJsonObject(outputSchemaString); if (outputSchemaJson != null) { - outputSchema = createSchemaObject(outputSchemaJson); + outputSchema = ConnectorVariableSchemaUtils.buildSchemaProperty(outputSchemaJson); } } - private Property createSchemaObject(JsonObject outputSchemaJson) { - JsonObject properties = outputSchemaJson.getAsJsonObject(Constant.PROPERTIES); - if (properties == null) { - return null; - } - Property outputSchemaObject = new Property("root", StringUtils.EMPTY); - // Store definitions for reference resolution - JsonObject definitions = outputSchemaJson.getAsJsonObject(Constant.DEFINITIONS); - List propertiesList = extractProperties(properties, definitions, new HashSet<>()); - outputSchemaObject.setProperties(propertiesList); - return outputSchemaObject; - } - - private List extractProperties(JsonObject propertiesObject, JsonObject definitions, Set processedRefs) { - List propertiesList = new ArrayList<>(); - for (Map.Entry entry : propertiesObject.entrySet()) { - String key = entry.getKey(); - JsonElement value = entry.getValue(); - if (value.isJsonObject()) { - JsonObject propertyObject = value.getAsJsonObject(); - - // Check if this is a reference to a definition - if (propertyObject.has(Constant.REF)) { - String ref = propertyObject.get(Constant.REF).getAsString(); - // Handle only definitions references (#/definitions/...) - if (ref.startsWith(Constant.SCHEMA_DEFINITION) && definitions != null) { - String definitionKey = ref.substring(Constant.SCHEMA_DEFINITION.length()); - - // Prevent circular references - if (!processedRefs.contains(definitionKey)) { - processedRefs.add(definitionKey); - - JsonObject definitionObj = definitions.getAsJsonObject(definitionKey); - if (definitionObj != null) { - // Create property with the key from the property name - Property property = new Property(key, StringUtils.EMPTY); - - // Get description from the definition if available - if (definitionObj.has(Constant.DESCRIPTION)) { - property.setDescription(definitionObj.get(Constant.DESCRIPTION).getAsString()); - } - - // Extract nested properties from the definition - if (definitionObj.has(Constant.PROPERTIES)) { - List nestedProps = extractProperties( - definitionObj.getAsJsonObject(Constant.PROPERTIES), - definitions, - new HashSet<>(processedRefs) - ); - property.setProperties(nestedProps); - } - - propertiesList.add(property); - } - } - continue; - } - } - - // Process regular properties (non-reference) - JsonElement propDescriptionObj = propertyObject.get(Constant.DESCRIPTION); - String propDescription = propDescriptionObj != null ? - propDescriptionObj.getAsString() : StringUtils.EMPTY; - - Property property = new Property(key, StringUtils.EMPTY, propDescription); - - if (propertyObject.has(Constant.PROPERTIES)) { - List properties = extractProperties( - propertyObject.getAsJsonObject(Constant.PROPERTIES), - definitions, - new HashSet<>(processedRefs) - ); - property.setProperties(properties); - } - - propertiesList.add(property); - } - } - return propertiesList; - } - public Property getOutputSchema() { if (outputSchema == null) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorVariableSchemaUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorVariableSchemaUtils.java new file mode 100644 index 000000000..a36570d04 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorVariableSchemaUtils.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com). + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * WSO2 LLC - support for WSO2 Micro Integrator Configuration + */ + +package org.eclipse.lemminx.customservice.synapse.connectors.entity; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.lemminx.customservice.synapse.mediator.tryout.pojo.Property; +import org.eclipse.lemminx.customservice.synapse.utils.Constant; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Utility for converting a JSON schema (the {@code outputschema.json} shipped by + * connectors and inbound connectors) into the {@link Property} tree consumed by the + * mediator tryout system. + */ +public class ConnectorVariableSchemaUtils { + + private ConnectorVariableSchemaUtils() { + + } + + /** + * Builds a {@link Property} tree rooted at "root" from the given output schema + * JSON object. Returns {@code null} if the schema has no {@code properties}. + */ + public static Property buildSchemaProperty(JsonObject outputSchemaJson) { + + if (outputSchemaJson == null) { + return null; + } + JsonObject properties = outputSchemaJson.getAsJsonObject(Constant.PROPERTIES); + if (properties == null) { + return null; + } + Property outputSchemaObject = new Property("root", StringUtils.EMPTY); + // Store definitions for reference resolution + JsonObject definitions = outputSchemaJson.getAsJsonObject(Constant.DEFINITIONS); + List propertiesList = extractProperties(properties, definitions, new HashSet<>()); + outputSchemaObject.setProperties(propertiesList); + return outputSchemaObject; + } + + private static List extractProperties(JsonObject propertiesObject, JsonObject definitions, + Set processedRefs) { + List propertiesList = new ArrayList<>(); + for (Map.Entry entry : propertiesObject.entrySet()) { + String key = entry.getKey(); + JsonElement value = entry.getValue(); + if (value.isJsonObject()) { + JsonObject propertyObject = value.getAsJsonObject(); + + // Check if this is a reference to a definition + if (propertyObject.has(Constant.REF)) { + String ref = propertyObject.get(Constant.REF).getAsString(); + // Handle only definitions references (#/definitions/...) + if (ref.startsWith(Constant.SCHEMA_DEFINITION) && definitions != null) { + String definitionKey = ref.substring(Constant.SCHEMA_DEFINITION.length()); + + // Prevent circular references + if (!processedRefs.contains(definitionKey)) { + processedRefs.add(definitionKey); + + JsonObject definitionObj = definitions.getAsJsonObject(definitionKey); + if (definitionObj != null) { + // Create property with the key from the property name + Property property = new Property(key, StringUtils.EMPTY); + + // Get description from the definition if available + if (definitionObj.has(Constant.DESCRIPTION)) { + property.setDescription(definitionObj.get(Constant.DESCRIPTION).getAsString()); + } + + // Extract nested properties from the definition + if (definitionObj.has(Constant.PROPERTIES)) { + List nestedProps = extractProperties( + definitionObj.getAsJsonObject(Constant.PROPERTIES), + definitions, + new HashSet<>(processedRefs) + ); + property.setProperties(nestedProps); + } + + propertiesList.add(property); + } + } + continue; + } + } + + // Process regular properties (non-reference) + JsonElement propDescriptionObj = propertyObject.get(Constant.DESCRIPTION); + String propDescription = propDescriptionObj != null ? + propDescriptionObj.getAsString() : StringUtils.EMPTY; + + Property property = new Property(key, StringUtils.EMPTY, propDescription); + + if (propertyObject.has(Constant.PROPERTIES)) { + List properties = extractProperties( + propertyObject.getAsJsonObject(Constant.PROPERTIES), + definitions, + new HashSet<>(processedRefs) + ); + property.setProperties(properties); + } + + propertiesList.add(property); + } + } + return propertiesList; + } +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java index cd1393f8c..4967acf84 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java @@ -27,6 +27,8 @@ import com.google.gson.JsonParser; import org.apache.commons.lang3.StringUtils; import org.eclipse.lemminx.customservice.synapse.connectors.UiSchemaFlattener; +import org.eclipse.lemminx.customservice.synapse.connectors.entity.ConnectorVariableSchemaUtils; +import org.eclipse.lemminx.customservice.synapse.mediator.tryout.pojo.Property; import org.eclipse.lemminx.customservice.synapse.parser.Node; import org.eclipse.lemminx.customservice.synapse.parser.OverviewPageDetailsResponse; import org.eclipse.lemminx.customservice.synapse.syntaxTree.SyntaxTreeGenerator; @@ -57,13 +59,17 @@ public class InboundConnectorHolder { private static final Logger LOGGER = Logger.getLogger(InboundConnectorHolder.class.getName()); + public static final String INPUT_SCHEMA_FILE_SUFFIX = "_inputschema.json"; + private static InboundConnectorHolder instance; private String projectId; private String projectPath; private String tempFolderPath; // map - private HashMap connectorIdMap; + private final HashMap connectorIdMap; // map - private HashMap inboundConnectors; + private final HashMap inboundConnectors; + // map + private final HashMap inboundConnectorInputSchemas; private Map localInboundConnectors; private JsonObject inboundConnectorListJson; private String projectRuntimeVersion; @@ -78,6 +84,15 @@ public InboundConnectorHolder() { this.inboundConnectors = new HashMap<>(); this.connectorIdMap = new HashMap<>(); + this.inboundConnectorInputSchemas = new HashMap<>(); + } + + public static InboundConnectorHolder getInstance() { + + if (instance == null) { + throw new IllegalStateException("InboundConnectorHolder has not yet been initialized"); + } + return instance; } public void init(String projectPath, String projectRuntimeVersion) { @@ -107,6 +122,7 @@ public void init(String projectPath, String projectRuntimeVersion) { getCustomInboundConnectors(); loadInboundConnectors(); this.localInboundEndpointsListForCopilot = generateInboundConnectorArray(); + instance = this; } private void loadInboundConnectors() { @@ -154,23 +170,30 @@ private boolean importInboundConnectorsFromDirectory(File extractFolder) { Utils.extractZip(zip, extractToFolder); String schema = Utils.readFile(extractToFolder.toPath().resolve(Constant.RESOURCES) .resolve(Constant.UI_SCHEMA_JSON).toFile()); - if (saveInboundConnector(Utils.getJsonObject(schema).get(Constant.NAME).getAsString(), schema)) { - JsonObject connectorSchema = Utils.getJsonObject(schema); - JsonArray connectorArray = this.inboundConnectorListJson.getAsJsonArray(Constant.INBOUND_CONNECTOR_DATA); - String connectorId = connectorSchema.get(Constant.ID) != null ? - connectorSchema.get(Constant.ID).getAsString() : StringUtils.EMPTY; - if (!isConnectorAlreadyListed(connectorArray, connectorId)) { - JsonObject newConnector = new JsonObject(); - newConnector.addProperty(Constant.NAME, connectorSchema.get(Constant.TITLE) != null ? - connectorSchema.get(Constant.TITLE).getAsString() : StringUtils.EMPTY); - newConnector.addProperty(Constant.ID, connectorId); - newConnector.addProperty(Constant.DESCRIPTION, connectorSchema.get(Constant.DESCRIPTION) != null ? - connectorSchema.get(Constant.DESCRIPTION).getAsString() : StringUtils.EMPTY); - newConnector.addProperty(Constant.TYPE, Constant.INBOUND_DASH_ENDPOINT); - connectorArray.add(newConnector); - } - isInboundConnectorAdded = true; - } + JsonObject connectorSchema = Utils.getJsonObject(schema); + if (saveInboundConnector(connectorSchema.get(Constant.NAME).getAsString(), schema)) { + File inputSchemaFile = extractToFolder.toPath().resolve(Constant.RESOURCES) + .resolve(Constant.INPUT_SCHEMA_JSON).toFile(); + if (inputSchemaFile.exists()) { + saveInboundConnectorInputSchema(connectorSchema.get(Constant.NAME).getAsString(), + connectorSchema.has(Constant.ID) ? connectorSchema.get(Constant.ID).getAsString() : null, + Utils.readFile(inputSchemaFile)); + } + JsonArray connectorArray = this.inboundConnectorListJson.getAsJsonArray(Constant.INBOUND_CONNECTOR_DATA); + String connectorId = connectorSchema.get(Constant.ID) != null ? + connectorSchema.get(Constant.ID).getAsString() : StringUtils.EMPTY; + if (!isConnectorAlreadyListed(connectorArray, connectorId)) { + JsonObject newConnector = new JsonObject(); + newConnector.addProperty(Constant.NAME, connectorSchema.get(Constant.TITLE) != null ? + connectorSchema.get(Constant.TITLE).getAsString() : StringUtils.EMPTY); + newConnector.addProperty(Constant.ID, connectorId); + newConnector.addProperty(Constant.DESCRIPTION, connectorSchema.get(Constant.DESCRIPTION) != null ? + connectorSchema.get(Constant.DESCRIPTION).getAsString() : StringUtils.EMPTY); + newConnector.addProperty(Constant.TYPE, Constant.INBOUND_DASH_ENDPOINT); + connectorArray.add(newConnector); + } + isInboundConnectorAdded = true; + } } catch (Exception e) { LOGGER.log(Level.SEVERE, "Failed to import custom inbound-connector:" + zipName, e); } @@ -217,8 +240,13 @@ private List getInboundConnectorZips(File extractFolder) { private void loadInboundConnector(File file) { + String fileName = file.getName(); + // Input schema files are loaded together with their uischema, skip them here. + if (fileName.endsWith(INPUT_SCHEMA_FILE_SUFFIX)) { + return; + } try { - String connectorName = file.getName().replace(".json", ""); + String connectorName = fileName.replace(Constant.JSON_FILE_EXT, StringUtils.EMPTY); String uiSchema = Utils.readFile(file); JsonObject inboundConnector = Utils.getJsonObject(uiSchema); if (inboundConnector == null || !inboundConnector.has(Constant.ID)) { @@ -227,6 +255,10 @@ private void loadInboundConnector(File file) { String id = inboundConnector.get(Constant.ID).getAsString(); connectorIdMap.put(connectorName, id); inboundConnectors.put(id, file.getAbsolutePath()); + File inputSchemaFile = Path.of(tempFolderPath, connectorName + INPUT_SCHEMA_FILE_SUFFIX).toFile(); + if (inputSchemaFile.exists()) { + inboundConnectorInputSchemas.put(id, inputSchemaFile.getAbsolutePath()); + } } catch (IOException e) { LOGGER.log(Level.SEVERE, "Error occurred while loading inbound connector schema from file", e); } @@ -249,6 +281,47 @@ public Boolean saveInboundConnector(String connectorName, String uiSchema) { return false; } + /** + * Persists the input schema shipped by an inbound connector (its + * {@code resources/inputschema.json}) next to the uischema in the per-project + * temp folder and registers it against the connector id. + * + * @param connectorName the connector name (uischema {@code name}) + * @param id the connector id (uischema {@code id}) + * @param inputSchema the raw input schema JSON + */ + public void saveInboundConnectorInputSchema(String connectorName, String id, String inputSchema) { + + if (StringUtils.isEmpty(id) || StringUtils.isEmpty(inputSchema)) { + return; + } + Path filePath = Path.of(tempFolderPath, connectorName + INPUT_SCHEMA_FILE_SUFFIX); + if (saveToFile(filePath.toFile(), inputSchema)) { + inboundConnectorInputSchemas.put(id, filePath.toString()); + } + } + + /** + * Returns the input schema of the inbound connector with the given id as a + * {@link Property} tree (built the same way as regular connector output schemas), + * or {@code null} if no input schema is registered. + */ + public Property getInboundConnectorInputSchema(String id) { + + String inputSchemaPath = inboundConnectorInputSchemas.get(id); + if (inputSchemaPath == null) { + return null; + } + try { + String inputSchema = Utils.readFile(new File(inputSchemaPath)); + JsonObject inputSchemaJson = Utils.getJsonObject(inputSchema); + return ConnectorVariableSchemaUtils.buildSchemaProperty(inputSchemaJson); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Error occurred while reading inbound connector input schema from file", e); + } + return null; + } + public InboundConnectorResponse getInboundConnectorSchema(File inboundEPFile) { try { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/ServerLessTryoutHandler.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/ServerLessTryoutHandler.java index be5a30302..bacfc135b 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/ServerLessTryoutHandler.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/ServerLessTryoutHandler.java @@ -15,6 +15,7 @@ package org.eclipse.lemminx.customservice.synapse.mediator.schema.generate; import com.google.gson.JsonPrimitive; +import org.eclipse.lemminx.customservice.synapse.InvalidConfigurationException; import org.eclipse.lemminx.customservice.synapse.mediator.TryOutUtils; import org.eclipse.lemminx.customservice.synapse.mediator.schema.generate.visitor.SchemaVisitor; import org.eclipse.lemminx.customservice.synapse.mediator.schema.generate.visitor.SchemaVisitorFactory; @@ -23,6 +24,9 @@ import org.eclipse.lemminx.customservice.synapse.mediator.tryout.pojo.MediatorTryoutRequest; import org.eclipse.lemminx.customservice.synapse.syntaxTree.SyntaxTreeGenerator; import org.eclipse.lemminx.customservice.synapse.syntaxTree.pojo.STNode; +import org.eclipse.lemminx.customservice.synapse.syntaxTree.pojo.inbound.InboundEndpoint; +import org.eclipse.lemminx.customservice.synapse.utils.ConfigFinder; +import org.eclipse.lemminx.customservice.synapse.utils.Constant; import org.eclipse.lemminx.customservice.synapse.utils.Utils; import org.eclipse.lemminx.dom.DOMDocument; @@ -44,27 +48,48 @@ public ServerLessTryoutHandler(String projectUri) { public MediatorTryoutInfo handle(MediatorTryoutRequest request) { try { - String filePath = request.getFile(); + String visitFilePath = request.getFile(); if (request.getEdits() != null) { + STNode node = getSTNode(request.getFile()); String documentUri = request.getFile(); + String editFilePath = TEMP_FOLDER.resolve(TEMP_FILE_NAME).toString(); + if (node instanceof InboundEndpoint) { + String sequence = ((InboundEndpoint) node).getSequence(); + if (sequence != null) { + String seqPath = ConfigFinder.findEsbComponentPath(sequence, Constant.SEQUENCES, projectUri); + if (seqPath != null) { + documentUri = seqPath; + } + } + } else { + visitFilePath = editFilePath; + } Utils.copyFile(documentUri, TEMP_FOLDER.toString(), TEMP_FILE_NAME); - filePath = TEMP_FOLDER.resolve(TEMP_FILE_NAME).toString(); - TryOutUtils.doEdits(request.getEdits(), Path.of(filePath)); - request = new MediatorTryoutRequest(filePath, request.getLine(), request.getColumn() + 1, + TryOutUtils.doEdits(request.getEdits(), Path.of(editFilePath)); + request = new MediatorTryoutRequest(editFilePath, request.getLine(), request.getColumn() + 1, request.getInputPayload(), null); } - DOMDocument domDocument = Utils.getDOMDocument(new File(filePath)); + DOMDocument domDocument = Utils.getDOMDocument(new File(visitFilePath)); STNode node = SyntaxTreeGenerator.buildTree(domDocument.getDocumentElement()); MediatorTryoutInfo mediatorTryoutInfo = createInitialMediatorTryoutInfo(request); if (node != null) { visitNode(node, request, mediatorTryoutInfo); } return mediatorTryoutInfo; - } catch (IOException e) { + } catch (IOException | InvalidConfigurationException e) { return new MediatorTryoutInfo(e.getMessage()); } } + private STNode getSTNode(String filePath) throws IOException, InvalidConfigurationException { + + if (filePath == null) { + throw new IllegalArgumentException("FilePath is null"); + } + DOMDocument domDocument = Utils.getDOMDocument(new File(filePath)); + return SyntaxTreeGenerator.buildTree(domDocument.getDocumentElement()); + } + private MediatorTryoutInfo createInitialMediatorTryoutInfo(MediatorTryoutRequest request) { MediatorInfo mediatorInfo = new MediatorInfo(); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/visitor/InboundEndpointVisitor.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/visitor/InboundEndpointVisitor.java index f38835174..80f2afd1c 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/visitor/InboundEndpointVisitor.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/visitor/InboundEndpointVisitor.java @@ -15,10 +15,15 @@ package org.eclipse.lemminx.customservice.synapse.mediator.schema.generate.visitor; import org.apache.commons.lang3.StringUtils; +import org.eclipse.lemminx.customservice.synapse.inbound.conector.InboundConnectorHolder; import org.eclipse.lemminx.customservice.synapse.mediator.tryout.pojo.MediatorTryoutInfo; import org.eclipse.lemminx.customservice.synapse.mediator.tryout.pojo.MediatorTryoutRequest; +import org.eclipse.lemminx.customservice.synapse.mediator.tryout.pojo.Property; import org.eclipse.lemminx.customservice.synapse.syntaxTree.pojo.STNode; import org.eclipse.lemminx.customservice.synapse.syntaxTree.pojo.inbound.InboundEndpoint; +import org.eclipse.lemminx.customservice.synapse.syntaxTree.pojo.inbound.InboundEndpointParameters; +import org.eclipse.lemminx.customservice.synapse.syntaxTree.pojo.misc.common.Parameter; +import org.eclipse.lemminx.customservice.synapse.utils.Constant; import java.io.IOException; import java.util.logging.Level; @@ -37,14 +42,68 @@ public InboundEndpointVisitor(String projectPath) { @Override public void visit(STNode node, MediatorTryoutInfo info, MediatorTryoutRequest request) { - String sequence = ((InboundEndpoint) node).getSequence(); + InboundEndpoint inboundEndpoint = (InboundEndpoint) node; + String sequence = inboundEndpoint.getSequence(); if (StringUtils.isEmpty(sequence)) { return; } + + loadInboundVariable(inboundEndpoint, info); + try { Utils.visitSequenceByKey(sequence, projectPath, info, request); } catch (IOException e) { LOGGER.log(Level.SEVERE, String.format("Error occurred while visiting the sequence: %s", sequence), e); } } + + /** + * If the inbound endpoint declares an {@code inboundVariableName} parameter, load the + * input schema of the corresponding inbound connector and seed a variable with that + * name into the tryout info. This mirrors how connector mediators seed their response + * variable, making the incoming message structure available to the dispatched sequence. + */ + private void loadInboundVariable(InboundEndpoint inboundEndpoint, MediatorTryoutInfo info) { + + String inboundVariableName = getParameterValue(inboundEndpoint, Constant.INBOUND_VARIABLE_NAME); + if (StringUtils.isEmpty(inboundVariableName)) { + return; + } + InboundConnectorHolder holder = InboundConnectorHolder.getInstance(); + if (holder == null) { + return; + } + String id = inboundEndpoint.getProtocol() != null ? inboundEndpoint.getProtocol() + : inboundEndpoint.getClazz(); + if (id == null) { + return; + } + Property inputSchema = holder.getInboundConnectorInputSchema(id); + if (inputSchema == null) { + return; + } + inputSchema.setKey(inboundVariableName); + // Added to the output; Utils.visitMediators calls replaceInputWithOutput() before + // visiting the sequence, so this becomes the sequence's input state. + info.addOutputVariable(inputSchema); + } + + private String getParameterValue(InboundEndpoint inboundEndpoint, String parameterName) { + + InboundEndpointParameters[] parametersList = inboundEndpoint.getParameters(); + if (parametersList == null) { + return null; + } + for (InboundEndpointParameters parameters : parametersList) { + if (parameters == null || parameters.getParameter() == null) { + continue; + } + for (Parameter parameter : parameters.getParameter()) { + if (parameter != null && parameterName.equals(parameter.getName())) { + return parameter.getContent(); + } + } + } + return null; + } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Constant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Constant.java index d28a68aca..05f3a10ed 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Constant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/utils/Constant.java @@ -596,6 +596,8 @@ public class Constant { public static final String BALLERINA_MODULE_PATH = "ballerinaModulePath"; public static final String BALLERINA = "ballerina"; public static final String UI_SCHEMA_JSON = "uischema.json"; + public static final String INPUT_SCHEMA_JSON = "inputschema.json"; + public static final String INBOUND_VARIABLE_NAME = "inboundVariableName"; public static final String JSON_FILE_EXT = ".json"; public static final String INBOUND_CONNECTOR_PREFIX = "mi-inbound-"; public static final String INBOUND_CONNECTORS = "inbound.connectors"; From d84648b34443ad448cfcec9202fe46709ebbddd1 Mon Sep 17 00:00:00 2001 From: Thuvarakan Sritharan Date: Mon, 20 Jul 2026 13:52:45 +0530 Subject: [PATCH 31/34] Fix coderabbit comments --- .../connectors/entity/ConnectorVariableSchemaUtils.java | 9 +++++---- .../schema/generate/visitor/InboundEndpointVisitor.java | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorVariableSchemaUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorVariableSchemaUtils.java index a36570d04..d507c95a2 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorVariableSchemaUtils.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/connectors/entity/ConnectorVariableSchemaUtils.java @@ -74,10 +74,9 @@ private static List extractProperties(JsonObject propertiesObject, Jso if (ref.startsWith(Constant.SCHEMA_DEFINITION) && definitions != null) { String definitionKey = ref.substring(Constant.SCHEMA_DEFINITION.length()); - // Prevent circular references + // Prevent circular references. Keep processedRefs path-scoped: do not mutate the + // shared set, so sibling properties can reuse the same definition. if (!processedRefs.contains(definitionKey)) { - processedRefs.add(definitionKey); - JsonObject definitionObj = definitions.getAsJsonObject(definitionKey); if (definitionObj != null) { // Create property with the key from the property name @@ -90,10 +89,12 @@ private static List extractProperties(JsonObject propertiesObject, Jso // Extract nested properties from the definition if (definitionObj.has(Constant.PROPERTIES)) { + Set nestedRefs = new HashSet<>(processedRefs); + nestedRefs.add(definitionKey); List nestedProps = extractProperties( definitionObj.getAsJsonObject(Constant.PROPERTIES), definitions, - new HashSet<>(processedRefs) + nestedRefs ); property.setProperties(nestedProps); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/visitor/InboundEndpointVisitor.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/visitor/InboundEndpointVisitor.java index 80f2afd1c..158ac5ccf 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/visitor/InboundEndpointVisitor.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/visitor/InboundEndpointVisitor.java @@ -69,8 +69,11 @@ private void loadInboundVariable(InboundEndpoint inboundEndpoint, MediatorTryout if (StringUtils.isEmpty(inboundVariableName)) { return; } - InboundConnectorHolder holder = InboundConnectorHolder.getInstance(); - if (holder == null) { + InboundConnectorHolder holder; + try { + holder = InboundConnectorHolder.getInstance(); + } catch (IllegalStateException e) { + LOGGER.severe("Inbound connector holder is not initialized"); return; } String id = inboundEndpoint.getProtocol() != null ? inboundEndpoint.getProtocol() @@ -83,8 +86,6 @@ private void loadInboundVariable(InboundEndpoint inboundEndpoint, MediatorTryout return; } inputSchema.setKey(inboundVariableName); - // Added to the output; Utils.visitMediators calls replaceInputWithOutput() before - // visiting the sequence, so this becomes the sequence's input state. info.addOutputVariable(inputSchema); } From e0b30a5c2c2a8605b78739650b7e0cf5abe578c9 Mon Sep 17 00:00:00 2001 From: Thuvarakan Sritharan Date: Tue, 21 Jul 2026 09:52:41 +0530 Subject: [PATCH 32/34] Replace string null checks with StringUtils --- .../synapse/inbound/conector/InboundConnectorHolder.java | 2 +- .../mediator/schema/generate/ServerLessTryoutHandler.java | 7 ++++--- .../schema/generate/visitor/InboundEndpointVisitor.java | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java index 4967acf84..860d70580 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/inbound/conector/InboundConnectorHolder.java @@ -309,7 +309,7 @@ public void saveInboundConnectorInputSchema(String connectorName, String id, Str public Property getInboundConnectorInputSchema(String id) { String inputSchemaPath = inboundConnectorInputSchemas.get(id); - if (inputSchemaPath == null) { + if (StringUtils.isEmpty(inputSchemaPath)) { return null; } try { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/ServerLessTryoutHandler.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/ServerLessTryoutHandler.java index bacfc135b..470007ab7 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/ServerLessTryoutHandler.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/ServerLessTryoutHandler.java @@ -15,6 +15,7 @@ package org.eclipse.lemminx.customservice.synapse.mediator.schema.generate; import com.google.gson.JsonPrimitive; +import org.apache.commons.lang3.StringUtils; import org.eclipse.lemminx.customservice.synapse.InvalidConfigurationException; import org.eclipse.lemminx.customservice.synapse.mediator.TryOutUtils; import org.eclipse.lemminx.customservice.synapse.mediator.schema.generate.visitor.SchemaVisitor; @@ -55,9 +56,9 @@ public MediatorTryoutInfo handle(MediatorTryoutRequest request) { String editFilePath = TEMP_FOLDER.resolve(TEMP_FILE_NAME).toString(); if (node instanceof InboundEndpoint) { String sequence = ((InboundEndpoint) node).getSequence(); - if (sequence != null) { + if (StringUtils.isNotEmpty(sequence)) { String seqPath = ConfigFinder.findEsbComponentPath(sequence, Constant.SEQUENCES, projectUri); - if (seqPath != null) { + if (StringUtils.isNotEmpty(seqPath)) { documentUri = seqPath; } } @@ -83,7 +84,7 @@ public MediatorTryoutInfo handle(MediatorTryoutRequest request) { private STNode getSTNode(String filePath) throws IOException, InvalidConfigurationException { - if (filePath == null) { + if (StringUtils.isEmpty(filePath)) { throw new IllegalArgumentException("FilePath is null"); } DOMDocument domDocument = Utils.getDOMDocument(new File(filePath)); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/visitor/InboundEndpointVisitor.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/visitor/InboundEndpointVisitor.java index 158ac5ccf..69dff96f6 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/visitor/InboundEndpointVisitor.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/mediator/schema/generate/visitor/InboundEndpointVisitor.java @@ -78,7 +78,7 @@ private void loadInboundVariable(InboundEndpoint inboundEndpoint, MediatorTryout } String id = inboundEndpoint.getProtocol() != null ? inboundEndpoint.getProtocol() : inboundEndpoint.getClazz(); - if (id == null) { + if (StringUtils.isEmpty(id)) { return; } Property inputSchema = holder.getInboundConnectorInputSchema(id); From 9c755684c4b04ccfccf79d1c5b65151403d65f6b Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Tue, 21 Jul 2026 20:05:31 +0530 Subject: [PATCH 33/34] Update OpenAPI spec generation process --- .../synapse/api/generator/APIGenerator.java | 193 +++++++++++++----- .../generator/GenericApiObjectDefinition.java | 41 +--- .../synapse/api/generator/RestApiAdmin.java | 27 ++- .../api/generator/SwaggerConstants.java | 5 + .../api/generator/pojo/GenerateAPIParam.java | 3 + 5 files changed, 175 insertions(+), 94 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/APIGenerator.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/APIGenerator.java index 4308d341e..2205cee4d 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/APIGenerator.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/APIGenerator.java @@ -16,6 +16,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import org.apache.commons.lang3.StringUtils; import org.eclipse.lemminx.customservice.synapse.syntaxTree.pojo.CommentMediator; import org.eclipse.lemminx.customservice.synapse.syntaxTree.pojo.api.API; import org.eclipse.lemminx.customservice.synapse.syntaxTree.pojo.api.APIResource; @@ -51,12 +52,99 @@ public class APIGenerator { private JsonObject swaggerJson; private String publishSwaggerPath; + private String context; + private String version; + private String versionType; private static final Logger log = Logger.getLogger(APIGenerator.class.getName()); public APIGenerator(JsonObject swaggerJson, String publishSwaggerPath) { + this(swaggerJson, publishSwaggerPath, null, null); + } + + public APIGenerator(JsonObject swaggerJson, String publishSwaggerPath, String context) { + + this(swaggerJson, publishSwaggerPath, context, null); + } + + public APIGenerator(JsonObject swaggerJson, String publishSwaggerPath, String context, String version) { + + this(swaggerJson, publishSwaggerPath, context, version, null); + } + + public APIGenerator(JsonObject swaggerJson, String publishSwaggerPath, String context, String version, + String versionType) { + this.swaggerJson = swaggerJson; this.publishSwaggerPath = publishSwaggerPath; + this.context = context; + this.version = version; + this.versionType = versionType; + } + + /** + * Normalize the optionally provided context so that it starts with '/' and has no trailing '/'. + * + * @return the normalized context, or null when no usable context was provided + */ + private String getNormalizedProvidedContext() { + + if (StringUtils.isBlank(context)) { + return null; + } + String resolved = context.trim(); + // remove trailing '/' + if (resolved.length() > 1 && resolved.endsWith("/")) { + resolved = resolved.substring(0, resolved.length() - 1); + } + // add leading '/' if not present + if (!resolved.startsWith("/")) { + resolved = "/" + resolved; + } + if (StringUtils.isEmpty(resolved) || "/".equals(resolved)) { + return null; + } + return resolved; + } + + /** + * Normalize the optionally provided version. + * + * @return the trimmed version, or null when no usable version was provided + */ + private String getNormalizedProvidedVersion() { + + if (StringUtils.isBlank(version)) { + return null; + } + return version.trim(); + } + + /** + * Normalize the optionally provided version type. + * + * @return the matching {@link ApiVersionType}, or null when no valid version type was provided + */ + private ApiVersionType getNormalizedProvidedVersionType() { + + if (StringUtils.isBlank(versionType)) { + return null; + } + try { + return ApiVersionType.valueOf(versionType.trim().toLowerCase()); + } catch (IllegalArgumentException e) { + return null; + } + } + + /** + * Check whether the caller explicitly asked for the API to be unversioned. + * + * @return true when the provided version type is "none" + */ + private boolean isProvidedVersionTypeNone() { + + return StringUtils.equalsIgnoreCase(SwaggerConstants.VERSION_TYPE_NONE, StringUtils.trim(versionType)); } public String generateSynapseAPIXml() { @@ -81,40 +169,43 @@ public String generateSynapseAPIXml() { */ public API generateSynapseAPI() throws APIGenException { - String apiContext; - if (swaggerJson.get(SwaggerConstants.SERVERS) == null || - swaggerJson.get(SwaggerConstants.SERVERS).getAsJsonArray().size() == 0) { - apiContext = SwaggerConstants.DEFAULT_CONTEXT; - } else { - JsonObject firstServer = swaggerJson.getAsJsonArray(SwaggerConstants.SERVERS).get(0).getAsJsonObject(); - // get the first path in the servers section - String serversString = firstServer.get(SwaggerConstants.URL).getAsString(); - if (serversString.contains("{") && serversString.contains("}")) { - // url is templated, need to resolve - if (firstServer.has(SwaggerConstants.VARIABLES)) { - JsonObject variables = firstServer.get(SwaggerConstants.VARIABLES).getAsJsonObject(); - serversString = replaceTemplates(serversString, variables); - } else { - throw new APIGenException("Server url is templated, but variables cannot be found"); - } - } - try { - URL url = new URL(serversString); - apiContext = url.getPath(); - } catch (MalformedURLException e) { - // url can be relative the place where the swagger is hosted. - apiContext = serversString; - } - if (apiContext.isEmpty() || "/".equals(apiContext)) { + // A provided context always takes priority over the one derived from the swagger servers section + String apiContext = getNormalizedProvidedContext(); + if (apiContext == null) { + if (swaggerJson.get(SwaggerConstants.SERVERS) == null || + swaggerJson.get(SwaggerConstants.SERVERS).getAsJsonArray().size() == 0) { apiContext = SwaggerConstants.DEFAULT_CONTEXT; - } - //cleanup context : remove ending '/' - if (apiContext.lastIndexOf('/') == (apiContext.length() - 1)) { - apiContext = apiContext.substring(0, apiContext.length() - 1); - } - // add leading / if not exists - if (!apiContext.startsWith("/")) { - apiContext = "/" + apiContext; + } else { + JsonObject firstServer = swaggerJson.getAsJsonArray(SwaggerConstants.SERVERS).get(0).getAsJsonObject(); + // get the first path in the servers section + String serversString = firstServer.get(SwaggerConstants.URL).getAsString(); + if (serversString.contains("{") && serversString.contains("}")) { + // url is templated, need to resolve + if (firstServer.has(SwaggerConstants.VARIABLES)) { + JsonObject variables = firstServer.get(SwaggerConstants.VARIABLES).getAsJsonObject(); + serversString = replaceTemplates(serversString, variables); + } else { + throw new APIGenException("Server url is templated, but variables cannot be found"); + } + } + try { + URL url = new URL(serversString); + apiContext = url.getPath(); + } catch (MalformedURLException e) { + // url can be relative the place where the swagger is hosted. + apiContext = serversString; + } + if (StringUtils.isEmpty(apiContext) || "/".equals(apiContext)) { + apiContext = SwaggerConstants.DEFAULT_CONTEXT; + } + //cleanup context : remove ending '/' + if (apiContext.lastIndexOf('/') == (apiContext.length() - 1)) { + apiContext = apiContext.substring(0, apiContext.length() - 1); + } + // add leading / if not exists + if (!apiContext.startsWith("/")) { + apiContext = "/" + apiContext; + } } } @@ -129,23 +220,29 @@ public API generateSynapseAPI() throws APIGenException { String apiName = swaggerInfo.get(SwaggerConstants.TITLE).getAsString(); - // Extract version information - ApiVersionType versionType = null; - String version = ""; - JsonElement swaggerVersionElement = swaggerInfo.get(SwaggerConstants.VERSION); - if (swaggerVersionElement != null && swaggerVersionElement.isJsonPrimitive() && - swaggerVersionElement.getAsJsonPrimitive().isString()) { - version = swaggerVersionElement.getAsString(); - if (apiContext.endsWith(version)) { - // If the base path ends with the version, then it will be considered as version-type=url - versionType = ApiVersionType.url; - //cleanup api context path : remove version from base path - apiContext = apiContext.substring(0, apiContext.length() - version.length() - 1); - } else { - // otherwise context based version strategy - versionType = ApiVersionType.context; + // Extract version information. A user provided version always takes priority over the values derived from the swagger info section. + String providedVersion = getNormalizedProvidedVersion(); + ApiVersionType versionType = getNormalizedProvidedVersionType(); + boolean versionTypeNone = isProvidedVersionTypeNone(); + String swaggerVersion = null; + if (!versionTypeNone) { + JsonElement swaggerVersionElement = swaggerInfo.get(SwaggerConstants.VERSION); + if (swaggerVersionElement != null && swaggerVersionElement.isJsonPrimitive() && + swaggerVersionElement.getAsJsonPrimitive().isString()) { + swaggerVersion = swaggerVersionElement.getAsString(); } } + String version = versionTypeNone ? StringUtils.EMPTY : + (StringUtils.isNotBlank(providedVersion) ? providedVersion : + (StringUtils.isNotBlank(swaggerVersion) ? swaggerVersion : StringUtils.EMPTY)); + + if (ApiVersionType.url.equals(versionType)) { + String versionInPath = StringUtils.isNotBlank(swaggerVersion) ? swaggerVersion : version; + if (StringUtils.isNotEmpty(versionInPath) && apiContext.endsWith(versionInPath)) { + // remove version from base path + apiContext = apiContext.substring(0, apiContext.length() - versionInPath.length() - 1); + } + } // Create API API genAPI = new API(); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/GenericApiObjectDefinition.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/GenericApiObjectDefinition.java index e722a2b23..a1d624fdd 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/GenericApiObjectDefinition.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/GenericApiObjectDefinition.java @@ -116,11 +116,10 @@ private static Object[] getResourceParameters(APIResource resource, String metho if (resource.getUrlMapping() != null) { uri = StringEscapeUtils.unescapeHtml4(resource.getUrlMapping()); - generateParameterList(parameterList, uri, false); } else { uri = StringEscapeUtils.unescapeHtml4(resource.getUriTemplate()); - generateParameterList(parameterList, uri, true); } + generateParameterList(parameterList, uri); if (log.isLoggable(Level.FINE)) { log.info("Parameters processed for the URI + " + uri + " size " + parameterList.size()); } @@ -131,31 +130,16 @@ private static Object[] getResourceParameters(APIResource resource, String metho } /** - * Generate URI and Path parameters for the given URI. + * Generate Path parameters for the given URI. * - * @param parameterList List of maps to be populated with parameters - * @param uriString URI string to be used to extract parameters - * @param generateBothTypes Indicates whether to consider both query and uri parameters. True if both to be - * considered. + * @param parameterList List of maps to be populated with parameters + * @param uriString URI string to be used to extract parameters */ - private static void generateParameterList(ArrayList> parameterList, String uriString, boolean - generateBothTypes) { + private static void generateParameterList(ArrayList> parameterList, String uriString) { if (uriString == null) { return; } - if (generateBothTypes) { - String[] params = getQueryStringFromUrl(uriString).split("&"); - for (String parameter : params) { - if (parameter != null) { - int pos = parameter.indexOf('='); - if (pos > 0) { - parameterList.add(getParametersMap(parameter.substring(0, pos), - SwaggerConstants.PARAMETER_IN_QUERY)); - } - } - } - } Matcher matcher = SwaggerConstants.PATH_PARAMETER_PATTERN.matcher(getPathFromUrl(uriString)); while (matcher.find()) { parameterList.add(getParametersMap(matcher.group(1), SwaggerConstants.PARAMETER_IN_PATH)); @@ -237,21 +221,6 @@ private static String getPathFromUrl(String uri) { return uri; } - /** - * Get query parameter portion from the URI. - * - * @param uri String URI to be analysed - * @return String containing the URI parameter portion of the URI - */ - private static String getQueryStringFromUrl(String uri) { - - int pos = uri.indexOf("?"); - if (pos > 0) { - return uri.substring(pos + 1); - } - return ""; - } - /** * A util method to convert from YAML to JSON. * diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/RestApiAdmin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/RestApiAdmin.java index 24c096083..4528d945d 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/RestApiAdmin.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/RestApiAdmin.java @@ -103,8 +103,11 @@ public GenerateAPIResponse createAPI(GenerateAPIParam param) { String endpoint = param.wsdlEndpointName; String publishSwaggerPath = param.publishSwaggerPath; String mode = param.mode; + String context = param.context; + String version = param.version; + String versionType = param.versionType; - return createAPI(apiName, sourcePath, endpoint, publishSwaggerPath, mode); + return createAPI(apiName, sourcePath, endpoint, publishSwaggerPath, mode, context, version, versionType); } /** @@ -115,14 +118,18 @@ public GenerateAPIResponse createAPI(GenerateAPIParam param) { * @param endpoint WSDL endpoint * @param publishSwaggerPath Swagger publish path * @param mode Mode of the API creation (Swagger / WSDL) + * @param context API context, takes priority over the context derived from the swagger + * @param version API version, takes priority over the version derived from the swagger + * @param versionType API version type (url / context), takes priority over the version type + * derived from the swagger * @return */ public GenerateAPIResponse createAPI(String apiName, String sourcePath, String endpoint, String publishSwaggerPath - , String mode) { + , String mode, String context, String version, String versionType) { if (CREATE_FROM_SWAGGER.equalsIgnoreCase(mode)) { try { - return createAPIFromSwagger(apiName, sourcePath, publishSwaggerPath); + return createAPIFromSwagger(apiName, sourcePath, publishSwaggerPath, context, version, versionType); } catch (JsonProcessingException e) { LOGGER.log(Level.SEVERE, "Exception occurred while creating API from Swagger", e); return new GenerateAPIResponse(null, null, @@ -130,7 +137,7 @@ public GenerateAPIResponse createAPI(String apiName, String sourcePath, String e } } else if (CREATE_FROM_WSDL.equalsIgnoreCase(mode)) { try { - return createAPIFromWSDL(apiName, endpoint, sourcePath); + return createAPIFromWSDL(apiName, endpoint, sourcePath, context, version, versionType); } catch (SOAPToRESTException e) { LOGGER.log(Level.SEVERE, "Exception occurred while converting SOAP to REST", e); return new GenerateAPIResponse(null, null, @@ -147,26 +154,26 @@ public GenerateAPIResponse createAPI(String apiName, String sourcePath, String e return null; } - private GenerateAPIResponse createAPIFromSwagger(String apiName, String swaggerPath, String publishSwaggerPath) throws JsonProcessingException { + private GenerateAPIResponse createAPIFromSwagger(String apiName, String swaggerPath, String publishSwaggerPath, String context, String version, String versionType) throws JsonProcessingException { File swaggerFile = new File(swaggerPath); String swaggerYaml = getSwaggerFileAsYAML(swaggerFile, apiName); - String api = getSynapseAPIFromSwagger(swaggerYaml, publishSwaggerPath); + String api = getSynapseAPIFromSwagger(swaggerYaml, publishSwaggerPath, context, version, versionType); return new GenerateAPIResponse(api); } - private String getSynapseAPIFromSwagger(String swaggerYaml, String publishSwaggerPath) throws JsonProcessingException { + private String getSynapseAPIFromSwagger(String swaggerYaml, String publishSwaggerPath, String context, String version, String versionType) throws JsonProcessingException { String swaggerString = GenericApiObjectDefinition.convertYamlToJson(swaggerYaml); JsonParser jsonParser = new JsonParser(); JsonElement swaggerJson = jsonParser.parse(swaggerString); - APIGenerator apiGenerator = new APIGenerator(swaggerJson.getAsJsonObject(), publishSwaggerPath); + APIGenerator apiGenerator = new APIGenerator(swaggerJson.getAsJsonObject(), publishSwaggerPath, context, version, versionType); String apiXml = apiGenerator.generateSynapseAPIXml(); return apiXml; } - private GenerateAPIResponse createAPIFromWSDL(String apiName, String endpoint, String sourcePath) throws SOAPToRESTException, MalformedURLException, JsonProcessingException, TransformerException { + private GenerateAPIResponse createAPIFromWSDL(String apiName, String endpoint, String sourcePath, String context, String version, String versionType) throws SOAPToRESTException, MalformedURLException, JsonProcessingException, TransformerException { URL url = new URL(sourcePath); SOAPtoRESTConversionData soaPtoRESTConversionData = SOAPToRESTConverter.getSOAPtoRESTConversionData(url, @@ -191,7 +198,7 @@ private GenerateAPIResponse createAPIFromWSDL(String apiName, String endpoint, S wsdlEndpoint.setWsdl(wsdlEndpointData); String swaggerYaml = soaPtoRESTConversionData.getOASString(); - String apiXml = getSynapseAPIFromSwagger(swaggerYaml, null); + String apiXml = getSynapseAPIFromSwagger(swaggerYaml, null, context, version, versionType); APIFactory apiFactory = new APIFactory(); API api = (API) apiFactory.create(Utils.getDOMDocument(apiXml).getDocumentElement()); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/SwaggerConstants.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/SwaggerConstants.java index 0dbf90d0e..d80bad128 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/SwaggerConstants.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/SwaggerConstants.java @@ -190,4 +190,9 @@ public class SwaggerConstants { * Path param normaized placeholder */ public static final String NORMALIZED_PLACEHOLDER = "{}"; + + /** + * Version type value indicating that the API should be unversioned + */ + static final String VERSION_TYPE_NONE = "none"; } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/pojo/GenerateAPIParam.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/pojo/GenerateAPIParam.java index 010659a5a..fc153bde7 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/pojo/GenerateAPIParam.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/customservice/synapse/api/generator/pojo/GenerateAPIParam.java @@ -21,4 +21,7 @@ public class GenerateAPIParam { public String wsdlEndpointName; public String publishSwaggerPath; public String mode; + public String context; + public String version; + public String versionType; } From 429d4ae95f815b9c8c6cb607cb15701db5ffeefd Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Tue, 28 Jul 2026 08:20:44 +0000 Subject: [PATCH 34/34] Update LS version --- org.eclipse.lemminx/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org.eclipse.lemminx/pom.xml b/org.eclipse.lemminx/pom.xml index 4bd06715c..77dbeba12 100644 --- a/org.eclipse.lemminx/pom.xml +++ b/org.eclipse.lemminx/pom.xml @@ -3,7 +3,7 @@ org.wso2.language.server mi-language-server-parent - 0.24.0-wso2v94 + 0.24.0-wso2v95 ../pom.xml MI Language Server diff --git a/pom.xml b/pom.xml index 7fcb5c405..5c22fc0db 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.wso2.language.server mi-language-server-parent - 0.24.0-wso2v94 + 0.24.0-wso2v95 pom MI Language Server - Parent LemMinX is a XML Language Server Protocol (LSP), and can be used with any editor that supports LSP, to offer an outstanding XML editing experience