diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ca8531bf..91f0d72e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,11 +32,19 @@ jobs: os: [ubuntu-latest, windows-latest] steps: - uses: actions/checkout@v3 + - name: Checkout ci.common + uses: actions/checkout@v3 + with: + repository: OpenLiberty/ci.common + path: ci.common - name: Set up Java uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: 17 + - name: Build ci common + working-directory: ci.common + run: ./mvnw -V clean install --batch-mode --no-transfer-progress --errors -DtrimStackTrace=false -DskipTests - name: Build Lemminx Liberty working-directory: ./lemminx-liberty run: ./mvnw clean package -ntp -DskipTests diff --git a/lemminx-liberty/pom.xml b/lemminx-liberty/pom.xml index dc3af9b5..c5ae7226 100644 --- a/lemminx-liberty/pom.xml +++ b/lemminx-liberty/pom.xml @@ -155,6 +155,29 @@ jakarta.xml.bind-api 4.0.0 + + org.mockito + mockito-core + 5.11.0 + test + + + org.mockito + mockito-junit-jupiter + 5.12.0 + test + + + io.openliberty.tools + ci.common + 1.8.36-SNAPSHOT + + + junit + junit + + + javax.activation activation diff --git a/lemminx-liberty/src/it/variable-processing-ol-it/pom.xml b/lemminx-liberty/src/it/variable-processing-ol-it/pom.xml new file mode 100644 index 00000000..8aea820b --- /dev/null +++ b/lemminx-liberty/src/it/variable-processing-ol-it/pom.xml @@ -0,0 +1,168 @@ + + + 4.0.0 + + io.openliberty.tools.test + variable-processing-ol-it + 1.0-SNAPSHOT + pom + + + + io.openliberty.tools + liberty-langserver-lemminx + @pom.version@ + + + org.eclipse.lemminx + org.eclipse.lemminx + ${lemminx.version} + provided + + + xml-apis + xml-apis + + + + + org.eclipse.lemminx + org.eclipse.lemminx + ${lemminx.version} + test + tests + test-jar + + + org.junit.jupiter + junit-jupiter-api + 5.6.1 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.6.1 + test + + + org.junit.jupiter + junit-jupiter-params + 5.6.1 + test + + + + + + + + io.openliberty.tools + liberty-maven-plugin + 3.7.1 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.0.0-M5 + + + + + + io.openliberty.tools + liberty-maven-plugin + + test + + io.openliberty + openliberty-runtime + 24.0.0.11 + + + + + install-server + pre-integration-test + + create + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + test-compile + compile + + testCompile + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + integration-test + + integration-test + + + + verify + install + + verify + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + true + + + + + + + lemminx-releases + https://repo.eclipse.org/content/repositories/lemminx-releases/ + + false + + + true + + + + lemminx-snapshots + https://repo.eclipse.org/content/repositories/lemminx-snapshots/ + + false + + + true + + + + diff --git a/lemminx-liberty/src/it/variable-processing-ol-it/src/main/liberty/config/bootstrap.properties b/lemminx-liberty/src/it/variable-processing-ol-it/src/main/liberty/config/bootstrap.properties new file mode 100644 index 00000000..d91d2998 --- /dev/null +++ b/lemminx-liberty/src/it/variable-processing-ol-it/src/main/liberty/config/bootstrap.properties @@ -0,0 +1,6 @@ +com.ibm.hpel.trace.purgeMaxSize=0 +default.http.port = 9080 +default.https.port = 9443 +com.ibm.ws.logging.message.format=json + +var.this.new=test \ No newline at end of file diff --git a/lemminx-liberty/src/it/variable-processing-ol-it/src/main/liberty/config/server.env b/lemminx-liberty/src/it/variable-processing-ol-it/src/main/liberty/config/server.env new file mode 100644 index 00000000..b6067508 --- /dev/null +++ b/lemminx-liberty/src/it/variable-processing-ol-it/src/main/liberty/config/server.env @@ -0,0 +1,4 @@ +WLP_LOGGING_CONSOLE_FORMAT=TBASIC +TEST_VAR=apple +NEW_VAR=test2 +NEW_V=true \ No newline at end of file diff --git a/lemminx-liberty/src/it/variable-processing-ol-it/src/main/liberty/config/server.xml b/lemminx-liberty/src/it/variable-processing-ol-it/src/main/liberty/config/server.xml new file mode 100644 index 00000000..c7c02bcc --- /dev/null +++ b/lemminx-liberty/src/it/variable-processing-ol-it/src/main/liberty/config/server.xml @@ -0,0 +1,43 @@ + + + + + javaee-6.0 + acmeCA-2.0 + servlet-3.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lemminx-liberty/src/it/variable-processing-ol-it/src/test/java/LibertyWorkspaceIT.java b/lemminx-liberty/src/it/variable-processing-ol-it/src/test/java/LibertyWorkspaceIT.java new file mode 100644 index 00000000..b2988a02 --- /dev/null +++ b/lemminx-liberty/src/it/variable-processing-ol-it/src/test/java/LibertyWorkspaceIT.java @@ -0,0 +1,187 @@ +package io.openliberty.tools.test; + +import java.util.List; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; + +import org.eclipse.lemminx.XMLAssert; +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.eclipse.lsp4j.TextDocumentEdit; +import org.eclipse.lsp4j.TextEdit; +import org.eclipse.lsp4j.WorkspaceFolder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import io.openliberty.tools.langserver.lemminx.services.LibertyProjectsManager; +import io.openliberty.tools.langserver.lemminx.util.LibertyUtils; + +import static org.eclipse.lemminx.XMLAssert.*; + +public class LibertyWorkspaceIT { + static String newLine = System.lineSeparator(); + + @AfterAll + public static void tearDown() { + LibertyProjectsManager.getInstance().cleanInstance(); + assert(LibertyProjectsManager.getInstance().getLibertyWorkspaceFolders().isEmpty()); + } + + @Test + public void testGetVariables() throws BadLocationException { + File testFolder = new File(System.getProperty("user.dir")); + File serverXmlFile = new File(testFolder, "src/main/liberty/config/server.xml"); + + //Configure Liberty workspace for testing + WorkspaceFolder testWorkspace = new WorkspaceFolder(testFolder.toURI().toString()); + List testWorkspaceFolders = new ArrayList(); + testWorkspaceFolders.add(testWorkspace); + LibertyProjectsManager.getInstance().setWorkspaceFolders(testWorkspaceFolders); + + String serverXML = String.join(newLine, // + "", // + " ", // + " javaee-6.0", // + " acmeCA-2.0", // + " ", // + " ",// + "" // + ); + + CompletionItem httpCompletion = c("${default.http.port}", "${default.http.port}"); + CompletionItem httpsCompletion = c("${default.https.port}", "${default.https.port}"); + final int TOTAL_ITEMS = 2; // total number of available completion items containing "default" + + XMLAssert.testCompletionFor(serverXML, null, serverXmlFile.toURI().toString(), TOTAL_ITEMS, httpCompletion, + httpsCompletion); + + } + + @Test + public void testVariableHover() throws BadLocationException { + + File testFolder = new File(System.getProperty("user.dir")); + File serverXmlFile = new File(testFolder, "src/main/liberty/config/server.xml"); + + //Configure Liberty workspace for testing + WorkspaceFolder testWorkspace = new WorkspaceFolder(testFolder.toURI().toString()); + List testWorkspaceFolders = new ArrayList(); + testWorkspaceFolders.add(testWorkspace); + LibertyProjectsManager.getInstance().setWorkspaceFolders(testWorkspaceFolders); + + String serverXML = String.join(newLine, // + "", // + " ", // + " javaee-6.0", // + " acmeCA-2.0", // + " ", // + " ",// + "" // + ); + + XMLAssert.assertHover(serverXML, serverXmlFile.toURI().toString(), + "default.http.port = 9080", + r(5, 33, 5, 55)); + } + + @Test + public void testInvalidVariableDiagnostic() { + + File testFolder = new File(System.getProperty("user.dir")); + File serverXmlFile = new File(testFolder, "src/main/liberty/config/server.xml"); + + //Configure Liberty workspace for testing + WorkspaceFolder testWorkspace = new WorkspaceFolder(testFolder.toURI().toString()); + List testWorkspaceFolders = new ArrayList(); + testWorkspaceFolders.add(testWorkspace); + LibertyProjectsManager.getInstance().setWorkspaceFolders(testWorkspaceFolders); + + String serverXML = String.join(newLine, // + "", // + " ", // + " javaee-6.0", // + " acmeCA-2.0", // + " ", // + " ",// + "" // + ); + + Diagnostic dup1 = new Diagnostic(); + dup1.setRange(r(5, 36, 5, 55)); + dup1.setCode("incorrect_variable"); + dup1.setSource("liberty-lemminx"); + dup1.setSeverity(DiagnosticSeverity.Error); + dup1.setMessage("ERROR: The variable \"default.httpsl.port\" does not exist."); + dup1.setData("default.httpsl.port"); + + Diagnostic dup2 = new Diagnostic(); + dup2.setRange(r(7, 31, 7, 50)); + dup2.setCode("incorrect_variable"); + dup2.setSource("liberty-lemminx"); + dup2.setSeverity(DiagnosticSeverity.Error); + dup2.setMessage("ERROR: The variable \"default.httpsj.port\" does not exist."); + dup2.setData("default.httpsj.port"); + + XMLAssert.testDiagnosticsFor(serverXML, null, null, serverXmlFile.toURI().toString(), false, dup1, dup2); + } + + @Test + public void testInvalidVariableDiagnosticWithCodeAction() throws IOException, BadLocationException { + + File testFolder = new File(System.getProperty("user.dir")); + File serverXmlFile = new File(testFolder, "src/main/liberty/config/server.xml"); + + //Configure Liberty workspace for testing + WorkspaceFolder testWorkspace = new WorkspaceFolder(testFolder.toURI().toString()); + List testWorkspaceFolders = new ArrayList(); + testWorkspaceFolders.add(testWorkspace); + LibertyProjectsManager.getInstance().setWorkspaceFolders(testWorkspaceFolders); + String serverXML = String.join(newLine, // + "", // + " ", // + " javaee-6.0", // + " acmeCA-2.0", // + " ", // + " ",// + "" // + ); + + Diagnostic invalid1 = new Diagnostic(); + invalid1.setRange(r(7, 31, 7, 44)); + invalid1.setCode("incorrect_variable"); + invalid1.setMessage("ERROR: The variable \"default.https\" does not exist."); + invalid1.setData("default.https"); + invalid1.setSource("liberty-lemminx"); + invalid1.setSeverity(DiagnosticSeverity.Error); + + XMLAssert.testDiagnosticsFor(serverXML, null, null, serverXmlFile.toURI().toString(), false, invalid1); + + // expecting code action to show only default.https.port + // 1. user has entered "default.https" + List variables = new ArrayList<>(); + variables.add("default.https.port"); + List codeActions = new ArrayList<>(); + for (String nextVar : variables) { + String variableInDoc = String.format("${%s}", nextVar); + TextEdit texted = te(invalid1.getRange().getStart().getLine(), invalid1.getRange().getStart().getCharacter(), + invalid1.getRange().getEnd().getLine(), invalid1.getRange().getEnd().getCharacter(), variableInDoc); + CodeAction invalidCodeAction = ca(invalid1, texted); + codeActions.add(invalidCodeAction); + invalidCodeAction.getEdit() + .getDocumentChanges() + .get(0).getLeft().getTextDocument() + .setUri(serverXmlFile.toURI().toString()); + } + + XMLAssert.testCodeActionsFor(serverXML, serverXmlFile.toURI().toString(), invalid1, codeActions.get(0)); + } +} diff --git a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyCodeActionParticipant.java b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyCodeActionParticipant.java index 0b5f8401..8d8b5cd1 100644 --- a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyCodeActionParticipant.java +++ b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyCodeActionParticipant.java @@ -18,6 +18,7 @@ import java.util.Map; import io.openliberty.tools.langserver.lemminx.codeactions.ReplacePlatform; +import io.openliberty.tools.langserver.lemminx.codeactions.ReplaceVariable; import org.eclipse.lemminx.services.extensions.codeaction.ICodeActionParticipant; import org.eclipse.lemminx.services.extensions.codeaction.ICodeActionRequest; import org.eclipse.lsp4j.CodeAction; @@ -60,6 +61,7 @@ private void registerCodeActions() { codeActionParticipants.put(LibertyDiagnosticParticipant.IMPLICIT_NOT_OPTIONAL_CODE, new AddAttribute()); codeActionParticipants.put(LibertyDiagnosticParticipant.INCORRECT_FEATURE_CODE, new ReplaceFeature()); codeActionParticipants.put(LibertyDiagnosticParticipant.INCORRECT_PLATFORM_CODE, new ReplacePlatform()); + codeActionParticipants.put(LibertyDiagnosticParticipant.INCORRECT_VARIABLE_CODE, new ReplaceVariable()); codeActionParticipants.put(LibertyDiagnosticParticipant.MISSING_CONFIGURED_FEATURE_CODE, new AddFeature()); codeActionParticipants.put(LibertyDiagnosticParticipant.IS_FILE_NOT_DIR_CODE, new RemoveTrailingSlash()); codeActionParticipants.put(LibertyDiagnosticParticipant.Is_DIR_NOT_FILE_CODE, new AddTrailingSlash()); diff --git a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyCompletionParticipant.java b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyCompletionParticipant.java index 83c9dc60..c2ccc534 100644 --- a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyCompletionParticipant.java +++ b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyCompletionParticipant.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; @@ -28,6 +29,7 @@ import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse; import org.eclipse.lemminx.utils.XMLPositionUtility; import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.CompletionItemKind; import org.eclipse.lsp4j.InsertReplaceEdit; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.TextEdit; @@ -43,6 +45,33 @@ public class LibertyCompletionParticipant extends CompletionParticipantAdapter { + + @Override + public void onAttributeValue(String valuePrefix, ICompletionRequest request, ICompletionResponse response, CancelChecker cancelChecker) throws Exception { + if (!LibertyUtils.isConfigXMLFile(request.getXMLDocument())) + return; + Properties variableProps = SettingsService.getInstance() + .getVariablesForServerXml(request.getXMLDocument() + .getDocumentURI()); + String variableName = valuePrefix.replace("$", "") + .replace("{", "") + .replace("}", ""); + variableProps.entrySet().stream().filter(it -> it.getKey().toString().toLowerCase().contains(variableName.toLowerCase())) + .forEach(variableProp -> { + String varValue = String.format("${%s}", variableProp.getKey()); + + Either edit = Either.forLeft(new + TextEdit(request.getReplaceRange(), varValue)); + CompletionItem completionItem = new CompletionItem(); + completionItem.setLabel(varValue); + completionItem.setTextEdit(edit); + completionItem.setFilterText(variableProp.getKey().toString()); + completionItem.setKind(CompletionItemKind.Value); + completionItem.setDocumentation(String.format("%s = %s", variableProp.getKey(),variableProp.getValue())); + response.addCompletionItem(completionItem); + }); + } + @Override public void onXMLContent(ICompletionRequest request, ICompletionResponse response, CancelChecker cancelChecker) throws IOException, BadLocationException { diff --git a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyDiagnosticParticipant.java b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyDiagnosticParticipant.java index 2b05aa37..79887574 100644 --- a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyDiagnosticParticipant.java +++ b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyDiagnosticParticipant.java @@ -13,6 +13,7 @@ package io.openliberty.tools.langserver.lemminx; import com.google.common.collect.Sets; +import io.openliberty.tools.langserver.lemminx.models.feature.VariableLoc; import org.eclipse.lemminx.dom.DOMAttr; import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.dom.DOMNode; @@ -40,6 +41,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Properties; import java.util.Set; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -71,6 +73,7 @@ public class LibertyDiagnosticParticipant implements IDiagnosticsParticipant { public static final String INCORRECT_FEATURE_CODE = "incorrect_feature"; public static final String INCORRECT_PLATFORM_CODE = "incorrect_platform"; + public static final String INCORRECT_VARIABLE_CODE = "incorrect_variable"; @Override public void doDiagnostics(DOMDocument domDocument, List diagnostics, @@ -107,8 +110,38 @@ private void validateDom(DOMDocument domDocument, List diagnosticsLi } } validateConfigElements(domDocument, diagnosticsList, tempDiagnosticsList, featureGraph, includedFeatures, featureManagerPresent); + validateVariables(domDocument,diagnosticsList); } + private void validateVariables(DOMDocument domDocument, List diagnosticsList) { + String docContent = domDocument.getTextDocument().getText(); + List variables = LibertyUtils.getVariablesFromTextContent(docContent); + Properties variablesMap = SettingsService.getInstance().getVariablesForServerXml(domDocument.getDocumentURI()); + if (variablesMap.isEmpty() && !variables.isEmpty()) { + String message = "WARNING: Variable resolution is not available for workspace %s. Please start the Liberty server for the workspace to enable variable resolution."; + LibertyWorkspace workspace = LibertyProjectsManager.getInstance().getWorkspaceFolder(domDocument.getDocumentURI()); + Range range = XMLPositionUtility.createRange(domDocument.getDocumentElement().getStartTagOpenOffset(), domDocument.getDocumentElement().getStartTagCloseOffset(), + domDocument); + Diagnostic diag = new Diagnostic(range, message.formatted(workspace.getWorkspaceURI().getPath()), DiagnosticSeverity.Warning, LIBERTY_LEMMINX_SOURCE); + diagnosticsList.add(diag); + return; + } + for (VariableLoc variable : variables) { + if (!variablesMap.containsKey(variable.getValue())) { + String variableInDoc = String.format("${%s}", variable.getValue()); + Range range = XMLPositionUtility.createRange(variable.getStartLoc(),variable.getEndLoc(), + domDocument); + String message = "ERROR: The variable \"" + variable.getValue() + "\" does not exist."; + + Diagnostic diag = new Diagnostic(range, message, DiagnosticSeverity.Error, LIBERTY_LEMMINX_SOURCE, INCORRECT_VARIABLE_CODE); + diag.setData(variable.getValue()); + diagnosticsList.add(diag); + } + } + } + + + private void validateFeaturesAndPlatforms(DOMDocument domDocument, List list, DOMNode featureManager, Set includedFeatures) { LibertyRuntime runtimeInfo = LibertyUtils.getLibertyRuntimeInfo(domDocument); diff --git a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyExtension.java b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyExtension.java index 509c4577..dc65d5e4 100644 --- a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyExtension.java +++ b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyExtension.java @@ -12,6 +12,7 @@ *******************************************************************************/ package io.openliberty.tools.langserver.lemminx; +import io.openliberty.tools.langserver.lemminx.services.LibertyWorkspace; import org.eclipse.lemminx.services.extensions.IDocumentLinkParticipant; import org.eclipse.lemminx.services.extensions.codeaction.ICodeActionParticipant; import org.eclipse.lemminx.services.extensions.completion.ICompletionParticipant; @@ -69,6 +70,13 @@ public void start(InitializeParams initializeParams, XMLExtensionsRegistry xmlEx documentLinkParticipant = new LibertyDocumentLinkParticipant(); xmlExtensionsRegistry.registerDocumentLinkParticipant(documentLinkParticipant); + + try { + SettingsService.getInstance() + .populateAllVariables(LibertyProjectsManager.getInstance().getLibertyWorkspaceFolders()); + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override @@ -95,5 +103,17 @@ public void doSave(ISaveContext saveContext) { SettingsService.getInstance().updateLibertySettings(xmlSettings); LOGGER.info("Liberty XML settings updated"); } + if (saveContext.getType() == SaveContextType.DOCUMENT) { + try { + LibertyWorkspace workspace = LibertyProjectsManager.getInstance().getWorkspaceFolder(saveContext.getUri()); + if (workspace != null) { + SettingsService.getInstance() + .populateVariablesForWorkspace(workspace); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + LOGGER.info("Liberty XML variables updated"); + } } } diff --git a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyHoverParticipant.java b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyHoverParticipant.java index 1e0bdf0d..612fcb4e 100644 --- a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyHoverParticipant.java +++ b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/LibertyHoverParticipant.java @@ -30,12 +30,12 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; -import java.util.Objects; import java.util.Optional; +import java.util.Properties; import java.util.Set; import java.util.logging.Logger; -import java.util.stream.Collectors; public class LibertyHoverParticipant implements IHoverParticipant { private static final Logger LOGGER = Logger.getLogger(LibertyHoverParticipant.class.getName()); @@ -47,6 +47,24 @@ public Hover onAttributeName(IHoverRequest request, CancelChecker cancelChecker) @Override public Hover onAttributeValue(IHoverRequest request, CancelChecker cancelChecker) { + List variables = LibertyUtils.getVariablesFromTextContent(request.getNode().getTextContent()); + Properties variableMap = SettingsService.getInstance() + .getVariablesForServerXml(request.getXMLDocument() + .getDocumentURI()); + StringBuilder stringBuilder = new StringBuilder(); + Iterator varIter = variables.iterator(); + while (varIter.hasNext()) { + VariableLoc variable = varIter.next(); + if (variableMap.containsKey(variable.getValue())) { + stringBuilder.append(String.format("%s = %s", variable.getValue(), variableMap.get(variable.getValue()))); + } + if (varIter.hasNext()) { + stringBuilder.append(System.lineSeparator()); + } + } + if (!stringBuilder.isEmpty()) { + return new Hover(new MarkupContent("plaintext", stringBuilder.toString())); + } return null; } diff --git a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/codeactions/ReplaceVariable.java b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/codeactions/ReplaceVariable.java new file mode 100644 index 00000000..237d1943 --- /dev/null +++ b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/codeactions/ReplaceVariable.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2024 IBM Corporation and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package io.openliberty.tools.langserver.lemminx.codeactions; + +import com.google.gson.JsonPrimitive; +import io.openliberty.tools.langserver.lemminx.services.SettingsService; +import org.eclipse.lemminx.commons.CodeActionFactory; +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.services.extensions.codeaction.ICodeActionParticipant; +import org.eclipse.lemminx.services.extensions.codeaction.ICodeActionRequest; +import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; + +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +public class ReplaceVariable implements ICodeActionParticipant { + private static final Logger LOGGER = Logger.getLogger(ReplaceVariable.class.getName()); + + @Override + public void doCodeAction(ICodeActionRequest request, List codeActions, CancelChecker cancelChecker) { + Diagnostic diagnostic = request.getDiagnostic(); + DOMDocument document = request.getDocument(); + try { + // Get a list of variables that partially match the specified invalid variables. + // Create a code action to replace the invalid variable with each possible valid variable. + // First, get the invalid variable. + String invalidVariable = null; + if (diagnostic.getData() instanceof JsonPrimitive) { + invalidVariable = ((JsonPrimitive) diagnostic.getData()).getAsString(); + } + if (diagnostic.getData() instanceof String) { + invalidVariable = (String) diagnostic.getData(); + } + final boolean replaceVariable = invalidVariable != null && !invalidVariable.isBlank(); + + if (replaceVariable) { + Properties existingVariables=SettingsService.getInstance().getVariablesForServerXml(document.getDocumentURI()); + // filter with entered word -> may not be required + String finalInvalidVariable = invalidVariable; + Set> filteredVariables = existingVariables + .entrySet().stream().filter(entry -> + entry.getKey().toString().toLowerCase() + .contains(finalInvalidVariable.toLowerCase())) + .collect(Collectors.toSet()); + for (Map.Entry nextVariable : filteredVariables) { + String title = "Replace attribute value with " + nextVariable.getKey(); + String variableInDoc = String.format("${%s}", nextVariable.getKey().toString()); + codeActions.add(CodeActionFactory.replace(title, diagnostic.getRange(), variableInDoc, document.getTextDocument(), diagnostic)); + } + /*for (Map.Entry nextVariable : existingVariables.entrySet()) { + String title = "Replace Variable with " + nextVariable.getKey() + " with value = " + nextVariable.getValue(); + codeActions.add(CodeActionFactory.replace(title, diagnostic.getRange(), nextVariable.getKey().toString(), document.getTextDocument(), diagnostic)); + }*/ + } + } catch (Exception e) { + // BadLocationException not expected + LOGGER.warning("Could not generate code action for replace attribute value: " + e); + } + } +} diff --git a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/models/feature/VariableLoc.java b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/models/feature/VariableLoc.java new file mode 100644 index 00000000..0e8b04b0 --- /dev/null +++ b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/models/feature/VariableLoc.java @@ -0,0 +1,50 @@ +/******************************************************************************* +* Copyright (c) 2024 IBM Corporation and others. +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License v. 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* IBM Corporation - initial API and implementation +*******************************************************************************/ +package io.openliberty.tools.langserver.lemminx.models.feature; + +public class VariableLoc { + + private String value; + private int startLoc; + private int endLoc; + + public VariableLoc(String value, int startLoc, int endLoc) { + this.value = value; + this.startLoc = startLoc; + this.endLoc = endLoc; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public int getStartLoc() { + return startLoc; + } + + public void setStartLoc(int startLoc) { + this.startLoc = startLoc; + } + + public int getEndLoc() { + return endLoc; + } + + public void setEndLoc(int endLoc) { + this.endLoc = endLoc; + } +} diff --git a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/services/SettingsService.java b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/services/SettingsService.java index a768155a..b3166154 100644 --- a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/services/SettingsService.java +++ b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/services/SettingsService.java @@ -12,9 +12,23 @@ *******************************************************************************/ package io.openliberty.tools.langserver.lemminx.services; +import io.openliberty.tools.common.plugins.config.ServerConfigDocument; +import io.openliberty.tools.langserver.lemminx.util.CommonLogger; +import io.openliberty.tools.langserver.lemminx.util.LibertyUtils; import org.eclipse.lemminx.utils.JSONUtility; import io.openliberty.tools.langserver.lemminx.models.settings.*; +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.logging.Logger; + +import static io.openliberty.tools.langserver.lemminx.util.LibertyUtils.findFileInWorkspace; + public class SettingsService { // Singleton so that only 1 Settings Service can be initialized and is @@ -28,12 +42,14 @@ public static SettingsService getInstance() { // default request delay is 10 seconds private static int DEFAULT_REQUEST_DELAY = 10; + private static final Logger LOGGER = Logger.getLogger(SettingsService.class.getName()); private SettingsService() { } private LibertySettings settings; + private Map variables; /** * Takes the xml settings object and parses out the Liberty Settings * @param xmlSettings - All xml settings provided by the client @@ -64,4 +80,63 @@ public int getRequestDelay() { return DEFAULT_REQUEST_DELAY; } + /** + * populate all variables for all available workspace folders + * + * @param workspaceFolders workspace folders + */ + public void populateAllVariables(Collection workspaceFolders) { + variables = new HashMap<>(); + for (LibertyWorkspace workspace : workspaceFolders) { + populateVariablesForWorkspace(workspace); + } + } + + /** + * read all variables from workspace directories + * + * @param workspace workspace + */ + public void populateVariablesForWorkspace(LibertyWorkspace workspace) { + Properties variablesForWorkspace = new Properties(); + Path pluginConfigFilePath = findFileInWorkspace(workspace, Paths.get("liberty-plugin-config.xml")); + if (pluginConfigFilePath != null) { + File installDirectory = LibertyUtils.getFileFromLibertyPluginXml(pluginConfigFilePath, "installDirectory"); + File serverDirectory = LibertyUtils.getFileFromLibertyPluginXml(pluginConfigFilePath, "serverDirectory"); + File userDirectory = LibertyUtils.getFileFromLibertyPluginXml(pluginConfigFilePath, "userDirectory"); + if (serverDirectory != null && installDirectory != null && userDirectory != null) { + try { + ServerConfigDocument serverConfigDocument = new ServerConfigDocument( + new CommonLogger(LOGGER), null, installDirectory, userDirectory, serverDirectory); + variablesForWorkspace.putAll(serverConfigDocument.getDefaultProperties()); + variablesForWorkspace.putAll(serverConfigDocument.getProperties()); + } catch (Exception e) { + LOGGER.warning("Variable resolution is not available because the necessary directory locations were not found in the liberty-plugin-config.xml file."); + LOGGER.info("Exception received: " + e.getMessage()); + } + } + } else { + LOGGER.warning("Could not find liberty-plugin-config.xml in workspace URI " + workspace.getWorkspaceString() + ". Variable resolution cannot be performed"); + } + variables.put(workspace.getWorkspaceString(), variablesForWorkspace); + } + + /** + * Get variables list for a workspace server xml file + * + * @param serverXmlURI serverXmlURI + * @return variables + */ + public Properties getVariablesForServerXml(String serverXmlURI) { + LibertyWorkspace workspace = LibertyProjectsManager.getInstance().getWorkspaceFolder(serverXmlURI); + Properties variableProps = new Properties(); + if (workspace == null) { + LOGGER.warning("Could not find workspace for server xml URI %s. Variable resolution cannot be performed.".formatted(serverXmlURI)); + } else if (variables.containsKey(workspace.getWorkspaceString())) { + variableProps = variables.get(workspace.getWorkspaceString()); + } else { + LOGGER.warning("Could not find variable mapping for workspace URI %s. Variable resolution cannot be performed.".formatted(workspace.getWorkspaceString())); + } + return variableProps; + } } diff --git a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/util/CommonLogger.java b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/util/CommonLogger.java new file mode 100644 index 00000000..e08718a3 --- /dev/null +++ b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/util/CommonLogger.java @@ -0,0 +1,78 @@ +/** + * (C) Copyright IBM Corporation 2024. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.openliberty.tools.langserver.lemminx.util; + +import io.openliberty.tools.common.CommonLoggerI; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class CommonLogger implements CommonLoggerI { + + private Logger loggerImpl; + + public CommonLogger(Logger mojoLogger) { + loggerImpl = mojoLogger; + } + + public Logger getLog() { + if (this.loggerImpl == null) { + this.loggerImpl = Logger.getLogger(CommonLogger.class.getName()); + } + return this.loggerImpl; + } + + @Override + public void debug(String msg) { + if (isDebugEnabled()) { + getLog().info(msg); + } + } + + @Override + public void debug(String msg, Throwable e) { + if (isDebugEnabled()) { + getLog().log(Level.INFO,msg, e); + } + } + + @Override + public void debug(Throwable e) { + if (isDebugEnabled()) { + getLog().log(Level.INFO,"", e); + } + } + + @Override + public void warn(String msg) { + getLog().warning(msg); + } + + @Override + public void info(String msg) { + getLog().info(msg); + } + + @Override + public void error(String msg) { + getLog().severe(msg); + } + + @Override + public boolean isDebugEnabled() { + return false; + } +} \ No newline at end of file diff --git a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/util/LibertyUtils.java b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/util/LibertyUtils.java index 44067e06..b685ab01 100644 --- a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/util/LibertyUtils.java +++ b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/util/LibertyUtils.java @@ -31,6 +31,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import io.openliberty.tools.langserver.lemminx.models.feature.VariableLoc; import org.eclipse.lemminx.dom.DOMDocument; import io.openliberty.tools.langserver.lemminx.data.LibertyRuntime; @@ -52,6 +53,11 @@ public class LibertyUtils { private static Thread thread; + //considering ${var} pattern for variable. do we have other representation for variable? + private static final String regex = "\\$\\{(.*?)\\}"; + // Compile the Regex. + private static final Pattern p = Pattern.compile(regex); + private LibertyUtils() { } @@ -573,4 +579,48 @@ public static String getPlatformDescription(String platformItem) { } return null; } + + /** + * get path for an element from the plugin config location xml file + * we will validate the file exists as well + * @param pluginConfigFilePath + * @param elementName + * @return the properties file to use, or null if not found + */ + public static File getFileFromLibertyPluginXml(Path pluginConfigFilePath, String elementName) { + String elementValue = XmlReader.getElementValue(pluginConfigFilePath, elementName); + if (elementValue != null) { + Path elementDir = Paths.get(elementValue); + if (elementDir.toFile().exists()) { + return elementDir.toFile(); + } + else { + LOGGER.warning("Path specified for %s in liberty-plugin-config path %s does not exist.".formatted(elementName, pluginConfigFilePath)); + } + } + else { + LOGGER.warning("Element %s does not exist in file %s.".formatted(elementName, pluginConfigFilePath)); + } + return null; + } + + /** + * read variables from text content + * @param docContent text content + * @return list of variables + */ + public static List getVariablesFromTextContent(String docContent) { + List variables = new ArrayList<>(); + // Find match between given string + // and regular expression + // using Pattern.matcher() + Matcher m = p.matcher(docContent); + // Get the subsequence + // using find() method + while (m.find()) { + VariableLoc variableLoc = new VariableLoc(m.group(1), m.start(1), m.end(1)); + variables.add(variableLoc); + } + return variables; + } } diff --git a/lemminx-liberty/src/test/java/io/openliberty/LibertyCompletionTest.java b/lemminx-liberty/src/test/java/io/openliberty/LibertyCompletionTest.java index 0663f4fa..b863ffee 100644 --- a/lemminx-liberty/src/test/java/io/openliberty/LibertyCompletionTest.java +++ b/lemminx-liberty/src/test/java/io/openliberty/LibertyCompletionTest.java @@ -1,16 +1,48 @@ package io.openliberty; +import io.openliberty.tools.langserver.lemminx.services.SettingsService; import org.eclipse.lemminx.XMLAssert; import org.eclipse.lemminx.commons.BadLocationException; import org.eclipse.lsp4j.CompletionItem; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; import static org.eclipse.lemminx.XMLAssert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) public class LibertyCompletionTest { + @Mock + SettingsService settingsService; + + MockedStatic settings; static String newLine = System.lineSeparator(); - static String serverXMLURI = "test/server.xml"; + static File srcResourcesDir = new File("src/test/resources/sample"); + static String serverXMLURI = new File(srcResourcesDir, "test/server.xml").toURI().toString(); + + @BeforeEach + public void setup(){ + settings = Mockito.mockStatic(SettingsService.class); + settings.when(SettingsService::getInstance).thenReturn(settingsService); + } + + @AfterEach + public void cleanup(){ + settings.close(); + } // Tests the availability of completion of XML elements provided by the // server.xsd file @@ -264,4 +296,78 @@ public void testFeatureRepetitionCompletionItem() throws BadLocationException { XMLAssert.testCompletionFor(serverXML, null, serverXMLURI, 3, sipServletCompletionItem); } + // Tests the + // availability of variable completion + @Test + public void testVariableCompletionItem() throws BadLocationException { + String serverXML = String.join(newLine, // + "", // + " ", // + " javaee-6.0", // + " acmeCA-2.0", // + " ", // + " ",// + "" // + ); + Map propsMap=new HashMap<>(); + propsMap.put("default.http.port","9080"); + propsMap.put("default.https.port","9443"); + propsMap.put("testVar","false"); + propsMap.put("testVar2","true"); + Properties props = new Properties(); + props.putAll(propsMap); + + when(settingsService.getVariablesForServerXml(any())).thenReturn(props); + CompletionItem httpCompletion = c("${default.http.port}", "${default.http.port}"); + CompletionItem httpsCompletion = c("${default.https.port}", "${default.https.port}"); + final int TOTAL_ITEMS = 2; // total number of available completion items + + XMLAssert.testCompletionFor(serverXML, null, serverXMLURI, TOTAL_ITEMS, httpCompletion, + httpsCompletion); + + serverXML = String.join(newLine, // + "", // + " ", // + " javaee-6.0", // + " acmeCA-2.0", // + " ", // + " ",// + "" // + ); + XMLAssert.testCompletionFor(serverXML, null, serverXMLURI, 0); + } + + @Test + public void testVariableCompletionItemWithDefaultXsdValues() throws BadLocationException { + String serverXML = String.join(newLine, // + "", // + " ", // + " javaee-6.0", // + " acmeCA-2.0", // + " ", // + " ",// + "" // + ); + Map propsMap=new HashMap<>(); + propsMap.put("default.http.port","9080"); + propsMap.put("default.https.port","9443"); + propsMap.put("testVar","false"); + propsMap.put("testVar2","true"); + Properties props = new Properties(); + props.putAll(propsMap); + + Map variables=new HashMap<>(); + variables.put(srcResourcesDir.toURI().toString(),props); + when(settingsService.getVariablesForServerXml(any())).thenReturn(props); + CompletionItem httpCompletion = c("${default.http.port}", "${default.http.port}"); + CompletionItem httpsCompletion = c("${default.https.port}", "${default.https.port}"); + final int TOTAL_ITEMS = 2; // total number of available completion items + // variables values -> default.http.port, default.https.port + XMLAssert.testCompletionFor(serverXML, null, serverXMLURI, TOTAL_ITEMS, httpCompletion, + httpsCompletion); + } + } diff --git a/lemminx-liberty/src/test/java/io/openliberty/LibertyDiagnosticTest.java b/lemminx-liberty/src/test/java/io/openliberty/LibertyDiagnosticTest.java index 197992f4..1486a954 100644 --- a/lemminx-liberty/src/test/java/io/openliberty/LibertyDiagnosticTest.java +++ b/lemminx-liberty/src/test/java/io/openliberty/LibertyDiagnosticTest.java @@ -1,14 +1,17 @@ package io.openliberty; +import io.openliberty.tools.langserver.lemminx.services.SettingsService; import org.eclipse.lemminx.XMLAssert; import org.eclipse.lemminx.commons.BadLocationException; import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.DiagnosticSeverity; import org.eclipse.lsp4j.TextDocumentEdit; import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4j.WorkspaceEdit; import org.eclipse.lsp4j.WorkspaceFolder; import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -19,6 +22,11 @@ import io.openliberty.tools.langserver.lemminx.services.LibertyProjectsManager; import io.openliberty.tools.langserver.lemminx.services.LibertyWorkspace; import jakarta.xml.bind.JAXBException; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; import static org.eclipse.lemminx.XMLAssert.r; import static org.eclipse.lemminx.XMLAssert.ca; @@ -26,15 +34,24 @@ import static org.eclipse.lemminx.XMLAssert.tde; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Collections; +import java.util.Map; +import java.util.Properties; +@ExtendWith(MockitoExtension.class) public class LibertyDiagnosticTest { + @Mock + SettingsService settingsService; + static String newLine = System.lineSeparator(); static File srcResourcesDir = new File("src/test/resources/sample"); @@ -44,6 +61,7 @@ public class LibertyDiagnosticTest { static List initList = new ArrayList(); LibertyProjectsManager libPM; LibertyWorkspace libWorkspace; + MockedStatic settings; @BeforeEach public void setupWorkspace() { @@ -51,6 +69,13 @@ public void setupWorkspace() { libPM = LibertyProjectsManager.getInstance(); libPM.setWorkspaceFolders(initList); libWorkspace = libPM.getLibertyWorkspaceFolders().iterator().next(); + settings= Mockito.mockStatic(SettingsService.class); + settings.when(SettingsService::getInstance).thenReturn(settingsService); + } + + @AfterEach + public void cleanup(){ + settings.close(); } @Test @@ -866,4 +891,141 @@ public void testInvalidPlatformDiagnosticWithCodeCompletion() throws BadLocation codeActions.get(13), codeActions.get(14), codeActions.get(15)); } + + @Test + public void testInvalidVariableDiagnostic() { + String serverXML = String.join(newLine, // + "", // + " ", // + " javaee-6.0", // + " acmeCA-2.0", // + " ", // + " ",// + "" // + ); + Map propsMap = new HashMap<>(); + propsMap.put("default.http.port", "9080"); + Properties props = new Properties(); + props.putAll(propsMap); + when(settingsService.getVariablesForServerXml(any())).thenReturn(props); + Diagnostic dup1 = new Diagnostic(); + dup1.setRange(r(7, 31, 7, 49)); + dup1.setCode(LibertyDiagnosticParticipant.INCORRECT_VARIABLE_CODE); + dup1.setSource("liberty-lemminx"); + dup1.setSeverity(DiagnosticSeverity.Error); + dup1.setMessage("ERROR: The variable \"default.https.port\" does not exist."); + dup1.setData("default.https.port"); + + XMLAssert.testDiagnosticsFor(serverXML, null, null, serverXMLURI, false, dup1); + } + + @Test + public void testInvalidVariableDiagnosticWithCodeAction() throws BadLocationException { + String serverXML = String.join(newLine, // + "", // + " ", // + " javaee-6.0", // + " acmeCA-2.0", // + " ", // + " ",// + "" // + ); + Map propsMap = new HashMap<>(); + propsMap.put("default.http.port", "9080"); + propsMap.put("default.https.port", "9443"); + Properties props = new Properties(); + props.putAll(propsMap); + when(settingsService.getVariablesForServerXml(any())).thenReturn(props); + Diagnostic invalid1 = new Diagnostic(); + invalid1.setRange(r(7, 31, 7, 44)); + invalid1.setCode(LibertyDiagnosticParticipant.INCORRECT_VARIABLE_CODE); + invalid1.setMessage("ERROR: The variable \"default.https\" does not exist."); + invalid1.setData("default.https"); + invalid1.setSource("liberty-lemminx"); + invalid1.setSeverity(DiagnosticSeverity.Error); + + XMLAssert.testDiagnosticsFor(serverXML, null, null, serverXMLURI, false, invalid1); + + // expecting code action to show only default.https.port + // 1. user has entered "default.https" + List variables = new ArrayList<>(); + variables.add("default.https.port"); + + + List codeActions = new ArrayList<>(); + for (String nextVar : variables) { + String variableInDoc = String.format("${%s}", nextVar); + TextEdit texted = te(invalid1.getRange().getStart().getLine(), invalid1.getRange().getStart().getCharacter(), + invalid1.getRange().getEnd().getLine(), invalid1.getRange().getEnd().getCharacter(), variableInDoc); + CodeAction invalidCodeAction = ca(invalid1, texted); + codeActions.add(invalidCodeAction); + invalidCodeAction.getEdit() + .getDocumentChanges() + .get(0).getLeft().getTextDocument() + .setUri(serverXMLURI); + } + + XMLAssert.testCodeActionsFor(serverXML, serverXMLURI, invalid1, codeActions.get(0)); + } + + + @Test + public void testNoVariableMappedDiagnostic() throws IOException { + String serverXML = String.join(newLine, // + "", // + " ", // + " javaee-6.0", // + " acmeCA-2.0", // + " ", // + " ",// + "" // + ); + when(settingsService.getVariablesForServerXml(any())).thenReturn(new Properties()); + Diagnostic dup1 = new Diagnostic(); + dup1.setRange(r(0, 0, 0, 43)); + String message="WARNING: Variable resolution is not available for workspace %s. Please start the Liberty server for the workspace to enable variable resolution."; + dup1.setMessage(message.formatted(srcResourcesDir.toURI().getPath())); + + XMLAssert.testDiagnosticsFor(serverXML, null, null, serverXMLURI, + dup1); + } + + @Test + public void testInvalidVariableRepeatedDiagnostic() { + String serverXML = String.join(newLine, // + "", // + " ", // + " javaee-6.0", // + " acmeCA-2.0", // + " ", // + " ",// + "" // + ); + Map propsMap = new HashMap<>(); + propsMap.put("default.http.port", "9080"); + Properties props = new Properties(); + props.putAll(propsMap); + when(settingsService.getVariablesForServerXml(any())).thenReturn(props); + Diagnostic dup1 = new Diagnostic(); + dup1.setRange(r(5, 36, 5, 54)); + dup1.setCode(LibertyDiagnosticParticipant.INCORRECT_VARIABLE_CODE); + dup1.setSource("liberty-lemminx"); + dup1.setSeverity(DiagnosticSeverity.Error); + dup1.setMessage("ERROR: The variable \"default.https.port\" does not exist."); + dup1.setData("default.https.port"); + + Diagnostic dup2 = new Diagnostic(); + dup2.setRange(r(7, 31, 7, 49)); + dup2.setCode(LibertyDiagnosticParticipant.INCORRECT_VARIABLE_CODE); + dup2.setSource("liberty-lemminx"); + dup2.setSeverity(DiagnosticSeverity.Error); + dup2.setMessage("ERROR: The variable \"default.https.port\" does not exist."); + dup2.setData("default.https.port"); + + XMLAssert.testDiagnosticsFor(serverXML, null, null, serverXMLURI, false, dup1, dup2); + } } \ No newline at end of file diff --git a/lemminx-liberty/src/test/java/io/openliberty/LibertyHoverTest.java b/lemminx-liberty/src/test/java/io/openliberty/LibertyHoverTest.java index dec3a2b7..2f886e7f 100644 --- a/lemminx-liberty/src/test/java/io/openliberty/LibertyHoverTest.java +++ b/lemminx-liberty/src/test/java/io/openliberty/LibertyHoverTest.java @@ -1,22 +1,66 @@ package io.openliberty; +import io.openliberty.tools.langserver.lemminx.services.LibertyProjectsManager; +import io.openliberty.tools.langserver.lemminx.services.LibertyWorkspace; +import io.openliberty.tools.langserver.lemminx.services.SettingsService; +import org.eclipse.lsp4j.WorkspaceFolder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static io.openliberty.tools.langserver.lemminx.LibertyXSDURIResolver.SERVER_XSD_RESOURCE; import org.eclipse.lemminx.XMLAssert; import org.eclipse.lemminx.commons.BadLocationException; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; import static org.eclipse.lemminx.XMLAssert.r; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +@ExtendWith(MockitoExtension.class) public class LibertyHoverTest { + @Mock + SettingsService settingsService; + + MockedStatic settings; static String newLine = System.lineSeparator(); - static String serverXMLURI = "test/server.xml"; + static File srcResourcesDir = new File("src/test/resources/sample"); + static List initList = new ArrayList(); + LibertyProjectsManager libPM; + LibertyWorkspace libWorkspace; + static String serverXMLURI = new File(srcResourcesDir, "test/server.xml").toURI().toString(); + + + @BeforeEach + public void setup(){ + initList.add(new WorkspaceFolder(srcResourcesDir.toURI().toString())); + libPM = LibertyProjectsManager.getInstance(); + libPM.setWorkspaceFolders(initList); + libWorkspace = libPM.getLibertyWorkspaceFolders().iterator().next(); + settings=Mockito.mockStatic(SettingsService.class); + settings.when(SettingsService::getInstance).thenReturn(settingsService); + } + + @AfterEach + public void cleanup(){ + settings.close(); + } @Test public void testFeatureHover() throws BadLocationException { - String serverXML = String.join(newLine, // "", // " ", // @@ -115,4 +159,26 @@ public void testPlatformHover() throws BadLocationException { r(2, 25, 3, 7)); } + + @Test + public void testVariableHover() throws BadLocationException { + String serverXML = String.join(newLine, // + "", // + " ", // + " javaee-6.0", // + " acmeCA-2.0", // + " ", // + " ",// + "" // + ); + Map propsMap = new HashMap<>(); + propsMap.put("default.http.port", "9080"); + Properties props = new Properties(); + props.putAll(propsMap); + when(settingsService.getVariablesForServerXml(any())).thenReturn(props); + XMLAssert.assertHover(serverXML, serverXMLURI, + "default.http.port = 9080", + r(5, 33, 5, 55)); + } } diff --git a/lemminx-liberty/src/test/java/io/openliberty/SettingsServiceTest.java b/lemminx-liberty/src/test/java/io/openliberty/SettingsServiceTest.java new file mode 100644 index 00000000..ee70c506 --- /dev/null +++ b/lemminx-liberty/src/test/java/io/openliberty/SettingsServiceTest.java @@ -0,0 +1,89 @@ +package io.openliberty; + +import io.openliberty.tools.langserver.lemminx.services.LibertyProjectsManager; +import io.openliberty.tools.langserver.lemminx.services.LibertyWorkspace; +import io.openliberty.tools.langserver.lemminx.services.SettingsService; +import io.openliberty.tools.langserver.lemminx.util.LibertyUtils; +import org.eclipse.lsp4j.WorkspaceFolder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + +public class SettingsServiceTest { + File resourcesDir = new File("src/test/resources/serverConfig"); + File resourcesLibertyDir = new File("src/test/resources/serverConfig", "liberty"); + File serverDir = new File("src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer"); + File installDir = new File("src/test/resources/serverConfig/liberty/wlp"); + File userDir = new File("src/test/resources/serverConfig/liberty/wlp/usr"); + + MockedStatic libertyUtils; + static List initList = new ArrayList(); + LibertyProjectsManager libPM; + LibertyWorkspace libWorkspace; + + @BeforeEach + public void setupWorkspace() { + initList.add(new WorkspaceFolder(resourcesDir.toURI().toString())); + libPM = LibertyProjectsManager.getInstance(); + libPM.setWorkspaceFolders(initList); + libWorkspace = libPM.getLibertyWorkspaceFolders().iterator().next(); + libertyUtils = Mockito.mockStatic(LibertyUtils.class); + libertyUtils.when(() -> LibertyUtils.findFileInWorkspace(any(), any())) + .thenReturn(Path.of(resourcesDir.getPath())); + libertyUtils.when(() -> LibertyUtils.getFileFromLibertyPluginXml(any(), eq("serverDirectory"))) + .thenReturn(serverDir); + libertyUtils.when(() -> LibertyUtils.getFileFromLibertyPluginXml(any(), eq("installDirectory"))) + .thenReturn(installDir); + libertyUtils.when(() -> LibertyUtils.getFileFromLibertyPluginXml(any(), eq("userDirectory"))) + .thenReturn(userDir); + } + + @AfterEach + public void cleanup() { + libertyUtils.close(); + } + + @Test + public void testPopulateAllVariables() throws IOException { + List initList = new ArrayList<>(); + initList.add(new LibertyWorkspace(resourcesLibertyDir.toURI().toString())); + SettingsService.getInstance().populateAllVariables(initList); + Properties variables = SettingsService.getInstance().getVariablesForServerXml(resourcesLibertyDir.toURI().toString()); + + assertNotNull(variables); + // bootstrap.properties.override added in server.env and bootstrap.properties + // bootstrap.properties gets highest priority + assertEquals("true", variables.get("bootstrap.properties.override")); + + /* server.env file read order. http.port is specified in 3 places + * 1. {wlp.install.dir}/etc/ + * 2. {wlp.user.dir}/shared/ + * 3. {server.config.dir}/ + */ + assertEquals("1111", variables.get("http.port")); + + // httpPort defined in multiple places. highest precedence is for configDropins/overrides + assertEquals("7777", variables.get("httpPort")); + + // variable defined in variables.override + assertEquals("true", variables.get("variables.override")); + + // checking default properties are set or not + assertEquals("includes", variables.get("includeLocation")); + } + +} diff --git a/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/etc/server.env b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/etc/server.env new file mode 100644 index 00000000..437b75ce --- /dev/null +++ b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/etc/server.env @@ -0,0 +1,3 @@ +etc.unique=true +shared.overriden=false +http.port=9080 \ No newline at end of file diff --git a/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/bootstrap.properties b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/bootstrap.properties new file mode 100644 index 00000000..883c74ad --- /dev/null +++ b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/bootstrap.properties @@ -0,0 +1,5 @@ +extras.filename=extraFeatures.xml +THAT_VALUE=DEFINED +bootstrap.properties.override=true +variables.override=false +HOST=localhost \ No newline at end of file diff --git a/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/configDropins/defaults/server.xml b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/configDropins/defaults/server.xml new file mode 100644 index 00000000..be4c1a48 --- /dev/null +++ b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/configDropins/defaults/server.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/configDropins/overrides/server.xml b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/configDropins/overrides/server.xml new file mode 100644 index 00000000..16445372 --- /dev/null +++ b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/configDropins/overrides/server.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env new file mode 100644 index 00000000..9caccade --- /dev/null +++ b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env @@ -0,0 +1,5 @@ +keystore_password=C7ANPlAi0MQD154BJ5ZOURn +http.port=1111 +overriden_value=old_value +this_value=DEFINED +bootstrap.properties.override=false \ No newline at end of file diff --git a/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.xml b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.xml new file mode 100644 index 00000000..77dfb53b --- /dev/null +++ b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.xml @@ -0,0 +1,29 @@ + + + jsp-2.3 + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/variables/httpPort b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/variables/httpPort new file mode 100644 index 00000000..04a3078b --- /dev/null +++ b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/variables/httpPort @@ -0,0 +1 @@ +9080 \ No newline at end of file diff --git a/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/variables/variables.override b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/variables/variables.override new file mode 100644 index 00000000..f32a5804 --- /dev/null +++ b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/variables/variables.override @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/shared/config/environment.xml b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/shared/config/environment.xml new file mode 100644 index 00000000..0558b8ce --- /dev/null +++ b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/shared/config/environment.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/shared/server.env b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/shared/server.env new file mode 100644 index 00000000..dcbb2923 --- /dev/null +++ b/lemminx-liberty/src/test/resources/serverConfig/liberty/wlp/usr/shared/server.env @@ -0,0 +1,3 @@ +shared.unique=true +shared.overriden=true +http.port=9081 \ No newline at end of file