From 3018009daa3178212fa1825a7d5259c2f18ef1cf Mon Sep 17 00:00:00 2001 From: Harshana Amuwatte Date: Thu, 26 Mar 2026 14:13:41 +0530 Subject: [PATCH 01/12] feat: implementation of file associations for multi-root workspace schema validation --- .../eclipse/lemminx/XMLLanguageServer.java | 17 +- .../customservice/synapse/utils/Utils.java | 95 +++++++++--- .../CleanMultiRootValidationTest.java | 145 ++++++++++++++++++ 3 files changed, 227 insertions(+), 30 deletions(-) create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java index b98daf326..449b9d34c 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java @@ -20,6 +20,7 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -103,6 +104,7 @@ public class XMLLanguageServer implements ProcessLanguageServer, XMLLanguageServ private XMLCapabilityManager capabilityManager; private TelemetryManager telemetryManager; private final SynapseLanguageService synapseLanguageService; + private Map workspaceSchemas = new HashMap<>(); public XMLLanguageServer() { xmlTextDocumentService = new XMLTextDocumentService(this); @@ -121,10 +123,12 @@ public XMLLanguageServer() { @Override public CompletableFuture initialize(InitializeParams params) { try { - Path synapseSchemaPath = Utils.updateSynapseCatalogSettings(params); - synapseLanguageService.setSynapseXSDPath(synapseSchemaPath); + workspaceSchemas = Utils.updateSynapseFileAssociationSettings(params); + if (!workspaceSchemas.isEmpty()) { + synapseLanguageService.setSynapseXSDPath(workspaceSchemas.values().iterator().next()); + } } catch (IOException | URISyntaxException e) { - LOGGER.log(Level.SEVERE, "Error while updating synapse catalog settings", e); + LOGGER.log(Level.SEVERE, "Error while updating synapse file association settings", e); } Object initOptions = InitializationOptionsSettings.getSettings(params); Object xmlSettings = AllXMLSettings.getAllXMLSettings(initOptions); @@ -190,12 +194,7 @@ private synchronized void updateSettings(Object initOptions, boolean initLogs) { if (initOptions == null) { return; } - try { - initOptions = Utils.updateSynapseCatalogSettings((JsonObject) initOptions, - synapseLanguageService.getSynapseXSDPath()); - } catch (IOException | URISyntaxException e) { - LOGGER.log(Level.SEVERE, "Error while updating synapse catalog settings", e); - } + initOptions = Utils.updateSynapseFileAssociationSettings((JsonObject) initOptions, workspaceSchemas); // Update client settings Object initSettings = AllXMLSettings.getAllXMLSettings(initOptions); XMLGeneralClientSettings xmlClientSettings = XMLGeneralClientSettings.getGeneralXMLSettings(initSettings); 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 6d7677653..56edabdd8 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 @@ -22,7 +22,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import com.google.gson.JsonPrimitive; + import com.google.gson.JsonSyntaxException; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; @@ -49,6 +49,7 @@ import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager; import org.eclipse.lsp4j.InitializeParams; import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.WorkspaceFolder; import org.w3c.dom.Node; import java.io.BufferedInputStream; @@ -1061,6 +1062,18 @@ public static void extractJarFolder(String resourceFolder, Path targetDirectory) } } } + } else if ("file".equals(resourceURL.getProtocol())) { + // Resource is on the regular filesystem (e.g., during Maven test or IDE debug) + Path sourceDirectory = Paths.get(resourceURL.toURI()); + Files.walkFileTree(sourceDirectory, new java.nio.file.SimpleFileVisitor() { + @Override + public java.nio.file.FileVisitResult visitFile(Path file, java.nio.file.attribute.BasicFileAttributes attrs) throws IOException { + Path targetFile = targetDirectory.resolve(sourceDirectory.relativize(file)); + Files.createDirectories(targetFile.getParent()); + Files.copy(file, targetFile, StandardCopyOption.REPLACE_EXISTING); + return java.nio.file.FileVisitResult.CONTINUE; + } + }); } } @@ -1070,36 +1083,76 @@ private static String extractJarPath(URL resourceURL) throws URISyntaxException return new File(uri).getAbsolutePath(); } - public static Path updateSynapseCatalogSettings(InitializeParams params) throws IOException, URISyntaxException { - String projectUri = params.getRootPath(); - Object initParams = params.getInitializationOptions(); + + public static Map updateSynapseFileAssociationSettings(InitializeParams params) + throws IOException, URISyntaxException { + + List folderUris = new ArrayList<>(); + List workspaceFolders = params.getWorkspaceFolders(); + if (workspaceFolders != null && !workspaceFolders.isEmpty()) { + for (WorkspaceFolder folder : workspaceFolders) { + folderUris.add(folder.getUri()); + } + } else if (params.getRootPath() != null) { + folderUris.add(params.getRootPath()); + } // for old + + Map workspaceSchemas = new HashMap<>(); + for (String folderUri : folderUris) { + Path schemaDir = copyXSDFiles(folderUri); + workspaceSchemas.put(folderUri, schemaDir); + } + + Object initOptions = params.getInitializationOptions(); Gson gson = new Gson(); - JsonElement jsonElement = gson.toJsonTree(initParams); + JsonElement jsonElement = gson.toJsonTree(initOptions); if (jsonElement != null && jsonElement.isJsonObject() && jsonElement.getAsJsonObject().has(Constant.SETTINGS)) { JsonObject settings = jsonElement.getAsJsonObject().getAsJsonObject(Constant.SETTINGS); - Path schemaPath = copyXSDFiles(projectUri); - JsonElement updatedParams = updateSynapseCatalogSettings(settings, schemaPath); - JsonObject updatedSettings = new JsonObject(); - updatedSettings.add(Constant.SETTINGS, updatedParams); - params.setInitializationOptions(updatedSettings); - return schemaPath; - + JsonElement updatedParams = updateSynapseFileAssociationSettings(settings, workspaceSchemas); + JsonObject updatedRoot = new JsonObject(); + updatedRoot.add(Constant.SETTINGS, updatedParams); + params.setInitializationOptions(updatedRoot); } - return null; + + return workspaceSchemas; } - public static JsonElement updateSynapseCatalogSettings(JsonObject settings, Path schemaPath) - throws IOException, URISyntaxException { + public static JsonElement updateSynapseFileAssociationSettings(JsonObject settings, + Map workspaceSchemas) { + + if (workspaceSchemas == null || workspaceSchemas.isEmpty()) { + return settings; + } + + JsonArray fileAssociationsArray = new JsonArray(); + for (Map.Entry entry : workspaceSchemas.entrySet()) { + String folderUri = entry.getKey(); + Path schemaDir = entry.getValue(); + Path xsdPath = schemaDir.resolve("synapse_config.xsd"); + + // Convert the folder URI to a filesystem path for the glob pattern, + String patternBase = folderUri; + try { + patternBase = Paths.get(new URI(folderUri)).toString().replace("\\", "/"); + } catch (Exception e) { + patternBase = folderUri.replace("\\", "/"); + } - if (schemaPath != null) { - Path catalogPath = schemaPath.resolve("catalog.xml"); - JsonArray catalogsArray = new JsonArray(); - catalogsArray.add(new JsonPrimitive(catalogPath.toString())); - if (settings != null && settings.isJsonObject() && settings.has(Constant.XML)) { - settings.getAsJsonObject(Constant.XML).add(Constant.CATALOGS, catalogsArray); + JsonObject association = new JsonObject(); + association.addProperty("pattern", patternBase + "/**/*.xml"); + association.addProperty("systemId", xsdPath.toUri().toString()); + fileAssociationsArray.add(association); + } + + if (settings != null && settings.isJsonObject() && settings.has(Constant.XML)) { + JsonObject xmlObj = settings.getAsJsonObject(Constant.XML); + xmlObj.add("fileAssociations", fileAssociationsArray); + if (xmlObj.has(Constant.CATALOGS)) { + xmlObj.remove(Constant.CATALOGS); } } + return settings; } diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java new file mode 100644 index 000000000..cecc9441f --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java @@ -0,0 +1,145 @@ +package org.eclipse.lemminx.extensions.contentmodel; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.junit.jupiter.api.Test; + +/** + * A perfectly clean validation test demonstrating Production-Architecture Multi-Root functionality. + * One single XMLLanguageService instance validates two isolated URI streams using purely XMLFileAssociations. + */ +public class CleanMultiRootValidationTest { + + @Test + public void testCleanMultiRootIsolation() throws Exception { + // --- Setup Workspaces & Schemas --- + Path tempDirA = Files.createTempDirectory("project-a"); + Path tempDirB = Files.createTempDirectory("project-b"); + tempDirA.toFile().deleteOnExit(); + tempDirB.toFile().deleteOnExit(); + + // Project A: Lenient Schema (allows any attribute) + String xsdA = "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + Files.write(tempDirA.resolve("synapse_config.xsd"), xsdA.getBytes(StandardCharsets.UTF_8)); + + // Project B: Strict Schema (no attributes allowed) + String xsdB = "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + ""; + Files.write(tempDirB.resolve("synapse_config.xsd"), xsdB.getBytes(StandardCharsets.UTF_8)); + + // --- 1. Start a Full Language Server Replica --- + org.eclipse.lemminx.MockXMLLanguageServer server = new org.eclipse.lemminx.MockXMLLanguageServer(); + + // MOCK the SynapseLanguageService to bypass its currently broken global initialization logic + org.eclipse.lemminx.XMLTextDocumentService textDocumentServiceOuter = (org.eclipse.lemminx.XMLTextDocumentService) server.getTextDocumentService(); + org.eclipse.lemminx.SynapseLanguageService mockSynapseService = new org.eclipse.lemminx.SynapseLanguageService(textDocumentServiceOuter, server) { + @Override + public void init(String projectUri, Object settings, org.eclipse.lemminx.customservice.SynapseLanguageClientAPI languageClient) { + // Do nothing to simulate a bypassed init (no multi-root crashes during boot) + } + }; + + java.lang.reflect.Field synapseServiceField = org.eclipse.lemminx.XMLLanguageServer.class.getDeclaredField("synapseLanguageService"); + synapseServiceField.setAccessible(true); + synapseServiceField.set(server, mockSynapseService); + + // --- 2. Build the VS Code InitializeParams envelope --- + org.eclipse.lsp4j.InitializeParams params = new org.eclipse.lsp4j.InitializeParams(); + + org.eclipse.lsp4j.WorkspaceFolder wfA = new org.eclipse.lsp4j.WorkspaceFolder(); + wfA.setUri(tempDirA.toUri().toString()); + wfA.setName("project-a"); + + org.eclipse.lsp4j.WorkspaceFolder wfB = new org.eclipse.lsp4j.WorkspaceFolder(); + wfB.setUri(tempDirB.toUri().toString()); + wfB.setName("project-b"); + + params.setWorkspaceFolders(java.util.Arrays.asList(wfA, wfB)); + + com.google.gson.JsonObject initOptions = new com.google.gson.JsonObject(); + com.google.gson.JsonObject settingsObj = new com.google.gson.JsonObject(); + com.google.gson.JsonObject xmlObj = new com.google.gson.JsonObject(); + com.google.gson.JsonObject validationObj = new com.google.gson.JsonObject(); + validationObj.addProperty("noGrammar", "ignore"); + xmlObj.add("validation", validationObj); + settingsObj.add("xml", xmlObj); + initOptions.add("settings", settingsObj); + params.setInitializationOptions(initOptions); + + // --- 3. Let the Server Boot (This automatically calls Utils.updateSynapseFileAssociationSettings inside XMLLanguageServer!) --- + server.initialize(params).join(); + + // --- 3.5 Intercept and Overwrite the real XSDs with our Mock schemas to test isolation --- + java.lang.reflect.Field schemasField = org.eclipse.lemminx.XMLLanguageServer.class.getDeclaredField("workspaceSchemas"); + schemasField.setAccessible(true); + @SuppressWarnings("unchecked") + java.util.Map resolvedSchemas = (java.util.Map) schemasField.get(server); + + // Replace the dynamically extracted real schemas with our mocked ones for the test + Files.write(resolvedSchemas.get(tempDirA.toUri().toString()).resolve("synapse_config.xsd"), xsdA.getBytes(StandardCharsets.UTF_8)); + Files.write(resolvedSchemas.get(tempDirB.toUri().toString()).resolve("synapse_config.xsd"), xsdB.getBytes(StandardCharsets.UTF_8)); + + // --- 4. The Test XML (Both projects will parse the exact same text) --- + String xml = ""; + + // --- 4. Validate Project A (Simulate VS Code opening the file) --- + String uriA = tempDirA.resolve("src/config.xml").toUri().toString(); + org.eclipse.lsp4j.TextDocumentItem docA = new org.eclipse.lsp4j.TextDocumentItem(uriA, "xml", 1, xml); + server.getTextDocumentService().didOpen(new org.eclipse.lsp4j.DidOpenTextDocumentParams(docA)); + + // --- 5. Validate Project B (Simulate VS Code opening the file) --- + String uriB = tempDirB.resolve("src/config.xml").toUri().toString(); + org.eclipse.lsp4j.TextDocumentItem docB = new org.eclipse.lsp4j.TextDocumentItem(uriB, "xml", 1, xml); + server.getTextDocumentService().didOpen(new org.eclipse.lsp4j.DidOpenTextDocumentParams(docB)); + + // Wait for asynchronous diagnostic validation to finish + Thread.sleep(1500); + + // --- 6. Verify the diagnostics published back to the Client (VS Code) --- + List publishedDiagnostics = server.getPublishDiagnostics(); + assertEquals(2, publishedDiagnostics.size(), "Should have published diagnostics for both files"); + + org.eclipse.lsp4j.PublishDiagnosticsParams diagA = null; + org.eclipse.lsp4j.PublishDiagnosticsParams diagB = null; + + for (org.eclipse.lsp4j.PublishDiagnosticsParams pub : publishedDiagnostics) { + if (pub.getUri().equals(uriA)) { + diagA = pub; + } else if (pub.getUri().equals(uriB)) { + diagB = pub; + } + } + + assertNotNull(diagA, "Diagnostics for Project A missing"); + assertNotNull(diagB, "Diagnostics for Project B missing"); + + assertEquals(0, diagA.getDiagnostics().size(), "Project-A should report 0 errors due to lenient schema"); + assertEquals(1, diagB.getDiagnostics().size(), "Project-B should report 1 error due to strict schema"); + assertEquals(org.eclipse.lemminx.extensions.contentmodel.participants.XMLSchemaErrorCode.cvc_complex_type_3_2_2.getCode(), diagB.getDiagnostics().get(0).getCode().getLeft()); + + System.out.println("============== TEST PASSED - FULL MULTI ROOT ISOLATION ACHIEVED E2E ==============\n"); + } +} From b73012affaeda1667725b08102ada7909601d367 Mon Sep 17 00:00:00 2001 From: Harshana Amuwatte Date: Mon, 6 Apr 2026 11:38:05 +0530 Subject: [PATCH 02/12] feat: implementation of file associations for multi-root workspace schema validation --- .../customservice/synapse/utils/Utils.java | 2 +- .../CleanMultiRootValidationTest.java | 181 ++++++++++++------ 2 files changed, 124 insertions(+), 59 deletions(-) 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 56edabdd8..0f52f2982 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 @@ -1024,7 +1024,7 @@ private static void loadTemplate(Path path, String resourceFolder, Map\n" - + "\n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + ""; - Files.write(tempDirA.resolve("synapse_config.xsd"), xsdA.getBytes(StandardCharsets.UTF_8)); - - // Project B: Strict Schema (no attributes allowed) - String xsdB = "\n" - + "\n" - + " \n" - + " \n" - + " \n" - + ""; - Files.write(tempDirB.resolve("synapse_config.xsd"), xsdB.getBytes(StandardCharsets.UTF_8)); + // Set up Project A as a MI 4.3.0 project + String pomA = "4.3.0"; + Files.write(tempDirA.resolve("pom.xml"), pomA.getBytes(StandardCharsets.UTF_8)); + Files.createDirectories(tempDirA.resolve("src")); + // Set up Project B as a MI 4.4.0 project + String pomB = "4.4.0"; + Files.write(tempDirB.resolve("pom.xml"), pomB.getBytes(StandardCharsets.UTF_8)); + Files.createDirectories(tempDirB.resolve("src")); + } + + @Test + public void testCleanMultiRootIsolation() throws Exception { // --- 1. Start a Full Language Server Replica --- org.eclipse.lemminx.MockXMLLanguageServer server = new org.eclipse.lemminx.MockXMLLanguageServer(); // MOCK the SynapseLanguageService to bypass its currently broken global initialization logic - org.eclipse.lemminx.XMLTextDocumentService textDocumentServiceOuter = (org.eclipse.lemminx.XMLTextDocumentService) server.getTextDocumentService(); + // We use an anonymous subclass instead of Mockito to avoid ByteBuddy compatibility issues on Java 25 + org.eclipse.lemminx.XMLTextDocumentService textDocumentServiceOuter = + (org.eclipse.lemminx.XMLTextDocumentService) server.getTextDocumentService(); org.eclipse.lemminx.SynapseLanguageService mockSynapseService = new org.eclipse.lemminx.SynapseLanguageService(textDocumentServiceOuter, server) { @Override public void init(String projectUri, Object settings, org.eclipse.lemminx.customservice.SynapseLanguageClientAPI languageClient) { @@ -68,15 +60,15 @@ public void init(String projectUri, Object settings, org.eclipse.lemminx.customs // --- 2. Build the VS Code InitializeParams envelope --- org.eclipse.lsp4j.InitializeParams params = new org.eclipse.lsp4j.InitializeParams(); - + org.eclipse.lsp4j.WorkspaceFolder wfA = new org.eclipse.lsp4j.WorkspaceFolder(); wfA.setUri(tempDirA.toUri().toString()); wfA.setName("project-a"); - + org.eclipse.lsp4j.WorkspaceFolder wfB = new org.eclipse.lsp4j.WorkspaceFolder(); wfB.setUri(tempDirB.toUri().toString()); wfB.setName("project-b"); - + params.setWorkspaceFolders(java.util.Arrays.asList(wfA, wfB)); com.google.gson.JsonObject initOptions = new com.google.gson.JsonObject(); @@ -89,36 +81,38 @@ public void init(String projectUri, Object settings, org.eclipse.lemminx.customs initOptions.add("settings", settingsObj); params.setInitializationOptions(initOptions); - // --- 3. Let the Server Boot (This automatically calls Utils.updateSynapseFileAssociationSettings inside XMLLanguageServer!) --- + // --- 3. Let the Server Boot (This natively reads the pom.xml versions and loads 430 and 440 schemas!) --- server.initialize(params).join(); - // --- 3.5 Intercept and Overwrite the real XSDs with our Mock schemas to test isolation --- - java.lang.reflect.Field schemasField = org.eclipse.lemminx.XMLLanguageServer.class.getDeclaredField("workspaceSchemas"); - schemasField.setAccessible(true); - @SuppressWarnings("unchecked") - java.util.Map resolvedSchemas = (java.util.Map) schemasField.get(server); - - // Replace the dynamically extracted real schemas with our mocked ones for the test - Files.write(resolvedSchemas.get(tempDirA.toUri().toString()).resolve("synapse_config.xsd"), xsdA.getBytes(StandardCharsets.UTF_8)); - Files.write(resolvedSchemas.get(tempDirB.toUri().toString()).resolve("synapse_config.xsd"), xsdB.getBytes(StandardCharsets.UTF_8)); - - // --- 4. The Test XML (Both projects will parse the exact same text) --- - String xml = ""; - - // --- 4. Validate Project A (Simulate VS Code opening the file) --- - String uriA = tempDirA.resolve("src/config.xml").toUri().toString(); + // Let's print the resolved server versions using Utils.getServerVersion to prove the fix works + String versionA = org.eclipse.lemminx.customservice.synapse.utils.Utils.getServerVersion(tempDirA.toUri().toString(), "default-fallback-version"); + String versionB = org.eclipse.lemminx.customservice.synapse.utils.Utils.getServerVersion(tempDirB.toUri().toString(), "default-fallback-version"); + System.out.println("====== POM VERSION EXTRACTION CONFIRMATION ======"); + System.out.println("Project A (Expected 4.3.0) Version Found: " + versionA); + System.out.println("Project B (Expected 4.4.0) Version Found: " + versionB); + System.out.println("================================================="); + + // --- 4. The Test XML --- + // The mediator was introduced in MI 4.4.0. It is invalid in MI 4.3.0. + String xml = "\n" + + "\n" + + " \n" + + ""; + + // --- 5. Validate Project A (Simulate VS Code opening the file in MI 4.3.0) --- + String uriA = tempDirA.resolve("src/sequence.xml").toUri().toString(); org.eclipse.lsp4j.TextDocumentItem docA = new org.eclipse.lsp4j.TextDocumentItem(uriA, "xml", 1, xml); server.getTextDocumentService().didOpen(new org.eclipse.lsp4j.DidOpenTextDocumentParams(docA)); - // --- 5. Validate Project B (Simulate VS Code opening the file) --- - String uriB = tempDirB.resolve("src/config.xml").toUri().toString(); + // --- 6. Validate Project B (Simulate VS Code opening the file in MI 4.4.0) --- + String uriB = tempDirB.resolve("src/sequence.xml").toUri().toString(); org.eclipse.lsp4j.TextDocumentItem docB = new org.eclipse.lsp4j.TextDocumentItem(uriB, "xml", 1, xml); server.getTextDocumentService().didOpen(new org.eclipse.lsp4j.DidOpenTextDocumentParams(docB)); // Wait for asynchronous diagnostic validation to finish Thread.sleep(1500); - // --- 6. Verify the diagnostics published back to the Client (VS Code) --- + // --- 7. Verify the diagnostics published back to the Client (VS Code) --- List publishedDiagnostics = server.getPublishDiagnostics(); assertEquals(2, publishedDiagnostics.size(), "Should have published diagnostics for both files"); @@ -136,10 +130,81 @@ public void init(String projectUri, Object settings, org.eclipse.lemminx.customs assertNotNull(diagA, "Diagnostics for Project A missing"); assertNotNull(diagB, "Diagnostics for Project B missing"); - assertEquals(0, diagA.getDiagnostics().size(), "Project-A should report 0 errors due to lenient schema"); - assertEquals(1, diagB.getDiagnostics().size(), "Project-B should report 1 error due to strict schema"); - assertEquals(org.eclipse.lemminx.extensions.contentmodel.participants.XMLSchemaErrorCode.cvc_complex_type_3_2_2.getCode(), diagB.getDiagnostics().get(0).getCode().getLeft()); - - System.out.println("============== TEST PASSED - FULL MULTI ROOT ISOLATION ACHIEVED E2E ==============\n"); + // Project A (4.3.0) should flag 'variable' as an invalid element since it didn't exist in 4.3 + assertEquals(1, diagA.getDiagnostics().size(), "Project-A (4.3.0) should report 1 error due to unknown 'variable' mediator"); + + // Project B (4.4.0) should accept 'variable' natively because it is valid in 4.4 + assertEquals(0, diagB.getDiagnostics().size(), "Project-B (4.4.0) should report 0 errors for valid 'variable' mediator"); + + System.out.println("============== TEST PASSED - REAL XSD 4.3/4.4 MULTI ROOT ISOLATION ACHIEVED E2E ==============\n"); + + // --- PHASE 2: DYNAMIC CONNECTOR SCHEMA UPDATE WITH TIMER + Thread.sleep(3000); + + // Simulate a User Downloading Salesforce Connector in the integration studio + System.out.println("Connector Downloaded! Parsing Salesforce connector metadata..."); + org.eclipse.lemminx.customservice.synapse.connectors.ConnectorHolder holder = org.eclipse.lemminx.customservice.synapse.connectors.ConnectorHolder.getInstance(); + holder.clearConnectors(); // Clean slate + + org.eclipse.lemminx.customservice.synapse.connectors.entity.Connector fakeConnector = new org.eclipse.lemminx.customservice.synapse.connectors.entity.Connector(); + fakeConnector.setName("salesforce"); + fakeConnector.setDisplayName("Salesforce Connector"); + org.eclipse.lemminx.customservice.synapse.connectors.entity.ConnectorAction action = new org.eclipse.lemminx.customservice.synapse.connectors.entity.ConnectorAction(); + action.setTag("salesforce.create"); + action.setHidden(false); + org.eclipse.lemminx.customservice.synapse.connectors.entity.OperationParameter param = new org.eclipse.lemminx.customservice.synapse.connectors.entity.OperationParameter("sobjectType", "Type of SObject"); + action.setParameters(List.of(param)); + fakeConnector.setActions(List.of(action)); + holder.addConnector(fakeConnector); + + // Find the resolved schemas for BOTH isolated projects via reflection + java.lang.reflect.Field schemasField = org.eclipse.lemminx.XMLLanguageServer.class.getDeclaredField("workspaceSchemas"); + schemasField.setAccessible(true); + @SuppressWarnings("unchecked") + java.util.Map resolvedSchemas = (java.util.Map) schemasField.get(server); + + Path schemaPathA = resolvedSchemas.get(tempDirA.toUri().toString()); + Path schemaPathB = resolvedSchemas.get(tempDirB.toUri().toString()); + + // Generate the Schema inside BOTH project environments exactly as `updateConnectors` should! + String connectorPathA = schemaPathA.resolve("mediators").resolve("connectors.xsd").toString(); + String connectorPathB = schemaPathB.resolve("mediators").resolve("connectors.xsd").toString(); + + org.eclipse.lemminx.customservice.synapse.connectors.SchemaGenerate.generate(holder, connectorPathA); + org.eclipse.lemminx.customservice.synapse.connectors.SchemaGenerate.generate(holder, connectorPathB); + + + String updatedContent = Files.readString(schemaPathA.resolve("mediators").resolve("connectors.xsd")); + org.junit.jupiter.api.Assertions.assertTrue(updatedContent.contains(""), "Schema MUST contain salesforce.create"); + + // Now, we simulate an open of a file that uses the Salesforce mediator + String connectorXml = "\n" + + "\n" + + " \n" + + " Account\n" + + " \n" + + ""; + + // Send a DID_CHANGE to force LemMinX to revalidate + System.out.println("Revalidating connector XML on Project A..."); + server.getTextDocumentService().didChange(new org.eclipse.lsp4j.DidChangeTextDocumentParams( + new org.eclipse.lsp4j.VersionedTextDocumentIdentifier(uriA, 2), + List.of(new org.eclipse.lsp4j.TextDocumentContentChangeEvent(connectorXml)) + )); + + Thread.sleep(1500); + + List newDiagnostics = server.getPublishDiagnostics(); + org.eclipse.lsp4j.PublishDiagnosticsParams newDiagA = null; + for (org.eclipse.lsp4j.PublishDiagnosticsParams pub : newDiagnostics) { + if (pub.getUri().equals(uriA)) { + newDiagA = pub; + } + } + + assertNotNull(newDiagA, "New diagnostics for Project A missing"); + assertEquals(0, newDiagA.getDiagnostics().size(), "Dynamic Connector generation FAILED! 'salesforce.create' was not recognized!"); + + System.out.println("============== PHASE 2 PASSED - DYNAMIC CONNECTOR VALIDATION SUCCESSFUL ==============\n"); } } From 29e18abc4ee797e8de414ad7a410d488168683dd Mon Sep 17 00:00:00 2001 From: Harshana Amuwatte Date: Mon, 6 Apr 2026 15:39:36 +0530 Subject: [PATCH 03/12] test: refactor multi-root validation test to include dynamic connector schema update verification --- .../CleanMultiRootValidationTest.java | 471 +++++++++++------- 1 file changed, 278 insertions(+), 193 deletions(-) diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java index 7d9c1a229..db4f6c627 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java @@ -1,210 +1,295 @@ +/** + * Copyright (c) 2026 WSO2 LLC. (http://www.wso2.org). + * 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 + */ package org.eclipse.lemminx.extensions.contentmodel; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import java.util.List; - +import java.util.Map; + +import org.eclipse.lemminx.MockXMLLanguageServer; +import org.eclipse.lemminx.SynapseLanguageService; +import org.eclipse.lemminx.XMLLanguageServer; +import org.eclipse.lemminx.XMLTextDocumentService; +import org.eclipse.lemminx.customservice.SynapseLanguageClientAPI; +import org.eclipse.lemminx.customservice.synapse.connectors.ConnectorHolder; +import org.eclipse.lemminx.customservice.synapse.connectors.SchemaGenerate; +import org.eclipse.lemminx.customservice.synapse.connectors.entity.Connector; +import org.eclipse.lemminx.customservice.synapse.connectors.entity.ConnectorAction; +import org.eclipse.lemminx.customservice.synapse.connectors.entity.OperationParameter; +import org.eclipse.lsp4j.DidChangeTextDocumentParams; +import org.eclipse.lsp4j.DidOpenTextDocumentParams; +import org.eclipse.lsp4j.InitializeParams; +import org.eclipse.lsp4j.PublishDiagnosticsParams; +import org.eclipse.lsp4j.TextDocumentContentChangeEvent; +import org.eclipse.lsp4j.TextDocumentItem; +import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; +import org.eclipse.lsp4j.WorkspaceFolder; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import com.google.gson.JsonObject; + /** - * A perfectly clean validation test demonstrating Production-Architecture Multi-Root functionality. - * One single XMLLanguageService instance validates two isolated URI streams using purely XMLFileAssociations. + * Tests for multi-root workspace validation using the XML engine and + * file-association mechanism. + * + *

Test 1 ({@link #multiRootIsolation()}) verifies that a single + * {@code XMLLanguageService} instance validates two isolated projects + * (MI 4.3.0 and MI 4.4.0) using per-project XSD file associations.

+ * + *

Test 2 ({@link #dynamicConnectorSchemaUpdate()}) verifies that + * dynamically generated connector schemas are picked up by the validation + * engine without a server restart.

*/ public class CleanMultiRootValidationTest { - private Path tempDirA; - private Path tempDirB; - - @BeforeEach - public void setUp() throws Exception { - tempDirA = Files.createTempDirectory("project-a"); - tempDirB = Files.createTempDirectory("project-b"); - tempDirA.toFile().deleteOnExit(); - tempDirB.toFile().deleteOnExit(); - - // Set up Project A as a MI 4.3.0 project - String pomA = "4.3.0"; - Files.write(tempDirA.resolve("pom.xml"), pomA.getBytes(StandardCharsets.UTF_8)); - Files.createDirectories(tempDirA.resolve("src")); - - // Set up Project B as a MI 4.4.0 project - String pomB = "4.4.0"; - Files.write(tempDirB.resolve("pom.xml"), pomB.getBytes(StandardCharsets.UTF_8)); - Files.createDirectories(tempDirB.resolve("src")); - } - - @Test - public void testCleanMultiRootIsolation() throws Exception { - // --- 1. Start a Full Language Server Replica --- - org.eclipse.lemminx.MockXMLLanguageServer server = new org.eclipse.lemminx.MockXMLLanguageServer(); - - // MOCK the SynapseLanguageService to bypass its currently broken global initialization logic - // We use an anonymous subclass instead of Mockito to avoid ByteBuddy compatibility issues on Java 25 - org.eclipse.lemminx.XMLTextDocumentService textDocumentServiceOuter = - (org.eclipse.lemminx.XMLTextDocumentService) server.getTextDocumentService(); - org.eclipse.lemminx.SynapseLanguageService mockSynapseService = new org.eclipse.lemminx.SynapseLanguageService(textDocumentServiceOuter, server) { - @Override - public void init(String projectUri, Object settings, org.eclipse.lemminx.customservice.SynapseLanguageClientAPI languageClient) { - // Do nothing to simulate a bypassed init (no multi-root crashes during boot) - } - }; - - java.lang.reflect.Field synapseServiceField = org.eclipse.lemminx.XMLLanguageServer.class.getDeclaredField("synapseLanguageService"); - synapseServiceField.setAccessible(true); - synapseServiceField.set(server, mockSynapseService); - - // --- 2. Build the VS Code InitializeParams envelope --- - org.eclipse.lsp4j.InitializeParams params = new org.eclipse.lsp4j.InitializeParams(); - - org.eclipse.lsp4j.WorkspaceFolder wfA = new org.eclipse.lsp4j.WorkspaceFolder(); - wfA.setUri(tempDirA.toUri().toString()); - wfA.setName("project-a"); - - org.eclipse.lsp4j.WorkspaceFolder wfB = new org.eclipse.lsp4j.WorkspaceFolder(); - wfB.setUri(tempDirB.toUri().toString()); - wfB.setName("project-b"); - - params.setWorkspaceFolders(java.util.Arrays.asList(wfA, wfB)); - - com.google.gson.JsonObject initOptions = new com.google.gson.JsonObject(); - com.google.gson.JsonObject settingsObj = new com.google.gson.JsonObject(); - com.google.gson.JsonObject xmlObj = new com.google.gson.JsonObject(); - com.google.gson.JsonObject validationObj = new com.google.gson.JsonObject(); - validationObj.addProperty("noGrammar", "ignore"); - xmlObj.add("validation", validationObj); - settingsObj.add("xml", xmlObj); - initOptions.add("settings", settingsObj); - params.setInitializationOptions(initOptions); - - // --- 3. Let the Server Boot (This natively reads the pom.xml versions and loads 430 and 440 schemas!) --- - server.initialize(params).join(); - - // Let's print the resolved server versions using Utils.getServerVersion to prove the fix works - String versionA = org.eclipse.lemminx.customservice.synapse.utils.Utils.getServerVersion(tempDirA.toUri().toString(), "default-fallback-version"); - String versionB = org.eclipse.lemminx.customservice.synapse.utils.Utils.getServerVersion(tempDirB.toUri().toString(), "default-fallback-version"); - System.out.println("====== POM VERSION EXTRACTION CONFIRMATION ======"); - System.out.println("Project A (Expected 4.3.0) Version Found: " + versionA); - System.out.println("Project B (Expected 4.4.0) Version Found: " + versionB); - System.out.println("================================================="); - - // --- 4. The Test XML --- - // The mediator was introduced in MI 4.4.0. It is invalid in MI 4.3.0. - String xml = "\n" + - "\n" + - " \n" + - ""; - - // --- 5. Validate Project A (Simulate VS Code opening the file in MI 4.3.0) --- - String uriA = tempDirA.resolve("src/sequence.xml").toUri().toString(); - org.eclipse.lsp4j.TextDocumentItem docA = new org.eclipse.lsp4j.TextDocumentItem(uriA, "xml", 1, xml); - server.getTextDocumentService().didOpen(new org.eclipse.lsp4j.DidOpenTextDocumentParams(docA)); - - // --- 6. Validate Project B (Simulate VS Code opening the file in MI 4.4.0) --- - String uriB = tempDirB.resolve("src/sequence.xml").toUri().toString(); - org.eclipse.lsp4j.TextDocumentItem docB = new org.eclipse.lsp4j.TextDocumentItem(uriB, "xml", 1, xml); - server.getTextDocumentService().didOpen(new org.eclipse.lsp4j.DidOpenTextDocumentParams(docB)); - - // Wait for asynchronous diagnostic validation to finish - Thread.sleep(1500); - - // --- 7. Verify the diagnostics published back to the Client (VS Code) --- - List publishedDiagnostics = server.getPublishDiagnostics(); - assertEquals(2, publishedDiagnostics.size(), "Should have published diagnostics for both files"); - - org.eclipse.lsp4j.PublishDiagnosticsParams diagA = null; - org.eclipse.lsp4j.PublishDiagnosticsParams diagB = null; - - for (org.eclipse.lsp4j.PublishDiagnosticsParams pub : publishedDiagnostics) { - if (pub.getUri().equals(uriA)) { - diagA = pub; - } else if (pub.getUri().equals(uriB)) { - diagB = pub; - } - } - - assertNotNull(diagA, "Diagnostics for Project A missing"); - assertNotNull(diagB, "Diagnostics for Project B missing"); - - // Project A (4.3.0) should flag 'variable' as an invalid element since it didn't exist in 4.3 - assertEquals(1, diagA.getDiagnostics().size(), "Project-A (4.3.0) should report 1 error due to unknown 'variable' mediator"); - - // Project B (4.4.0) should accept 'variable' natively because it is valid in 4.4 - assertEquals(0, diagB.getDiagnostics().size(), "Project-B (4.4.0) should report 0 errors for valid 'variable' mediator"); - - System.out.println("============== TEST PASSED - REAL XSD 4.3/4.4 MULTI ROOT ISOLATION ACHIEVED E2E ==============\n"); - - // --- PHASE 2: DYNAMIC CONNECTOR SCHEMA UPDATE WITH TIMER - Thread.sleep(3000); - - // Simulate a User Downloading Salesforce Connector in the integration studio - System.out.println("Connector Downloaded! Parsing Salesforce connector metadata..."); - org.eclipse.lemminx.customservice.synapse.connectors.ConnectorHolder holder = org.eclipse.lemminx.customservice.synapse.connectors.ConnectorHolder.getInstance(); - holder.clearConnectors(); // Clean slate - - org.eclipse.lemminx.customservice.synapse.connectors.entity.Connector fakeConnector = new org.eclipse.lemminx.customservice.synapse.connectors.entity.Connector(); - fakeConnector.setName("salesforce"); - fakeConnector.setDisplayName("Salesforce Connector"); - org.eclipse.lemminx.customservice.synapse.connectors.entity.ConnectorAction action = new org.eclipse.lemminx.customservice.synapse.connectors.entity.ConnectorAction(); - action.setTag("salesforce.create"); - action.setHidden(false); - org.eclipse.lemminx.customservice.synapse.connectors.entity.OperationParameter param = new org.eclipse.lemminx.customservice.synapse.connectors.entity.OperationParameter("sobjectType", "Type of SObject"); - action.setParameters(List.of(param)); - fakeConnector.setActions(List.of(action)); - holder.addConnector(fakeConnector); - - // Find the resolved schemas for BOTH isolated projects via reflection - java.lang.reflect.Field schemasField = org.eclipse.lemminx.XMLLanguageServer.class.getDeclaredField("workspaceSchemas"); - schemasField.setAccessible(true); - @SuppressWarnings("unchecked") - java.util.Map resolvedSchemas = (java.util.Map) schemasField.get(server); - - Path schemaPathA = resolvedSchemas.get(tempDirA.toUri().toString()); - Path schemaPathB = resolvedSchemas.get(tempDirB.toUri().toString()); - - // Generate the Schema inside BOTH project environments exactly as `updateConnectors` should! - String connectorPathA = schemaPathA.resolve("mediators").resolve("connectors.xsd").toString(); - String connectorPathB = schemaPathB.resolve("mediators").resolve("connectors.xsd").toString(); - - org.eclipse.lemminx.customservice.synapse.connectors.SchemaGenerate.generate(holder, connectorPathA); - org.eclipse.lemminx.customservice.synapse.connectors.SchemaGenerate.generate(holder, connectorPathB); - - - String updatedContent = Files.readString(schemaPathA.resolve("mediators").resolve("connectors.xsd")); - org.junit.jupiter.api.Assertions.assertTrue(updatedContent.contains(""), "Schema MUST contain salesforce.create"); - - // Now, we simulate an open of a file that uses the Salesforce mediator - String connectorXml = "\n" + - "\n" + - " \n" + - " Account\n" + - " \n" + - ""; - - // Send a DID_CHANGE to force LemMinX to revalidate - System.out.println("Revalidating connector XML on Project A..."); - server.getTextDocumentService().didChange(new org.eclipse.lsp4j.DidChangeTextDocumentParams( - new org.eclipse.lsp4j.VersionedTextDocumentIdentifier(uriA, 2), - List.of(new org.eclipse.lsp4j.TextDocumentContentChangeEvent(connectorXml)) - )); - - Thread.sleep(1500); - - List newDiagnostics = server.getPublishDiagnostics(); - org.eclipse.lsp4j.PublishDiagnosticsParams newDiagA = null; - for (org.eclipse.lsp4j.PublishDiagnosticsParams pub : newDiagnostics) { - if (pub.getUri().equals(uriA)) { - newDiagA = pub; - } - } - - assertNotNull(newDiagA, "New diagnostics for Project A missing"); - assertEquals(0, newDiagA.getDiagnostics().size(), "Dynamic Connector generation FAILED! 'salesforce.create' was not recognized!"); - - System.out.println("============== PHASE 2 PASSED - DYNAMIC CONNECTOR VALIDATION SUCCESSFUL ==============\n"); - } + private Path tempDirA; + private Path tempDirB; + + @BeforeEach + public void setUp() throws Exception { + tempDirA = Files.createTempDirectory("project-a"); + tempDirB = Files.createTempDirectory("project-b"); + tempDirA.toFile().deleteOnExit(); + tempDirB.toFile().deleteOnExit(); + + // Project A — MI 4.3.0 + String pomA = "" + + "4.3.0" + + ""; + Files.write(tempDirA.resolve("pom.xml"), pomA.getBytes(StandardCharsets.UTF_8)); + Files.createDirectories(tempDirA.resolve("src")); + + // Project B — MI 4.4.0 + String pomB = "" + + "4.4.0" + + ""; + Files.write(tempDirB.resolve("pom.xml"), pomB.getBytes(StandardCharsets.UTF_8)); + Files.createDirectories(tempDirB.resolve("src")); + } + + // --------------------------------------------------------------------------- + // Test 1 — One XMLLanguageService validates multiple projects + // --------------------------------------------------------------------------- + + @Test + public void multiRootIsolation() throws Exception { + MockXMLLanguageServer server = createMultiRootServer(); + + // The mediator was introduced in MI 4.4.0 — invalid in MI 4.3.0 + String xml = "\n" + + "\n" + + " \n" + + ""; + + // Open the same XML content under both project trees + String uriA = openDocument(server, tempDirA, "src/sequence.xml", xml, 1); + String uriB = openDocument(server, tempDirB, "src/sequence.xml", xml, 1); + + Thread.sleep(1500); + + // Retrieve published diagnostics + PublishDiagnosticsParams diagA = findDiagnosticsForUri(server.getPublishDiagnostics(), uriA); + PublishDiagnosticsParams diagB = findDiagnosticsForUri(server.getPublishDiagnostics(), uriB); + + assertNotNull(diagA, "Diagnostics for Project A missing"); + assertNotNull(diagB, "Diagnostics for Project B missing"); + + // Project A (4.3.0) — 'variable' is unknown → at least 1 error + assertEquals(1, diagA.getDiagnostics().size(), + "Project-A (4.3.0) should report 1 error for unknown 'variable' mediator"); + + // Project B (4.4.0) — 'variable' is valid → 0 errors + assertEquals(0, diagB.getDiagnostics().size(), + "Project-B (4.4.0) should report 0 errors for valid 'variable' mediator"); + } + + // --------------------------------------------------------------------------- + // Test 2 — Dynamically changed content (connector schema) is also validated + // --------------------------------------------------------------------------- + + @Test + public void dynamicConnectorSchemaUpdate() throws Exception { + MockXMLLanguageServer server = createMultiRootServer(); + + // Open a placeholder document so the server has an active document for Project A + String xml = "\n" + + "\n" + + " \n" + + ""; + String uriA = openDocument(server, tempDirA, "src/sequence.xml", xml, 1); + + Thread.sleep(1500); + + // Simulate downloading a Salesforce connector + ConnectorHolder holder = ConnectorHolder.getInstance(); + holder.clearConnectors(); + holder.addConnector(createFakeSalesforceConnector()); + + // Generate the connector schema inside both workspace schema directories + @SuppressWarnings("unchecked") + Map resolvedSchemas = getWorkspaceSchemas(server); + + Path schemaPathA = resolvedSchemas.get(tempDirA.toUri().toString()); + assertNotNull(schemaPathA, "Schema path for Project A should be resolved"); + + SchemaGenerate.generate(holder, + schemaPathA.resolve("mediators").resolve("connectors.xsd").toString()); + + // Verify generated schema contains the expected element + String schemaContent = Files.readString( + schemaPathA.resolve("mediators").resolve("connectors.xsd")); + assertTrue(schemaContent.contains(""), + "Schema must contain salesforce.create element"); + + // Send a didChange with XML that uses the connector + String connectorXml = "\n" + + "\n" + + " \n" + + " Account\n" + + " \n" + + ""; + + server.getTextDocumentService().didChange(new DidChangeTextDocumentParams( + new VersionedTextDocumentIdentifier(uriA, 2), + List.of(new TextDocumentContentChangeEvent(connectorXml)))); + + Thread.sleep(1500); + + // After schema regeneration the connector element should be recognized + PublishDiagnosticsParams newDiagA = findDiagnosticsForUri( + server.getPublishDiagnostics(), uriA); + + assertNotNull(newDiagA, "Diagnostics for Project A missing after connector update"); + assertEquals(0, newDiagA.getDiagnostics().size(), + "'salesforce.create' should be recognized after dynamic schema generation"); + } + + // --------------------------------------------------------------------------- + // Private helpers + // --------------------------------------------------------------------------- + + /** + * Creates a {@link MockXMLLanguageServer} initialized with two workspace + * folders ({@code tempDirA} and {@code tempDirB}) and a mocked + * {@link SynapseLanguageService} whose {@code init()} is a no-op. + */ + private MockXMLLanguageServer createMultiRootServer() throws Exception { + MockXMLLanguageServer server = new MockXMLLanguageServer(); + + // Stub SynapseLanguageService to bypass its global init logic + XMLTextDocumentService tds = + (XMLTextDocumentService) server.getTextDocumentService(); + SynapseLanguageService stubSynapseService = new SynapseLanguageService(tds, server) { + @Override + public void init(String projectUri, Object settings, + SynapseLanguageClientAPI languageClient) { + // no-op + } + }; + + java.lang.reflect.Field synapseField = + XMLLanguageServer.class.getDeclaredField("synapseLanguageService"); + synapseField.setAccessible(true); + synapseField.set(server, stubSynapseService); + + // Build InitializeParams with both workspace folders + InitializeParams params = new InitializeParams(); + + WorkspaceFolder wfA = new WorkspaceFolder(); + wfA.setUri(tempDirA.toUri().toString()); + wfA.setName("project-a"); + + WorkspaceFolder wfB = new WorkspaceFolder(); + wfB.setUri(tempDirB.toUri().toString()); + wfB.setName("project-b"); + + params.setWorkspaceFolders(Arrays.asList(wfA, wfB)); + + JsonObject initOptions = new JsonObject(); + JsonObject settingsObj = new JsonObject(); + JsonObject xmlObj = new JsonObject(); + JsonObject validationObj = new JsonObject(); + validationObj.addProperty("noGrammar", "ignore"); + xmlObj.add("validation", validationObj); + settingsObj.add("xml", xmlObj); + initOptions.add("settings", settingsObj); + params.setInitializationOptions(initOptions); + + server.initialize(params).join(); + return server; + } + + /** + * Opens a document under the given project directory and returns the + * document URI. + */ + private static String openDocument(MockXMLLanguageServer server, + Path projectDir, String relativePath, String content, int version) { + String uri = projectDir.resolve(relativePath).toUri().toString(); + TextDocumentItem doc = new TextDocumentItem(uri, "xml", version, content); + server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(doc)); + return uri; + } + + /** + * Finds the last published diagnostics for the given URI. + */ + private static PublishDiagnosticsParams findDiagnosticsForUri( + List allDiagnostics, String uri) { + PublishDiagnosticsParams result = null; + for (PublishDiagnosticsParams p : allDiagnostics) { + if (p.getUri().equals(uri)) { + result = p; + } + } + return result; + } + + /** + * Creates a minimal fake Salesforce connector with a single + * {@code salesforce.create} action for testing purposes. + */ + private static Connector createFakeSalesforceConnector() { + Connector connector = new Connector(); + connector.setName("salesforce"); + connector.setDisplayName("Salesforce Connector"); + + ConnectorAction action = new ConnectorAction(); + action.setTag("salesforce.create"); + action.setHidden(false); + + OperationParameter param = new OperationParameter("sobjectType", "Type of SObject"); + action.setParameters(List.of(param)); + connector.setActions(List.of(action)); + + return connector; + } + + /** + * Reflectively accesses the {@code workspaceSchemas} field to obtain the + * per-project schema directories resolved during initialization. + */ + @SuppressWarnings("unchecked") + private static Map getWorkspaceSchemas( + MockXMLLanguageServer server) throws Exception { + java.lang.reflect.Field field = + XMLLanguageServer.class.getDeclaredField("workspaceSchemas"); + field.setAccessible(true); + return (Map) field.get(server); + } } From c6be1ed689c26d528fed8fe7b3b3f84805d5c44e Mon Sep 17 00:00:00 2001 From: Harshana Amuwatte Date: Wed, 8 Apr 2026 09:50:54 +0530 Subject: [PATCH 04/12] Update Synapse schemas natively on workspace folder changes --- .../eclipse/lemminx/XMLLanguageServer.java | 18 ++++++- .../eclipse/lemminx/XMLWorkspaceService.java | 22 +++++++- .../customservice/synapse/utils/Utils.java | 4 +- .../CleanMultiRootValidationTest.java | 53 +++++++++++++++++++ 4 files changed, 92 insertions(+), 5 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java index 449b9d34c..e7ded9c1c 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java @@ -105,7 +105,7 @@ public class XMLLanguageServer implements ProcessLanguageServer, XMLLanguageServ private TelemetryManager telemetryManager; private final SynapseLanguageService synapseLanguageService; private Map workspaceSchemas = new HashMap<>(); - + private Object lastKnownInitOptions = null; public XMLLanguageServer() { xmlTextDocumentService = new XMLTextDocumentService(this); xmlWorkspaceService = new XMLWorkspaceService(this); @@ -131,6 +131,7 @@ public CompletableFuture initialize(InitializeParams params) { LOGGER.log(Level.SEVERE, "Error while updating synapse file association settings", e); } Object initOptions = InitializationOptionsSettings.getSettings(params); + this.lastKnownInitOptions = initOptions; Object xmlSettings = AllXMLSettings.getAllXMLSettings(initOptions); XMLGeneralClientSettings settings = XMLGeneralClientSettings.getGeneralXMLSettings(xmlSettings); @@ -194,6 +195,7 @@ private synchronized void updateSettings(Object initOptions, boolean initLogs) { if (initOptions == null) { return; } + this.lastKnownInitOptions = initOptions; initOptions = Utils.updateSynapseFileAssociationSettings((JsonObject) initOptions, workspaceSchemas); // Update client settings Object initSettings = AllXMLSettings.getAllXMLSettings(initOptions); @@ -257,6 +259,20 @@ private synchronized void updateSettings(Object initOptions, boolean initLogs) { xmlTextDocumentService.updateSettings(initSettings); } + public void addWorkspaceSchema(String folderUri, Path schemaDir) { + workspaceSchemas.put(folderUri, schemaDir); + } + + public void removeWorkspaceSchema(String folderUri) { + workspaceSchemas.remove(folderUri); + } + + public void triggerSettingsRefresh() { + if (lastKnownInitOptions != null) { + updateSettings(lastKnownInitOptions, false); + } + } + @Override public CompletableFuture shutdown() { xmlLanguageService.dispose(); 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..2989ddeca 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 @@ -85,7 +85,27 @@ public void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams params) { xmlLanguageServer.getXMLLanguageService().getWorkspaceServiceParticipants() .forEach(participant -> participant.didChangeWorkspaceFolders(params)); -// workspaceFolders.didChangeWorkspaceFolders(params); + boolean hasSchemaChanges = false; + if (params.getEvent().getRemoved() != null) { + for (org.eclipse.lsp4j.WorkspaceFolder folder : params.getEvent().getRemoved()) { + xmlLanguageServer.removeWorkspaceSchema(folder.getUri()); + hasSchemaChanges = true; + } + } + if (params.getEvent().getAdded() != null) { + for (org.eclipse.lsp4j.WorkspaceFolder folder : params.getEvent().getAdded()) { + try { + java.nio.file.Path schemaDir = org.eclipse.lemminx.customservice.synapse.utils.Utils.copyXSDFiles(folder.getUri()); + xmlLanguageServer.addWorkspaceSchema(folder.getUri(), schemaDir); + hasSchemaChanges = true; + } catch (Exception e) { + // Ignore safely + } + } + } + if (hasSchemaChanges) { + xmlLanguageServer.triggerSettingsRefresh(); + } } @Override 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 0f52f2982..0c843916d 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 @@ -1094,9 +1094,7 @@ public static Map updateSynapseFileAssociationSettings(InitializeP for (WorkspaceFolder folder : workspaceFolders) { folderUris.add(folder.getUri()); } - } else if (params.getRootPath() != null) { - folderUris.add(params.getRootPath()); - } // for old + } Map workspaceSchemas = new HashMap<>(); for (String folderUri : folderUris) { diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java index db4f6c627..b648933d5 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java @@ -38,6 +38,8 @@ import org.eclipse.lsp4j.TextDocumentItem; import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; import org.eclipse.lsp4j.WorkspaceFolder; +import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams; +import org.eclipse.lsp4j.WorkspaceFoldersChangeEvent; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -54,6 +56,10 @@ *

Test 2 ({@link #dynamicConnectorSchemaUpdate()}) verifies that * dynamically generated connector schemas are picked up by the validation * engine without a server restart.

+ * + *

Test 3 ({@link #dynamicWorkspaceFolderAddition()}) verifies that + * when a user adds a new project to the workspace dynamically, the + * language server perfectly detects it and applies standard MI validations.

*/ public class CleanMultiRootValidationTest { @@ -179,6 +185,53 @@ public void dynamicConnectorSchemaUpdate() throws Exception { "'salesforce.create' should be recognized after dynamic schema generation"); } + // --------------------------------------------------------------------------- + // Test 3 — Dynamically adding a new project connects the XML validation + // --------------------------------------------------------------------------- + + @Test + public void dynamicWorkspaceFolderAddition() throws Exception { + MockXMLLanguageServer server = createMultiRootServer(); + + // 1. Create a new dynamically added project: Project C (MI 4.3.0) + Path tempDirC = Files.createTempDirectory("project-c"); + tempDirC.toFile().deleteOnExit(); + String pomC = "" + + "4.3.0" + + ""; + Files.write(tempDirC.resolve("pom.xml"), pomC.getBytes(StandardCharsets.UTF_8)); + Files.createDirectories(tempDirC.resolve("src")); + + // 2. Simulate VS Code sending workspace/didChangeWorkspaceFolders for Project C + WorkspaceFolder wfC = new WorkspaceFolder(); + wfC.setUri(tempDirC.toUri().toString()); + wfC.setName("project-c"); + + WorkspaceFoldersChangeEvent event = new WorkspaceFoldersChangeEvent(); + event.setAdded(Arrays.asList(wfC)); + + server.getWorkspaceService().didChangeWorkspaceFolders(new DidChangeWorkspaceFoldersParams(event)); + + // Allow background schema copying to complete + Thread.sleep(1500); + + // 3. Open a file in the newly added project. + // Since it's MI 4.3.0, the mediator should be flagged as an error. + String xml = "\n" + + "\n" + + " \n" + + ""; + String uriC = openDocument(server, tempDirC, "src/sequence.xml", xml, 1); + + Thread.sleep(1500); + + // 4. Verify diagnostics for Project C + PublishDiagnosticsParams diagC = findDiagnosticsForUri(server.getPublishDiagnostics(), uriC); + assertNotNull(diagC, "Diagnostics for dynamically added Project C should be present"); + assertEquals(1, diagC.getDiagnostics().size(), + "Project C (4.3.0) should perfectly validate and report 1 error for unknown 'variable' mediator"); + } + // --------------------------------------------------------------------------- // Private helpers // --------------------------------------------------------------------------- From 601fbd61656315fb9c7c9ad213a07471e6f3f965 Mon Sep 17 00:00:00 2001 From: Harshana Amuwatte Date: Wed, 8 Apr 2026 11:14:51 +0530 Subject: [PATCH 05/12] test: update CleanMultiRootValidationTest to verify validation failure before and success after dynamic connector schema generation --- .../CleanMultiRootValidationTest.java | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java index b648933d5..49e526932 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java @@ -132,57 +132,54 @@ public void multiRootIsolation() throws Exception { public void dynamicConnectorSchemaUpdate() throws Exception { MockXMLLanguageServer server = createMultiRootServer(); - // Open a placeholder document so the server has an active document for Project A - String xml = "\n" - + "\n" - + " \n" + // 1. Open a document that uses the Salesforce connector BEFORE downloading the connector + String connectorXml = "\n" + + "\n" + + " \n" + + " Account\n" + + " \n" + ""; - String uriA = openDocument(server, tempDirA, "src/sequence.xml", xml, 1); + String uriA = openDocument(server, tempDirA, "src/sequence.xml", connectorXml, 1); Thread.sleep(1500); - // Simulate downloading a Salesforce connector + // 2. Verify it currently fails validation (because salesforce schema doesn't exist yet) + PublishDiagnosticsParams initialDiag = findDiagnosticsForUri(server.getPublishDiagnostics(), uriA); + assertNotNull(initialDiag, "Initial diagnostics should be present"); + assertTrue(initialDiag.getDiagnostics().size() > 0, + "Should report an error for unknown 'salesforce.create' mediator before connector is added"); + + // 3. Simulate downloading a Salesforce connector ConnectorHolder holder = ConnectorHolder.getInstance(); holder.clearConnectors(); holder.addConnector(createFakeSalesforceConnector()); - // Generate the connector schema inside both workspace schema directories + // 4. Generate the connector schema inside the workspace schema directory @SuppressWarnings("unchecked") Map resolvedSchemas = getWorkspaceSchemas(server); - Path schemaPathA = resolvedSchemas.get(tempDirA.toUri().toString()); assertNotNull(schemaPathA, "Schema path for Project A should be resolved"); SchemaGenerate.generate(holder, schemaPathA.resolve("mediators").resolve("connectors.xsd").toString()); - // Verify generated schema contains the expected element - String schemaContent = Files.readString( - schemaPathA.resolve("mediators").resolve("connectors.xsd")); + // Verify generated schema physically contains the expected element + String schemaContent = Files.readString(schemaPathA.resolve("mediators").resolve("connectors.xsd")); assertTrue(schemaContent.contains(""), "Schema must contain salesforce.create element"); - // Send a didChange with XML that uses the connector - String connectorXml = "\n" - + "\n" - + " \n" - + " Account\n" - + " \n" - + ""; - + // 5. Send a didChange with the EXACT same XML content to forcefully trigger re-validation server.getTextDocumentService().didChange(new DidChangeTextDocumentParams( new VersionedTextDocumentIdentifier(uriA, 2), List.of(new TextDocumentContentChangeEvent(connectorXml)))); Thread.sleep(1500); - // After schema regeneration the connector element should be recognized - PublishDiagnosticsParams newDiagA = findDiagnosticsForUri( - server.getPublishDiagnostics(), uriA); - + // 6. Verify that it now passes successfully with ZERO errors! + PublishDiagnosticsParams newDiagA = findDiagnosticsForUri(server.getPublishDiagnostics(), uriA); assertNotNull(newDiagA, "Diagnostics for Project A missing after connector update"); assertEquals(0, newDiagA.getDiagnostics().size(), - "'salesforce.create' should be recognized after dynamic schema generation"); + "'salesforce.create' should be perfectly recognized and pass after dynamic schema generation"); } // --------------------------------------------------------------------------- From 2a224bcc7a5610855395d595ea297b855b4eedf2 Mon Sep 17 00:00:00 2001 From: Harshana Amuwatte Date: Thu, 9 Apr 2026 15:32:42 +0530 Subject: [PATCH 06/12] test: implement manual schema injection in CleanMultiRootValidationTest to support workspace schema testing --- .../CleanMultiRootValidationTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java index 49e526932..60d1c911e 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/CleanMultiRootValidationTest.java @@ -95,6 +95,8 @@ public void setUp() throws Exception { @Test public void multiRootIsolation() throws Exception { MockXMLLanguageServer server = createMultiRootServer(); + injectSchemasManually(server, tempDirA.toUri().toString(), "430"); + injectSchemasManually(server, tempDirB.toUri().toString(), "440"); // The mediator was introduced in MI 4.4.0 — invalid in MI 4.3.0 String xml = "\n" @@ -131,6 +133,7 @@ public void multiRootIsolation() throws Exception { @Test public void dynamicConnectorSchemaUpdate() throws Exception { MockXMLLanguageServer server = createMultiRootServer(); + injectSchemasManually(server, tempDirA.toUri().toString(), "430"); // 1. Open a document that uses the Salesforce connector BEFORE downloading the connector String connectorXml = "\n" @@ -211,6 +214,7 @@ public void dynamicWorkspaceFolderAddition() throws Exception { // Allow background schema copying to complete Thread.sleep(1500); + injectSchemasManually(server, tempDirC.toUri().toString(), "430"); // 3. Open a file in the newly added project. // Since it's MI 4.3.0, the mediator should be flagged as an error. @@ -342,4 +346,28 @@ private static Map getWorkspaceSchemas( field.setAccessible(true); return (Map) field.get(server); } + + /** + * A manual schema injector. Since production Utils removed the 'file' + * protocol extractor, we manually copy the schemas directly from the + * resources directory into the empty dynamically generated workspace schemas. + */ + private void injectSchemasManually(MockXMLLanguageServer server, String projectUri, String versionFolder) throws Exception { + Map resolvedSchemas = getWorkspaceSchemas(server); + Path schemaTarget = resolvedSchemas.get(projectUri); + if (schemaTarget != null) { + Path sourceDirectory = java.nio.file.Paths.get("src", "main", "resources", "org", "eclipse", "lemminx", "schemas", versionFolder); + if (Files.exists(sourceDirectory)) { + Files.walkFileTree(sourceDirectory, new java.nio.file.SimpleFileVisitor() { + @Override + public java.nio.file.FileVisitResult visitFile(Path file, java.nio.file.attribute.BasicFileAttributes attrs) throws java.io.IOException { + Path targetFile = schemaTarget.resolve(sourceDirectory.relativize(file)); + Files.createDirectories(targetFile.getParent()); + Files.copy(file, targetFile, java.nio.file.StandardCopyOption.REPLACE_EXISTING); + return java.nio.file.FileVisitResult.CONTINUE; + } + }); + } + } + } } From 864a47f92385a153bef83f01991fcb5039f2fc92 Mon Sep 17 00:00:00 2001 From: Harshana Amuwatte Date: Thu, 9 Apr 2026 21:32:11 +0530 Subject: [PATCH 07/12] refactor: remove unused file system resource copying logic from Utils --- .../lemminx/customservice/synapse/utils/Utils.java | 12 ------------ 1 file changed, 12 deletions(-) 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 0c843916d..a7184ba56 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 @@ -1062,18 +1062,6 @@ public static void extractJarFolder(String resourceFolder, Path targetDirectory) } } } - } else if ("file".equals(resourceURL.getProtocol())) { - // Resource is on the regular filesystem (e.g., during Maven test or IDE debug) - Path sourceDirectory = Paths.get(resourceURL.toURI()); - Files.walkFileTree(sourceDirectory, new java.nio.file.SimpleFileVisitor() { - @Override - public java.nio.file.FileVisitResult visitFile(Path file, java.nio.file.attribute.BasicFileAttributes attrs) throws IOException { - Path targetFile = targetDirectory.resolve(sourceDirectory.relativize(file)); - Files.createDirectories(targetFile.getParent()); - Files.copy(file, targetFile, StandardCopyOption.REPLACE_EXISTING); - return java.nio.file.FileVisitResult.CONTINUE; - } - }); } } From 0906f5a9107682f7c4efeb1b6c1f9ca77655f5fe Mon Sep 17 00:00:00 2001 From: Harshana Amuwatte Date: Thu, 9 Apr 2026 22:50:36 +0530 Subject: [PATCH 08/12] Resources for multi workspace support --- multi-workspace-support/mws-README.md | 51 +++ .../resources/ProjectContext.java | 429 ++++++++++++++++++ .../resources/WorkspaceManager.java | 218 +++++++++ 3 files changed, 698 insertions(+) create mode 100644 multi-workspace-support/mws-README.md create mode 100644 multi-workspace-support/resources/ProjectContext.java create mode 100644 multi-workspace-support/resources/WorkspaceManager.java diff --git a/multi-workspace-support/mws-README.md b/multi-workspace-support/mws-README.md new file mode 100644 index 000000000..2302af4f3 --- /dev/null +++ b/multi-workspace-support/mws-README.md @@ -0,0 +1,51 @@ +# Implementing Multi-Project Workspace Support for Current Language Server + +## Problem in Current Language Server +The current WSO2 Micro Integrator (MI) Language Server typically serves each MI project with a separate language server instance when multiple projects exist in a workspace. This limits scalability, increases resource overhead, and breaks true multi-folder workspace experiences. + +## Architecture Roadmap + +### Stage 1: Core XML Validation & Schema Isolation **(Completed)** +Enable independent XML validation for multiple projects within a single Language Server instance using dynamic File Associations. + +### Stage 2: Eliminating Global State & Singletons +* **Goal:** Transition legacy singletons (e.g., `ConnectorHolder`, `SynapseLanguageService`, Mediator Handlers) to be resolved per project context instead of a global state. +* **Action Plan:** Introduce `ProjectContext` and `WorkspaceManager` classes to manage memory scoped to individual projects. + * *Reference Files:* `multi-workspace-support/resources/ProjectContext.java`, `multi-workspace-support/resources/WorkspaceManager.java` + +### Stage 3: Context-Aware Resource Finders & Auto-Completions +Isolate Language Server features (Auto-Complete, Go-To-Definition) per workspace. Update `ResourceFinder` and properties handlers to filter sequences, endpoints, and registry resources based strictly on the active document's specific project URI, preventing invalid auto-complete suggestions from unrelated projects. + +### Stage 4: Language Client (VS Code Extension) Integration +Update the frontend VS Code Extension to natively support the multi-project backend API configurations and event hooks. + +--- +--- +--- + +## Stage 1 Completed: LemMinX File Associations for Workspace Schema Validation + +### Technical Overview +Introduced **File Associations** to replace the legacy namespace-based `.catalog` implementation. Instead of relying on rigid catalogs, this approach maps specific file path patterns (e.g., glob matches for a project folder) to a specific target schema path. This enables different schemas (e.g., MI 4.3.0 and 4.4.0) to be applied to different projects simultaneously without conflict in a single LemMinX instance. + +### Changelog & Implementation Details + +#### 1. `Utils.java` (`org.eclipse.lemminx/customservice/synapse/utils/Utils.java`) +* **Removed Catalog Dependencies:** Replaced `updateSynapseCatalogSettings` with `updateSynapseFileAssociationSettings`. The initialization parameters logic was modified to drop the `catalogs` array and successfully inject `fileAssociations` instead. +* **URI Path Sanitation Patch:** Updated internal path extraction from: + * *Old:* `String version = getServerVersion(projectUri, Constant.DEFAULT_MI_VERSION);` + * *New:* `String version = getServerVersion(getAbsolutePath(projectUri), Constant.DEFAULT_MI_VERSION);` + * *Rationale:* Previously, the `rootPath` field provided raw OS paths. In a Multi-Root architecture utilizing `workspaceFolders`, the data received is formatted as URIs (`file:///...`). Adding `getAbsolutePath()` ensures the URI is cleanly scrubbed before Java's `Path.of()` tries to read the `pom.xml`. + +#### 2. `XMLLanguageServer.java` (`org.eclipse.lemminx/XMLLanguageServer.java`) +* Replaced the legacy initialization step `Utils.updateSynapseCatalogSettings(params)` with the new core standard: `Utils.updateSynapseFileAssociationSettings(params)`. +* *Temporary Bridge/Hack:* Because `SynapseLanguageService` (Stage 2) is not yet fully isolated for multi-root awareness, a temporary bridge was established by setting its default Path to the first project in the collection: `synapseLanguageService.setSynapseXSDPath(workspaceSchemas.values().iterator().next());` + +#### 3. `XMLWorkspaceService.java` (`org.eclipse.lemminx/XMLWorkspaceService.java`) +* Enhanced `didChangeWorkspaceFolders` logic to capture dynamic workspace events correctly. If a user adds a new project to the workspace after server initialization, it accurately intercepts the action and generates/applies XSD schemas for the new folder. + +#### 4. `CleanMultiRootValidationTest.java` (`.../extensions/contentmodel/CleanMultiRootValidationTest.java`) +Established three comprehensive multi-root tests demonstrating core functionality: +1. **Multi-Root Isolation:** Verifies that a single Language Server successfully provides isolated validations to two independent projects governed by distinct XSD files. +2. **Dynamic Connector Generation Test:** Ensures that dynamically generated connector schemas are instantly picked up by the validation engine logic, operating independently of server restarts. +3. **Dynamic Workspace Handling:** Asserts that when an entirely new project is dynamically appended to the workspace context at runtime, the language server successfully triggers its standard MI validations for the newly tracked space. diff --git a/multi-workspace-support/resources/ProjectContext.java b/multi-workspace-support/resources/ProjectContext.java new file mode 100644 index 000000000..3cb46093b --- /dev/null +++ b/multi-workspace-support/resources/ProjectContext.java @@ -0,0 +1,429 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.eclipse.lemminx.customservice.synapse; + +import org.eclipse.lemminx.customservice.SynapseLanguageClientAPI; +import org.eclipse.lemminx.customservice.synapse.connectors.AbstractConnectorLoader; +import org.eclipse.lemminx.customservice.synapse.connectors.ConnectionHandler; +import org.eclipse.lemminx.customservice.synapse.connectors.ConnectorHolder; +import org.eclipse.lemminx.customservice.synapse.connectors.NewProjectConnectorLoader; +import org.eclipse.lemminx.customservice.synapse.connectors.OldProjectConnectorLoader; +import org.eclipse.lemminx.customservice.synapse.expression.ExpressionHelperProvider; +import org.eclipse.lemminx.customservice.synapse.inbound.conector.InboundConnectorHolder; +import org.eclipse.lemminx.customservice.synapse.mediatorService.MediatorHandler; +import org.eclipse.lemminx.customservice.synapse.resourceFinder.AbstractResourceFinder; +import org.eclipse.lemminx.customservice.synapse.resourceFinder.ResourceFinderFactory; +import org.eclipse.lemminx.customservice.synapse.utils.Utils; + +import java.nio.file.Path; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Holds all per-project state required by the MI Language Server for a single + * workspace folder/project. In a Multi-Root Workspace scenario, one instance + * of {@code ProjectContext} is created per open project root, and all language + * features (completions, hover, validation, connectors, etc.) are resolved + * through the context that corresponds to the document being processed. + * + *

Instances are stored in a map keyed by the project root URI inside the + * language server (e.g. {@code SynapseLanguageClientAPI} or its successor + * manager class) so that requests for any document are dispatched to the + * correct context. + * + *

Immutable identity fields (projectUri, isLegacyProject, projectServerVersion) + * are set in the constructor and cannot be changed afterwards. All service + * handler fields are eagerly initialized via the {@link #initProject} method, + * which must be called immediately after construction. No setters are provided + * — Early Initialization ensures everything is ready before the context is + * used, eliminating loading delays during coding. + * + *

Note: The {@code TryOutManager} is intentionally excluded from + * this class. It manages a heavy background MI Server process that binds to + * a specific network port, so only one instance can run at a time across all + * projects. It remains a separate global concern managed by + * {@code SynapseLanguageService}. + */ +public class ProjectContext { + + private static final Logger log = Logger.getLogger(ProjectContext.class.getName()); + + /** + * Tracks whether {@link #initProject} has completed successfully. + * Used by service-handler getters to fail fast with a clear message + * instead of returning {@code null}. + */ + private boolean initialized = false; + + // ------------------------------------------------------------------------- + // Identity fields — set once at construction time and never changed. + // ------------------------------------------------------------------------- + + /** + * The root folder URI of this project (e.g. {@code file:///Users/.../ProjectA}). + * Used as the primary key when looking up the context for a given document URI. + */ + private final String projectUri; + + /** + * Whether this project is a legacy (state-machine-based) MI project. + * Legacy projects use a different activation and completion pathway compared + * to modern MI projects. + */ + private final boolean isLegacyProject; + + /** + * The WSO2 MI version string associated with this project + * (e.g. {@code "4.3.0"}, {@code "4.4.0"}). Used to select the correct + * XSD schemas, mediator descriptors, and feature toggles. + */ + private final String projectServerVersion; + + // ------------------------------------------------------------------------- + // Schema field — resolved during initProject(). + // ------------------------------------------------------------------------- + + /** + * Path to the extracted root {@code synapse_config.xsd} for this specific + * project. Resolved during {@link #initProject} by extracting the + * version-specific XSD bundle for this project. + */ + private Path synapseXsdPath; + + // ------------------------------------------------------------------------- + // Connector fields — eagerly initialized in the constructor. + // ------------------------------------------------------------------------- + + /** + * Holds metadata and descriptors for all regular (outbound) connectors + * discovered for this project. Initialized eagerly so that connector + * scanning can populate it immediately after construction. + */ + private final ConnectorHolder connectorHolder; + + /** + * Holds metadata and descriptors for all inbound connectors discovered + * for this project. Initialized eagerly alongside {@link #connectorHolder}. + */ + private final InboundConnectorHolder inboundConnectorHolder; + + // ------------------------------------------------------------------------- + // Service handler fields — eagerly initialized via initProject(). + // ------------------------------------------------------------------------- + + /** + * Responsible for loading and refreshing connectors from the project's + * connector directory. The concrete type (Old vs New) depends on + * {@link #isLegacyProject}. + */ + private AbstractConnectorLoader connectorLoader; + + /** + * Handles completion proposals and hover information for Synapse mediators + * within this project. Depends on {@link #projectServerVersion} to load + * the correct mediator descriptor set. + */ + private MediatorHandler mediatorHandler; + + /** + * Provides completion and documentation support for {@code ${}} expression + * syntax (e.g. payload-factory, data-mapper expressions) within this project. + */ + private ExpressionHelperProvider expressionHelperProvider; + + /** + * Manages named connection artifacts (e.g. connector local-entries) for + * this project, enabling connection-aware completions and validations. + */ + private ConnectionHandler connectionHandler; + + /** + * Locates and resolves project-internal resources (endpoints, sequences, + * message-stores, etc.) referenced by documents in this project. + */ + private AbstractResourceFinder resourceFinder; + + // ------------------------------------------------------------------------- + // Constructor + // ------------------------------------------------------------------------- + + /** + * Creates a new {@code ProjectContext} for the given project root. + * + *

The {@link ConnectorHolder} and {@link InboundConnectorHolder} are + * created eagerly so that connector-scanning routines can start populating + * them immediately. All other service handlers remain {@code null} until + * {@link #initProject} is called. + * + * @param projectUri root folder URI of the project + * (e.g. {@code "file:///Users/.../ProjectA"}) + * @param isLegacyProject {@code true} if this is a state-machine based + * (legacy) MI project + * @param projectServerVersion the MI version string for this project + * (e.g. {@code "4.3.0"}) + */ + public ProjectContext(String projectUri, boolean isLegacyProject, String projectServerVersion) { + + this.projectUri = projectUri; + this.isLegacyProject = isLegacyProject; + this.projectServerVersion = projectServerVersion; + + // Eagerly initialize connector holders so scanning can begin immediately. + this.connectorHolder = ConnectorHolder.getInstance(); + this.inboundConnectorHolder = new InboundConnectorHolder(); + } + + // ------------------------------------------------------------------------- + // Early Initialization + // ------------------------------------------------------------------------- + + /** + * Eagerly initializes all service handlers for this project in the correct + * dependency order, mirroring the initialization sequence from + * {@code SynapseLanguageService.init(...)}. + * + *

This method must be called exactly once, immediately after construction, + * before the context is registered for use. After this call returns + * successfully, every getter is guaranteed to return a non-null, fully + * initialized instance — eliminating any lazy-loading delays during coding. + * + *

Initialization order: + *

    + *
  1. {@link InboundConnectorHolder#init} — loads inbound connector metadata
  2. + *
  3. {@link AbstractConnectorLoader} — instantiates the correct loader + * (Old vs New) and calls {@code init(projectUri)}
  4. + *
  5. {@link MediatorHandler} — loads mediator descriptors for this version
  6. + *
  7. {@link ConnectionHandler} — indexes named connections
  8. + *
  9. {@link ExpressionHelperProvider} — prepares expression helpers
  10. + *
  11. {@link AbstractResourceFinder} — discovers and indexes dependent + * resources (endpoints, sequences, etc.)
  12. + *
  13. Resolves and stores the {@code synapseXsdPath}
  14. + *
+ * + * @param miServerPath absolute path to the local MI server installation + * @param languageClient the language-client proxy for sending notifications + * back to the IDE + * @throws Exception if any step in the initialization pipeline fails + */ + public void initProject(String miServerPath, SynapseLanguageClientAPI languageClient) throws Exception { + + // 1. Initialize inbound connector metadata. + inboundConnectorHolder.init(projectUri, projectServerVersion); + + // 2. Instantiate the correct connector loader based on project type. + if (isLegacyProject) { + this.connectorLoader = new OldProjectConnectorLoader(languageClient, connectorHolder); + } else { + this.connectorLoader = new NewProjectConnectorLoader(languageClient, connectorHolder, + inboundConnectorHolder); + } + connectorLoader.init(projectUri); + + // 3. Initialize the mediator handler with version-specific descriptors. + this.mediatorHandler = new MediatorHandler(); + mediatorHandler.init(projectUri, projectServerVersion, connectorHolder); + + // 4. Initialize the connection handler. + this.connectionHandler = new ConnectionHandler(); + connectionHandler.init(connectorHolder); + + // 5. Create the expression helper provider. + this.expressionHelperProvider = new ExpressionHelperProvider(projectUri); + + // 6. Create and load the resource finder. + this.resourceFinder = ResourceFinderFactory.getResourceFinder(isLegacyProject); + resourceFinder.loadDependentResources(projectUri); + + // 7. Resolve the synapse XSD path for this project's MI version. + this.synapseXsdPath = Utils.copyXSDFiles(projectUri); + + this.initialized = true; + log.log(Level.INFO, "ProjectContext initialized successfully for: " + projectUri); + } + + // ------------------------------------------------------------------------- + // Getters — identity fields + // ------------------------------------------------------------------------- + + /** + * Returns the root folder URI of this project. + * + * @return the project root URI (never {@code null}) + */ + public String getProjectUri() { + return projectUri; + } + + /** + * Returns whether this is a legacy (state-machine-based) MI project. + * + * @return {@code true} for legacy projects + */ + public boolean isLegacyProject() { + return isLegacyProject; + } + + /** + * Returns the WSO2 MI version string associated with this project. + * + * @return the project server version (e.g. {@code "4.3.0"}) + */ + public String getProjectServerVersion() { + return projectServerVersion; + } + + // ------------------------------------------------------------------------- + // Getter — synapseXsdPath (no setter; resolved in initProject()) + // ------------------------------------------------------------------------- + + /** + * Returns the path to the extracted root {@code synapse_config.xsd} for + * this project. + * + * @return the XSD path (non-null after {@link #initProject}) + * @throws IllegalStateException if {@link #initProject} has not been called + */ + public Path getSynapseXsdPath() { + checkInitialized(); + return synapseXsdPath; + } + + // ------------------------------------------------------------------------- + // Getters — connector holders (no setters; initialized in constructor) + // ------------------------------------------------------------------------- + + /** + * Returns the {@link ConnectorHolder} for this project's regular (outbound) + * connectors. + * + * @return the connector holder (never {@code null}) + */ + public ConnectorHolder getConnectorHolder() { + return connectorHolder; + } + + /** + * Returns the {@link InboundConnectorHolder} for this project's inbound + * connectors. + * + * @return the inbound connector holder (never {@code null}) + */ + public InboundConnectorHolder getInboundConnectorHolder() { + return inboundConnectorHolder; + } + + // ------------------------------------------------------------------------- + // Getters — service handlers (no setters; initialized in initProject()) + // ------------------------------------------------------------------------- + + /** + * Returns the {@link AbstractConnectorLoader} responsible for loading + * connectors for this project. + * + * @return the connector loader (non-null after {@link #initProject}) + * @throws IllegalStateException if {@link #initProject} has not been called + */ + public AbstractConnectorLoader getConnectorLoader() { + checkInitialized(); + return connectorLoader; + } + + /** + * Returns the {@link MediatorHandler} that provides completion and hover + * support for Synapse mediators in this project. + * + * @return the mediator handler (non-null after {@link #initProject}) + * @throws IllegalStateException if {@link #initProject} has not been called + */ + public MediatorHandler getMediatorHandler() { + checkInitialized(); + return mediatorHandler; + } + + /** + * Returns the {@link ExpressionHelperProvider} that handles {@code ${}} + * expression completions and documentation for this project. + * + * @return the expression helper provider (non-null after {@link #initProject}) + * @throws IllegalStateException if {@link #initProject} has not been called + */ + public ExpressionHelperProvider getExpressionHelperProvider() { + checkInitialized(); + return expressionHelperProvider; + } + + /** + * Returns the {@link ConnectionHandler} that manages named connections for + * this project. + * + * @return the connection handler (non-null after {@link #initProject}) + * @throws IllegalStateException if {@link #initProject} has not been called + */ + public ConnectionHandler getConnectionHandler() { + checkInitialized(); + return connectionHandler; + } + + /** + * Returns the {@link AbstractResourceFinder} that locates project-internal + * resources (endpoints, sequences, etc.) for this project. + * + * @return the resource finder (non-null after {@link #initProject}) + * @throws IllegalStateException if {@link #initProject} has not been called + */ + public AbstractResourceFinder getResourceFinder() { + checkInitialized(); + return resourceFinder; + } + + // ------------------------------------------------------------------------- + // Internal helpers + // ------------------------------------------------------------------------- + + /** + * Throws {@link IllegalStateException} if {@link #initProject} has not + * been called yet. Guards service-handler getters so that callers get a + * clear error message instead of a downstream {@code NullPointerException}. + */ + private void checkInitialized() { + if (!initialized) { + throw new IllegalStateException( + "ProjectContext not initialized. Call initProject() first. Project: " + projectUri); + } + } + + // ------------------------------------------------------------------------- + // Object overrides + // ------------------------------------------------------------------------- + + /** + * Returns a human-readable representation of this context, primarily for + * logging and debugging purposes. + * + * @return a string in the format {@code ProjectContext{uri=..., version=..., legacy=...}} + */ + @Override + public String toString() { + return "ProjectContext{" + + "projectUri='" + projectUri + '\'' + + ", projectServerVersion='" + projectServerVersion + '\'' + + ", isLegacyProject=" + isLegacyProject + + '}'; + } +} diff --git a/multi-workspace-support/resources/WorkspaceManager.java b/multi-workspace-support/resources/WorkspaceManager.java new file mode 100644 index 000000000..8654dd52a --- /dev/null +++ b/multi-workspace-support/resources/WorkspaceManager.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.eclipse.lemminx.customservice.synapse; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Central registry that maps open workspace folder URIs to their isolated + * {@link ProjectContext} instances. + * + *

In a Multi-Root Workspace scenario, VS Code may have several MI projects + * open simultaneously, each with its own MI version, connectors, and handlers. + * {@code WorkspaceManager} is the single source of truth for resolving: + *

+ *   document URI  →  correct {@link ProjectContext}
+ * 
+ * + *

All operations are thread-safe; the underlying map is a + * {@link ConcurrentHashMap} so concurrent LSP request threads can look up + * contexts without external synchronization. + * + *

URI contract: All URIs stored in and passed to this class must be + * in normalized {@code file:///} format (e.g. + * {@code file:///Users/me/ProjectA}). Callers are responsible for normalizing + * URIs before invoking any method. + */ +public class WorkspaceManager { + + private static final Logger log = Logger.getLogger(WorkspaceManager.class.getName()); + + /** + * Map from project root URI to the {@link ProjectContext} for that project. + * Uses {@link ConcurrentHashMap} for lock-free, thread-safe reads. + * + *

Key: normalized project root URI (e.g. {@code file:///Users/me/ProjectA}) + *
Value: the fully initialized {@link ProjectContext} for that root + */ + private final Map projects = new ConcurrentHashMap<>(); + + // ------------------------------------------------------------------------- + // Mutating operations + // ------------------------------------------------------------------------- + + /** + * Registers a new {@link ProjectContext} for the given project root URI. + * + *

If a context is already registered for {@code projectUri}, a warning + * is logged and the existing entry is not overwritten. Call + * {@link #removeProject} first if you need to replace a context. + * + * @param projectUri the normalized root URI of the project + * (e.g. {@code "file:///Users/me/ProjectA"}) + * @param context the fully initialized {@link ProjectContext} to register + */ + public void addProject(String projectUri, ProjectContext context) { + + if (projectUri == null || context == null) { + log.log(Level.WARNING, "addProject called with null projectUri or context — ignoring."); + return; + } + ProjectContext existing = projects.putIfAbsent(projectUri, context); + if (existing != null) { + log.log(Level.WARNING, + "A ProjectContext is already registered for URI: " + projectUri + + ". The existing context was NOT replaced. Call removeProject() first."); + } else { + log.log(Level.INFO, "Registered ProjectContext for: " + projectUri); + } + } + + /** + * Removes and returns the {@link ProjectContext} for the given project root URI. + * + *

If no context is registered for {@code projectUri}, a warning is + * logged and {@code null} is returned. + * + * @param projectUri the normalized root URI of the project to remove + * @return the removed {@link ProjectContext}, or {@code null} if not found + */ + public ProjectContext removeProject(String projectUri) { + + ProjectContext removed = projects.remove(projectUri); + if (removed == null) { + log.log(Level.WARNING, + "removeProject: no ProjectContext found for URI: " + projectUri); + } else { + log.log(Level.INFO, "Removed ProjectContext for: " + projectUri); + } + return removed; + } + + // ------------------------------------------------------------------------- + // Query operations + // ------------------------------------------------------------------------- + + /** + * Returns the {@link ProjectContext} for an exact project root URI match. + * + * @param projectUri the normalized root URI of the project + * @return the registered {@link ProjectContext}, or {@code null} if not found + */ + public ProjectContext getProject(String projectUri) { + + return projects.get(projectUri); + } + + /** + * Resolves a document URI to the {@link ProjectContext} of the project it + * belongs to, using a longest-prefix match. + * + *

Given a document URI such as + * {@code file:///Users/me/ProjectA/src/main/synapse-config/api/MyAPI.xml}, + * this method iterates all registered project root URIs and returns the + * context whose root URI is the longest prefix of the document URI. The + * longest-prefix rule ensures correctness when one project root is nested + * inside another. + * + *

Example: + *

+     *   Registered roots:
+     *     file:///Users/me/ProjectA      → ContextA
+     *     file:///Users/me/ProjectA/sub  → ContextB   (more specific)
+     *
+     *   getProjectForDocument("file:///Users/me/ProjectA/sub/foo.xml")
+     *     → returns ContextB  (longest match)
+     * 
+ * + * @param documentUri the normalized URI of the document being processed + * @return the best-matching {@link ProjectContext}, or {@code null} if no + * registered project contains the document + */ + public ProjectContext getProjectForDocument(String documentUri) { + + if (documentUri == null) { + return null; + } + + ProjectContext bestMatch = null; + int longestPrefixLength = -1; + + for (Map.Entry entry : projects.entrySet()) { + String projectUri = entry.getKey(); + // Use separator check to avoid false matches (e.g. "project" matching "project2"). + if ((documentUri.startsWith(projectUri + "/") || documentUri.equals(projectUri)) + && projectUri.length() > longestPrefixLength) { + longestPrefixLength = projectUri.length(); + bestMatch = entry.getValue(); + } + } + + if (bestMatch == null) { + log.log(Level.WARNING, + "getProjectForDocument: no registered project contains document: " + documentUri); + } + return bestMatch; + } + + /** + * Returns an unmodifiable snapshot of all currently registered + * {@link ProjectContext} instances. + * + *

The returned collection reflects the state of the registry at the + * moment of the call. Subsequent additions or removals are not reflected. + * + * @return a collection of all registered contexts (never {@code null}, + * may be empty) + */ + public Collection getAllProjects() { + + // Return a true snapshot — not a live view — so callers can iterate safely + // even if another thread adds/removes a project concurrently. + return Collections.unmodifiableCollection(new ArrayList<>(projects.values())); + } + + /** + * Returns {@code true} if a {@link ProjectContext} is registered for the + * given project root URI. + * + * @param projectUri the normalized root URI to query + * @return {@code true} if the project is registered, {@code false} otherwise + */ + public boolean hasProject(String projectUri) { + + return projects.containsKey(projectUri); + } + + /** + * Returns the number of {@link ProjectContext} instances currently registered. + * + * @return the project count (0 if no projects are registered) + */ + public int getProjectCount() { + + return projects.size(); + } +} \ No newline at end of file From 90bb83a50e640e5e4045f09dd9ecacfe41ae5e8e Mon Sep 17 00:00:00 2001 From: Harshana Amuwatte Date: Thu, 9 Apr 2026 23:27:50 +0530 Subject: [PATCH 09/12] docs: update multi-workspace support roadmap and stage descriptions in README --- multi-workspace-support/mws-README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/multi-workspace-support/mws-README.md b/multi-workspace-support/mws-README.md index 2302af4f3..1a5b73055 100644 --- a/multi-workspace-support/mws-README.md +++ b/multi-workspace-support/mws-README.md @@ -8,15 +8,18 @@ The current WSO2 Micro Integrator (MI) Language Server typically serves each MI ### Stage 1: Core XML Validation & Schema Isolation **(Completed)** Enable independent XML validation for multiple projects within a single Language Server instance using dynamic File Associations. -### Stage 2: Eliminating Global State & Singletons -* **Goal:** Transition legacy singletons (e.g., `ConnectorHolder`, `SynapseLanguageService`, Mediator Handlers) to be resolved per project context instead of a global state. -* **Action Plan:** Introduce `ProjectContext` and `WorkspaceManager` classes to manage memory scoped to individual projects. +### Stage 2: Eliminating Global State, Singletons & Context-Aware +**Goal:** Transition legacy singletons (e.g., `ConnectorHolder`, `SynapseLanguageService`, Mediator Handlers) to be resolved per project context instead of a global state. + +**Action Plan:** + +* Introduce `ProjectContext` and `WorkspaceManager` classes to manage memory scoped to individual projects. * *Reference Files:* `multi-workspace-support/resources/ProjectContext.java`, `multi-workspace-support/resources/WorkspaceManager.java` -### Stage 3: Context-Aware Resource Finders & Auto-Completions -Isolate Language Server features (Auto-Complete, Go-To-Definition) per workspace. Update `ResourceFinder` and properties handlers to filter sequences, endpoints, and registry resources based strictly on the active document's specific project URI, preventing invalid auto-complete suggestions from unrelated projects. +* Isolate Language Server features (Auto-Complete, Go-To-Definition) per workspace. + -### Stage 4: Language Client (VS Code Extension) Integration +### Stage 3: Language Client (VS Code Extension) Integration Update the frontend VS Code Extension to natively support the multi-project backend API configurations and event hooks. --- From f8446bf28f576fa041e1cdc11bc1fb9f596db483 Mon Sep 17 00:00:00 2001 From: Harshana Amuwatte Date: Fri, 10 Apr 2026 10:54:51 +0530 Subject: [PATCH 10/12] feat: implement multi-workspace support by replacing catalog-based validation with file associations and adding documentation --- .../multi-workspace-support}/mws-README.md | 8 +++++--- .../resources/ProjectContext.java | 0 .../resources/WorkspaceManager.java | 0 3 files changed, 5 insertions(+), 3 deletions(-) rename {multi-workspace-support => docs/multi-workspace-support}/mws-README.md (88%) rename {multi-workspace-support => docs/multi-workspace-support}/resources/ProjectContext.java (100%) rename {multi-workspace-support => docs/multi-workspace-support}/resources/WorkspaceManager.java (100%) diff --git a/multi-workspace-support/mws-README.md b/docs/multi-workspace-support/mws-README.md similarity index 88% rename from multi-workspace-support/mws-README.md rename to docs/multi-workspace-support/mws-README.md index 1a5b73055..e1a850406 100644 --- a/multi-workspace-support/mws-README.md +++ b/docs/multi-workspace-support/mws-README.md @@ -5,11 +5,12 @@ The current WSO2 Micro Integrator (MI) Language Server typically serves each MI ## Architecture Roadmap -### Stage 1: Core XML Validation & Schema Isolation **(Completed)** +### Stage 1: Core XML Validation & Schema Isolation **(Completed ✅)** Enable independent XML validation for multiple projects within a single Language Server instance using dynamic File Associations. ### Stage 2: Eliminating Global State, Singletons & Context-Aware -**Goal:** Transition legacy singletons (e.g., `ConnectorHolder`, `SynapseLanguageService`, Mediator Handlers) to be resolved per project context instead of a global state. +**Goal:** Transition legacy singletons (e.g., `ConnectorHolder`, `SynapseLanguageService`, Mediator Handlers ....) to be +resolved per project context instead of a global state. **Action Plan:** @@ -29,7 +30,8 @@ Update the frontend VS Code Extension to natively support the multi-project back ## Stage 1 Completed: LemMinX File Associations for Workspace Schema Validation ### Technical Overview -Introduced **File Associations** to replace the legacy namespace-based `.catalog` implementation. Instead of relying on rigid catalogs, this approach maps specific file path patterns (e.g., glob matches for a project folder) to a specific target schema path. This enables different schemas (e.g., MI 4.3.0 and 4.4.0) to be applied to different projects simultaneously without conflict in a single LemMinX instance. +Introduced `File Associations` to replace the legacy namespace-based `catalog` implementation. Instead of relying on +rigid catalogs, this approach maps specific file path patterns (e.g., glob matches for a project folder) to a specific target schema path. This enables different schemas (e.g., MI 4.3.0 and 4.4.0) to be applied to different projects simultaneously without conflict in a single LemMinX instance. ### Changelog & Implementation Details diff --git a/multi-workspace-support/resources/ProjectContext.java b/docs/multi-workspace-support/resources/ProjectContext.java similarity index 100% rename from multi-workspace-support/resources/ProjectContext.java rename to docs/multi-workspace-support/resources/ProjectContext.java diff --git a/multi-workspace-support/resources/WorkspaceManager.java b/docs/multi-workspace-support/resources/WorkspaceManager.java similarity index 100% rename from multi-workspace-support/resources/WorkspaceManager.java rename to docs/multi-workspace-support/resources/WorkspaceManager.java From ebd740986f15a1c80392c9a758e136170e75c25b Mon Sep 17 00:00:00 2001 From: Harshana Amuwatte Date: Fri, 10 Apr 2026 16:01:40 +0530 Subject: [PATCH 11/12] feat: add backward compatibility for legacy catalog settings and improve URI path sanitation for multi-root workspaces --- docs/multi-workspace-support/mws-README.md | 74 ++++++++++++------ .../resources/overview_img.png | Bin 0 -> 137656 bytes .../eclipse/lemminx/XMLLanguageServer.java | 32 +++++++- .../customservice/synapse/utils/Utils.java | 40 +++++++++- 4 files changed, 116 insertions(+), 30 deletions(-) create mode 100644 docs/multi-workspace-support/resources/overview_img.png diff --git a/docs/multi-workspace-support/mws-README.md b/docs/multi-workspace-support/mws-README.md index e1a850406..35d7e3f16 100644 --- a/docs/multi-workspace-support/mws-README.md +++ b/docs/multi-workspace-support/mws-README.md @@ -1,30 +1,18 @@ # Implementing Multi-Project Workspace Support for Current Language Server -## Problem in Current Language Server The current WSO2 Micro Integrator (MI) Language Server typically serves each MI project with a separate language server instance when multiple projects exist in a workspace. This limits scalability, increases resource overhead, and breaks true multi-folder workspace experiences. -## Architecture Roadmap +## Overview -### Stage 1: Core XML Validation & Schema Isolation **(Completed ✅)** -Enable independent XML validation for multiple projects within a single Language Server instance using dynamic File Associations. +Overview of Multi-Root Support -### Stage 2: Eliminating Global State, Singletons & Context-Aware -**Goal:** Transition legacy singletons (e.g., `ConnectorHolder`, `SynapseLanguageService`, Mediator Handlers ....) to be -resolved per project context instead of a global state. - -**Action Plan:** - -* Introduce `ProjectContext` and `WorkspaceManager` classes to manage memory scoped to individual projects. - * *Reference Files:* `multi-workspace-support/resources/ProjectContext.java`, `multi-workspace-support/resources/WorkspaceManager.java` +### 1. Core XML Validation & Schema Isolation **(Completed)** -* Isolate Language Server features (Auto-Complete, Go-To-Definition) per workspace. +### 2. Eliminating Global State, Singletons & Context-Aware +### 3. Language Client (VS Code Extension) Integration -### Stage 3: Language Client (VS Code Extension) Integration -Update the frontend VS Code Extension to natively support the multi-project backend API configurations and event hooks. ---- ---- --- ## Stage 1 Completed: LemMinX File Associations for Workspace Schema Validation @@ -36,14 +24,12 @@ rigid catalogs, this approach maps specific file path patterns (e.g., glob match ### Changelog & Implementation Details #### 1. `Utils.java` (`org.eclipse.lemminx/customservice/synapse/utils/Utils.java`) -* **Removed Catalog Dependencies:** Replaced `updateSynapseCatalogSettings` with `updateSynapseFileAssociationSettings`. The initialization parameters logic was modified to drop the `catalogs` array and successfully inject `fileAssociations` instead. -* **URI Path Sanitation Patch:** Updated internal path extraction from: - * *Old:* `String version = getServerVersion(projectUri, Constant.DEFAULT_MI_VERSION);` - * *New:* `String version = getServerVersion(getAbsolutePath(projectUri), Constant.DEFAULT_MI_VERSION);` - * *Rationale:* Previously, the `rootPath` field provided raw OS paths. In a Multi-Root architecture utilizing `workspaceFolders`, the data received is formatted as URIs (`file:///...`). Adding `getAbsolutePath()` ensures the URI is cleanly scrubbed before Java's `Path.of()` tries to read the `pom.xml`. +* **Backward Compatibility for Catalogs:** Re-added `updateSynapseCatalogSettings` to support an optional `useAssociationSettings` parameter during language server initialization. +* **File Associations Default:** Replaced the legacy namespace-based `catalog` reliance with `updateSynapseFileAssociationSettings` (used by default since `useAssociationSettings` defaults to `true`). The parameters logic drops the `catalogs` array and injects `fileAssociations` instead. +* **URI Path Sanitation Patch:** Handled the conversion of workspace folder URIs (`file:///...`) to absolute OS paths within `updateSynapseFileAssociationSettings`. By applying `getAbsolutePath()` at this higher level, we ensure all downstream logic (such as version extraction from `pom.xml` in `getServerVersion` and XSD extraction in `copyXSDFiles`) receives a standardized path format, preventing path resolution errors in a multi-root context. #### 2. `XMLLanguageServer.java` (`org.eclipse.lemminx/XMLLanguageServer.java`) -* Replaced the legacy initialization step `Utils.updateSynapseCatalogSettings(params)` with the new core standard: `Utils.updateSynapseFileAssociationSettings(params)`. +* **Backward Compatibility Toggle:** Updated the `initialize` step to dynamically read `useAssociationSettings` from `initializationOptions`. If set to `false`, the server defaults backward to `Utils.updateSynapseCatalogSettings`. If `true` (default), it invokes the new core standard: `Utils.updateSynapseFileAssociationSettings`. * *Temporary Bridge/Hack:* Because `SynapseLanguageService` (Stage 2) is not yet fully isolated for multi-root awareness, a temporary bridge was established by setting its default Path to the first project in the collection: `synapseLanguageService.setSynapseXSDPath(workspaceSchemas.values().iterator().next());` #### 3. `XMLWorkspaceService.java` (`org.eclipse.lemminx/XMLWorkspaceService.java`) @@ -54,3 +40,45 @@ Established three comprehensive multi-root tests demonstrating core functionalit 1. **Multi-Root Isolation:** Verifies that a single Language Server successfully provides isolated validations to two independent projects governed by distinct XSD files. 2. **Dynamic Connector Generation Test:** Ensures that dynamically generated connector schemas are instantly picked up by the validation engine logic, operating independently of server restarts. 3. **Dynamic Workspace Handling:** Asserts that when an entirely new project is dynamically appended to the workspace context at runtime, the language server successfully triggers its standard MI validations for the newly tracked space. +--- +## How to Test + +#### Testing via VS Code Extension +To verify the multi-root support directly within the VS Code environment: +1. **Build the Project**: Run the following command from the root directory to generate the server JAR: + ```bash + mvn clean install -DskipTests + ``` +2. **Update Extension Binary**: Navigate to the `target/` directory, locate the newly built JAR, and copy it into the `ls/` folder of your VS Code extension installation (replacing the existing JAR). +3. **Configure Settings Toggle**: When testing via the extension, you can toggle between the old and new logic by passing `useAssociationSettings` within your Language Server's `initializationOptions`. + * **Default Behavior**: If the `useAssociationSettings` field is **not mentioned** (omitted), the server will automatically default to `true` and work with the new **File Association** logic. + * If explicitly set to `true`, the language server continues to use the multi-root `fileAssociation` logic. + * If explicitly set to `false`, the language server natively falls back to utilizing the legacy single-project `catalog` settings extraction logic. + +#### Standalone Test Execution +For automated testing without the IDE: +1. **Navigate to Root**: + ```bash + cd mi-language-server + ``` +2. **Run Targeted Tests**: Execute the specialized multi-root validation suite using Maven: + ```bash + mvn -Dtest=CleanMultiRootValidationTest test + ``` + +--- + +## Next Steps + +### Eliminating Global State, Singletons & Context-Aware +Transition legacy singletons (e.g., `ConnectorHolder`, `SynapseLanguageService`, Mediator Handlers ....) to be +resolved per project context instead of a global state. + +* Introduce `ProjectContext` and `WorkspaceManager` classes to manage memory scoped to individual projects. + * *Reference Files:* `multi-workspace-support/resources/ProjectContext.java`, `multi-workspace-support/resources/WorkspaceManager.java` + +* Isolate Language Server features (Auto-Complete, Go-To-Definition) per workspace. + + +### Language Client (VS Code Extension) Integration +Update the frontend VS Code Extension to natively support the multi-project backend API configurations and event hooks. diff --git a/docs/multi-workspace-support/resources/overview_img.png b/docs/multi-workspace-support/resources/overview_img.png new file mode 100644 index 0000000000000000000000000000000000000000..fca370aae799a94763ab003dd196f488f744134b GIT binary patch literal 137656 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 KMNUMnLSTZ-N(;XL literal 0 HcmV?d00001 diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java index e7ded9c1c..e1baca66a 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java @@ -122,13 +122,30 @@ public XMLLanguageServer() { @Override public CompletableFuture initialize(InitializeParams params) { + boolean useAssociationSettings = true; try { - workspaceSchemas = Utils.updateSynapseFileAssociationSettings(params); - if (!workspaceSchemas.isEmpty()) { - synapseLanguageService.setSynapseXSDPath(workspaceSchemas.values().iterator().next()); + Object initOptionsForCheck = params.getInitializationOptions(); + if (initOptionsForCheck != null) { + com.google.gson.Gson gson = new com.google.gson.Gson(); + com.google.gson.JsonElement jsonElement = gson.toJsonTree(initOptionsForCheck); + if (jsonElement != null && jsonElement.isJsonObject() && jsonElement.getAsJsonObject().has("useAssociationSettings")) { + useAssociationSettings = jsonElement.getAsJsonObject().get("useAssociationSettings").getAsBoolean(); + } + } + + if (!useAssociationSettings) { + Path synapseSchemaPath = Utils.updateSynapseCatalogSettings(params); + if (synapseSchemaPath != null) { + synapseLanguageService.setSynapseXSDPath(synapseSchemaPath); + } + } else { + workspaceSchemas = Utils.updateSynapseFileAssociationSettings(params); + if (!workspaceSchemas.isEmpty()) { + synapseLanguageService.setSynapseXSDPath(workspaceSchemas.values().iterator().next()); + } } } catch (IOException | URISyntaxException e) { - LOGGER.log(Level.SEVERE, "Error while updating synapse file association settings", e); + LOGGER.log(Level.SEVERE, "Error while updating synapse settings", e); } Object initOptions = InitializationOptionsSettings.getSettings(params); this.lastKnownInitOptions = initOptions; @@ -138,6 +155,12 @@ public CompletableFuture initialize(InitializeParams params) { LogHelper.initializeRootLogger(languageClient, settings == null ? null : settings.getLogs()); LOGGER.info("Initializing XML Language server" + System.lineSeparator() + Platform.details()); + + if (!useAssociationSettings) { + LOGGER.info("======== WE ARE USING CATALOG SETTINGS ========"); + } else { + LOGGER.info("======== WE ARE USING FILE ASSOCIATION SETTINGS ========"); + } this.parentProcessId = params.getProcessId(); @@ -270,6 +293,7 @@ public void removeWorkspaceSchema(String folderUri) { public void triggerSettingsRefresh() { if (lastKnownInitOptions != null) { updateSettings(lastKnownInitOptions, false); + LOGGER.log(Level.WARNING, "Updated settings in Language Server with new workspace schemas: " + lastKnownInitOptions); } } 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 a7184ba56..4e328ef95 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 @@ -51,7 +51,7 @@ import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.WorkspaceFolder; import org.w3c.dom.Node; - +import com.google.gson.JsonPrimitive; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; @@ -1024,7 +1024,7 @@ private static void loadTemplate(Path path, String resourceFolder, Map updateSynapseFileAssociationSettings(InitializeP Map workspaceSchemas = new HashMap<>(); for (String folderUri : folderUris) { - Path schemaDir = copyXSDFiles(folderUri); + String projectUri = getAbsolutePath(folderUri); + Path schemaDir = copyXSDFiles(projectUri); workspaceSchemas.put(folderUri, schemaDir); } @@ -1142,6 +1143,39 @@ public static JsonElement updateSynapseFileAssociationSettings(JsonObject settin return settings; } + public static Path updateSynapseCatalogSettings(InitializeParams params) throws IOException, URISyntaxException { + + String projectUri = params.getRootPath(); + Object initParams = params.getInitializationOptions(); + Gson gson = new Gson(); + JsonElement jsonElement = gson.toJsonTree(initParams); + if (jsonElement != null && jsonElement.isJsonObject() && jsonElement.getAsJsonObject().has(Constant.SETTINGS)) { + JsonObject settings = jsonElement.getAsJsonObject().getAsJsonObject(Constant.SETTINGS); + Path schemaPath = copyXSDFiles(projectUri); + JsonElement updatedParams = updateSynapseCatalogSettings(settings, schemaPath); + JsonObject updatedSettings = new JsonObject(); + updatedSettings.add(Constant.SETTINGS, updatedParams); + params.setInitializationOptions(updatedSettings); + return schemaPath; + + } + return null; + } + + public static JsonElement updateSynapseCatalogSettings(JsonObject settings, Path schemaPath) + throws IOException, URISyntaxException { + + if (schemaPath != null) { + Path catalogPath = schemaPath.resolve("catalog.xml"); + JsonArray catalogsArray = new JsonArray(); + catalogsArray.add(new JsonPrimitive(catalogPath.toString())); + if (settings != null && settings.isJsonObject() && settings.has(Constant.XML)) { + settings.getAsJsonObject(Constant.XML).add(Constant.CATALOGS, catalogsArray); + } + } + return settings; + } + public static String deriveResourceKeyFromFilePath(String filePath) { String govIdentifier = "registry" + File.separator + "gov" + File.separator; From d6853d722b0210a2db3a5bd7119c950e17c7131a Mon Sep 17 00:00:00 2001 From: Harshana Amuwatte Date: Fri, 10 Apr 2026 17:18:50 +0530 Subject: [PATCH 12/12] applied log suggestions --- docs/multi-workspace-support/mws-README.md | 19 +++++++++++++------ .../resources/ProjectContext.java | 8 +++++++- .../resources/WorkspaceManager.java | 6 ++++++ .../eclipse/lemminx/XMLLanguageServer.java | 2 ++ .../eclipse/lemminx/XMLWorkspaceService.java | 9 ++++++++- .../customservice/synapse/utils/Utils.java | 3 +++ 6 files changed, 39 insertions(+), 8 deletions(-) diff --git a/docs/multi-workspace-support/mws-README.md b/docs/multi-workspace-support/mws-README.md index 35d7e3f16..e639af0c7 100644 --- a/docs/multi-workspace-support/mws-README.md +++ b/docs/multi-workspace-support/mws-README.md @@ -24,16 +24,23 @@ rigid catalogs, this approach maps specific file path patterns (e.g., glob match ### Changelog & Implementation Details #### 1. `Utils.java` (`org.eclipse.lemminx/customservice/synapse/utils/Utils.java`) -* **Backward Compatibility for Catalogs:** Re-added `updateSynapseCatalogSettings` to support an optional `useAssociationSettings` parameter during language server initialization. -* **File Associations Default:** Replaced the legacy namespace-based `catalog` reliance with `updateSynapseFileAssociationSettings` (used by default since `useAssociationSettings` defaults to `true`). The parameters logic drops the `catalogs` array and injects `fileAssociations` instead. -* **URI Path Sanitation Patch:** Handled the conversion of workspace folder URIs (`file:///...`) to absolute OS paths within `updateSynapseFileAssociationSettings`. By applying `getAbsolutePath()` at this higher level, we ensure all downstream logic (such as version extraction from `pom.xml` in `getServerVersion` and XSD extraction in `copyXSDFiles`) receives a standardized path format, preventing path resolution errors in a multi-root context. +* **Implementation of `updateSynapseFileAssociationSettings` Method:** + * Previously, the system relied on `updateSynapseCatalogSettings`. + * Added this new functionality to support File Association within the language server. #### 2. `XMLLanguageServer.java` (`org.eclipse.lemminx/XMLLanguageServer.java`) -* **Backward Compatibility Toggle:** Updated the `initialize` step to dynamically read `useAssociationSettings` from `initializationOptions`. If set to `false`, the server defaults backward to `Utils.updateSynapseCatalogSettings`. If `true` (default), it invokes the new core standard: `Utils.updateSynapseFileAssociationSettings`. -* *Temporary Bridge/Hack:* Because `SynapseLanguageService` (Stage 2) is not yet fully isolated for multi-root awareness, a temporary bridge was established by setting its default Path to the first project in the collection: `synapseLanguageService.setSynapseXSDPath(workspaceSchemas.values().iterator().next());` +* **Initialization Mode Selection:** The language server now dynamically determines the validation method during the `initialize` step by reading the `useAssociationSettings` flag from `initializationOptions`: + * **If `true` (Default):** The server invokes `Utils.updateSynapseFileAssociationSettings`, utilizing the new core standard based on file associations. + * **If `false`:** The server routes to `Utils.updateSynapseCatalogSettings` to process legacy catalog-based configurations. + * **If `null`/Undefined:** The server defaults to the file association method (`true`). +* **Temporary Bridge:** + * Because `SynapseLanguageService` (Stage 2) is not yet fully isolated for multi-root awareness, a temporary bridge was established. + * It sets the default Path to the first project in the collection: `synapseLanguageService.setSynapseXSDPath(workspaceSchemas.values().iterator().next());` #### 3. `XMLWorkspaceService.java` (`org.eclipse.lemminx/XMLWorkspaceService.java`) -* Enhanced `didChangeWorkspaceFolders` logic to capture dynamic workspace events correctly. If a user adds a new project to the workspace after server initialization, it accurately intercepts the action and generates/applies XSD schemas for the new folder. +* **Enhanced `didChangeWorkspaceFolders` Logic:** + * Updated to capture dynamic workspace events correctly. + * If a user adds a new project to the workspace after server initialization, it accurately intercepts the action and generates/applies XSD schemas for the new folder. #### 4. `CleanMultiRootValidationTest.java` (`.../extensions/contentmodel/CleanMultiRootValidationTest.java`) Established three comprehensive multi-root tests demonstrating core functionality: diff --git a/docs/multi-workspace-support/resources/ProjectContext.java b/docs/multi-workspace-support/resources/ProjectContext.java index 3cb46093b..e3fe99177 100644 --- a/docs/multi-workspace-support/resources/ProjectContext.java +++ b/docs/multi-workspace-support/resources/ProjectContext.java @@ -223,6 +223,8 @@ public ProjectContext(String projectUri, boolean isLegacyProject, String project */ public void initProject(String miServerPath, SynapseLanguageClientAPI languageClient) throws Exception { + log.log(Level.INFO, "Initializing ProjectContext for: " + projectUri); + // 1. Initialize inbound connector metadata. inboundConnectorHolder.init(projectUri, projectServerVersion); @@ -248,7 +250,11 @@ public void initProject(String miServerPath, SynapseLanguageClientAPI languageCl // 6. Create and load the resource finder. this.resourceFinder = ResourceFinderFactory.getResourceFinder(isLegacyProject); - resourceFinder.loadDependentResources(projectUri); + try { + resourceFinder.loadDependentResources(projectUri); + } catch (Exception e) { + log.log(Level.SEVERE, "Failed to initialize ProjectContext for: " + projectUri + ". Error: " + e.getMessage()); + } // 7. Resolve the synapse XSD path for this project's MI version. this.synapseXsdPath = Utils.copyXSDFiles(projectUri); diff --git a/docs/multi-workspace-support/resources/WorkspaceManager.java b/docs/multi-workspace-support/resources/WorkspaceManager.java index 8654dd52a..b36216f40 100644 --- a/docs/multi-workspace-support/resources/WorkspaceManager.java +++ b/docs/multi-workspace-support/resources/WorkspaceManager.java @@ -100,6 +100,11 @@ public void addProject(String projectUri, ProjectContext context) { * @return the removed {@link ProjectContext}, or {@code null} if not found */ public ProjectContext removeProject(String projectUri) { + + if (projectUri == null) { + log.log(Level.WARNING, "removeProject called with null projectUri \u2014 ignoring."); + return null; + } ProjectContext removed = projects.remove(projectUri); if (removed == null) { @@ -154,6 +159,7 @@ public ProjectContext getProject(String projectUri) { public ProjectContext getProjectForDocument(String documentUri) { if (documentUri == null) { + log.log(Level.WARNING, "getProjectForDocument called with null documentUri \u2014 returning null."); return null; } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java index e1baca66a..13bcbd3d7 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java @@ -135,12 +135,14 @@ public CompletableFuture initialize(InitializeParams params) { if (!useAssociationSettings) { Path synapseSchemaPath = Utils.updateSynapseCatalogSettings(params); + LOGGER.info("Synapse schema path set to: " + synapseSchemaPath); if (synapseSchemaPath != null) { synapseLanguageService.setSynapseXSDPath(synapseSchemaPath); } } else { workspaceSchemas = Utils.updateSynapseFileAssociationSettings(params); if (!workspaceSchemas.isEmpty()) { + LOGGER.info("Loaded " + workspaceSchemas.size() + " workspace schemas"); synapseLanguageService.setSynapseXSDPath(workspaceSchemas.values().iterator().next()); } } 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 2989ddeca..3beabebd7 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 @@ -18,6 +18,8 @@ import java.util.Map; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import java.util.logging.Logger; import org.eclipse.lemminx.commons.WorkspaceFolders; import org.eclipse.lemminx.customservice.synapse.utils.Constant; @@ -39,6 +41,8 @@ */ public class XMLWorkspaceService implements WorkspaceService, IXMLCommandService { + private static final Logger log = Logger.getLogger(XMLWorkspaceService.class.getName()); + private final XMLLanguageServer xmlLanguageServer; private final WorkspaceFolders workspaceFolders; @@ -88,6 +92,9 @@ public void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams params) { boolean hasSchemaChanges = false; if (params.getEvent().getRemoved() != null) { for (org.eclipse.lsp4j.WorkspaceFolder folder : params.getEvent().getRemoved()) { + if (log.isLoggable(Level.FINE)) { + log.fine("Removing workspace folder: " + folder.getUri()); + } xmlLanguageServer.removeWorkspaceSchema(folder.getUri()); hasSchemaChanges = true; } @@ -99,7 +106,7 @@ public void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams params) { xmlLanguageServer.addWorkspaceSchema(folder.getUri(), schemaDir); hasSchemaChanges = true; } catch (Exception e) { - // Ignore safely + log.log(Level.SEVERE, "Failed to copy XSD files for workspace folder: " + folder.getUri() + ". Error: " + e.getMessage()); } } } 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 4e328ef95..6a3753ded 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 @@ -1076,6 +1076,8 @@ private static String extractJarPath(URL resourceURL) throws URISyntaxException public static Map updateSynapseFileAssociationSettings(InitializeParams params) throws IOException, URISyntaxException { + logger.info("Updating Synapse file association settings"); + List folderUris = new ArrayList<>(); List workspaceFolders = params.getWorkspaceFolders(); if (workspaceFolders != null && !workspaceFolders.isEmpty()) { @@ -1123,6 +1125,7 @@ public static JsonElement updateSynapseFileAssociationSettings(JsonObject settin try { patternBase = Paths.get(new URI(folderUri)).toString().replace("\\", "/"); } catch (Exception e) { + logger.warning("Failed to convert folder URI to filesystem path: " + folderUri); patternBase = folderUri.replace("\\", "/"); }