Initial support for Reactive API

Fixes #520
Fixes #458 (for non-reactive binders)

Adds spring-cloud-stream-reactive module.
Introduces support for declarative @StreamListener and @Input and @Output annotated parameters.
Add StreamListenerArgumentAdapter and StreamListenerResultAdapter for wrapping bindable inputs
and outputs when passing arguments to declarative @StreamListener.
Adds support for using reactive types (Flux/Observable) with traditional binders.
Introduce FluxSender and ObservableSender for handling multiple streaming outputs per method.
Ensure that errors are caught and logged.

Fixing constructor assertions and Javadoc

Addressing PR comments

- rework @Input/@Output parameter validation
- renamed StreamListenerArgumentAdapter to StreamListenerParameterAdapter
- ensure that parameter direction is accounted for in the current adapters
This commit is contained in:
Marius Bogoevici
2016-07-21 17:25:04 -04:00
committed by Mark Fisher
parent 55b9aa75dd
commit 08d65a92bb
26 changed files with 1530 additions and 69 deletions

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2016 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
*
* http://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.cloud.stream.config;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Processor;
import org.springframework.cloud.stream.test.binder.MessageCollector;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.support.MessageBuilder;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Marius Bogoevici
*/
public class StreamListenerWithAnnotatedArgsTests {
@Test
public void testInputOutputArgs() throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(TestInputOutputArgs.class, "--server.port=0");
sendMessageAndValidate(context);
}
private void sendMessageAndValidate(ConfigurableApplicationContext context) throws InterruptedException {
@SuppressWarnings("unchecked")
Processor processor = context.getBean(Processor.class);
processor.input().send(MessageBuilder.withPayload("hello").setHeader("contentType", "text/plain").build());
MessageCollector messageCollector = context.getBean(MessageCollector.class);
Message<?> result = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS);
assertThat(result).isNotNull();
assertThat(result.getPayload()).isEqualTo("HELLO");
context.close();
}
@EnableBinding(Processor.class)
@EnableAutoConfiguration
public static class TestInputOutputArgs {
@StreamListener
public void receive(@Input(Processor.INPUT) SubscribableChannel input, @Output(Processor.OUTPUT) final MessageChannel output) {
input.subscribe(new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
output.send(MessageBuilder.withPayload(message.getPayload().toString().toUpperCase()).build());
}
});
}
}
}

View File

@@ -51,7 +51,7 @@ import static org.junit.Assert.fail;
/**
* @author Marius Bogoevici
*/
public class StreamListenerTests {
public class StreamListenerWithHandlerTests {
@Test
public void testContentTypeConversion() throws Exception {
@@ -240,8 +240,8 @@ public class StreamListenerTests {
@StreamListener(Sink.INPUT)
public void receive(FooPojo fooPojo) {
receivedArguments.add(fooPojo);
latch.countDown();
this.receivedArguments.add(fooPojo);
this.latch.countDown();
}
}
@@ -254,7 +254,7 @@ public class StreamListenerTests {
@StreamListener(Processor.INPUT)
@SendTo(Processor.OUTPUT)
public String receive(FooPojo fooPojo) {
receivedPojos.add(fooPojo);
this.receivedPojos.add(fooPojo);
return fooPojo.getBar();
}
}
@@ -268,7 +268,7 @@ public class StreamListenerTests {
@StreamListener(Processor.INPUT)
@SendTo(Processor.OUTPUT)
public BazPojo receive(FooPojo fooPojo) {
receivedPojos.add(fooPojo);
this.receivedPojos.add(fooPojo);
BazPojo bazPojo = new BazPojo();
bazPojo.setQux(fooPojo.getBar());
return bazPojo;
@@ -285,9 +285,9 @@ public class StreamListenerTests {
public void receive(@Payload FooPojo fooPojo,
@Headers Map<String, Object> headers,
@Header(MessageHeaders.CONTENT_TYPE) String contentType) {
receivedArguments.add(fooPojo);
receivedArguments.add(headers);
receivedArguments.add(contentType);
this.receivedArguments.add(fooPojo);
this.receivedArguments.add(headers);
this.receivedArguments.add(contentType);
}
}
@@ -300,7 +300,7 @@ public class StreamListenerTests {
@StreamListener(Processor.INPUT)
@SendTo(Processor.OUTPUT)
public Message<?> receive(FooPojo fooPojo) {
receivedPojos.add(fooPojo);
this.receivedPojos.add(fooPojo);
BazPojo bazPojo = new BazPojo();
bazPojo.setQux(fooPojo.getBar());
return MessageBuilder.withPayload(bazPojo).setHeader("foo", "bar").build();
@@ -316,7 +316,7 @@ public class StreamListenerTests {
@StreamListener(Processor.INPUT)
@SendTo(Processor.OUTPUT)
public BazPojo receive(Message<String> fooMessage) {
receivedMessages.add(fooMessage);
this.receivedMessages.add(fooMessage);
BazPojo bazPojo = new BazPojo();
bazPojo.setQux(fooMessage.getPayload());
return bazPojo;
@@ -353,7 +353,7 @@ public class StreamListenerTests {
@StreamListener(Processor.INPUT)
@SendTo(Processor.OUTPUT)
public BazPojo receive(FooPojo fooMessage) {
receivedPojos.add(fooMessage);
this.receivedPojos.add(fooMessage);
BazPojo bazPojo = new BazPojo();
bazPojo.setQux(fooMessage.getBar());
return bazPojo;
@@ -365,7 +365,7 @@ public class StreamListenerTests {
private String bar;
public String getBar() {
return bar;
return this.bar;
}
public void setBar(String bar) {
@@ -378,7 +378,7 @@ public class StreamListenerTests {
private String qux;
public String getQux() {
return qux;
return this.qux;
}
public void setQux(String qux) {