Skip to content

Commit 0519e3b

Browse files
authored
Merge pull request #25 from steve-community/enable-bean-validation
Enable bean validation
2 parents bf76af0 + e158b3c commit 0519e3b

File tree

9 files changed

+476
-1
lines changed

9 files changed

+476
-1
lines changed

ocpp-jaxb/pom.xml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,16 @@
3232
<bindingFile>${basedir}/src/main/resources/wsdl-binding/add_interface.xml</bindingFile>
3333
</bindingFiles>
3434
<extraargs>
35+
<extraarg>-verbose</extraarg>
36+
<extraarg>-frontend</extraarg>
37+
<extraarg>krasa</extraarg>
38+
<extraarg>-xjc-XReplacePrimitives</extraarg>
39+
<extraarg>-xjc-XJsr303Annotations</extraarg>
40+
<extraarg>-xjc-XJsr303Annotations:validationAnnotations=jakarta</extraarg>
41+
3542
<extraarg>-xjc-Xfluent-api</extraarg>
3643
<extraarg>-xjc-Xinheritance</extraarg>
3744
<extraarg>-xjc-Xannotate</extraarg>
38-
<!--<extraarg>-verbose</extraarg>-->
3945
<extraarg>-exsh</extraarg>
4046
<extraarg>true</extraarg>
4147
</extraargs>
@@ -66,6 +72,12 @@
6672
<artifactId>jaxb-plugin-annotate</artifactId>
6773
<version>4.0.8</version>
6874
</dependency>
75+
<!-- provides validation -->
76+
<dependency>
77+
<groupId>com.fillumina</groupId>
78+
<artifactId>krasa-jaxb-tools</artifactId>
79+
<version>2.5.1</version>
80+
</dependency>
6981
</dependencies>
7082
</plugin>
7183
<plugin>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package de.rwth.idsg.ocpp.jaxb.validation;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import tools.jackson.databind.BeanDescription.Supplier;
5+
import tools.jackson.databind.DeserializationConfig;
6+
import tools.jackson.databind.ValueDeserializer;
7+
import tools.jackson.databind.deser.ValueDeserializerModifier;
8+
import tools.jackson.databind.deser.bean.BeanDeserializer;
9+
10+
import jakarta.validation.Validation;
11+
import jakarta.validation.Validator;
12+
13+
/**
14+
* https://www.baeldung.com/java-object-validation-deserialization
15+
*/
16+
@RequiredArgsConstructor
17+
public class BeanDeserializerModifierWithValidation extends ValueDeserializerModifier {
18+
19+
private final Validator validator;
20+
21+
@Override
22+
public ValueDeserializer<?> modifyDeserializer(DeserializationConfig config,
23+
Supplier beanDescRef,
24+
ValueDeserializer<?> deserializer) {
25+
if (deserializer instanceof BeanDeserializer) {
26+
return new BeanDeserializerWithValidation((BeanDeserializer) deserializer, validator);
27+
}
28+
29+
return deserializer;
30+
}
31+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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.Validator;
10+
11+
/**
12+
* https://www.baeldung.com/java-object-validation-deserialization
13+
*/
14+
public class BeanDeserializerWithValidation extends BeanDeserializer {
15+
16+
private final Validator validator;
17+
18+
public BeanDeserializerWithValidation(BeanDeserializer src, Validator validator) {
19+
super(src);
20+
this.validator = validator;
21+
}
22+
23+
@Override
24+
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
25+
var instance = super.deserialize(p, ctxt);
26+
27+
var violations = validator.validate(instance);
28+
if (!violations.isEmpty()) {
29+
throw new ConstraintViolationException(violations);
30+
}
31+
32+
return instance;
33+
}
34+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package de.rwth.idsg.ocpp.jaxb.validation;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import tools.jackson.databind.BeanDescription;
5+
import tools.jackson.databind.SerializationConfig;
6+
import tools.jackson.databind.ValueSerializer;
7+
import tools.jackson.databind.ser.ValueSerializerModifier;
8+
import tools.jackson.databind.ser.bean.BeanSerializerBase;
9+
10+
import jakarta.validation.Validator;
11+
12+
@RequiredArgsConstructor
13+
public class BeanSerializerModifierWithValidation extends ValueSerializerModifier {
14+
15+
private final Validator validator;
16+
17+
@Override
18+
public ValueSerializer<?> modifySerializer(SerializationConfig config,
19+
BeanDescription.Supplier beanDesc,
20+
ValueSerializer<?> serializer) {
21+
if (serializer instanceof BeanSerializerBase) {
22+
return new BeanSerializerWithValidation((BeanSerializerBase) serializer, validator);
23+
}
24+
25+
return serializer;
26+
}
27+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package de.rwth.idsg.ocpp.jaxb.validation;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import tools.jackson.core.JacksonException;
5+
import tools.jackson.core.JsonGenerator;
6+
import tools.jackson.databind.SerializationContext;
7+
import tools.jackson.databind.ValueSerializer;
8+
import tools.jackson.databind.ser.bean.BeanSerializerBase;
9+
10+
import jakarta.validation.ConstraintViolationException;
11+
import jakarta.validation.Validator;
12+
13+
@RequiredArgsConstructor
14+
public class BeanSerializerWithValidation extends ValueSerializer<Object> {
15+
16+
private final BeanSerializerBase delegate;
17+
private final Validator validator;
18+
19+
@Override
20+
public void serialize(Object bean, JsonGenerator gen, SerializationContext ctxt) throws JacksonException {
21+
var violations = validator.validate(bean);
22+
if (!violations.isEmpty()) {
23+
throw new ConstraintViolationException(violations);
24+
}
25+
26+
delegate.serialize(bean, gen, ctxt);
27+
}
28+
29+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package de.rwth.idsg.ocpp.jaxb.validation;
2+
3+
import tools.jackson.databind.module.SimpleModule;
4+
5+
import jakarta.validation.Validation;
6+
import jakarta.validation.Validator;
7+
8+
/**
9+
* https://www.baeldung.com/java-object-validation-deserialization
10+
*/
11+
public class BeanValidationModule extends SimpleModule {
12+
13+
private BeanValidationModule(Validator validator, boolean forReading, boolean forWriting) {
14+
Validator validatorToUse = validator == null
15+
? Validation.buildDefaultValidatorFactory().getValidator()
16+
: validator;
17+
18+
if (forReading) {
19+
setDeserializerModifier(new BeanDeserializerModifierWithValidation(validatorToUse));
20+
}
21+
22+
if (forWriting) {
23+
setSerializerModifier(new BeanSerializerModifierWithValidation(validatorToUse));
24+
}
25+
}
26+
27+
public static BeanValidationModule forReading(Validator validator) {
28+
return new BeanValidationModule(validator, true, false);
29+
}
30+
31+
public static BeanValidationModule forWriting(Validator validator) {
32+
return new BeanValidationModule(validator, false, true);
33+
}
34+
35+
public static BeanValidationModule forReadingAndWriting(Validator validator) {
36+
return new BeanValidationModule(validator, true, true);
37+
}
38+
39+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package de.rwth.idsg.ocpp.jaxb;
2+
3+
import de.rwth.idsg.ocpp.jaxb.validation.BeanValidationModule;
4+
import ocpp._2020._03.CustomData;
5+
import ocpp._2020._03.SecurityEventNotificationRequest;
6+
import ocpp.cs._2015._10.AuthorizeResponse;
7+
import ocpp.cs._2015._10.IdTagInfo;
8+
import ocpp.cs._2015._10.StartTransactionRequest;
9+
import org.joda.time.DateTime;
10+
import org.junit.jupiter.api.Assertions;
11+
import org.junit.jupiter.api.BeforeAll;
12+
import org.junit.jupiter.api.Test;
13+
import tools.jackson.databind.DatabindException;
14+
import tools.jackson.databind.ObjectMapper;
15+
import tools.jackson.databind.json.JsonMapper;
16+
import tools.jackson.datatype.joda.JodaModule;
17+
18+
import jakarta.validation.ConstraintViolation;
19+
import jakarta.validation.ConstraintViolationException;
20+
import java.util.Set;
21+
import java.util.stream.Collectors;
22+
23+
import static org.junit.jupiter.api.Assertions.assertThrows;
24+
25+
public class BeanDeserializerValidationTest {
26+
27+
private static ObjectMapper mapper;
28+
29+
@BeforeAll
30+
public static void setup() {
31+
mapper = JsonMapper.builder()
32+
.addModule(new JodaModule())
33+
.addModule(BeanValidationModule.forReading(null))
34+
.build();
35+
}
36+
37+
@Test
38+
public void nullFieldsOcpp12() {
39+
String input = mapper.writeValueAsString(new ocpp.cs._2010._08.StartTransactionRequest());
40+
41+
var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp.cs._2010._08.StartTransactionRequest.class));
42+
43+
var violations = exception.getConstraintViolations();
44+
checkViolatingNullFields(Set.of("connectorId", "idTag", "timestamp", "meterStart"), violations);
45+
}
46+
47+
@Test
48+
public void nullFieldsOcpp15() {
49+
String input = mapper.writeValueAsString(new ocpp.cs._2012._06.StartTransactionRequest());
50+
51+
var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp.cs._2012._06.StartTransactionRequest.class));
52+
53+
var violations = exception.getConstraintViolations();
54+
checkViolatingNullFields(Set.of("connectorId", "idTag", "timestamp", "meterStart"), violations);
55+
}
56+
57+
@Test
58+
public void nullFieldsOcpp16() {
59+
String input = mapper.writeValueAsString(new ocpp.cs._2015._10.StartTransactionRequest());
60+
61+
var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp.cs._2015._10.StartTransactionRequest.class));
62+
63+
var violations = exception.getConstraintViolations();
64+
checkViolatingNullFields(Set.of("connectorId", "idTag", "timestamp", "meterStart"), violations);
65+
}
66+
67+
@Test
68+
public void nullFieldsOcpp16Security() {
69+
String input = mapper.writeValueAsString(new ocpp._2022._02.security.SecurityEventNotification());
70+
71+
var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp._2022._02.security.SecurityEventNotification.class));
72+
73+
var violations = exception.getConstraintViolations();
74+
checkViolatingNullFields(Set.of("type", "timestamp"), violations);
75+
}
76+
77+
@Test
78+
public void nullFieldsOcpp2() {
79+
String input = mapper.writeValueAsString(new ocpp._2020._03.SecurityEventNotificationRequest());
80+
81+
var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp._2020._03.SecurityEventNotificationRequest.class));
82+
83+
var violations = exception.getConstraintViolations();
84+
checkViolatingNullFields(Set.of("type", "timestamp"), violations);
85+
}
86+
87+
@Test
88+
public void startTransactionIdTagTooLong() {
89+
StartTransactionRequest request = new StartTransactionRequest()
90+
.withConnectorId(1)
91+
.withMeterStart(0)
92+
.withTimestamp(DateTime.now())
93+
.withIdTag("ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC");
94+
95+
String input = mapper.writeValueAsString(request);
96+
System.out.println(input);
97+
98+
var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, StartTransactionRequest.class));
99+
100+
var violations = exception.getConstraintViolations();
101+
Assertions.assertEquals(1, violations.size());
102+
103+
ConstraintViolation<?> violation = violations.iterator().next();
104+
105+
Assertions.assertEquals("idTag", violation.getPropertyPath().toString());
106+
Assertions.assertEquals("size must be between 0 and 20", violation.getMessage());
107+
}
108+
109+
@Test
110+
public void embeddedCustomDataEmpty() {
111+
var req = new SecurityEventNotificationRequest()
112+
.withType("type")
113+
.withTimestamp(DateTime.now())
114+
.withCustomData(new CustomData());
115+
116+
String input = mapper.writeValueAsString(req);
117+
118+
var exception = assertThrows(DatabindException.class, () -> mapper.readValue(input, SecurityEventNotificationRequest.class));
119+
120+
Throwable cause = exception.getCause();
121+
Assertions.assertInstanceOf(ConstraintViolationException.class, cause);
122+
123+
Assertions.assertEquals("vendorId: must not be null", cause.getMessage());
124+
}
125+
126+
@Test
127+
public void embeddedIdTagInfoEmpty() {
128+
var req = new AuthorizeResponse()
129+
.withIdTagInfo(new IdTagInfo());
130+
131+
String input = mapper.writeValueAsString(req);
132+
133+
var exception = assertThrows(DatabindException.class, () -> mapper.readValue(input, AuthorizeResponse.class));
134+
135+
Throwable cause = exception.getCause();
136+
Assertions.assertInstanceOf(ConstraintViolationException.class, cause);
137+
138+
Assertions.assertEquals("status: must not be null", cause.getMessage());
139+
}
140+
141+
private static void checkViolatingNullFields(Set<String> expected, Set<ConstraintViolation<?>> violations) {
142+
Assertions.assertEquals(expected.size(), violations.size());
143+
144+
var violatingFields = violations.stream()
145+
.map(it -> it.getPropertyPath().toString())
146+
.collect(Collectors.toSet());
147+
148+
Assertions.assertEquals(expected, violatingFields);
149+
150+
var violationReason = violations.stream()
151+
.map(ConstraintViolation::getMessage)
152+
.collect(Collectors.toSet());
153+
154+
Assertions.assertEquals(violationReason, Set.of("must not be null"));
155+
}
156+
}

0 commit comments

Comments
 (0)