feat: introduce multi-project workspace support and schema isolation#523
feat: introduce multi-project workspace support and schema isolation#523harshanacz wants to merge 13 commits intowso2:mi-ext-alpha-releasefrom
Conversation
…r schema update verification
…e before and success after dynamic connector schema generation
Update Synapse schemas natively on workspace folder changes
…st to support workspace schema testing
…lidation with file associations and adding documentation
…ove URI path sanitation for multi-root workspaces
| * @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 | ||
| */ |
There was a problem hiding this comment.
Log Improvement Suggestion No: 1
| * @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 { | |
| log.log(Level.INFO, "Initializing ProjectContext for: " + projectUri); | |
| // 1. Initialize inbound connector metadata. |
| this.resourceFinder = ResourceFinderFactory.getResourceFinder(isLegacyProject); | ||
| resourceFinder.loadDependentResources(projectUri); |
There was a problem hiding this comment.
Log Improvement Suggestion No: 2
| this.resourceFinder = ResourceFinderFactory.getResourceFinder(isLegacyProject); | |
| resourceFinder.loadDependentResources(projectUri); | |
| } catch (Exception e) { | |
| log.log(Level.ERROR, "Failed to initialize ProjectContext for: " + projectUri + ". Error: " + e.getMessage()); | |
| throw e; | |
| } | |
| this.initialized = true; |
| * @param projectUri the normalized root URI of the project to remove | ||
| * @return the removed {@link ProjectContext}, or {@code null} if not found |
There was a problem hiding this comment.
Log Improvement Suggestion No: 3
| * @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) { | |
| if (projectUri == null) { | |
| log.log(Level.WARNING, "removeProject called with null projectUri — ignoring."); | |
| return null; | |
| } |
| * @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) { |
There was a problem hiding this comment.
Log Improvement Suggestion No: 4
| * @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) { | |
| public ProjectContext getProjectForDocument(String documentUri) { | |
| if (documentUri == null) { | |
| log.log(Level.WARNING, "getProjectForDocument called with null documentUri — returning null."); | |
| return null; | |
| } |
| if (!useAssociationSettings) { | ||
| Path synapseSchemaPath = Utils.updateSynapseCatalogSettings(params); | ||
| if (synapseSchemaPath != null) { | ||
| synapseLanguageService.setSynapseXSDPath(synapseSchemaPath); | ||
| } | ||
| } else { |
There was a problem hiding this comment.
Log Improvement Suggestion No: 5
| if (!useAssociationSettings) { | |
| Path synapseSchemaPath = Utils.updateSynapseCatalogSettings(params); | |
| if (synapseSchemaPath != null) { | |
| synapseLanguageService.setSynapseXSDPath(synapseSchemaPath); | |
| } | |
| } else { | |
| try { | |
| Path synapseSchemaPath = Utils.updateSynapseCatalogSettings(params); | |
| if (synapseSchemaPath != null) { | |
| LOGGER.info("Synapse schema path set to: " + synapseSchemaPath); | |
| synapseLanguageService.setSynapseXSDPath(synapseSchemaPath); | |
| } |
| } else { | ||
| workspaceSchemas = Utils.updateSynapseFileAssociationSettings(params); | ||
| if (!workspaceSchemas.isEmpty()) { | ||
| synapseLanguageService.setSynapseXSDPath(workspaceSchemas.values().iterator().next()); | ||
| } | ||
| } |
There was a problem hiding this comment.
Log Improvement Suggestion No: 6
| } else { | |
| workspaceSchemas = Utils.updateSynapseFileAssociationSettings(params); | |
| if (!workspaceSchemas.isEmpty()) { | |
| synapseLanguageService.setSynapseXSDPath(workspaceSchemas.values().iterator().next()); | |
| } | |
| } | |
| } else { | |
| workspaceSchemas = Utils.updateSynapseFileAssociationSettings(params); | |
| if (!workspaceSchemas.isEmpty()) { | |
| LOGGER.info("Loaded " + workspaceSchemas.size() + " workspace schemas"); | |
| synapseLanguageService.setSynapseXSDPath(workspaceSchemas.values().iterator().next()); | |
| } |
| boolean hasSchemaChanges = false; | ||
| if (params.getEvent().getRemoved() != null) { | ||
| for (org.eclipse.lsp4j.WorkspaceFolder folder : params.getEvent().getRemoved()) { | ||
| xmlLanguageServer.removeWorkspaceSchema(folder.getUri()); | ||
| hasSchemaChanges = true; | ||
| } |
There was a problem hiding this comment.
Log Improvement Suggestion No: 7
| boolean hasSchemaChanges = false; | |
| if (params.getEvent().getRemoved() != null) { | |
| for (org.eclipse.lsp4j.WorkspaceFolder folder : params.getEvent().getRemoved()) { | |
| xmlLanguageServer.removeWorkspaceSchema(folder.getUri()); | |
| hasSchemaChanges = true; | |
| } | |
| boolean hasSchemaChanges = false; | |
| if (params.getEvent().getRemoved() != null) { | |
| for (org.eclipse.lsp4j.WorkspaceFolder folder : params.getEvent().getRemoved()) { | |
| if (log.isDebugEnabled()) { | |
| log.debug("Removing workspace folder: " + folder.getUri()); | |
| } | |
| xmlLanguageServer.removeWorkspaceSchema(folder.getUri()); | |
| hasSchemaChanges = true; |
| hasSchemaChanges = true; | ||
| } catch (Exception e) { | ||
| // Ignore safely | ||
| } |
There was a problem hiding this comment.
Log Improvement Suggestion No: 8
| hasSchemaChanges = true; | |
| } catch (Exception e) { | |
| // Ignore safely | |
| } | |
| hasSchemaChanges = true; | |
| } catch (Exception e) { | |
| log.error("Failed to copy XSD files for workspace folder: " + folder.getUri() + ". Error: " + e.getMessage()); | |
| } |
| public static Map<String, Path> updateSynapseFileAssociationSettings(InitializeParams params) | ||
| throws IOException, URISyntaxException { |
There was a problem hiding this comment.
Log Improvement Suggestion No: 9
| public static Map<String, Path> updateSynapseFileAssociationSettings(InitializeParams params) | |
| throws IOException, URISyntaxException { | |
| public static Map<String, Path> updateSynapseFileAssociationSettings(InitializeParams params) | |
| throws IOException, URISyntaxException { | |
| LOGGER.info("Updating Synapse file association settings"); |
| patternBase = Paths.get(new URI(folderUri)).toString().replace("\\", "/"); | ||
| } catch (Exception e) { |
There was a problem hiding this comment.
Log Improvement Suggestion No: 10
| patternBase = Paths.get(new URI(folderUri)).toString().replace("\\", "/"); | |
| } catch (Exception e) { | |
| } catch (Exception e) { | |
| LOGGER.warn("Failed to convert folder URI to filesystem path: " + folderUri); | |
| patternBase = folderUri.replace("\\", "/"); |
There was a problem hiding this comment.
AI Agent Log Improvement Checklist
- The log-related comments and suggestions in this review were generated by an AI tool to assist with identifying potential improvements. Purpose of reviewing the code for log improvements is to improve the troubleshooting capabilities of our products.
- Please make sure to manually review and validate all suggestions before applying any changes. Not every code suggestion would make sense or add value to our purpose. Therefore, you have the freedom to decide which of the suggestions are helpful.
✅ Before merging this pull request:
- Review all AI-generated comments for accuracy and relevance.
- Complete and verify the table below. We need your feedback to measure the accuracy of these suggestions and the value they add. If you are rejecting a certain code suggestion, please mention the reason briefly in the suggestion for us to capture it.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Implementing Multi-Project Workspace Support for 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.
Overview
1. Core XML Validation & Schema Isolation (Completed)
2. Eliminating Global State, Singletons & Context-Aware
3. Language Client (VS Code Extension) Integration
Stage 1 Completed: LemMinX File Associations for Workspace Schema Validation
Technical Overview
Introduced
File Associationsto replace the legacy namespace-basedcatalogimplementation. Instead of relying onrigid 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)updateSynapseFileAssociationSettingsMethod:updateSynapseCatalogSettings.2.
XMLLanguageServer.java(org.eclipse.lemminx/XMLLanguageServer.java)initializestep by reading theuseAssociationSettingsflag frominitializationOptions:true(Default): The server invokesUtils.updateSynapseFileAssociationSettings, utilizing the new core standard based on file associations.false: The server routes toUtils.updateSynapseCatalogSettingsto process legacy catalog-based configurations.null/Undefined: The server defaults to the file association method (true).SynapseLanguageService(Stage 2) is not yet fully isolated for multi-root awareness, a temporary bridge was established.synapseLanguageService.setSynapseXSDPath(workspaceSchemas.values().iterator().next());3.
XMLWorkspaceService.java(org.eclipse.lemminx/XMLWorkspaceService.java)didChangeWorkspaceFoldersLogic:4.
CleanMultiRootValidationTest.java(.../extensions/contentmodel/CleanMultiRootValidationTest.java)Established three comprehensive multi-root tests demonstrating core functionality:
How to Test
Testing via VS Code Extension
To verify the multi-root support directly within the VS Code environment:
target/directory, locate the newly built JAR, and copy it into thels/folder of your VS Code extension installation (replacing the existing JAR).useAssociationSettingswithin your Language Server'sinitializationOptions.useAssociationSettingsfield is not mentioned (omitted), the server will automatically default totrueand work with the new File Association logic.true, the language server continues to use the multi-rootfileAssociationlogic.false, the language server natively falls back to utilizing the legacy single-projectcatalogsettings extraction logic.Standalone Test Execution
For automated testing without the IDE:
cd mi-language-servermvn -Dtest=CleanMultiRootValidationTest testNext Steps
Eliminating Global State, Singletons & Context-Aware
Transition legacy singletons (e.g.,
ConnectorHolder,SynapseLanguageService, Mediator Handlers ....) to beresolved per project context instead of a global state.
Introduce
ProjectContextandWorkspaceManagerclasses to manage memory scoped to individual projects.multi-workspace-support/resources/ProjectContext.java,multi-workspace-support/resources/WorkspaceManager.javaIsolate 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.