Skip to content

Commit eb2f203

Browse files
committed
provide jackson module for validating beans after deserialization
1 parent 216b7e0 commit eb2f203

4 files changed

Lines changed: 194 additions & 0 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package de.rwth.idsg.ocpp.jaxb.validation;
2+
3+
import tools.jackson.databind.BeanDescription.Supplier;
4+
import tools.jackson.databind.DeserializationConfig;
5+
import tools.jackson.databind.ValueDeserializer;
6+
import tools.jackson.databind.deser.ValueDeserializerModifier;
7+
import tools.jackson.databind.deser.bean.BeanDeserializer;
8+
9+
/**
10+
* https://www.baeldung.com/java-object-validation-deserialization
11+
*/
12+
public class BeanDeserializerModifierWithValidation extends ValueDeserializerModifier {
13+
14+
@Override
15+
public ValueDeserializer<?> modifyDeserializer(DeserializationConfig config,
16+
Supplier beanDescRef,
17+
ValueDeserializer<?> deserializer) {
18+
if (deserializer instanceof BeanDeserializer) {
19+
return new BeanDeserializerWithValidation((BeanDeserializer) deserializer);
20+
}
21+
22+
return deserializer;
23+
}
24+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package de.rwth.idsg.ocpp.jaxb.validation;
2+
3+
import tools.jackson.core.JacksonException;
4+
import tools.jackson.core.JsonParser;
5+
import tools.jackson.databind.DeserializationContext;
6+
import tools.jackson.databind.deser.bean.BeanDeserializer;
7+
8+
import jakarta.validation.ConstraintViolationException;
9+
import jakarta.validation.Validation;
10+
import jakarta.validation.Validator;
11+
12+
/**
13+
* https://www.baeldung.com/java-object-validation-deserialization
14+
*/
15+
public class BeanDeserializerWithValidation extends BeanDeserializer {
16+
17+
private final Validator validator;
18+
19+
public BeanDeserializerWithValidation(BeanDeserializer src) {
20+
super(src);
21+
validator = Validation.buildDefaultValidatorFactory().getValidator();
22+
}
23+
24+
@Override
25+
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
26+
var instance = super.deserialize(p, ctxt);
27+
validate(instance);
28+
return instance;
29+
}
30+
31+
private void validate(Object instance) {
32+
var violations = validator.validate(instance);
33+
if (!violations.isEmpty()) {
34+
throw new ConstraintViolationException(violations);
35+
}
36+
}
37+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package de.rwth.idsg.ocpp.jaxb.validation;
2+
3+
import tools.jackson.databind.module.SimpleModule;
4+
5+
/**
6+
* https://www.baeldung.com/java-object-validation-deserialization
7+
*/
8+
public class BeanValidationModule extends SimpleModule {
9+
10+
public BeanValidationModule() {
11+
super();
12+
setDeserializerModifier(new BeanDeserializerModifierWithValidation());
13+
}
14+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package de.rwth.idsg.ocpp.jaxb;
2+
3+
import de.rwth.idsg.ocpp.jaxb.validation.BeanValidationModule;
4+
import ocpp.cs._2015._10.StartTransactionRequest;
5+
import org.joda.time.DateTime;
6+
import org.junit.jupiter.api.Assertions;
7+
import org.junit.jupiter.api.Test;
8+
import tools.jackson.databind.ObjectMapper;
9+
import tools.jackson.databind.json.JsonMapper;
10+
import tools.jackson.datatype.joda.JodaModule;
11+
12+
import jakarta.validation.ConstraintViolation;
13+
import jakarta.validation.ConstraintViolationException;
14+
import java.util.Set;
15+
import java.util.stream.Collectors;
16+
17+
import static org.junit.jupiter.api.Assertions.assertThrows;
18+
19+
public class BeanValidationTest {
20+
21+
private final ObjectMapper mapper = JsonMapper.builder()
22+
.addModule(new JodaModule())
23+
.addModule(new BeanValidationModule())
24+
.build();
25+
26+
@Test
27+
public void nullFieldsOcpp12() {
28+
String input = mapper.writeValueAsString(new ocpp.cs._2010._08.StartTransactionRequest());
29+
System.out.println(input);
30+
31+
var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp.cs._2010._08.StartTransactionRequest.class));
32+
33+
var violations = exception.getConstraintViolations();
34+
checkViolatingNullFields(Set.of("connectorId", "idTag", "timestamp", "meterStart"), violations);
35+
}
36+
37+
@Test
38+
public void nullFieldsOcpp15() {
39+
String input = mapper.writeValueAsString(new ocpp.cs._2012._06.StartTransactionRequest());
40+
System.out.println(input);
41+
42+
var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp.cs._2012._06.StartTransactionRequest.class));
43+
44+
var violations = exception.getConstraintViolations();
45+
checkViolatingNullFields(Set.of("connectorId", "idTag", "timestamp", "meterStart"), violations);
46+
}
47+
48+
@Test
49+
public void nullFieldsOcpp16() {
50+
String input = mapper.writeValueAsString(new ocpp.cs._2015._10.StartTransactionRequest());
51+
System.out.println(input);
52+
53+
var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp.cs._2015._10.StartTransactionRequest.class));
54+
55+
var violations = exception.getConstraintViolations();
56+
checkViolatingNullFields(Set.of("connectorId", "idTag", "timestamp", "meterStart"), violations);
57+
}
58+
59+
@Test
60+
public void nullFieldsOcpp16Security() {
61+
String input = mapper.writeValueAsString(new ocpp._2022._02.security.SecurityEventNotification());
62+
System.out.println(input);
63+
64+
var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp._2022._02.security.SecurityEventNotification.class));
65+
66+
var violations = exception.getConstraintViolations();
67+
checkViolatingNullFields(Set.of("type", "timestamp"), violations);
68+
}
69+
70+
@Test
71+
public void nullFieldsOcpp2() {
72+
String input = mapper.writeValueAsString(new ocpp._2020._03.SecurityEventNotificationRequest());
73+
System.out.println(input);
74+
75+
var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp._2020._03.SecurityEventNotificationRequest.class));
76+
77+
var violations = exception.getConstraintViolations();
78+
checkViolatingNullFields(Set.of("type", "timestamp"), violations);
79+
}
80+
81+
@Test
82+
public void startTransactionIdTagTooLong() {
83+
StartTransactionRequest request = new StartTransactionRequest()
84+
.withConnectorId(1)
85+
.withMeterStart(0)
86+
.withTimestamp(DateTime.now())
87+
.withIdTag("ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC");
88+
89+
String input = mapper.writeValueAsString(request);
90+
System.out.println(input);
91+
92+
var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, StartTransactionRequest.class));
93+
94+
var violations = exception.getConstraintViolations();
95+
Assertions.assertEquals(1, violations.size());
96+
97+
ConstraintViolation<?> violation = violations.iterator().next();
98+
99+
Assertions.assertEquals("idTag", violation.getPropertyPath().toString());
100+
Assertions.assertEquals("size must be between 0 and 20", violation.getMessage());
101+
102+
}
103+
104+
private static void checkViolatingNullFields(Set<String> expected, Set<ConstraintViolation<?>> violations) {
105+
Assertions.assertEquals(expected.size(), violations.size());
106+
107+
var violatingFields = violations.stream()
108+
.map(it -> it.getPropertyPath().toString())
109+
.collect(Collectors.toSet());
110+
111+
Assertions.assertEquals(expected, violatingFields);
112+
113+
var violationReason = violations.stream()
114+
.map(ConstraintViolation::getMessage)
115+
.collect(Collectors.toSet());
116+
117+
Assertions.assertEquals(violationReason, Set.of("must not be null"));
118+
}
119+
}

0 commit comments

Comments
 (0)