Skip to content

Commit c04aade

Browse files
committed
added spel syntax validation for xml spring config files
1 parent 3207ec2 commit c04aade

File tree

14 files changed

+1041
-0
lines changed

14 files changed

+1041
-0
lines changed

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/SpringXMLLanguageServerComponents.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*******************************************************************************/
1111
package org.springframework.ide.vscode.boot.xml;
1212

13+
import java.util.Optional;
1314
import java.util.Set;
1415

1516
import org.slf4j.Logger;
@@ -20,6 +21,7 @@
2021
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine;
2122
import org.springframework.ide.vscode.commons.languageserver.composable.LanguageServerComponents;
2223
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
24+
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
2325
import org.springframework.ide.vscode.commons.languageserver.util.HoverHandler;
2426
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
2527
import org.springframework.ide.vscode.commons.util.text.LanguageId;
@@ -40,6 +42,8 @@ public class SpringXMLLanguageServerComponents implements LanguageServerComponen
4042
private final JavaProjectFinder projectFinder;
4143
private final SpringSymbolIndex symbolIndex;
4244
private final SpringXMLCompletionEngine completionEngine;
45+
private final SpringXMLReconcileEngine reconcileEngine;
46+
4347

4448
public SpringXMLLanguageServerComponents(
4549
SimpleLanguageServer server,
@@ -56,6 +60,12 @@ public SpringXMLLanguageServerComponents(
5660
server.onShutdown(this::shutdown);
5761

5862
this.completionEngine = new SpringXMLCompletionEngine(this, server, projectFinder, symbolIndex, config);
63+
this.reconcileEngine = new SpringXMLReconcileEngine(projectFinder);
64+
65+
config.addListener(ignore -> {
66+
reconcileEngine.setSpelExpressionSyntaxValidationEnabled(config.isSpelExpressionValidationEnabled());
67+
});
68+
5969
}
6070

6171
@Override
@@ -67,6 +77,11 @@ public Set<LanguageId> getInterestingLanguages() {
6777
public ICompletionEngine getCompletionEngine() {
6878
return this.completionEngine;
6979
}
80+
81+
@Override
82+
public Optional<IReconcileEngine> getReconcileEngine() {
83+
return Optional.of(this.reconcileEngine);
84+
}
7085

7186
@Override
7287
public HoverHandler getHoverProvider() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2020 Pivotal, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Pivotal, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.xml;
12+
13+
import static org.springframework.ide.vscode.boot.xml.XmlConfigConstants.BEANS_NAMESPACE;
14+
import static org.springframework.ide.vscode.boot.xml.XmlConfigConstants.BEAN_ELEMENT;
15+
import static org.springframework.ide.vscode.boot.xml.XmlConfigConstants.VALUE_ATTRIBUTE;
16+
import static org.springframework.ide.vscode.boot.xml.XmlConfigConstants.PROPERTY_ELEMENT;
17+
18+
import java.net.URI;
19+
20+
import org.eclipse.lemminx.dom.DOMDocument;
21+
import org.eclipse.lemminx.dom.DOMNode;
22+
import org.eclipse.lemminx.dom.DOMParser;
23+
import org.eclipse.lsp4j.TextDocumentIdentifier;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
import org.springframework.ide.vscode.boot.java.handlers.SpelExpressionReconciler;
27+
import org.springframework.ide.vscode.commons.java.IJavaProject;
28+
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
29+
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
30+
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
31+
import org.springframework.ide.vscode.commons.util.text.IDocument;
32+
33+
/**
34+
* @author Martin Lippert
35+
*/
36+
public class SpringXMLReconcileEngine implements IReconcileEngine {
37+
38+
private static final Logger log = LoggerFactory.getLogger(SpringXMLReconcileEngine.class);
39+
40+
private final JavaProjectFinder projectFinder;
41+
private final SpelExpressionReconciler spelExpressionReconciler;
42+
private final XMLElementReconciler[] reconcilers;
43+
44+
public SpringXMLReconcileEngine(JavaProjectFinder projectFinder) {
45+
this.projectFinder = projectFinder;
46+
47+
this.spelExpressionReconciler = new SpelExpressionReconciler();
48+
49+
this.reconcilers = new XMLElementReconciler[] {
50+
51+
new XMLElementReconciler(new XMLElementKey(BEANS_NAMESPACE, BEAN_ELEMENT, PROPERTY_ELEMENT, VALUE_ATTRIBUTE), "#{", "}", spelExpressionReconciler)
52+
53+
};
54+
}
55+
56+
public void setSpelExpressionSyntaxValidationEnabled(boolean spelExpressionValidationEnabled) {
57+
this.spelExpressionReconciler.setEnabled(spelExpressionValidationEnabled);
58+
}
59+
60+
@Override
61+
public void reconcile(final IDocument doc, final IProblemCollector problemCollector) {
62+
IJavaProject project = projectFinder.find(new TextDocumentIdentifier(doc.getUri())).orElse(null);
63+
URI uri = URI.create(doc.getUri());
64+
65+
if (project != null) {
66+
67+
try {
68+
problemCollector.beginCollecting();
69+
reconcileXML(doc, problemCollector);
70+
}
71+
finally {
72+
problemCollector.endCollecting();
73+
}
74+
}
75+
}
76+
77+
private void reconcileXML(final IDocument doc, final IProblemCollector problemCollector) {
78+
String content = doc.get();
79+
80+
DOMParser parser = DOMParser.getInstance();
81+
DOMDocument dom = parser.parse(content, "", null);
82+
reconcileNode(dom, problemCollector);
83+
}
84+
85+
private void reconcileNode(DOMNode node, IProblemCollector problemCollector) {
86+
reconcile(node, problemCollector);
87+
88+
for (DOMNode domNode : node.getChildren()) {
89+
reconcileNode(domNode, problemCollector);
90+
}
91+
}
92+
93+
private void reconcile(DOMNode node, IProblemCollector problemCollector) {
94+
for (int i = 0; i < reconcilers.length; i++) {
95+
reconcilers[i].visit(node, problemCollector);
96+
}
97+
}
98+
99+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2020 Pivotal, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Pivotal, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.xml;
12+
13+
import java.util.List;
14+
15+
import org.eclipse.lemminx.dom.DOMAttr;
16+
import org.eclipse.lemminx.dom.DOMNode;
17+
import org.springframework.ide.vscode.boot.java.handlers.Reconciler;
18+
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
19+
20+
/**
21+
* @author mlippert
22+
*/
23+
public class XMLElementReconciler {
24+
25+
private final XMLElementKey xmlElementKey;
26+
private final String prefix;
27+
private final String postfix;
28+
private final Reconciler reconciler;
29+
30+
public XMLElementReconciler(XMLElementKey xmlElementKey, String prefix, String postfix, Reconciler reconciler) {
31+
this.xmlElementKey = xmlElementKey;
32+
this.prefix = prefix;
33+
this.postfix = postfix;
34+
this.reconciler = reconciler;
35+
}
36+
37+
public void visit(DOMNode node, IProblemCollector problemCollector) {
38+
if (!this.xmlElementKey.getNamespaceURI().equals(node.getNamespaceURI())) {
39+
return;
40+
}
41+
42+
if (!this.xmlElementKey.getElementName().equals(node.getLocalName())) {
43+
return;
44+
}
45+
46+
if (this.xmlElementKey.getParentNodeName() != null && node.getParentNode() != null && !this.xmlElementKey.getParentNodeName().equals(node.getParentNode().getLocalName())) {
47+
return;
48+
}
49+
50+
if (this.xmlElementKey.getAttributeName() == null) {
51+
return;
52+
}
53+
54+
List<DOMAttr> attributeNodes = node.getAttributeNodes();
55+
for (DOMAttr attributeNode : attributeNodes) {
56+
if (this.xmlElementKey.getAttributeName().equals(attributeNode.getLocalName())) {
57+
visitAttributeNode(attributeNode, problemCollector);
58+
}
59+
}
60+
}
61+
62+
private void visitAttributeNode(DOMAttr attributeNode, IProblemCollector problemCollector) {
63+
DOMNode valueNode = attributeNode.getNodeAttrValue();
64+
int start = valueNode.getStart();
65+
66+
String value = attributeNode.getNodeValue();
67+
68+
if (value != null && value.startsWith(prefix) && value.endsWith(postfix)) {
69+
String valueToReconcile = value.substring(prefix.length(), value.length() - postfix.length());
70+
reconciler.reconcile(valueToReconcile, start + prefix.length() + 1, problemCollector);
71+
}
72+
}
73+
74+
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/XmlConfigConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class XmlConfigConstants {
4242
public static final String VALUE_REF_ATTRIBUTE = "value-ref";
4343
public static final String KEY_REF_ATTRIBUTE = "key-ref";
4444
public static final String LOCAL_ATTRIBUTE = "local";
45+
public static final String VALUE_ATTRIBUTE = "value";
4546

4647

4748
public static final String CONTEXT_NAMESPACE = "http://www.springframework.org/schema/context";

0 commit comments

Comments
 (0)