Add aggregate builder and sample applications

No need to specify channel names (just spring.cloud.streams.name). Example
app:

@SpringBootApplication
@EnableChannelBinding
public class ExtendedApplication implements AggregateConfigurer {

	@Override
	public void configure(AggregateBuilder builder) {
		builder
		.from(TimeSource.class).as("source")
		.via(LoggingTransformer.class)
		.via(LoggingTransformer.class).profiles("other")
		.to(LogSink.class);
	}
}

Fixes gh-2
This commit is contained in:
Dave Syer
2015-07-09 14:02:20 +01:00
parent 0c82a37360
commit 6e33369318
35 changed files with 954 additions and 20 deletions

View File

@@ -23,8 +23,9 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.cloud.streams.config.LifecycleConfiguration;
import org.springframework.cloud.streams.config.AggregateBuilderConfiguration;
import org.springframework.cloud.streams.config.ChannelBindingAdapterConfiguration;
import org.springframework.cloud.streams.config.LifecycleConfiguration;
import org.springframework.cloud.streams.config.RabbitServiceConfiguration;
import org.springframework.cloud.streams.config.RedisServiceConfiguration;
import org.springframework.context.annotation.Configuration;
@@ -40,7 +41,8 @@ import org.springframework.context.annotation.Import;
@Inherited
@Configuration
@Import({ RedisServiceConfiguration.class, RabbitServiceConfiguration.class,
ChannelBindingAdapterConfiguration.class, LifecycleConfiguration.class })
ChannelBindingAdapterConfiguration.class, LifecycleConfiguration.class,
AggregateBuilderConfiguration.class })
public @interface EnableChannelBinding {
}

View File

@@ -0,0 +1,257 @@
/*
* 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.streams.aggregate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
/**
* @author Dave Syer
*
*/
@ConfigurationProperties("spring.cloud.streams")
public class AggregateBuilder implements ApplicationContextAware {
public static final String DEFAULT_NAME = "application";
private ConfigurableApplicationContext parent;
private int index = 0;
private String streamName = "stream";
private HashSet<SinkConfigurer> sinks = new HashSet<SinkConfigurer>();
public String getName() {
return this.streamName;
}
public void setName(String name) {
this.streamName = name;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.parent = (ConfigurableApplicationContext) applicationContext;
}
public void build() {
for (SinkConfigurer sink : this.sinks) {
sink.build();
}
}
public SourceConfigurer from(Class<?> module) {
return new SourceConfigurer(module);
}
private String channelName() {
return this.streamName + "." + this.index;
}
private String incrementChannelName() {
return this.streamName + "." + (this.index++);
}
public class SourceConfigurer {
private Class<?> module;
private String[] names = null;
private String[] profiles = null;
public SourceConfigurer(Class<?> module) {
this.module = module;
}
public SourceConfigurer as(String... names) {
this.names = names;
return this;
}
public SourceConfigurer profiles(String... profiles) {
this.profiles = profiles;
return this;
}
public SinkConfigurer to(Class<?> sink) {
build();
return new SinkConfigurer(sink);
}
public ProcessorConfigurer via(Class<?> processor) {
build();
return new ProcessorConfigurer(processor);
}
private void build() {
childContext(this.module).config(this.names).profiles(this.profiles)
.output(channelName()).build();
}
}
public class SinkConfigurer {
private Class<?> module;
private String[] names = null;
private String[] profiles = null;
public SinkConfigurer profiles(String... profiles) {
this.profiles = profiles;
return this;
}
public SinkConfigurer(Class<?> module) {
AggregateBuilder.this.sinks.add(this);
this.module = module;
}
public SinkConfigurer as(String... names) {
this.names = names;
return this;
}
void build() {
childContext(this.module).config(this.names).profiles(this.profiles)
.input(channelName()).build();
}
}
public class ProcessorConfigurer {
private Class<?> module;
private String[] names = null;
private String[] profiles = null;
public ProcessorConfigurer(Class<?> module) {
this.module = module;
}
public ProcessorConfigurer as(String... names) {
this.names = names;
return this;
}
public ProcessorConfigurer profiles(String... profiles) {
this.profiles = profiles;
return this;
}
public SinkConfigurer to(Class<?> sink) {
build();
return new SinkConfigurer(sink);
}
public ProcessorConfigurer via(Class<?> processor) {
build();
return new ProcessorConfigurer(processor);
}
private void build() {
childContext(this.module).config(this.names).profiles(this.profiles)
.input(incrementChannelName()).output(channelName()).build();
}
}
private ChildContextBuilder childContext(Class<?> type) {
return new ChildContextBuilder(new SpringApplicationBuilder(type,
SeedConfiguration.class).parent(AggregateBuilder.this.parent)
.showBanner(false).web(false)
.initializers(new BeanPostProcessorInitializer()));
}
private class ChildContextBuilder {
private SpringApplicationBuilder builder;
private String configName;
private String input;
private String output;
public ChildContextBuilder(SpringApplicationBuilder builder) {
this.builder = builder;
}
public ChildContextBuilder profiles(String... profiles) {
if (profiles != null) {
this.builder.profiles(profiles);
}
return this;
}
public ChildContextBuilder config(String... configs) {
if (configs != null) {
this.configName = StringUtils.arrayToCommaDelimitedString(configs);
}
return this;
}
public ChildContextBuilder input(String input) {
this.input = input;
return this;
}
public ChildContextBuilder output(String output) {
this.output = output;
return this;
}
public void build() {
List<String> args = new ArrayList<String>();
if (this.configName != null) {
args.add("--spring.config.name=" + this.configName);
}
if (this.input != null) {
args.add("--spring.cloud.channels.inputChannelName=" + this.input);
}
if (this.output != null) {
args.add("--spring.cloud.channels.outputChannelName=" + this.output);
}
this.builder.run(args.toArray(new String[0]));
}
}
private class BeanPostProcessorInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
}
}
@Configuration
@EnableAutoConfiguration
protected static class SeedConfiguration {
}
}

View File

@@ -0,0 +1,27 @@
/*
* 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.streams.aggregate;
/**
* @author Dave Syer
*
*/
public interface AggregateConfigurer {
void configure(AggregateBuilder builder);
}

View File

@@ -0,0 +1,50 @@
/*
* 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.streams.config;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.cloud.streams.aggregate.AggregateBuilder;
import org.springframework.cloud.streams.aggregate.AggregateConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Dave Syer
*
*/
@Configuration
public class AggregateBuilderConfiguration implements CommandLineRunner {
@Autowired
private ListableBeanFactory beanFactory;
@Bean
public AggregateBuilder aggregateBuilder() {
return new AggregateBuilder();
}
@Override
public void run(String... args) throws Exception {
for (AggregateConfigurer configurer : this.beanFactory.getBeansOfType(AggregateConfigurer.class).values()) {
configurer.configure(aggregateBuilder());
}
aggregateBuilder().build();
}
}

View File

@@ -32,6 +32,7 @@ import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.cloud.streams.adapter.ChannelBindingAdapter;
import org.springframework.cloud.streams.adapter.ChannelLocator;
import org.springframework.cloud.streams.adapter.Input;
@@ -104,8 +105,7 @@ public class ChannelBindingAdapterConfiguration {
protected Collection<OutputChannelBinding> getOutputChannels() {
Set<OutputChannelBinding> channels = new LinkedHashSet<OutputChannelBinding>();
String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, MessageChannel.class);
String[] names = this.beanFactory.getBeanNamesForType(MessageChannel.class);
for (String name : names) {
if (name.startsWith("output")) {
channels.add(new OutputChannelBinding(name));
@@ -116,8 +116,7 @@ public class ChannelBindingAdapterConfiguration {
protected Collection<InputChannelBinding> getInputChannels() {
Set<InputChannelBinding> channels = new LinkedHashSet<InputChannelBinding>();
String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, MessageChannel.class);
String[] names = this.beanFactory.getBeanNamesForType(MessageChannel.class);
for (String name : names) {
if (name.startsWith("input")) {
channels.add(new InputChannelBinding(name));
@@ -174,7 +173,7 @@ public class ChannelBindingAdapterConfiguration {
}
@Configuration
@ConditionalOnMissingBean(ChannelBindingProperties.class)
@ConditionalOnMissingBean(value=ChannelBindingProperties.class, search=SearchStrategy.CURRENT)
protected static class ModulePropertiesConfiguration {
@Bean(name = "spring.cloud.channels.CONFIGURATION_PROPERTIES")
public ChannelBindingProperties moduleProperties() {

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.streams.config;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.Cloud;
import org.springframework.cloud.CloudFactory;
import org.springframework.context.annotation.Bean;
@@ -35,6 +36,7 @@ import org.springframework.xd.dirt.integration.rabbit.RabbitMessageBus;
*/
@Configuration
@ConditionalOnClass(RabbitMessageBus.class)
@ConditionalOnMissingBean(RabbitMessageBus.class)
@ImportResource({ "classpath*:/META-INF/spring-xd/bus/rabbit-bus.xml",
"classpath*:/META-INF/spring-xd/analytics/rabbit-analytics.xml" })
@PropertySource("classpath:/META-INF/spring-cloud-streams/rabbit-bus.properties")

View File

@@ -17,6 +17,7 @@
package org.springframework.cloud.streams.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.Cloud;
import org.springframework.cloud.CloudFactory;
import org.springframework.context.annotation.Bean;
@@ -35,6 +36,7 @@ import org.springframework.xd.dirt.integration.redis.RedisMessageBus;
*/
@Configuration
@ConditionalOnClass(RedisMessageBus.class)
@ConditionalOnMissingBean(RedisMessageBus.class)
@ImportResource({ "classpath*:/META-INF/spring-xd/bus/redis-bus.xml",
"classpath*:/META-INF/spring-xd/analytics/redis-analytics.xml" })
@PropertySource("classpath:/META-INF/spring-cloud-streams/redis-bus.properties")

View File

@@ -25,6 +25,8 @@ import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
@@ -129,6 +131,7 @@ ApplicationContextInitializer<ConfigurableApplicationContext> {
}
@Configuration
@ConditionalOnMissingBean(value=ModuleProperties.class, search=SearchStrategy.CURRENT)
protected static class ModulePropertiesConfiguration {
@Bean(name = "spring.cloud.channels.CONFIGURATION_PROPERTIES")
public ModuleProperties moduleProperties() {

View File

@@ -0,0 +1,69 @@
<?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-streams-sample-double</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-cloud-streams-sample-double</name>
<description>Demo project for Spring XD module</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-streams-samples</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>demo.SinkApplication</start-class>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-streams</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.xd</groupId>
<artifactId>spring-xd-messagebus-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-lattice-connector</artifactId>
<version>1.0.2.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -33,9 +33,9 @@ import org.springframework.messaging.MessageChannel;
@Configuration
@EnableChannelBinding
@MessageEndpoint
public class ModuleDefinition {
public class SinkModuleDefinition {
private static Logger logger = LoggerFactory.getLogger(ModuleDefinition.class);
private static Logger logger = LoggerFactory.getLogger(SinkModuleDefinition.class);
@Bean
public MessageChannel input() {

View File

@@ -0,0 +1,55 @@
/*
* 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 config;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.streams.EnableChannelBinding;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.InboundChannelAdapter;
import org.springframework.integration.annotation.Poller;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageSource;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.GenericMessage;
/**
* @author Dave Syer
*
*/
@Configuration
@EnableChannelBinding
public class SourceModuleDefinition {
@Value("${format:YYYY/MM/dd hh:mm:ss}")
private String format;
@Bean
public MessageChannel output() {
return new DirectChannel();
}
@Bean
@InboundChannelAdapter(value = "output", autoStartup = "false", poller = @Poller(fixedDelay = "${fixedDelay}", maxMessagesPerPoll = "1"))
public MessageSource<String> timerMessageSource() {
return () -> new GenericMessage<>(new SimpleDateFormat(this.format).format(new Date()));
}
}

View File

@@ -0,0 +1,26 @@
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.streams.EnableChannelBinding;
import org.springframework.cloud.streams.aggregate.AggregateBuilder;
import org.springframework.cloud.streams.aggregate.AggregateConfigurer;
import config.SinkModuleDefinition;
import config.SourceModuleDefinition;
@SpringBootApplication
@EnableChannelBinding
public class DoubleApplication implements AggregateConfigurer {
@Override
public void configure(AggregateBuilder builder) {
builder.from(SourceModuleDefinition.class).as("source")
.to(SinkModuleDefinition.class).as("sink");
}
public static void main(String[] args) throws InterruptedException {
SpringApplication.run(DoubleApplication.class, args);
}
}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,5 @@
spring:
cloud:
channels:
inputChannelName: testtock

View File

@@ -0,0 +1,6 @@
fixedDelay: 5000
spring:
cloud:
channels:
outputChannelName: testtock

View File

@@ -0,0 +1,20 @@
package demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = DoubleApplication.class)
@WebAppConfiguration
@DirtiesContext
public class ModuleApplicationTests {
@Test
public void contextLoads() {
}
}

View File

@@ -0,0 +1,81 @@
<?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-streams-sample-extended</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-cloud-streams-sample-extended</name>
<description>Demo project for Spring XD module</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-streams-samples</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>demo.SinkApplication</start-class>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-streams</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-streams-sample-source</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-streams-sample-transform</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-streams-sample-sink</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.xd</groupId>
<artifactId>spring-xd-messagebus-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-lattice-connector</artifactId>
<version>1.0.2.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,32 @@
package extended;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.streams.EnableChannelBinding;
import org.springframework.cloud.streams.aggregate.AggregateBuilder;
import org.springframework.cloud.streams.aggregate.AggregateConfigurer;
import sink.LogSink;
import source.TimeSource;
import transform.LoggingTransformer;
@SpringBootApplication
@EnableChannelBinding
public class ExtendedApplication implements AggregateConfigurer {
@Override
public void configure(AggregateBuilder builder) {
// @formatter:off
builder
.from(TimeSource.class).as("source")
.via(LoggingTransformer.class)
.via(LoggingTransformer.class).profiles("other")
.to(LogSink.class);
// @formatter:on
}
public static void main(String[] args) throws InterruptedException {
SpringApplication.run(ExtendedApplication.class, args);
}
}

View File

@@ -0,0 +1,6 @@
---
spring:
profiles: other
module:
logging:
name: other

View File

@@ -0,0 +1 @@
fixedDelay: 5000

View File

@@ -0,0 +1,22 @@
package demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import extended.ExtendedApplication;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ExtendedApplication.class)
@WebAppConfiguration
@DirtiesContext
public class ModuleApplicationTests {
@Test
public void contextLoads() {
}
}

View File

@@ -18,7 +18,29 @@
<modules>
<module>source</module>
<module>sink</module>
<module>transform</module>
<module>double</module>
<module>extended</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-streams-sample-source</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-streams-sample-sink</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-streams-sample-transform</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>

View File

@@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-xd-samples</artifactId>
<artifactId>spring-cloud-streams-samples</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</parent>

View File

@@ -4,10 +4,10 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import config.ModuleDefinition;
import sink.LogSink;
@SpringBootApplication
@ComponentScan(basePackageClasses=ModuleDefinition.class)
@ComponentScan(basePackageClasses=LogSink.class)
public class SinkApplication {
public static void main(String[] args) throws InterruptedException {

View File

@@ -0,0 +1,50 @@
/*
* 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 sink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.streams.EnableChannelBinding;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.messaging.MessageChannel;
/**
* @author Dave Syer
*
*/
@Configuration
@EnableChannelBinding
@MessageEndpoint
public class LogSink {
private static Logger logger = LoggerFactory.getLogger(LogSink.class);
@Bean
public MessageChannel input() {
return new DirectChannel();
}
@ServiceActivator(inputChannel="input")
public void loggerSink(Object payload) {
logger.info("Received: " + payload);
}
}

View File

@@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-xd-samples</artifactId>
<artifactId>spring-cloud-streams-samples</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</parent>

View File

@@ -4,10 +4,10 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import config.ModuleDefinition;
import source.TimeSource;
@SpringBootApplication
@ComponentScan(basePackageClasses=ModuleDefinition.class)
@ComponentScan(basePackageClasses=TimeSource.class)
public class SourceApplication {
public static void main(String[] args) throws InterruptedException {

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package config;
package source;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -38,7 +38,7 @@ import org.springframework.messaging.support.GenericMessage;
@Configuration
@EnableChannelBinding
@EnableConfigurationProperties(TimeSourceOptionsMetadata.class)
public class ModuleDefinition {
public class TimeSource {
@Autowired
private TimeSourceOptionsMetadata options;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package config;
package source;
import javax.validation.constraints.Min;
import javax.validation.constraints.Pattern;

View File

@@ -1,11 +1,21 @@
fixedDelay: 5000
server:
port: 8081
spring:
cloud:
channels:
inpoutChannelName: testtock
inputChannelName: testtock
# uncomment below to use the last digit of the seconds as a partition key
# hashcode(key) % N is then applied with N being the partitionCount value
# thus, even seconds should go to the 0 queue, odd seconds to the 1 queue
#producerProperties:
# partitionKeyExpression: payload.charAt(payload.length()-1)
# partitionCount: 2
---
spring:
profiles: extended
spring:
cloud:
channels:
inputChannelName: xformed

View File

@@ -0,0 +1,69 @@
<?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-streams-sample-transform</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-cloud-streams-sample-transform</name>
<description>Demo project for Spring XD module</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-streams-samples</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>demo.SinkApplication</start-class>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-streams</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.xd</groupId>
<artifactId>spring-xd-messagebus-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-lattice-connector</artifactId>
<version>1.0.2.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,17 @@
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import transform.LoggingTransformer;
@SpringBootApplication
@ComponentScan(basePackageClasses=LoggingTransformer.class)
public class TransformApplication {
public static void main(String[] args) throws InterruptedException {
SpringApplication.run(TransformApplication.class, args);
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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 transform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.streams.EnableChannelBinding;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
/**
* @author Dave Syer
*
*/
@Configuration
@EnableChannelBinding
@MessageEndpoint
@ConfigurationProperties("module.logging")
public class LoggingTransformer {
private static Logger logger = LoggerFactory.getLogger(LoggingTransformer.class);
/**
* The name to include in the log message
*/
private String name = "logging";
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Bean
public MessageChannel input() {
return new DirectChannel();
}
@Bean
public SubscribableChannel output() {
return new DirectChannel();
}
@ServiceActivator(inputChannel = "input", outputChannel = "output")
public Object transform(Object payload) {
logger.info("Transformed by " + this.name + ": " + payload);
return payload;
}
}

View File

@@ -0,0 +1,8 @@
server:
port: 8082
spring:
cloud:
channels:
outputChannelName: xformed
inputChannelName: testtock

View File

@@ -0,0 +1,20 @@
package demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TransformApplication.class)
@WebAppConfiguration
@DirtiesContext
public class ModuleApplicationTests {
@Test
public void contextLoads() {
}
}