From 216b7e0088ca4bdad48ae3071af2daef6526aedb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20Go=CC=88kay?= Date: Wed, 28 Jan 2026 14:18:38 +0100 Subject: [PATCH 1/6] create bean validation annotations for JAXB classes https://github.com/fillumina/krasa-jaxb-tools --- ocpp-jaxb/pom.xml | 14 +++++++++++++- pom.xml | 13 +++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/ocpp-jaxb/pom.xml b/ocpp-jaxb/pom.xml index 602ae9f..bcdeb05 100644 --- a/ocpp-jaxb/pom.xml +++ b/ocpp-jaxb/pom.xml @@ -32,10 +32,16 @@ ${basedir}/src/main/resources/wsdl-binding/add_interface.xml + -verbose + -frontend + krasa + -xjc-XReplacePrimitives + -xjc-XJsr303Annotations + -xjc-XJsr303Annotations:validationAnnotations=jakarta + -xjc-Xfluent-api -xjc-Xinheritance -xjc-Xannotate - -exsh true @@ -66,6 +72,12 @@ jaxb-plugin-annotate 4.0.8 + + + com.fillumina + krasa-jaxb-tools + 2.5.1 + diff --git a/pom.xml b/pom.xml index 82daa7b..d85c8df 100644 --- a/pom.xml +++ b/pom.xml @@ -144,6 +144,19 @@ provided + + org.apache.tomcat.embed + tomcat-embed-el + 11.0.15 + provided + + + org.hibernate.validator + hibernate-validator + 9.0.1.Final + provided + + org.junit.jupiter junit-jupiter-engine From eb2f20385e0aaf6d6157083d1c7c6d0dd1bba27e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20Go=CC=88kay?= Date: Wed, 28 Jan 2026 14:22:34 +0100 Subject: [PATCH 2/6] provide jackson module for validating beans after deserialization --- ...eanDeserializerModifierWithValidation.java | 24 ++++ .../BeanDeserializerWithValidation.java | 37 ++++++ .../jaxb/validation/BeanValidationModule.java | 14 +++ .../idsg/ocpp/jaxb/BeanValidationTest.java | 119 ++++++++++++++++++ 4 files changed, 194 insertions(+) create mode 100644 ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanDeserializerModifierWithValidation.java create mode 100644 ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanDeserializerWithValidation.java create mode 100644 ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanValidationModule.java create mode 100644 ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanValidationTest.java diff --git a/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanDeserializerModifierWithValidation.java b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanDeserializerModifierWithValidation.java new file mode 100644 index 0000000..563421c --- /dev/null +++ b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanDeserializerModifierWithValidation.java @@ -0,0 +1,24 @@ +package de.rwth.idsg.ocpp.jaxb.validation; + +import tools.jackson.databind.BeanDescription.Supplier; +import tools.jackson.databind.DeserializationConfig; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.deser.ValueDeserializerModifier; +import tools.jackson.databind.deser.bean.BeanDeserializer; + +/** + * https://www.baeldung.com/java-object-validation-deserialization + */ +public class BeanDeserializerModifierWithValidation extends ValueDeserializerModifier { + + @Override + public ValueDeserializer modifyDeserializer(DeserializationConfig config, + Supplier beanDescRef, + ValueDeserializer deserializer) { + if (deserializer instanceof BeanDeserializer) { + return new BeanDeserializerWithValidation((BeanDeserializer) deserializer); + } + + return deserializer; + } +} diff --git a/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanDeserializerWithValidation.java b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanDeserializerWithValidation.java new file mode 100644 index 0000000..0d41fad --- /dev/null +++ b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanDeserializerWithValidation.java @@ -0,0 +1,37 @@ +package de.rwth.idsg.ocpp.jaxb.validation; + +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.deser.bean.BeanDeserializer; + +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Validation; +import jakarta.validation.Validator; + +/** + * https://www.baeldung.com/java-object-validation-deserialization + */ +public class BeanDeserializerWithValidation extends BeanDeserializer { + + private final Validator validator; + + public BeanDeserializerWithValidation(BeanDeserializer src) { + super(src); + validator = Validation.buildDefaultValidatorFactory().getValidator(); + } + + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException { + var instance = super.deserialize(p, ctxt); + validate(instance); + return instance; + } + + private void validate(Object instance) { + var violations = validator.validate(instance); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + } +} diff --git a/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanValidationModule.java b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanValidationModule.java new file mode 100644 index 0000000..6f777f0 --- /dev/null +++ b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanValidationModule.java @@ -0,0 +1,14 @@ +package de.rwth.idsg.ocpp.jaxb.validation; + +import tools.jackson.databind.module.SimpleModule; + +/** + * https://www.baeldung.com/java-object-validation-deserialization + */ +public class BeanValidationModule extends SimpleModule { + + public BeanValidationModule() { + super(); + setDeserializerModifier(new BeanDeserializerModifierWithValidation()); + } +} diff --git a/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanValidationTest.java b/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanValidationTest.java new file mode 100644 index 0000000..21a4699 --- /dev/null +++ b/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanValidationTest.java @@ -0,0 +1,119 @@ +package de.rwth.idsg.ocpp.jaxb; + +import de.rwth.idsg.ocpp.jaxb.validation.BeanValidationModule; +import ocpp.cs._2015._10.StartTransactionRequest; +import org.joda.time.DateTime; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.datatype.joda.JodaModule; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class BeanValidationTest { + + private final ObjectMapper mapper = JsonMapper.builder() + .addModule(new JodaModule()) + .addModule(new BeanValidationModule()) + .build(); + + @Test + public void nullFieldsOcpp12() { + String input = mapper.writeValueAsString(new ocpp.cs._2010._08.StartTransactionRequest()); + System.out.println(input); + + var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp.cs._2010._08.StartTransactionRequest.class)); + + var violations = exception.getConstraintViolations(); + checkViolatingNullFields(Set.of("connectorId", "idTag", "timestamp", "meterStart"), violations); + } + + @Test + public void nullFieldsOcpp15() { + String input = mapper.writeValueAsString(new ocpp.cs._2012._06.StartTransactionRequest()); + System.out.println(input); + + var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp.cs._2012._06.StartTransactionRequest.class)); + + var violations = exception.getConstraintViolations(); + checkViolatingNullFields(Set.of("connectorId", "idTag", "timestamp", "meterStart"), violations); + } + + @Test + public void nullFieldsOcpp16() { + String input = mapper.writeValueAsString(new ocpp.cs._2015._10.StartTransactionRequest()); + System.out.println(input); + + var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp.cs._2015._10.StartTransactionRequest.class)); + + var violations = exception.getConstraintViolations(); + checkViolatingNullFields(Set.of("connectorId", "idTag", "timestamp", "meterStart"), violations); + } + + @Test + public void nullFieldsOcpp16Security() { + String input = mapper.writeValueAsString(new ocpp._2022._02.security.SecurityEventNotification()); + System.out.println(input); + + var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp._2022._02.security.SecurityEventNotification.class)); + + var violations = exception.getConstraintViolations(); + checkViolatingNullFields(Set.of("type", "timestamp"), violations); + } + + @Test + public void nullFieldsOcpp2() { + String input = mapper.writeValueAsString(new ocpp._2020._03.SecurityEventNotificationRequest()); + System.out.println(input); + + var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp._2020._03.SecurityEventNotificationRequest.class)); + + var violations = exception.getConstraintViolations(); + checkViolatingNullFields(Set.of("type", "timestamp"), violations); + } + + @Test + public void startTransactionIdTagTooLong() { + StartTransactionRequest request = new StartTransactionRequest() + .withConnectorId(1) + .withMeterStart(0) + .withTimestamp(DateTime.now()) + .withIdTag("ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC"); + + String input = mapper.writeValueAsString(request); + System.out.println(input); + + var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, StartTransactionRequest.class)); + + var violations = exception.getConstraintViolations(); + Assertions.assertEquals(1, violations.size()); + + ConstraintViolation violation = violations.iterator().next(); + + Assertions.assertEquals("idTag", violation.getPropertyPath().toString()); + Assertions.assertEquals("size must be between 0 and 20", violation.getMessage()); + + } + + private static void checkViolatingNullFields(Set expected, Set> violations) { + Assertions.assertEquals(expected.size(), violations.size()); + + var violatingFields = violations.stream() + .map(it -> it.getPropertyPath().toString()) + .collect(Collectors.toSet()); + + Assertions.assertEquals(expected, violatingFields); + + var violationReason = violations.stream() + .map(ConstraintViolation::getMessage) + .collect(Collectors.toSet()); + + Assertions.assertEquals(violationReason, Set.of("must not be null")); + } +} From 9f55673de414abc9dcc50e55fc88c4e2d7b6937b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20Go=CC=88kay?= Date: Wed, 28 Jan 2026 15:02:33 +0100 Subject: [PATCH 3/6] refactor --- ...eanDeserializerModifierWithValidation.java | 9 ++++++++- .../BeanDeserializerWithValidation.java | 11 ++++------- .../jaxb/validation/BeanValidationModule.java | 10 ++++++++-- .../idsg/ocpp/jaxb/BeanValidationTest.java | 19 ++++++++++--------- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanDeserializerModifierWithValidation.java b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanDeserializerModifierWithValidation.java index 563421c..211a0de 100644 --- a/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanDeserializerModifierWithValidation.java +++ b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanDeserializerModifierWithValidation.java @@ -1,22 +1,29 @@ package de.rwth.idsg.ocpp.jaxb.validation; +import lombok.RequiredArgsConstructor; import tools.jackson.databind.BeanDescription.Supplier; import tools.jackson.databind.DeserializationConfig; import tools.jackson.databind.ValueDeserializer; import tools.jackson.databind.deser.ValueDeserializerModifier; import tools.jackson.databind.deser.bean.BeanDeserializer; +import jakarta.validation.Validation; +import jakarta.validation.Validator; + /** * https://www.baeldung.com/java-object-validation-deserialization */ +@RequiredArgsConstructor public class BeanDeserializerModifierWithValidation extends ValueDeserializerModifier { + private final Validator validator; + @Override public ValueDeserializer modifyDeserializer(DeserializationConfig config, Supplier beanDescRef, ValueDeserializer deserializer) { if (deserializer instanceof BeanDeserializer) { - return new BeanDeserializerWithValidation((BeanDeserializer) deserializer); + return new BeanDeserializerWithValidation((BeanDeserializer) deserializer, validator); } return deserializer; diff --git a/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanDeserializerWithValidation.java b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanDeserializerWithValidation.java index 0d41fad..895563c 100644 --- a/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanDeserializerWithValidation.java +++ b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanDeserializerWithValidation.java @@ -6,7 +6,6 @@ import tools.jackson.databind.deser.bean.BeanDeserializer; import jakarta.validation.ConstraintViolationException; -import jakarta.validation.Validation; import jakarta.validation.Validator; /** @@ -16,22 +15,20 @@ public class BeanDeserializerWithValidation extends BeanDeserializer { private final Validator validator; - public BeanDeserializerWithValidation(BeanDeserializer src) { + public BeanDeserializerWithValidation(BeanDeserializer src, Validator validator) { super(src); - validator = Validation.buildDefaultValidatorFactory().getValidator(); + this.validator = validator; } @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException { var instance = super.deserialize(p, ctxt); - validate(instance); - return instance; - } - private void validate(Object instance) { var violations = validator.validate(instance); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); } + + return instance; } } diff --git a/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanValidationModule.java b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanValidationModule.java index 6f777f0..de99d35 100644 --- a/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanValidationModule.java +++ b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanValidationModule.java @@ -2,13 +2,19 @@ import tools.jackson.databind.module.SimpleModule; +import jakarta.validation.Validation; +import jakarta.validation.Validator; + /** * https://www.baeldung.com/java-object-validation-deserialization */ public class BeanValidationModule extends SimpleModule { public BeanValidationModule() { - super(); - setDeserializerModifier(new BeanDeserializerModifierWithValidation()); + this(Validation.buildDefaultValidatorFactory().getValidator()); + } + + public BeanValidationModule(Validator validator) { + setDeserializerModifier(new BeanDeserializerModifierWithValidation(validator)); } } diff --git a/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanValidationTest.java b/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanValidationTest.java index 21a4699..3fb1e85 100644 --- a/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanValidationTest.java +++ b/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanValidationTest.java @@ -4,6 +4,7 @@ import ocpp.cs._2015._10.StartTransactionRequest; import org.joda.time.DateTime; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper; @@ -18,15 +19,19 @@ public class BeanValidationTest { - private final ObjectMapper mapper = JsonMapper.builder() - .addModule(new JodaModule()) - .addModule(new BeanValidationModule()) - .build(); + private static ObjectMapper mapper; + + @BeforeAll + public static void setup() { + mapper = JsonMapper.builder() + .addModule(new JodaModule()) + .addModule(new BeanValidationModule()) + .build(); + } @Test public void nullFieldsOcpp12() { String input = mapper.writeValueAsString(new ocpp.cs._2010._08.StartTransactionRequest()); - System.out.println(input); var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp.cs._2010._08.StartTransactionRequest.class)); @@ -37,7 +42,6 @@ public void nullFieldsOcpp12() { @Test public void nullFieldsOcpp15() { String input = mapper.writeValueAsString(new ocpp.cs._2012._06.StartTransactionRequest()); - System.out.println(input); var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp.cs._2012._06.StartTransactionRequest.class)); @@ -48,7 +52,6 @@ public void nullFieldsOcpp15() { @Test public void nullFieldsOcpp16() { String input = mapper.writeValueAsString(new ocpp.cs._2015._10.StartTransactionRequest()); - System.out.println(input); var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp.cs._2015._10.StartTransactionRequest.class)); @@ -59,7 +62,6 @@ public void nullFieldsOcpp16() { @Test public void nullFieldsOcpp16Security() { String input = mapper.writeValueAsString(new ocpp._2022._02.security.SecurityEventNotification()); - System.out.println(input); var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp._2022._02.security.SecurityEventNotification.class)); @@ -70,7 +72,6 @@ public void nullFieldsOcpp16Security() { @Test public void nullFieldsOcpp2() { String input = mapper.writeValueAsString(new ocpp._2020._03.SecurityEventNotificationRequest()); - System.out.println(input); var exception = assertThrows(ConstraintViolationException.class, () -> mapper.readValue(input, ocpp._2020._03.SecurityEventNotificationRequest.class)); From b810819d665906e229af7f7ce85731356a200fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20Go=CC=88kay?= Date: Wed, 28 Jan 2026 15:33:54 +0100 Subject: [PATCH 4/6] add bean validation for writing --- .../BeanSerializerModifierWithValidation.java | 30 +++++ .../BeanSerializerWithValidation.java | 30 +++++ .../jaxb/validation/BeanValidationModule.java | 27 ++++- ...va => BeanDeserializerValidationTest.java} | 4 +- .../jaxb/BeanSerializerValidationTest.java | 107 ++++++++++++++++++ 5 files changed, 192 insertions(+), 6 deletions(-) create mode 100644 ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanSerializerModifierWithValidation.java create mode 100644 ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanSerializerWithValidation.java rename ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/{BeanValidationTest.java => BeanDeserializerValidationTest.java} (97%) create mode 100644 ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanSerializerValidationTest.java diff --git a/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanSerializerModifierWithValidation.java b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanSerializerModifierWithValidation.java new file mode 100644 index 0000000..645dd3e --- /dev/null +++ b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanSerializerModifierWithValidation.java @@ -0,0 +1,30 @@ +package de.rwth.idsg.ocpp.jaxb.validation; + +import lombok.RequiredArgsConstructor; +import tools.jackson.databind.BeanDescription; +import tools.jackson.databind.SerializationConfig; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.deser.bean.BeanDeserializer; +import tools.jackson.databind.ser.BeanSerializer; +import tools.jackson.databind.ser.ValueSerializerModifier; +import tools.jackson.databind.ser.bean.BeanSerializerBase; + +import jakarta.validation.Validator; + +@RequiredArgsConstructor +public class BeanSerializerModifierWithValidation extends ValueSerializerModifier { + + private final Validator validator; + + + @Override + public ValueSerializer modifySerializer(SerializationConfig config, + BeanDescription.Supplier beanDesc, + ValueSerializer serializer) { + if (serializer instanceof BeanSerializerBase) { + return new BeanSerializerWithValidation((BeanSerializerBase) serializer, validator); + } + + return serializer; + } +} diff --git a/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanSerializerWithValidation.java b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanSerializerWithValidation.java new file mode 100644 index 0000000..86442f7 --- /dev/null +++ b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanSerializerWithValidation.java @@ -0,0 +1,30 @@ +package de.rwth.idsg.ocpp.jaxb.validation; + +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ser.BeanSerializer; +import tools.jackson.databind.ser.bean.BeanSerializerBase; + +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Validator; + +public class BeanSerializerWithValidation extends BeanSerializer { + + private final Validator validator; + + public BeanSerializerWithValidation(BeanSerializerBase src, Validator validator) { + super(src); + this.validator = validator; + } + + @Override + public void serialize(Object bean, JsonGenerator gen, SerializationContext provider) throws JacksonException { + var violations = validator.validate(bean); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + + super.serialize(bean, gen, provider); + } +} diff --git a/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanValidationModule.java b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanValidationModule.java index de99d35..f134a40 100644 --- a/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanValidationModule.java +++ b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanValidationModule.java @@ -10,11 +10,30 @@ */ public class BeanValidationModule extends SimpleModule { - public BeanValidationModule() { - this(Validation.buildDefaultValidatorFactory().getValidator()); + private BeanValidationModule(Validator validator, boolean forReading, boolean forWriting) { + Validator validatorToUse = validator == null + ? Validation.buildDefaultValidatorFactory().getValidator() + : validator; + + if (forReading) { + setDeserializerModifier(new BeanDeserializerModifierWithValidation(validatorToUse)); + } + + if (forWriting) { + setSerializerModifier(new BeanSerializerModifierWithValidation(validatorToUse)); + } + } + + public static BeanValidationModule forReading(Validator validator) { + return new BeanValidationModule(validator, true, false); } - public BeanValidationModule(Validator validator) { - setDeserializerModifier(new BeanDeserializerModifierWithValidation(validator)); + public static BeanValidationModule forWriting(Validator validator) { + return new BeanValidationModule(validator, false, true); } + + public static BeanValidationModule forReadingAndWriting(Validator validator) { + return new BeanValidationModule(validator, true, true); + } + } diff --git a/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanValidationTest.java b/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanDeserializerValidationTest.java similarity index 97% rename from ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanValidationTest.java rename to ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanDeserializerValidationTest.java index 3fb1e85..5655949 100644 --- a/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanValidationTest.java +++ b/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanDeserializerValidationTest.java @@ -17,7 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; -public class BeanValidationTest { +public class BeanDeserializerValidationTest { private static ObjectMapper mapper; @@ -25,7 +25,7 @@ public class BeanValidationTest { public static void setup() { mapper = JsonMapper.builder() .addModule(new JodaModule()) - .addModule(new BeanValidationModule()) + .addModule(BeanValidationModule.forReading(null)) .build(); } diff --git a/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanSerializerValidationTest.java b/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanSerializerValidationTest.java new file mode 100644 index 0000000..90df0d8 --- /dev/null +++ b/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanSerializerValidationTest.java @@ -0,0 +1,107 @@ +package de.rwth.idsg.ocpp.jaxb; + +import de.rwth.idsg.ocpp.jaxb.validation.BeanValidationModule; +import ocpp.cs._2015._10.StartTransactionRequest; +import org.joda.time.DateTime; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.datatype.joda.JodaModule; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; + +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class BeanSerializerValidationTest { + + private static ObjectMapper mapper; + + @BeforeAll + public static void setup() { + mapper = JsonMapper.builder() + .addModule(new JodaModule()) + .addModule(BeanValidationModule.forWriting(null)) + .build(); + } + + @Test + public void nullFieldsOcpp12() { + var exception = assertThrows(ConstraintViolationException.class, () -> mapper.writeValueAsString(new ocpp.cs._2010._08.StartTransactionRequest())); + + var violations = exception.getConstraintViolations(); + checkViolatingNullFields(Set.of("connectorId", "idTag", "timestamp", "meterStart"), violations); + } + + @Test + public void nullFieldsOcpp15() { + var exception = assertThrows(ConstraintViolationException.class, () -> mapper.writeValueAsString(new ocpp.cs._2012._06.StartTransactionRequest())); + + var violations = exception.getConstraintViolations(); + checkViolatingNullFields(Set.of("connectorId", "idTag", "timestamp", "meterStart"), violations); + } + + @Test + public void nullFieldsOcpp16() { + var exception = assertThrows(ConstraintViolationException.class, () -> mapper.writeValueAsString(new ocpp.cs._2015._10.StartTransactionRequest())); + + var violations = exception.getConstraintViolations(); + checkViolatingNullFields(Set.of("connectorId", "idTag", "timestamp", "meterStart"), violations); + } + + @Test + public void nullFieldsOcpp16Security() { + var exception = assertThrows(ConstraintViolationException.class, () -> mapper.writeValueAsString(new ocpp._2022._02.security.SecurityEventNotification())); + + var violations = exception.getConstraintViolations(); + checkViolatingNullFields(Set.of("type", "timestamp"), violations); + } + + @Test + public void nullFieldsOcpp2() { + var exception = assertThrows(ConstraintViolationException.class, () -> mapper.writeValueAsString(new ocpp._2020._03.SecurityEventNotificationRequest())); + + var violations = exception.getConstraintViolations(); + checkViolatingNullFields(Set.of("type", "timestamp"), violations); + } + + @Test + public void startTransactionIdTagTooLong() { + StartTransactionRequest request = new StartTransactionRequest() + .withConnectorId(1) + .withMeterStart(0) + .withTimestamp(DateTime.now()) + .withIdTag("ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC"); + + var exception = assertThrows(ConstraintViolationException.class, () -> mapper.writeValueAsString(request)); + + var violations = exception.getConstraintViolations(); + Assertions.assertEquals(1, violations.size()); + + ConstraintViolation violation = violations.iterator().next(); + + Assertions.assertEquals("idTag", violation.getPropertyPath().toString()); + Assertions.assertEquals("size must be between 0 and 20", violation.getMessage()); + } + + private static void checkViolatingNullFields(Set expected, Set> violations) { + Assertions.assertEquals(expected.size(), violations.size()); + + var violatingFields = violations.stream() + .map(it -> it.getPropertyPath().toString()) + .collect(Collectors.toSet()); + + Assertions.assertEquals(expected, violatingFields); + + var violationReason = violations.stream() + .map(ConstraintViolation::getMessage) + .collect(Collectors.toSet()); + + Assertions.assertEquals(violationReason, Set.of("must not be null")); + } +} From 53adf6c90c1602bfd2f694229c0a736c58f905a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20Go=CC=88kay?= Date: Wed, 28 Jan 2026 16:43:45 +0100 Subject: [PATCH 5/6] refactor --- .../BeanSerializerModifierWithValidation.java | 3 --- .../BeanSerializerWithValidation.java | 17 ++++++++--------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanSerializerModifierWithValidation.java b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanSerializerModifierWithValidation.java index 645dd3e..4d4b18a 100644 --- a/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanSerializerModifierWithValidation.java +++ b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanSerializerModifierWithValidation.java @@ -4,8 +4,6 @@ import tools.jackson.databind.BeanDescription; import tools.jackson.databind.SerializationConfig; import tools.jackson.databind.ValueSerializer; -import tools.jackson.databind.deser.bean.BeanDeserializer; -import tools.jackson.databind.ser.BeanSerializer; import tools.jackson.databind.ser.ValueSerializerModifier; import tools.jackson.databind.ser.bean.BeanSerializerBase; @@ -16,7 +14,6 @@ public class BeanSerializerModifierWithValidation extends ValueSerializerModifie private final Validator validator; - @Override public ValueSerializer modifySerializer(SerializationConfig config, BeanDescription.Supplier beanDesc, diff --git a/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanSerializerWithValidation.java b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanSerializerWithValidation.java index 86442f7..bbeeee5 100644 --- a/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanSerializerWithValidation.java +++ b/ocpp-jaxb/src/main/java/de/rwth/idsg/ocpp/jaxb/validation/BeanSerializerWithValidation.java @@ -1,30 +1,29 @@ package de.rwth.idsg.ocpp.jaxb.validation; +import lombok.RequiredArgsConstructor; import tools.jackson.core.JacksonException; import tools.jackson.core.JsonGenerator; import tools.jackson.databind.SerializationContext; -import tools.jackson.databind.ser.BeanSerializer; +import tools.jackson.databind.ValueSerializer; import tools.jackson.databind.ser.bean.BeanSerializerBase; import jakarta.validation.ConstraintViolationException; import jakarta.validation.Validator; -public class BeanSerializerWithValidation extends BeanSerializer { +@RequiredArgsConstructor +public class BeanSerializerWithValidation extends ValueSerializer { + private final BeanSerializerBase delegate; private final Validator validator; - public BeanSerializerWithValidation(BeanSerializerBase src, Validator validator) { - super(src); - this.validator = validator; - } - @Override - public void serialize(Object bean, JsonGenerator gen, SerializationContext provider) throws JacksonException { + public void serialize(Object bean, JsonGenerator gen, SerializationContext ctxt) throws JacksonException { var violations = validator.validate(bean); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); } - super.serialize(bean, gen, provider); + delegate.serialize(bean, gen, ctxt); } + } From e158b3cf16bf333acd36e5d6f37b9a09fb2b850c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sevket=20Go=CC=88kay?= Date: Wed, 28 Jan 2026 17:09:21 +0100 Subject: [PATCH 6/6] add tests --- .../jaxb/BeanDeserializerValidationTest.java | 36 +++++++++++++++++++ .../jaxb/BeanSerializerValidationTest.java | 29 ++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanDeserializerValidationTest.java b/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanDeserializerValidationTest.java index 5655949..818f0fa 100644 --- a/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanDeserializerValidationTest.java +++ b/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanDeserializerValidationTest.java @@ -1,11 +1,16 @@ package de.rwth.idsg.ocpp.jaxb; import de.rwth.idsg.ocpp.jaxb.validation.BeanValidationModule; +import ocpp._2020._03.CustomData; +import ocpp._2020._03.SecurityEventNotificationRequest; +import ocpp.cs._2015._10.AuthorizeResponse; +import ocpp.cs._2015._10.IdTagInfo; import ocpp.cs._2015._10.StartTransactionRequest; import org.joda.time.DateTime; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import tools.jackson.databind.DatabindException; import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper; import tools.jackson.datatype.joda.JodaModule; @@ -99,7 +104,38 @@ public void startTransactionIdTagTooLong() { Assertions.assertEquals("idTag", violation.getPropertyPath().toString()); Assertions.assertEquals("size must be between 0 and 20", violation.getMessage()); + } + + @Test + public void embeddedCustomDataEmpty() { + var req = new SecurityEventNotificationRequest() + .withType("type") + .withTimestamp(DateTime.now()) + .withCustomData(new CustomData()); + + String input = mapper.writeValueAsString(req); + + var exception = assertThrows(DatabindException.class, () -> mapper.readValue(input, SecurityEventNotificationRequest.class)); + + Throwable cause = exception.getCause(); + Assertions.assertInstanceOf(ConstraintViolationException.class, cause); + + Assertions.assertEquals("vendorId: must not be null", cause.getMessage()); + } + + @Test + public void embeddedIdTagInfoEmpty() { + var req = new AuthorizeResponse() + .withIdTagInfo(new IdTagInfo()); + + String input = mapper.writeValueAsString(req); + + var exception = assertThrows(DatabindException.class, () -> mapper.readValue(input, AuthorizeResponse.class)); + + Throwable cause = exception.getCause(); + Assertions.assertInstanceOf(ConstraintViolationException.class, cause); + Assertions.assertEquals("status: must not be null", cause.getMessage()); } private static void checkViolatingNullFields(Set expected, Set> violations) { diff --git a/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanSerializerValidationTest.java b/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanSerializerValidationTest.java index 90df0d8..447772a 100644 --- a/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanSerializerValidationTest.java +++ b/ocpp-jaxb/src/test/java/de/rwth/idsg/ocpp/jaxb/BeanSerializerValidationTest.java @@ -1,6 +1,10 @@ package de.rwth.idsg.ocpp.jaxb; import de.rwth.idsg.ocpp.jaxb.validation.BeanValidationModule; +import ocpp._2020._03.CustomData; +import ocpp._2020._03.SecurityEventNotificationRequest; +import ocpp.cs._2015._10.AuthorizeResponse; +import ocpp.cs._2015._10.IdTagInfo; import ocpp.cs._2015._10.StartTransactionRequest; import org.joda.time.DateTime; import org.junit.jupiter.api.Assertions; @@ -12,7 +16,6 @@ import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; - import java.util.Set; import java.util.stream.Collectors; @@ -89,6 +92,30 @@ public void startTransactionIdTagTooLong() { Assertions.assertEquals("size must be between 0 and 20", violation.getMessage()); } + @Test + public void embeddedCustomDataEmpty() { + var req = new SecurityEventNotificationRequest() + .withType("type") + .withTimestamp(DateTime.now()) + .withCustomData(new CustomData()); + + var exception = assertThrows(ConstraintViolationException.class, () -> mapper.writeValueAsString(req)); + + var violations = exception.getConstraintViolations(); + checkViolatingNullFields(Set.of("customData.vendorId"), violations); + } + + @Test + public void embeddedIdTagInfoEmpty() { + var req = new AuthorizeResponse() + .withIdTagInfo(new IdTagInfo()); + + var exception = assertThrows(ConstraintViolationException.class, () -> mapper.writeValueAsString(req)); + + var violations = exception.getConstraintViolations(); + checkViolatingNullFields(Set.of("idTagInfo.status"), violations); + } + private static void checkViolatingNullFields(Set expected, Set> violations) { Assertions.assertEquals(expected.size(), violations.size());