Skip to content

Commit

Permalink
Support custom config files (#212)
Browse files Browse the repository at this point in the history
* Process liberty-plugin-config.xml to parse and store list of custom config files, upon startup and file changes to the XML

* Fix uri string mismatch across all methods and classes.

* Add check for config file path, moved from IDE client-side filewatch filtering

* Make sure Github build/test actions are separate. Suppress progress tracking for downloads

Co-authored-by: David Shi <dshi@utexas.edu>
  • Loading branch information
evie-lau and dshimo committed Oct 10, 2023
1 parent 5d51c2c commit c4640e9
Show file tree
Hide file tree
Showing 26 changed files with 605 additions and 67 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:
java-version: 17
- name: Build LCLS
working-directory: ./liberty-ls
run: ./mvnw clean package
run: ./mvnw clean package -ntp -DskipTests
- name: Test LCLS
working-directory: ./liberty-ls
run: ./mvnw verify
run: ./mvnw verify -ntp
lemminx-liberty:
runs-on: ${{ matrix.os }}
strategy:
Expand All @@ -39,7 +39,7 @@ jobs:
java-version: 17
- name: Build Lemminx Liberty
working-directory: ./lemminx-liberty
run: ./mvnw clean package
run: ./mvnw clean package -ntp
- name: Test Lemminx Liberty
working-directory: ./lemminx-liberty
run: ./mvnw verify
run: ./mvnw verify -ntp
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/*******************************************************************************
* 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;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import org.eclipse.lsp4j.WorkspaceFolder;

import io.openliberty.tools.langserver.ls.LibertyTextDocument;
import io.openliberty.tools.langserver.utils.XmlReader;

public class LibertyConfigFileManager {
public static final String LIBERTY_PLUGIN_CONFIG_XML = "liberty-plugin-config.xml";
public static final String CUSTOM_SERVER_ENV_XML_TAG = "serverEnv";
public static final String CUSTOM_BOOTSTRAP_PROPERTIES_XML_TAG = "bootstrapPropertiesFile";

private static final String DEFAULT_SERVER_ENV = "src/main/liberty/config/server.env".replace("/", File.separator);
private static final String DEFAULT_BOOTSTRAP_PROPERTIES = "src/main/liberty/config/bootstrap.properties".replace("/", File.separator);


private static Set<String> customServerEnvFiles = new HashSet<String>();
private static Set<String> customBootstrapFiles = new HashSet<String>();

private static final Logger LOGGER = Logger.getLogger(LibertyConfigFileManager.class.getName());

public static void initLibertyConfigFileManager(List<WorkspaceFolder> workspaceFolders) {
if (workspaceFolders == null) {
return;
}
for (WorkspaceFolder folder : workspaceFolders) {
processWorkspaceDir(folder);
}
}

/**
* Given a workspace folder, find and process all Liberty plugin config xmls
*/
public static void processWorkspaceDir(WorkspaceFolder workspaceFolder) {
String workspaceUriString = workspaceFolder.getUri();
String normalizedUriString = workspaceUriString.replace("///", "/");
URI workspaceURI = URI.create(normalizedUriString);
Path workspacePath = Paths.get(workspaceURI);

try {
List<Path> lpcXmlList = findFilesEndsWithInDirectory(workspacePath, LIBERTY_PLUGIN_CONFIG_XML);
for (Path lpcXml : lpcXmlList) {
processLibertyPluginConfigXml(lpcXml.toUri().toString());
}
} catch (IOException e) {
LOGGER.warning("Encountered an IOException on initial custom config processing: " + e.getMessage());
}
// LOGGER.info("Found custom files: server-" + customServerEnvFiles + "; bootstrap-"+customBootstrapFiles);
}

/**
* Given a Liberty plugin config xml, store custom config file paths to memory.
* @param uri - URI-formatted string
*/
public static void processLibertyPluginConfigXml(String uri) {
if (!uri.endsWith(LIBERTY_PLUGIN_CONFIG_XML)) {
return;
}
Map<String, String> customConfigFiles = XmlReader.readTagsFromXml(uri,
CUSTOM_SERVER_ENV_XML_TAG,
CUSTOM_BOOTSTRAP_PROPERTIES_XML_TAG);
// TODO: handle deletions
// match uri
if (customConfigFiles.containsKey(CUSTOM_SERVER_ENV_XML_TAG)) {
customServerEnvFiles.add(customConfigFiles.get(CUSTOM_SERVER_ENV_XML_TAG));
}
if (customConfigFiles.containsKey(CUSTOM_BOOTSTRAP_PROPERTIES_XML_TAG)) {
customBootstrapFiles.add(customConfigFiles.get(CUSTOM_BOOTSTRAP_PROPERTIES_XML_TAG));
}
}

public static boolean isServerEnvFile(LibertyTextDocument tdi) {
return isServerEnvFile(tdi.getUri());
}

/**
* Checks if file matches one of these conditions:
* - is default server.env file in `src/main/liberty/config`
* - is custom env file specified in liberty-plugin-config.xml (generated from
* build file)
*
* @param uri - normally comes from LibertyTextDocument.getUri() which is a URI formatted string (file:///path/to/file)
* @return
*/
public static boolean isServerEnvFile(String uri) {
String filePath = normalizeFilePath(uri);
return filePath.endsWith(DEFAULT_SERVER_ENV) || customServerEnvFiles.contains(filePath);
}

public static boolean isBootstrapPropertiesFile(LibertyTextDocument tdi) {
return isBootstrapPropertiesFile(tdi.getUri());
}

/**
* Checks if file matches one of these conditions:
* - is default bootstrap.properties file in `src/main/liberty/config`
* - is custom properties file specified in liberty-plugin-config.xml (generated
* from build file)
*
* @param uri - normally comes from LibertyTextDocument.getUri() which is a URI formatted string (file:///path/to/file)
* @return
*/
public static boolean isBootstrapPropertiesFile(String uri) {
String filePath = normalizeFilePath(uri);
return filePath.endsWith(DEFAULT_BOOTSTRAP_PROPERTIES) || customBootstrapFiles.contains(filePath);
}

/**
* Normalize and fix file path, starting from uri-formatted string.
* - Converts to OS-specific filepaths (/ for unix, \ for windows)
* - Handles URL encoding, Windows drive letter discrepancies
* @param uri - URI-formatted string
* @return - OS-specific filepath
*/
public static String normalizeFilePath(String uri) {
// make sure Windows backslashes are replaced with forwardslash for URI.create
String normalizedUriString = uri.replace("\\","/");
String finalPath = null;
if (File.separator.equals("/")) { // unix
Path path = Paths.get(URI.create(normalizedUriString));
finalPath = path.toString();
} else { // windows - URI.create with string instead of URI to handle test paths in Windows. normalize drive letter
String filepath = URI.create(normalizedUriString).getPath();
if (filepath.charAt(0) == '/') {
filepath = filepath.substring(1);
}
Path path = Paths.get(filepath);
finalPath = path.toString();
finalPath = normalizeDriveLetter(finalPath);
/**
* Note - This else case is mostly needed for testing, which use fake paths that don't actually exist.
* - Paths.get(URI) fails, whereas Paths.get(String) works with fake paths.
* - In practice, when URIs are provided by the Liberty Tools IDE client, they are valid paths, where Paths.get(URI) will work
* - just have to normalizeDriveLetter()
*/
}
return finalPath;
}

/**
* Necessary adjustment for Windows URI:
* https://github.com/Microsoft/vscode/issues/42159#issuecomment-360533151
* Notes: LSP uses lowercase drive letters, but Windows file system uses uppercase drive letters
*/
public static String normalizeDriveLetter(String path) {
return path.substring(0, 1).toUpperCase() + path.substring(1);
}

/**
* Search the dir path for all files that end with a name or extension. If none
* are found,
* an empty List is returned.
*
* @param dir Path to search under
* @param nameOrExtension String to match
* @return List<Path> collection of Path that match the given nameOrExtension in
* the specified dir Path.
*/
protected static List<Path> findFilesEndsWithInDirectory(Path dir, String nameOrExtension) throws IOException {
List<Path> matchingFiles = Files.walk(dir)
.filter(p -> (Files.isRegularFile(p) && p.toFile().getName().endsWith(nameOrExtension)))
.collect(Collectors.toList());

return matchingFiles;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2020, 2022 IBM Corporation and others.
* Copyright (c) 2020, 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
Expand All @@ -24,14 +24,11 @@
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageClientAware;
import org.eclipse.lsp4j.services.LanguageServer;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.eclipse.lsp4j.services.WorkspaceService;

import io.openliberty.tools.langserver.common.ParentProcessWatcher.ProcessLanguageServer;

public class LibertyLanguageServer extends AbstractLanguageServer implements ProcessLanguageServer, LanguageClientAware {

public static final String LANGUAGE_ID = "LANGUAGE_ID_LIBERTY";
private static final Logger LOGGER = Logger.getLogger(LibertyLanguageServer.class.getName());

Expand All @@ -57,6 +54,7 @@ public CompletableFuture<InitializeResult> initialize(InitializeParams params) {

ServerCapabilities serverCapabilities = createServerCapabilities();
InitializeResult initializeResult = new InitializeResult(serverCapabilities);
LibertyConfigFileManager.initLibertyConfigFileManager(params.getWorkspaceFolders());
return CompletableFuture.completedFuture(initializeResult);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2020, 2022 IBM Corporation and others.
* Copyright (c) 2020, 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
Expand All @@ -12,13 +12,17 @@
*******************************************************************************/
package io.openliberty.tools.langserver;

import java.util.logging.Logger;

import org.eclipse.lsp4j.DidChangeConfigurationParams;
import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
import org.eclipse.lsp4j.FileEvent;
import org.eclipse.lsp4j.services.WorkspaceService;

public class LibertyWorkspaceService implements WorkspaceService {

private final LibertyLanguageServer libertyLanguageServer;
private static final Logger LOGGER = Logger.getLogger(LibertyWorkspaceService.class.getName());

public LibertyWorkspaceService(LibertyLanguageServer libertyls) {
this.libertyLanguageServer = libertyls;
Expand All @@ -31,6 +35,11 @@ public void didChangeConfiguration(DidChangeConfigurationParams params) {

@Override
public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {
// Do nothing
for (FileEvent change : params.getChanges()) {
String uri = change.getUri();
if (uri.endsWith(LibertyConfigFileManager.LIBERTY_PLUGIN_CONFIG_XML)) {
LibertyConfigFileManager.processLibertyPluginConfigXml(uri);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2022 IBM Corporation and others.
* Copyright (c) 2022, 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
Expand Down Expand Up @@ -29,8 +29,8 @@
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;

import io.openliberty.tools.langserver.LibertyConfigFileManager;
import io.openliberty.tools.langserver.ls.LibertyTextDocument;
import io.openliberty.tools.langserver.utils.ParserFileHelperUtil;
import io.openliberty.tools.langserver.utils.PropertiesValidationResult;
import io.openliberty.tools.langserver.utils.ServerPropertyValues;

Expand All @@ -42,7 +42,7 @@ public class LibertyPropertiesDiagnosticService {

public Map<String, PropertiesValidationResult> compute(String text, LibertyTextDocument openedDocument) {
Map<String, PropertiesValidationResult> errors = new HashMap<>();
if (ParserFileHelperUtil.isBootstrapPropertiesFile(openedDocument) || ParserFileHelperUtil.isServerEnvFile(openedDocument)) {
if (LibertyConfigFileManager.isBootstrapPropertiesFile(openedDocument) || LibertyConfigFileManager.isServerEnvFile(openedDocument)) {
BufferedReader br = new BufferedReader(new StringReader(text));
String line = null;
int lineNumber = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.eclipse.lsp4j.MarkupContent;
import org.eclipse.lsp4j.Position;

import io.openliberty.tools.langserver.LibertyConfigFileManager;
import io.openliberty.tools.langserver.ls.LibertyTextDocument;
import io.openliberty.tools.langserver.utils.ParserFileHelperUtil;

Expand Down Expand Up @@ -47,7 +48,7 @@ public PropertiesEntryInstance(String entryLine, LibertyTextDocument textDocumen
propertyValueInstanceString = null;
}
// In bootstrap.properties files, ignore whitespaces before and after keys and values
if (ParserFileHelperUtil.isBootstrapPropertiesFile(textDocumentItem)) {
if (LibertyConfigFileManager.isBootstrapPropertiesFile(textDocumentItem)) {
propertyKeyInstanceString = propertyKeyInstanceString.trim();
if (propertyValueInstanceString != null) {
propertyValueInstanceString = propertyValueInstanceString.trim();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.logging.Logger;
import java.util.regex.Pattern;

import io.openliberty.tools.langserver.LibertyConfigFileManager;
import io.openliberty.tools.langserver.ls.LibertyTextDocument;

public class Messages {
Expand Down Expand Up @@ -81,7 +82,6 @@ public static List<String> getMatchingKeys(String query, LibertyTextDocument tex
initializeBundles();
}

String filename = textDocument.getUri();
// remove completion results that don't contain the query string (case-insensitive search)
Predicate<String> filter = s -> {
for (int i = s.length() - query.length(); i >= 0; --i) {
Expand All @@ -90,11 +90,11 @@ public static List<String> getMatchingKeys(String query, LibertyTextDocument tex
}
return true;
};
if (filename.contains("server.env")) { // server.env file
if (LibertyConfigFileManager.isServerEnvFile(textDocument)) { // server.env file
List<String> keys = new ArrayList<String>(serverPropertyKeys);
keys.removeIf(filter);
return keys;
} else if (filename.contains("bootstrap.properties")) { // bootstrap.properties file
} else if (LibertyConfigFileManager.isBootstrapPropertiesFile(textDocument)) { // bootstrap.properties file
List<String> keys = new ArrayList<String>(bootstrapPropertyKeys);
keys.removeIf(filter);
return keys;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2022 IBM Corporation and others.
* Copyright (c) 2022, 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
Expand All @@ -14,7 +14,6 @@
import io.openliberty.tools.langserver.ls.LibertyTextDocument;

public class ParserFileHelperUtil {

public String getLine(LibertyTextDocument textDocumentItem, Position position) {
return getLine(textDocumentItem, position.getLine());
}
Expand All @@ -30,12 +29,4 @@ public String getLine(String text, int line) {
}
return null;
}

public static boolean isServerEnvFile(LibertyTextDocument tdi) {
return tdi.getUri().endsWith("server.env");
}

public static boolean isBootstrapPropertiesFile(LibertyTextDocument tdi) {
return tdi.getUri().endsWith("bootstrap.properties");
}
}
}
Loading

0 comments on commit c4640e9

Please sign in to comment.