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/