Add MongoDbMessageSource UPDATE option (#3493)
* Add MongoDbMessageSource UPDATE option * Extract `AbstractMongoDbMessageSource` with common options and methods for both `MongoDbMessageSource` and `ReactiveMongoDbMessageSource` * Add an `updateExpression` option into MongoDb source implementations * Implement respective `update` logic after fetching the data from the collection * Cover both reactive and blocking updates with tests * Add `MongoDbMessageSourceSpec` into Java DSL for MongoDb channel adapters * Expose an `update` XML attribute for the `<int-mongo:inbound-channel-adapter>` * Upgrade MongoDb driver for latest Spring Data compatibility * Document a new feature * Upgrade `mongodb.adoc` for code block switch whenever it is appropriate * * Add a Kotlin sample for `MongoDb.outboundGateway()` DSL * Apply suggestions from code review Co-authored-by: Gary Russell <grussell@vmware.com> Co-authored-by: Gary Russell <grussell@vmware.com>
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
<int-mongodb:inbound-channel-adapter id="fullConfigWithQueryExpression"
|
||||
collection-name-expression="'foo'"
|
||||
query-expression="new BasicQuery('{''address.state'' : ''PA''}').limit(2)"
|
||||
update="{ $set: {'address.state' : 'NJ'} }"
|
||||
mongo-converter="mongoConverter"
|
||||
mongodb-factory="mongoDbFactory"
|
||||
auto-startup="false">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -17,9 +17,9 @@
|
||||
package org.springframework.integration.mongodb.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@@ -34,8 +34,7 @@ import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
|
||||
import org.springframework.integration.mongodb.inbound.MongoDbMessageSource;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
@@ -43,8 +42,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
* @author Artem Bilan
|
||||
* @author Yaron Yamin
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringJUnitConfig
|
||||
@DirtiesContext
|
||||
public class MongoDbInboundChannelAdapterParserTests {
|
||||
|
||||
@@ -112,6 +110,8 @@ public class MongoDbInboundChannelAdapterParserTests {
|
||||
assertThat(TestUtils.getPropertyValue(source, "queryExpression") instanceof SpelExpression).isTrue();
|
||||
assertThat(TestUtils.getPropertyValue(source, "queryExpression.expression"))
|
||||
.isEqualTo("new BasicQuery('{''address.state'' : ''PA''}').limit(2)");
|
||||
assertThat(TestUtils.getPropertyValue(source, "updateExpression.literalValue"))
|
||||
.isEqualTo("{ $set: {'address.state' : 'NJ'} }");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -152,16 +152,20 @@ public class MongoDbInboundChannelAdapterParserTests {
|
||||
assertThat(TestUtils.getPropertyValue(source, "collectionNameExpression.literalValue")).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test(expected = BeanDefinitionParsingException.class)
|
||||
@Test
|
||||
public void templateAndFactoryFail() {
|
||||
new ClassPathXmlApplicationContext("inbound-adapter-parser-fail-template-factory-config.xml", this.getClass())
|
||||
.close();
|
||||
assertThatExceptionOfType(BeanDefinitionParsingException.class)
|
||||
.isThrownBy(() ->
|
||||
new ClassPathXmlApplicationContext("inbound-adapter-parser-fail-template-factory-config.xml",
|
||||
getClass()));
|
||||
}
|
||||
|
||||
@Test(expected = BeanDefinitionParsingException.class)
|
||||
@Test
|
||||
public void templateAndConverterFail() {
|
||||
new ClassPathXmlApplicationContext("inbound-adapter-parser-fail-template-converter-config.xml", this.getClass())
|
||||
.close();
|
||||
assertThatExceptionOfType(BeanDefinitionParsingException.class)
|
||||
.isThrownBy(() ->
|
||||
new ClassPathXmlApplicationContext("inbound-adapter-parser-fail-template-converter-config.xml",
|
||||
getClass()));
|
||||
}
|
||||
|
||||
private MongoDbMessageSource assertMongoDbMessageSource(Object testedBean) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2007-2020 the original author or authors.
|
||||
* Copyright 2007-2021 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.
|
||||
@@ -35,6 +35,7 @@ import org.springframework.data.mongodb.core.MongoOperations;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.query.BasicQuery;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
@@ -241,10 +242,8 @@ public class MongoDbMessageSourceTests extends MongoDbAvailableTests {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
@MongoDbAvailable
|
||||
public void validateSuccessfulQueryWithMongoTemplate() {
|
||||
|
||||
MongoDatabaseFactory mongoDbFactory = this.prepareMongoFactory();
|
||||
|
||||
public void validateSuccessfulQueryWithMongoTemplateAndUpdate() {
|
||||
MongoDatabaseFactory mongoDbFactory = prepareMongoFactory();
|
||||
MappingMongoConverter converter = new TestMongoConverter(mongoDbFactory, new MongoMappingContext());
|
||||
converter.afterPropertiesSet();
|
||||
converter = spy(converter);
|
||||
@@ -253,16 +252,22 @@ public class MongoDbMessageSourceTests extends MongoDbAvailableTests {
|
||||
Expression queryExpression = new LiteralExpression("{'address.state' : 'PA'}");
|
||||
MongoDbMessageSource messageSource = new MongoDbMessageSource(template, queryExpression);
|
||||
messageSource.setBeanFactory(mock(BeanFactory.class));
|
||||
messageSource.setUpdateExpression(new LiteralExpression("{ $set: {'address.state' : 'NJ'} }"));
|
||||
messageSource.afterPropertiesSet();
|
||||
|
||||
MongoTemplate writingTemplate = new MongoTemplate(mongoDbFactory, converter);
|
||||
writingTemplate.save(this.createPerson("Manny"), "data");
|
||||
writingTemplate.save(this.createPerson("Moe"), "data");
|
||||
writingTemplate.save(this.createPerson("Jack"), "data");
|
||||
writingTemplate.save(createPerson("Manny"), "data");
|
||||
writingTemplate.save(createPerson("Moe"), "data");
|
||||
writingTemplate.save(createPerson("Jack"), "data");
|
||||
|
||||
List<Person> persons = (List<Person>) messageSource.receive().getPayload();
|
||||
assertThat(persons.size()).isEqualTo(3);
|
||||
assertThat(persons).hasSize(3);
|
||||
verify(converter, times(3)).read((Class<Person>) Mockito.any(), Mockito.any(Bson.class));
|
||||
|
||||
assertThat(messageSource.receive()).isNull();
|
||||
|
||||
assertThat(template.find(new BasicQuery("{'address.state' : 'NJ'}"), Object.class, "data"))
|
||||
.hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
@@ -42,6 +42,7 @@ import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.channel.FluxMessageChannel;
|
||||
@@ -91,7 +92,7 @@ public class ReactiveMongoDbMessageSourceTests extends MongoDbAvailableTests {
|
||||
@MongoDbAvailable
|
||||
@SuppressWarnings("unchecked")
|
||||
public void validateSuccessfulQueryWithSingleElementFluxOfDbObject() {
|
||||
ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory = this.prepareReactiveMongoFactory();
|
||||
ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory = prepareReactiveMongoFactory();
|
||||
|
||||
ReactiveMongoTemplate template = new ReactiveMongoTemplate(reactiveMongoDatabaseFactory);
|
||||
waitFor(template.save(createPerson(), "data"));
|
||||
@@ -111,7 +112,7 @@ public class ReactiveMongoDbMessageSourceTests extends MongoDbAvailableTests {
|
||||
@MongoDbAvailable
|
||||
@SuppressWarnings("unchecked")
|
||||
public void validateSuccessfulQueryWithSingleElementFluxOfPerson() {
|
||||
ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory = this.prepareReactiveMongoFactory();
|
||||
ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory = prepareReactiveMongoFactory();
|
||||
|
||||
ReactiveMongoTemplate template = new ReactiveMongoTemplate(reactiveMongoDatabaseFactory);
|
||||
waitFor(template.save(createPerson(), "data"));
|
||||
@@ -183,6 +184,7 @@ public class ReactiveMongoDbMessageSourceTests extends MongoDbAvailableTests {
|
||||
StepVerifier.create(output)
|
||||
.assertNext(
|
||||
message -> assertThat(((Person) message.getPayload()).getName()).isEqualTo("Oleg"))
|
||||
.expectNoEvent(Duration.ofMillis(100))
|
||||
.thenCancel()
|
||||
.verify(Duration.ofSeconds(10));
|
||||
|
||||
@@ -247,9 +249,10 @@ public class ReactiveMongoDbMessageSourceTests extends MongoDbAvailableTests {
|
||||
public IntegrationFlow pollingFlow() {
|
||||
return IntegrationFlows
|
||||
.from(MongoDb.reactiveInboundChannelAdapter(
|
||||
MongoDbAvailableTests.REACTIVE_MONGO_DATABASE_FACTORY, "{'name' : 'Oleg'}")
|
||||
REACTIVE_MONGO_DATABASE_FACTORY, "{'name' : 'Oleg'}")
|
||||
.update(Update.update("name", "DONE"))
|
||||
.entityClass(Person.class),
|
||||
c -> c.poller(Pollers.fixedDelay(100).maxMessagesPerPoll(1)))
|
||||
c -> c.poller(Pollers.fixedDelay(100)))
|
||||
.split()
|
||||
.channel(c -> c.flux("output"))
|
||||
.get();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -24,6 +24,7 @@ import org.bson.conversions.Bson;
|
||||
import org.junit.Rule;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.MongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
|
||||
@@ -130,6 +131,9 @@ public abstract class MongoDbAvailableTests {
|
||||
|
||||
public static class Person {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
private Address address;
|
||||
|
||||
private String name;
|
||||
|
||||
Reference in New Issue
Block a user