Skip to content

Commit 1b7224a

Browse files
authored
Merge pull request #121 from Rafaellinos/fix/clean-context-on-exception
fix: Add AutoCloseable to clean up the context when an exception occurs
2 parents c339ebf + ff61837 commit 1b7224a

File tree

8 files changed

+142
-52
lines changed

8 files changed

+142
-52
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.github.mvallim</groupId>
88
<artifactId>java-fluent-validator</artifactId>
9-
<version>1.10.1-SNAPSHOT</version>
9+
<version>1.10.2-SNAPSHOT</version>
1010
<packaging>jar</packaging>
1111

1212
<properties>

src/main/java/br/com/fluentvalidator/AbstractValidator.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,10 @@ public <P> P getPropertyOnContext(final String property, final Class<P> clazz) {
121121
*/
122122
@Override
123123
public ValidationResult validate(final T instance) {
124-
ruleProcessor.process(instance, this);
125-
return ValidationContext.get().getValidationResult();
124+
try (ValidationContext.Context context = ValidationContext.get()) {
125+
ruleProcessor.process(instance, this);
126+
return context.getValidationResult();
127+
}
126128
}
127129

128130
/**

src/main/java/br/com/fluentvalidator/context/ProcessorContext.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public static void remove() {
5050
/**
5151
* Context of processor
5252
*/
53-
public static final class Context {
53+
public static final class Context implements AutoCloseable {
5454

5555
private final Deque<AtomicInteger> stackCounter = new ConcurrentLinkedDeque<>();
5656

@@ -74,6 +74,10 @@ public Integer get() {
7474
return stackCounter.isEmpty() ? 0 : stackCounter.peek().get();
7575
}
7676

77+
@Override
78+
public void close() {
79+
remove();
80+
}
7781
}
7882

7983
}

src/main/java/br/com/fluentvalidator/context/ValidationContext.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static void remove() {
5252
/**
5353
* Context of validation
5454
*/
55-
public static final class Context {
55+
public static final class Context implements AutoCloseable {
5656

5757
private final Map<String, Object> properties = new ConcurrentHashMap<>();
5858

@@ -99,6 +99,10 @@ public <P> P getProperty(final String property, final Class<P> clazz) {
9999
return clazz.cast(properties.getOrDefault(property, null));
100100
}
101101

102+
@Override
103+
public void close() {
104+
ValidationContext.remove();
105+
}
102106
}
103107

104108
}

src/main/java/br/com/fluentvalidator/rule/RuleProcessorStrategy.java

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,21 @@ default <E> boolean process(final E value, final Rule<E> rule) {
3232
}
3333

3434
default <E> boolean process(final Object obj, final Collection<E> values, final Rule<E> rule) {
35-
ProcessorContext.get().create();
36-
final boolean allMatch = values.stream().map(value -> {
37-
ProcessorContext.get().inc();
38-
return this.process(obj, value, rule);
39-
}).collect(Collectors.toList()).stream().allMatch(result -> result);
40-
ProcessorContext.get().remove();
41-
return allMatch;
35+
try(ProcessorContext.Context context = ProcessorContext.get()) {
36+
return values.stream().map(value -> {
37+
context.inc();
38+
return this.process(obj, value, rule);
39+
}).collect(Collectors.toList()).stream().allMatch(result -> result);
40+
}
4241
}
4342

4443
default <E> boolean process(final Collection<E> values, final Rule<E> rule) {
45-
ProcessorContext.get().create();
46-
final boolean allMatch = values.stream().map(value -> {
47-
ProcessorContext.get().inc();
48-
return this.process(value, rule);
49-
}).collect(Collectors.toList()).stream().allMatch(result -> result);
50-
ProcessorContext.get().remove();
51-
return allMatch;
44+
try(ProcessorContext.Context context = ProcessorContext.get()) {
45+
return values.stream().map(value -> {
46+
context.inc();
47+
return this.process(value, rule);
48+
}).collect(Collectors.toList()).stream().allMatch(result -> result);
49+
}
5250
}
5351

5452
default <E> boolean process(final Object obj, final E value, final Collection<Rule<E>> rules) {

src/test/java/br/com/fluentvalidator/ValidatorTest.java

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,29 @@
1616

1717
package br.com.fluentvalidator;
1818

19+
import br.com.fluentvalidator.context.Error;
20+
import br.com.fluentvalidator.context.ProcessorContext;
21+
import br.com.fluentvalidator.context.ValidationResult;
22+
import br.com.fluentvalidator.model.Bill;
23+
import br.com.fluentvalidator.model.Boy;
24+
import br.com.fluentvalidator.model.Girl;
25+
import br.com.fluentvalidator.model.Parent;
26+
import br.com.fluentvalidator.validator.ValidatorBill;
27+
import br.com.fluentvalidator.validator.ValidatorErrorPredicate;
28+
import br.com.fluentvalidator.validator.ValidatorParent;
29+
import org.junit.jupiter.api.Test;
30+
31+
import java.time.LocalDate;
32+
import java.util.ArrayList;
33+
import java.util.Arrays;
34+
import java.util.Collection;
35+
import java.util.List;
36+
import java.util.concurrent.ConcurrentLinkedQueue;
37+
import java.util.concurrent.ExecutionException;
38+
import java.util.concurrent.ExecutorService;
39+
import java.util.concurrent.Executors;
40+
import java.util.concurrent.TimeUnit;
41+
1942
import static br.com.fluentvalidator.predicate.LogicalPredicate.not;
2043
import static br.com.fluentvalidator.predicate.StringPredicate.stringEmptyOrNull;
2144
import static org.hamcrest.MatcherAssert.assertThat;
@@ -27,31 +50,11 @@
2750
import static org.hamcrest.Matchers.hasSize;
2851
import static org.hamcrest.Matchers.not;
2952
import static org.hamcrest.Matchers.nullValue;
53+
import static org.junit.jupiter.api.Assertions.assertEquals;
3054
import static org.junit.jupiter.api.Assertions.assertFalse;
55+
import static org.junit.jupiter.api.Assertions.assertThrows;
3156
import static org.junit.jupiter.api.Assertions.assertTrue;
3257

33-
import java.time.LocalDate;
34-
import java.util.ArrayList;
35-
import java.util.Arrays;
36-
import java.util.Collection;
37-
import java.util.List;
38-
import java.util.concurrent.ConcurrentLinkedQueue;
39-
import java.util.concurrent.ExecutionException;
40-
import java.util.concurrent.ExecutorService;
41-
import java.util.concurrent.Executors;
42-
import java.util.concurrent.TimeUnit;
43-
44-
import org.junit.jupiter.api.Test;
45-
46-
import br.com.fluentvalidator.context.Error;
47-
import br.com.fluentvalidator.context.ValidationResult;
48-
import br.com.fluentvalidator.model.Bill;
49-
import br.com.fluentvalidator.model.Boy;
50-
import br.com.fluentvalidator.model.Girl;
51-
import br.com.fluentvalidator.model.Parent;
52-
import br.com.fluentvalidator.validator.ValidatorBill;
53-
import br.com.fluentvalidator.validator.ValidatorParent;
54-
5558
class ValidatorTest {
5659

5760
@Test
@@ -671,7 +674,44 @@ void testSuccessWhenBillDueDateIsExactlyThreeYears() {
671674
assertTrue(validate.isValid());
672675
}
673676

674-
class StringValidator extends AbstractValidator<String> {
677+
@Test
678+
public void testSuccessWhenBrokenPredicate() {
679+
final Validator<Bill> validator = new ValidatorErrorPredicate();
680+
681+
// First validation: bill with null description causes NullPointerException
682+
// This simulates a corrupted or incomplete data scenario
683+
final Bill billWithNullDescription = new Bill(null, 0F, LocalDate.now());
684+
685+
assertThrows(NullPointerException.class, () -> validator.validate(billWithNullDescription));
686+
687+
// Second validation: valid electricity bill with numeric code
688+
// ValidationContext and ProcessorContext should be clean after the exception
689+
final Bill electricityBill = new Bill("12345", 150.75F, LocalDate.now().plusDays(30));
690+
691+
assertTrue(validator.validate(electricityBill).isValid());
692+
693+
}
694+
695+
@Test
696+
public void testSuccessWhenBrokenCollectionPredicate() {
697+
final Validator<Bill> validator = new ValidatorErrorPredicate();
698+
699+
// First validation: bill with non-numeric description causes NumberFormatException
700+
// when the validator tries to parse it as integer in the collection rule
701+
final Bill billWithInvalidCodes = new Bill("WATER-BILL", 85.50F, LocalDate.now());
702+
703+
assertThrows(NumberFormatException.class, () -> validator.validate(billWithInvalidCodes));
704+
705+
// Second validation: valid bill with numeric service codes separated by comma
706+
// ValidationContext and ProcessorContext should be clean after the exception
707+
final Bill billWithValidCodes = new Bill("100,200,300", 250.00F, LocalDate.now().plusDays(15));
708+
709+
assertEquals(0, (int) ProcessorContext.get().get());
710+
assertTrue(validator.validate(billWithValidCodes).isValid());
711+
712+
}
713+
714+
static class StringValidator extends AbstractValidator<String> {
675715

676716
@Override
677717
public void rules() {
@@ -687,7 +727,7 @@ public void rules() {
687727

688728
}
689729

690-
class String2Validator extends AbstractValidator<String> {
730+
static class String2Validator extends AbstractValidator<String> {
691731

692732
@Override
693733
public void rules() {
@@ -698,7 +738,7 @@ public void rules() {
698738

699739
}
700740

701-
class String3Validator extends AbstractValidator<Collection<String>> {
741+
static class String3Validator extends AbstractValidator<Collection<String>> {
702742

703743
@Override
704744
public void rules() {

src/test/java/br/com/fluentvalidator/predicate/MapPredicateTest.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,14 @@ public void before() {
4646

4747
@Test
4848
void testMapGetKPredicateOfV() {
49-
assertThat(MapPredicate.<String, String, Map<String, String>>mapGet(of(x -> "a"), stringSize(6)).test(map), equalTo(true));
50-
assertThat(MapPredicate.<String, String, Map<String, String>>mapGet(of(x -> "a"), isNumber()).test(map), equalTo(true));
51-
assertThat(MapPredicate.<String, String, Map<String, String>>mapGet(of(x -> "a"), not(stringEmptyOrNull())).test(map), equalTo(true));
52-
assertThat(MapPredicate.<String, String, Map<String, String>>mapGet(of(x -> "b"), stringSize(5)).test(map), equalTo(true));
53-
assertThat(MapPredicate.<String, String, Map<String, String>>mapGet(of(x -> "b"), isAlpha()).test(map), equalTo(true));
54-
assertThat(MapPredicate.<String, String, Map<String, String>>mapGet(of(x -> "b"), not(stringEmptyOrNull())).test(map), equalTo(true));
55-
assertThat(MapPredicate.<String, String, Map<String, String>>mapGet(of(x -> "c"), stringEmptyOrNull()).test(map), equalTo(true));
56-
assertThat(MapPredicate.<String, String, Map<String, String>>mapGet(of(x -> "c"), not(stringEmptyOrNull())).test(map), equalTo(false));
49+
assertThat(mapGet(of((Map<String, String> x) -> "a"), stringSize(6)).test(map), equalTo(true));
50+
assertThat(mapGet(of((Map<String, String> x) -> "a"), isNumber()).test(map), equalTo(true));
51+
assertThat(mapGet(of((Map<String, String> x) -> "a"), not(stringEmptyOrNull())).test(map), equalTo(true));
52+
assertThat(mapGet(of((Map<String, String> x) -> "b"), stringSize(5)).test(map), equalTo(true));
53+
assertThat(mapGet(of((Map<String, String> x) -> "b"), isAlpha()).test(map), equalTo(true));
54+
assertThat(mapGet(of((Map<String, String> x) -> "b"), not(stringEmptyOrNull())).test(map), equalTo(true));
55+
assertThat(mapGet(of((Map<String, String> x) -> "c"), stringEmptyOrNull()).test(map), equalTo(true));
56+
assertThat(mapGet(of((Map<String, String> x) -> "c"), not(stringEmptyOrNull())).test(map), equalTo(false));
5757
assertThat(MapPredicate.<String, String, Map<String, String>>mapGet(of(x -> "a"), null).test(map), equalTo(false));
5858
assertThat(MapPredicate.<String, String, Map<String, String>>mapGet(null, not(stringEmptyOrNull())).test(map), equalTo(false));
5959
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package br.com.fluentvalidator.validator;
2+
3+
import br.com.fluentvalidator.AbstractValidator;
4+
import br.com.fluentvalidator.model.Bill;
5+
6+
import java.util.Arrays;
7+
8+
import static br.com.fluentvalidator.predicate.LogicalPredicate.not;
9+
import static br.com.fluentvalidator.predicate.ObjectPredicate.nullValue;
10+
11+
public class ValidatorErrorPredicate extends AbstractValidator<Bill> {
12+
13+
@Override
14+
public void rules() {
15+
16+
17+
ruleFor(bill -> bill)
18+
.must(not(nullValue()))
19+
.withMessage("Object is required")
20+
.withFieldName("root")
21+
22+
.must(bill -> bill.getValue() > 1)
23+
.when(not(nullValue())).withMessage("Value must be greater than 1");
24+
25+
26+
ruleFor(bill -> bill.getDescription())
27+
.must(not(nullValue()))
28+
.when(e -> e.equalsIgnoreCase("1"));
29+
30+
ruleForEach(bill -> Arrays.asList(bill.getDescription().split(",")))
31+
.whenever(not(nullValue()))
32+
.withValidator(new AbstractValidator<String>() {
33+
@Override
34+
public void rules() {
35+
ruleFor(s -> s)
36+
.must(s -> Integer.parseInt(s) > 1);
37+
}
38+
});
39+
40+
41+
}
42+
}

0 commit comments

Comments
 (0)