diff --git a/lemminx-liberty/pom.xml b/lemminx-liberty/pom.xml index 28698ab8..263026b8 100644 --- a/lemminx-liberty/pom.xml +++ b/lemminx-liberty/pom.xml @@ -4,7 +4,7 @@ io.openliberty.tools liberty-langserver-lemminx jar - 2.0.2-SNAPSHOT + 2.1-SNAPSHOT lemminx-liberty https://github.com/OpenLiberty/liberty-language-server 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 0d8a93fa..a2092722 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 @@ -24,6 +24,7 @@ import org.eclipse.lsp4j.jsonrpc.CancelChecker; import io.openliberty.tools.langserver.lemminx.codeactions.AddAttribute; +import io.openliberty.tools.langserver.lemminx.codeactions.AddFeature; import io.openliberty.tools.langserver.lemminx.codeactions.CreateFile; import io.openliberty.tools.langserver.lemminx.codeactions.EditAttribute; import io.openliberty.tools.langserver.lemminx.codeactions.ReplaceFeature; @@ -55,6 +56,7 @@ private void registerCodeActions() { codeActionParticipants.put(LibertyDiagnosticParticipant.NOT_OPTIONAL_CODE, new EditAttribute()); codeActionParticipants.put(LibertyDiagnosticParticipant.IMPLICIT_NOT_OPTIONAL_CODE, new AddAttribute()); codeActionParticipants.put(LibertyDiagnosticParticipant.INCORRECT_FEATURE_CODE, new ReplaceFeature()); + codeActionParticipants.put(LibertyDiagnosticParticipant.MISSING_CONFIGURED_FEATURE_CODE, new AddFeature()); } } } 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 b61941a0..aeb1b13e 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 @@ -23,6 +23,7 @@ import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.CancelChecker; +import io.openliberty.tools.langserver.lemminx.data.FeatureListGraph; import io.openliberty.tools.langserver.lemminx.data.LibertyRuntime; import io.openliberty.tools.langserver.lemminx.services.FeatureService; import io.openliberty.tools.langserver.lemminx.services.SettingsService; @@ -30,20 +31,31 @@ import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.logging.Logger; public class LibertyDiagnosticParticipant implements IDiagnosticsParticipant { + private static final Logger LOGGER = Logger.getLogger(LibertyDiagnosticParticipant.class.getName()); + + public static final String LIBERTY_LEMMINX_SOURCE = "liberty-lemminx"; + public static final String MISSING_FILE_MESSAGE = "The resource at the specified location could not be found."; public static final String MISSING_FILE_CODE = "missing_file"; + public static final String MISSING_CONFIGURED_FEATURE_MESSAGE = "This config element does not configure a feature in the featureManager. Remove this element or add a relevant feature."; + public static final String MISSING_CONFIGURED_FEATURE_CODE = "lost_config_element"; + public static final String NOT_OPTIONAL_MESSAGE = "The specified resource cannot be skipped. Check location value or set optional to true."; public static final String NOT_OPTIONAL_CODE = "not_optional"; public static final String IMPLICIT_NOT_OPTIONAL_MESSAGE = "The specified resource cannot be skipped. Check location value or add optional attribute."; public static final String IMPLICIT_NOT_OPTIONAL_CODE = "implicit_not_optional"; public static final String INCORRECT_FEATURE_CODE = "incorrect_feature"; + + private Set includedFeatures = null; @Override public void doDiagnostics(DOMDocument domDocument, List diagnostics, @@ -53,22 +65,33 @@ public void doDiagnostics(DOMDocument domDocument, List diagnostics, try { validateDom(domDocument, diagnostics); } catch (IOException e) { + // LOGGER.severe("Error validating document " + domDocument.getDocumentURI()); + // LOGGER.severe(e.getMessage()); System.err.println("Error validating document " + domDocument.getDocumentURI()); System.err.println(e.getMessage()); } } - private void validateDom(DOMDocument domDocument, List list) throws IOException { + private void validateDom(DOMDocument domDocument, List diagnosticsList) throws IOException { List nodes = domDocument.getDocumentElement().getChildren(); + List tempDiagnosticsList = new ArrayList(); + + // TODO: consider initiallizing FeatureService even when features aren't detected + FeatureListGraph featureGraph = FeatureService.getInstance().getFeatureListGraph(); for (DOMNode node : nodes) { - if (LibertyConstants.FEATURE_MANAGER_ELEMENT.equals(node.getNodeName())) { - validateFeature(domDocument, list, node); - } else if (LibertyConstants.INCLUDE_ELEMENT.equals(node.getNodeName())) { - validateIncludeLocation(domDocument, list, node); + String nodeName = node.getNodeName(); + if (LibertyConstants.FEATURE_MANAGER_ELEMENT.equals(nodeName)) { + validateFeature(domDocument, diagnosticsList, node); + } else if (LibertyConstants.INCLUDE_ELEMENT.equals(nodeName)) { + validateIncludeLocation(domDocument, diagnosticsList, node); + } else if (featureGraph.isConfigElement(nodeName)) { + LOGGER.warning("Detected a config element!"); + holdConfigElement(domDocument, diagnosticsList, node, tempDiagnosticsList); } } - + LOGGER.warning("Entering validateConfigElements"); + validateConfigElements(diagnosticsList, tempDiagnosticsList, featureGraph); } private void validateFeature(DOMDocument domDocument, List list, DOMNode featureManager) { @@ -93,19 +116,20 @@ private void validateFeature(DOMDocument domDocument, List list, DOM Range range = XMLPositionUtility.createRange(featureTextNode.getStart(), featureTextNode.getEnd(), domDocument); String message = "ERROR: The feature \"" + featureName + "\" does not exist."; - list.add(new Diagnostic(range, message, DiagnosticSeverity.Error, "liberty-lemminx", INCORRECT_FEATURE_CODE)); + list.add(new Diagnostic(range, message, DiagnosticSeverity.Error, LIBERTY_LEMMINX_SOURCE, INCORRECT_FEATURE_CODE)); } else { if (includedFeatures.contains(featureName)) { Range range = XMLPositionUtility.createRange(featureTextNode.getStart(), featureTextNode.getEnd(), domDocument); String message = "ERROR: " + featureName + " is already included."; - list.add(new Diagnostic(range, message, DiagnosticSeverity.Error, "liberty-lemminx")); + list.add(new Diagnostic(range, message, DiagnosticSeverity.Error, LIBERTY_LEMMINX_SOURCE)); } else { includedFeatures.add(featureName); } } } } + this.includedFeatures = includedFeatures; } /** @@ -117,7 +141,7 @@ private void validateFeature(DOMDocument domDocument, List list, DOM * 2) performed in isConfigXMLFile * 4) not yet implemented/determined */ - private void validateIncludeLocation(DOMDocument domDocument, List list, DOMNode node) { + private void validateIncludeLocation(DOMDocument domDocument, List diagnosticsList, DOMNode node) { String locAttribute = node.getAttribute("location"); if (locAttribute == null) { return; @@ -131,7 +155,7 @@ private void validateIncludeLocation(DOMDocument domDocument, List l Range range = XMLPositionUtility.createRange(locNode.getStart(), locNode.getEnd(), domDocument); if (!locAttribute.endsWith(".xml")) { String message = "The specified resource is not an XML file."; - list.add(new Diagnostic(range, message, DiagnosticSeverity.Warning, "liberty-lemminx")); + diagnosticsList.add(new Diagnostic(range, message, DiagnosticSeverity.Warning, LIBERTY_LEMMINX_SOURCE)); return; } @@ -144,15 +168,38 @@ private void validateIncludeLocation(DOMDocument domDocument, List l if (!configFile.exists()) { DOMAttr optNode = node.getAttributeNode("optional"); if (optNode == null) { - list.add(new Diagnostic(range, IMPLICIT_NOT_OPTIONAL_MESSAGE, DiagnosticSeverity.Error, "liberty-lemminx", IMPLICIT_NOT_OPTIONAL_CODE)); + diagnosticsList.add(new Diagnostic(range, IMPLICIT_NOT_OPTIONAL_MESSAGE, DiagnosticSeverity.Error, LIBERTY_LEMMINX_SOURCE, IMPLICIT_NOT_OPTIONAL_CODE)); } else if (optNode.getValue().equals("false")) { Range optRange = XMLPositionUtility.createRange(optNode.getStart(), optNode.getEnd(), domDocument); - list.add(new Diagnostic(optRange, NOT_OPTIONAL_MESSAGE, DiagnosticSeverity.Error, "liberty-lemminx", NOT_OPTIONAL_CODE)); + diagnosticsList.add(new Diagnostic(optRange, NOT_OPTIONAL_MESSAGE, DiagnosticSeverity.Error, LIBERTY_LEMMINX_SOURCE, NOT_OPTIONAL_CODE)); } - list.add(new Diagnostic(range, MISSING_FILE_MESSAGE, DiagnosticSeverity.Warning, "liberty-lemminx", MISSING_FILE_CODE)); + diagnosticsList.add(new Diagnostic(range, MISSING_FILE_MESSAGE, DiagnosticSeverity.Warning, LIBERTY_LEMMINX_SOURCE, MISSING_FILE_CODE)); } } catch (IllegalArgumentException e) { - list.add(new Diagnostic(range, MISSING_FILE_MESSAGE, DiagnosticSeverity.Warning, "liberty-lemminx-exception", MISSING_FILE_CODE)); + diagnosticsList.add(new Diagnostic(range, MISSING_FILE_MESSAGE, DiagnosticSeverity.Warning, "liberty-lemminx-exception", MISSING_FILE_CODE)); + } + } + + private void holdConfigElement(DOMDocument domDocument, List diagnosticsList, DOMNode configElementNode, + List tempDiagnosticsList) { + String configElementName = configElementNode.getNodeName(); + Range range = XMLPositionUtility.createRange(configElementNode.getStart(), configElementNode.getEnd(), domDocument); + Diagnostic tempDiagnostic = new Diagnostic(range, MISSING_CONFIGURED_FEATURE_MESSAGE, null, + LIBERTY_LEMMINX_SOURCE, MISSING_CONFIGURED_FEATURE_CODE); + tempDiagnostic.setSource(configElementName); + tempDiagnosticsList.add(tempDiagnostic); + } + + private void validateConfigElements(List diagnosticsList, List tempDiagnosticsList, + FeatureListGraph featureGraph) { + for (Diagnostic tempDiagnostic : tempDiagnosticsList) { + String configElement = tempDiagnostic.getSource(); + Set includedFeaturesCopy = new HashSet(includedFeatures); + Set compatibleFeaturesList = featureGraph.getAllEnablers(configElement); + includedFeaturesCopy.retainAll(compatibleFeaturesList); + if (includedFeaturesCopy.isEmpty()) { + diagnosticsList.add(tempDiagnostic); + } } } -} +} \ No newline at end of file diff --git a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/codeactions/AddFeature.java b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/codeactions/AddFeature.java new file mode 100644 index 00000000..8a1650ae --- /dev/null +++ b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/codeactions/AddFeature.java @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2023 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 java.util.List; +import java.util.Set; + +import org.eclipse.lemminx.commons.CodeActionFactory; +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMNode; +import org.eclipse.lemminx.services.extensions.codeaction.ICodeActionParticipant; +import org.eclipse.lemminx.services.extensions.codeaction.ICodeActionRequest; +import org.eclipse.lemminx.utils.XMLPositionUtility; +import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; + +import io.openliberty.tools.langserver.lemminx.services.FeatureService; +import io.openliberty.tools.langserver.lemminx.util.LibertyConstants; + +public class AddFeature implements ICodeActionParticipant { + + // This method is still a work in progress. + @Override + public void doCodeAction(ICodeActionRequest request, List codeActions, CancelChecker cancelChecker) { + Diagnostic diagnostic = request.getDiagnostic(); + DOMDocument document = request.getDocument(); + try { + List nodes = document.getDocumentElement().getChildren(); + DOMNode featureManagerNode = null; + + for (DOMNode node : nodes) { + if (LibertyConstants.FEATURE_MANAGER_ELEMENT.equals(node.getNodeName())) { + featureManagerNode = node; + break; + } + } + + Position insertPosition; + String insertText; + DOMNode locationNode; + if (featureManagerNode == null) { + locationNode = document.getDocumentElement().getFirstChild(); + insertPosition = XMLPositionUtility.createRange(locationNode.getStart(), locationNode.getEnd(), document).getStart(); + insertText = "\nasdf\n"; + + } else { + locationNode = featureManagerNode.getLastChild(); + insertPosition = XMLPositionUtility.createRange(locationNode.getStart(), locationNode.getEnd(), document).getEnd(); + insertText = "\n\tmicroProfile-5.0"; + } + + String configElement = diagnostic.getSource(); + if (configElement.equals("ssl")) { + FeatureService.getInstance().getFeatureListGraph().get("ssl").getEnablers(); + } + Set featureCandidates = FeatureService.getInstance().getFeatureListGraph().get(configElement).getEnablers(); + codeActions.add(CodeActionFactory.insert("This is a test: " + featureCandidates, insertPosition, insertText, document.getTextDocument(), diagnostic)); + for (String feature : featureCandidates) { + String title = "Add feature " + feature; + codeActions.add(CodeActionFactory.insert(title, insertPosition, feature, + document.getTextDocument(), diagnostic)); + } + } catch (Exception e) { + + } + } +} diff --git a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/data/FeatureListGraph.java b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/data/FeatureListGraph.java new file mode 100644 index 00000000..a4fab68e --- /dev/null +++ b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/data/FeatureListGraph.java @@ -0,0 +1,144 @@ +/******************************************************************************* +* Copyright (c) 2023 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.data; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class FeatureListGraph { + private Map nodes; + // private Map> enablersCache; + // private Map> enablesCache; + + public FeatureListGraph() { + nodes = new HashMap(); + // enablersCache = new HashMap>(); + // enablesCache = new HashMap>(); + } + + public FeatureListNode addFeature(String nodeName) { + if (nodes.containsKey(nodeName)) { + return nodes.get(nodeName); + } + FeatureListNode node = new FeatureListNode(nodeName); + nodes.put(nodeName, node); + return node; + } + + public FeatureListNode addConfigElement(String nodeName) { + if (nodes.containsKey(nodeName)) { + return nodes.get(nodeName); + } + FeatureListNode node = new FeatureListNode(nodeName); + nodes.put(nodeName, node); + return node; + } + + public FeatureListNode get(String nodeName) { + return nodes.get(nodeName); + } + + public boolean isEmpty() { + return nodes.isEmpty(); + } + + public boolean isConfigElement(String featureListNode) { + if (!nodes.containsKey(featureListNode)) { + return false; + } + return nodes.get(featureListNode).isConfigElement(); + } + + /** + * Returns a superset of 'owning' features that enable a given config element or feature. + * @param elementName + * @return + */ + public Set getAllEnablers(String elementName) { + // if (enablersCache.containsKey(elementName)) { + // return enablersCache.get(elementName); + // } + if (!nodes.containsKey(elementName)) { + return null; + } + Set allEnablers = nodes.get(elementName).getEnablers(); + Deque queue = new ArrayDeque(allEnablers); + Set visited = new HashSet(); + while (!queue.isEmpty()) { + String node = queue.getFirst(); + queue.removeFirst(); + if (visited.contains(node)) { + continue; + } + Set enablers = nodes.get(node).getEnablers(); + visited.add(node); + allEnablers.addAll(enablers); + queue.addAll(enablers); + } + return allEnablers; + } + + /** + * Returns the set of supported features or config elements for a given feature. + * @param feature + * @return + */ + public Set getAllEnables(String feature) { + // if (enablesCache.containsKey(feature)) { + // return enablesCache.get(feature); + // } + if (!nodes.containsKey(feature)) { + return null; + } + Set allEnables = nodes.get(feature).getEnables(); + Deque queue = new ArrayDeque(allEnables); + Set visited = new HashSet(); + while (!queue.isEmpty()) { + String node = queue.getFirst(); + queue.removeFirst(); + if (visited.contains(node)) { + continue; + } + Set enablers = nodes.get(node).getEnables(); + visited.add(node); + allEnables.addAll(enablers); + queue.addAll(enablers); + } + // enablesCache.put(feature, allEnables); + return allEnables; + } + + // public Set getAllConfigElements(String feature) { + // Set configElements = new HashSet(); + // for (String node : getAllEnables(feature)) { + // if (isConfigElement(node)) { + // configElements.add(node); + // } + // } + // return configElements; + // } + + // public Set getAllEnabledFeatures(String feature) { + // Set enabledFeatures = new HashSet(); + // for (String node : getAllEnables(feature)) { + // if (!isConfigElement(node)) { + // enabledFeatures.add(node); + // } + // } + // return enabledFeatures; + // } +} \ No newline at end of file diff --git a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/data/FeatureListNode.java b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/data/FeatureListNode.java new file mode 100644 index 00000000..56c32513 --- /dev/null +++ b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/data/FeatureListNode.java @@ -0,0 +1,48 @@ +/******************************************************************************* +* Copyright (c) 2023 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.data; + +import java.util.HashSet; +import java.util.Set; + +public class FeatureListNode { + protected String nodeName; + protected Set enabledBy; + protected Set enables; + + public FeatureListNode(String nodeName) { + enabledBy = new HashSet(); + enables = new HashSet(); + this.nodeName = nodeName; + } + + public void addEnabler(String nodeName) { + enabledBy.add(nodeName); + } + + public void addEnables(String nodeName) { + enables.add(nodeName); + } + + public Set getEnablers() { + return enabledBy; + } + + public Set getEnables() { + return enables; + } + + public boolean isConfigElement() { + return this.nodeName.indexOf('.') == -1; + } +} \ No newline at end of file diff --git a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/models/feature/Feature.java b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/models/feature/Feature.java index 1b1d798e..13c3943c 100644 --- a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/models/feature/Feature.java +++ b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/models/feature/Feature.java @@ -16,6 +16,7 @@ import jakarta.xml.bind.annotation.XmlAttribute; import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlRootElement; +import java.util.List; @XmlRootElement(name = "feature") @XmlAccessorType(XmlAccessType.FIELD) @@ -33,6 +34,9 @@ public class Feature { private String version; WlpInformation wlpInformation; + private List configElement; + private List enables; + // Getter Methods public String getDescription() { @@ -67,6 +71,14 @@ public WlpInformation getWlpInformation() { return wlpInformation; } + public List getConfigElements() { + return configElement; + } + + public List getEnables() { + return enables; + } + // Setter Methods public void setDescription(String description) { @@ -100,4 +112,12 @@ public void setVersion(String version) { public void setWlpInformation(WlpInformation wlpInformation) { this.wlpInformation = wlpInformation; } + + public void setConfigElements(List configElement) { + this.configElement = configElement; + } + + public void setEnables(List enables) { + this.enables = enables; + } } diff --git a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/services/FeatureService.java b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/services/FeatureService.java index afe29631..548562ef 100644 --- a/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/services/FeatureService.java +++ b/lemminx-liberty/src/main/java/io/openliberty/tools/langserver/lemminx/services/FeatureService.java @@ -42,7 +42,8 @@ import com.google.gson.Gson; import com.google.gson.JsonParseException; - +import io.openliberty.tools.langserver.lemminx.data.FeatureListGraph; +import io.openliberty.tools.langserver.lemminx.data.FeatureListNode; import io.openliberty.tools.langserver.lemminx.models.feature.Feature; import io.openliberty.tools.langserver.lemminx.models.feature.FeatureInfo; import io.openliberty.tools.langserver.lemminx.models.feature.WlpInformation; @@ -72,8 +73,12 @@ public static FeatureService getInstance() { private List defaultFeatureList; private long featureUpdateTime; + // Contains the mapping a configElement to a set of features that use said configElement + private FeatureListGraph featureListGraph; + private FeatureService() { featureCache = new HashMap<>(); + featureListGraph = new FeatureListGraph(); featureUpdateTime = -1; } @@ -167,13 +172,18 @@ public List getFeatures(String libertyVersion, String libertyRuntime, i String featureCacheKey = libertyRuntime + "-" + libertyVersion; // if the features are already cached in the feature cache - if (featureCache.containsKey(featureCacheKey)) { + if (featureCache.containsKey(featureCacheKey) || !featureListGraph.isEmpty()) { LOGGER.info("Getting cached features for: " + featureCacheKey); return featureCache.get(featureCacheKey); } LOGGER.info("Getting features for: " + featureCacheKey); + List installedFeatures = getInstalledFeaturesList(documentURI, libertyRuntime, libertyVersion); + if (installedFeatures.size() != 0) { + return installedFeatures; + } + // if not a beta runtime, fetch features from maven central // - beta runtimes do not have a published features.json in mc if (!libertyVersion.endsWith("-beta")) { @@ -194,13 +204,6 @@ public List getFeatures(String libertyVersion, String libertyRuntime, i } } - // fetch installed features list - this would only happen if a features.json was not able to be downloaded from Maven Central - // This is the case for beta runtimes and for very old runtimes pre 18.0.0.2 (or if within the requestDelay window of 120 seconds). - List installedFeatures = getInstalledFeaturesList(documentURI, libertyRuntime, libertyVersion); - if (installedFeatures.size() != 0) { - return installedFeatures; - } - // return default feature list List defaultFeatures = getDefaultFeatureList(); return defaultFeatures; @@ -271,7 +274,6 @@ public List collectExistingFeatures(DOMNode featureManager, String curre */ private List getInstalledFeaturesList(String documentURI, String libertyRuntime, String libertyVersion) { List installedFeatures = new ArrayList(); - LibertyWorkspace libertyWorkspace = LibertyProjectsManager.getInstance().getWorkspaceFolder(documentURI); if (libertyWorkspace == null || libertyWorkspace.getWorkspaceString() == null) { return installedFeatures; @@ -287,7 +289,6 @@ private List getInstalledFeaturesList(String documentURI, String libert try { // Need to handle both local installation and container File featureListFile = null; - if (libertyWorkspace.isLibertyInstalled()) { Path featureListJAR = LibertyUtils.findLibertyFileForWorkspace(libertyWorkspace, Paths.get("bin", "tools", "ws-featurelist.jar")); if (featureListJAR != null && featureListJAR.toFile().exists()) { @@ -374,14 +375,32 @@ public List readFeaturesFromFeatureListFile(List installedFeat // Note: Only the public features are loaded when unmarshalling the passed featureListFile. if ((featureInfo.getFeatures() != null) && (featureInfo.getFeatures().size() > 0)) { - for (int i = 0; i < featureInfo.getFeatures().size(); i++) { - Feature f = featureInfo.getFeatures().get(i); + for (Feature f : featureInfo.getFeatures()) { f.setShortDescription(f.getDescription()); // The xml featureListFile does not have a wlpInformation element like the json does, but our code depends on looking up // features by the shortName found in wlpInformation. So create a WlpInformation object and initialize the shortName to // the feature name. WlpInformation wlpInfo = new WlpInformation(f.getName()); f.setWlpInformation(wlpInfo); + + String currentFeature = f.getName(); + List enables = f.getEnables(); + List configElements = f.getConfigElements(); + FeatureListNode currentFeatureNode = featureListGraph.addFeature(currentFeature); + if (enables != null) { + for (String enabledFeature : enables) { + FeatureListNode feature = featureListGraph.addFeature(enabledFeature); + feature.addEnabler(currentFeature); + currentFeatureNode.addEnables(enabledFeature); + } + } + if (configElements != null) { + for (String configElement : configElements) { + FeatureListNode configNode = featureListGraph.addConfigElement(configElement); + configNode.addEnabler(currentFeature); + currentFeatureNode.addEnables(configElement); + } + } } installedFeatures = featureInfo.getFeatures(); libertyWorkspace.setInstalledFeatureList(installedFeatures); @@ -391,4 +410,7 @@ public List readFeaturesFromFeatureListFile(List installedFeat return installedFeatures; } + public FeatureListGraph getFeatureListGraph() { + return this.featureListGraph; + } } diff --git a/lemminx-liberty/src/test/java/io/openliberty/LibertyDiagnosticTest.java b/lemminx-liberty/src/test/java/io/openliberty/LibertyDiagnosticTest.java index 2ff59d45..c6601d09 100644 --- a/lemminx-liberty/src/test/java/io/openliberty/LibertyDiagnosticTest.java +++ b/lemminx-liberty/src/test/java/io/openliberty/LibertyDiagnosticTest.java @@ -9,7 +9,11 @@ import org.junit.jupiter.api.Test; import io.openliberty.tools.langserver.lemminx.LibertyDiagnosticParticipant; +import io.openliberty.tools.langserver.lemminx.models.feature.Feature; +import io.openliberty.tools.langserver.lemminx.services.FeatureService; import io.openliberty.tools.langserver.lemminx.services.LibertyProjectsManager; +import io.openliberty.tools.langserver.lemminx.services.LibertyWorkspace; +import jakarta.xml.bind.JAXBException; import static org.eclipse.lemminx.XMLAssert.r; import static org.eclipse.lemminx.XMLAssert.ca; @@ -21,7 +25,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Collection; import java.util.Collections; public class LibertyDiagnosticTest { @@ -175,4 +178,73 @@ public void testDiagnosticsForInclude() throws IOException { XMLAssert.testDiagnosticsFor(serverXML, null, null, serverXMLFile.toURI().toString(), not_xml, multi_liner, not_optional, missing_xml, optional_not_defined, missing_xml2); } + + @Test + public void testConfigElementMissingFeatureManager() throws JAXBException { + File featureList = new File("src/test/resources/featurelist-ol-23.0.0.1-beta.xml"); + assertTrue(featureList.exists()); + FeatureService.getInstance().readFeaturesFromFeatureListFile(new ArrayList(), + new LibertyWorkspace(""), featureList); + + String serverXml = "ssl id=\"\"/>"; + Diagnostic config_for_missing_feature = new Diagnostic(); + config_for_missing_feature.setRange(r(0, 0, 0, serverXml.length())); + config_for_missing_feature.setCode(LibertyDiagnosticParticipant.MISSING_CONFIGURED_FEATURE_CODE); + config_for_missing_feature.setMessage(LibertyDiagnosticParticipant.MISSING_CONFIGURED_FEATURE_MESSAGE); + } + + @Test + public void testConfigElementDirect() throws JAXBException { + File featureList = new File("src/test/resources/featurelist-ol-23.0.0.1-beta.xml"); + assertTrue(featureList.exists()); + FeatureService.getInstance().readFeaturesFromFeatureListFile(new ArrayList(), + new LibertyWorkspace(""), featureList); + + String correctFeature = " ssl-1.0"; + String incorrectFeature = " jaxrs-2.0"; + String configElement = " "; + int diagnosticStart = configElement.indexOf("<"); + int diagnosticLength = configElement.trim().length(); + + String serverXML1 = String.join(newLine, + "", + " ", + correctFeature, + " ", + configElement, + "" + ); + XMLAssert.testDiagnosticsFor(serverXML1, null, null, serverXMLURI); + + String serverXML2 = String.join(newLine, + "", + " ", + incorrectFeature, + " ", + configElement, + "" + ); + + Diagnostic config_for_missing_feature = new Diagnostic(); + config_for_missing_feature.setRange(r(4, diagnosticStart, 4, diagnosticStart + diagnosticLength)); + config_for_missing_feature.setCode(LibertyDiagnosticParticipant.MISSING_CONFIGURED_FEATURE_CODE); + config_for_missing_feature.setMessage(LibertyDiagnosticParticipant.MISSING_CONFIGURED_FEATURE_MESSAGE); + + XMLAssert.testDiagnosticsFor(serverXML2, null, null, serverXMLURI, config_for_missing_feature); + } + + @Test + public void testConfigElementTransitive() { + File featureList = new File("src/test/resources/featurelist-ol-23.0.0.1-beta.xml"); + assertTrue(featureList.exists()); + String serverXML1 = String.join(newLine, + "", + " ", + " microProfile-5.0", + " ", + " ", + "" + ); + XMLAssert.testDiagnosticsFor(serverXML1, null, null, serverXMLURI); + } } \ No newline at end of file diff --git a/lemminx-liberty/src/test/java/io/openliberty/LibertyFeatureTest.java b/lemminx-liberty/src/test/java/io/openliberty/LibertyFeatureTest.java index 74264808..76603973 100644 --- a/lemminx-liberty/src/test/java/io/openliberty/LibertyFeatureTest.java +++ b/lemminx-liberty/src/test/java/io/openliberty/LibertyFeatureTest.java @@ -1,5 +1,6 @@ package io.openliberty; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -11,6 +12,7 @@ import org.eclipse.lsp4j.WorkspaceFolder; import org.junit.jupiter.api.Test; +import io.openliberty.tools.langserver.lemminx.data.FeatureListGraph; import io.openliberty.tools.langserver.lemminx.models.feature.Feature; import io.openliberty.tools.langserver.lemminx.services.FeatureService; import io.openliberty.tools.langserver.lemminx.services.LibertyProjectsManager; @@ -41,5 +43,16 @@ public void getInstalledFeaturesListTest() throws JAXBException { assertTrue(installedFeatures.equals(libWorkspace.getInstalledFeatureList())); // Check that list contains a beta feature assertTrue(installedFeatures.removeIf(f -> (f.getName().equals("cdi-4.0")))); + + // Check if config map gets built + FeatureListGraph fg = fs.getFeatureListGraph(); + assertEquals(1, fg.get("ssl").getEnablers().size()); + assertTrue(fg.get("ssl").getEnablers().contains("ssl-1.0")); + assertEquals(76, fg.getAllEnablers("ssl-1.0").size()); + assertEquals(235, fg.getAllEnablers("library").size()); + assertTrue(fg.getAllEnablers("ltpa").contains("adminCenter-1.0")); // direct enabler + assertTrue(fg.getAllEnablers("ssl").contains("microProfile-5.0")); // transitive enabler + assertTrue(fg.getAllEnables("microProfile-5.0").contains("ssl")); + assertTrue(fg.getAllEnables("jakartaee-8.0").contains("classloading")); } } diff --git a/liberty-ls/pom.xml b/liberty-ls/pom.xml index 4ab1bf44..31e31b44 100644 --- a/liberty-ls/pom.xml +++ b/liberty-ls/pom.xml @@ -5,7 +5,7 @@ io.openliberty.tools liberty-langserver - 2.0.2-SNAPSHOT + 2.1-SNAPSHOT liberty.langserver https://openliberty.io/