Compare commits
4 Commits
issue/4429
...
issue/4454
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9439e7feef | ||
|
|
f80e2e7f1d | ||
|
|
d1ed973fa0 | ||
|
|
24e1ae0a2b |
2
pom.xml
2
pom.xml
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>4.2.x-4429-SNAPSHOT</version>
|
||||
<version>4.2.x-4454-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Spring Data MongoDB</name>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>4.2.x-4429-SNAPSHOT</version>
|
||||
<version>4.2.x-4454-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>4.2.x-4429-SNAPSHOT</version>
|
||||
<version>4.2.x-4454-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>4.2.x-4429-SNAPSHOT</version>
|
||||
<version>4.2.x-4454-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -203,8 +203,9 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
target.properties(nestedProperties.toArray(new JsonSchemaProperty[0])), required));
|
||||
}
|
||||
}
|
||||
return targetProperties.size() == 1 ? targetProperties.iterator().next()
|
||||
JsonSchemaProperty schemaProperty = targetProperties.size() == 1 ? targetProperties.iterator().next()
|
||||
: JsonSchemaProperty.merged(targetProperties);
|
||||
return applyEncryptionDataIfNecessary(property, schemaProperty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,12 +18,8 @@ package org.springframework.data.mongodb.core;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.bson.Document;
|
||||
@@ -191,19 +187,16 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
return new SessionScoped() {
|
||||
|
||||
private final Lock lock = new ReentrantLock();
|
||||
private @Nullable ClientSession session;
|
||||
private final Object lock = new Object();
|
||||
private @Nullable ClientSession session = null;
|
||||
|
||||
@Override
|
||||
public <T> T execute(SessionCallback<T> action, Consumer<ClientSession> onComplete) {
|
||||
|
||||
lock.lock();
|
||||
try {
|
||||
synchronized (lock) {
|
||||
if (session == null) {
|
||||
session = sessionProvider.get();
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -22,9 +22,6 @@ import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
@@ -137,8 +134,7 @@ public final class LazyLoadingProxyFactory {
|
||||
}
|
||||
|
||||
return prepareProxyFactory(propertyType,
|
||||
() -> new LazyLoadingInterceptor(property, callback, source, exceptionTranslator))
|
||||
.getProxy(LazyLoadingProxy.class.getClassLoader());
|
||||
() -> new LazyLoadingInterceptor(property, callback, source, exceptionTranslator)).getProxy(LazyLoadingProxy.class.getClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,8 +171,6 @@ public final class LazyLoadingProxyFactory {
|
||||
}
|
||||
}
|
||||
|
||||
private final ReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
|
||||
private final MongoPersistentProperty property;
|
||||
private final DbRefResolverCallback callback;
|
||||
private final Object source;
|
||||
@@ -345,29 +339,25 @@ public final class LazyLoadingProxyFactory {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object resolve() {
|
||||
private synchronized Object resolve() {
|
||||
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
if (resolved) {
|
||||
if (resolved) {
|
||||
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace(String.format("Accessing already resolved lazy loading property %s.%s",
|
||||
property.getOwner() != null ? property.getOwner().getName() : "unknown", property.getName()));
|
||||
}
|
||||
return result;
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace(String.format("Accessing already resolved lazy loading property %s.%s",
|
||||
property.getOwner() != null ? property.getOwner().getName() : "unknown", property.getName()));
|
||||
}
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace(String.format("Resolving lazy loading property %s.%s",
|
||||
property.getOwner() != null ? property.getOwner().getName() : "unknown", property.getName()));
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
return executeWhileLocked(lock.writeLock(), () -> callback.resolve(property));
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace(String.format("Resolving lazy loading property %s.%s",
|
||||
property.getOwner() != null ? property.getOwner().getName() : "unknown", property.getName()));
|
||||
}
|
||||
|
||||
return callback.resolve(property);
|
||||
|
||||
} catch (RuntimeException ex) {
|
||||
|
||||
DataAccessException translatedException = exceptionTranslator.translateExceptionIfPossible(ex);
|
||||
@@ -380,16 +370,6 @@ public final class LazyLoadingProxyFactory {
|
||||
translatedException != null ? translatedException : ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T executeWhileLocked(Lock lock, Supplier<T> stuff) {
|
||||
|
||||
lock.lock();
|
||||
try {
|
||||
return stuff.get();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@ package org.springframework.data.mongodb.core.messaging;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
@@ -41,7 +39,7 @@ import com.mongodb.client.MongoCursor;
|
||||
*/
|
||||
abstract class CursorReadingTask<T, R> implements Task {
|
||||
|
||||
private final Lock lock = new ReentrantLock();
|
||||
private final Object lifecycleMonitor = new Object();
|
||||
|
||||
private final MongoTemplate template;
|
||||
private final SubscriptionRequest<T, R, RequestOptions> request;
|
||||
@@ -88,14 +86,19 @@ abstract class CursorReadingTask<T, R> implements Task {
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
|
||||
doWhileLocked(lock, () -> state = State.CANCELLED);
|
||||
synchronized (lifecycleMonitor) {
|
||||
state = State.CANCELLED;
|
||||
}
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
|
||||
doWhileLocked(lock, () -> state = State.CANCELLED);
|
||||
synchronized (lifecycleMonitor) {
|
||||
state = State.CANCELLED;
|
||||
}
|
||||
|
||||
errorHandler.handleError(e);
|
||||
}
|
||||
}
|
||||
@@ -111,32 +114,30 @@ abstract class CursorReadingTask<T, R> implements Task {
|
||||
*/
|
||||
private void start() {
|
||||
|
||||
doWhileLocked(lock, () -> {
|
||||
synchronized (lifecycleMonitor) {
|
||||
if (!State.RUNNING.equals(state)) {
|
||||
state = State.STARTING;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
// boolean valid = false;
|
||||
boolean valid = false;
|
||||
|
||||
boolean valid = executeWhileLocked(lock, () -> {
|
||||
synchronized (lifecycleMonitor) {
|
||||
|
||||
if (!State.STARTING.equals(state)) {
|
||||
return false;
|
||||
if (State.STARTING.equals(state)) {
|
||||
|
||||
MongoCursor<T> cursor = execute(() -> initCursor(template, request.getRequestOptions(), targetType));
|
||||
valid = isValidCursor(cursor);
|
||||
if (valid) {
|
||||
this.cursor = cursor;
|
||||
state = State.RUNNING;
|
||||
} else if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
MongoCursor<T> cursor = execute(() -> initCursor(template, request.getRequestOptions(), targetType));
|
||||
boolean isValid = isValidCursor(cursor);
|
||||
if (isValid) {
|
||||
this.cursor = cursor;
|
||||
state = State.RUNNING;
|
||||
} else if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
return isValid;
|
||||
});
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
|
||||
@@ -144,7 +145,9 @@ abstract class CursorReadingTask<T, R> implements Task {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
|
||||
doWhileLocked(lock, () -> state = State.CANCELLED);
|
||||
synchronized (lifecycleMonitor) {
|
||||
state = State.CANCELLED;
|
||||
}
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
@@ -160,7 +163,7 @@ abstract class CursorReadingTask<T, R> implements Task {
|
||||
@Override
|
||||
public void cancel() throws DataAccessResourceFailureException {
|
||||
|
||||
doWhileLocked(lock, () -> {
|
||||
synchronized (lifecycleMonitor) {
|
||||
|
||||
if (State.RUNNING.equals(state) || State.STARTING.equals(state)) {
|
||||
this.state = State.CANCELLED;
|
||||
@@ -168,7 +171,7 @@ abstract class CursorReadingTask<T, R> implements Task {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -178,7 +181,10 @@ abstract class CursorReadingTask<T, R> implements Task {
|
||||
|
||||
@Override
|
||||
public State getState() {
|
||||
return executeWhileLocked(lock, () -> state);
|
||||
|
||||
synchronized (lifecycleMonitor) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -214,12 +220,13 @@ abstract class CursorReadingTask<T, R> implements Task {
|
||||
@Nullable
|
||||
private T getNext() {
|
||||
|
||||
return executeWhileLocked(lock, () -> {
|
||||
synchronized (lifecycleMonitor) {
|
||||
if (State.RUNNING.equals(state)) {
|
||||
return cursor.tryNext();
|
||||
}
|
||||
throw new IllegalStateException(String.format("Cursor %s is not longer open", cursor));
|
||||
});
|
||||
}
|
||||
|
||||
throw new IllegalStateException(String.format("Cursor %s is not longer open", cursor));
|
||||
}
|
||||
|
||||
private static boolean isValidCursor(@Nullable MongoCursor<?> cursor) {
|
||||
@@ -256,23 +263,4 @@ abstract class CursorReadingTask<T, R> implements Task {
|
||||
throw translated != null ? translated : e;
|
||||
}
|
||||
}
|
||||
|
||||
private static void doWhileLocked(Lock lock, Runnable action) {
|
||||
|
||||
executeWhileLocked(lock, () -> {
|
||||
action.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static <T> T executeWhileLocked(Lock lock, Supplier<T> stuff) {
|
||||
|
||||
lock.lock();
|
||||
try {
|
||||
return stuff.get();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,6 @@ import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -39,7 +35,8 @@ import org.springframework.util.ObjectUtils;
|
||||
/**
|
||||
* Simple {@link Executor} based {@link MessageListenerContainer} implementation for running {@link Task tasks} like
|
||||
* listening to MongoDB <a href="https://docs.mongodb.com/manual/changeStreams/">Change Streams</a> and tailable
|
||||
* cursors. <br />
|
||||
* cursors.
|
||||
* <br />
|
||||
* This message container creates long-running tasks that are executed on {@link Executor}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
@@ -52,11 +49,9 @@ public class DefaultMessageListenerContainer implements MessageListenerContainer
|
||||
private final TaskFactory taskFactory;
|
||||
private final Optional<ErrorHandler> errorHandler;
|
||||
|
||||
private final Object lifecycleMonitor = new Object();
|
||||
private final Map<SubscriptionRequest, Subscription> subscriptions = new LinkedHashMap<>();
|
||||
|
||||
ReadWriteLock lifecycleMonitor = new ReentrantReadWriteLock();
|
||||
ReadWriteLock subscriptionMonitor = new ReentrantReadWriteLock();
|
||||
|
||||
private boolean running = false;
|
||||
|
||||
/**
|
||||
@@ -114,34 +109,43 @@ public class DefaultMessageListenerContainer implements MessageListenerContainer
|
||||
@Override
|
||||
public void start() {
|
||||
|
||||
doWhileLocked(lifecycleMonitor.writeLock(), () -> {
|
||||
if (!this.running) {
|
||||
subscriptions.values().stream() //
|
||||
.filter(it -> !it.isActive()) //
|
||||
.filter(TaskSubscription.class::isInstance) //
|
||||
.map(TaskSubscription.class::cast) //
|
||||
.map(TaskSubscription::getTask) //
|
||||
.forEach(taskExecutor::execute);
|
||||
synchronized (lifecycleMonitor) {
|
||||
|
||||
running = true;
|
||||
if (this.running) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
subscriptions.values().stream() //
|
||||
.filter(it -> !it.isActive()) //
|
||||
.filter(TaskSubscription.class::isInstance) //
|
||||
.map(TaskSubscription.class::cast) //
|
||||
.map(TaskSubscription::getTask) //
|
||||
.forEach(taskExecutor::execute);
|
||||
|
||||
running = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
|
||||
doWhileLocked(lifecycleMonitor.writeLock(), () -> {
|
||||
synchronized (lifecycleMonitor) {
|
||||
|
||||
if (this.running) {
|
||||
|
||||
subscriptions.values().forEach(Cancelable::cancel);
|
||||
|
||||
running = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return executeWhileLocked(lifecycleMonitor.readLock(), () -> running);
|
||||
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
return running;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -166,32 +170,36 @@ public class DefaultMessageListenerContainer implements MessageListenerContainer
|
||||
|
||||
@Override
|
||||
public Optional<Subscription> lookup(SubscriptionRequest<?, ?, ?> request) {
|
||||
return executeWhileLocked(subscriptionMonitor.readLock(), () -> Optional.ofNullable(subscriptions.get(request)));
|
||||
|
||||
synchronized (lifecycleMonitor) {
|
||||
return Optional.ofNullable(subscriptions.get(request));
|
||||
}
|
||||
}
|
||||
|
||||
public Subscription register(SubscriptionRequest request, Task task) {
|
||||
|
||||
return executeWhileLocked(this.subscriptionMonitor.writeLock(), () ->
|
||||
{
|
||||
Subscription subscription = new TaskSubscription(task);
|
||||
|
||||
synchronized (lifecycleMonitor) {
|
||||
|
||||
if (subscriptions.containsKey(request)) {
|
||||
return subscriptions.get(request);
|
||||
}
|
||||
|
||||
Subscription subscription = new TaskSubscription(task);
|
||||
this.subscriptions.put(request, subscription);
|
||||
|
||||
if (this.isRunning()) {
|
||||
if (this.running) {
|
||||
taskExecutor.execute(task);
|
||||
}
|
||||
return subscription;
|
||||
});
|
||||
}
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Subscription subscription) {
|
||||
|
||||
doWhileLocked(this.subscriptionMonitor.writeLock(), () -> {
|
||||
synchronized (lifecycleMonitor) {
|
||||
|
||||
if (subscriptions.containsValue(subscription)) {
|
||||
|
||||
@@ -201,25 +209,6 @@ public class DefaultMessageListenerContainer implements MessageListenerContainer
|
||||
|
||||
subscriptions.values().remove(subscription);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void doWhileLocked(Lock lock, Runnable action) {
|
||||
|
||||
executeWhileLocked(lock, () -> {
|
||||
action.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static <T> T executeWhileLocked(Lock lock, Supplier<T> stuff) {
|
||||
|
||||
lock.lock();
|
||||
try {
|
||||
return stuff.get();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.Timest
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link JsonSchemaProperty} implementation.
|
||||
@@ -1139,7 +1140,9 @@ public class IdentifiableJsonSchemaProperty<T extends JsonSchemaObject> implemen
|
||||
enc.append("bsonType", type.toBsonType().value()); // TODO: no samples with type -> is it bson type all the way?
|
||||
}
|
||||
|
||||
enc.append("algorithm", algorithm);
|
||||
if (StringUtils.hasText(algorithm)) {
|
||||
enc.append("algorithm", algorithm);
|
||||
}
|
||||
|
||||
propertySpecification.append("encrypt", enc);
|
||||
|
||||
|
||||
@@ -271,6 +271,17 @@ class MappingMongoJsonSchemaCreatorUnitTests {
|
||||
.containsEntry("properties.value", new Document("type", "string"));
|
||||
}
|
||||
|
||||
@Test // GH-4454
|
||||
void wrapEncryptedEntityTypeLikeProperty() {
|
||||
|
||||
MongoJsonSchema schema = MongoJsonSchemaCreator.create() //
|
||||
.filter(MongoJsonSchemaCreator.encryptedOnly()) // filter non encrypted fields
|
||||
.createSchemaFor(WithEncryptedEntityLikeProperty.class);
|
||||
|
||||
assertThat(schema.schemaDocument()) //
|
||||
.containsEntry("properties.domainTypeValue", Document.parse("{'encrypt': {'bsonType': 'object' } }"));
|
||||
}
|
||||
|
||||
// --> TYPES AND JSON
|
||||
|
||||
// --> ENUM
|
||||
@@ -676,4 +687,9 @@ class MappingMongoJsonSchemaCreatorUnitTests {
|
||||
static class PropertyClashWithA {
|
||||
Integer aNonEncrypted;
|
||||
}
|
||||
|
||||
@Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
|
||||
static class WithEncryptedEntityLikeProperty {
|
||||
@Encrypted SomeDomainType domainTypeValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,10 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
|
||||
import org.springframework.data.mongodb.core.CollectionOptions.ValidationOptions;
|
||||
import org.springframework.data.mongodb.core.mapping.Encrypted;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
|
||||
import org.springframework.data.mongodb.test.util.Client;
|
||||
import org.springframework.data.mongodb.test.util.MongoClientExtension;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -46,11 +48,13 @@ import com.mongodb.client.model.ValidationLevel;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link CollectionOptions#validation(ValidationOptions)} using
|
||||
* {@link org.springframework.data.mongodb.core.validation.CriteriaValidator} and
|
||||
* {@link org.springframework.data.mongodb.core.validation.DocumentValidator}.
|
||||
* {@link org.springframework.data.mongodb.core.validation.CriteriaValidator},
|
||||
* {@link org.springframework.data.mongodb.core.validation.DocumentValidator} and
|
||||
* {@link org.springframework.data.mongodb.core.validation.JsonSchemaValidator}.
|
||||
*
|
||||
* @author Andreas Zink
|
||||
* @author Christoph Strobl
|
||||
* @author Julia Lee
|
||||
*/
|
||||
@ExtendWith({ MongoClientExtension.class, SpringExtension.class })
|
||||
public class MongoTemplateValidationTests {
|
||||
@@ -186,6 +190,20 @@ public class MongoTemplateValidationTests {
|
||||
assertThat(getValidatorInfo(COLLECTION_NAME)).isEqualTo(new Document("customName", new Document("$type", "bool")));
|
||||
}
|
||||
|
||||
@Test // GH-4454
|
||||
public void failsJsonSchemaValidationForEncryptedDomainEntityProperty() {
|
||||
|
||||
MongoJsonSchema schema = MongoJsonSchemaCreator.create().createSchemaFor(BeanWithEncryptedDomainEntity.class);
|
||||
template.createCollection(COLLECTION_NAME, CollectionOptions.empty().schema(schema));
|
||||
|
||||
BeanWithEncryptedDomainEntity person = new BeanWithEncryptedDomainEntity();
|
||||
person.encryptedDomainEntity = new SimpleBean("some string", 100, null);
|
||||
|
||||
assertThatExceptionOfType(DataIntegrityViolationException.class)
|
||||
.isThrownBy(() -> template.save(person))
|
||||
.withMessageContaining("Document failed validation");
|
||||
}
|
||||
|
||||
private Document getCollectionOptions(String collectionName) {
|
||||
return getCollectionInfo(collectionName).get("options", Document.class);
|
||||
}
|
||||
@@ -271,4 +289,10 @@ public class MongoTemplateValidationTests {
|
||||
return "MongoTemplateValidationTests.SimpleBean(nonNullString=" + this.getNonNullString() + ", rangedInteger=" + this.getRangedInteger() + ", customFieldName=" + this.getCustomFieldName() + ")";
|
||||
}
|
||||
}
|
||||
|
||||
@org.springframework.data.mongodb.core.mapping.Document(collection = COLLECTION_NAME)
|
||||
@Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
|
||||
static class BeanWithEncryptedDomainEntity {
|
||||
@Encrypted SimpleBean encryptedDomainEntity;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user