Skip to content

Commit

Permalink
feat: Resolve system properties/environment variables while browsing the
Browse files Browse the repository at this point in the history
application.properties values

Fixes #448

Signed-off-by: azerr <azerr@redhat.com>
  • Loading branch information
angelozerr committed Aug 14, 2024
1 parent 8b3973d commit d0d87ac
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.eclipse.lsp4mp.commons.metadata.ItemHint;
import org.eclipse.lsp4mp.commons.metadata.ItemMetadata;
import org.eclipse.lsp4mp.commons.metadata.ValueHint;
import org.eclipse.lsp4mp.commons.utils.StringUtils;
import org.eclipse.lsp4mp.model.PropertiesModel;

/**
Expand Down Expand Up @@ -130,7 +131,8 @@ public synchronized void updateSourcesProperties(List<ItemMetadata> propertiesFr

// expand properties by using new dynamic properties
expandProperties(staticProperties, dynamicProperties, getHint);
// expand properties by using old dynamic properties (coming from binary properties)
// expand properties by using old dynamic properties (coming from binary
// properties)
expandProperties(staticProperties, getDynamicProperties(), getHint);
updateListFromPropertiesSources(getProperties(), staticProperties);
updateListFromPropertiesSources(getDynamicProperties(), dynamicProperties);
Expand Down Expand Up @@ -202,7 +204,10 @@ private synchronized void synchUpdateCustomProperties(PropertiesModel document)
// - a Java sources changes (document = null)
// - a microprofile-config.properties changes (document != null)
if (document != null || provider.isAvailable()) {
List<ItemMetadata> oldProperties = new ArrayList<>(provider.getProperties());
List<ItemMetadata> oldProperties = provider.getProperties();
if (oldProperties != null) {
oldProperties = new ArrayList<>(oldProperties);
}
provider.update(document);
List<ItemMetadata> newProperties = provider.getProperties();
if (!Objects.deepEquals(oldProperties, newProperties)) {
Expand All @@ -220,4 +225,16 @@ private synchronized void synchUpdateCustomProperties(PropertiesModel document)
}
}

/**
* Returns true if the given metadata property comes from a Java file and false
* otherwise (ex : env/sys properties).
*
* @param metadata the metadata item.
* @return true if the given metadata property comes from a Java file and false
* otherwise (ex : env/sys properties).
*/
public static boolean isPropertyFromJava(ItemMetadata metadata) {
return metadata != null && !StringUtils.isEmpty(metadata.getSourceType());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.eclipse.lsp4mp.extensions.sysenv;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.lsp4mp.commons.metadata.ItemMetadata;
import org.eclipse.lsp4mp.commons.utils.StringUtils;
import org.eclipse.lsp4mp.extensions.ExtendedMicroProfileProjectInfo;
import org.eclipse.lsp4mp.extensions.ItemMetadataProvider;
import org.eclipse.lsp4mp.model.PropertiesModel;

/**
* Properties provider for Environment variables and System properties.
*/
public class SysEnvItemMetadataProvider implements ItemMetadataProvider {

private List<ItemMetadata> sysEnvProperties;

public SysEnvItemMetadataProvider(ExtendedMicroProfileProjectInfo projectInfo) {

}

@Override
public boolean isAvailable() {
return true;
}

@Override
public void update(PropertiesModel document) {
this.sysEnvProperties = collectSysEnvProperties();
}

@Override
public List<ItemMetadata> getProperties() {
return sysEnvProperties;
}

private static List<ItemMetadata> collectSysEnvProperties() {
Stream<ItemMetadata> sysProps = System.getProperties().entrySet().stream().map(e -> {
String name = e.getKey().toString();
String defaultValue = e.getValue() != null ? e.getValue().toString() : null;

ItemMetadata item = new ItemMetadata();
item.setExtensionName("System property");
item.setName(name);
item.setDefaultValue(defaultValue);
item.setType("java.lang.String");
return item;
});

Stream<ItemMetadata> envVars = System.getenv().entrySet().stream().map(e -> {
String name = e.getKey();
// Poor-man obfuscation of env var keys (*_KEY) and secrets (*_SECRET)
// Maybe later add configuration for suffixes and/or actual keys to obfuscate
String defaultValue = obfuscate(name, e.getValue());

ItemMetadata item = new ItemMetadata();
item.setExtensionName("Environment variable");
item.setName(name);
item.setDefaultValue(defaultValue);
item.setType("java.lang.String");
return item;
});

return Stream.concat(sysProps, envVars)//
.collect(Collectors.toList());

}

private static String obfuscate(String key, String value) {
if (StringUtils.isEmpty(key)) {
return key;
}
String upKey = key.toUpperCase().replace(".", "_");
return upKey.endsWith("_KEY") || upKey.endsWith("_SECRET") || upKey.endsWith("_PASSOWORD")
|| upKey.endsWith("_TOKEN") ? "*********" : value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*******************************************************************************
* Copyright (c) 2024 Red Hat Inc. 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, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lsp4mp.extensions.sysenv;

import org.eclipse.lsp4mp.extensions.ExtendedMicroProfileProjectInfo;
import org.eclipse.lsp4mp.extensions.ItemMetadataProvider;
import org.eclipse.lsp4mp.extensions.ItemMetadataProviderFactory;

/**
* Factory for creating {@link ItemMetadataProvider} instance for System
* properties and Environment variables.
*
* @author Angelo ZERR
*
*/
public class SysEnvItemMetadataProviderFactory implements ItemMetadataProviderFactory {

@Override
public ItemMetadataProvider create(ExtendedMicroProfileProjectInfo projectInfo) {
return new SysEnvItemMetadataProvider(projectInfo);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
Expand All @@ -41,9 +39,9 @@
import org.eclipse.lsp4mp.commons.metadata.ConverterKind;
import org.eclipse.lsp4mp.commons.metadata.ItemMetadata;
import org.eclipse.lsp4mp.commons.metadata.ValueHint;
import org.eclipse.lsp4mp.commons.utils.ConfigSourcePropertiesProviderUtils;
import org.eclipse.lsp4mp.commons.utils.PropertyValueExpander;
import org.eclipse.lsp4mp.commons.utils.StringUtils;
import org.eclipse.lsp4mp.extensions.ExtendedMicroProfileProjectInfo;
import org.eclipse.lsp4mp.ls.commons.BadLocationException;
import org.eclipse.lsp4mp.ls.commons.SnippetsBuilder;
import org.eclipse.lsp4mp.ls.commons.TextDocument;
Expand Down Expand Up @@ -122,8 +120,8 @@ public CompletionList doComplete(PropertiesModel document, Position position, Mi
completionItemDefaults, list);
} else {
// other.test.property = ${|}
collectPropertyValueExpressionSuggestions(propExpr, document, projectInfo, completionCapabilities,
list, cancelChecker);
collectPropertyValueExpressionSuggestions(propExpr, document, projectInfo, completionCapabilities, list,
cancelChecker);
}
break;

Expand Down Expand Up @@ -327,7 +325,8 @@ private static void collectPropertyKeySuggestions(int offset, Node node, Propert
* was invoked
* @param key the property key
* @param model the properties model
* @param markdownSupported boolean determining if markdown is supported
* @param markdownSupported boolean determining if
* markdown is supported
* @param completionResolveDocumentationSupported true if completion resolve for
* documentation is supported
* @param completionCapabilities the completion capabilities
Expand Down Expand Up @@ -534,7 +533,8 @@ private static void collectPropertyValueExpressionSuggestions(PropertyValueExpre
// Add all properties not referenced in the properties file as completion
// options only the property has no default value
for (ItemMetadata candidateCompletion : projectInfo.getProperties()) {
if (candidateCompletion.getDefaultValue() == null) {
if (!ExtendedMicroProfileProjectInfo.isPropertyFromJava(candidateCompletion)
|| candidateCompletion.getDefaultValue() == null) {
String candidateCompletionName = candidateCompletion.getName();
if (!model.hasKey(candidateCompletionName)) {
list.getItems().add(getPropertyCompletionItem(candidateCompletionName, node, model));
Expand All @@ -551,7 +551,8 @@ private static void collectPropertyValueExpressionSuggestions(PropertyValueExpre
* @param converterKinds the converter kinds.
* @param range the range for completion
* @param model the property model
* @param markdownSupported true if markdown is supported and false otherwise.
* @param markdownSupported true if markdown is supported and false
* otherwise.
* @param completionItemDefaults the completion itemDefaults
* @return the value completion item
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.eclipse.lsp4mp.commons.DocumentFormat;
import org.eclipse.lsp4mp.commons.MicroProfileProjectInfo;
import org.eclipse.lsp4mp.commons.MicroProfilePropertyDefinitionParams;
import org.eclipse.lsp4mp.commons.MicroProfilePropertyDocumentationParams;
import org.eclipse.lsp4mp.commons.metadata.ConfigurationMetadata;
import org.eclipse.lsp4mp.commons.metadata.ItemHint;
Expand All @@ -35,6 +34,7 @@
import org.eclipse.lsp4mp.commons.utils.IConfigSourcePropertiesProvider;
import org.eclipse.lsp4mp.commons.utils.PropertyValueExpander;
import org.eclipse.lsp4mp.commons.utils.StringUtils;
import org.eclipse.lsp4mp.extensions.ExtendedMicroProfileProjectInfo;
import org.eclipse.lsp4mp.ls.api.MicroProfilePropertyDocumentationProvider;
import org.eclipse.lsp4mp.ls.commons.BadLocationException;
import org.eclipse.lsp4mp.model.BasePropertyValue;
Expand Down Expand Up @@ -67,8 +67,9 @@ class PropertiesFileHover {
* @param cancelChecker the cancel checker
* @return Hover object for the currently hovered token
*/
public CompletableFuture<Hover> doHover(PropertiesModel document, Position position, MicroProfileProjectInfo projectInfo,
MicroProfileHoverSettings hoverSettings, MicroProfilePropertyDocumentationProvider documentationProvider, CancelChecker cancelChecker) {
public CompletableFuture<Hover> doHover(PropertiesModel document, Position position,
MicroProfileProjectInfo projectInfo, MicroProfileHoverSettings hoverSettings,
MicroProfilePropertyDocumentationProvider documentationProvider, CancelChecker cancelChecker) {

Node node = null;
int offset = -1;
Expand All @@ -92,21 +93,25 @@ public CompletableFuture<Hover> doHover(PropertiesModel document, Position posit
boolean inDefaultValue = propExpr.isInDefaultValue(offset);
if (inDefaultValue) {
// quarkus.log.file.level=${ENV:OF|F}
return CompletableFuture.completedFuture(getPropertyValueHover(propExpr, inDefaultValue, projectInfo, hoverSettings));
return CompletableFuture
.completedFuture(getPropertyValueHover(propExpr, inDefaultValue, projectInfo, hoverSettings));
}
// quarkus.log.file.level=${E|NV:OFF}
return CompletableFuture.completedFuture(getPropertyValueExpressionHover(propExpr, projectInfo, hoverSettings, cancelChecker));
return CompletableFuture.completedFuture(
getPropertyValueExpressionHover(propExpr, projectInfo, hoverSettings, cancelChecker));
case PROPERTY_VALUE_LITERAL:
case PROPERTY_VALUE:
return CompletableFuture.completedFuture(getPropertyValueHover((BasePropertyValue) node, false, projectInfo, hoverSettings));
return CompletableFuture.completedFuture(
getPropertyValueHover((BasePropertyValue) node, false, projectInfo, hoverSettings));
case PROPERTY_KEY:
PropertyKey key = (PropertyKey) node;
if (key.isBeforeProfile(offset)) {
// hover documentation on profile
return CompletableFuture.completedFuture(getProfileHover(key, hoverSettings));
} else {
// hover documentation on property key
return getPropertyKeyHover(key, projectInfo, hoverSettings, documentationProvider, document.getDocumentURI(), cancelChecker);
return getPropertyKeyHover(key, projectInfo, hoverSettings, documentationProvider,
document.getDocumentURI(), cancelChecker);
}

default:
Expand Down Expand Up @@ -143,16 +148,17 @@ private static Hover getProfileHover(PropertyKey key, MicroProfileHoverSettings
* Returns the documentation hover for property key represented by the property
* key <code>key</code>
*
* @param key the property key
* @param offset the hover offset
* @param projectInfo the MicroProfile project information
* @param hoverSettings the hover settings
* @param key the property key
* @param offset the hover offset
* @param projectInfo the MicroProfile project information
* @param hoverSettings the hover settings
* @param documentationProvider the documentation provider
* @param cancelChecker the cancel checker
* @param cancelChecker the cancel checker
* @return the documentation hover for property key represented by token
*/
private static CompletableFuture<Hover> getPropertyKeyHover(PropertyKey key, MicroProfileProjectInfo projectInfo,
MicroProfileHoverSettings hoverSettings, MicroProfilePropertyDocumentationProvider documentationProvider, String uri, CancelChecker cancelChecker) {
MicroProfileHoverSettings hoverSettings, MicroProfilePropertyDocumentationProvider documentationProvider,
String uri, CancelChecker cancelChecker) {
boolean markdownSupported = hoverSettings.isContentFormatSupported(MarkupKind.MARKDOWN);
// retrieve MicroProfile property from the project information
String propertyName = key.getPropertyName();
Expand All @@ -175,7 +181,9 @@ private static CompletableFuture<Hover> getPropertyKeyHover(PropertyKey key, Mic
if (item != null || propertyValue != null) {

CompletableFuture<Void> docsCollect = null;
if (item != null && (item.getDescription() == null || item.getDescription().isEmpty())) {
if (item != null && ExtendedMicroProfileProjectInfo.isPropertyFromJava(item)
&& StringUtils.isEmpty(item.getDescription())) {
// It is a property declared in a Java file, try to collect the Javadoc
MicroProfilePropertyDocumentationParams params = new MicroProfilePropertyDocumentationParams();
params.setUri(uri);
params.setSourceField(item.getSourceField());
Expand Down Expand Up @@ -209,7 +217,8 @@ private static CompletableFuture<Hover> getPropertyKeyHover(PropertyKey key, Mic
return docsCollect.thenApply((_null) -> {
Hover hover = new Hover();
MarkupContent markupContent = null;
// Docs are only collected asynchronously from JDT.LS if the ItemMetadata resolves
// Docs are only collected asynchronously from JDT.LS if the ItemMetadata
// resolves
// MicroProfile property found, display the documentation as hover
markupContent = DocumentationUtils.getDocumentation(item, key.getProfile(), propertyValueFinal,
markdownSupported);
Expand Down Expand Up @@ -265,7 +274,8 @@ private static Hover getPropertyValueExpressionHover(PropertyValueExpression nod
}

PropertiesModel model = node.getOwnerModel();
IConfigSourcePropertiesProvider propertiesProvider = ConfigSourcePropertiesProviderUtils.layer(model, new PropertiesInfoPropertiesProvider(projectInfo.getProperties()));
IConfigSourcePropertiesProvider propertiesProvider = ConfigSourcePropertiesProviderUtils.layer(model,
new PropertiesInfoPropertiesProvider(projectInfo.getProperties()));
PropertyValueExpander expander = new PropertyValueExpander(propertiesProvider);
cancelChecker.checkCanceled();

Expand Down
Loading

0 comments on commit d0d87ac

Please sign in to comment.