Browse Source

Introduce `ValidatingEntityCallback`.

Closes #4901
Original pull request: #4910

Signed-off-by: felgentraeger <rene.felgentraeger@gvl.de>
pull/4915/head
felgentraeger 10 months ago committed by Mark Paluch
parent
commit
54c7d8f8fe
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 77
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/event/ValidatingEntityCallback.java
  2. 2
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListener.java
  3. 75
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ValidatingEntityCallbackUnitTests.java

77
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/event/ValidatingEntityCallback.java

@ -0,0 +1,77 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.mapping.event;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Validator;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bson.Document;
import org.springframework.core.Ordered;
import org.springframework.util.Assert;
/**
* JSR-303 dependant entities validator.
* <p>
* When it is registered as Spring component its automatically invoked after any {@link AbstractMongoEventListener} and
* before entities are saved in database.
*
* @author original authors of {@link ValidatingMongoEventListener}
* @author Rene Felgenträger
* @see {@link ValidatingMongoEventListener}
*/
public class ValidatingEntityCallback implements BeforeSaveCallback<Object>, Ordered {
private static final Log LOG = LogFactory.getLog(ValidatingEntityCallback.class);
// TODO: create a validation handler (similar to "AuditingHandler") an reference it from "ValidatingMongoEventListener" and "ValidatingMongoEventListener"
private final Validator validator;
/**
* Creates a new {@link ValidatingEntityCallback} using the given {@link Validator}.
*
* @param validator must not be {@literal null}.
*/
public ValidatingEntityCallback(Validator validator) {
Assert.notNull(validator, "Validator must not be null");
this.validator = validator;
}
// TODO: alternatively implement the "BeforeConvertCallback" interface and set the order to highest value ?
@Override
public Object onBeforeSave(Object entity, Document document, String collection) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Validating object: %s", entity));
}
Set<ConstraintViolation<Object>> violations = validator.validate(entity);
if (!violations.isEmpty()) {
if (LOG.isDebugEnabled()) {
LOG.info(String.format("During object: %s validation violations found: %s", entity, violations));
}
throw new ConstraintViolationException(violations);
}
return entity;
}
@Override
public int getOrder() {
return 100;
}
}

2
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListener.java

@ -32,6 +32,8 @@ import org.springframework.util.Assert;
* @author Maciej Walkowiak * @author Maciej Walkowiak
* @author Oliver Gierke * @author Oliver Gierke
* @author Christoph Strobl * @author Christoph Strobl
*
* @see {@link ValidatingEntityCallback}
*/ */
public class ValidatingMongoEventListener extends AbstractMongoEventListener<Object> { public class ValidatingMongoEventListener extends AbstractMongoEventListener<Object> {

75
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ValidatingEntityCallbackUnitTests.java

@ -0,0 +1,75 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.mapping.event;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Validation;
import jakarta.validation.ValidatorFactory;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import org.bson.Document;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* Unit test for {@link ValidatingEntityCallback}.
*
* @author Rene Felgenträger
*/
class ValidatingEntityCallbackUnitTests {
private ValidatingEntityCallback callback;
@BeforeEach
public void setUp() {
try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {
callback = new ValidatingEntityCallback(factory.getValidator());
}
}
@Test
// GH-4910
void invalidModel_throwsException() {
Coordinates coordinates = new Coordinates(-1, -1);
assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(
() -> callback.onBeforeSave(coordinates, coordinates.toDocument(), "coordinates"))
.satisfies(e -> assertThat(e.getConstraintViolations()).hasSize(2));
}
@Test
// GH-4910
void validModel_noExceptionThrown() {
Coordinates coordinates = new Coordinates(0, 0);
Object entity = callback.onBeforeSave(coordinates, coordinates.toDocument(), "coordinates");
assertThat(entity).isEqualTo(coordinates);
}
record Coordinates(@NotNull @Min(0) Integer x, @NotNull @Min(0) Integer y) {
Document toDocument() {
return Document.parse("""
{
"x": %d,
"y": %d
}
""".formatted(x, y));
}
}
}
Loading…
Cancel
Save