Skip to content

Commit

Permalink
fix: properly validate variables with default values in @scheduled field
Browse files Browse the repository at this point in the history
Signed-off-by: Fred Bricon <[email protected]>
  • Loading branch information
fbricon committed Apr 30, 2024
1 parent 25ce1d6 commit a3bb4c5
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ private void collectDefinition(String uri, PsiFile typeRoot, PsiElement hyperlin
hyperlinkedElement, hyperlinkedPosition);
List<IJavaDefinitionParticipant> definitions = IJavaDefinitionParticipant.EP_NAME.extensions()
.filter(definition -> definition.isAdaptedForDefinition(context))
.collect(Collectors.toList());
.toList();
if (definitions.isEmpty()) {
return;
}
Expand Down Expand Up @@ -290,7 +290,7 @@ private void collectDiagnostics(String uri, IPsiUtils utils, DocumentFormat docu
JavaDiagnosticsContext context = new JavaDiagnosticsContext(uri, typeRoot, utils, module, documentFormat, settings);
List<IJavaDiagnosticsParticipant> definitions = IJavaDiagnosticsParticipant.EP_NAME.extensions()
.filter(definition -> definition.isAdaptedForDiagnostics(context))
.collect(Collectors.toList());
.toList();
if (definitions.isEmpty()) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,25 @@ public Hover collectHover(JavaHoverContext context) {
return null;
}

String defaultValue = null;
// property references may be surrounded by curly braces in Java file
if (propertyReplacer != null) {
propertyKey = propertyReplacer.apply(propertyKey);
int colonIdx = propertyKey.indexOf(":");
if (colonIdx > -1) {
defaultValue = propertyKey.substring(colonIdx+1);
propertyKey = propertyKey.substring(0,colonIdx);
}
}

String defaultAnnotationValue = getAnnotationMemberValue(annotation, defaultValueAnnotationMemberName);

PsiMicroProfileProject mpProject = PsiMicroProfileProjectManager.getInstance(javaProject.getProject())
.getMicroProfileProject(javaProject);
List<MicroProfileConfigPropertyInformation> propertyInformation = getConfigPropertyInformation(propertyKey,
annotation, defaultValueAnnotationMemberName, typeRoot, mpProject, utils);
annotation,
defaultAnnotationValue == null? defaultValue : defaultAnnotationValue,
typeRoot, mpProject, utils);
return new Hover(getDocumentation(propertyInformation, context.getDocumentFormat(),
context.isSurroundEqualsWithSpaces()), propertyKeyRange);
}
Expand Down Expand Up @@ -211,34 +222,30 @@ protected boolean isAdaptableFor(PsiElement hoverElement) {

/**
* Returns all the config property information for the given property key.
*
* <p>
* Includes the information for all the different profiles.
*
* @param propertyKey the property key without the profile
* @param annotation the annotation that defines the
* config property
* @param defaultValueAnnotationMemberName the annotation member name for
* default value and null otherwise.
* @param project the project
* @param utils the JDT LS utilities.
* @param propertyKey the property key without the profile
* @param annotation the annotation that defines the
* config property
* @param defaultValue default value and null otherwise.
* @param project the project
* @param utils the JDT LS utilities.
* @return the config property information for the given property key
*/
private static List<MicroProfileConfigPropertyInformation> getConfigPropertyInformation(String propertyKey,
PsiAnnotation annotation, String defaultValueAnnotationMemberName, PsiFile typeRoot,
PsiMicroProfileProject project, IPsiUtils utils) {
PsiAnnotation annotation,
String defaultValue,
PsiFile typeRoot,
PsiMicroProfileProject project,
IPsiUtils utils) {

List<MicroProfileConfigPropertyInformation> infos = project.getPropertyInformations(propertyKey);
boolean defaultProfileDefined = false;

for (MicroProfileConfigPropertyInformation info : infos) {
if (info.getPropertyNameWithProfile().equals(propertyKey)) {
defaultProfileDefined = true;
}
}
boolean defaultProfileDefined = infos.stream().anyMatch(info -> propertyKey.equals(info.getPropertyNameWithProfile()));

if (defaultValueAnnotationMemberName != null && !defaultProfileDefined) {
if (!defaultProfileDefined) {
infos.add(new MicroProfileConfigPropertyInformation(propertyKey,
getAnnotationMemberValue(annotation, defaultValueAnnotationMemberName), utils.toUri(typeRoot),
defaultValue, utils.toUri(typeRoot),
annotation.getContainingFile().getName()));
}

Expand Down Expand Up @@ -271,7 +278,7 @@ private static void buildDocumentation(List<MicroProfileConfigPropertyInformatio

for (MicroProfileConfigPropertyInformation info : propertyInformation) {

if (content.length() > 0) {
if (!content.isEmpty()) {
content.append(" \n");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public enum SchedulerErrorCodes implements IJavaErrorCode {
INVALID_CRON_YEAR("Year must be in the range [1970, 2099]"),
INVALID_CRON_LENGTH("The cron expression must contain 6-7 parts, delimited by whitespace."),
INVALID_DURATION_PARSE_PATTERN("Text cannot be parsed to a Duration."),
INVALID_CHAR_IN_EXPRESSION("Invalid char(s) in expression."), VALID_EXPRESSION("Expression is valid.");
INVALID_CHAR_IN_EXPRESSION("Invalid char(s) in expression."),
VALID_EXPRESSION("Expression is valid.");

private final String errorMessage;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import java.time.Duration;
import java.time.format.DateTimeParseException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
Expand All @@ -23,6 +24,10 @@
*/
public class SchedulerUtils {

public static enum ValidationType {
cron, duration
}

// Patterns for each cron part of the cron member expression in order of
// SchedulerErrorCode enum ordinal
// Note: DAY_OF_MONTH and DAY_OF_WEEK are mutually exclusive
Expand Down Expand Up @@ -57,7 +62,8 @@ public class SchedulerUtils {

private static final Pattern TIME_PERIOD_PATTERN = Pattern.compile("\\d+[smhSMH]$");

private static final Pattern ENV_PATTERN = Pattern.compile("^\\$?\\{[0-9a-zA-Z.:]*\\}$");
// Define the regex pattern for environment variables with optional default values (allowing spaces in default values) and optional prefix
private static final Pattern ENV_PATTERN = Pattern.compile("^\\$?\\{([^\\{\\}:\\s=]+)(?:\\:([^\\{\\}=]*))?\\}$");

private static final Pattern LOOSE_ENV_PATTERN = Pattern.compile("^.*\\$?\\{.*\\}.*$");

Expand All @@ -69,7 +75,7 @@ private SchedulerUtils() {
* error message if necessary
*
* @param cronString the cron member value
* @return the error fault for the cron string validation and null if valid
* @return the error fault for the cron string validation and <code>null</code> if valid
*/
public static SchedulerErrorCodes validateCronPattern(String cronString) {

Expand All @@ -93,7 +99,7 @@ public static SchedulerErrorCodes validateCronPattern(String cronString) {
* unit
*
* @param timePattern the member value for the time pattern
* @return the INVALID_DURATION_PARSE_PATTERN error code if invalid and null if
* @return the INVALID_DURATION_PARSE_PATTERN error code if invalid and <code>null</code> if
* valid
*/
// \\d+[smhSMH]
Expand All @@ -112,21 +118,38 @@ public static SchedulerErrorCodes validateDurationParse(String timePattern) {
* Check if the member value env variable pattern is well formed
*
* @param memberValue the member value from expression
* @return INVALID_CHAR_IN_EXPRESSION if the env variable is malformed
* @param validationType the type of validation to perform on the default value
* @return a SchedulerErrorCodes if memberValue is invalid,
* <code>null</code> otherwise.
*/
public static SchedulerErrorCodes matchEnvMember(String memberValue) {
return checkLooseEnvPattern(memberValue) ? (checkEnvPattern(memberValue) ? SchedulerErrorCodes.VALID_EXPRESSION
: SchedulerErrorCodes.INVALID_CHAR_IN_EXPRESSION) : null;
public static SchedulerErrorCodes matchEnvMember(String memberValue, ValidationType validationType) {
return checkLooseEnvPattern(memberValue) ? checkEnvPattern(memberValue, validationType) : null;
}

/**
* Match the member to an env variable pattern
*
* @param memberValue the member value from expression
* @return true if pattern is well-formed env variable, false otherwise
* @param validationType the type of validation to perform on the default value
* @return SchedulerErrorCodes if the variable is invalid or the default value is not a valid cron expression,
* <code>SchedulerErrorCodes.VALID_EXPRESSION</code> otherwise.
*/
private static boolean checkEnvPattern(String memberValue) {
return ENV_PATTERN.matcher(memberValue).matches();
private static SchedulerErrorCodes checkEnvPattern(String memberValue, ValidationType validationType) {
Matcher matcher = ENV_PATTERN.matcher(memberValue);
if (matcher.matches()) {
String defaultValue = matcher.group(2);
if (defaultValue == null) {
return SchedulerErrorCodes.VALID_EXPRESSION;
}
SchedulerErrorCodes defaultValueValidation;
if (validationType == ValidationType.cron) {
defaultValueValidation = validateCronPattern(defaultValue);
} else {
defaultValueValidation = validateDurationParse(defaultValue);
}
return defaultValueValidation == null? SchedulerErrorCodes.VALID_EXPRESSION: defaultValueValidation;
}
return SchedulerErrorCodes.INVALID_CHAR_IN_EXPRESSION;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,8 @@ public void visitMethod(PsiMethod node) {
private void validateScheduledAnnotation(PsiMethod node, PsiAnnotation annotation) {
PsiAnnotationMemberValue cronExpr = getAnnotationMemberValueExpression(annotation,
QuarkusConstants.SCHEDULED_ANNOTATION_CRON);
if (cronExpr != null && cronExpr instanceof PsiLiteral && ((PsiLiteral) cronExpr).getValue() instanceof String) {
String cronValue = (String) ((PsiLiteral) cronExpr).getValue();
if (!malformedEnvDiagnostic(cronExpr, cronValue)) {
if (cronExpr instanceof PsiLiteral cronExprLit && cronExprLit.getValue() instanceof String cronValue) {
if (!checkedEnvDiagnostic(cronExpr, cronValue, SchedulerUtils.ValidationType.cron)) {
SchedulerErrorCodes cronPartFault = SchedulerUtils.validateCronPattern(cronValue);
if (cronPartFault != null) {
super.addDiagnostic(cronPartFault.getErrorMessage(), QuarkusConstants.QUARKUS_PREFIX, cronExpr,
Expand All @@ -80,12 +79,12 @@ private void validateScheduledAnnotation(PsiMethod node, PsiAnnotation annotatio
}
PsiAnnotationMemberValue everyExpr = getAnnotationMemberValueExpression(annotation,
QuarkusConstants.SCHEDULED_ANNOTATION_EVERY);
if (everyExpr != null && everyExpr instanceof PsiLiteral && ((PsiLiteral) everyExpr).getValue() instanceof String) {
if (everyExpr instanceof PsiLiteral everyExprLit && everyExprLit.getValue() instanceof String) {
durationParseDiagnostics(everyExpr);
}
PsiAnnotationMemberValue delayedExpr = getAnnotationMemberValueExpression(annotation,
QuarkusConstants.SCHEDULED_ANNOTATION_DELAYED);
if (delayedExpr != null && delayedExpr instanceof PsiLiteral && ((PsiLiteral) delayedExpr).getValue() instanceof String) {
if (delayedExpr instanceof PsiLiteral delayedExprLit && delayedExprLit.getValue() instanceof String) {
durationParseDiagnostics(delayedExpr);
}
}
Expand All @@ -97,7 +96,7 @@ private void validateScheduledAnnotation(PsiMethod node, PsiAnnotation annotatio
*/
private void durationParseDiagnostics(PsiAnnotationMemberValue expr) {
String value = (String) ((PsiLiteral) expr).getValue();
if (!malformedEnvDiagnostic(expr, value)) {
if (!checkedEnvDiagnostic(expr, value, SchedulerUtils.ValidationType.duration)) {
SchedulerErrorCodes memberFault = SchedulerUtils.validateDurationParse(value);
if (memberFault != null) {
super.addDiagnostic(memberFault.getErrorMessage(), QuarkusConstants.QUARKUS_PREFIX, expr, memberFault,
Expand All @@ -109,13 +108,14 @@ private void durationParseDiagnostics(PsiAnnotationMemberValue expr) {
/**
* Retrieve the SchedulerErrorCodes for env member value check
*
* @param expr The expression retrieved from the annotation
* @param memberValue The member value from expression
* @param expr The expression retrieved from the annotation
* @param memberValue The member value from expression
* @param validationType
*/
private boolean malformedEnvDiagnostic(PsiAnnotationMemberValue expr, String memberValue) {
SchedulerErrorCodes malformedEnvFault = SchedulerUtils.matchEnvMember(memberValue);
private boolean checkedEnvDiagnostic(PsiAnnotationMemberValue expr, String memberValue, SchedulerUtils.ValidationType validationType) {
SchedulerErrorCodes malformedEnvFault = SchedulerUtils.matchEnvMember(memberValue, validationType);
if (malformedEnvFault != null) {
if (malformedEnvFault == SchedulerErrorCodes.INVALID_CHAR_IN_EXPRESSION) {
if (!SchedulerErrorCodes.VALID_EXPRESSION.equals(malformedEnvFault)) {
super.addDiagnostic(malformedEnvFault.getErrorMessage(), QuarkusConstants.QUARKUS_PREFIX, expr,
malformedEnvFault, DiagnosticSeverity.Warning);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class QuarkusScheduledPropertiesProvider extends AbstractAnnotationTypeRe

private static final String[] ANNOTATION_NAMES = { QuarkusConstants.SCHEDULED_ANNOTATION };

private static Pattern PROP_PATTERN = Pattern.compile("\\{(.*)\\}");
private static final Pattern PROP_PATTERN = Pattern.compile("\\{(.*)\\}");

@Override
protected String[] getAnnotationNames() {
Expand All @@ -62,6 +62,10 @@ protected void processAnnotation(PsiModifierListOwner javaElement, PsiAnnotation
Matcher m = PROP_PATTERN.matcher(name);
if (m.matches()) {
name = m.group(1);
int colonIdx = name.indexOf(":");
if (colonIdx > -1) {//remove default value
name = name.substring(0,colonIdx);
}
addItemMetadata(collector, name, "java.lang.String", description, sourceType, null, sourceMethod, null,
extensionName, binary);
}
Expand Down
Loading

0 comments on commit a3bb4c5

Please sign in to comment.