Skip to content

Commit

Permalink
Contribute to exclude property from validation
Browse files Browse the repository at this point in the history
Fixes eclipse-lsp4mp#95

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed Nov 16, 2020
1 parent 2e149b4 commit ec0bea2
Show file tree
Hide file tree
Showing 9 changed files with 417 additions and 128 deletions.
49 changes: 40 additions & 9 deletions microprofile.ls/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,24 @@ Here are some clients consuming the MicroProfile language server:
* Eclipse with [quarkus-lsp4e (POC)](https://github.com/angelozerr/quarkus-lsp4e)
* IntelliJ with [intellij-quarkus](https://github.com/jeffmaury/intellij-quarkus)
* Visual Studio Code with [vscode-quarkus](https://github.com/redhat-developer/vscode-quarkus)
Code Snippets

LSPMP - LS - extensions
-------

The MicroProfile language server can be extensable:

* to add your own [code snippets](#code-snippets).
* to manage [complex properties](#managing-complex-properties).
* to [contribute to settings](#contribute-to-settings).

Those contribution must be hosted in an external JAR and must be added in the MicroProfile language server classpath.
If you are using [vscode-microprofile](https://github.com/redhat-developer/vscode-microprofile), see the [Contributing to properties and Java support](https://github.com/redhat-developer/vscode-microprofile#contributing-to-properties-and-java-support) section.

## Code Snippets

Java and properties completion snippets are managed by the MicroProfile LS (snippets on server side) by using Java SPI.

## Describing snippets in JSON
### Describing snippets in JSON

`Snippets` are described in JSON files using the [vscode snippet format](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_create-your-own-snippets).

Expand Down Expand Up @@ -98,23 +109,22 @@ means that the snippet is shown only if the project has the `quarkus.datasource.

means that the snippet is shown only if the project has the `org.eclipse.microprofile.openapi.annotations.Operation` Java Annotation in the classpath. In other words, only when the Java project has a dependency on MicroProfile Open API.

## Adding new internal snippets
### Adding new internal snippets

To register a snippet, it must be added in:

* [MicroProfileJavaSnippetRegistryLoader](https://github.com/eclipse/lsp4mp/blob/master/microprofile.ls/org.eclipse.lsp4mp.ls/src/main/java/org/eclipse/lsp4mp/snippets/MicroProfileJavaSnippetRegistryLoader.java) if the new snippet is for Java files.
* [MicroProfilePropertiesSnippetRegistryLoader](https://github.com/eclipse/lsp4mp/blob/master/microprofile.ls/org.eclipse.lsp4mp.ls/src/main/java/org/eclipse/lsp4mp/snippets/MicroProfilePropertiesSnippetRegistryLoader.java) if the new snippet is for properties files.

## Adding new external snippets
### Adding new external snippets

To add external snippets (like Quarkus snippets) an implementation of `ISnippetRegistryLoader` must be created and registered with Java SPI. See for the [quarkus.ls.ext](https://github.com/redhat-developer/quarkus-ls/tree/master/quarkus.ls.ext) for Quarkus snippets:

* [Java Quarkus snippets loader](https://github.com/redhat-developer/quarkus-ls/tree/master/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/java/com/redhat/quarkus/snippets).
* [JSON Quarkus snippet](https://github.com/redhat-developer/quarkus-ls/tree/master/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/resources/com/redhat/quarkus/snippets).
* Java Quarkus snippets loader must be declared in [META-INF/services/org.eclipse.lsp4mp.ls.commons.snippets.ISnippetRegistryLoader](https://github.com/redhat-developer/quarkus-ls/blob/master/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/resources/META-INF/services/org.eclipse.lsp4mp.ls.commons.snippets.ISnippetRegistryLoader)

Managing complex properties
-------
## Managing complex properties

The properties available in `microprofile-config.properties` come from the external component (ex: MicroProfile JDT LS extension). In some case a property
cannot be computed on the external component and must be computed on MicroProfile LS side.
Expand All @@ -134,6 +144,27 @@ In other words, `mp.messaging.outgoing.generated-price.topic` exists only
if there is the declaration `mp.messaging.outgoing.generated-price.connector=smallrye-kafka`

The `mp.messaging.outgoing.generated-price.topic` property cannot be computed on the external component side because it depends on the value of
`mp.messaging.outgoing.generated-price.connector`. The comput tion is done on MicroProfile LS side with custom builder by using Java SPI [ItemMetadataProviderFactory](/src/main/java/org/eclipse/lsp4mp/extensions/ItemMetadataProviderFactory).
`mp.messaging.outgoing.generated-price.connector`. The computation is done on MicroProfile LS side with custom builder by using Java SPI [ItemMetadataProviderFactory](https://github.com/eclipse/lsp4mp/blob/master/microprofile.ls/org.eclipse.lsp4mp.ls/src/main/java/org/eclipse/lsp4mp/extensions/ItemMetadataProviderFactory.java).

Please see the [sample of MicroProfile Reactive Messaging](https://github.com/eclipse/lsp4mp/blob/master/microprofile.ls/org.eclipse.lsp4mp.ls/src/main/java/org/eclipse/lsp4mp/extensions/reactivemessaging).

## Contribute to settings

In microprofile-config.properties file when a property doesn't exists, there is an unkwown error. The user can configure which unknown property errors to hide in the client settings.
It's possible to ignore this error with contribution (ex : camel toolings can ignore validation for all `camel.*` properties).

To do that in the JAR extension, create the `META-INF/lsp4mp/settings.json` like this:

```json
{
"validation": {
"unknown": {
"excluded": [
"camel.*"
]
}
}
}
```

Please see the [sample of MicroProfile Reactive Messaging](/src/main/java/org/eclipse/lsp4mp/extensions/reactivemessaging).
In this sample all camel properties will be ignored during the validation.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.eclipse.lsp4mp.settings.AllMicroProfileSettings;
import org.eclipse.lsp4mp.settings.InitializationOptionsSettings;
import org.eclipse.lsp4mp.settings.MicroProfileCodeLensSettings;
import org.eclipse.lsp4mp.settings.MicroProfileExtensionSettings;
import org.eclipse.lsp4mp.settings.MicroProfileFormattingSettings;
import org.eclipse.lsp4mp.settings.MicroProfileGeneralClientSettings;
import org.eclipse.lsp4mp.settings.MicroProfileSymbolSettings;
Expand All @@ -62,6 +63,8 @@ public class MicroProfileLanguageServer implements LanguageServer, ProcessLangua
private final MicroProfileTextDocumentService textDocumentService;
private final WorkspaceService workspaceService;

private final MicroProfileExtensionSettings extensionSettings;

private Integer parentProcessId;
private MicroProfileLanguageClientAPI languageClient;
private MicroProfileCapabilityManager capabilityManager;
Expand All @@ -70,6 +73,7 @@ public MicroProfileLanguageServer() {
microProfileLanguageService = new MicroProfileLanguageService();
textDocumentService = new MicroProfileTextDocumentService(this);
workspaceService = new MicroProfileWorkspaceService(this);
this.extensionSettings = new MicroProfileExtensionSettings();
}

@Override
Expand Down Expand Up @@ -120,6 +124,8 @@ public synchronized void updateSettings(Object initializationOptionsSettings) {
MicroProfileGeneralClientSettings clientSettings = MicroProfileGeneralClientSettings
.getGeneralMicroProfileSettings(initializationOptionsSettings);
if (clientSettings != null) {
// Merge client settings with extension settings
extensionSettings.merge(clientSettings);
MicroProfileSymbolSettings newSymbols = clientSettings.getSymbols();
if (newSymbols != null) {
textDocumentService.updateSymbolSettings(newSymbols);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*******************************************************************************
* Copyright (c) 2020 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.settings;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.lsp4mp.services.ValidationType;

import com.google.gson.Gson;

/**
* Contribute to {@link MicroProfileGeneralClientSettings} with extension.
*
*/
public class MicroProfileExtensionSettings {

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

private static final String SETTINGS_JSON = "META-INF/lsp4mp/settings.json";
private List<MicroProfileGeneralClientSettings> extensionSettings;

/**
* Merge the settings from list of {@link MicroProfileGeneralClientSettings}
* loaded from "META-INF/lsp4mp/settings.json" files included in the classpath.
*
* <p>
* The merge takes only excluded validation for unknown property.
* </p>
*
* @param settings
*/
public void merge(MicroProfileGeneralClientSettings settings) {
getExtensionSettings().forEach(extensionSettings -> {
merge(settings, extensionSettings);
});
}

/**
*
* @param settings
* @param extensionSettings
*/
private void merge(MicroProfileGeneralClientSettings settings,
MicroProfileGeneralClientSettings extensionSettings) {
// Merge validation unknown excluded
List<String> extensionValidationUnknownExcluded = getValidationExcluded(extensionSettings,
ValidationType.unknown, false);
if (extensionValidationUnknownExcluded != null && !extensionValidationUnknownExcluded.isEmpty()) {
List<String> validationUnknownExcluded = getValidationExcluded(settings, ValidationType.unknown, true);
merge(extensionValidationUnknownExcluded, validationUnknownExcluded);
}
}

private void merge(List<String> from, List<String> to) {
for (String value : from) {
if (!to.contains(value)) {
to.add(value);
}
}
}

private static List<String> getValidationExcluded(MicroProfileGeneralClientSettings settings, ValidationType type,
boolean create) {
MicroProfileValidationSettings validation = settings.getValidation();
if (validation == null && create) {
validation = new MicroProfileValidationSettings();
settings.setValidation(validation);
}
if (validation == null) {
return null;
}
MicroProfileValidationTypeSettings validationType = null;
switch (type) {
case unknown:
validationType = validation.getUnknown();
if (validationType == null && create) {
validationType = new MicroProfileValidationTypeSettings();
validation.setUnknown(validationType);
}
break;
default:
break;
}
if (validationType == null) {
return null;
}
List<String> excluded = validationType.getExcluded();
if (excluded == null && create) {
excluded = new ArrayList<>();
validationType.setExcluded(excluded);
}
return excluded;
}

/**
* Returns the list of {@link MicroProfileGeneralClientSettings} loaded from
* "META-INF/lsp4mp/settings.json" files included in the classpath.
*
* @return the list of {@link MicroProfileGeneralClientSettings} loaded from
* "META-INF/lsp4mp/settings.json" files included in the classpath
*/
public List<MicroProfileGeneralClientSettings> getExtensionSettings() {
if (extensionSettings == null) {
extensionSettings = loadExtensionSettings();
}
return extensionSettings;
}

/**
* Load all "META-INF/lsp4mp/settings.json" files from the classpath.
*
* @return list of {@link MicroProfileGeneralClientSettings}.
*/
private synchronized List<MicroProfileGeneralClientSettings> loadExtensionSettings() {
if (extensionSettings != null) {
return extensionSettings;
}
List<MicroProfileGeneralClientSettings> extensionSettings = new ArrayList<>();
try {
URL url = null;
Enumeration<URL> resources = this.getClass().getClassLoader().getResources(SETTINGS_JSON);
while (resources.hasMoreElements()) {
try {
url = resources.nextElement();
MicroProfileGeneralClientSettings settings = new Gson().fromJson(
new InputStreamReader((InputStream) url.getContent()),
MicroProfileGeneralClientSettings.class);
extensionSettings.add(settings);
} catch (Exception e) {
LOGGER.log(Level.SEVERE,
"Error while loading settings extension from '" + url != null ? url.toExternalForm()
: SETTINGS_JSON + "'",
e);
}
}
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Error while loading settings extensions '" + SETTINGS_JSON + "'", e);
}
return extensionSettings;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

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

import org.eclipse.lsp4j.DiagnosticSeverity;
import org.eclipse.lsp4mp.utils.AntPathMatcher;
Expand All @@ -30,7 +29,7 @@ public class MicroProfileValidationTypeSettings {

private String severity;

private String[] excluded;
private List<String> excluded;

private transient List<ExcludedProperty> excludedProperties;

Expand Down Expand Up @@ -87,7 +86,7 @@ public void setSeverity(String severity) {
*
* @return the array of properties to ignore for this validation type.
*/
public String[] getExcluded() {
public List<String> getExcluded() {
return excluded;
}

Expand All @@ -96,7 +95,7 @@ public String[] getExcluded() {
*
* @param excluded the array of properties to ignore for this validation type.
*/
public void setExcluded(String[] excluded) {
public void setExcluded(List<String> excluded) {
this.excluded = excluded;
}

Expand Down Expand Up @@ -170,7 +169,7 @@ private synchronized List<ExcludedProperty> createExcludedProperties() {
}
AntPathMatcher matcher = new AntPathMatcher();
matcher.setCachePatterns(true);
return Stream.of(excluded) //
return excluded.stream() //
.map(p -> new ExcludedProperty(p, matcher)) //
.collect(Collectors.toList());
}
Expand Down
Loading

0 comments on commit ec0bea2

Please sign in to comment.