Infrastructure for ReactiveMessageHandler (#3137)
* Infrastructure for ReactiveMessageHandler We have now a `ReactiveMongoDbStoringMessageHandler` which implements a `ReactiveMessageHandler`, but not a `MessageHandler` for possible deferred subscriptions to the returned Reactor type We don't have a proper application context processing for this new type of message handlers * Change a `ConsumerEndpointFactoryBean` to apply an `MH` and `RMH` as possible types for handler * Introduce a `ReactiveMessageHandlerAdapter` to wrap an `RMH` into a `MH` for synchronous calls in the regular consumer endpoints * Wrap an `RMH` into a `ReactiveMessageHandlerAdapter` for regular endpoints and unwrap for `ReactiveStreamsConsumer` * Add `RMH`-based ctor into `ReactiveStreamsConsumer` for target reactive streams composition (`flatMap()` on the `RMH`) * Remove a `DelegatingSubscriber` from the `ReactiveStreamsConsumer` in favor of direct calls from the `doOnSubscribe()`, `doOnComplete()` & `doOnNext()` * Add an `onErrorContinue()` to handle per-message errors, but don't cancel the whole source `Publisher` * Use `Disposable` from the `subscribe()` to cancel in the `stop()` - recommended way in Reactor * Use `onErrorContinue()` in the `FluxMessageChannel` instead of `try..catch` in the `doOnNext()` - for possible `onErrorStop()` in the provided upstream `Publisher` * Handle `RMH` in the `ServiceActivatorFactoryBean` as a direct handler as well with wrapping into `ReactiveMessageHandlerAdapter` for return. The `ConsumerEndpointFactoryBean` extracts an `RMH` from the adapter for the `ReactiveStreamsConsumer` anyway * Add XML parsing test for `ReactiveMongoDbStoringMessageHandler` * Add `log4j-slf4j-impl` for all the test runtime since `slf4j-api` comes as a transitive dependency from many places * * Fix conflicts after rebasing to master * * Fix typo in warn message * Change `Assert.state()` to `Assert.isTrue()` for `ConsumerEndpointFactoryBean.setHandler()` * * Fix `ConsumerEndpointFactoryBean` when reactive and no advice-chain * Fix race condition in the `ReactiveMongoDbStoringMessageHandlerTests.testReactiveMongoMessageHandlerFromApplicationContext()` * * Handle `ReactiveMessageHandler` in Java DSL. Essentially request a wrapping into `ReactiveMessageHandlerAdapter`. Describe such a requirements in the `ReactiveMessageHandlerAdapter` JavaDocs * Some Java DSL test polishing * Add Java DSL for `ReactiveMongoDbStoringMessageHandler` * Propagate missed `ApplicationContext` population into an internally created `ReactiveMongoTemplate` in the `ReactiveMongoDbStoringMessageHandler`
This commit is contained in:
committed by
Gary Russell
parent
e0509cc339
commit
d13752b40b
@@ -17,7 +17,9 @@
|
||||
package org.springframework.integration.mongodb.dsl;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@@ -34,8 +36,10 @@ import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.BulkOperations;
|
||||
import org.springframework.data.mongodb.core.MongoOperations;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.query.BasicQuery;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
@@ -255,6 +259,25 @@ public class MongoDbTests extends MongoDbAvailableTests {
|
||||
bulkOperations.execute();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@Qualifier("reactiveStore.input")
|
||||
private MessageChannel reactiveStoreInput;
|
||||
|
||||
@Test
|
||||
@MongoDbAvailable
|
||||
public void testReactiveMongoDbMessageHandler() {
|
||||
this.reactiveStoreInput.send(MessageBuilder.withPayload(createPerson("Bob")).build());
|
||||
|
||||
ReactiveMongoTemplate reactiveMongoTemplate = new ReactiveMongoTemplate(REACTIVE_MONGO_DATABASE_FACTORY);
|
||||
|
||||
await().untilAsserted(() ->
|
||||
assertThat(
|
||||
reactiveMongoTemplate.findOne(new BasicQuery("{'name' : 'Bob'}"), Person.class, "data")
|
||||
.block(Duration.ofSeconds(10)))
|
||||
.isNotNull()
|
||||
.extracting("name", "address.state").contains("Bob", "PA"));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableIntegration
|
||||
public static class ContextConfiguration {
|
||||
@@ -395,6 +418,14 @@ public class MongoDbTests extends MongoDbAvailableTests {
|
||||
.entityClass(Person.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IntegrationFlow reactiveStore() {
|
||||
return f -> f
|
||||
.channel(MessageChannels.flux())
|
||||
.handle(MongoDb.reactiveOutboundChannelAdapter(REACTIVE_MONGO_DATABASE_FACTORY));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,7 +39,9 @@ import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
/**
|
||||
* @author Xavier Padr?
|
||||
* @author Xavier Padro
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@@ -52,7 +54,7 @@ public class MongoDbOutboundGatewayXmlTests extends MongoDbAvailableTests {
|
||||
private ApplicationContext context;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
public void setUp() {
|
||||
MongoDbFactory mongoDbFactory = this.prepareMongoFactory();
|
||||
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory);
|
||||
|
||||
@@ -63,7 +65,7 @@ public class MongoDbOutboundGatewayXmlTests extends MongoDbAvailableTests {
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() throws Exception {
|
||||
public void cleanUp() {
|
||||
MongoDbFactory mongoDbFactory = this.prepareMongoFactory();
|
||||
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory);
|
||||
|
||||
@@ -73,7 +75,7 @@ public class MongoDbOutboundGatewayXmlTests extends MongoDbAvailableTests {
|
||||
|
||||
@Test
|
||||
@MongoDbAvailable
|
||||
public void testSingleQuery() throws Exception {
|
||||
public void testSingleQuery() {
|
||||
EventDrivenConsumer consumer = context.getBean("gatewaySingleQuery", EventDrivenConsumer.class);
|
||||
PollableChannel outChannel = context.getBean("out", PollableChannel.class);
|
||||
|
||||
@@ -87,7 +89,7 @@ public class MongoDbOutboundGatewayXmlTests extends MongoDbAvailableTests {
|
||||
|
||||
@Test
|
||||
@MongoDbAvailable
|
||||
public void testSingleQueryWithTemplate() throws Exception {
|
||||
public void testSingleQueryWithTemplate() {
|
||||
EventDrivenConsumer consumer = context.getBean("gatewayWithTemplate", EventDrivenConsumer.class);
|
||||
PollableChannel outChannel = context.getBean("out", PollableChannel.class);
|
||||
|
||||
@@ -101,7 +103,7 @@ public class MongoDbOutboundGatewayXmlTests extends MongoDbAvailableTests {
|
||||
|
||||
@Test
|
||||
@MongoDbAvailable
|
||||
public void testSingleQueryExpression() throws Exception {
|
||||
public void testSingleQueryExpression() {
|
||||
EventDrivenConsumer consumer = context.getBean("gatewaySingleQueryExpression", EventDrivenConsumer.class);
|
||||
PollableChannel outChannel = context.getBean("out", PollableChannel.class);
|
||||
|
||||
@@ -120,7 +122,7 @@ public class MongoDbOutboundGatewayXmlTests extends MongoDbAvailableTests {
|
||||
|
||||
@Test
|
||||
@MongoDbAvailable
|
||||
public void testQueryExpression() throws Exception {
|
||||
public void testQueryExpression() {
|
||||
EventDrivenConsumer consumer = context.getBean("gatewayQueryExpression", EventDrivenConsumer.class);
|
||||
PollableChannel outChannel = context.getBean("out", PollableChannel.class);
|
||||
|
||||
@@ -139,7 +141,7 @@ public class MongoDbOutboundGatewayXmlTests extends MongoDbAvailableTests {
|
||||
|
||||
@Test
|
||||
@MongoDbAvailable
|
||||
public void testQueryExpressionWithLimit() throws Exception {
|
||||
public void testQueryExpressionWithLimit() {
|
||||
EventDrivenConsumer consumer = context.getBean("gatewayQueryExpressionLimit", EventDrivenConsumer.class);
|
||||
PollableChannel outChannel = context.getBean("out", PollableChannel.class);
|
||||
|
||||
@@ -157,7 +159,7 @@ public class MongoDbOutboundGatewayXmlTests extends MongoDbAvailableTests {
|
||||
|
||||
@Test
|
||||
@MongoDbAvailable
|
||||
public void testCollectionCallback() throws Exception {
|
||||
public void testCollectionCallback() {
|
||||
EventDrivenConsumer consumer = context.getBean("gatewayCollectionCallback", EventDrivenConsumer.class);
|
||||
PollableChannel outChannel = context.getBean("out", PollableChannel.class);
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="input" class="org.springframework.integration.channel.FluxMessageChannel"/>
|
||||
|
||||
|
||||
<int:service-activator input-channel="input">
|
||||
<bean class="org.springframework.integration.mongodb.outbound.ReactiveMongoDbStoringMessageHandler">
|
||||
<constructor-arg
|
||||
value="#{T (org.springframework.integration.mongodb.outbound.ReactiveMongoDbStoringMessageHandlerTests).REACTIVE_MONGO_DATABASE_FACTORY}"/>
|
||||
</bean>
|
||||
</int:service-activator>
|
||||
|
||||
</beans>
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
* Copyright 2019-2020 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.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.integration.mongodb.outbound;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
@@ -26,8 +27,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
||||
@@ -40,6 +45,9 @@ import org.springframework.integration.mongodb.rules.MongoDbAvailable;
|
||||
import org.springframework.integration.mongodb.rules.MongoDbAvailableTests;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -48,19 +56,25 @@ import reactor.core.publisher.Mono;
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
* @author David Turanski
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@DirtiesContext
|
||||
public class ReactiveMongoDbStoringMessageHandlerTests extends MongoDbAvailableTests {
|
||||
|
||||
private ReactiveMongoTemplate template;
|
||||
|
||||
private ReactiveMongoDatabaseFactory mongoDbFactory;
|
||||
|
||||
@Autowired
|
||||
private MessageChannel input;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mongoDbFactory = this.prepareReactiveMongoFactory("foo");
|
||||
template = new ReactiveMongoTemplate(mongoDbFactory);
|
||||
this.mongoDbFactory = prepareReactiveMongoFactory("foo");
|
||||
this.template = new ReactiveMongoTemplate(this.mongoDbFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -82,6 +96,7 @@ public class ReactiveMongoDbStoringMessageHandlerTests extends MongoDbAvailableT
|
||||
public void validateMessageHandlingWithDefaultCollection() {
|
||||
ReactiveMongoDbStoringMessageHandler handler = new ReactiveMongoDbStoringMessageHandler(this.mongoDbFactory);
|
||||
handler.setBeanFactory(mock(BeanFactory.class));
|
||||
handler.setApplicationContext(mock(ApplicationContext.class, Answers.RETURNS_MOCKS));
|
||||
handler.afterPropertiesSet();
|
||||
Message<Person> message = MessageBuilder.withPayload(this.createPerson("Bob")).build();
|
||||
waitFor(handler.handleMessage(message));
|
||||
@@ -99,6 +114,7 @@ public class ReactiveMongoDbStoringMessageHandlerTests extends MongoDbAvailableT
|
||||
ReactiveMongoDbStoringMessageHandler handler = new ReactiveMongoDbStoringMessageHandler(this.mongoDbFactory);
|
||||
handler.setCollectionNameExpression(new LiteralExpression("foo"));
|
||||
handler.setBeanFactory(mock(BeanFactory.class));
|
||||
handler.setApplicationContext(mock(ApplicationContext.class, Answers.RETURNS_MOCKS));
|
||||
handler.afterPropertiesSet();
|
||||
|
||||
Message<Person> message = MessageBuilder.withPayload(this.createPerson("Bob")).build();
|
||||
@@ -119,6 +135,7 @@ public class ReactiveMongoDbStoringMessageHandlerTests extends MongoDbAvailableT
|
||||
ReactiveMongoDbStoringMessageHandler handler = new ReactiveMongoDbStoringMessageHandler(this.mongoDbFactory);
|
||||
handler.setCollectionNameExpression(new LiteralExpression(null));
|
||||
handler.setBeanFactory(mock(BeanFactory.class));
|
||||
handler.setApplicationContext(mock(ApplicationContext.class, Answers.RETURNS_MOCKS));
|
||||
handler.afterPropertiesSet();
|
||||
|
||||
Message<Person> message = MessageBuilder.withPayload(createPerson("Bob")).build();
|
||||
@@ -139,6 +156,7 @@ public class ReactiveMongoDbStoringMessageHandlerTests extends MongoDbAvailableT
|
||||
converter = spy(converter);
|
||||
handler.setMongoConverter(converter);
|
||||
handler.setBeanFactory(mock(BeanFactory.class));
|
||||
handler.setApplicationContext(mock(ApplicationContext.class, Answers.RETURNS_MOCKS));
|
||||
handler.afterPropertiesSet();
|
||||
Message<Person> message = MessageBuilder.withPayload(this.createPerson("Bob")).build();
|
||||
waitFor(handler.handleMessage(message));
|
||||
@@ -161,6 +179,7 @@ public class ReactiveMongoDbStoringMessageHandlerTests extends MongoDbAvailableT
|
||||
ReactiveMongoDbStoringMessageHandler handler = new ReactiveMongoDbStoringMessageHandler(writingTemplate);
|
||||
handler.setCollectionNameExpression(new LiteralExpression("foo"));
|
||||
handler.setBeanFactory(mock(BeanFactory.class));
|
||||
handler.setApplicationContext(mock(ApplicationContext.class, Answers.RETURNS_MOCKS));
|
||||
handler.afterPropertiesSet();
|
||||
Message<Person> message = MessageBuilder.withPayload(this.createPerson("Bob")).build();
|
||||
waitFor(handler.handleMessage(message));
|
||||
@@ -172,8 +191,22 @@ public class ReactiveMongoDbStoringMessageHandlerTests extends MongoDbAvailableT
|
||||
assertThat(person.getAddress().getState()).isEqualTo("PA");
|
||||
}
|
||||
|
||||
@Test
|
||||
@MongoDbAvailable
|
||||
public void testReactiveMongoMessageHandlerFromApplicationContext() {
|
||||
Message<Person> message = MessageBuilder.withPayload(createPerson("Bob")).build();
|
||||
this.input.send(message);
|
||||
|
||||
Query query = new BasicQuery("{'name' : 'Bob'}");
|
||||
|
||||
await().untilAsserted(() ->
|
||||
assertThat(waitFor(this.template.findOne(query, Person.class, "data")))
|
||||
.isNotNull()
|
||||
.extracting("name", "address.state").contains("Bob", "PA"));
|
||||
}
|
||||
|
||||
private static <T> T waitFor(Mono<T> mono) {
|
||||
return mono.block(Duration.ofSeconds(3));
|
||||
return mono.block(Duration.ofSeconds(10));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -65,16 +65,20 @@ public abstract class MongoDbAvailableTests {
|
||||
MongoClientSettings.builder().build()),
|
||||
"test");
|
||||
|
||||
public static final ReactiveMongoDatabaseFactory REACTIVE_MONGO_DATABASE_FACTORY =
|
||||
new SimpleReactiveMongoDatabaseFactory(
|
||||
com.mongodb.reactivestreams.client.MongoClients.create(
|
||||
MongoClientSettings.builder().build()),
|
||||
"test");
|
||||
|
||||
protected MongoDbFactory prepareMongoFactory(String... additionalCollectionsToDrop) {
|
||||
cleanupCollections(MONGO_DATABASE_FACTORY, additionalCollectionsToDrop);
|
||||
return MONGO_DATABASE_FACTORY;
|
||||
}
|
||||
|
||||
protected ReactiveMongoDatabaseFactory prepareReactiveMongoFactory(String... additionalCollectionsToDrop) {
|
||||
ReactiveMongoDatabaseFactory mongoDbFactory = new SimpleReactiveMongoDatabaseFactory(
|
||||
com.mongodb.reactivestreams.client.MongoClients.create(), "test");
|
||||
cleanupCollections(mongoDbFactory, additionalCollectionsToDrop);
|
||||
return mongoDbFactory;
|
||||
cleanupCollections(REACTIVE_MONGO_DATABASE_FACTORY, additionalCollectionsToDrop);
|
||||
return REACTIVE_MONGO_DATABASE_FACTORY;
|
||||
}
|
||||
|
||||
protected void cleanupCollections(ReactiveMongoDatabaseFactory mongoDbFactory,
|
||||
|
||||
Reference in New Issue
Block a user