Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion ocpp-jaxb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,16 @@
<bindingFile>${basedir}/src/main/resources/wsdl-binding/add_interface.xml</bindingFile>
</bindingFiles>
<extraargs>
<extraarg>-verbose</extraarg>
<extraarg>-frontend</extraarg>
<extraarg>krasa</extraarg>
<extraarg>-xjc-XReplacePrimitives</extraarg>
<extraarg>-xjc-XJsr303Annotations</extraarg>
<extraarg>-xjc-XJsr303Annotations:validationAnnotations=jakarta</extraarg>

<extraarg>-xjc-Xfluent-api</extraarg>
<extraarg>-xjc-Xinheritance</extraarg>
<extraarg>-xjc-Xannotate</extraarg>
<!--<extraarg>-verbose</extraarg>-->
<extraarg>-exsh</extraarg>
<extraarg>true</extraarg>
</extraargs>
Expand Down Expand Up @@ -66,6 +72,12 @@
<artifactId>jaxb-plugin-annotate</artifactId>
<version>4.0.8</version>
</dependency>
<!-- provides validation -->
<dependency>
<groupId>com.fillumina</groupId>
<artifactId>krasa-jaxb-tools</artifactId>
<version>2.5.1</version>
</dependency>
</dependencies>
</plugin>
<plugin>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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, validator);
}

return deserializer;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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.Validator;

/**
* https://www.baeldung.com/java-object-validation-deserialization
*/
public class BeanDeserializerWithValidation extends BeanDeserializer {

private final Validator validator;

public BeanDeserializerWithValidation(BeanDeserializer src, Validator validator) {
super(src);
this.validator = validator;
}

@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
var instance = super.deserialize(p, ctxt);

var violations = validator.validate(instance);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}

return instance;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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.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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +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.ValueSerializer;
import tools.jackson.databind.ser.bean.BeanSerializerBase;

import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Validator;

@RequiredArgsConstructor
public class BeanSerializerWithValidation extends ValueSerializer<Object> {

private final BeanSerializerBase delegate;
private final Validator validator;

@Override
public void serialize(Object bean, JsonGenerator gen, SerializationContext ctxt) throws JacksonException {
var violations = validator.validate(bean);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}

delegate.serialize(bean, gen, ctxt);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package de.rwth.idsg.ocpp.jaxb.validation;

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 {

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 static BeanValidationModule forWriting(Validator validator) {
return new BeanValidationModule(validator, false, true);
}

public static BeanValidationModule forReadingAndWriting(Validator validator) {
return new BeanValidationModule(validator, true, true);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
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;

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 BeanDeserializerValidationTest {

private static ObjectMapper mapper;

@BeforeAll
public static void setup() {
mapper = JsonMapper.builder()
.addModule(new JodaModule())
.addModule(BeanValidationModule.forReading(null))
.build();
}

@Test
public void nullFieldsOcpp12() {
String input = mapper.writeValueAsString(new ocpp.cs._2010._08.StartTransactionRequest());

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());

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());

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());

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());

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());
}

@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<String> expected, Set<ConstraintViolation<?>> 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"));
}
}
Loading