Skip to content

Commit cb599f4

Browse files
committed
feat(validation): Add new @Cron validation expression based on jakarta.validation #618
1 parent 21dfdbb commit cb599f4

File tree

8 files changed

+160
-20
lines changed

8 files changed

+160
-20
lines changed

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,10 @@ cron-utils is available on [Maven central](http://search.maven.org/#search%7Cga%
2020
</dependency>
2121

2222
For Android developers, cron-utils 7.0.0 assumes Android 26+. For earlier Android versions consider using cron-utils 6.0.6.
23-
If using ScheduleExpression from Java EE, this should be provided as a runtime dependency.
23+
If using ScheduleExpression from Java EE or Jakarta, this should be provided as a runtime dependency.
2424

2525
**Current development**
2626

27-
*We are currently working to update the codebase towards JDK 16, to ensure will be fully compatible with JDK 17 when released.*
28-
2927
Now we are developing a new generation of cron-descriptors using neural-translation! Any kind of contributions are welcome: from help with dataset generation to machine learning models training and utilities to load them! If interested, please follow issue [#3](https://github.com/jmrozanec/cron-utils/issues/3)
3028

3129
**Features**

pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@
8181
<version>2.0.1.Final</version>
8282
<scope>provided</scope>
8383
</dependency>
84+
<dependency>
85+
<groupId>jakarta.validation</groupId>
86+
<artifactId>jakarta.validation-api</artifactId>
87+
<version>3.0.2</version>
88+
<scope>provided</scope>
89+
</dependency>
8490

8591
<!--test dependencies-->
8692
<dependency>
@@ -123,6 +129,13 @@
123129
<scope>test</scope>
124130
</dependency>
125131

132+
<dependency>
133+
<groupId>org.hibernate.validator</groupId>
134+
<artifactId>hibernate-validator</artifactId>
135+
<version>7.0.5.Final</version>
136+
<scope>test</scope>
137+
</dependency>
138+
126139

127140
<dependency>
128141
<groupId>javax.el</groupId>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.cronutils.validation;
2+
3+
import com.cronutils.model.CronType;
4+
import com.cronutils.model.definition.CronDefinition;
5+
import com.cronutils.model.definition.CronDefinitionBuilder;
6+
import com.cronutils.parser.CronParser;
7+
8+
public abstract class AbstractCronValidator {
9+
10+
private CronType type;
11+
12+
protected void initialize(CronType constraintAnnotation) {
13+
this.type = constraintAnnotation;
14+
}
15+
16+
protected boolean isValid(String value) throws IllegalArgumentException {
17+
if (value == null) {
18+
return true;
19+
}
20+
CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(type);
21+
CronParser cronParser = new CronParser(cronDefinition);
22+
cronParser.parse(value).validate();
23+
return true;
24+
}
25+
}

src/main/java/com/cronutils/validation/Cron.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@
2121

2222
CronType type();
2323

24-
}
24+
}

src/main/java/com/cronutils/validation/CronValidator.java

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,20 @@
11
package com.cronutils.validation;
22

3-
import com.cronutils.model.CronType;
4-
import com.cronutils.model.definition.CronDefinition;
5-
import com.cronutils.model.definition.CronDefinitionBuilder;
6-
import com.cronutils.parser.CronParser;
7-
83
import javax.validation.ConstraintValidator;
94
import javax.validation.ConstraintValidatorContext;
105

11-
public class CronValidator implements ConstraintValidator<Cron, String> {
6+
public class CronValidator extends AbstractCronValidator implements ConstraintValidator<Cron, String> {
127

13-
private CronType type;
148

159
@Override
1610
public void initialize(Cron constraintAnnotation) {
17-
this.type = constraintAnnotation.type();
11+
super.initialize(constraintAnnotation.type());
1812
}
1913

2014
@Override
2115
public boolean isValid(String value, ConstraintValidatorContext context) {
22-
if (value == null) {
23-
return true;
24-
}
25-
26-
CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(type);
27-
CronParser cronParser = new CronParser(cronDefinition);
2816
try {
29-
cronParser.parse(value).validate();
30-
return true;
17+
return super.isValid(value);
3118
} catch (IllegalArgumentException e) {
3219
context.disableDefaultConstraintViolation();
3320
context.buildConstraintViolationWithTemplate(e.getMessage()).addConstraintViolation();
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.cronutils.validation.jakarta;
2+
3+
import com.cronutils.model.CronType;
4+
import jakarta.validation.Constraint;
5+
import jakarta.validation.Payload;
6+
7+
import java.lang.annotation.*;
8+
9+
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
10+
@Retention(RetentionPolicy.RUNTIME)
11+
@Constraint(validatedBy = CronJakartaValidator.class)
12+
@Inherited
13+
@Documented
14+
public @interface Cron {
15+
16+
String message() default "UNUSED";
17+
18+
Class<?>[] groups() default {};
19+
20+
Class<? extends Payload>[] payload() default {};
21+
22+
CronType type();
23+
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.cronutils.validation.jakarta;
2+
3+
import com.cronutils.validation.AbstractCronValidator;
4+
import jakarta.validation.ConstraintValidator;
5+
import jakarta.validation.ConstraintValidatorContext;
6+
7+
public class CronJakartaValidator extends AbstractCronValidator implements ConstraintValidator<Cron, String> {
8+
9+
@Override
10+
public void initialize(Cron constraintAnnotation) {
11+
super.initialize(constraintAnnotation.type());
12+
}
13+
14+
@Override
15+
public boolean isValid(String value, ConstraintValidatorContext context) {
16+
try {
17+
return super.isValid(value);
18+
} catch (IllegalArgumentException e) {
19+
context.disableDefaultConstraintViolation();
20+
context.buildConstraintViolationWithTemplate(e.getMessage()).addConstraintViolation();
21+
return false;
22+
}
23+
}
24+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.cronutils.validation.jakarta;
2+
3+
import com.cronutils.model.CronType;
4+
import jakarta.validation.ConstraintViolation;
5+
import jakarta.validation.Validation;
6+
import jakarta.validation.Validator;
7+
import org.junit.jupiter.params.ParameterizedTest;
8+
import org.junit.jupiter.params.provider.Arguments;
9+
import org.junit.jupiter.params.provider.MethodSource;
10+
import org.slf4j.Logger;
11+
import org.slf4j.LoggerFactory;
12+
13+
import java.util.Set;
14+
import java.util.stream.Stream;
15+
16+
import static org.junit.jupiter.api.Assertions.assertFalse;
17+
import static org.junit.jupiter.api.Assertions.assertTrue;
18+
19+
public class CronJakartaTestValidatorTest {
20+
21+
private static final Logger LOGGER = LoggerFactory.getLogger(CronJakartaTestValidatorTest.class);
22+
23+
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
24+
25+
public static Stream<Arguments> expressions() {
26+
return Stream.of(
27+
Arguments.of("0 0 * * * *", true),
28+
Arguments.of("*/10 * * * * *", true),
29+
Arguments.of("0 0 8-10 * * *", true),
30+
Arguments.of("0 0 6,19 * * *", true),
31+
Arguments.of("0 0/30 8-10 * * *", true),
32+
Arguments.of("0 0 9-17 * * MON-FRI", true),
33+
Arguments.of("0 0 0 25 12 ?", true),
34+
Arguments.of("0 0 0 L 12 ?", false),
35+
Arguments.of("1,2, * * * * *", false),
36+
Arguments.of("1- * * * * *", false),
37+
// Verification for RCE security vulnerability fix: https://github.com/jmrozanec/cron-utils/issues/461
38+
Arguments.of("java.lang.Runtime.getRuntime().exec('touch /tmp/pwned'); // 4 5 [${''.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('js').eval(validatedValue)}]", false)
39+
);
40+
}
41+
42+
@ParameterizedTest(name = "{0} ")
43+
@MethodSource("expressions")
44+
public void validateExamples(String expression, boolean valid) {
45+
TestPojo testPojo = new TestPojo(expression);
46+
Set<ConstraintViolation<TestPojo>> violations = validator.validate(testPojo);
47+
violations.stream().map(ConstraintViolation::getMessage).forEach(LOGGER::info);
48+
49+
if (valid) {
50+
assertTrue(violations.isEmpty());
51+
} else {
52+
assertFalse(violations.isEmpty());
53+
}
54+
}
55+
56+
public static class TestPojo {
57+
@Cron(type = CronType.SPRING)
58+
private final String cron;
59+
60+
public TestPojo(String cron) {
61+
this.cron = cron;
62+
}
63+
64+
public String getCron() {
65+
return cron;
66+
}
67+
68+
}
69+
}

0 commit comments

Comments
 (0)