Skip to content

Commit

Permalink
Use default feature list for config element validation (#226)
Browse files Browse the repository at this point in the history
* Use default feature list for config element validation

* Few updates on how list is loaded and stored
  • Loading branch information
cherylking authored Oct 11, 2023
1 parent 7ebb1d5 commit 8a145b5
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ private void validateDom(DOMDocument domDocument, List<Diagnostic> diagnosticsLi
List<Diagnostic> tempDiagnosticsList = new ArrayList<Diagnostic>();
includedFeatures = new HashSet<>();
LibertyWorkspace workspace = LibertyProjectsManager.getInstance().getWorkspaceFolder(domDocument.getDocumentURI());
// TODO: Consider adding a cached feature list onto repo to optimize
FeatureListGraph featureGraph = (workspace == null) ? new FeatureListGraph() : workspace.getFeatureListGraph();
for (DOMNode node : nodes) {
String nodeName = node.getNodeName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*******************************************************************************/
package io.openliberty.tools.langserver.lemminx.codeactions;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
Expand All @@ -30,7 +32,11 @@
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.FeatureListNode;
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 io.openliberty.tools.langserver.lemminx.util.LibertyConstants;

public class AddFeature implements ICodeActionParticipant {
Expand All @@ -57,14 +63,30 @@ public void doCodeAction(ICodeActionRequest request, List<CodeAction> codeAction
Diagnostic diagnostic = request.getDiagnostic();
DOMDocument document = request.getDocument();
TextDocument textDocument = document.getTextDocument();

LibertyWorkspace ws = LibertyProjectsManager.getInstance().getWorkspaceFolder(document.getDocumentURI());
if (ws == null) {
LOGGER.warning("Could not add quick fix for missing feature because could not find Liberty workspace for document: "+document.getDocumentURI());
return;
}

FeatureListNode flNode = ws.getFeatureListGraph().get(diagnostic.getSource());
if (flNode == null) {
LOGGER.warning("Could not add quick fix for missing feature for config element due to missing information in the feature list: "+diagnostic.getSource());
return;
}

// getAllEnabledBy would return all transitive features but typically offers too much
Set<String> featureCandidates = LibertyProjectsManager.getInstance()
.getWorkspaceFolder(document.getDocumentURI())
.getFeatureListGraph().get(diagnostic.getSource()).getEnabledBy();
Set<String> featureCandidates = flNode.getEnabledBy();
if (featureCandidates.isEmpty()) {
return;
}

// Need to sort the collection of features so that they are in a reliable order for tests.
ArrayList<String> sortedFeatures = new ArrayList<String>();
sortedFeatures.addAll(featureCandidates);
Collections.sort(sortedFeatures);

String insertText = "";
int referenceRangeStart = 0;
int referenceRangeEnd = 0;
Expand Down Expand Up @@ -111,7 +133,7 @@ public void doCodeAction(ICodeActionRequest request, List<CodeAction> codeAction
}
insertText = IndentUtil.formatText(insertText, indent, referenceRange.getStart().getCharacter());

for (String feature : featureCandidates) {
for (String feature : sortedFeatures) {
String title = "Add feature " + feature;
codeActions.add(CodeActionFactory.insert(
title, referenceRange.getEnd(), String.format(insertText, feature), textDocument, diagnostic));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import java.util.logging.Logger;

import org.eclipse.lemminx.dom.DOMNode;
import org.eclipse.lemminx.uriresolver.CacheResourcesManager;
import org.eclipse.lemminx.uriresolver.CacheResourcesManager.ResourceToDeploy;

import java.util.concurrent.TimeUnit;

Expand Down Expand Up @@ -60,6 +62,24 @@ public class FeatureService {
private static String olFeatureEndpoint = "https://repo1.maven.org/maven2/io/openliberty/features/features/%1$s/features-%1$s.json";
private static String wlpFeatureEndpoint = "https://repo1.maven.org/maven2/com/ibm/websphere/appserver/features/features/%1$s/features-%1$s.json";

// This file is copied to the local .lemminx cache.
// This is how we ensure the latest default featurelist xml gets used in each developer environment.
private static final String FEATURELIST_XML_RESOURCE_URL = "https://github.com/OpenLiberty/liberty-language-server/blob/master/lemminx-liberty/src/main/resources/featurelist-cached-23.0.0.9.xml";
private static final String FEATURELIST_XML_CLASSPATH_LOCATION = "/featurelist-cached-23.0.0.9.xml";

/**
* FEATURELIST_XML_RESOURCE is the featurelist xml that is located at FEATURELIST_XML_CLASSPATH_LOCATION
* that gets deployed (copied) to the .lemminx cache. The FEATURELIST_XML_RESOURCE_URL is
* used by lemmix to determine the path to store the file in the cache. So for the
* featurelist xml it takes the resource located at FEATURELIST_XML_CLASSPATH_LOCATION and deploys
* it to:
* ~/.lemminx/cache/https/github.com/OpenLiberty/liberty-language-server/master/lemminx-liberty/featurelist-cached-<version>.xml
*
* Declared public to be used by tests
*/
public static final ResourceToDeploy FEATURELIST_XML_RESOURCE = new ResourceToDeploy(FEATURELIST_XML_RESOURCE_URL,
FEATURELIST_XML_CLASSPATH_LOCATION);

public static FeatureService getInstance() {
if (instance == null) {
instance = new FeatureService();
Expand All @@ -69,7 +89,8 @@ public static FeatureService getInstance() {

// Cache of Liberty version -> list of supported features
private Map<String, List<Feature>> featureCache; // the key consists of runtime-version, where runtime is 'ol' or 'wlp'
private List<Feature> defaultFeatureList;
private List<Feature> defaultFeatures;
private FeatureListGraph defaultFeatureList;
private long featureUpdateTime;

private FeatureService() {
Expand Down Expand Up @@ -103,28 +124,28 @@ private List<Feature> fetchFeaturesForVersion(String libertyVersion, String libe
}

/**
* Returns the default feature list
* Returns the default list of features
*
* @return list of features supported by the default version of Liberty
*/
private List<Feature> getDefaultFeatureList() {
private List<Feature> getDefaultFeatures() {
try {
if (defaultFeatureList == null) {
if (defaultFeatures == null) {
// Changing this to contain the version in the file name since the file is copied to the local .lemminx cache.
// This is how we ensure the latest default features json gets used in each developer environment.
InputStream is = getClass().getClassLoader().getResourceAsStream("features-cached-23.0.0.9.json");
InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8);

// Only need the public features
defaultFeatureList = readPublicFeatures(reader);
defaultFeatures = readPublicFeatures(reader);
}
LOGGER.info("Returning default feature list");
return defaultFeatureList;
LOGGER.info("Returning default list of features");
return defaultFeatures;

} catch (JsonParseException e) {
// unable to read json in resources file, return empty list
LOGGER.severe("Error: Unable to get default features.");
return defaultFeatureList;
return defaultFeatures;
}
}

Expand All @@ -147,7 +168,7 @@ private ArrayList<Feature> readPublicFeatures(InputStreamReader reader) throws J

/**
* Returns the Liberty features corresponding to the Liberty version. First
* attempts to fetch the feature list from Maven, otherwise falls back to the
* attempts to fetch the feature json from Maven, otherwise falls back to the
* list of installed features. If the installed features list cannot be
* gathered, falls back to the default cached features json file.
*
Expand All @@ -159,8 +180,9 @@ private ArrayList<Feature> readPublicFeatures(InputStreamReader reader) throws J
*/
public List<Feature> getFeatures(String libertyVersion, String libertyRuntime, int requestDelay, String documentURI) {
if (libertyRuntime == null || libertyVersion == null) {
// return default feature list
List<Feature> defaultFeatures = getDefaultFeatureList();
// return default list of features
List<Feature> defaultFeatures = getDefaultFeatures();
getDefaultFeatureList();
return defaultFeatures;
}

Expand Down Expand Up @@ -201,8 +223,8 @@ public List<Feature> getFeatures(String libertyVersion, String libertyRuntime, i
return installedFeatures;
}

// return default feature list
List<Feature> defaultFeatures = getDefaultFeatureList();
// return default list of features
List<Feature> defaultFeatures = getDefaultFeatures();
return defaultFeatures;
}

Expand Down Expand Up @@ -345,6 +367,37 @@ public List<Feature> getInstalledFeaturesList(String documentURI, String liberty
return getInstalledFeaturesList(libertyWorkspace, libertyRuntime, libertyVersion);
}

public FeatureListGraph getDefaultFeatureList() {
if (defaultFeatureList != null) {
return defaultFeatureList;
}

try {
Path featurelistXmlFile = CacheResourcesManager.getResourceCachePath(FEATURELIST_XML_RESOURCE);
LOGGER.info("Using cached Liberty featurelist xml file located at: " + featurelistXmlFile.toString());

File featureListFile = featurelistXmlFile.toFile();

if (featureListFile != null && featureListFile.exists()) {
try {
readFeaturesFromFeatureListFile(null, null, featureListFile, true);
} catch (JAXBException e) {
LOGGER.severe("Error: Unable to load the default cached featurelist file due to exception: "+e.getMessage());
}
} else {
LOGGER.warning("Unable to find the default cached featurelist at location: "+featurelistXmlFile.toString());
}
} catch (Exception e) {
LOGGER.severe("Error: Unable to retrieve default cached featurelist file due to exception: "+e.getMessage());
}

if (defaultFeatureList == null) {
defaultFeatureList = new FeatureListGraph();
}

return defaultFeatureList;
}

/**
* Generate the featurelist file for a LibertyWorkspace using the ws-featurelist.jar in the corresponding Liberty installation
* @param libertyWorkspace
Expand Down Expand Up @@ -397,13 +450,20 @@ private File generateFeatureListXml(LibertyWorkspace libertyWorkspace, Path feat

public List<Feature> readFeaturesFromFeatureListFile(List<Feature> installedFeatures, LibertyWorkspace libertyWorkspace,
File featureListFile) throws JAXBException {
return readFeaturesFromFeatureListFile(installedFeatures, libertyWorkspace, featureListFile, false);
}

// If the graphOnly boolean is true, the libertyWorkspace parameter may be null. Also, the defaultFeatureList should be initialized
// after calling this method with graphOnly set to true.
public List<Feature> readFeaturesFromFeatureListFile(List<Feature> installedFeatures, LibertyWorkspace libertyWorkspace,
File featureListFile, boolean graphOnly) throws JAXBException {
JAXBContext jaxbContext = JAXBContext.newInstance(FeatureInfo.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
FeatureInfo featureInfo = (FeatureInfo) jaxbUnmarshaller.unmarshal(featureListFile);
FeatureListGraph featureListGraph = new FeatureListGraph();

// Note: Only the public features are loaded when unmarshalling the passed featureListFile.
if ((featureInfo.getFeatures() != null) && (featureInfo.getFeatures().size() > 0)) {
FeatureListGraph featureListGraph = new FeatureListGraph();
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
Expand Down Expand Up @@ -431,12 +491,21 @@ public List<Feature> readFeaturesFromFeatureListFile(List<Feature> installedFeat
}
}
}
installedFeatures = featureInfo.getFeatures();
libertyWorkspace.setFeatureListGraph(featureListGraph);
libertyWorkspace.setInstalledFeatureList(installedFeatures);

if (!graphOnly) {
installedFeatures = featureInfo.getFeatures();
libertyWorkspace.setInstalledFeatureList(installedFeatures);
libertyWorkspace.setFeatureListGraph(featureListGraph);
} else {
defaultFeatureList = featureListGraph;
}
} else {
LOGGER.warning("Unable to get installed features for current Liberty workspace: " + libertyWorkspace.getWorkspaceString());
libertyWorkspace.setFeatureListGraph(new FeatureListGraph());
if (!graphOnly) {
LOGGER.warning("Unable to get installed features for current Liberty workspace: " + libertyWorkspace.getWorkspaceString());
libertyWorkspace.setFeatureListGraph(new FeatureListGraph());
} else {
defaultFeatureList = featureListGraph;
}
}
return installedFeatures;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Unmarshaller;
import io.openliberty.tools.langserver.lemminx.data.FeatureListGraph;
import io.openliberty.tools.langserver.lemminx.data.LibertyRuntime;
import io.openliberty.tools.langserver.lemminx.models.feature.Feature;
import io.openliberty.tools.langserver.lemminx.models.settings.DevcMetadata;
import io.openliberty.tools.langserver.lemminx.util.LibertyUtils;

public class LibertyWorkspace {

Expand Down Expand Up @@ -221,24 +219,39 @@ public String toString() {
return workspaceFolderURI;
}

public String getWorkspaceRuntime() {
if (libertyRuntime == null || libertyVersion == null) {
return "";
}
return libertyRuntime + "-" + libertyVersion;
}

public void setFeatureListGraph(FeatureListGraph featureListGraph) {
this.featureListGraph = featureListGraph;
if (isLibertyInstalled) {
if (isLibertyInstalled || isContainerAlive()) {
this.featureListGraph.setRuntime(libertyRuntime + "-" + libertyVersion);
} else {
this.featureListGraph.setRuntime("");
}
}

public FeatureListGraph getFeatureListGraph() {
String workspaceRuntime = libertyRuntime + "-" + libertyVersion;
boolean generateGraph = featureListGraph.isEmpty() || !featureListGraph.getRuntime().equals(workspaceRuntime);
if (this.isLibertyInstalled && generateGraph) {
LOGGER.info("Generating installed features list and storing to cache for workspace " + workspaceFolderURI);
FeatureService.getInstance().getInstalledFeaturesList(this, libertyRuntime, libertyVersion);
if (!this.featureListGraph.isEmpty()) {
FeatureListGraph useFeatureListGraph = this.featureListGraph;
boolean generateGraph = featureListGraph.isEmpty() || !featureListGraph.getRuntime().equals(getWorkspaceRuntime());
if (generateGraph) {
if (isLibertyInstalled || isContainerAlive()) {
LOGGER.info("Generating installed features list and storing to cache for workspace " + workspaceFolderURI);
FeatureService.getInstance().getInstalledFeaturesList(this, libertyRuntime, libertyVersion);
useFeatureListGraph = this.featureListGraph;
} else {
LOGGER.info("Retrieving default cached feature list for workspace " + workspaceFolderURI);
useFeatureListGraph = FeatureService.getInstance().getDefaultFeatureList();
}
if (!useFeatureListGraph.isEmpty()) {
LOGGER.info("Config element validation enabled for workspace: " + workspaceFolderURI);
}
}
return this.featureListGraph;
return useFeatureListGraph;
}

}
Loading

0 comments on commit 8a145b5

Please sign in to comment.