GH-2936: Add custom converters for MongoDbMS

Fixes https://github.com/spring-projects/spring-integration/issues/2936

* Add `MongoDbMessageStore.setCustomConverters()` to allow to inject
any custom converters for `payload` as well as header values

**Cherry-pick to 5.1.x**
This commit is contained in:
Artem Bilan
2019-05-29 17:46:07 -04:00
committed by Gary Russell
parent f992c05736
commit ef856b2187
3 changed files with 96 additions and 14 deletions

View File

@@ -17,7 +17,9 @@
package org.springframework.integration.mongodb.store;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -140,13 +142,12 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore
private final String collectionName;
private volatile ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
private ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
private ApplicationContext applicationContext;
private String[] whiteListPatterns;
/**
* Create a MongoDbMessageStore using the provided {@link MongoDbFactory}.and the default collection name.
* @param mongoDbFactory The mongodb factory.
@@ -186,7 +187,20 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore
* @param patterns the patterns.
*/
public void addWhiteListPatterns(String... patterns) {
this.whiteListPatterns = patterns;
this.whiteListPatterns = patterns != null ? Arrays.copyOf(patterns, patterns.length) : null;
}
/**
* Configure a set of converters to use in the {@link MappingMongoConverter}.
* Must be instances of {@code org.springframework.core.convert.converter.Converter},
* {@code org.springframework.core.convert.converter.ConverterFactory},
* {@code org.springframework.core.convert.converter.GenericConverter} or
* {@code org.springframework.data.convert.ConverterBuilder.ConverterAware}.
* @param customConverters the converters to use.
* @since 5.1.6
*/
public void setCustomConverters(Object... customConverters) {
this.converter.setCustomConverters(customConverters);
}
@Override
@@ -194,6 +208,7 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore
if (this.applicationContext != null) {
this.converter.setApplicationContext(this.applicationContext);
}
this.converter.afterPropertiesSet();
IndexOperations indexOperations = this.template.indexOps(this.collectionName);
@@ -512,26 +527,38 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore
*/
private final class MessageReadingMongoConverter extends MappingMongoConverter {
private Object[] customConverters;
MessageReadingMongoConverter(MongoDbFactory mongoDbFactory,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
super(new DefaultDbRefResolver(mongoDbFactory), mappingContext);
}
void setCustomConverters(Object... customConverters) {
this.customConverters =
customConverters != null ? Arrays.copyOf(customConverters, customConverters.length) : null;
}
@Override
public void afterPropertiesSet() {
List<Object> customConverters = new ArrayList<>();
customConverters.add(new MessageHistoryToDocumentConverter());
customConverters.add(new DocumentToGenericMessageConverter());
customConverters.add(new DocumentToMutableMessageConverter());
List<Object> converters = new ArrayList<>();
converters.add(new MessageHistoryToDocumentConverter());
converters.add(new DocumentToGenericMessageConverter());
converters.add(new DocumentToMutableMessageConverter());
DocumentToErrorMessageConverter docToErrorMessageConverter = new DocumentToErrorMessageConverter();
if (MongoDbMessageStore.this.whiteListPatterns != null) {
docToErrorMessageConverter.deserializingConverter
.addWhiteListPatterns(MongoDbMessageStore.this.whiteListPatterns);
}
customConverters.add(docToErrorMessageConverter);
customConverters.add(new DocumentToAdviceMessageConverter());
customConverters.add(new ThrowableToBytesConverter());
this.setCustomConversions(new MongoCustomConversions(customConverters));
converters.add(docToErrorMessageConverter);
converters.add(new DocumentToAdviceMessageConverter());
converters.add(new ThrowableToBytesConverter());
if (this.customConverters != null) {
Collections.addAll(converters, this.customConverters);
}
setCustomConversions(new MongoCustomConversions(converters));
super.afterPropertiesSet();
}

View File

@@ -16,8 +16,18 @@
package org.springframework.integration.mongodb.store;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.integration.store.MessageStore;
import org.springframework.messaging.support.GenericMessage;
import com.mongodb.MongoClient;
@@ -36,4 +46,49 @@ public class MongoDbMessageStoreTests extends AbstractMongoDbMessageStoreTests {
return mongoDbMessageStore;
}
@Test
public void testCustomConverter() throws Exception {
MongoDbMessageStore mongoDbMessageStore =
new MongoDbMessageStore(new SimpleMongoDbFactory(new MongoClient(), "test"));
FooToBytesConverter fooToBytesConverter = new FooToBytesConverter();
mongoDbMessageStore.setCustomConverters(fooToBytesConverter);
mongoDbMessageStore.afterPropertiesSet();
mongoDbMessageStore.addMessage(new GenericMessage<>(new Foo("foo")));
assertThat(fooToBytesConverter.called.await(10, TimeUnit.SECONDS)).isTrue();
}
private static class Foo {
String foo;
Foo(String foo) {
this.foo = foo;
}
@Override
public String toString() {
return foo;
}
}
@WritingConverter
private static class FooToBytesConverter implements Converter<Foo, byte[]> {
private final CountDownLatch called = new CountDownLatch(1);
@Override
public byte[] convert(Foo source) {
try {
return source.toString().getBytes();
}
finally {
this.called.countDown();
}
}
}
}

View File

@@ -123,9 +123,9 @@ The `MongoDbMessageStore` expands the `Message` as a Mongo document with all nes
It is useful when you need to have access to the `payload` or `headers` for auditing or analytics -- for example, against stored messages.
IMPORTANT: The `MongoDbMessageStore` uses a custom `MappingMongoConverter` implementation to store `Message` instances as MongoDB documents, and there are some limitations for the properties (`payload` and `header` values) of the `Message`.
For example, there is no ability to configure custom converters for complex domain `payload` instances or `header` values.
There is also no way to provide a custom `MongoTemplate` (or `MappingMongoConverter`).
To achieve these capabilities, an alternative MongoDB `MessageStore` implementation has been introduced (we describe it next).
Starting with version 5.1.6, the `MongoDbMessageStore` can be configured with custom converters which are propagated into an internal `MappingMongoConverter` implementation.
See `MongoDbMessageStore.setCustomConverters(Object... customConverters)` JavaDocs for more information.
Spring Integration 3.0 introduced the `ConfigurableMongoDbMessageStore`.
It implements both the `MessageStore` and `MessageGroupStore` interfaces.