Support flexible bound element types

Fixes #519

Introduces some internal changes to the framework allowing the use
of other types than MessageChannel/SubscribableChannel as bindable
types (e.g. Flux, Observable, KStream, etc.)

- Modify BinderFactory to allow the retrieval and lookup of a
  binder not only by name, but also by binding target type
- Subsequent changes to ChannelBindingService and tests to account
  for the modified signature
- Introduce BindingTargetFactory as the contract for creating bound
  elements
- Remove any references to chanels and bound elements and use
  'binding target' systematically across the board

Reinstate our own Checkstyle checks with a reduced set of rules so that header
  validation can be performed automatically at compile time.

Use ${project.version} for the checkstyle plugin configuration

Renaming some occurences of 'boundElement' to 'bindingTarget'

Renamed a stray occurence of 'channel'

Fix some occurences of String concatenation in the same line after reformatting
This commit is contained in:
Marius Bogoevici
2016-11-21 23:58:18 -05:00
committed by markfisher
parent 8e5689c298
commit 8ac71c7b57
99 changed files with 1997 additions and 1375 deletions

491
pom.xml
View File

@@ -1,246 +1,249 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-parent</artifactId>
<version>1.1.1.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>1.2.1.RELEASE</version>
<relativePath />
</parent>
<scm>
<url>https://github.com/spring-cloud/spring-cloud-stream</url>
<connection>scm:git:git://github.com/spring-cloud/spring-cloud-stream.git</connection>
<developerConnection>scm:git:ssh://git@github.com/spring-cloud/spring-cloud-stream.git</developerConnection>
<tag>HEAD</tag>
</scm>
<properties>
<java.version>1.7</java.version>
<rxjava.version>1.1.10</rxjava.version>
<spring.tuple.version>1.0.0.RELEASE</spring.tuple.version>
<spring.integration.tuple.version>1.0.0.RELEASE</spring.integration.tuple.version>
<reactor.version>3.0.2.RELEASE</reactor.version>
<kryo-shaded.version>3.0.3</kryo-shaded.version>
<objenesis.version>2.1</objenesis.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-tools</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-codec</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-rxjava</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-schema-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-test</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tuple</artifactId>
<version>${spring.tuple.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-tuple</artifactId>
<version>${spring.integration.tuple.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-test-support</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-test-support-internal</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo-shaded</artifactId>
<version>${kryo-shaded.version}</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>${reactor.version}</version>
</dependency>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
<version>${rxjava.version}</version>
</dependency>
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>${objenesis.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>spring-cloud-stream</module>
<module>spring-cloud-stream-binder-test</module>
<module>spring-cloud-stream-codec</module>
<module>spring-cloud-stream-rxjava</module>
<module>spring-cloud-stream-test-support</module>
<module>spring-cloud-stream-test-support-internal</module>
<module>spring-cloud-stream-integration-tests</module>
<module>spring-cloud-stream-core-docs</module>
<module>spring-cloud-stream-reactive</module>
<module>spring-cloud-stream-schema</module>
<module>spring-cloud-stream-schema-server</module>
<module>spring-cloud-stream-tools</module>
</modules>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>2.17</version>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>7.1</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<quiet>true</quiet>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build-tools</artifactId>
<version>1.2.0.RELEASE</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>checkstyle-validation</id>
<phase>validate</phase>
<configuration>
<configLocation>checkstyle.xml</configLocation>
<encoding>UTF-8</encoding>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
</configuration>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>spring</id>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/libs-release-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-parent</artifactId>
<version>1.1.1.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>1.2.1.RELEASE</version>
<relativePath/>
</parent>
<scm>
<url>https://github.com/spring-cloud/spring-cloud-stream</url>
<connection>scm:git:git://github.com/spring-cloud/spring-cloud-stream.git</connection>
<developerConnection>scm:git:ssh://git@github.com/spring-cloud/spring-cloud-stream.git</developerConnection>
<tag>HEAD</tag>
</scm>
<properties>
<java.version>1.7</java.version>
<rxjava.version>1.1.10</rxjava.version>
<spring.tuple.version>1.0.0.RELEASE</spring.tuple.version>
<spring.integration.tuple.version>1.0.0.RELEASE</spring.integration.tuple.version>
<reactor.version>3.0.2.RELEASE</reactor.version>
<kryo-shaded.version>3.0.3</kryo-shaded.version>
<objenesis.version>2.1</objenesis.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-tools</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-codec</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-rxjava</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-schema-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-test</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tuple</artifactId>
<version>${spring.tuple.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-tuple</artifactId>
<version>${spring.integration.tuple.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-test-support</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-test-support-internal</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo-shaded</artifactId>
<version>${kryo-shaded.version}</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>${reactor.version}</version>
</dependency>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
<version>${rxjava.version}</version>
</dependency>
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>${objenesis.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>spring-cloud-stream</module>
<module>spring-cloud-stream-binder-test</module>
<module>spring-cloud-stream-codec</module>
<module>spring-cloud-stream-rxjava</module>
<module>spring-cloud-stream-test-support</module>
<module>spring-cloud-stream-test-support-internal</module>
<module>spring-cloud-stream-integration-tests</module>
<module>spring-cloud-stream-core-docs</module>
<module>spring-cloud-stream-reactive</module>
<module>spring-cloud-stream-schema</module>
<module>spring-cloud-stream-schema-server</module>
<module>spring-cloud-stream-tools</module>
</modules>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>2.17</version>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>7.1</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<quiet>true</quiet>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-tools</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>checkstyle-validation</id>
<phase>validate</phase>
<configuration>
<configLocation>checkstyle.xml</configLocation>
<headerLocation>checkstyle-header.txt</headerLocation>
<suppressionsLocation>checkstyle-suppressions.xml</suppressionsLocation>
<encoding>UTF-8</encoding>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
</configuration>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>spring</id>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/libs-release-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</project>

View File

@@ -25,7 +25,7 @@ import org.junit.Test;
import org.springframework.cloud.stream.binding.MessageConverterConfigurer;
import org.springframework.cloud.stream.config.BindingProperties;
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
import org.springframework.cloud.stream.config.BindingServiceProperties;
import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.Lifecycle;
@@ -233,17 +233,17 @@ public abstract class AbstractBinderTests<B extends AbstractTestBinder<? extends
protected DirectChannel createBindableChannel(String channelName, BindingProperties bindingProperties) throws
Exception {
ChannelBindingServiceProperties channelBindingServiceProperties = new ChannelBindingServiceProperties();
channelBindingServiceProperties.getBindings().put(channelName, bindingProperties);
BindingServiceProperties bindingServiceProperties = new BindingServiceProperties();
bindingServiceProperties.getBindings().put(channelName, bindingProperties);
ConfigurableApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.refresh();
channelBindingServiceProperties.setApplicationContext(applicationContext);
channelBindingServiceProperties.setConversionService(new DefaultConversionService());
channelBindingServiceProperties.afterPropertiesSet();
bindingServiceProperties.setApplicationContext(applicationContext);
bindingServiceProperties.setConversionService(new DefaultConversionService());
bindingServiceProperties.afterPropertiesSet();
DirectChannel channel = new DirectChannel();
channel.setBeanName(channelName);
MessageConverterConfigurer messageConverterConfigurer = new MessageConverterConfigurer(
channelBindingServiceProperties,
bindingServiceProperties,
new CompositeMessageConverterFactory(null, null));
messageConverterConfigurer.setBeanFactory(applicationContext.getBeanFactory());
messageConverterConfigurer.afterPropertiesSet();

View File

@@ -30,6 +30,7 @@ import org.springframework.cloud.stream.test.binder.TestSupportBinder;
import org.springframework.context.annotation.PropertySource;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -53,7 +54,7 @@ public class ContentTypeOutboundSourceTests {
@SuppressWarnings("unchecked")
public void testMessageHeaderWhenNoExplicitContentTypeOnMessage() throws Exception {
testSource.output().send(MessageBuilder.withPayload("{\"message\":\"Hi\"}").build());
Message<String> received = (Message<String>) ((TestSupportBinder) binderFactory.getBinder(null))
Message<String> received = (Message<String>) ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class))
.messageCollector().forChannel(testSource.output()).poll();
assertThat(received.getHeaders().get(MessageHeaders.CONTENT_TYPE).toString()).isEqualTo("application/json");
assertThat(received).hasFieldOrPropertyWithValue("payload", "{\"message\":\"Hi\"}");

View File

@@ -37,6 +37,7 @@ import org.springframework.context.annotation.PropertySource;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.support.converter.DefaultDatatypeChannelMessageConverter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.AbstractMessageConverter;
import org.springframework.messaging.converter.MessageConverter;
@@ -70,7 +71,7 @@ public class CustomMessageConverterTests {
BarConverter.class, DefaultDatatypeChannelMessageConverter.class);
testSource.output().send(MessageBuilder.withPayload(new Foo("hi")).build());
@SuppressWarnings("unchecked")
Message<String> received = (Message<String>) ((TestSupportBinder) binderFactory.getBinder(null))
Message<String> received = (Message<String>) ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class))
.messageCollector().forChannel(testSource.output()).poll(1, TimeUnit.SECONDS);
Assert.assertThat(received, notNullValue());
assertThat(received.getHeaders().get(MessageHeaders.CONTENT_TYPE)).isEqualTo(MimeType.valueOf("test/foo"));

View File

@@ -34,6 +34,7 @@ import org.springframework.context.annotation.PropertySource;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -59,7 +60,7 @@ public class DeserializeJSONToJavaTypeTests {
public void testMessageDeserialized() throws Exception {
testProcessor.input().send(MessageBuilder.withPayload("{\"name\":\"Bar\"}").setHeader("contentType", "application/json").build());
@SuppressWarnings("unchecked")
Message<?> received = ((TestSupportBinder) binderFactory.getBinder(null))
Message<?> received = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class))
.messageCollector().forChannel(testProcessor.output()).poll(1, TimeUnit.SECONDS);
assertThat(received).isNotNull();
assertThat(received.getPayload()).isInstanceOf(Foo.class);

View File

@@ -35,6 +35,7 @@ import org.springframework.integration.annotation.Poller;
import org.springframework.integration.channel.PublishSubscribeChannel;
import org.springframework.integration.core.MessageSource;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -55,7 +56,7 @@ public class ErrorChannelTests {
@Test
public void testErrorChannelBinding() throws Exception {
Message<?> message = ((TestSupportBinder) binderFactory.getBinder(null)).messageCollector().forChannel(errorChannel).poll(10, TimeUnit.SECONDS);
Message<?> message = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)).messageCollector().forChannel(errorChannel).poll(10, TimeUnit.SECONDS);
Assert.isTrue(message instanceof ErrorMessage, "Message should be an instance of ErrorMessage");
Assert.isTrue(message.getPayload() instanceof MessagingException, "Message payload should be an instance" +
"of MessagingException");

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.config;
import java.util.ArrayList;

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.config;
import java.util.ArrayList;

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.config;
import org.junit.Test;

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.config;
import java.util.ArrayList;

View File

@@ -208,9 +208,9 @@ public class StreamListenerHandlerMethodTests {
latch.countDown();
}
});
processor.input().send(MessageBuilder.withPayload("{\"foo\":\"fooTESTing" + "\"}")
processor.input().send(MessageBuilder.withPayload("{\"foo\":\"fooTESTing\"}")
.setHeader("contentType", "application/json").build());
inboundChannel2.input().send(MessageBuilder.withPayload("{\"bar\":\"bartestING" + "\"}")
inboundChannel2.input().send(MessageBuilder.withPayload("{\"bar\":\"bartestING\"}")
.setHeader("contentType", "application/json").build());
assertThat(latch.await(1, TimeUnit.SECONDS));
context.close();

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.config;
import java.util.ArrayList;

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.config;
import java.util.ArrayList;

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.config;
import java.util.ArrayList;

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.config;
import java.util.ArrayList;

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.config;
import org.springframework.cloud.stream.annotation.Input;
@@ -53,7 +54,7 @@ public class StreamListenerTestUtils {
public interface FooInboundChannel1 {
public String INPUT = "foo1-input";
String INPUT = "foo1-input";
@Input(FooInboundChannel1.INPUT)
SubscribableChannel input();

View File

@@ -20,18 +20,19 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* Used for {@link org.springframework.cloud.stream.annotation.StreamListener} arguments annotated with {@link
* org.springframework.cloud.stream.annotation.Output}.
* Used for {@link org.springframework.cloud.stream.annotation.StreamListener} arguments
* annotated with {@link org.springframework.cloud.stream.annotation.Output}.
* @author Marius Bogoevici
*/
public interface FluxSender {
/**
* Streams the {@link reactor.core.publisher.Flux} through the bound
* element corresponding to the {@link org.springframework.cloud.stream.annotation.Output} annotation of the
* argument.
* @param flux a {@link Flux} that will be streamed through the bound element
* @return a {@link Mono} representing the result of sending the flux (completion or error)
* Streams the {@link reactor.core.publisher.Flux} through the binding target
* corresponding to the {@link org.springframework.cloud.stream.annotation.Output}
* annotation of the argument.
* @param flux a {@link Flux} that will be streamed through the binding target
* @return a {@link Mono} representing the result of sending the flux (completion or
* error)
*/
Mono<Void> send(Flux<?> flux);
}

View File

@@ -36,16 +36,16 @@ public class FluxToMessageChannelResultAdapter
private Log log = LogFactory.getLog(FluxToMessageChannelResultAdapter.class);
@Override
public boolean supports(Class<?> resultType, Class<?> boundType) {
return Flux.class.isAssignableFrom(resultType) && MessageChannel.class.isAssignableFrom(boundType);
public boolean supports(Class<?> resultType, Class<?> bindingTarget) {
return Flux.class.isAssignableFrom(resultType) && MessageChannel.class.isAssignableFrom(bindingTarget);
}
public void adapt(Flux<?> streamListenerResult, MessageChannel boundElement) {
public void adapt(Flux<?> streamListenerResult, MessageChannel bindingTarget) {
streamListenerResult
.doOnError(e -> this.log.error("Error while processing result", e))
.retry()
.subscribe(
result -> boundElement.send(result instanceof Message<?> ? (Message<?>) result
result -> bindingTarget.send(result instanceof Message<?> ? (Message<?>) result
: MessageBuilder.withPayload(result).build()));
}
}

View File

@@ -38,14 +38,14 @@ public class MessageChannelToFluxSenderParameterAdapter
private Log log = LogFactory.getLog(MessageChannelToFluxSenderParameterAdapter.class);
@Override
public boolean supports(Class<?> boundElementType, MethodParameter methodParameter) {
public boolean supports(Class<?> bindingTargetType, MethodParameter methodParameter) {
ResolvableType type = ResolvableType.forMethodParameter(methodParameter);
return MessageChannel.class.isAssignableFrom(boundElementType)
return MessageChannel.class.isAssignableFrom(bindingTargetType)
&& FluxSender.class.isAssignableFrom(type.getRawClass());
}
@Override
public FluxSender adapt(MessageChannel boundElement, MethodParameter parameter) {
public FluxSender adapt(MessageChannel bindingTarget, MethodParameter parameter) {
return resultPublisher -> {
MonoProcessor<Void> sendResult = MonoProcessor.create();
// add error handling and reconnect in the event of an error
@@ -53,7 +53,7 @@ public class MessageChannelToFluxSenderParameterAdapter
.doOnError(e -> this.log.error("Error during processing: ", e))
.retry()
.subscribe(
result -> boundElement.send(result instanceof Message<?> ? (Message<?>) result :
result -> bindingTarget.send(result instanceof Message<?> ? (Message<?>) result :
MessageBuilder.withPayload(result).build()), e -> sendResult.onError(e),
() -> sendResult.onComplete());
return sendResult;

View File

@@ -45,13 +45,13 @@ public class MessageChannelToInputFluxParameterAdapter
}
@Override
public boolean supports(Class<?> boundElementType, MethodParameter methodParameter) {
return SubscribableChannel.class.isAssignableFrom(boundElementType)
public boolean supports(Class<?> bindingTargetType, MethodParameter methodParameter) {
return SubscribableChannel.class.isAssignableFrom(bindingTargetType)
&& Flux.class.isAssignableFrom(methodParameter.getParameterType());
}
@Override
public Flux<?> adapt(final SubscribableChannel boundElement, MethodParameter parameter) {
public Flux<?> adapt(final SubscribableChannel bindingTarget, MethodParameter parameter) {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
final Class<?> argumentClass = (resolvableType.getGeneric(0).getRawClass() != null) ? (resolvableType
.getGeneric(0).getRawClass()) : Object.class;
@@ -63,8 +63,8 @@ public class MessageChannelToInputFluxParameterAdapter
emitter.next(message);
}
};
boundElement.subscribe(messageHandler);
emitter.setCancellation(() -> boundElement.unsubscribe(messageHandler));
bindingTarget.subscribe(messageHandler);
emitter.setCancellation(() -> bindingTarget.unsubscribe(messageHandler));
}).publish().autoConnect();
}
else {
@@ -80,8 +80,8 @@ public class MessageChannelToInputFluxParameterAdapter
}
}
};
boundElement.subscribe(messageHandler);
emitter.setCancellation(() -> boundElement.unsubscribe(messageHandler));
bindingTarget.subscribe(messageHandler);
emitter.setCancellation(() -> bindingTarget.unsubscribe(messageHandler));
}).publish().autoConnect();
}
}

View File

@@ -41,14 +41,14 @@ public class MessageChannelToInputObservableParameterAdapter
this.messageChannelToInputFluxArgumentAdapter = messageChannelToInputFluxArgumentAdapter;
}
public boolean supports(Class<?> boundElementType, MethodParameter methodParameter) {
return SubscribableChannel.class.isAssignableFrom(boundElementType)
public boolean supports(Class<?> bindingTargetType, MethodParameter methodParameter) {
return SubscribableChannel.class.isAssignableFrom(bindingTargetType)
&& Observable.class.isAssignableFrom(methodParameter.getParameterType());
}
@Override
public Observable<?> adapt(final SubscribableChannel boundElement, MethodParameter parameter) {
public Observable<?> adapt(final SubscribableChannel bindingTarget, MethodParameter parameter) {
return RxJava1Adapter.publisherToObservable(
this.messageChannelToInputFluxArgumentAdapter.adapt(boundElement, parameter));
this.messageChannelToInputFluxArgumentAdapter.adapt(bindingTarget, parameter));
}
}

View File

@@ -43,19 +43,19 @@ public class MessageChannelToObservableSenderParameterAdapter implements
}
@Override
public boolean supports(Class<?> boundElementType, MethodParameter methodParameter) {
public boolean supports(Class<?> bindingTargetType, MethodParameter methodParameter) {
ResolvableType type = ResolvableType.forMethodParameter(methodParameter);
return MessageChannel.class.isAssignableFrom(boundElementType)
return MessageChannel.class.isAssignableFrom(bindingTargetType)
&& ObservableSender.class.isAssignableFrom(type.getRawClass());
}
@Override
public ObservableSender adapt(MessageChannel boundElement, MethodParameter parameter) {
public ObservableSender adapt(MessageChannel bindingTarget, MethodParameter parameter) {
return new ObservableSender() {
private FluxSender fluxSender = MessageChannelToObservableSenderParameterAdapter.this
.messageChannelToFluxSenderArgumentAdapter
.adapt(boundElement, parameter);
.adapt(bindingTarget, parameter);
@Override
public Single<Void> send(Observable<?> observable) {

View File

@@ -20,17 +20,20 @@ import rx.Observable;
import rx.Single;
/**
* Used for {@link org.springframework.cloud.stream.annotation.StreamListener} arguments annotated with {@link
* org.springframework.cloud.stream.annotation.Output}.
* Used for {@link org.springframework.cloud.stream.annotation.StreamListener} arguments
* annotated with {@link org.springframework.cloud.stream.annotation.Output}.
*
* @author Marius Bogoevici
*/
public interface ObservableSender {
/**
* Streams the {@link Observable} through the bound
* element corresponding to the {@link org.springframework.cloud.stream.annotation.Output} annotation of the
* Streams the {@link Observable} through the binding target corresponding to the
* {@link org.springframework.cloud.stream.annotation.Output} annotation of the
* argument.
* @param observable an {@link Observable} that will be streamed through the bound element
*
* @param observable an {@link Observable} that will be streamed through the bound
* element
* @return a {@link Single} representing the result of an operation
*/
Single<Void> send(Observable<?> observable);

View File

@@ -40,13 +40,13 @@ public class ObservableToMessageChannelResultAdapter
}
@Override
public boolean supports(Class<?> resultType, Class<?> boundType) {
public boolean supports(Class<?> resultType, Class<?> bindingTarget) {
return Observable.class.isAssignableFrom(resultType)
&& MessageChannel.class.isAssignableFrom(boundType);
&& MessageChannel.class.isAssignableFrom(bindingTarget);
}
public void adapt(Observable<?> streamListenerResult, MessageChannel boundElement) {
public void adapt(Observable<?> streamListenerResult, MessageChannel bindingTarget) {
this.fluxToMessageChannelResultAdapter.adapt(RxJava1Adapter.observableToFlux(streamListenerResult),
boundElement);
bindingTarget);
}
}

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.reactive;
import org.junit.Test;

View File

@@ -16,8 +16,6 @@
package org.springframework.cloud.stream.reactive;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import java.util.Collection;
import java.util.UUID;
@@ -42,6 +40,8 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.support.MessageBuilder;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan

View File

@@ -16,8 +16,6 @@
package org.springframework.cloud.stream.reactive;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import java.util.Collection;
import java.util.UUID;
@@ -42,6 +40,8 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.support.MessageBuilder;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan

View File

@@ -16,8 +16,6 @@
package org.springframework.cloud.stream.reactive;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
@@ -41,6 +39,8 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.support.MessageBuilder;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan

View File

@@ -19,6 +19,8 @@ package org.springframework.cloud.stream.test.binder;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.cloud.stream.binder.Binder;
import org.springframework.cloud.stream.binder.BinderFactory;
import org.springframework.cloud.stream.binder.ConsumerProperties;
import org.springframework.cloud.stream.binder.ProducerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
@@ -45,15 +47,15 @@ public class TestSupportBinderAutoConfiguration {
public BinderFactory binderFactory() {
return new BinderFactory() {
@Override
public Binder getBinder(String configurationName) {
return messageChannelBinder;
public <T> Binder<T, ? extends ConsumerProperties, ? extends ProducerProperties> getBinder(String configurationName, Class<? extends T> bindableType) {
return (Binder<T, ? extends ConsumerProperties, ? extends ProducerProperties>) messageChannelBinder;
}
};
}
@Bean
public MessageCollector messageCollector(BinderFactory<MessageChannel> binderFactory) {
return ((TestSupportBinder) binderFactory.getBinder(null)).messageCollector();
public MessageCollector messageCollector(BinderFactory binderFactory) {
return ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)).messageCollector();
}
}

View File

@@ -1,3 +1,19 @@
/*
* 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.test.aggregate;
import java.util.concurrent.TimeUnit;

View File

@@ -1,3 +1,19 @@
/*
* 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.test.aggregate;
import java.util.concurrent.TimeUnit;

View File

@@ -29,7 +29,6 @@ import org.springframework.cloud.stream.messaging.Processor;
import org.springframework.cloud.stream.test.binder.MessageCollector;
import org.springframework.integration.annotation.Transformer;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -46,7 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class ExampleTest {
@Autowired
private BinderFactory<MessageChannel> binderFactory;
private BinderFactory binderFactory;
@Autowired
private MessageCollector messageCollector;

View File

@@ -4,4 +4,5 @@
"http://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
<suppressions>
<suppress files="[\\/]src[\\/]main[\\/]java[\\/]org[\\/]springframework[\\/]cloud[\\/]stream[\\/]reactive[\\/]reactor[\\/]core[\\/]scheduler[\\/]" checks=".*" />
<suppress files="[\\/]src[\\/]test[\\/]java[\\/]" checks="AvoidStaticImport" />
</suppressions>

View File

@@ -1,151 +1,164 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<module name="Checker">
<!-- Root Checks -->
<module name="RegexpHeader">
<property name="headerFile" value="${checkstyle.header.file}" />
<property name="fileExtensions" value="java" />
</module>
<module name="NewlineAtEndOfFile">
<property name="lineSeparator" value="lf"/>
</module>
<!-- Root Checks -->
<module name="RegexpHeader">
<property name="headerFile" value="${checkstyle.header.file}"/>
<property name="fileExtensions" value="java"/>
</module>
<module name="NewlineAtEndOfFile">
<property name="lineSeparator" value="lf"/>
</module>
<module name="TreeWalker">
<module name="TreeWalker">
<!-- Annotations -->
<module name="AnnotationUseStyle">
<property name="elementStyle" value="compact" />
</module>
<module name="MissingOverride" />
<module name="PackageAnnotation" />
<module name="AnnotationLocation">
<property name="allowSamelineSingleParameterlessAnnotation"
value="false" />
</module>
<!-- Annotations -->
<module name="AnnotationUseStyle">
<property name="elementStyle" value="compact"/>
</module>
<module name="MissingOverride"/>
<module name="PackageAnnotation"/>
<module name="AnnotationLocation">
<property name="allowSamelineSingleParameterlessAnnotation"
value="false"/>
</module>
<!-- Block Checks -->
<module name="EmptyBlock">
<property name="option" value="text" />
</module>
<module name="LeftCurly" />
<module name="RightCurly">
<property name="option" value="alone" />
</module>
<module name="NeedBraces" />
<module name="AvoidNestedBlocks" />
<!-- Block Checks -->
<module name="EmptyBlock">
<property name="option" value="text"/>
</module>
<!--
<module name="LeftCurly"/>
<module name="RightCurly">
<property name="option" value="alone"/>
</module>
<module name="NeedBraces"/>
<module name="AvoidNestedBlocks"/>
-->
<!-- tabs instead of spaces -->
<module name="RegexpSinglelineJava">
<property name="format" value="^\t* "/>
<property name="message" value="Indent must use tab characters"/>
<property name="ignoreComments" value="true"/>
</module>
<!-- tabs instead of spaces -->
<module name="RegexpSinglelineJava">
<property name="format" value="^\t* "/>
<property name="message" value="Indent must use tab characters"/>
<property name="ignoreComments" value="true"/>
</module>
<!-- Class Design -->
<module name="FinalClass" />
<module name="InterfaceIsType" />
<module name="MutableException" />
<module name="InnerTypeLast" />
<module name="OneTopLevelClass" />
<!-- Class Design -->
<!--
<module name="FinalClass"/>
<module name="InterfaceIsType"/>
<module name="MutableException"/>
<module name="InnerTypeLast"/>
<module name="OneTopLevelClass"/>
-->
<!-- Coding -->
<module name="CovariantEquals" />
<module name="EmptyStatement" />
<module name="EqualsHashCode" />
<module name="InnerAssignment" />
<module name="SimplifyBooleanExpression" />
<module name="SimplifyBooleanReturn" />
<module name="StringLiteralEquality" />
<module name="NestedForDepth">
<property name="max" value="3" />
</module>
<module name="NestedIfDepth">
<property name="max" value="3" />
</module>
<module name="NestedTryDepth">
<property name="max" value="3" />
</module>
<module name="MultipleVariableDeclarations" />
<module name="RequireThis">
<property name="checkMethods" value="false" />
</module>
<module name="OneStatementPerLine" />
<module name="ExplicitInitialization"/>
<module name="ParameterAssignment"/>
<!-- Coding -->
<!--
<module name="CovariantEquals"/>
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="InnerAssignment"/>
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<module name="StringLiteralEquality"/>
<module name="NestedForDepth">
<property name="max" value="3"/>
</module>
<module name="NestedIfDepth">
<property name="max" value="3"/>
</module>
<module name="NestedTryDepth">
<property name="max" value="3"/>
</module>
<module name="MultipleVariableDeclarations"/>
<module name="RequireThis">
<property name="checkMethods" value="false"/>
</module>
<module name="OneStatementPerLine"/>
<module name="ExplicitInitialization"/>
<!-- Imports -->
<module name="AvoidStarImport"/>
<module name="AvoidStaticImport">
<property name="excludes"
value="org.junit.Assert.*,org.mockito.Mockito.*,org.mockito.Matchers.*,org.hamcrest.Matchers.*,org.assertj.core.api.Assertions.*"/>
</module>
<module name="FallThrough"/>
<module name="ImportOrder">
<property name="groups" value="java,/^javax?\./,*,org.springframework" />
<property name="ordered" value="true" />
<property name="separated" value="true" />
<property name="option" value="bottom" />
<property name="sortStaticImportsAlphabetically" value="true" />
</module>
<module name="IllegalImport">
<property name="illegalPkgs" value="org.slf4j"/>
</module>
<module name="RedundantImport"/>
<module name="ReturnCount">
<property name="max" value="0"/>
<property name="tokens" value="CTOR_DEF"/>
</module>
<module name="ReturnCount">
<property name="max" value="1"/>
<property name="tokens" value="LAMBDA"/>
</module>
<module name="ReturnCount">
<property name="max" value="3"/>
<property name="tokens" value="METHOD_DEF"/>
</module>
<module name="UnusedImports"/>
<module name="ParameterAssignment"/>
-->
<!-- Imports -->
<module name="AvoidStarImport"/>
<module name="AvoidStaticImport">
<property name="excludes"
value="org.junit.Assert.*,org.mockito.Mockito.*,org.mockito.Matchers.*,org.hamcrest.Matchers.*,org.assertj.core.api.Assertions.*"/>
</module>
<module name="FallThrough"/>
<module name="ImportOrder">
<property name="groups" value="java,/^javax?\./,*,org.springframework"/>
<property name="ordered" value="true"/>
<property name="separated" value="true"/>
<property name="option" value="bottom"/>
<property name="sortStaticImportsAlphabetically" value="true"/>
</module>
<module name="IllegalImport">
<property name="illegalPkgs" value="org.slf4j"/>
</module>
<module name="RedundantImport"/>
<module name="ReturnCount">
<property name="max" value="0"/>
<property name="tokens" value="CTOR_DEF"/>
</module>
<module name="ReturnCount">
<property name="max" value="1"/>
<property name="tokens" value="LAMBDA"/>
</module>
<!--
<module name="ReturnCount">
<property name="max" value="3"/>
<property name="tokens" value="METHOD_DEF"/>
</module>
-->
<module name="UnusedImports"/>
<!-- Miscellaneous -->
<module name="CommentsIndentation" />
<module name="UpperEll" />
<module name="ArrayTypeStyle" />
<module name="OuterTypeFilename" />
<!-- Miscellaneous -->
<module name="CommentsIndentation"/>
<module name="UpperEll"/>
<module name="ArrayTypeStyle"/>
<module name="OuterTypeFilename"/>
<!-- Modifiers -->
<module name="RedundantModifier" />
<!-- Modifiers -->
<module name="RedundantModifier"/>
<!-- Regexp -->
<module name="RegexpSinglelineJava">
<property name="format" value="^\t* +\t*\S" />
<property name="message"
value="Line has leading space characters; indentation should be performed with tabs only." />
<property name="ignoreComments" value="true" />
</module>
<module name="Regexp">
<property name="format" value="[ \t]+$" />
<property name="illegalPattern" value="true" />
<property name="message" value="Trailing whitespace" />
</module>
<module name="RegexpSinglelineJava">
<property name="maximum" value="0"/>
<property name="format" value="org\.junit\.Assert\.assert"/>
<property name="message"
value="Please use AssertJ imports."/>
<property name="ignoreComments" value="true"/>
</module>
<!-- Regexp -->
<module name="RegexpSinglelineJava">
<property name="format" value="^\t* +\t*\S"/>
<property name="message"
value="Line has leading space characters; indentation should be performed with tabs only."/>
<property name="ignoreComments" value="true"/>
</module>
<!--
<module name="Regexp">
<property name="format" value="[ \t]+$"/>
<property name="illegalPattern" value="true"/>
<property name="message" value="Trailing whitespace"/>
</module>
<!-- Whitespace -->
<module name="GenericWhitespace" />
<module name="MethodParamPad" />
<module name="NoWhitespaceAfter" >
<property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS, UNARY_PLUS, ARRAY_DECLARATOR"/>
</module>
<module name="NoWhitespaceBefore" />
<module name="ParenPad" />
<module name="TypecastParenPad" />
<module name="WhitespaceAfter" />
<module name="WhitespaceAround" />
</module>
<module name="RegexpSinglelineJava">
<property name="maximum" value="0"/>
<property name="format" value="org\.junit\.Assert\.assert"/>
<property name="message"
value="Please use AssertJ imports."/>
<property name="ignoreComments" value="true"/>
</module>
-->
<!-- Whitespace -->
<!--
<module name="GenericWhitespace"/>
<module name="MethodParamPad"/>
<module name="NoWhitespaceAfter">
<property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS, UNARY_PLUS, ARRAY_DECLARATOR"/>
</module>
<module name="NoWhitespaceBefore"/>
<module name="ParenPad"/>
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround"/>
-->
</module>
</module>

View File

@@ -1,3 +1,19 @@
/*
* 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.aggregate;
/**

View File

@@ -77,7 +77,7 @@ public class AggregateApplicationBuilder implements AggregateApplication, Applic
private boolean webEnvironment = true;
public AggregateApplicationBuilder(String... args) {
this(new Object[]{ ParentConfiguration.class }, args);
this(new Object[] { ParentConfiguration.class }, args);
}
public AggregateApplicationBuilder(Object source, String... args) {
@@ -184,8 +184,7 @@ public class AggregateApplicationBuilder implements AggregateApplication, Applic
Class<?> appToEmbed = appConfigurer.getApp();
// Always update namespace before preparing SharedChannelRegistry
if (appConfigurer.namespace == null) {
appConfigurer.namespace = AggregateApplicationUtils.getDefaultNamespace(appConfigurer.getApp().getName(),
i);
appConfigurer.namespace = AggregateApplicationUtils.getDefaultNamespace(appConfigurer.getApp().getName(), i);
}
appsToEmbed.put(appToEmbed, appConfigurer.namespace);
appConfigurers.put(appConfigurer, appConfigurer.namespace);
@@ -195,18 +194,21 @@ public class AggregateApplicationBuilder implements AggregateApplication, Applic
this.parentArgs.toArray(new String[0]), selfContained(), this.webEnvironment, this.headless);
}
else {
if (BeanFactoryUtils.beansOfTypeIncludingAncestors(this.parentContext, SharedChannelRegistry.class)
if (BeanFactoryUtils.beansOfTypeIncludingAncestors(this.parentContext, SharedBindingTargetRegistry.class)
.size() == 0) {
SharedBindingTargetRegistry sharedBindingTargetRegistry = new SharedBindingTargetRegistry();
this.parentContext.getBeanFactory().registerSingleton("sharedBindingTargetRegistry",
sharedBindingTargetRegistry);
this.parentContext.getBeanFactory().registerSingleton("sharedChannelRegistry",
new SharedChannelRegistry());
new SharedChannelRegistry(sharedBindingTargetRegistry));
}
}
SharedChannelRegistry sharedChannelRegistry = this.parentContext.getBean(SharedChannelRegistry.class);
AggregateApplicationUtils.prepareSharedChannelRegistry(sharedChannelRegistry, appsToEmbed);
SharedBindingTargetRegistry sharedBindingTargetRegistry = this.parentContext.getBean(SharedBindingTargetRegistry.class);
AggregateApplicationUtils.prepareSharedBindingTargetRegistry(sharedBindingTargetRegistry, appsToEmbed);
PropertySources propertySources = this.parentContext.getEnvironment()
.getPropertySources();
for (Map.Entry<AppConfigurer, String> appConfigurerEntry : appConfigurers
.entrySet()) {
for (Map.Entry<AppConfigurer, String> appConfigurerEntry : appConfigurers.entrySet()) {
AppConfigurer appConfigurer = appConfigurerEntry.getKey();
String namespace = appConfigurerEntry.getValue().toLowerCase();
Set<String> argsToUpdate = new LinkedHashSet<>();
@@ -219,7 +221,8 @@ public class AggregateApplicationBuilder implements AggregateApplication, Applic
// only update the values with the highest precedence level.
if (!relaxedNameKeyExists(entry.getKey(), argKeys)) {
String key = entry.getKey();
// in case of environment variables pass the lower-case property key
// in case of environment variables pass the lower-case property
// key
// as we pass the properties as command line properties
if (key.contains("_")) {
key = key.replace("_", "-").toLowerCase();
@@ -232,8 +235,9 @@ public class AggregateApplicationBuilder implements AggregateApplication, Applic
// Add the args that are set at the application level if they weren't
// overridden above from other property sources.
if (appConfigurer.getArgs() != null) {
for (String arg: appConfigurer.getArgs()) {
// use the key part left to the assignment and trimming the prefix `--`
for (String arg : appConfigurer.getArgs()) {
// use the key part left to the assignment and trimming the prefix
// `--`
String key = arg.substring(0, arg.indexOf("=")).substring(2);
if (!relaxedNameKeyExists(key, argKeys)) {
argsToUpdate.add(arg);
@@ -269,10 +273,9 @@ public class AggregateApplicationBuilder implements AggregateApplication, Applic
return false;
}
private ChildContextBuilder childContext(Class<?> app,
ConfigurableApplicationContext parentContext, String namespace) {
return new ChildContextBuilder(AggregateApplicationUtils.embedApp(parentContext,
namespace, app));
private ChildContextBuilder childContext(Class<?> app, ConfigurableApplicationContext parentContext,
String namespace) {
return new ChildContextBuilder(AggregateApplicationUtils.embedApp(parentContext, namespace, app));
}
public class SourceConfigurer extends AppConfigurer<SourceConfigurer> {
@@ -364,9 +367,8 @@ public class AggregateApplicationBuilder implements AggregateApplication, Applic
}
void embed() {
final ConfigurableApplicationContext childContext =
childContext(this.app, AggregateApplicationBuilder.this.parentContext,
this.namespace).args(this.args).config(this.names)
final ConfigurableApplicationContext childContext = childContext(this.app,
AggregateApplicationBuilder.this.parentContext, this.namespace).args(this.args).config(this.names)
.profiles(this.profiles).run();
// Register bindable proxies as beans so they can be queried for later
Map<String, BindableProxyFactory> bindableProxies = BeanFactoryUtils
@@ -456,10 +458,16 @@ public class AggregateApplicationBuilder implements AggregateApplication, Applic
@EnableAutoConfiguration
@EnableBinding
public static class ParentConfiguration {
@Bean
@ConditionalOnMissingBean(SharedBindingTargetRegistry.class)
public SharedBindingTargetRegistry sharedBindingTargetRegistry() {
return new SharedBindingTargetRegistry();
}
@Bean
@ConditionalOnMissingBean(SharedChannelRegistry.class)
public SharedChannelRegistry sharedChannelRegistry() {
return new SharedChannelRegistry();
public SharedChannelRegistry sharedChannelRegistry(SharedBindingTargetRegistry sharedBindingTargetRegistry) {
return new SharedChannelRegistry(sharedBindingTargetRegistry);
}
}

View File

@@ -21,6 +21,7 @@ import java.util.Map.Entry;
import org.springframework.boot.Banner.Mode;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.stream.internal.InternalPropertyNames;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.messaging.SubscribableChannel;
@@ -34,29 +35,20 @@ import org.springframework.messaging.SubscribableChannel;
*/
abstract class AggregateApplicationUtils {
private static final String SPRING_CLOUD_STREAM_INTERNAL_PREFIX = "spring.cloud.stream.internal";
public static final String INPUT_BINDING_NAME = "input";
private static final String CHANNEL_NAMESPACE_PROPERTY_NAME =
SPRING_CLOUD_STREAM_INTERNAL_PREFIX + ".channelNamespace";
public static final String OUTPUT_BINDING_NAME = "output";
private static final String SELF_CONTAINED_APP_PROPERTY_NAME =
SPRING_CLOUD_STREAM_INTERNAL_PREFIX + ".selfContained";
public static final String INPUT_CHANNEL_NAME = "input";
public static final String OUTPUT_CHANNEL_NAME = "output";
static ConfigurableApplicationContext createParentContext(Object[] sources, String[] args, final
boolean selfContained, boolean webEnvironment,
static ConfigurableApplicationContext createParentContext(Object[] sources,
String[] args, final boolean selfContained, boolean webEnvironment,
boolean headless) {
SpringApplicationBuilder aggregatorParentConfiguration = new SpringApplicationBuilder();
aggregatorParentConfiguration
.sources(sources)
.web(webEnvironment)
aggregatorParentConfiguration.sources(sources).web(webEnvironment)
.headless(headless)
.properties("spring.jmx.default-domain="
+ AggregateApplicationBuilder.ParentConfiguration.class.getName(),
SELF_CONTAINED_APP_PROPERTY_NAME + "=" + selfContained);
+ AggregateApplicationBuilder.ParentConfiguration.class.getName(),
InternalPropertyNames.SELF_CONTAINED_APP_PROPERTY_NAME + "="
+ selfContained);
return aggregatorParentConfiguration.run(args);
}
@@ -64,32 +56,31 @@ abstract class AggregateApplicationUtils {
return appClassName + "_" + index;
}
protected static SpringApplicationBuilder embedApp(
ConfigurableApplicationContext parentContext, String namespace,
Class<?> app) {
return new SpringApplicationBuilder(app)
.web(false)
.main(app)
.bannerMode(Mode.OFF)
return new SpringApplicationBuilder(app).web(false).main(app).bannerMode(Mode.OFF)
.properties("spring.jmx.default-domain=" + namespace)
.properties(CHANNEL_NAMESPACE_PROPERTY_NAME + "=" + namespace)
.registerShutdownHook(false)
.parent(parentContext);
.properties(
InternalPropertyNames.NAMESPACE_PROPERTY_NAME + "=" + namespace)
.registerShutdownHook(false).parent(parentContext);
}
static void prepareSharedChannelRegistry(SharedChannelRegistry sharedChannelRegistry,
static void prepareSharedBindingTargetRegistry(
SharedBindingTargetRegistry sharedBindingTargetRegistry,
LinkedHashMap<Class<?>, String> appsWithNamespace) {
int i = 0;
SubscribableChannel sharedChannel = null;
for (Entry<Class<?>, String> appEntry : appsWithNamespace.entrySet()) {
String namespace = appEntry.getValue();
if (i > 0) {
sharedChannelRegistry.register(namespace + "." + INPUT_CHANNEL_NAME, sharedChannel);
sharedBindingTargetRegistry.register(namespace + "." + INPUT_BINDING_NAME,
sharedChannel);
}
sharedChannel = new DirectChannel();
if (i < appsWithNamespace.size() - 1) {
sharedChannelRegistry.register(namespace + "." + OUTPUT_CHANNEL_NAME, sharedChannel);
sharedBindingTargetRegistry.register(namespace + "." + OUTPUT_BINDING_NAME,
sharedChannel);
}
i++;
}

View File

@@ -0,0 +1,55 @@
/*
* 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.aggregate;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentSkipListMap;
/**
* Stores binding targets shared by the components of an aggregate application.
*
* @author Marius Bogoevici
* @since 1.1.1
*/
public class SharedBindingTargetRegistry {
private Map<String, Object> sharedBindingTargets = new ConcurrentSkipListMap<>(String.CASE_INSENSITIVE_ORDER);
public <T> T get(String id, Class<T> bindingTargetType) {
Object sharedBindingTarget = this.sharedBindingTargets.get(id);
if (sharedBindingTarget == null) {
return null;
}
if (!bindingTargetType.isAssignableFrom(sharedBindingTarget.getClass())) {
throw new IllegalArgumentException("A shared " + bindingTargetType.getName() + " was requested, "
+ "but the existing shared target with id '" + id + "' is a " + sharedBindingTarget.getClass());
}
else {
return (T) sharedBindingTarget;
}
}
public void register(String id, Object bindingTarget) {
this.sharedBindingTargets.put(id, bindingTarget);
}
public Map<String, Object> getAll() {
return Collections.unmodifiableMap(this.sharedBindingTargets);
}
}

View File

@@ -17,31 +17,45 @@
package org.springframework.cloud.stream.aggregate;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentSkipListMap;
import org.springframework.messaging.MessageChannel;
/**
* A registry for channels that can be shared between modules, used for module aggregation.
* Wraps the {@link SharedBindingTargetRegistry} for access to {@link MessageChannel}
* instances. This class is provided as a convenience for users of
* {@link SharedChannelRegistry} in previous versions and will be removed in the future.
*
* @author Marius Bogoevici
* @deprecated in favour of {@link SharedBindingTargetRegistry}
*/
@Deprecated
public class SharedChannelRegistry {
/**
* A {@link Map} of channels, indexed by name. A channel's name may be prefixed by a namespace.
*/
private Map<String, MessageChannel> sharedChannels = new ConcurrentSkipListMap<>(String.CASE_INSENSITIVE_ORDER);
private final SharedBindingTargetRegistry sharedBindingTargetRegistry;
public MessageChannel get(String id) {
return sharedChannels.get(id);
public SharedChannelRegistry(SharedBindingTargetRegistry sharedBindingTargetRegistry) {
this.sharedBindingTargetRegistry = sharedBindingTargetRegistry;
}
public void register(String id, MessageChannel messageChannel) {
this.sharedChannels.put(id, messageChannel);
public MessageChannel get(String id) {
return this.sharedBindingTargetRegistry.get(id, MessageChannel.class);
}
public void register(String id, MessageChannel bindingTarget) {
this.sharedBindingTargetRegistry.register(id, bindingTarget);
}
public Map<String, MessageChannel> getAll() {
return Collections.unmodifiableMap(this.sharedChannels);
Map<String, Object> sharedBindingTargets = this.sharedBindingTargetRegistry.getAll();
Map<String, MessageChannel> sharedMessageChannels = new HashMap<>();
for (Map.Entry<String, Object> sharedBindingTargetEntry : sharedBindingTargets.entrySet()) {
if (MessageChannel.class.isAssignableFrom(sharedBindingTargetEntry.getValue().getClass())) {
sharedMessageChannels.put(sharedBindingTargetEntry.getKey(),
(MessageChannel) sharedBindingTargetEntry.getValue());
}
}
return Collections.unmodifiableMap(sharedMessageChannels);
}
}

View File

@@ -25,33 +25,33 @@ import java.lang.annotation.Target;
import org.springframework.cloud.stream.config.BinderFactoryConfiguration;
import org.springframework.cloud.stream.config.BindingBeansRegistrar;
import org.springframework.cloud.stream.config.ChannelBindingServiceConfiguration;
import org.springframework.cloud.stream.config.BindingServiceConfiguration;
import org.springframework.cloud.stream.config.SpelExpressionConverterConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.integration.config.EnableIntegration;
/**
* Enables the binding of components annotated with {@link Input} and {@link Output} to a broker, according to the list
* of interfaces passed as value to the annotation.
* Enables the binding of targets annotated with {@link Input} and {@link Output} to a
* broker, according to the list of interfaces passed as value to the annotation.
*
* @author Dave Syer
* @author Marius Bogoevici
* @author David Turanski
*/
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Configuration
@Import({ChannelBindingServiceConfiguration.class, BindingBeansRegistrar.class, BinderFactoryConfiguration.class,
SpelExpressionConverterConfiguration.class})
@Import({ BindingServiceConfiguration.class, BindingBeansRegistrar.class, BinderFactoryConfiguration.class,
SpelExpressionConverterConfiguration.class })
@EnableIntegration
public @interface EnableBinding {
/**
* A list of interfaces having methods annotated with {@link Input} and/or
* {@link Output} to indicate bindable components.
* {@link Output} to indicate binding targets.
*/
Class<?>[] value() default {};

View File

@@ -26,7 +26,7 @@ import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Qualifier;
/**
* Indicates that an input channel will be created by the framework.
* Indicates that an input binding target will be created by the framework.
*
* @author Dave Syer
* @author Marius Bogoevici

View File

@@ -26,7 +26,7 @@ import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Qualifier;
/**
* Indicates that an output channel will be created by the framework.
* Indicates that an output binding target will be created by the framework.
*
* @author Dave Syer
* @author Marius Bogoevici

View File

@@ -36,34 +36,35 @@ import org.springframework.messaging.handler.annotation.MessageMapping;
* <h3>Declarative mode</h3>
*
* A method is considered declarative if all its method parameter types and return type
* (if not void) are bound elements or conversion targets from bound elements via a
* (if not void) are binding targets or conversion targets from binding targets via a
* registered {@link StreamListenerParameterAdapter}.
*
* Only declarative methods can have bound elements or conversion targets as arguments and
* return type.
* Only declarative methods can have binding targets or conversion targets as arguments
* and return type.
*
* Declarative methods must specify what inputs and outputs correspond to their arguments
* and return type, and can do this in one of the following ways.
*
* <ul>
* <li>By using either the {@link Input} or {@link Output} annotation for each of the
* parameters and the {@link Output} annotation on the method for the return type (if applicable). The use
* of annotations in this case is mandatory. In this case the {@link StreamListener}
* annotation must not specify a value.</li>
* <li>By setting an {@link Input} bound target as the annotation value of {@link StreamListener}
* and using {@link org.springframework.messaging.handler.annotation.SendTo}</li> on the method
* for the return type (if applicable). In this case the method must have exactly one parameter, corresponding
* to an input.</li>
* parameters and the {@link Output} annotation on the method for the return type (if
* applicable). The use of annotations in this case is mandatory. In this case the
* {@link StreamListener} annotation must not specify a value.</li>
* <li>By setting an {@link Input} bound target as the annotation value of
* {@link StreamListener} and using
* {@link org.springframework.messaging.handler.annotation.SendTo}</li> on the method for
* the return type (if applicable). In this case the method must have exactly one
* parameter, corresponding to an input.</li>
* </ul>
*
* An example of declarative method signature using the former idiom is as follows:
*
* <pre>
* {@code
* @StreamListener
* &#64;StreamListener
* public @Output("joined") Flux<String> join(
* @Input("input1") Flux<String> input1,
* @Input("input2") Flux<String> input2) {
* &#64;Input("input1") Flux<String> input1,
* &#64;Input("input2") Flux<String> input2) {
* // ... join the two input streams via functional operators
* }
* }
@@ -73,8 +74,8 @@ import org.springframework.messaging.handler.annotation.MessageMapping;
*
* <pre>
* {@code
* @StreamListener(Processor.INPUT)
* @SendTo(Processor.OUTPUT)
* &#64;StreamListener(Processor.INPUT)
* &#64;SendTo(Processor.OUTPUT)
* public Flux<String> convert(Flux<String> input) {
* return input.map(String::toUppercase);
* }
@@ -90,7 +91,7 @@ import org.springframework.messaging.handler.annotation.MessageMapping;
* flexible signature, as described by {@link MessageMapping}.
*
* If the method returns a {@link org.springframework.messaging.Message}, the result will
* be automatically sent to a channel, as follows:
* be automatically sent to a binding target, as follows:
* <ul>
* <li>A result of the type {@link org.springframework.messaging.Message} will be sent
* as-is</li>
@@ -98,8 +99,8 @@ import org.springframework.messaging.handler.annotation.MessageMapping;
* {@link org.springframework.messaging.Message}</li>
* </ul>
*
* The target channel of the return message is determined by consulting in the following
* order:
* The output binding target where the return message is sent is determined by consulting
* in the following order:
* <ul>
* <li>The {@link org.springframework.messaging.MessageHeaders} of the resulting
* message.</li>
@@ -112,12 +113,8 @@ import org.springframework.messaging.handler.annotation.MessageMapping;
*
* <pre>
* {@code
* @StreamListener(Processor.INPUT)
* @SendTo(Processor.OUTPUT)
* public String convert(String input) {
* return input.toUppercase();
* }
* }
* @StreamListener(Processor.INPUT) @SendTo(Processor.OUTPUT) public String convert(String
* input) { return input.toUppercase(); } }
*
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan
@@ -132,7 +129,7 @@ import org.springframework.messaging.handler.annotation.MessageMapping;
public @interface StreamListener {
/**
* The name of the bound component (e.g. channel) that the method subscribes to.
* The name of the binding target (e.g. channel) that the method subscribes to.
*/
String value() default "";

View File

@@ -49,6 +49,8 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Base class for {@link Binder} implementations.
*
* @author David Turanski
* @author Gary Russell
* @author Ilayaperumal Gopinathan
@@ -59,7 +61,8 @@ public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends
implements ApplicationContextAware, InitializingBean, Binder<T, C, P> {
/**
* The delimiter between a group and index when constructing a binder consumer/producer.
* The delimiter between a group and index when constructing a binder
* consumer/producer.
*/
private static final String GROUP_INDEX_DELIMITER = ".";
@@ -78,15 +81,15 @@ public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends
/**
* For binder implementations that support a prefix, apply the prefix to the name.
* @param prefix the prefix.
* @param name the name.
* @param name the name.
*/
public static String applyPrefix(String prefix, String name) {
return prefix + name;
}
/**
* For binder implementations that support dead lettering, construct the name of the dead letter entity for the
* underlying pipe name.
* For binder implementations that support dead lettering, construct the name of the
* dead letter entity for the underlying pipe name.
* @param name the name.
*/
public static String constructDLQName(String name) {
@@ -125,8 +128,8 @@ public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends
}
/**
* Subclasses may implement this method to perform any necessary initialization.
* It will be invoked from {@link #afterPropertiesSet()} which is itself {@code final}.
* Subclasses may implement this method to perform any necessary initialization. It
* will be invoked from {@link #afterPropertiesSet()} which is itself {@code final}.
*/
protected void onInit() throws Exception {
// no-op default
@@ -135,8 +138,7 @@ public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends
@Override
public final Binding<T> bindConsumer(String name, String group, T target, C properties) {
if (StringUtils.isEmpty(group)) {
Assert.isTrue(!properties.isPartitioned(),
"A consumer group is required for a partitioned subscription");
Assert.isTrue(!properties.isPartitioned(), "A consumer group is required for a partitioned subscription");
}
return doBindConsumer(name, group, target, properties);
}
@@ -152,7 +154,7 @@ public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends
/**
* Construct a name comprised of the name and group.
* @param name the name.
* @param name the name.
* @param group the group.
* @return the constructed name.
*/
@@ -164,7 +166,8 @@ public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends
Object originalPayload = message.getPayload();
Object originalContentType = message.getHeaders().get(MessageHeaders.CONTENT_TYPE);
//Pass content type as String since some transport adapters will exclude CONTENT_TYPE Header otherwise
// Pass content type as String since some transport adapters will exclude
// CONTENT_TYPE Header otherwise
Object contentType = JavaClassMimeTypeConversion
.mimeTypeFromObject(originalPayload, ObjectUtils.nullSafeToString(originalContentType)).toString();
Object payload = serializePayloadIfNecessary(originalPayload);
@@ -191,8 +194,8 @@ public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends
return bos.toByteArray();
}
catch (IOException e) {
throw new SerializationFailedException("unable to serialize payload ["
+ originalPayload.getClass().getName() + "]", e);
throw new SerializationFailedException(
"unable to serialize payload [" + originalPayload.getClass().getName() + "]", e);
}
}
}
@@ -208,7 +211,8 @@ public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends
if (payload != null) {
messageValues.setPayload(payload);
Object originalContentType = messageValues.get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE);
// Reset content-type only if the original content type is not null (when receiving messages from
// Reset content-type only if the original content type is not null (when
// receiving messages from
// non-SCSt applications).
if (originalContentType != null) {
messageValues.put(MessageHeaders.CONTENT_TYPE, originalContentType);
@@ -236,8 +240,8 @@ public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends
return new String(bytes, "UTF-8");
}
catch (UnsupportedEncodingException e) {
throw new SerializationFailedException("unable to deserialize [java.lang.String]. Encoding not supported.",
e);
throw new SerializationFailedException(
"unable to deserialize [java.lang.String]. Encoding not supported.", e);
}
}
else {
@@ -253,7 +257,7 @@ public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends
}
catch (ClassNotFoundException e) {
throw new SerializationFailedException("unable to deserialize [" + className + "]. Class not found.",
e); //NOSONAR
e); // NOSONAR
}
catch (IOException e) {
throw new SerializationFailedException("unable to deserialize [" + className + "]", e);
@@ -307,7 +311,8 @@ public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends
if (mimeType == null) {
String modifiedClassName = className;
if (payload.getClass().isArray()) {
// Need to remove trailing ';' for an object array, e.g. "[Ljava.lang.String;" or multi-dimensional
// Need to remove trailing ';' for an object array, e.g.
// "[Ljava.lang.String;" or multi-dimensional
// "[[[Ljava.lang.String;"
if (modifiedClassName.endsWith(";")) {
modifiedClassName = modifiedClassName.substring(0, modifiedClassName.length() - 1);
@@ -327,7 +332,7 @@ public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends
if (className == null) {
return null;
}
//unwrap quotes if any
// unwrap quotes if any
className = className.replace("\"", "");
// restore trailing ';'

View File

@@ -17,9 +17,9 @@
package org.springframework.cloud.stream.binder;
/**
* A strategy interface used to bind an app interface to a logical name. The name is intended to identify a
* logical consumer or producer of messages. This may be a queue, a channel adapter, another message channel, a Spring
* bean, etc.
* A strategy interface used to bind an app interface to a logical name. The name is
* intended to identify a logical consumer or producer of messages. This may be a queue, a
* channel adapter, another message channel, a Spring bean, etc.
*
* @author Mark Fisher
* @author David Turanski
@@ -32,18 +32,21 @@ package org.springframework.cloud.stream.binder;
public interface Binder<T, C extends ConsumerProperties, P extends ProducerProperties> {
/**
* Bind the target component as a message consumer to the logical entity identified by the name.
* Bind the target component as a message consumer to the logical entity identified by
* the name.
* @param name the logical identity of the message source
* @param group the consumer group to which this consumer belongs - subscriptions are shared among consumers
* in the same group (a <code>null</code> or empty String, must be treated as an anonymous group that doesn't share
* the subscription with any other consumer)
* @param group the consumer group to which this consumer belongs - subscriptions are
* shared among consumers in the same group (a <code>null</code> or empty String, must
* be treated as an anonymous group that doesn't share the subscription with any other
* consumer)
* @param inboundBindTarget the app interface to be bound as a consumer
* @param consumerProperties the consumer properties
*/
Binding<T> bindConsumer(String name, String group, T inboundBindTarget, C consumerProperties);
/**
* Bind the target component as a message producer to the logical entity identified by the name.
* Bind the target component as a message producer to the logical entity identified by
* the name.
* @param name the logical identity of the message target
* @param outboundBindTarget the app interface to be bound as a producer
* @param producerProperties the producer properties

View File

@@ -19,8 +19,9 @@ package org.springframework.cloud.stream.binder;
import java.util.Properties;
/**
* Configuration for a binder instance, associating a {@link BinderType} with its configuration {@link Properties}.
* An application may contain multiple {@link BinderConfiguration}s per {@link BinderType}, when connecting to multiple
* Configuration for a binder instance, associating a {@link BinderType} with its
* configuration {@link Properties}. An application may contain multiple
* {@link BinderConfiguration}s per {@link BinderType}, when connecting to multiple
* systems of the same type.
*
* @author Marius Bogoevici
@@ -38,8 +39,10 @@ public class BinderConfiguration {
/**
* @param binderType the binder type used by this configuration
* @param properties the properties for setting up the binder
* @param inheritEnvironment whether the binder should inherit the environment of the application
* @param defaultCandidate whether the binder should be considered as a candidate when determining a default
* @param inheritEnvironment whether the binder should inherit the environment of the
* application
* @param defaultCandidate whether the binder should be considered as a candidate when
* determining a default
*/
public BinderConfiguration(BinderType binderType, Properties properties, boolean inheritEnvironment,
boolean defaultCandidate) {

View File

@@ -19,14 +19,16 @@ package org.springframework.cloud.stream.binder;
/**
* @author Marius Bogoevici
*/
public interface BinderFactory<T> {
public interface BinderFactory {
/**
* Returns the binder instance associated with the given configuration name. Instance caching is a requirement,
* and implementations must return the same instance on subsequent invocations with the same argument.
* Returns the binder instance associated with the given configuration name. Instance
* caching is a requirement, and implementations must return the same instance on
* subsequent invocations with the same arguments.
*
* @param configurationName the name of a binder configuration
* @return the binder instance
*/
Binder<T, ?, ?> getBinder(String configurationName);
<T> Binder<T, ? extends ConsumerProperties, ? extends ProducerProperties> getBinder(String configurationName,
Class<? extends T> bindableType);
}

View File

@@ -35,6 +35,7 @@ import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.stream.reflection.GenericsUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
@@ -48,14 +49,16 @@ import org.springframework.util.StringUtils;
* Default {@link BinderFactory} implementation.
* @author Marius Bogoevici
*/
public class DefaultBinderFactory<T> implements BinderFactory<T>, DisposableBean, ApplicationContextAware {
public class DefaultBinderFactory implements BinderFactory, DisposableBean, ApplicationContextAware {
private final Map<String, BinderConfiguration> binderConfigurations;
private final Map<String, BinderInstanceHolder<T>> binderInstanceCache = new HashMap<>();
private final Map<String, BinderInstanceHolder> binderInstanceCache = new HashMap<>();
private volatile ConfigurableApplicationContext context;
private Map<String, String> defaultBinderForBindingTargetType = new HashMap<>();
private volatile String defaultBinder;
private volatile CompositeHealthIndicator bindersHealthIndicator;
@@ -82,14 +85,15 @@ public class DefaultBinderFactory<T> implements BinderFactory<T>, DisposableBean
@Override
public void destroy() throws Exception {
for (Map.Entry<String, BinderInstanceHolder<T>> entry : this.binderInstanceCache.entrySet()) {
BinderInstanceHolder<T> binderInstanceHolder = entry.getValue();
for (Map.Entry<String, BinderInstanceHolder> entry : this.binderInstanceCache.entrySet()) {
BinderInstanceHolder binderInstanceHolder = entry.getValue();
binderInstanceHolder.getBinderContext().close();
}
this.defaultBinderForBindingTargetType.clear();
}
@Override
public synchronized Binder<T, ?, ?> getBinder(String name) {
public synchronized <T> Binder<T, ?, ?> getBinder(String name, Class<? extends T> bindingTargetType) {
String configurationName;
// Fall back to a default if no argument is provided
if (StringUtils.isEmpty(name)) {
@@ -106,37 +110,57 @@ public class DefaultBinderFactory<T> implements BinderFactory<T>, DisposableBean
}
}
if (defaultCandidateConfigurations.size() == 1) {
this.defaultBinder = defaultCandidateConfigurations.iterator().next();
configurationName = this.defaultBinder;
configurationName = defaultCandidateConfigurations.iterator().next();
this.defaultBinderForBindingTargetType.put(bindingTargetType.getName(), configurationName);
}
else {
if (defaultCandidateConfigurations.size() > 1) {
throw new IllegalStateException(
"A default binder has been requested, but there is more than one binder available: "
+ StringUtils.collectionToCommaDelimitedString(defaultCandidateConfigurations)
+ ", and no default binder has been set.");
List<String> candidatesForBindableType = new ArrayList<>();
for (String defaultCandidateConfiguration : defaultCandidateConfigurations) {
Binder<Object, ?, ?> binderInstance = getBinderInstance(defaultCandidateConfiguration);
Class<?> binderType = GenericsUtils.getParameterType(binderInstance.getClass(), Binder.class, 0);
if (binderType.isAssignableFrom(bindingTargetType)) {
candidatesForBindableType.add(defaultCandidateConfiguration);
}
}
if (candidatesForBindableType.size() == 1) {
configurationName = candidatesForBindableType.iterator().next();
this.defaultBinderForBindingTargetType.put(bindingTargetType.getName(), configurationName);
} else if (candidatesForBindableType.size() > 1) {
throw new IllegalStateException(
"A default binder has been requested, but there is more than one binder available for '"
+ bindingTargetType.getName() + "' : "
+ StringUtils.collectionToCommaDelimitedString(candidatesForBindableType)
+ ", and no default binder has been set.");
} else {
throw new IllegalStateException("A default binder has been requested, but none of the " +
"registered binders can bind a '" + bindingTargetType + "': "
+ StringUtils.collectionToCommaDelimitedString(defaultCandidateConfigurations));
}
}
else {
throw new IllegalStateException(
"A default binder has been requested, but there there is no binder available");
throw new IllegalArgumentException(
"A default binder has been requested, but there is no default available");
}
}
}
else {
if (StringUtils.hasText(this.defaultBinder)) {
configurationName = this.defaultBinder;
}
else {
throw new IllegalStateException(
"A default binder has been requested, but there is more than one binder available: "
+ StringUtils.collectionToCommaDelimitedString(this.binderConfigurations.keySet())
+ ", and no default binder has been set.");
}
configurationName = this.defaultBinder;
}
}
else {
configurationName = name;
}
Binder<T, ?, ?> binderInstance = getBinderInstance(configurationName);
if (!(GenericsUtils.getParameterType(binderInstance.getClass(), Binder.class, 0)
.isAssignableFrom(bindingTargetType))) {
throw new IllegalStateException(
"The binder '" + configurationName + "' cannot bind a " + bindingTargetType.getName());
}
return binderInstance;
}
private <T> Binder<T, ?, ?> getBinderInstance(String configurationName) {
if (!this.binderInstanceCache.containsKey(configurationName)) {
BinderConfiguration binderConfiguration = this.binderConfigurations.get(configurationName);
if (binderConfiguration == null) {
@@ -196,28 +220,27 @@ public class DefaultBinderFactory<T> implements BinderFactory<T>, DisposableBean
healthAggregator, indicators);
this.bindersHealthIndicator.addHealthIndicator(configurationName, binderHealthIndicator);
}
this.binderInstanceCache.put(configurationName, new BinderInstanceHolder<>(binder,
this.binderInstanceCache.put(configurationName, new BinderInstanceHolder(binder,
binderProducingContext));
}
return this.binderInstanceCache.get(configurationName).getBinderInstance();
return (Binder<T, ?, ?>) this.binderInstanceCache.get(configurationName).getBinderInstance();
}
/**
* Utility class for storing {@link Binder} instances, along with their associated contexts.
* @param <T>
*/
private static final class BinderInstanceHolder<T> {
private static final class BinderInstanceHolder {
private final Binder<T, ?, ?> binderInstance;
private final Binder<?, ?, ?> binderInstance;
private final ConfigurableApplicationContext binderContext;
private BinderInstanceHolder(Binder<T, ?, ?> binderInstance, ConfigurableApplicationContext binderContext) {
private BinderInstanceHolder(Binder<?, ?, ?> binderInstance, ConfigurableApplicationContext binderContext) {
this.binderInstance = binderInstance;
this.binderContext = binderContext;
}
public Binder<T, ?, ?> getBinderInstance() {
public Binder<?, ?, ?> getBinderInstance() {
return this.binderInstance;
}

View File

@@ -0,0 +1,46 @@
/*
* 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.binding;
import org.springframework.util.Assert;
/**
* A {@link BindingTargetFactory} implementation that restricts the type of binding target
* to a specified class and its supertypes.
*
* @author Marius Bogoevici
*/
public abstract class AbstractBindingTargetFactory<T> implements BindingTargetFactory {
private final Class<T> bindingTargetType;
protected AbstractBindingTargetFactory(Class<T> bindingTargetType) {
Assert.notNull(bindingTargetType, "The binding target type cannot be null");
this.bindingTargetType = bindingTargetType;
}
@Override
public final boolean canCreate(Class<?> clazz) {
return clazz.isAssignableFrom(this.bindingTargetType);
}
@Override
public abstract T createInput(String name);
@Override
public abstract T createOutput(String name);
}

View File

@@ -30,22 +30,22 @@ public interface Bindable {
/**
* Binds all the inputs associated with this instance.
*/
void bindInputs(ChannelBindingService adapter);
void bindInputs(BindingService adapter);
/**
* Binds all the outputs associated with this instance.
*/
void bindOutputs(ChannelBindingService adapter);
void bindOutputs(BindingService adapter);
/**
* Unbinds all the inputs associated with this instance.
*/
void unbindInputs(ChannelBindingService adapter);
void unbindInputs(BindingService adapter);
/**
* Unbinds all the outputs associated with this instance.
*/
void unbindOutputs(ChannelBindingService adapter);
void unbindOutputs(BindingService adapter);
/**
* Enumerates all the input binding names.

View File

@@ -28,19 +28,19 @@ public class BindableAdapter implements Bindable {
@Override
public void bindInputs(ChannelBindingService adapter) {
public void bindInputs(BindingService adapter) {
}
@Override
public void bindOutputs(ChannelBindingService adapter) {
public void bindOutputs(BindingService adapter) {
}
@Override
public void unbindInputs(ChannelBindingService adapter) {
public void unbindInputs(BindingService adapter) {
}
@Override
public void unbindOutputs(ChannelBindingService adapter) {
public void unbindOutputs(BindingService adapter) {
}
@Override

View File

@@ -1,44 +0,0 @@
/*
* Copyright 2015 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.binding;
import org.springframework.messaging.SubscribableChannel;
/**
* Defines methods to create/configure the {@link org.springframework.messaging.MessageChannel}s defined
* in {@link org.springframework.cloud.stream.annotation.EnableBinding}.
* @author Ilayaperumal Gopinathan
*/
public interface BindableChannelFactory {
/**
* Create an input {@link SubscribableChannel} that will be bound via
* the message channel {@link org.springframework.cloud.stream.binder.Binder}.
* @param name name of the message channel
* @return subscribable message channel
*/
SubscribableChannel createInputChannel(String name);
/**
* Create an output {@link SubscribableChannel} that will be bound via
* the message channel {@link org.springframework.cloud.stream.binder.Binder}.
* @param name name of the message channel
* @return subscribable message channel
*/
SubscribableChannel createOutputChannel(String name);
}

View File

@@ -17,7 +17,9 @@
package org.springframework.cloud.stream.binding;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -31,15 +33,15 @@ import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.aggregate.SharedChannelRegistry;
import org.springframework.cloud.stream.aggregate.SharedBindingTargetRegistry;
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.internal.InternalPropertyNames;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* {@link FactoryBean} for instantiating the interfaces specified via
@@ -55,26 +57,22 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
private static Log log = LogFactory.getLog(BindableProxyFactory.class);
private static final String SPRING_CLOUD_STREAM_INTERNAL_PREFIX = "spring.cloud.stream.internal";
private static final String CHANNEL_NAMESPACE_PROPERTY_NAME = SPRING_CLOUD_STREAM_INTERNAL_PREFIX + ".channelNamespace";
@Value("${" + CHANNEL_NAMESPACE_PROPERTY_NAME + ":}")
private String channelNamespace;
@Autowired
private BindableChannelFactory channelFactory;
@Value("${" + InternalPropertyNames.NAMESPACE_PROPERTY_NAME + ":}")
private String namespace;
@Autowired(required = false)
private SharedChannelRegistry sharedChannelRegistry;
private SharedBindingTargetRegistry sharedBindingTargetRegistry;
@Autowired
private Map<String, BindingTargetFactory> bindingTargetFactories;
private Class<?> type;
private Object proxy;
private Map<String, ChannelHolder> inputHolders = new HashMap<>();
private Map<String, BoundTargetHolder> inputHolders = new HashMap<>();
private Map<String, ChannelHolder> outputHolders = new HashMap<>();
private Map<String, BoundTargetHolder> outputHolders = new HashMap<>();
public BindableProxyFactory(Class<?> type) {
this.type = type;
@@ -82,41 +80,40 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
@Override
public synchronized Object invoke(MethodInvocation invocation) throws Throwable {
MessageChannel messageChannel = null;
Method method = invocation.getMethod();
if (MessageChannel.class.isAssignableFrom(method.getReturnType())) {
Input input = AnnotationUtils.findAnnotation(method, Input.class);
if (input != null) {
String name = BindingBeanDefinitionRegistryUtils.getChannelName(input, method);
messageChannel = this.inputHolders.get(name).getMessageChannel();
}
Input input = AnnotationUtils.findAnnotation(method, Input.class);
if (input != null) {
String name = BindingBeanDefinitionRegistryUtils.getBindingTargetName(input, method);
return this.inputHolders.get(name).getBoundTarget();
}
else {
Output output = AnnotationUtils.findAnnotation(method, Output.class);
if (output != null) {
String name = BindingBeanDefinitionRegistryUtils.getChannelName(output, method);
messageChannel = this.outputHolders.get(name).getMessageChannel();
String name = BindingBeanDefinitionRegistryUtils.getBindingTargetName(output, method);
return this.outputHolders.get(name).getBoundTarget();
}
}
//ignore
return messageChannel;
return null;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(BindableProxyFactory.this.channelFactory, "Channel Factory cannot be null");
Assert.notEmpty(BindableProxyFactory.this.bindingTargetFactories, "'bindingTargetFactories' cannot be empty");
ReflectionUtils.doWithMethods(this.type, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException {
Input input = AnnotationUtils.findAnnotation(method, Input.class);
if (input != null) {
String name = BindingBeanDefinitionRegistryUtils.getChannelName(input, method);
validateChannelType(method.getReturnType());
MessageChannel sharedChannel = locateSharedChannel(name);
if (sharedChannel == null) {
BindableProxyFactory.this.inputHolders.put(name, new ChannelHolder(
BindableProxyFactory.this.channelFactory.createInputChannel(name), true));
String name = BindingBeanDefinitionRegistryUtils.getBindingTargetName(input, method);
Class<?> returnType = method.getReturnType();
Object sharedBindingTarget = locateSharedBindingTarget(name, returnType);
if (sharedBindingTarget != null) {
BindableProxyFactory.this.inputHolders.put(name,
new BoundTargetHolder(sharedBindingTarget, false));
}
else {
BindableProxyFactory.this.inputHolders.put(name, new ChannelHolder(sharedChannel, false));
BindableProxyFactory.this.inputHolders.put(name,
new BoundTargetHolder(getBindingTargetFactory(returnType).createInput(name), true));
}
}
}
@@ -126,35 +123,55 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
public void doWith(Method method) throws IllegalArgumentException {
Output output = AnnotationUtils.findAnnotation(method, Output.class);
if (output != null) {
String name = BindingBeanDefinitionRegistryUtils.getChannelName(output, method);
validateChannelType(method.getReturnType());
MessageChannel sharedChannel = locateSharedChannel(name);
if (sharedChannel == null) {
BindableProxyFactory.this.outputHolders.put(name, new ChannelHolder(
BindableProxyFactory.this.channelFactory.createOutputChannel(name), true));
String name = BindingBeanDefinitionRegistryUtils.getBindingTargetName(output, method);
Class<?> returnType = method.getReturnType();
Object sharedBindingTarget = locateSharedBindingTarget(name, returnType);
if (sharedBindingTarget != null) {
BindableProxyFactory.this.outputHolders.put(name,
new BoundTargetHolder(sharedBindingTarget, false));
}
else {
BindableProxyFactory.this.outputHolders.put(name, new ChannelHolder(sharedChannel, false));
BindableProxyFactory.this.outputHolders.put(name,
new BoundTargetHolder(getBindingTargetFactory(returnType).createOutput(name), true));
}
}
}
});
}
private void validateChannelType(Class<?> channelType) {
Assert.isTrue(SubscribableChannel.class.equals(channelType) || MessageChannel.class.equals(channelType),
"A bound channel should be either a '" + MessageChannel.class.getName() + "', " +
" or a '" + SubscribableChannel.class.getName() + "'");
private BindingTargetFactory getBindingTargetFactory(Class<?> bindingTargetType) {
List<String> candidateBindingTargetFactories = new ArrayList<>();
for (Map.Entry<String, BindingTargetFactory> bindingTargetFactoryEntry : this.bindingTargetFactories
.entrySet()) {
if (bindingTargetFactoryEntry.getValue().canCreate(bindingTargetType)) {
candidateBindingTargetFactories.add(bindingTargetFactoryEntry.getKey());
}
}
if (candidateBindingTargetFactories.size() == 1) {
return this.bindingTargetFactories.get(candidateBindingTargetFactories.get(0));
}
else {
if (candidateBindingTargetFactories.size() == 0) {
throw new IllegalStateException("No factory found for binding target type: "
+ bindingTargetType.getName() + " among registered factories: "
+ StringUtils.collectionToCommaDelimitedString(bindingTargetFactories.keySet()));
}
else {
throw new IllegalStateException(
"Multiple factories found for binding target type: " + bindingTargetType.getName() + ": "
+ StringUtils.collectionToCommaDelimitedString(candidateBindingTargetFactories));
}
}
}
private MessageChannel locateSharedChannel(String name) {
return this.sharedChannelRegistry != null ?
this.sharedChannelRegistry.get(getNamespacePrefixedChannelName(name)) : null;
private <T> T locateSharedBindingTarget(String name, Class<T> bindingTargetType) {
return this.sharedBindingTargetRegistry != null
? this.sharedBindingTargetRegistry.get(getNamespacePrefixedBindingTargetName(name), bindingTargetType)
: null;
}
private String getNamespacePrefixedChannelName(String name) {
return this.channelNamespace + "." + name;
private String getNamespacePrefixedBindingTargetName(String name) {
return this.namespace + "." + name;
}
@Override
@@ -177,65 +194,67 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
}
@Override
public void bindInputs(ChannelBindingService channelBindingService) {
public void bindInputs(BindingService bindingService) {
if (log.isDebugEnabled()) {
log.debug(String.format("Binding inputs for %s:%s", this.channelNamespace, this.type));
log.debug(String.format("Binding inputs for %s:%s", this.namespace, this.type));
}
for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.inputHolders.entrySet()) {
String inputChannelName = channelHolderEntry.getKey();
ChannelHolder channelHolder = channelHolderEntry.getValue();
if (channelHolder.isBindable()) {
for (Map.Entry<String, BoundTargetHolder> boundTargetHolderEntry : this.inputHolders.entrySet()) {
String inputTargetName = boundTargetHolderEntry.getKey();
BoundTargetHolder boundTargetHolder = boundTargetHolderEntry.getValue();
if (boundTargetHolder.isBindable()) {
if (log.isDebugEnabled()) {
log.debug(String.format("Binding %s:%s:%s", this.channelNamespace, this.type, inputChannelName));
log.debug(String.format("Binding %s:%s:%s", this.namespace, this.type, inputTargetName));
}
channelBindingService.bindConsumer(channelHolder.getMessageChannel(), inputChannelName);
bindingService.bindConsumer(boundTargetHolder.getBoundTarget(), inputTargetName);
}
}
}
@Override
public void bindOutputs(ChannelBindingService channelBindingService) {
public void bindOutputs(BindingService bindingService) {
if (log.isDebugEnabled()) {
log.debug(String.format("Binding outputs for %s:%s", this.channelNamespace, this.type));
log.debug(String.format("Binding outputs for %s:%s", this.namespace, this.type));
}
for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.outputHolders.entrySet()) {
ChannelHolder channelHolder = channelHolderEntry.getValue();
String outputChannelName = channelHolderEntry.getKey();
if (channelHolderEntry.getValue().isBindable()) {
for (Map.Entry<String, BoundTargetHolder> boundTargetHolderEntry : this.outputHolders.entrySet()) {
BoundTargetHolder boundTargetHolder = boundTargetHolderEntry.getValue();
String outputTargetName = boundTargetHolderEntry.getKey();
if (boundTargetHolderEntry.getValue().isBindable()) {
if (log.isDebugEnabled()) {
log.debug(String.format("Binding %s:%s:%s", this.channelNamespace, this.type, outputChannelName));
log.debug(String.format("Binding %s:%s:%s", this.namespace, this.type, outputTargetName));
}
channelBindingService.bindProducer(channelHolder.getMessageChannel(), outputChannelName);
bindingService.bindProducer(boundTargetHolder.getBoundTarget(), outputTargetName);
}
}
}
@Override
public void unbindInputs(ChannelBindingService channelBindingService) {
public void unbindInputs(BindingService bindingService) {
if (log.isDebugEnabled()) {
log.debug(String.format("Unbinding inputs for %s:%s", this.channelNamespace, this.type));
log.debug(String.format("Unbinding inputs for %s:%s", this.namespace, this.type));
}
for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.inputHolders.entrySet()) {
if (channelHolderEntry.getValue().isBindable()) {
for (Map.Entry<String, BoundTargetHolder> boundTargetHolderEntry : this.inputHolders.entrySet()) {
if (boundTargetHolderEntry.getValue().isBindable()) {
if (log.isDebugEnabled()) {
log.debug(String.format("Unbinding %s:%s:%s", this.channelNamespace, this.type, channelHolderEntry.getKey()));
log.debug(String.format("Unbinding %s:%s:%s", this.namespace, this.type,
boundTargetHolderEntry.getKey()));
}
channelBindingService.unbindConsumers(channelHolderEntry.getKey());
bindingService.unbindConsumers(boundTargetHolderEntry.getKey());
}
}
}
@Override
public void unbindOutputs(ChannelBindingService channelBindingService) {
public void unbindOutputs(BindingService bindingService) {
if (log.isDebugEnabled()) {
log.debug(String.format("Unbinding outputs for %s:%s", this.channelNamespace, this.type));
log.debug(String.format("Unbinding outputs for %s:%s", this.namespace, this.type));
}
for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.outputHolders.entrySet()) {
if (channelHolderEntry.getValue().isBindable()) {
for (Map.Entry<String, BoundTargetHolder> boundTargetHolderEntry : this.outputHolders.entrySet()) {
if (boundTargetHolderEntry.getValue().isBindable()) {
if (log.isDebugEnabled()) {
log.debug(String.format("Binding %s:%s:%s", this.channelNamespace, this.type, channelHolderEntry.getKey()));
log.debug(String.format("Binding %s:%s:%s", this.namespace, this.type,
boundTargetHolderEntry.getKey()));
}
channelBindingService.unbindProducers(channelHolderEntry.getKey());
bindingService.unbindProducers(boundTargetHolderEntry.getKey());
}
}
}
@@ -251,22 +270,22 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
}
/**
* Holds information about the channels exposed by the interface proxy, as well as
* their status.
* Holds information about the binding targets exposed by the interface proxy, as well
* as their status.
*/
private final class ChannelHolder {
private final class BoundTargetHolder {
private MessageChannel messageChannel;
private Object boundTarget;
private boolean bindable;
private ChannelHolder(MessageChannel messageChannel, boolean bindable) {
this.messageChannel = messageChannel;
private BoundTargetHolder(Object boundTarget, boolean bindable) {
this.boundTarget = boundTarget;
this.bindable = bindable;
}
public MessageChannel getMessageChannel() {
return this.messageChannel;
public Object getBoundTarget() {
return this.boundTarget;
}
public boolean isBindable() {

View File

@@ -19,7 +19,7 @@ package org.springframework.cloud.stream.binding;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.cloud.stream.binder.Binding;
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
import org.springframework.cloud.stream.config.BindingServiceProperties;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.core.BeanFactoryMessageChannelDestinationResolver;
import org.springframework.messaging.core.DestinationResolutionException;
@@ -37,22 +37,23 @@ import org.springframework.util.ObjectUtils;
*/
public class BinderAwareChannelResolver extends BeanFactoryMessageChannelDestinationResolver {
private final ChannelBindingService channelBindingService;
private final BindingService bindingService;
private final BindableChannelFactory bindableChannelFactory;
private final AbstractBindingTargetFactory<? extends MessageChannel> bindingTargetFactory;
private final DynamicDestinationsBindable dynamicDestinationsBindable;
private ConfigurableListableBeanFactory beanFactory;
@SuppressWarnings("unchecked")
public BinderAwareChannelResolver(ChannelBindingService channelBindingService,
BindableChannelFactory bindableChannelFactory, DynamicDestinationsBindable dynamicDestinationsBindable) {
public BinderAwareChannelResolver(BindingService bindingService,
AbstractBindingTargetFactory<? extends MessageChannel> bindingTargetFactory,
DynamicDestinationsBindable dynamicDestinationsBindable) {
this.dynamicDestinationsBindable = dynamicDestinationsBindable;
Assert.notNull(channelBindingService, "'channelBindingService' cannot be null");
Assert.notNull(bindableChannelFactory, "'bindableChannelFactory' cannot be null");
this.channelBindingService = channelBindingService;
this.bindableChannelFactory = bindableChannelFactory;
Assert.notNull(bindingService, "'bindingService' cannot be null");
Assert.notNull(bindingTargetFactory, "'bindingTargetFactory' cannot be null");
this.bindingService = bindingService;
this.bindingTargetFactory = bindingTargetFactory;
}
@Override
@@ -76,18 +77,18 @@ public class BinderAwareChannelResolver extends BeanFactoryMessageChannelDestina
synchronized (this) {
if (this.beanFactory != null) {
String[] dynamicDestinations = null;
ChannelBindingServiceProperties channelBindingServiceProperties =
this.channelBindingService.getChannelBindingServiceProperties();
if (channelBindingServiceProperties != null) {
dynamicDestinations = channelBindingServiceProperties.getDynamicDestinations();
BindingServiceProperties bindingServiceProperties = this.bindingService
.getBindingServiceProperties();
if (bindingServiceProperties != null) {
dynamicDestinations = bindingServiceProperties.getDynamicDestinations();
}
boolean dynamicAllowed = ObjectUtils.isEmpty(dynamicDestinations)
|| ObjectUtils.containsElement(dynamicDestinations, channelName);
if (dynamicAllowed) {
channel = this.bindableChannelFactory.createOutputChannel(channelName);
channel = this.bindingTargetFactory.createOutput(channelName);
this.beanFactory.registerSingleton(channelName, channel);
channel = (MessageChannel) this.beanFactory.initializeBean(channel, channelName);
Binding<MessageChannel> binding = this.channelBindingService.bindProducer(channel, channelName);
Binding<MessageChannel> binding = this.bindingService.bindProducer(channel, channelName);
this.dynamicDestinationsBindable.addOutputBinding(channelName, binding);
}
else {

View File

@@ -32,88 +32,79 @@ import org.springframework.util.ReflectionUtils.MethodCallback;
import org.springframework.util.StringUtils;
/**
* Utility class for registering bean definitions for message channels.
* Utility class for registering bean definitions for binding targets.
*
* @author Marius Bogoevici
* @author Dave Syer
*/
public abstract class BindingBeanDefinitionRegistryUtils {
public static void registerInputChannelBeanDefinition(String qualifierValue,
String name, String channelInterfaceBeanName,
String channelInterfaceMethodName, BeanDefinitionRegistry registry) {
registerChannelBeanDefinition(Input.class, qualifierValue, name,
channelInterfaceBeanName, channelInterfaceMethodName, registry);
}
public static void registerOutputChannelBeanDefinition(String qualifierValue,
String name, String channelInterfaceBeanName,
String channelInterfaceMethodName, BeanDefinitionRegistry registry) {
registerChannelBeanDefinition(Output.class, qualifierValue, name,
channelInterfaceBeanName, channelInterfaceMethodName, registry);
}
private static void registerChannelBeanDefinition(
Class<? extends Annotation> qualifier, String qualifierValue, String name,
String channelInterfaceBeanName, String channelInterfaceMethodName,
public static void registerInputBindingTargetBeanDefinition(String qualifierValue, String name,
String bindingTargetInterfaceBeanName, String bindingTargetInterfaceMethodName,
BeanDefinitionRegistry registry) {
registerBindingTargetBeanDefinition(Input.class, qualifierValue, name, bindingTargetInterfaceBeanName,
bindingTargetInterfaceMethodName, registry);
}
public static void registerOutputBindingTargetBeanDefinition(String qualifierValue, String name,
String bindingTargetInterfaceBeanName, String bindingTargetInterfaceMethodName,
BeanDefinitionRegistry registry) {
registerBindingTargetBeanDefinition(Output.class, qualifierValue, name, bindingTargetInterfaceBeanName,
bindingTargetInterfaceMethodName, registry);
}
private static void registerBindingTargetBeanDefinition(Class<? extends Annotation> qualifier,
String qualifierValue, String name, String bindingTargetInterfaceBeanName,
String bindingTargetInterfaceMethodName, BeanDefinitionRegistry registry) {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
rootBeanDefinition.setFactoryBeanName(channelInterfaceBeanName);
rootBeanDefinition.setUniqueFactoryMethodName(channelInterfaceMethodName);
rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(qualifier,
qualifierValue));
rootBeanDefinition.setFactoryBeanName(bindingTargetInterfaceBeanName);
rootBeanDefinition.setUniqueFactoryMethodName(bindingTargetInterfaceMethodName);
rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(qualifier, qualifierValue));
registry.registerBeanDefinition(name, rootBeanDefinition);
}
public static void registerChannelBeanDefinitions(Class<?> type,
final String channelInterfaceBeanName, final BeanDefinitionRegistry registry) {
public static void registerBindingTargetBeanDefinitions(Class<?> type, final String bindingTargetInterfaceBeanName,
final BeanDefinitionRegistry registry) {
ReflectionUtils.doWithMethods(type, new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException,
IllegalAccessException {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Input input = AnnotationUtils.findAnnotation(method, Input.class);
if (input != null) {
String name = getChannelName(input, method);
registerInputChannelBeanDefinition(input.value(), name,
channelInterfaceBeanName, method.getName(), registry);
String name = getBindingTargetName(input, method);
registerInputBindingTargetBeanDefinition(input.value(), name, bindingTargetInterfaceBeanName,
method.getName(), registry);
}
Output output = AnnotationUtils.findAnnotation(method, Output.class);
if (output != null) {
String name = getChannelName(output, method);
registerOutputChannelBeanDefinition(output.value(), name,
channelInterfaceBeanName, method.getName(), registry);
String name = getBindingTargetName(output, method);
registerOutputBindingTargetBeanDefinition(output.value(), name, bindingTargetInterfaceBeanName,
method.getName(), registry);
}
}
});
}
public static void registerChannelsQualifiedBeanDefinitions(Class<?> parent,
Class<?> type, final BeanDefinitionRegistry registry) {
public static void registerBindingTargetsQualifiedBeanDefinitions(Class<?> parent, Class<?> type,
final BeanDefinitionRegistry registry) {
if (type.isInterface()) {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(
BindableProxyFactory.class);
rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(
Bindings.class, parent));
rootBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(
type);
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(BindableProxyFactory.class);
rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(Bindings.class, parent));
rootBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(type);
registry.registerBeanDefinition(type.getName(), rootBeanDefinition);
}
else {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(type);
rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(
Bindings.class, parent));
rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(Bindings.class, parent));
registry.registerBeanDefinition(type.getName(), rootBeanDefinition);
}
}
public static String getChannelName(Annotation annotation, Method method) {
Map<String, Object> attrs = AnnotationUtils.getAnnotationAttributes(annotation,
false);
if (attrs.containsKey("value")
&& StringUtils.hasText((CharSequence) attrs.get("value"))) {
public static String getBindingTargetName(Annotation annotation, Method method) {
Map<String, Object> attrs = AnnotationUtils.getAnnotationAttributes(annotation, false);
if (attrs.containsKey("value") && StringUtils.hasText((CharSequence) attrs.get("value"))) {
return (String) attrs.get("value");
}
return method.getName();

View File

@@ -0,0 +1,180 @@
/*
* Copyright 2015-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.binding;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.cloud.stream.binder.Binder;
import org.springframework.cloud.stream.binder.BinderFactory;
import org.springframework.cloud.stream.binder.Binding;
import org.springframework.cloud.stream.binder.ConsumerProperties;
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
import org.springframework.cloud.stream.binder.ExtendedPropertiesBinder;
import org.springframework.cloud.stream.binder.ProducerProperties;
import org.springframework.cloud.stream.config.BindingServiceProperties;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.beanvalidation.CustomValidatorBean;
/**
* Handles binding of input/output targets by delegating to an underlying
* {@link Binder}.
*
* @author Mark Fisher
* @author Dave Syer
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan
* @author Gary Russell
*/
public class BindingService {
private final CustomValidatorBean validator;
private final Log log = LogFactory.getLog(BindingService.class);
private BinderFactory binderFactory;
private final BindingServiceProperties bindingServiceProperties;
private final Map<String, Binding<?>> producerBindings = new HashMap<>();
private final Map<String, List<Binding<?>>> consumerBindings = new HashMap<>();
public BindingService(
BindingServiceProperties bindingServiceProperties,
BinderFactory binderFactory) {
this.bindingServiceProperties = bindingServiceProperties;
this.binderFactory = binderFactory;
this.validator = new CustomValidatorBean();
this.validator.afterPropertiesSet();
}
@SuppressWarnings("unchecked")
public <T> Collection<Binding<T>> bindConsumer(T input, String inputName) {
String bindingTarget = this.bindingServiceProperties
.getBindingDestination(inputName);
String[] bindingTargets = StringUtils
.commaDelimitedListToStringArray(bindingTarget);
Collection<Binding<T>> bindings = new ArrayList<>();
Binder<T, ConsumerProperties, ?> binder = (Binder<T, ConsumerProperties, ?>) getBinder(
inputName, input.getClass());
ConsumerProperties consumerProperties = this.bindingServiceProperties
.getConsumerProperties(inputName);
if (binder instanceof ExtendedPropertiesBinder) {
Object extension = ((ExtendedPropertiesBinder) binder)
.getExtendedConsumerProperties(inputName);
ExtendedConsumerProperties extendedConsumerProperties = new ExtendedConsumerProperties(
extension);
BeanUtils.copyProperties(consumerProperties, extendedConsumerProperties);
consumerProperties = extendedConsumerProperties;
}
validate(consumerProperties);
for (String target : bindingTargets) {
Binding<T> binding = binder.bindConsumer(target,
bindingServiceProperties.getGroup(inputName), input,
consumerProperties);
bindings.add(binding);
}
bindings = Collections.unmodifiableCollection(bindings);
this.consumerBindings.put(inputName, new ArrayList<Binding<?>>(bindings));
return bindings;
}
@SuppressWarnings("unchecked")
public <T> Binding<T> bindProducer(T output, String outputName) {
String bindingTarget = this.bindingServiceProperties
.getBindingDestination(outputName);
Binder<T, ?, ProducerProperties> binder = (Binder<T, ?, ProducerProperties>) getBinder(
outputName, output.getClass());
ProducerProperties producerProperties = this.bindingServiceProperties
.getProducerProperties(outputName);
if (binder instanceof ExtendedPropertiesBinder) {
Object extension = ((ExtendedPropertiesBinder) binder)
.getExtendedProducerProperties(outputName);
ExtendedProducerProperties extendedProducerProperties = new ExtendedProducerProperties<>(
extension);
BeanUtils.copyProperties(producerProperties, extendedProducerProperties);
producerProperties = extendedProducerProperties;
}
validate(producerProperties);
Binding<T> binding = binder.bindProducer(bindingTarget, output,
producerProperties);
this.producerBindings.put(outputName, binding);
return binding;
}
public void unbindConsumers(String inputName) {
List<Binding<?>> bindings = this.consumerBindings.remove(inputName);
if (bindings != null && !CollectionUtils.isEmpty(bindings)) {
for (Binding<?> binding : bindings) {
binding.unbind();
}
}
else if (log.isWarnEnabled()) {
log.warn("Trying to unbind '" + inputName + "', but no binding found.");
}
}
public void unbindProducers(String outputName) {
Binding<?> binding = this.producerBindings.remove(outputName);
if (binding != null) {
binding.unbind();
}
else if (log.isWarnEnabled()) {
log.warn("Trying to unbind '" + outputName + "', but no binding found.");
}
}
@SuppressWarnings("unchecked")
private <T> Binder<T, ?, ?> getBinder(String channelName, Class<T> bindableType) {
String transport = this.bindingServiceProperties.getBinder(channelName);
return binderFactory.getBinder(transport, bindableType);
}
/**
* Provided for backwards compatibility. Will be removed in a future version.
* @return
*/
@Deprecated
public BindingServiceProperties getChannelBindingServiceProperties() {
return this.bindingServiceProperties;
}
public BindingServiceProperties getBindingServiceProperties() {
return this.bindingServiceProperties;
}
private void validate(Object properties) {
RelaxedDataBinder dataBinder = new RelaxedDataBinder(properties);
dataBinder.setValidator(validator);
dataBinder.validate();
if (dataBinder.getBindingResult().hasErrors()) {
throw new IllegalStateException(dataBinder.getBindingResult().toString());
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.binding;
/**
* Defines methods to create/configure the binding targets defined by
* {@link org.springframework.cloud.stream.annotation.EnableBinding}.
*
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan
*/
public interface BindingTargetFactory {
/**
* Checks whether a specific binding target type can be created by this factory.
* @param clazz the binding target type
* @return true if the binding target can be created
*/
boolean canCreate(Class<?> clazz);
/**
* Create an input binding target that will be bound via a corresponding
* {@link org.springframework.cloud.stream.binder.Binder}.
* @param name name of the binding target
* @return binding target
*/
Object createInput(String name);
/**
* Create an output binding target that will be bound via a corresponding
* {@link org.springframework.cloud.stream.binder.Binder}.
* @param name name of the binding target
* @return binding target
*/
Object createOutput(String name);
}

View File

@@ -1,157 +0,0 @@
/*
* Copyright 2015-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.binding;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.cloud.stream.binder.Binder;
import org.springframework.cloud.stream.binder.BinderFactory;
import org.springframework.cloud.stream.binder.Binding;
import org.springframework.cloud.stream.binder.ConsumerProperties;
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
import org.springframework.cloud.stream.binder.ExtendedPropertiesBinder;
import org.springframework.cloud.stream.binder.ProducerProperties;
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
import org.springframework.messaging.MessageChannel;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.beanvalidation.CustomValidatorBean;
/**
* Handles the operations related to channel binding including binding of input/output channels by delegating
* to an underlying {@link Binder}, setting up data type conversion for binding channel.
* @author Mark Fisher
* @author Dave Syer
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan
* @author Gary Russell
*/
public class ChannelBindingService {
private final CustomValidatorBean validator;
private final Log log = LogFactory.getLog(ChannelBindingService.class);
private BinderFactory<MessageChannel> binderFactory;
private final ChannelBindingServiceProperties channelBindingServiceProperties;
private final Map<String, Binding<MessageChannel>> producerBindings = new HashMap<>();
private final Map<String, List<Binding<MessageChannel>>> consumerBindings = new HashMap<>();
public ChannelBindingService(ChannelBindingServiceProperties channelBindingServiceProperties,
BinderFactory<MessageChannel> binderFactory) {
this.channelBindingServiceProperties = channelBindingServiceProperties;
this.binderFactory = binderFactory;
this.validator = new CustomValidatorBean();
this.validator.afterPropertiesSet();
}
@SuppressWarnings("unchecked")
public Collection<Binding<MessageChannel>> bindConsumer(MessageChannel inputChannel, String inputChannelName) {
String channelBindingTarget = this.channelBindingServiceProperties.getBindingDestination(inputChannelName);
String[] channelBindingTargets = StringUtils.commaDelimitedListToStringArray(channelBindingTarget);
List<Binding<MessageChannel>> bindings = new ArrayList<>();
Binder<MessageChannel, ConsumerProperties, ?> binder =
(Binder<MessageChannel, ConsumerProperties, ?>) getBinderForChannel(inputChannelName);
ConsumerProperties consumerProperties =
this.channelBindingServiceProperties.getConsumerProperties(inputChannelName);
if (binder instanceof ExtendedPropertiesBinder) {
Object extension = ((ExtendedPropertiesBinder) binder).getExtendedConsumerProperties(inputChannelName);
ExtendedConsumerProperties extendedConsumerProperties = new ExtendedConsumerProperties(extension);
BeanUtils.copyProperties(consumerProperties, extendedConsumerProperties);
consumerProperties = extendedConsumerProperties;
}
validate(consumerProperties);
for (String target : channelBindingTargets) {
Binding<MessageChannel> binding = binder.bindConsumer(target, channelBindingServiceProperties.getGroup(inputChannelName), inputChannel, consumerProperties);
bindings.add(binding);
}
this.consumerBindings.put(inputChannelName, bindings);
return bindings;
}
@SuppressWarnings("unchecked")
public Binding<MessageChannel> bindProducer(MessageChannel outputChannel, String outputChannelName) {
String channelBindingTarget = this.channelBindingServiceProperties.getBindingDestination(outputChannelName);
Binder<MessageChannel, ?, ProducerProperties> binder =
(Binder<MessageChannel, ?, ProducerProperties>) getBinderForChannel(outputChannelName);
ProducerProperties producerProperties = this.channelBindingServiceProperties.getProducerProperties(outputChannelName);
if (binder instanceof ExtendedPropertiesBinder) {
Object extension = ((ExtendedPropertiesBinder) binder).getExtendedProducerProperties(outputChannelName);
ExtendedProducerProperties extendedProducerProperties = new ExtendedProducerProperties<>(extension);
BeanUtils.copyProperties(producerProperties, extendedProducerProperties);
producerProperties = extendedProducerProperties;
}
validate(producerProperties);
Binding<MessageChannel> binding = binder.bindProducer(channelBindingTarget, outputChannel, producerProperties);
this.producerBindings.put(outputChannelName, binding);
return binding;
}
public void unbindConsumers(String inputChannelName) {
List<Binding<MessageChannel>> bindings = this.consumerBindings.remove(inputChannelName);
if (bindings != null && !CollectionUtils.isEmpty(bindings)) {
for (Binding<MessageChannel> binding : bindings) {
binding.unbind();
}
}
else if (log.isWarnEnabled()) {
log.warn("Trying to unbind channel '" + inputChannelName + "', but no binding found.");
}
}
public void unbindProducers(String outputChannelName) {
Binding<MessageChannel> binding = this.producerBindings.remove(outputChannelName);
if (binding != null) {
binding.unbind();
}
else if (log.isWarnEnabled()) {
log.warn("Trying to unbind channel '" + outputChannelName + "', but no binding found.");
}
}
private Binder<MessageChannel, ?, ?> getBinderForChannel(String channelName) {
String transport = this.channelBindingServiceProperties.getBinder(channelName);
return binderFactory.getBinder(transport);
}
public ChannelBindingServiceProperties getChannelBindingServiceProperties() {
return this.channelBindingServiceProperties;
}
private void validate(Object properties) {
RelaxedDataBinder dataBinder = new RelaxedDataBinder(properties);
dataBinder.setValidator(validator);
dataBinder.validate();
if (dataBinder.getBindingResult().hasErrors()) {
throw new IllegalStateException(dataBinder.getBindingResult().toString());
}
}
}

View File

@@ -47,7 +47,7 @@ public final class DynamicDestinationsBindable extends BindableAdapter {
}
@Override
public void unbindOutputs(ChannelBindingService adapter) {
public void unbindOutputs(BindingService adapter) {
for (Map.Entry<String, Binding> entry: outputBindings.entrySet()) {
entry.getValue().unbind();
}

View File

@@ -25,7 +25,7 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.SmartLifecycle;
/**
* Coordinates binding/unbinding of input channels in accordance to the lifecycle
* Coordinates binding/unbinding of input binding targets in accordance to the lifecycle
* of the host context.
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan
@@ -45,14 +45,14 @@ public class InputBindingLifecycle implements SmartLifecycle, ApplicationContext
@Override
public void start() {
if (!running) {
// retrieve the ChannelBindingService lazily, avoiding early initialization
// retrieve the BindingService lazily, avoiding early initialization
try {
ChannelBindingService channelBindingService = this.applicationContext
.getBean(ChannelBindingService.class);
BindingService bindingService = this.applicationContext
.getBean(BindingService.class);
Map<String, Bindable> bindables = this.applicationContext
.getBeansOfType(Bindable.class);
for (Bindable bindable : bindables.values()) {
bindable.bindInputs(channelBindingService);
bindable.bindInputs(bindingService);
}
}
catch (BeansException e) {
@@ -67,14 +67,14 @@ public class InputBindingLifecycle implements SmartLifecycle, ApplicationContext
public void stop() {
if (running) {
try {
// retrieve the ChannelBindingService lazily, avoiding early
// retrieve the BindingService lazily, avoiding early
// initialization
ChannelBindingService channelBindingService = this.applicationContext
.getBean(ChannelBindingService.class);
BindingService bindingService = this.applicationContext
.getBean(BindingService.class);
Map<String, Bindable> bindables = this.applicationContext
.getBeansOfType(Bindable.class);
for (Bindable bindable : bindables.values()) {
bindable.unbindInputs(channelBindingService);
bindable.unbindInputs(bindingService);
}
}
catch (BeansException e) {

View File

@@ -29,15 +29,15 @@ public class MessageChannelStreamListenerResultAdapter
implements StreamListenerResultAdapter<MessageChannel, MessageChannel> {
@Override
public boolean supports(Class<?> resultType, Class<?> boundElement) {
public boolean supports(Class<?> resultType, Class<?> bindingTarget) {
return MessageChannel.class.isAssignableFrom(resultType)
&& MessageChannel.class.isAssignableFrom(boundElement);
&& MessageChannel.class.isAssignableFrom(bindingTarget);
}
@Override
public void adapt(MessageChannel streamListenerResult, MessageChannel boundElement) {
public void adapt(MessageChannel streamListenerResult, MessageChannel bindingTarget) {
BridgeHandler handler = new BridgeHandler();
handler.setOutputChannel(boundElement);
handler.setOutputChannel(bindingTarget);
handler.afterPropertiesSet();
((SubscribableChannel) streamListenerResult).subscribe(handler);
}

View File

@@ -25,7 +25,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.cloud.stream.binder.BinderHeaders;
import org.springframework.cloud.stream.binder.PartitionHandler;
import org.springframework.cloud.stream.config.BindingProperties;
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
import org.springframework.cloud.stream.config.BindingServiceProperties;
import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory;
import org.springframework.cloud.stream.converter.MessageConverterUtils;
import org.springframework.expression.EvaluationContext;
@@ -63,12 +63,12 @@ public class MessageConverterConfigurer implements MessageChannelConfigurer, Bea
private final CompositeMessageConverterFactory compositeMessageConverterFactory;
private final ChannelBindingServiceProperties channelBindingServiceProperties;
private final BindingServiceProperties bindingServiceProperties;
public MessageConverterConfigurer(ChannelBindingServiceProperties channelBindingServiceProperties,
public MessageConverterConfigurer(BindingServiceProperties bindingServiceProperties,
CompositeMessageConverterFactory compositeMessageConverterFactory) {
Assert.notNull(compositeMessageConverterFactory, "The message converter factory cannot be null");
this.channelBindingServiceProperties = channelBindingServiceProperties;
this.bindingServiceProperties = bindingServiceProperties;
this.compositeMessageConverterFactory = compositeMessageConverterFactory;
}
@@ -100,7 +100,7 @@ public class MessageConverterConfigurer implements MessageChannelConfigurer, Bea
private void configureMessageChannel(MessageChannel channel, String channelName, boolean input) {
Assert.isAssignable(AbstractMessageChannel.class, channel.getClass());
AbstractMessageChannel messageChannel = (AbstractMessageChannel) channel;
final BindingProperties bindingProperties = this.channelBindingServiceProperties.getBindingProperties(
final BindingProperties bindingProperties = this.bindingServiceProperties.getBindingProperties(
channelName);
final String contentType = bindingProperties.getContentType();
if (!input && bindingProperties.getProducer() != null && bindingProperties.getProducer().isPartitioned()) {

View File

@@ -25,7 +25,7 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.SmartLifecycle;
/**
* Coordinates binding/unbinding of output channels in accordance to the lifecycle
* Coordinates binding/unbinding of output binding targets in accordance to the lifecycle
* of the host context.
*
* @author Marius Bogoevici
@@ -47,14 +47,14 @@ public class OutputBindingLifecycle implements SmartLifecycle, ApplicationContex
public void start() {
if (!running) {
// retrieve the ChannelBindingService lazily, avoiding early initialization
// retrieve the BindingService lazily, avoiding early initialization
try {
ChannelBindingService channelBindingService = this.applicationContext
.getBean(ChannelBindingService.class);
BindingService bindingService = this.applicationContext
.getBean(BindingService.class);
Map<String, Bindable> bindables = this.applicationContext
.getBeansOfType(Bindable.class);
for (Bindable bindable : bindables.values()) {
bindable.bindOutputs(channelBindingService);
bindable.bindOutputs(bindingService);
}
}
catch (BeansException e) {
@@ -69,14 +69,14 @@ public class OutputBindingLifecycle implements SmartLifecycle, ApplicationContex
public void stop() {
if (running) {
try {
// retrieve the ChannelBindingService lazily, avoiding early
// retrieve the BindingService lazily, avoiding early
// initialization
ChannelBindingService channelBindingService = this.applicationContext
.getBean(ChannelBindingService.class);
BindingService bindingService = this.applicationContext
.getBean(BindingService.class);
Map<String, Bindable> bindables = this.applicationContext
.getBeansOfType(Bindable.class);
for (Bindable bindable : bindables.values()) {
bindable.unbindOutputs(channelBindingService);
bindable.unbindOutputs(bindingService);
}
}
catch (BeansException e) {
@@ -106,7 +106,8 @@ public class OutputBindingLifecycle implements SmartLifecycle, ApplicationContex
}
/**
* Return a low value so that this bean is started after receiving Lifecycle beans are started. Beans that need to start before bindings will set a lower phase value.
* Return a low value so that this bean is started after receiving Lifecycle beans are
* started. Beans that need to start before bindings will set a lower phase value.
*/
@Override
public int getPhase() {

View File

@@ -21,38 +21,37 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.springframework.messaging.MessageChannel;
/**
* A {@link Bindable} component that wraps a generic channel. Useful for binding channels outside the
* {@link org.springframework.cloud.stream.annotation.Input} and {@link org.springframework.cloud.stream.annotation.Output}
* annotated interfaces.
* A {@link Bindable} component that wraps a generic output binding target. Useful for
* binding targets outside the {@link org.springframework.cloud.stream.annotation.Input}
* and {@link org.springframework.cloud.stream.annotation.Output} annotated interfaces.
*
* @author Ilayaperumal Gopinathan
* @author Marius Bogoevici
*/
public class SingleChannelBindable extends BindableAdapter {
public class SingleBindingTargetBindable<T> extends BindableAdapter {
private final String name;
private final MessageChannel messageChannel;
private final T bindingTarget;
public SingleChannelBindable(String name, MessageChannel messageChannel) {
public SingleBindingTargetBindable(String name, T bindingTarget) {
this.name = name;
this.messageChannel = messageChannel;
this.bindingTarget = bindingTarget;
}
@Override
public void bindOutputs(ChannelBindingService adapter) {
adapter.bindProducer(messageChannel, name);
public void bindOutputs(BindingService bindingService) {
bindingService.bindProducer(bindingTarget, name);
}
@Override
public void unbindOutputs(ChannelBindingService adapter) {
adapter.unbindProducers(name);
public void unbindOutputs(BindingService bindingService) {
bindingService.unbindProducers(name);
}
@Override
public Set<String> getOutputs() {
return Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(name)));
return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(name)));
}
}

View File

@@ -79,16 +79,16 @@ public class StreamListenerAnnotationBeanPostProcessor
}
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
Map<String, StreamListenerParameterAdapter> parameterAdapterMap =
this.applicationContext.getBeansOfType(StreamListenerParameterAdapter.class);
Map<String, StreamListenerParameterAdapter> parameterAdapterMap = this.applicationContext
.getBeansOfType(StreamListenerParameterAdapter.class);
for (StreamListenerParameterAdapter parameterAdapter : parameterAdapterMap.values()) {
this.streamListenerParameterAdapters.add(parameterAdapter);
}
Map<String, StreamListenerResultAdapter> resultAdapterMap =
this.applicationContext.getBeansOfType(StreamListenerResultAdapter.class);
Map<String, StreamListenerResultAdapter> resultAdapterMap = this.applicationContext
.getBeansOfType(StreamListenerResultAdapter.class);
this.streamListenerResultAdapters.add(new MessageChannelStreamListenerResultAdapter());
for (StreamListenerResultAdapter resultAdapter : resultAdapterMap.values()) {
this.streamListenerResultAdapters.add(resultAdapter);
@@ -108,24 +108,30 @@ public class StreamListenerAnnotationBeanPostProcessor
public void doWith(final Method method) throws IllegalArgumentException, IllegalAccessException {
StreamListener streamListener = AnnotationUtils.findAnnotation(method, StreamListener.class);
if (streamListener != null) {
Assert.isTrue(method.getAnnotation(Input.class) == null, StreamListenerErrorMessages.INPUT_AT_STREAM_LISTENER);
Assert.isTrue(method.getAnnotation(Input.class) == null,
StreamListenerErrorMessages.INPUT_AT_STREAM_LISTENER);
String methodAnnotatedInboundName = streamListener.value();
String methodAnnotatedOutboundName = StreamListenerMethodUtils.getOutboundElementNameFromMethod(method);
String methodAnnotatedOutboundName = StreamListenerMethodUtils.getOutboundBindingTargetName(method);
int inputAnnotationCount = StreamListenerMethodUtils.inputAnnotationCount(method);
int outputAnnotationCount = StreamListenerMethodUtils.outputAnnotationCount(method);
boolean isDeclarative = checkDeclarativeMethod(method, methodAnnotatedInboundName, methodAnnotatedOutboundName);
StreamListenerMethodUtils.validateStreamListenerMethod(method, inputAnnotationCount, outputAnnotationCount,
methodAnnotatedInboundName, methodAnnotatedOutboundName, isDeclarative);
boolean isDeclarative = checkDeclarativeMethod(method, methodAnnotatedInboundName,
methodAnnotatedOutboundName);
StreamListenerMethodUtils.validateStreamListenerMethod(method, inputAnnotationCount,
outputAnnotationCount, methodAnnotatedInboundName, methodAnnotatedOutboundName,
isDeclarative);
if (!method.getReturnType().equals(Void.TYPE)) {
if (!StringUtils.hasText(methodAnnotatedOutboundName)) {
if (outputAnnotationCount == 0) {
throw new IllegalArgumentException(StreamListenerErrorMessages.RETURN_TYPE_NO_OUTBOUND_SPECIFIED);
throw new IllegalArgumentException(
StreamListenerErrorMessages.RETURN_TYPE_NO_OUTBOUND_SPECIFIED);
}
Assert.isTrue((outputAnnotationCount == 1), StreamListenerErrorMessages.RETURN_TYPE_MULTIPLE_OUTBOUND_SPECIFIED);
Assert.isTrue((outputAnnotationCount == 1),
StreamListenerErrorMessages.RETURN_TYPE_MULTIPLE_OUTBOUND_SPECIFIED);
}
}
if (isDeclarative) {
invokeSetupMethodOnListenedChannel(method, bean, methodAnnotatedInboundName, methodAnnotatedOutboundName);
invokeSetupMethodOnListenedChannel(method, bean, methodAnnotatedInboundName,
methodAnnotatedOutboundName);
}
else {
registerHandlerMethodOnListenedChannel(method, streamListener, bean);
@@ -136,34 +142,36 @@ public class StreamListenerAnnotationBeanPostProcessor
return bean;
}
private boolean checkDeclarativeMethod(Method method, String methodAnnotatedInboundName, String methodAnnotatedOutboundName) {
private boolean checkDeclarativeMethod(Method method, String methodAnnotatedInboundName,
String methodAnnotatedOutboundName) {
int methodArgumentsLength = method.getParameterTypes().length;
for (int parameterIndex = 0; parameterIndex < methodArgumentsLength; parameterIndex++) {
MethodParameter methodParameter = MethodParameter
.forMethodOrConstructor(method, parameterIndex);
MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, parameterIndex);
if (methodParameter.hasParameterAnnotation(Input.class)) {
String inboundName = (String) AnnotationUtils
.getValue(methodParameter.getParameterAnnotation(Input.class));
Assert.isTrue(StringUtils.hasText(inboundName), StreamListenerErrorMessages.INVALID_INBOUND_NAME);
Assert.isTrue(isDeclarativeMethodParameter(this.applicationContext.getBean(inboundName),
methodParameter), StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
Assert.isTrue(
isDeclarativeMethodParameter(this.applicationContext.getBean(inboundName), methodParameter),
StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
return true;
}
if (methodParameter.hasParameterAnnotation(Output.class)) {
String outboundName = (String) AnnotationUtils
.getValue(methodParameter.getParameterAnnotation(Output.class));
Assert.isTrue(StringUtils.hasText(outboundName), StreamListenerErrorMessages.INVALID_OUTBOUND_NAME);
Assert.isTrue(isDeclarativeMethodParameter(this.applicationContext.getBean(outboundName),
methodParameter), StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
Assert.isTrue(
isDeclarativeMethodParameter(this.applicationContext.getBean(outboundName), methodParameter),
StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
return true;
}
if (StringUtils.hasText(methodAnnotatedOutboundName)) {
return isDeclarativeMethodParameter(
this.applicationContext.getBean(methodAnnotatedOutboundName), methodParameter);
return isDeclarativeMethodParameter(this.applicationContext.getBean(methodAnnotatedOutboundName),
methodParameter);
}
if (StringUtils.hasText(methodAnnotatedInboundName)) {
return isDeclarativeMethodParameter(
this.applicationContext.getBean(methodAnnotatedInboundName), methodParameter);
return isDeclarativeMethodParameter(this.applicationContext.getBean(methodAnnotatedInboundName),
methodParameter);
}
}
return false;
@@ -183,8 +191,9 @@ public class StreamListenerAnnotationBeanPostProcessor
return false;
}
@SuppressWarnings({"rawtypes", "unchecked"})
private void invokeSetupMethodOnListenedChannel(Method method, Object bean, String inboundName, String outboundName) {
@SuppressWarnings({ "rawtypes", "unchecked" })
private void invokeSetupMethodOnListenedChannel(Method method, Object bean, String inboundName,
String outboundName) {
Object[] arguments = new Object[method.getParameterTypes().length];
for (int parameterIndex = 0; parameterIndex < arguments.length; parameterIndex++) {
MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, parameterIndex);
@@ -206,17 +215,16 @@ public class StreamListenerAnnotationBeanPostProcessor
arguments[parameterIndex] = targetBean;
}
else {
for (StreamListenerParameterAdapter<?, Object> streamListenerParameterAdapter :
this.streamListenerParameterAdapters) {
for (StreamListenerParameterAdapter<?, Object> streamListenerParameterAdapter : this.streamListenerParameterAdapters) {
if (streamListenerParameterAdapter.supports(targetBean.getClass(), methodParameter)) {
arguments[parameterIndex] = streamListenerParameterAdapter.adapt(targetBean, methodParameter);
arguments[parameterIndex] = streamListenerParameterAdapter.adapt(targetBean,
methodParameter);
break;
}
}
}
Assert.notNull(arguments[parameterIndex],
"Cannot convert argument " + parameterIndex + " of " + method + "from " + targetBean.getClass()
+ " to " + parameterType);
Assert.notNull(arguments[parameterIndex], "Cannot convert argument " + parameterIndex + " of " + method
+ "from " + targetBean.getClass() + " to " + parameterType);
}
else {
throw new IllegalStateException(StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
@@ -230,15 +238,15 @@ public class StreamListenerAnnotationBeanPostProcessor
Object result = method.invoke(bean, arguments);
if (!StringUtils.hasText(outboundName)) {
for (int parameterIndex = 0; parameterIndex < method.getParameterTypes().length; parameterIndex++) {
MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, parameterIndex);
MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method,
parameterIndex);
if (methodParameter.hasParameterAnnotation(Output.class)) {
outboundName = methodParameter.getParameterAnnotation(Output.class).value();
}
}
}
Object targetBean = this.applicationContext.getBean(outboundName);
for (StreamListenerResultAdapter streamListenerResultAdapter : this
.streamListenerResultAdapters) {
for (StreamListenerResultAdapter streamListenerResultAdapter : this.streamListenerResultAdapters) {
if (streamListenerResultAdapter.supports(result.getClass(), targetBean.getClass())) {
streamListenerResultAdapter.adapt(result, targetBean);
break;
@@ -254,31 +262,29 @@ public class StreamListenerAnnotationBeanPostProcessor
protected void registerHandlerMethodOnListenedChannel(Method method, StreamListener streamListener, Object bean) {
Method targetMethod = checkProxy(method, bean);
Assert.hasText(streamListener.value(), "The binding name cannot be null");
final InvocableHandlerMethod invocableHandlerMethod =
this.messageHandlerMethodFactory.createInvocableHandlerMethod(bean, targetMethod);
final InvocableHandlerMethod invocableHandlerMethod = this.messageHandlerMethodFactory
.createInvocableHandlerMethod(bean, targetMethod);
if (!StringUtils.hasText(streamListener.value())) {
throw new BeanInitializationException("A bound component name must be specified");
}
if (this.mappedBindings.containsKey(streamListener.value())) {
throw new BeanInitializationException("Duplicate @" + StreamListener.class.getSimpleName() +
" mapping for '" + streamListener.value() + "' on " + invocableHandlerMethod.getShortLogMessage() +
" already existing for " + this.mappedBindings.get(streamListener.value()).getShortLogMessage());
throw new BeanInitializationException("Duplicate @" + StreamListener.class.getSimpleName()
+ " mapping for '" + streamListener.value() + "' on " + invocableHandlerMethod.getShortLogMessage()
+ " already existing for " + this.mappedBindings.get(streamListener.value()).getShortLogMessage());
}
this.mappedBindings.put(streamListener.value(), invocableHandlerMethod);
SubscribableChannel channel = this.applicationContext.getBean(streamListener.value(),
SubscribableChannel.class);
final String defaultOutputChannel = StreamListenerMethodUtils.getOutboundElementNameFromMethod(method);
final String defaultOutputChannel = StreamListenerMethodUtils.getOutboundBindingTargetName(method);
if (invocableHandlerMethod.isVoid()) {
Assert.isTrue(StringUtils.isEmpty(defaultOutputChannel),
"An output channel cannot be specified for a method that " +
"does not return a value");
"An output channel cannot be specified for a method that does not return a value");
}
else {
Assert.isTrue(!StringUtils.isEmpty(defaultOutputChannel),
"An output channel must be specified for a method that " +
"can return a value");
"An output channel must be specified for a method that can return a value");
}
StreamListenerMethodUtils.assertStreamListenerMessageHandlerMethod(method);
StreamListenerMethodUtils.validateStreamListenerMessageHandler(method);
StreamListenerMessageHandler handler = new StreamListenerMessageHandler(invocableHandlerMethod);
handler.setApplicationContext(this.applicationContext);
handler.setChannelResolver(this.binderAwareChannelResolver);
@@ -291,7 +297,8 @@ public class StreamListenerAnnotationBeanPostProcessor
@Override
public void afterSingletonsInstantiated() {
// Dump the mappings after the context has been created, ensuring that beans can be processed correctly
// Dump the mappings after the context has been created, ensuring that beans can
// be processed correctly
// again.
this.mappedBindings.clear();
}
@@ -300,7 +307,8 @@ public class StreamListenerAnnotationBeanPostProcessor
Method method = methodArg;
if (AopUtils.isJdkDynamicProxy(bean)) {
try {
// Found a @StreamListener method on the target class for this JDK proxy ->
// Found a @StreamListener method on the target class for this JDK proxy
// ->
// is it also present on the proxy itself?
method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
Class<?>[] proxiedInterfaces = ((Advised) bean).getProxiedInterfaces();
@@ -318,11 +326,11 @@ public class StreamListenerAnnotationBeanPostProcessor
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException(String.format(
"@StreamListener method '%s' found on bean target class '%s', " +
"but not found in any interface(s) for bean JDK proxy. Either " +
"pull the method up to an interface or switch to subclass (CGLIB) " +
"proxies by setting proxy-target-class/proxyTargetClass " +
"attribute to 'true'", method.getName(), method.getDeclaringClass().getSimpleName()), ex);
"@StreamListener method '%s' found on bean target class '%s', "
+ "but not found in any interface(s) for bean JDK proxy. Either "
+ "pull the method up to an interface or switch to subclass (CGLIB) "
+ "proxies by setting proxy-target-class/proxyTargetClass attribute to 'true'",
method.getName(), method.getDeclaringClass().getSimpleName()), ex);
}
}
return method;
@@ -351,9 +359,8 @@ public class StreamListenerAnnotationBeanPostProcessor
throw (MessagingException) e;
}
else {
throw new MessagingException(requestMessage, "Exception thrown while invoking " + this
.invocableHandlerMethod
.getShortLogMessage(), e);
throw new MessagingException(requestMessage,
"Exception thrown while invoking " + this.invocableHandlerMethod.getShortLogMessage(), e);
}
}
}

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.binding;
/**
@@ -48,7 +49,7 @@ public abstract class StreamListenerErrorMessages {
public static final String NO_INPUT_DESTINATION = "No input destination is configured. Use either the @StreamListener value or @Input";
public static final String INVALID_DECLARATIVE_METHOD_PARAMETERS = PREFIX
+ "may use @Input or @Output annotations only in declarative mode and for parameters that are bound elements or convertible from bound elements.";
+ "may use @Input or @Output annotations only in declarative mode and for parameters that are binding targets or convertible from binding targets.";
public static final String AMBIGUOUS_MESSAGE_HANDLER_METHOD_ARGUMENTS = "Ambiguous method arguments for the StreamListener method";
@@ -59,6 +60,4 @@ public abstract class StreamListenerErrorMessages {
public static final String INVALID_OUTPUT_VALUES = "Cannot set both output (@Output/@SendTo) method annotation value"
+ " and @Output annotation as a method parameter";
public static final String TARGET_BEAN_NOT_EXISTS = "Target bean doesn't exist for the bound element name";
}

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.binding;
import java.lang.reflect.Method;
@@ -29,7 +30,8 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* This class contains utility methods for handling {@link StreamListener} annotated bean methods.
* This class contains utility methods for handling {@link StreamListener} annotated bean
* methods.
*
* @author Ilayaperumal Gopinathan
*/
@@ -57,17 +59,22 @@ public class StreamListenerMethodUtils {
return outputAnnotationCount;
}
protected static void validateStreamListenerMethod(Method method, int inputAnnotationCount, int outputAnnotationCount, String methodAnnotatedInboundName, String methodAnnotatedOutboundName, boolean isDeclarative) {
protected static void validateStreamListenerMethod(Method method, int inputAnnotationCount,
int outputAnnotationCount, String methodAnnotatedInboundName, String methodAnnotatedOutboundName,
boolean isDeclarative) {
int methodArgumentsLength = method.getParameterTypes().length;
if (!isDeclarative) {
Assert.isTrue(inputAnnotationCount == 0 && outputAnnotationCount == 0, StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
Assert.isTrue(inputAnnotationCount == 0 && outputAnnotationCount == 0,
StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
}
if (StringUtils.hasText(methodAnnotatedInboundName) && StringUtils.hasText(methodAnnotatedOutboundName)) {
Assert.isTrue(inputAnnotationCount == 0 && outputAnnotationCount == 0, StreamListenerErrorMessages.INVALID_INPUT_OUTPUT_METHOD_PARAMETERS);
Assert.isTrue(inputAnnotationCount == 0 && outputAnnotationCount == 0,
StreamListenerErrorMessages.INVALID_INPUT_OUTPUT_METHOD_PARAMETERS);
}
if (StringUtils.hasText(methodAnnotatedInboundName)) {
Assert.isTrue(inputAnnotationCount == 0, StreamListenerErrorMessages.INVALID_INPUT_VALUES);
Assert.isTrue(outputAnnotationCount == 0, StreamListenerErrorMessages.INVALID_INPUT_VALUE_WITH_OUTPUT_METHOD_PARAM);
Assert.isTrue(outputAnnotationCount == 0,
StreamListenerErrorMessages.INVALID_INPUT_VALUE_WITH_OUTPUT_METHOD_PARAM);
}
else {
Assert.isTrue(inputAnnotationCount >= 1, StreamListenerErrorMessages.NO_INPUT_DESTINATION);
@@ -79,21 +86,24 @@ public class StreamListenerMethodUtils {
for (int parameterIndex = 0; parameterIndex < methodArgumentsLength; parameterIndex++) {
MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, parameterIndex);
if (methodParameter.hasParameterAnnotation(Input.class)) {
String inboundName = (String) AnnotationUtils.getValue(methodParameter.getParameterAnnotation(Input.class));
String inboundName = (String) AnnotationUtils
.getValue(methodParameter.getParameterAnnotation(Input.class));
Assert.isTrue(StringUtils.hasText(inboundName), StreamListenerErrorMessages.INVALID_INBOUND_NAME);
}
if (methodParameter.hasParameterAnnotation(Output.class)) {
String outboundName = (String) AnnotationUtils.getValue(methodParameter.getParameterAnnotation(Output.class));
String outboundName = (String) AnnotationUtils
.getValue(methodParameter.getParameterAnnotation(Output.class));
Assert.isTrue(StringUtils.hasText(outboundName), StreamListenerErrorMessages.INVALID_OUTBOUND_NAME);
}
}
if (methodArgumentsLength > 1){
Assert.isTrue(inputAnnotationCount + outputAnnotationCount == methodArgumentsLength, StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
if (methodArgumentsLength > 1) {
Assert.isTrue(inputAnnotationCount + outputAnnotationCount == methodArgumentsLength,
StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
}
}
}
protected static void assertStreamListenerMessageHandlerMethod(Method method) {
protected static void validateStreamListenerMessageHandler(Method method) {
int methodArgumentsLength = method.getParameterTypes().length;
if (methodArgumentsLength > 1) {
int numAnnotatedMethodParameters = 0;
@@ -114,7 +124,7 @@ public class StreamListenerMethodUtils {
}
}
protected static String getOutboundElementNameFromMethod(Method method) {
protected static String getOutboundBindingTargetName(Method method) {
SendTo sendTo = AnnotationUtils.findAnnotation(method, SendTo.class);
if (sendTo != null) {
Assert.isTrue(!ObjectUtils.isEmpty(sendTo.value()), StreamListenerErrorMessages.ATLEAST_ONE_OUTPUT);

View File

@@ -21,9 +21,8 @@ import org.springframework.core.MethodParameter;
/**
* Strategy for adapting a method argument type annotated with
* {@link org.springframework.cloud.stream.annotation.Input} or
* {@link org.springframework.cloud.stream.annotation.Output} from a bound element
* (e.g. {@link org.springframework.messaging.MessageChannel}) supported by an
* existing binder.
* {@link org.springframework.cloud.stream.annotation.Output} from a binding type (e.g.
* {@link org.springframework.messaging.MessageChannel}) supported by an existing binder.
*
* This is a framework extension and is not primarily intended for use by end-users.
* @author Marius Bogoevici
@@ -31,21 +30,22 @@ import org.springframework.core.MethodParameter;
public interface StreamListenerParameterAdapter<A, B> {
/**
* Return true if the conversion from the bound element type to the argument type
* is supported.
* @param boundElementType the bound element type
* @param methodParameter the method parameter for which the conversion is performed
* Return true if the conversion from the binding target type to the argument type is
* supported.
* @param bindingTargetType the binding target type
* @param methodParameter the method parameter for which the conversion is performed
* @return true if the conversion is supported
*/
boolean supports(Class<?> boundElementType, MethodParameter methodParameter);
boolean supports(Class<?> bindingTargetType, MethodParameter methodParameter);
/**
* Adapts the bound element to the argument type. The result will be passed
* as argument to a method annotated with {@link org.springframework.cloud.stream.annotation.StreamListener}
* when used for setting up a pipeline.
* @param boundElement the bound element
* @param parameter the method parameter for which the conversion is performed
* Adapts the binding target to the argument type. The result will be passed as
* argument to a method annotated with
* {@link org.springframework.cloud.stream.annotation.StreamListener} when used for
* setting up a pipeline.
* @param bindingTarget the binding target
* @param parameter the method parameter for which the conversion is performed
* @return an instance of the parameter type, which will be passed to the method
*/
A adapt(B boundElement, MethodParameter parameter);
A adapt(B bindingTarget, MethodParameter parameter);
}

View File

@@ -17,28 +17,30 @@
package org.springframework.cloud.stream.binding;
/**
* A strategy for adapting the result of a {@link org.springframework.cloud.stream.annotation.StreamListener}
* annotated method to a bound element annotated with {@link org.springframework.cloud.stream.annotation.Output}.
* A strategy for adapting the result of a
* {@link org.springframework.cloud.stream.annotation.StreamListener} annotated method to
* a binding target annotated with
* {@link org.springframework.cloud.stream.annotation.Output}.
*
* Used when the {@link org.springframework.cloud.stream.annotation.StreamListener} annotated method is operating in
* declarative mode.
* Used when the {@link org.springframework.cloud.stream.annotation.StreamListener}
* annotated method is operating in declarative mode.
* @author Marius Bogoevici
*/
public interface StreamListenerResultAdapter<R, B> {
/**
* Return true if the result type can be converted to the bound element.
* @param resultType the result type.
* @param boundElement the bound element.
* Return true if the result type can be converted to the binding target.
* @param resultType the result type.
* @param bindingTarget the binding target.
* @return true if the conversion can take place.
*/
boolean supports(Class<?> resultType, Class<?> boundElement);
boolean supports(Class<?> resultType, Class<?> bindingTarget);
/**
* Adapts the result to the bound element.
* Adapts the result to the binding target.
* @param streamListenerResult the result of invoking the method.
* @param boundElement the bound element
* @param bindingTarget the binding target.
*/
void adapt(R streamListenerResult, B boundElement);
void adapt(R streamListenerResult, B bindingTarget);
}

View File

@@ -20,29 +20,30 @@ import org.springframework.integration.channel.DirectChannel;
import org.springframework.messaging.SubscribableChannel;
/**
* Class that {@link BindableProxyFactory} uses to create and configure message channels.
* An implementation of {@link BindingTargetFactory} for creating {@link SubscribableChannel}s.
*
* @author Marius Bogoevici
* @author David Syer
* @author Ilayaperumal Gopinathan
*/
public class DefaultBindableChannelFactory implements BindableChannelFactory {
public class SubscribableChannelBindingTargetFactory extends AbstractBindingTargetFactory<SubscribableChannel> {
private final MessageChannelConfigurer messageChannelConfigurer;
public DefaultBindableChannelFactory(MessageChannelConfigurer messageChannelConfigurer) {
public SubscribableChannelBindingTargetFactory(MessageChannelConfigurer messageChannelConfigurer) {
super(SubscribableChannel.class);
this.messageChannelConfigurer = messageChannelConfigurer;
}
@Override
public SubscribableChannel createInputChannel(String name) {
public SubscribableChannel createInput(String name) {
SubscribableChannel subscribableChannel = new DirectChannel();
this.messageChannelConfigurer.configureInputChannel(subscribableChannel, name);
return subscribableChannel;
}
@Override
public SubscribableChannel createOutputChannel(String name) {
public SubscribableChannel createOutput(String name) {
SubscribableChannel subscribableChannel = new DirectChannel();
this.messageChannelConfigurer.configureOutputChannel(subscribableChannel, name);
return subscribableChannel;

View File

@@ -61,10 +61,10 @@ public class BinderFactoryConfiguration {
@Bean
@ConditionalOnMissingBean(BinderFactory.class)
public BinderFactory<?> binderFactory(BinderTypeRegistry binderTypeRegistry,
ChannelBindingServiceProperties channelBindingServiceProperties) {
public BinderFactory binderFactory(BinderTypeRegistry binderTypeRegistry,
BindingServiceProperties bindingServiceProperties) {
Map<String, BinderConfiguration> binderConfigurations = new HashMap<>();
Map<String, BinderProperties> declaredBinders = channelBindingServiceProperties.getBinders();
Map<String, BinderProperties> declaredBinders = bindingServiceProperties.getBinders();
boolean defaultCandidatesExist = false;
Iterator<Map.Entry<String, BinderProperties>> binderPropertiesIterator = declaredBinders.entrySet().iterator();
while (!defaultCandidatesExist && binderPropertiesIterator.hasNext()) {
@@ -94,8 +94,8 @@ public class BinderFactoryConfiguration {
new BinderConfiguration(entry.getValue(), new Properties(), true, true));
}
}
DefaultBinderFactory<?> binderFactory = new DefaultBinderFactory<>(binderConfigurations);
binderFactory.setDefaultBinder(channelBindingServiceProperties.getDefaultBinder());
DefaultBinderFactory binderFactory = new DefaultBinderFactory(binderConfigurations);
binderFactory.setDefaultBinder(bindingServiceProperties.getDefaultBinder());
return binderFactory;
}
@@ -105,7 +105,7 @@ public class BinderFactoryConfiguration {
Map<String, BinderType> binderTypes = new HashMap<>();
ClassLoader classLoader = configurableApplicationContext.getClassLoader();
if (classLoader == null) {
classLoader = ChannelBindingAutoConfiguration.class.getClassLoader();
classLoader = BindingAutoConfiguration.class.getClassLoader();
}
try {
Enumeration<URL> resources = classLoader.getResources("META-INF/spring.binders");

View File

@@ -28,7 +28,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.stream.binding.Bindable;
import org.springframework.cloud.stream.binding.ChannelBindingService;
import org.springframework.cloud.stream.binding.BindingService;
import org.springframework.cloud.stream.endpoint.ChannelsEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -43,10 +43,10 @@ import org.springframework.messaging.MessageChannel;
* @author Marius Bogoevici
*/
@Configuration
@ConditionalOnBean(ChannelBindingService.class)
@ConditionalOnBean(BindingService.class)
@EnableConfigurationProperties(DefaultPollerProperties.class)
@AutoConfigureBefore(EndpointAutoConfiguration.class)
public class ChannelBindingAutoConfiguration {
public class BindingAutoConfiguration {
@Autowired
private DefaultPollerProperties poller;
@@ -61,7 +61,7 @@ public class ChannelBindingAutoConfiguration {
}
@Bean
public ChannelsEndpoint channelsEndpoint(ChannelBindingServiceProperties properties) {
public ChannelsEndpoint channelsEndpoint(BindingServiceProperties properties) {
return new ChannelsEndpoint(this.adapters, properties);
}

View File

@@ -40,9 +40,9 @@ public class BindingBeansRegistrar implements ImportBeanDefinitionRegistrar {
ClassUtils.resolveClassName(metadata.getClassName(), null),
EnableBinding.class);
for (Class<?> type : collectClasses(attrs, metadata.getClassName())) {
BindingBeanDefinitionRegistryUtils.registerChannelBeanDefinitions(type,
BindingBeanDefinitionRegistryUtils.registerBindingTargetBeanDefinitions(type,
type.getName(), registry);
BindingBeanDefinitionRegistryUtils.registerChannelsQualifiedBeanDefinitions(
BindingBeanDefinitionRegistryUtils.registerBindingTargetsQualifiedBeanDefinitions(
ClassUtils.resolveClassName(metadata.getClassName(), null), type,
registry);
}

View File

@@ -34,20 +34,20 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.stream.binder.BinderFactory;
import org.springframework.cloud.stream.binding.BindableChannelFactory;
import org.springframework.cloud.stream.binding.AbstractBindingTargetFactory;
import org.springframework.cloud.stream.binding.BinderAwareChannelResolver;
import org.springframework.cloud.stream.binding.BinderAwareRouterBeanPostProcessor;
import org.springframework.cloud.stream.binding.ChannelBindingService;
import org.springframework.cloud.stream.binding.BindingService;
import org.springframework.cloud.stream.binding.CompositeMessageChannelConfigurer;
import org.springframework.cloud.stream.binding.ContextStartAfterRefreshListener;
import org.springframework.cloud.stream.binding.DefaultBindableChannelFactory;
import org.springframework.cloud.stream.binding.DynamicDestinationsBindable;
import org.springframework.cloud.stream.binding.InputBindingLifecycle;
import org.springframework.cloud.stream.binding.MessageChannelConfigurer;
import org.springframework.cloud.stream.binding.MessageConverterConfigurer;
import org.springframework.cloud.stream.binding.OutputBindingLifecycle;
import org.springframework.cloud.stream.binding.SingleChannelBindable;
import org.springframework.cloud.stream.binding.SingleBindingTargetBindable;
import org.springframework.cloud.stream.binding.StreamListenerAnnotationBeanPostProcessor;
import org.springframework.cloud.stream.binding.SubscribableChannelBindingTargetFactory;
import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -76,8 +76,8 @@ import org.springframework.util.CollectionUtils;
* @author Gary Russell
*/
@Configuration
@EnableConfigurationProperties(ChannelBindingServiceProperties.class)
public class ChannelBindingServiceConfiguration {
@EnableConfigurationProperties(BindingServiceProperties.class)
public class BindingServiceConfiguration {
private static final String ERROR_CHANNEL_NAME = "error";
@@ -92,25 +92,24 @@ public class ChannelBindingServiceConfiguration {
@Bean
// This conditional is intentionally not in an autoconfig (usually a bad idea) because
// it is used to detect a ChannelBindingService in the parent context (which we know
// it is used to detect a BindingService in the parent context (which we know
// already exists).
@ConditionalOnMissingBean(ChannelBindingService.class)
public ChannelBindingService bindingService(ChannelBindingServiceProperties channelBindingServiceProperties,
BinderFactory<MessageChannel> binderFactory) {
return new ChannelBindingService(channelBindingServiceProperties, binderFactory);
@ConditionalOnMissingBean(BindingService.class)
public BindingService bindingService(BindingServiceProperties bindingServiceProperties,
BinderFactory binderFactory) {
return new BindingService(bindingServiceProperties, binderFactory);
}
@Bean
public MessageConverterConfigurer messageConverterConfigurer(
ChannelBindingServiceProperties channelBindingServiceProperties,
public MessageConverterConfigurer messageConverterConfigurer(BindingServiceProperties bindingServiceProperties,
CompositeMessageConverterFactory compositeMessageConverterFactory) {
return new MessageConverterConfigurer(channelBindingServiceProperties,
compositeMessageConverterFactory);
return new MessageConverterConfigurer(bindingServiceProperties, compositeMessageConverterFactory);
}
@Bean
public BindableChannelFactory channelFactory(CompositeMessageChannelConfigurer compositeMessageChannelConfigurer) {
return new DefaultBindableChannelFactory(compositeMessageChannelConfigurer);
public SubscribableChannelBindingTargetFactory channelFactory(
CompositeMessageChannelConfigurer compositeMessageChannelConfigurer) {
return new SubscribableChannelBindingTargetFactory(compositeMessageChannelConfigurer);
}
@Bean
@@ -140,17 +139,17 @@ public class ChannelBindingServiceConfiguration {
}
@Bean
public BinderAwareChannelResolver binderAwareChannelResolver(ChannelBindingService channelBindingService,
BindableChannelFactory bindableChannelFactory, DynamicDestinationsBindable dynamicDestinationsBindable) {
return new BinderAwareChannelResolver(channelBindingService, bindableChannelFactory,
dynamicDestinationsBindable);
public BinderAwareChannelResolver binderAwareChannelResolver(BindingService bindingService,
AbstractBindingTargetFactory<? extends MessageChannel> bindingTargetFactory,
DynamicDestinationsBindable dynamicDestinationsBindable) {
return new BinderAwareChannelResolver(bindingService, bindingTargetFactory, dynamicDestinationsBindable);
}
@Bean
@ConditionalOnProperty("spring.cloud.stream.bindings." + ERROR_CHANNEL_NAME + ".destination")
public SingleChannelBindable errorChannelBindable(
public SingleBindingTargetBindable<MessageChannel> errorChannelBindable(
@Qualifier(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME) PublishSubscribeChannel errorChannel) {
return new SingleChannelBindable(ERROR_CHANNEL_NAME, errorChannel);
return new SingleBindingTargetBindable<MessageChannel>(ERROR_CHANNEL_NAME, errorChannel);
}
@Bean
@@ -171,8 +170,8 @@ public class ChannelBindingServiceConfiguration {
public static MessageHandlerMethodFactory messageHandlerMethodFactory(
CompositeMessageConverterFactory compositeMessageConverterFactory) {
DefaultMessageHandlerMethodFactory messageHandlerMethodFactory = new DefaultMessageHandlerMethodFactory();
messageHandlerMethodFactory.setMessageConverter(
compositeMessageConverterFactory.getMessageConverterForAllRegistered());
messageHandlerMethodFactory
.setMessageConverter(compositeMessageConverterFactory.getMessageConverterForAllRegistered());
return messageHandlerMethodFactory;
}
@@ -180,8 +179,14 @@ public class ChannelBindingServiceConfiguration {
public static StreamListenerAnnotationBeanPostProcessor bindToAnnotationBeanPostProcessor(
@Lazy BinderAwareChannelResolver binderAwareChannelResolver,
@Lazy MessageHandlerMethodFactory messageHandlerMethodFactory) {
return new StreamListenerAnnotationBeanPostProcessor(binderAwareChannelResolver,
messageHandlerMethodFactory);
return new StreamListenerAnnotationBeanPostProcessor(binderAwareChannelResolver, messageHandlerMethodFactory);
}
@Bean
// provided for backwards compatibility scenarios
public ChannelBindingServiceProperties channelBindingServiceProperties(
BindingServiceProperties bindingServiceProperties) {
return new ChannelBindingServiceProperties(bindingServiceProperties);
}
// IMPORTANT: Nested class to avoid instantiating all of the above early
@@ -194,22 +199,18 @@ public class ChannelBindingServiceConfiguration {
public BinderAwareRouterBeanPostProcessor binderAwareRouterBeanPostProcessor(
final ConfigurableListableBeanFactory beanFactory) {
// IMPORTANT: Lazy delegate to avoid instantiating all of the above early
return new BinderAwareRouterBeanPostProcessor(
new DestinationResolver<MessageChannel>() {
return new BinderAwareRouterBeanPostProcessor(new DestinationResolver<MessageChannel>() {
@Override
public MessageChannel resolveDestination(String name)
throws DestinationResolutionException {
if (PostProcessorConfiguration.this.binderAwareChannelResolver == null) {
PostProcessorConfiguration.this.binderAwareChannelResolver = BeanFactoryUtils
.beanOfType(beanFactory,
BinderAwareChannelResolver.class);
}
return PostProcessorConfiguration.this.binderAwareChannelResolver
.resolveDestination(name);
}
@Override
public MessageChannel resolveDestination(String name) throws DestinationResolutionException {
if (PostProcessorConfiguration.this.binderAwareChannelResolver == null) {
PostProcessorConfiguration.this.binderAwareChannelResolver = BeanFactoryUtils
.beanOfType(beanFactory, BinderAwareChannelResolver.class);
}
return PostProcessorConfiguration.this.binderAwareChannelResolver.resolveDestination(name);
}
});
});
}
/**
@@ -225,8 +226,7 @@ public class ChannelBindingServiceConfiguration {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME.equals(beanName)) {
IntegrationEvaluationContextFactoryBean factoryBean =
(IntegrationEvaluationContextFactoryBean) bean;
IntegrationEvaluationContextFactoryBean factoryBean = (IntegrationEvaluationContextFactoryBean) bean;
Map<String, PropertyAccessor> factoryBeanAccessors = factoryBean.getPropertyAccessors();
for (Map.Entry<String, PropertyAccessor> entry : accessors.entrySet()) {
if (!factoryBeanAccessors.containsKey(entry.getKey())) {

View File

@@ -0,0 +1,209 @@
/*
* Copyright 2015-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.HashMap;
import java.util.Map;
import java.util.TreeMap;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.stream.binder.ConsumerProperties;
import org.springframework.cloud.stream.binder.ProducerProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.convert.ConversionService;
import org.springframework.integration.support.utils.IntegrationUtils;
import org.springframework.util.Assert;
/**
* @author Dave Syer
* @author Marius Bogoevici
* @author Gary Russell
* @author Ilayaperumal Gopinathan
*/
@ConfigurationProperties("spring.cloud.stream")
@JsonInclude(Include.NON_DEFAULT)
public class BindingServiceProperties
implements ApplicationContextAware, InitializingBean {
private ConversionService conversionService;
@Value("${INSTANCE_INDEX:${CF_INSTANCE_INDEX:0}}")
private int instanceIndex;
private int instanceCount = 1;
private Map<String, BindingProperties> bindings = new TreeMap<>(
String.CASE_INSENSITIVE_ORDER);
private Map<String, BinderProperties> binders = new HashMap<>();
private String defaultBinder;
private String[] dynamicDestinations = new String[0];
private ConfigurableApplicationContext applicationContext;
public Map<String, BindingProperties> getBindings() {
return this.bindings;
}
public void setBindings(Map<String, BindingProperties> bindings) {
this.bindings = bindings;
}
public Map<String, BinderProperties> getBinders() {
return this.binders;
}
public void setBinders(Map<String, BinderProperties> binders) {
this.binders = binders;
}
public String getDefaultBinder() {
return this.defaultBinder;
}
public void setDefaultBinder(String defaultBinder) {
this.defaultBinder = defaultBinder;
}
public int getInstanceIndex() {
return this.instanceIndex;
}
public void setInstanceIndex(int instanceIndex) {
this.instanceIndex = instanceIndex;
}
public int getInstanceCount() {
return this.instanceCount;
}
public void setInstanceCount(int instanceCount) {
this.instanceCount = instanceCount;
}
public String[] getDynamicDestinations() {
return this.dynamicDestinations;
}
public void setDynamicDestinations(String[] dynamicDestinations) {
this.dynamicDestinations = dynamicDestinations;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
// override the bindings store with the environment-initializing version if in a
// Spring context
this.bindings = new EnvironmentEntryInitializingTreeMap<>(this.applicationContext,
BindingProperties.class, "spring.cloud.stream.default",
new TreeMap<String, BindingProperties>(String.CASE_INSENSITIVE_ORDER));
}
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
@Override
public void afterPropertiesSet() throws Exception {
if (this.conversionService == null) {
this.conversionService = this.applicationContext.getBean(
IntegrationUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME,
ConversionService.class);
}
}
public String getBinder(String bindingName) {
return getBindingProperties(bindingName).getBinder();
}
/**
* Return configuration properties as Map.
* @return map of binding configuration properties.
*/
public Map<String, Object> asMapProperties() {
Map<String, Object> properties = new HashMap<>();
properties.put("instanceIndex", String.valueOf(getInstanceIndex()));
properties.put("instanceCount", String.valueOf(getInstanceCount()));
properties.put("defaultBinder", getDefaultBinder());
properties.put("dynamicDestinations", getDynamicDestinations());
for (Map.Entry<String, BindingProperties> entry : this.bindings.entrySet()) {
properties.put(entry.getKey(), entry.getValue().toString());
}
for (Map.Entry<String, BinderProperties> entry : this.binders.entrySet()) {
properties.put(entry.getKey(), entry.getValue());
}
return properties;
}
public ConsumerProperties getConsumerProperties(String inputBindingName) {
Assert.notNull(inputBindingName, "The input binding name cannot be null");
ConsumerProperties consumerProperties = getBindingProperties(inputBindingName)
.getConsumer();
if (consumerProperties == null) {
consumerProperties = new ConsumerProperties();
}
// propagate instance count and instance index if not already set
if (consumerProperties.getInstanceCount() < 0) {
consumerProperties.setInstanceCount(this.instanceCount);
}
if (consumerProperties.getInstanceIndex() < 0) {
consumerProperties.setInstanceIndex(this.instanceIndex);
}
return consumerProperties;
}
public ProducerProperties getProducerProperties(String outputBindingName) {
Assert.notNull(outputBindingName, "The output binding name cannot be null");
ProducerProperties producerProperties = getBindingProperties(outputBindingName)
.getProducer();
if (producerProperties == null) {
producerProperties = new ProducerProperties();
}
return producerProperties;
}
public BindingProperties getBindingProperties(String bindingName) {
BindingProperties bindingProperties = new BindingProperties();
if (this.bindings.containsKey(bindingName)) {
BeanUtils.copyProperties(this.bindings.get(bindingName), bindingProperties);
}
if (bindingProperties.getDestination() == null) {
bindingProperties.setDestination(bindingName);
}
return bindingProperties;
}
public String getGroup(String bindingName) {
return getBindingProperties(bindingName).getGroup();
}
public String getBindingDestination(String bindingName) {
return getBindingProperties(bindingName).getDestination();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2016 the original author or authors.
* 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.
@@ -16,187 +16,114 @@
package org.springframework.cloud.stream.config;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.stream.binder.ConsumerProperties;
import org.springframework.cloud.stream.binder.ProducerProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.convert.ConversionService;
import org.springframework.integration.support.utils.IntegrationUtils;
import org.springframework.util.Assert;
/**
* @author Dave Syer
* Provides a wrapper around {@link BindingServiceProperties} for backwards compatibility.
* @author Marius Bogoevici
* @author Gary Russell
* @author Ilayaperumal Gopinathan
*/
@ConfigurationProperties("spring.cloud.stream")
@JsonInclude(Include.NON_DEFAULT)
public class ChannelBindingServiceProperties implements ApplicationContextAware, InitializingBean {
@Deprecated
public class ChannelBindingServiceProperties {
private ConversionService conversionService;
private final BindingServiceProperties bindingServiceProperties;
@Value("${INSTANCE_INDEX:${CF_INSTANCE_INDEX:0}}")
private int instanceIndex;
private int instanceCount = 1;
private Map<String, BindingProperties> bindings = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private Map<String, BinderProperties> binders = new HashMap<>();
private String defaultBinder;
private String[] dynamicDestinations = new String[0];
private ConfigurableApplicationContext applicationContext;
public ChannelBindingServiceProperties(
BindingServiceProperties bindingServiceProperties) {
this.bindingServiceProperties = bindingServiceProperties;
}
public Map<String, BindingProperties> getBindings() {
return this.bindings;
return bindingServiceProperties.getBindings();
}
public void setBindings(Map<String, BindingProperties> bindings) {
this.bindings = bindings;
bindingServiceProperties.setBindings(bindings);
}
public Map<String, BinderProperties> getBinders() {
return this.binders;
return bindingServiceProperties.getBinders();
}
public void setBinders(Map<String, BinderProperties> binders) {
this.binders = binders;
bindingServiceProperties.setBinders(binders);
}
public String getDefaultBinder() {
return this.defaultBinder;
return bindingServiceProperties.getDefaultBinder();
}
public void setDefaultBinder(String defaultBinder) {
this.defaultBinder = defaultBinder;
bindingServiceProperties.setDefaultBinder(defaultBinder);
}
public int getInstanceIndex() {
return this.instanceIndex;
return bindingServiceProperties.getInstanceIndex();
}
public void setInstanceIndex(int instanceIndex) {
this.instanceIndex = instanceIndex;
bindingServiceProperties.setInstanceIndex(instanceIndex);
}
public int getInstanceCount() {
return this.instanceCount;
return bindingServiceProperties.getInstanceCount();
}
public void setInstanceCount(int instanceCount) {
this.instanceCount = instanceCount;
bindingServiceProperties.setInstanceCount(instanceCount);
}
public String[] getDynamicDestinations() {
return this.dynamicDestinations;
return bindingServiceProperties.getDynamicDestinations();
}
public void setDynamicDestinations(String[] dynamicDestinations) {
this.dynamicDestinations = dynamicDestinations;
bindingServiceProperties.setDynamicDestinations(dynamicDestinations);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
// override the bindings store with the environment-initializing version if in a Spring context
this.bindings = new EnvironmentEntryInitializingTreeMap<>(this.applicationContext, BindingProperties.class,
"spring.cloud.stream.default", new TreeMap<String, BindingProperties>(String.CASE_INSENSITIVE_ORDER));
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
bindingServiceProperties.setApplicationContext(applicationContext);
}
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
bindingServiceProperties.setConversionService(conversionService);
}
@Override
public void afterPropertiesSet() throws Exception {
if (this.conversionService == null) {
this.conversionService = this.applicationContext.getBean(
IntegrationUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME, ConversionService.class);
}
bindingServiceProperties.afterPropertiesSet();
}
public String getBinder(String channelName) {
return getBindingProperties(channelName).getBinder();
return bindingServiceProperties.getBinder(channelName);
}
/**
* Return configuration properties as Map.
* @return map of channel binding configuration properties.
*/
public Map<String, Object> asMapProperties() {
Map<String, Object> properties = new HashMap<>();
properties.put("instanceIndex", String.valueOf(getInstanceIndex()));
properties.put("instanceCount", String.valueOf(getInstanceCount()));
properties.put("defaultBinder", getDefaultBinder());
properties.put("dynamicDestinations", getDynamicDestinations());
for (Map.Entry<String, BindingProperties> entry : this.bindings.entrySet()) {
properties.put(entry.getKey(), entry.getValue().toString());
}
for (Map.Entry<String, BinderProperties> entry : this.binders.entrySet()) {
properties.put(entry.getKey(), entry.getValue());
}
return properties;
return bindingServiceProperties.asMapProperties();
}
public ConsumerProperties getConsumerProperties(String inputChannelName) {
Assert.notNull(inputChannelName, "The input channel name cannot be null");
ConsumerProperties consumerProperties = getBindingProperties(inputChannelName).getConsumer();
if (consumerProperties == null) {
consumerProperties = new ConsumerProperties();
}
// propagate instance count and instance index if not already set
if (consumerProperties.getInstanceCount() < 0) {
consumerProperties.setInstanceCount(this.instanceCount);
}
if (consumerProperties.getInstanceIndex() < 0) {
consumerProperties.setInstanceIndex(this.instanceIndex);
}
return consumerProperties;
return bindingServiceProperties.getConsumerProperties(inputChannelName);
}
public ProducerProperties getProducerProperties(String outputChannelName) {
Assert.notNull(outputChannelName, "The output channel name cannot be null");
ProducerProperties producerProperties = getBindingProperties(outputChannelName).getProducer();
if (producerProperties == null) {
producerProperties = new ProducerProperties();
}
return producerProperties;
return bindingServiceProperties.getProducerProperties(outputChannelName);
}
public BindingProperties getBindingProperties(String channelName) {
BindingProperties bindingProperties = new BindingProperties();
if (this.bindings.containsKey(channelName)) {
BeanUtils.copyProperties(this.bindings.get(channelName), bindingProperties);
}
if (bindingProperties.getDestination() == null) {
bindingProperties.setDestination(channelName);
}
return bindingProperties;
return bindingServiceProperties.getBindingProperties(channelName);
}
public String getGroup(String channelName) {
return getBindingProperties(channelName).getGroup();
return bindingServiceProperties.getGroup(channelName);
}
public String getBindingDestination(String channelName) {
return getBindingProperties(channelName).getDestination();
return bindingServiceProperties.getBindingDestination(channelName);
}
}

View File

@@ -1,3 +1,19 @@
/*
* 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.AbstractMap;

View File

@@ -29,10 +29,11 @@ import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.cloud.stream.binding.Bindable;
import org.springframework.cloud.stream.config.BindingProperties;
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
import org.springframework.cloud.stream.config.BindingServiceProperties;
/**
* An {@link Endpoint} that has the binding information on all the {@link Bindable} message channels.
* An {@link Endpoint} that has the binding information on all the {@link Bindable}
* message channels.
*
* @author Dave Syer
* @author Ilayaperumal Gopinathan
@@ -41,9 +42,9 @@ public class ChannelsEndpoint extends AbstractEndpoint<Map<String, Object>> {
private List<Bindable> adapters;
private ChannelBindingServiceProperties properties;
private BindingServiceProperties properties;
public ChannelsEndpoint(List<Bindable> adapters, ChannelBindingServiceProperties properties) {
public ChannelsEndpoint(List<Bindable> adapters, BindingServiceProperties properties) {
super("channels");
this.adapters = adapters;
this.properties = properties;

View File

@@ -0,0 +1,32 @@
/*
* 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.internal;
/**
* Contains the names of properties for the internal use of Spring Cloud Stream.
*
* @author Marius Bogoevici
*/
public abstract class InternalPropertyNames {
public static final String SPRING_CLOUD_STREAM_INTERNAL_PREFIX = "spring.cloud.stream.internal";
public static final String NAMESPACE_PROPERTY_NAME = SPRING_CLOUD_STREAM_INTERNAL_PREFIX + ".namespace";
public static final String SELF_CONTAINED_APP_PROPERTY_NAME = SPRING_CLOUD_STREAM_INTERNAL_PREFIX
+ ".selfContained";
}

View File

@@ -0,0 +1,88 @@
/*
* 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.reflection;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
/**
* Internal utilities for handling generics.
*
* @author Marius Bogoevici
*/
public abstract class GenericsUtils {
/**
* For a specific class that implements or extends a parametrized type
* returns the parameter of that interface at a given position. For example,
* for this class:
* <pre>
* {@code
* class MessageChannelBinder implements Binder<MessageChannel, ?, ?>
* }
*
* <pre>
* {@code
* getParameterType(MessageChannelBinder.class, Binder.class, 0);
* }
*
* will return {@code Binder}
*
* @param evaluatedClass the evaluated class
* @param interfaceClass the parametrized interface
* @param position the position
* @return the parameter type if any
* @throws IllegalStateException if the evaluated class does not implement the interface or
*/
public static Class<?> getParameterType(Class<?> evaluatedClass, Class<?> interfaceClass, int position) {
Class<?> bindableType = null;
Assert.isTrue(interfaceClass.isInterface(), "'interfaceClass' must be an interface");
if (!interfaceClass.isAssignableFrom(evaluatedClass)) {
throw new IllegalStateException(evaluatedClass + " does not implement " + interfaceClass);
}
ResolvableType currentType = ResolvableType.forType(evaluatedClass);
while (!Object.class.equals(currentType.getRawClass()) && bindableType == null) {
ResolvableType[] interfaces = currentType.getInterfaces();
ResolvableType resolvableType = null;
for (ResolvableType interfaceType : interfaces) {
if (interfaceClass.equals(interfaceType.getRawClass())) {
resolvableType = interfaceType;
break;
}
}
if (resolvableType == null) {
currentType = currentType.getSuperType();
}
else {
ResolvableType[] generics = resolvableType.getGenerics();
ResolvableType generic = generics[position];
Class<?> resolvedParameter = generic.resolve();
if (resolvedParameter != null) {
bindableType = resolvedParameter;
}
else {
bindableType = Object.class;
}
}
}
if (bindableType == null) {
throw new IllegalStateException("Cannot find parameter of " + evaluatedClass.getName() + " for "
+ interfaceClass + " at position " + position);
}
return bindableType;
}
}

View File

@@ -1,2 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration:\
org.springframework.cloud.stream.config.ChannelBindingAutoConfiguration
org.springframework.cloud.stream.config.BindingAutoConfiguration

View File

@@ -27,9 +27,10 @@ import org.springframework.beans.DirectFieldAccessor;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.stream.aggregate.AggregateApplicationBuilder;
import org.springframework.cloud.stream.aggregate.AggregateApplicationBuilder.SourceConfigurer;
import org.springframework.cloud.stream.aggregate.SharedBindingTargetRegistry;
import org.springframework.cloud.stream.aggregate.SharedChannelRegistry;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.binding.BindableChannelFactory;
import org.springframework.cloud.stream.binding.BindingTargetFactory;
import org.springframework.cloud.stream.messaging.Processor;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
@@ -66,10 +67,26 @@ public class AggregationTest {
.from(TestSource.class)
.to(TestProcessor.class)
.run();
SharedBindingTargetRegistry sharedBindingTargetRegistry = aggregatedApplicationContext
.getBean(SharedBindingTargetRegistry.class);
BindingTargetFactory channelFactory = aggregatedApplicationContext
.getBean(BindingTargetFactory.class);
assertThat(channelFactory).isNotNull();
assertThat(sharedBindingTargetRegistry.getAll().keySet()).hasSize(2);
aggregatedApplicationContext.close();
}
@Test
public void testModuleAggregationUsingSharedChannelRegistry() {
// test backward compatibility
aggregatedApplicationContext = new AggregateApplicationBuilder(
MockBinderRegistryConfiguration.class, "--server.port=0")
.from(TestSource.class).to(TestProcessor.class).run();
SharedChannelRegistry sharedChannelRegistry = aggregatedApplicationContext
.getBean(SharedChannelRegistry.class);
BindableChannelFactory channelFactory = aggregatedApplicationContext
.getBean(BindableChannelFactory.class);
BindingTargetFactory channelFactory = aggregatedApplicationContext
.getBean(BindingTargetFactory.class);
assertThat(channelFactory).isNotNull();
assertThat(sharedChannelRegistry.getAll().keySet()).hasSize(2);
aggregatedApplicationContext.close();
@@ -301,8 +318,8 @@ public class AggregationTest {
.namespace("bar").run();
SharedChannelRegistry sharedChannelRegistry = aggregatedApplicationContext
.getBean(SharedChannelRegistry.class);
BindableChannelFactory channelFactory = aggregatedApplicationContext
.getBean(BindableChannelFactory.class);
BindingTargetFactory channelFactory = aggregatedApplicationContext
.getBean(BindingTargetFactory.class);
Object fooOutput = sharedChannelRegistry.get("foo.output");
assertThat(fooOutput).isNotNull();
assertThat(fooOutput).isInstanceOf(MessageChannel.class);

View File

@@ -27,6 +27,7 @@ import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.mockito.Matchers.anyString;
@@ -51,7 +52,7 @@ public class ArbitraryInterfaceWithBindingTargetsTests {
@SuppressWarnings("unchecked")
@Test
public void testArbitraryInterfaceChannelsBound() {
Binder binder = binderFactory.getBinder(null);
Binder binder = binderFactory.getBinder(null, MessageChannel.class);
verify(binder).bindConsumer(eq("someQueue.0"), anyString(), eq(this.fooChannels.foo()),
Mockito.<ConsumerProperties>any());
verify(binder).bindConsumer(eq("someQueue.1"), anyString(), eq(this.fooChannels.bar()),

View File

@@ -26,6 +26,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.mockito.Matchers.anyString;
@@ -50,7 +51,7 @@ public class ArbitraryInterfaceWithDefaultsTests {
@SuppressWarnings("unchecked")
@Test
public void testArbitraryInterfaceChannelsBound() {
final Binder binder = this.binderFactory.getBinder(null);
final Binder binder = this.binderFactory.getBinder(null, MessageChannel.class);
verify(binder).bindConsumer(eq("foo"), anyString(), eq(this.fooChannels.foo()),
Mockito.<ConsumerProperties>any());
verify(binder).bindConsumer(eq("bar"), anyString(), eq(this.fooChannels.bar()),

View File

@@ -31,16 +31,16 @@ import org.mockito.Mockito;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.cloud.stream.binding.BindableChannelFactory;
import org.springframework.cloud.stream.binding.AbstractBindingTargetFactory;
import org.springframework.cloud.stream.binding.BinderAwareChannelResolver;
import org.springframework.cloud.stream.binding.ChannelBindingService;
import org.springframework.cloud.stream.binding.DefaultBindableChannelFactory;
import org.springframework.cloud.stream.binding.BindingService;
import org.springframework.cloud.stream.binding.DynamicDestinationsBindable;
import org.springframework.cloud.stream.binding.InputBindingLifecycle;
import org.springframework.cloud.stream.binding.MessageConverterConfigurer;
import org.springframework.cloud.stream.binding.OutputBindingLifecycle;
import org.springframework.cloud.stream.binding.SubscribableChannelBindingTargetFactory;
import org.springframework.cloud.stream.config.BindingProperties;
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
import org.springframework.cloud.stream.config.BindingServiceProperties;
import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.integration.channel.DirectChannel;
@@ -75,9 +75,9 @@ public class BinderAwareChannelResolverTests {
protected volatile Binder<MessageChannel, ConsumerProperties, ProducerProperties> binder;
protected volatile BindableChannelFactory bindableChannelFactory;
protected volatile AbstractBindingTargetFactory<? extends MessageChannel> bindingTargetFactory;
protected volatile ChannelBindingServiceProperties channelBindingServiceProperties;
protected volatile BindingServiceProperties bindingServiceProperties;
protected volatile DynamicDestinationsBindable dynamicDestinationsBindable;
@@ -87,29 +87,29 @@ public class BinderAwareChannelResolverTests {
public void setupContext() throws Exception {
producerBindings = new ArrayList<>();
this.binder = new TestBinder();
BinderFactory binderFactory = new BinderFactory<MessageChannel>() {
BinderFactory binderFactory = new BinderFactory() {
@Override
public Binder<MessageChannel, ConsumerProperties, ProducerProperties> getBinder(String configurationName) {
return binder;
public <T> Binder<T, ? extends ConsumerProperties, ? extends ProducerProperties> getBinder(String configurationName, Class<? extends T> bindableType) {
return (Binder<T, ? extends ConsumerProperties, ? extends ProducerProperties>) binder;
}
};
this.channelBindingServiceProperties = new ChannelBindingServiceProperties();
this.bindingServiceProperties = new BindingServiceProperties();
Map<String, BindingProperties> bindings = new HashMap<>();
BindingProperties bindingProperties = new BindingProperties();
bindingProperties.setContentType("text/plain");
bindings.put("foo", bindingProperties);
this.channelBindingServiceProperties.setBindings(bindings);
ChannelBindingService channelBindingService = new ChannelBindingService(channelBindingServiceProperties,
this.bindingServiceProperties.setBindings(bindings);
BindingService bindingService = new BindingService(bindingServiceProperties,
binderFactory);
MessageConverterConfigurer messageConverterConfigurer = new MessageConverterConfigurer(
this.channelBindingServiceProperties,
this.bindingServiceProperties,
new CompositeMessageConverterFactory());
messageConverterConfigurer.setBeanFactory(Mockito.mock(ConfigurableListableBeanFactory.class));
messageConverterConfigurer.afterPropertiesSet();
this.bindableChannelFactory = new DefaultBindableChannelFactory(messageConverterConfigurer);
this.bindingTargetFactory = new SubscribableChannelBindingTargetFactory(messageConverterConfigurer);
dynamicDestinationsBindable = new DynamicDestinationsBindable();
this.resolver = new BinderAwareChannelResolver(channelBindingService, this.bindableChannelFactory,
this.resolver = new BinderAwareChannelResolver(bindingService, this.bindingTargetFactory,
dynamicDestinationsBindable);
this.resolver.setBeanFactory(context.getBeanFactory());
context.getBeanFactory().registerSingleton("channelResolver", this.resolver);
@@ -117,7 +117,7 @@ public class BinderAwareChannelResolverTests {
context.registerSingleton("other", DirectChannel.class);
context.registerSingleton(IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME,
DefaultMessageBuilderFactory.class);
context.getBeanFactory().registerSingleton("channelBindingService", channelBindingService);
context.getBeanFactory().registerSingleton("bindingService", bindingService);
context.registerSingleton("inputBindingLifecycle", InputBindingLifecycle.class);
context.registerSingleton("outputBindingLifecycle", OutputBindingLifecycle.class);
context.refresh();
@@ -171,24 +171,24 @@ public class BinderAwareChannelResolverTests {
BindingProperties genericProperties = new BindingProperties();
genericProperties.setContentType("text/plain");
bindings.put("foo", genericProperties);
this.channelBindingServiceProperties.setBindings(bindings);
this.bindingServiceProperties.setBindings(bindings);
@SuppressWarnings("unchecked")
Binder binder = mock(Binder.class);
Binder binder2 = mock(Binder.class);
BinderFactory<MessageChannel> mockBinderFactory = Mockito.mock(BinderFactory.class);
BinderFactory mockBinderFactory = Mockito.mock(BinderFactory.class);
Binding<MessageChannel> fooBinding = Mockito.mock(Binding.class);
Binding<MessageChannel> barBinding = Mockito.mock(Binding.class);
when(binder.bindProducer(
matches("foo"), any(DirectChannel.class), any(ProducerProperties.class))).thenReturn(fooBinding);
when(binder2.bindProducer(
matches("bar"), any(DirectChannel.class), any(ProducerProperties.class))).thenReturn(barBinding);
when(mockBinderFactory.getBinder(null)).thenReturn(binder);
when(mockBinderFactory.getBinder("someTransport")).thenReturn(binder2);
ChannelBindingService channelBindingService = new ChannelBindingService(channelBindingServiceProperties,
when(mockBinderFactory.getBinder(null, DirectChannel.class)).thenReturn(binder);
when(mockBinderFactory.getBinder("someTransport", DirectChannel.class)).thenReturn(binder2);
BindingService bindingService = new BindingService(bindingServiceProperties,
mockBinderFactory);
@SuppressWarnings("unchecked")
BinderAwareChannelResolver resolver =
new BinderAwareChannelResolver(channelBindingService, this.bindableChannelFactory,
new BinderAwareChannelResolver(bindingService, this.bindingTargetFactory,
new DynamicDestinationsBindable());
BeanFactory beanFactory = new DefaultListableBeanFactory();
resolver.setBeanFactory(beanFactory);

View File

@@ -36,6 +36,7 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.messaging.MessageChannel;
import org.springframework.util.ObjectUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -95,10 +96,10 @@ public class BinderFactoryConfigurationTests {
BinderFactory binderFactory = context.getBean(BinderFactory.class);
Binder binder1 = binderFactory.getBinder("binder1");
Binder binder1 = binderFactory.getBinder("binder1", MessageChannel.class);
assertThat(binder1).isInstanceOf(StubBinder1.class);
Binder defaultBinder = binderFactory.getBinder(null);
Binder defaultBinder = binderFactory.getBinder(null, MessageChannel.class);
assertThat(defaultBinder).isSameAs(binder1);
}
@@ -109,7 +110,7 @@ public class BinderFactoryConfigurationTests {
BinderFactory binderFactory = context.getBean(BinderFactory.class);
Binder binder1 = binderFactory.getBinder("binder1");
Binder binder1 = binderFactory.getBinder("binder1", MessageChannel.class);
assertThat(binder1).hasFieldOrPropertyWithValue("name", "foo");
}
@@ -122,10 +123,10 @@ public class BinderFactoryConfigurationTests {
BinderFactory binderFactory = context.getBean(BinderFactory.class);
Binder binder1 = binderFactory.getBinder("custom");
Binder binder1 = binderFactory.getBinder("custom", MessageChannel.class);
assertThat(binder1).hasFieldOrPropertyWithValue("name", "foo");
assertThat(binderFactory.getBinder(null)).isSameAs(binder1);
assertThat(binderFactory.getBinder(null, MessageChannel.class)).isSameAs(binder1);
}
@Test
@@ -138,7 +139,7 @@ public class BinderFactoryConfigurationTests {
BinderFactory binderFactory = context.getBean(BinderFactory.class);
Binder binder1 = binderFactory.getBinder("custom");
Binder binder1 = binderFactory.getBinder("custom", MessageChannel.class);
assertThat(binder1).hasFieldOrPropertyWithValue("name", null);
}
@@ -157,18 +158,18 @@ public class BinderFactoryConfigurationTests {
BinderFactory binderFactory = context.getBean(BinderFactory.class);
try {
binderFactory.getBinder(null);
binderFactory.getBinder(null, MessageChannel.class);
fail();
}
catch (Exception e) {
assertThat(e).isInstanceOf(IllegalStateException.class);
assertThat(e.getMessage()).contains(
"A default binder has been requested, but there is more than one binder available:");
"A default binder has been requested, but there is more than one binder available");
}
Binder binder1 = binderFactory.getBinder("binder1");
Binder binder1 = binderFactory.getBinder("binder1", MessageChannel.class);
assertThat(binder1).isInstanceOf(StubBinder1.class);
Binder binder2 = binderFactory.getBinder("binder2");
Binder binder2 = binderFactory.getBinder("binder2", MessageChannel.class);
assertThat(binder2).isInstanceOf(StubBinder2.class);
}
@@ -190,15 +191,15 @@ public class BinderFactoryConfigurationTests {
BinderFactory binderFactory = context.getBean(BinderFactory.class);
Binder defaultBinder = binderFactory.getBinder(null);
Binder defaultBinder = binderFactory.getBinder(null, MessageChannel.class);
assertThat(defaultBinder).isInstanceOf(StubBinder1.class);
assertThat(((StubBinder1) defaultBinder).getName()).isNullOrEmpty();
Binder binder1 = binderFactory.getBinder("binder1");
Binder binder1 = binderFactory.getBinder("binder1", MessageChannel.class);
assertThat(binder1).isInstanceOf(StubBinder1.class);
assertThat(binder1).isSameAs(defaultBinder);
Binder custom = binderFactory.getBinder("custom");
Binder custom = binderFactory.getBinder("custom", MessageChannel.class);
assertThat(custom).isInstanceOf(StubBinder1.class);
assertThat(custom).isNotSameAs(defaultBinder);
assertThat(((StubBinder1) custom).getName()).isEqualTo("foo");
@@ -222,12 +223,12 @@ public class BinderFactoryConfigurationTests {
BinderFactory binderFactory = context.getBean(BinderFactory.class);
Binder binder1 = binderFactory.getBinder("binder1");
Binder binder1 = binderFactory.getBinder("binder1", MessageChannel.class);
assertThat(binder1).isInstanceOf(StubBinder1.class);
Binder binder2 = binderFactory.getBinder("binder2");
Binder binder2 = binderFactory.getBinder("binder2", MessageChannel.class);
assertThat(binder2).isInstanceOf(StubBinder2.class);
Binder defaultBinder = binderFactory.getBinder(null);
Binder defaultBinder = binderFactory.getBinder(null, MessageChannel.class);
assertThat(defaultBinder).isSameAs(binder2);
}

View File

@@ -43,10 +43,10 @@ public class ErrorBindingTests {
public void testErrorChannelNotBoundByDefault() {
ConfigurableApplicationContext applicationContext = SpringApplication.run(TestProcessor.class, "--server.port=0");
BinderFactory<?> binderFactory = applicationContext.getBean(BinderFactory.class);
BinderFactory binderFactory = applicationContext.getBean(BinderFactory.class);
@SuppressWarnings("unchecked")
Binder binder = binderFactory.getBinder(null);
Binder binder = binderFactory.getBinder(null, MessageChannel.class);
Mockito.verify(binder).bindConsumer(eq("input"), isNull(String.class), any(MessageChannel.class), any(ConsumerProperties.class));
Mockito.verify(binder).bindProducer(eq("output"), any(MessageChannel.class), any(ProducerProperties.class));
@@ -59,10 +59,10 @@ public class ErrorBindingTests {
ConfigurableApplicationContext applicationContext =
SpringApplication.run(TestProcessor.class, "--spring.cloud.stream.bindings.error.destination=foo", "--server.port=0");
BinderFactory<?> binderFactory = applicationContext.getBean(BinderFactory.class);
BinderFactory binderFactory = applicationContext.getBean(BinderFactory.class, MessageChannel.class);
@SuppressWarnings("unchecked")
Binder binder = binderFactory.getBinder(null);
Binder binder = binderFactory.getBinder(null, MessageChannel.class);
MessageChannel errorChannel = applicationContext.getBean(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME,
MessageChannel.class);

View File

@@ -30,14 +30,14 @@ import org.mockito.Mockito;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.cloud.stream.binding.BinderAwareChannelResolver;
import org.springframework.cloud.stream.binding.ChannelBindingService;
import org.springframework.cloud.stream.binding.DefaultBindableChannelFactory;
import org.springframework.cloud.stream.binding.BindingService;
import org.springframework.cloud.stream.binding.DynamicDestinationsBindable;
import org.springframework.cloud.stream.binding.InputBindingLifecycle;
import org.springframework.cloud.stream.binding.MessageConverterConfigurer;
import org.springframework.cloud.stream.binding.OutputBindingLifecycle;
import org.springframework.cloud.stream.binding.SubscribableChannelBindingTargetFactory;
import org.springframework.cloud.stream.config.BindingProperties;
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
import org.springframework.cloud.stream.config.BindingServiceProperties;
import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.support.DefaultMessageBuilderFactory;
@@ -68,29 +68,29 @@ public class ExtendedPropertiesBinderAwareChannelResolverTests extends BinderAwa
public void setupContext() throws Exception {
producerBindings = new ArrayList<>();
this.binder = new TestBinder();
BinderFactory binderFactory = new BinderFactory<MessageChannel>() {
BinderFactory binderFactory = new BinderFactory() {
@Override
public ExtendedPropertiesBinder<MessageChannel, ExtendedConsumerProperties, ExtendedProducerProperties> getBinder(String configurationName) {
return binder;
public <T> Binder<T, ? extends ConsumerProperties, ? extends ProducerProperties> getBinder(String configurationName, Class<? extends T> bindableType) {
return (Binder<T, ? extends ConsumerProperties, ? extends ProducerProperties>) binder;
}
};
this.channelBindingServiceProperties = new ChannelBindingServiceProperties();
this.bindingServiceProperties = new BindingServiceProperties();
Map<String, BindingProperties> bindings = new HashMap<String, BindingProperties>();
BindingProperties bindingProperties = new BindingProperties();
bindingProperties.setContentType("text/plain");
bindings.put("foo", bindingProperties);
this.channelBindingServiceProperties.setBindings(bindings);
this.bindingServiceProperties.setBindings(bindings);
MessageConverterConfigurer messageConverterConfigurer = new MessageConverterConfigurer(
this.channelBindingServiceProperties,
this.bindingServiceProperties,
new CompositeMessageConverterFactory());
messageConverterConfigurer.setBeanFactory(Mockito.mock(ConfigurableListableBeanFactory.class));
messageConverterConfigurer.afterPropertiesSet();
this.bindableChannelFactory = new DefaultBindableChannelFactory(messageConverterConfigurer);
this.bindingTargetFactory = new SubscribableChannelBindingTargetFactory(messageConverterConfigurer);
dynamicDestinationsBindable = new DynamicDestinationsBindable();
ChannelBindingService channelBindingService = new ChannelBindingService(channelBindingServiceProperties,
BindingService bindingService = new BindingService(bindingServiceProperties,
binderFactory);
this.resolver = new BinderAwareChannelResolver(channelBindingService, this.bindableChannelFactory,
this.resolver = new BinderAwareChannelResolver(bindingService, this.bindingTargetFactory,
dynamicDestinationsBindable);
this.resolver.setBeanFactory(context.getBeanFactory());
context.getBeanFactory().registerSingleton("channelResolver", this.resolver);
@@ -98,7 +98,7 @@ public class ExtendedPropertiesBinderAwareChannelResolverTests extends BinderAwa
context.registerSingleton("other", DirectChannel.class);
context.registerSingleton(IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME,
DefaultMessageBuilderFactory.class);
context.getBeanFactory().registerSingleton("channelBindingService", channelBindingService);
context.getBeanFactory().registerSingleton("bindingService", bindingService);
context.registerSingleton("inputBindingLifecycle", InputBindingLifecycle.class);
context.registerSingleton("outputBindingLifecycle", OutputBindingLifecycle.class);
context.refresh();

View File

@@ -39,6 +39,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.messaging.MessageChannel;
import org.springframework.util.ObjectUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -54,9 +55,9 @@ public class HealthIndicatorsConfigurationTests {
public void healthIndicatorsCheck() throws Exception {
ConfigurableApplicationContext context = createBinderTestContext(new String[] { "binder1", "binder2" },
"spring.cloud.stream.defaultBinder:binder2");
Binder binder1 = context.getBean(BinderFactory.class).getBinder("binder1");
Binder binder1 = context.getBean(BinderFactory.class).getBinder("binder1", MessageChannel.class);
assertThat(binder1).isInstanceOf(StubBinder1.class);
Binder binder2 = context.getBean(BinderFactory.class).getBinder("binder2");
Binder binder2 = context.getBean(BinderFactory.class).getBinder("binder2", MessageChannel.class);
assertThat(binder2).isInstanceOf(StubBinder2.class);
CompositeHealthIndicator bindersHealthIndicator = context.getBean("bindersHealthIndicator",
CompositeHealthIndicator.class);
@@ -81,9 +82,9 @@ public class HealthIndicatorsConfigurationTests {
"spring.cloud.stream.defaultBinder:binder2",
"management.health.binders.enabled:false");
Binder binder1 = context.getBean(BinderFactory.class).getBinder("binder1");
Binder binder1 = context.getBean(BinderFactory.class).getBinder("binder1", MessageChannel.class);
assertThat(binder1).isInstanceOf(StubBinder1.class);
Binder binder2 = context.getBean(BinderFactory.class).getBinder("binder2");
Binder binder2 = context.getBean(BinderFactory.class).getBinder("binder2", MessageChannel.class);
assertThat(binder2).isInstanceOf(StubBinder2.class);
try {
context.getBean("bindersHealthIndicator", CompositeHealthIndicator.class);

View File

@@ -30,6 +30,7 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.messaging.MessageChannel;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.anyString;
@@ -48,7 +49,7 @@ public class InputOutputBindingOrderTest {
public void testInputOutputBindingOrder() {
ConfigurableApplicationContext applicationContext = SpringApplication.run(TestSource.class, "--server.port=-1");
@SuppressWarnings("rawtypes")
Binder binder = applicationContext.getBean(BinderFactory.class).getBinder(null);
Binder binder = applicationContext.getBean(BinderFactory.class).getBinder(null, MessageChannel.class);
Processor processor = applicationContext.getBean(Processor.class);
// input is bound after the context has been started
verify(binder).bindConsumer(eq("input"), anyString(), eq(processor.input()), Mockito.<ConsumerProperties>any());
@@ -84,7 +85,7 @@ public class InputOutputBindingOrderTest {
@Override
@SuppressWarnings("unchecked")
public synchronized void start() {
Binder binder = this.binderFactory.getBinder(null);
Binder binder = this.binderFactory.getBinder(null, MessageChannel.class);
verify(binder).bindProducer(eq("output"), eq(this.processor.output()), Mockito.<ProducerProperties>any());
// input was not bound yet
verifyNoMoreInteractions(binder);

View File

@@ -28,6 +28,7 @@ import org.springframework.cloud.stream.messaging.Processor;
import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.mockito.Matchers.anyString;
@@ -51,7 +52,7 @@ public class ProcessorBindingWithBindingTargetsTests {
@SuppressWarnings("unchecked")
@Test
public void testSourceOutputChannelBound() {
final Binder binder = binderFactory.getBinder(null);
final Binder binder = binderFactory.getBinder(null, MessageChannel.class);
verify(binder).bindConsumer(eq("testtock.0"), anyString(),
eq(this.testProcessor.input()), Mockito.<ConsumerProperties>any());
verify(binder).bindProducer(eq("testtock.1"), eq(this.testProcessor.output()),

View File

@@ -27,6 +27,7 @@ import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Processor;
import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.mockito.Matchers.anyString;
@@ -50,7 +51,7 @@ public class ProcessorBindingsWithDefaultsTests {
@SuppressWarnings("unchecked")
@Test
public void testSourceOutputChannelBound() {
Binder binder = this.binderFactory.getBinder(null);
Binder binder = this.binderFactory.getBinder(null, MessageChannel.class);
Mockito.verify(binder).bindConsumer(eq("input"), anyString(), eq(this.processor.input()),
Mockito.<ConsumerProperties>any());
Mockito.verify(binder).bindProducer(eq("output"), eq(this.processor.output()),

View File

@@ -28,6 +28,7 @@ import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.mockito.Matchers.anyString;
@@ -52,7 +53,7 @@ public class SinkBindingWithDefaultTargetsTests {
@SuppressWarnings("unchecked")
@Test
public void testSourceOutputChannelBound() {
Binder binder = binderFactory.getBinder(null);
Binder binder = binderFactory.getBinder(null, MessageChannel.class);
verify(binder).bindConsumer(eq("testtock"), anyString(), eq(this.testSink.input()),
Mockito.<ConsumerProperties>any());
verifyNoMoreInteractions(binder);

View File

@@ -27,6 +27,7 @@ import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.mockito.Matchers.anyString;
@@ -51,7 +52,7 @@ public class SinkBindingWithDefaultsTests {
@SuppressWarnings("unchecked")
@Test
public void testSourceOutputChannelBound() {
Binder binder = binderFactory.getBinder(null);
Binder binder = binderFactory.getBinder(null, MessageChannel.class);
verify(binder).bindConsumer(eq("input"), anyString(), eq(this.testSink.input()),
Mockito.<ConsumerProperties>any());
verifyNoMoreInteractions(binder);

View File

@@ -31,6 +31,7 @@ import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.integration.channel.PublishSubscribeChannel;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.mockito.Matchers.eq;
@@ -59,7 +60,7 @@ public class SourceBindingWithBindingTargetsTests {
@SuppressWarnings("unchecked")
@Test
public void testSourceOutputChannelBound() {
Binder binder = binderFactory.getBinder(null);
Binder binder = binderFactory.getBinder(null, MessageChannel.class);
verify(binder).bindProducer(eq("testtock"), eq(this.testSource.output()),
Mockito.<ProducerProperties>any());
verifyNoMoreInteractions(binder);

View File

@@ -27,6 +27,7 @@ import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.mockito.Matchers.eq;
@@ -50,7 +51,7 @@ public class SourceBindingWithDefaultsTests {
@SuppressWarnings("unchecked")
@Test
public void testSourceOutputChannelBound() {
Binder binder = binderFactory.getBinder(null);
Binder binder = binderFactory.getBinder(null, MessageChannel.class);
verify(binder).bindProducer(eq("output"), eq(this.testSource.output()), Mockito.<ProducerProperties>any());
verifyNoMoreInteractions(binder);
}

View File

@@ -39,8 +39,9 @@ import org.springframework.cloud.stream.binder.ConsumerProperties;
import org.springframework.cloud.stream.binder.DefaultBinderFactory;
import org.springframework.cloud.stream.binder.ProducerProperties;
import org.springframework.cloud.stream.config.BindingProperties;
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
import org.springframework.cloud.stream.config.BindingServiceProperties;
import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory;
import org.springframework.cloud.stream.reflection.GenericsUtils;
import org.springframework.cloud.stream.utils.MockBinderConfiguration;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.messaging.MessageChannel;
@@ -64,23 +65,23 @@ import static org.mockito.Mockito.when;
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan
*/
public class ChannelBindingServiceTests {
public class BindingServiceTests {
@Test
public void testDefaultGroup() throws Exception {
ChannelBindingServiceProperties properties = new ChannelBindingServiceProperties();
BindingServiceProperties properties = new BindingServiceProperties();
Map<String, BindingProperties> bindingProperties = new HashMap<>();
BindingProperties props = new BindingProperties();
props.setDestination("foo");
final String inputChannelName = "input";
bindingProperties.put(inputChannelName, props);
properties.setBindings(bindingProperties);
DefaultBinderFactory<MessageChannel> binderFactory =
new DefaultBinderFactory<>(Collections.singletonMap("mock",
DefaultBinderFactory binderFactory =
new DefaultBinderFactory(Collections.singletonMap("mock",
new BinderConfiguration(new BinderType("mock", new Class[]{MockBinderConfiguration.class}),
new Properties(), true, true)));
Binder binder = binderFactory.getBinder("mock");
ChannelBindingService service = new ChannelBindingService(properties, binderFactory);
Binder binder = binderFactory.getBinder("mock", MessageChannel.class);
BindingService service = new BindingService(properties, binderFactory);
MessageChannel inputChannel = new DirectChannel();
@SuppressWarnings("unchecked")
Binding<MessageChannel> mockBinding = Mockito.mock(Binding.class);
@@ -100,7 +101,7 @@ public class ChannelBindingServiceTests {
@Test
public void testMultipleConsumerBindings() throws Exception {
ChannelBindingServiceProperties properties = new ChannelBindingServiceProperties();
BindingServiceProperties properties = new BindingServiceProperties();
Map<String, BindingProperties> bindingProperties = new HashMap<>();
BindingProperties props = new BindingProperties();
props.setDestination("foo,bar");
@@ -109,12 +110,12 @@ public class ChannelBindingServiceTests {
properties.setBindings(bindingProperties);
DefaultBinderFactory<MessageChannel> binderFactory = new DefaultBinderFactory<>(Collections.singletonMap("mock",
DefaultBinderFactory binderFactory = new DefaultBinderFactory(Collections.singletonMap("mock",
new BinderConfiguration(new BinderType("mock", new Class[] { MockBinderConfiguration.class }),
new Properties(), true, true)));
Binder binder = binderFactory.getBinder("mock");
ChannelBindingService service = new ChannelBindingService(properties,
Binder binder = binderFactory.getBinder("mock", MessageChannel.class);
BindingService service = new BindingService(properties,
binderFactory);
MessageChannel inputChannel = new DirectChannel();
@@ -153,7 +154,7 @@ public class ChannelBindingServiceTests {
@Test
public void testExplicitGroup() throws Exception {
ChannelBindingServiceProperties properties = new ChannelBindingServiceProperties();
BindingServiceProperties properties = new BindingServiceProperties();
Map<String, BindingProperties> bindingProperties = new HashMap<>();
BindingProperties props = new BindingProperties();
props.setDestination("foo");
@@ -161,7 +162,7 @@ public class ChannelBindingServiceTests {
final String inputChannelName = "input";
bindingProperties.put(inputChannelName, props);
properties.setBindings(bindingProperties);
DefaultBinderFactory<MessageChannel> binderFactory = new DefaultBinderFactory<>(
DefaultBinderFactory binderFactory = new DefaultBinderFactory(
Collections
.singletonMap("mock",
new BinderConfiguration(
@@ -169,8 +170,8 @@ public class ChannelBindingServiceTests {
new Class[] {
MockBinderConfiguration.class }),
new Properties(), true, true)));
Binder binder = binderFactory.getBinder("mock");
ChannelBindingService service = new ChannelBindingService(properties,
Binder binder = binderFactory.getBinder("mock", MessageChannel.class);
BindingService service = new BindingService(properties,
binderFactory);
MessageChannel inputChannel = new DirectChannel();
@SuppressWarnings("unchecked")
@@ -192,8 +193,8 @@ public class ChannelBindingServiceTests {
@Test
public void checkDynamicBinding() {
ChannelBindingServiceProperties properties = new ChannelBindingServiceProperties();
DefaultBinderFactory<MessageChannel> binderFactory = new DefaultBinderFactory<>(
BindingServiceProperties properties = new BindingServiceProperties();
DefaultBinderFactory binderFactory = new DefaultBinderFactory(
Collections
.singletonMap("mock",
new BinderConfiguration(
@@ -201,19 +202,20 @@ public class ChannelBindingServiceTests {
new Class[] {
MockBinderConfiguration.class }),
new Properties(), true, true)));
Binder binder = binderFactory.getBinder("mock");
Binder binder = binderFactory.getBinder("mock", MessageChannel.class);
@SuppressWarnings("unchecked")
Binding<MessageChannel> mockBinding = Mockito.mock(Binding.class);
@SuppressWarnings("unchecked")
final AtomicReference<MessageChannel> dynamic = new AtomicReference<>();
when(binder.bindProducer(matches("foo"), any(DirectChannel.class),
any(ProducerProperties.class))).thenReturn(mockBinding);
ChannelBindingService channelBindingService = new ChannelBindingService(properties, binderFactory);
BindingService bindingService = new BindingService(properties, binderFactory);
SubscribableChannelBindingTargetFactory bindableSubscribableChannelFactory = new SubscribableChannelBindingTargetFactory(
new MessageConverterConfigurer(properties,
new CompositeMessageConverterFactory()));
BinderAwareChannelResolver resolver = new BinderAwareChannelResolver(
channelBindingService,
new DefaultBindableChannelFactory(new MessageConverterConfigurer(
properties,
new CompositeMessageConverterFactory())), new DynamicDestinationsBindable());
bindingService, bindableSubscribableChannelFactory,
new DynamicDestinationsBindable());
ConfigurableListableBeanFactory beanFactory = mock(
ConfigurableListableBeanFactory.class);
when(beanFactory.getBean("foo", MessageChannel.class))
@@ -257,7 +259,7 @@ public class ChannelBindingServiceTests {
@Test
public void testProducerPropertiesValidation() {
ChannelBindingServiceProperties serviceProperties = new ChannelBindingServiceProperties();
BindingServiceProperties serviceProperties = new BindingServiceProperties();
Map<String, BindingProperties> bindingProperties = new HashMap<>();
BindingProperties props = new BindingProperties();
ProducerProperties producerProperties = new ProducerProperties();
@@ -267,11 +269,11 @@ public class ChannelBindingServiceTests {
final String outputChannelName = "output";
bindingProperties.put(outputChannelName, props);
serviceProperties.setBindings(bindingProperties);
DefaultBinderFactory<MessageChannel> binderFactory =
new DefaultBinderFactory<>(Collections.singletonMap("mock",
DefaultBinderFactory binderFactory =
new DefaultBinderFactory(Collections.singletonMap("mock",
new BinderConfiguration(new BinderType("mock", new Class[]{MockBinderConfiguration.class}),
new Properties(), true, true)));
ChannelBindingService service = new ChannelBindingService(serviceProperties, binderFactory);
BindingService service = new BindingService(serviceProperties, binderFactory);
MessageChannel outputChannel = new DirectChannel();
try {
service.bindProducer(outputChannel, outputChannelName);
@@ -284,7 +286,7 @@ public class ChannelBindingServiceTests {
@Test
public void testConsumerPropertiesValidation() {
ChannelBindingServiceProperties serviceProperties = new ChannelBindingServiceProperties();
BindingServiceProperties serviceProperties = new BindingServiceProperties();
Map<String, BindingProperties> bindingProperties = new HashMap<>();
BindingProperties props = new BindingProperties();
ConsumerProperties consumerProperties = new ConsumerProperties();
@@ -294,10 +296,10 @@ public class ChannelBindingServiceTests {
final String inputChannelName = "input";
bindingProperties.put(inputChannelName, props);
serviceProperties.setBindings(bindingProperties);
DefaultBinderFactory<MessageChannel> binderFactory = new DefaultBinderFactory<>(Collections.singletonMap("mock",
DefaultBinderFactory binderFactory = new DefaultBinderFactory(Collections.singletonMap("mock",
new BinderConfiguration(new BinderType("mock", new Class[] { MockBinderConfiguration.class }),
new Properties(), true, true)));
ChannelBindingService service = new ChannelBindingService(serviceProperties,
BindingService service = new BindingService(serviceProperties,
binderFactory);
MessageChannel inputChannel = new DirectChannel();
try {
@@ -308,4 +310,30 @@ public class ChannelBindingServiceTests {
assertThat(e).hasMessageContaining("Concurrency should be greater than zero.");
}
}
@Test
public void testResolveBindableType() {
Class<?> bindableType = GenericsUtils.getParameterType(FooBinder.class, Binder.class, 0);
assertThat(bindableType).isSameAs(SomeBindableType.class);
}
public static class FooBinder
implements Binder<SomeBindableType, ConsumerProperties, ProducerProperties> {
@Override
public Binding<SomeBindableType> bindConsumer(String name, String group,
SomeBindableType inboundBindTarget,
ConsumerProperties consumerProperties) {
throw new UnsupportedOperationException();
}
@Override
public Binding<SomeBindableType> bindProducer(String name,
SomeBindableType outboundBindTarget,
ProducerProperties producerProperties) {
throw new UnsupportedOperationException();
}
}
public static class SomeBindableType {
}
}

View File

@@ -35,6 +35,7 @@ import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.hamcrest.Matchers.equalTo;
@@ -62,7 +63,7 @@ public class PartitionedConsumerTest {
@Test
@SuppressWarnings("unchecked")
public void testBindingPartitionedConsumer() {
Binder binder = this.binderFactory.getBinder(null);
Binder binder = this.binderFactory.getBinder(null, MessageChannel.class);
ArgumentCaptor<ConsumerProperties> argumentCaptor = ArgumentCaptor.forClass(ConsumerProperties.class);
verify(binder).bindConsumer(eq("partIn"), anyString(), eq(this.testSink.input()),
argumentCaptor.capture());

View File

@@ -33,6 +33,7 @@ import org.springframework.cloud.stream.messaging.Source;
import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.hamcrest.Matchers.equalTo;
@@ -59,7 +60,7 @@ public class PartitionedProducerTest {
@Test
@SuppressWarnings("unchecked")
public void testBindingPartitionedProducer() {
Binder binder = this.binderFactory.getBinder(null);
Binder binder = this.binderFactory.getBinder(null, MessageChannel.class);
ArgumentCaptor<ProducerProperties> argumentCaptor = ArgumentCaptor.forClass(ProducerProperties.class);
verify(binder).bindProducer(eq("partOut"), eq(this.testSource.output()), argumentCaptor.capture());
Assert.assertThat(argumentCaptor.getValue().getPartitionCount(), equalTo(3));