Allow SPEL or Property Holder in the cron parameter of @Scheduled

This commit is contained in:
aboyko
2024-08-21 16:32:46 -04:00
parent ae76991822
commit b26d6dab09
7 changed files with 164 additions and 7 deletions

View File

@@ -58,7 +58,7 @@ public class JdtCronReconciler implements JdtAstReconciler {
if (value instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) value;
String name = pair.getName().getFullyQualifiedName();
if (name != null && "cron".equals(name)) {
if (name != null && "cron".equals(name) && JdtCronSemanticTokensProvider.isCronExpression(pair.getValue())) {
QueryJdtAstReconciler.reconcileExpression(cronReconciler, pair.getValue(), problemCollector);
}
}

View File

@@ -14,9 +14,12 @@ import java.util.List;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.TextBlock;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.JdtSemanticTokensProvider;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtDataQuerySemanticTokensProvider;
@@ -68,7 +71,7 @@ public class JdtCronSemanticTokensProvider implements JdtSemanticTokensProvider
if (value instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) value;
String name = pair.getName().getFullyQualifiedName();
if (name != null && "cron".equals(name)) {
if (name != null && "cron".equals(name) && isCronExpression(pair.getValue())) {
JdtDataQuerySemanticTokensProvider.computeTokensForExpression(tokensProvider, pair.getValue()).forEach(collector::accept);
}
}
@@ -81,5 +84,20 @@ public class JdtCronSemanticTokensProvider implements JdtSemanticTokensProvider
};
}
public static boolean isCronExpression(Expression e) {
String value = null;
if (e instanceof StringLiteral sl) {
value = sl.getLiteralValue();
} else if (e instanceof TextBlock tb) {
value = tb.getLiteralValue();
}
value = value.trim();
if (value.startsWith("#{") || value.startsWith("${")) {
// Either SPEL or Property Holder
return false;
}
return value != null;
}
}

View File

@@ -58,7 +58,9 @@ public final class AnnotationParamSpelExtractor {
new AnnotationParamSpelExtractor(SPRING_POST_FILTER, "value", "", ""),
new AnnotationParamSpelExtractor(SPRING_CONDITIONAL_ON_EXPRESSION, null, "", ""),
new AnnotationParamSpelExtractor(SPRING_CONDITIONAL_ON_EXPRESSION, "value", "", "")
new AnnotationParamSpelExtractor(SPRING_CONDITIONAL_ON_EXPRESSION, "value", "", ""),
new AnnotationParamSpelExtractor(Annotations.SCHEDULED, "cron", "#{", "}"),
};
@@ -125,10 +127,16 @@ public final class AnnotationParamSpelExtractor {
private Optional<Snippet> fromStringLiteral(StringLiteral valueExp) {
String value = valueExp.getEscapedValue();
value = value.substring(1, value.length() - 1);
if (value != null && value.startsWith(paramValuePrefix) && value.endsWith(paramValuePostfix)) {
String spelText = value.substring(paramValuePrefix.length(), value.length() - paramValuePostfix.length());
int offset = valueExp.getStartPosition() + paramValuePrefix.length() + 1;
return Optional.of(new Snippet(spelText, offset));
if (value != null) {
int startIdx = value.indexOf(paramValuePrefix);
if (startIdx >= 0) {
int endIdx = value.lastIndexOf(paramValuePostfix);
if (endIdx >= 0) {
String spelText = value.substring(startIdx + paramValuePrefix.length(), endIdx);
int offset = valueExp.getStartPosition() + startIdx + paramValuePrefix.length() + 1;
return Optional.of(new Snippet(spelText, offset));
}
}
}
return Optional.empty();
}

View File

@@ -274,4 +274,5 @@ public class CronSemanticTokensTest {
assertThat(tokens.get(11)).isEqualTo(new SemanticTokenData(24, 25, "operator", new String[0])); // -
assertThat(tokens.get(12)).isEqualTo(new SemanticTokenData(25, 30, "enum", new String[0])); // MARCH
}
}

View File

@@ -87,6 +87,46 @@ public class JdtCronReconcilerTest {
editor.assertProblems();
}
@Test
void noErrors_PropertyHolder() throws Exception {
String source = """
package example.demo;
import org.springframework.scheduling.annotation.Scheduled;
public class A {
@Scheduled(cron = " ${demo.cron} ")
void foo() {}
}
""";
String docUri = directory.toPath().resolve("src/main/java/example/demo/A.java").toUri()
.toString();
Editor editor = harness.newEditor(LanguageId.JAVA, source, docUri);
editor.assertProblems();
}
@Test
void noErrors_SPEL() throws Exception {
String source = """
package example.demo;
import org.springframework.scheduling.annotation.Scheduled;
public class A {
@Scheduled(cron = " #{demo.cron} ")
void foo() {}
}
""";
String docUri = directory.toPath().resolve("src/main/java/example/demo/A.java").toUri()
.toString();
Editor editor = harness.newEditor(LanguageId.JAVA, source, docUri);
editor.assertProblems();
}
@Test
void errorsReported_1() throws Exception {
String source = """

View File

@@ -367,4 +367,56 @@ public class JdtCronSemanticTokensProviderTest {
assertThat(token).isEqualTo(new SemanticTokenData(142, 147, "enum", new String[0]));
assertThat(source.substring(token.start(), token.end())).isEqualTo("MARCH");
}
@Test
void noTokens_SPEL() throws Exception {
String source = """
package my.package
import org.springframework.scheduling.annotation.Scheduled;
public class A {
@Scheduled(cron=" #{demo.cron} ")
void foo() {}
}
""";
String uri = Paths.get(jp.getLocationUri()).resolve("src/main/resource/my/package/A.java").toUri()
.toASCIIString();
CompilationUnit cu = CompilationUnitCache.parse2(source.toCharArray(), uri, "A.java", jp);
assertThat(cu).isNotNull();
List<SemanticTokenData> tokens = computeTokens(cu);
assertThat(tokens.size()).isEqualTo(0);
}
@Test
void noTokens_PropertyHolder() throws Exception {
String source = """
package my.package
import org.springframework.scheduling.annotation.Scheduled;
public class A {
@Scheduled(cron=" ${demo.cron} ")
void foo() {}
}
""";
String uri = Paths.get(jp.getLocationUri()).resolve("src/main/resource/my/package/A.java").toUri()
.toASCIIString();
CompilationUnit cu = CompilationUnitCache.parse2(source.toCharArray(), uri, "A.java", jp);
assertThat(cu).isNotNull();
List<SemanticTokenData> tokens = computeTokens(cu);
assertThat(tokens.size()).isEqualTo(0);
}
}

View File

@@ -154,5 +154,43 @@ public class JdtSpelSemanticTokensProviderTest {
assertThat(source.substring(token.start(), token.end())).isEqualTo("'[a-zA-Z\\s]+'");
}
@Test
void leadingAndTrailingSpaces() throws Exception {
String source = """
package my.package
import org.springframework.scheduling.annotation.Scheduled;
public class A {
@Scheduled(cron=" #{demo.cron} ")
void foo() {}
}
""";
String uri = Paths.get(jp.getLocationUri()).resolve("src/main/resource/my/package/A.java").toUri()
.toASCIIString();
CompilationUnit cu = CompilationUnitCache.parse2(source.toCharArray(), uri, "A.java", jp);
assertThat(cu).isNotNull();
List<SemanticTokenData> tokens = computeTokens(cu);
assertThat(tokens.size()).isEqualTo(3);
SemanticTokenData token = tokens.get(0);
assertThat(token).isEqualTo(new SemanticTokenData(121, 125, "variable", new String[0]));
assertThat(source.substring(token.start(), token.end())).isEqualTo("demo");
token = tokens.get(1);
assertThat(token).isEqualTo(new SemanticTokenData(125, 126, "operator", new String[0]));
assertThat(source.substring(token.start(), token.end())).isEqualTo(".");
token = tokens.get(2);
assertThat(token).isEqualTo(new SemanticTokenData(126, 130, "property", new String[0]));
assertThat(source.substring(token.start(), token.end())).isEqualTo("cron");
}
}