diff --git a/.gitignore b/.gitignore
index 7087c8692..eade3d4c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,5 @@ _site/
.factorypath
spring-xd-samples/*/xd
dump.rdb
+.apt_generated
+artifacts
diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-local/src/main/java/org/springframework/cloud/stream/binder/local/config/LocalExecutorConfigurationProperties.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-local/src/main/java/org/springframework/cloud/stream/binder/local/config/LocalExecutorConfigurationProperties.java
index 79e624e09..95ed90f1c 100644
--- a/spring-cloud-stream-binders/spring-cloud-stream-binder-local/src/main/java/org/springframework/cloud/stream/binder/local/config/LocalExecutorConfigurationProperties.java
+++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-local/src/main/java/org/springframework/cloud/stream/binder/local/config/LocalExecutorConfigurationProperties.java
@@ -24,43 +24,43 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "spring.cloud.stream.binder.local.executor")
class LocalExecutorConfigurationProperties {
- private int executorCorePoolSize;
+ private int corePoolSize;
- private int executorMaxPoolSize;
+ private int maxPoolSize;
- private int executorQueueSize = Integer.MAX_VALUE;
+ private int queueSize = Integer.MAX_VALUE;
- private int executorKeepAliveSeconds;
+ private int keepAliveSeconds;
- public int getExecutorCorePoolSize() {
- return executorCorePoolSize;
+ public int getCorePoolSize() {
+ return corePoolSize;
}
- public void setExecutorCorePoolSize(int executorCorePoolSize) {
- this.executorCorePoolSize = executorCorePoolSize;
+ public void setCorePoolSize(int corePoolSize) {
+ this.corePoolSize = corePoolSize;
}
- public int getExecutorMaxPoolSize() {
- return executorMaxPoolSize;
+ public int getMaxPoolSize() {
+ return maxPoolSize;
}
- public void setExecutorMaxPoolSize(int executorMaxPoolSize) {
- this.executorMaxPoolSize = executorMaxPoolSize;
+ public void setMaxPoolSize(int maxPoolSize) {
+ this.maxPoolSize = maxPoolSize;
}
- public int getExecutorQueueSize() {
- return executorQueueSize;
+ public int getQueueSize() {
+ return queueSize;
}
- public void setExecutorQueueSize(int executorQueueSize) {
- this.executorQueueSize = executorQueueSize;
+ public void setQueueSize(int queueSize) {
+ this.queueSize = queueSize;
}
- public int getExecutorKeepAliveSeconds() {
- return executorKeepAliveSeconds;
+ public int getKeepAliveSeconds() {
+ return keepAliveSeconds;
}
- public void setExecutorKeepAliveSeconds(int executorKeepAliveSeconds) {
- this.executorKeepAliveSeconds = executorKeepAliveSeconds;
+ public void setKeepAliveSeconds(int keepAliveSeconds) {
+ this.keepAliveSeconds = keepAliveSeconds;
}
}
diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-local/src/main/java/org/springframework/cloud/stream/binder/local/config/LocalMessageChannelBinderConfiguration.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-local/src/main/java/org/springframework/cloud/stream/binder/local/config/LocalMessageChannelBinderConfiguration.java
index c4e043e75..1108a8a91 100644
--- a/spring-cloud-stream-binders/spring-cloud-stream-binder-local/src/main/java/org/springframework/cloud/stream/binder/local/config/LocalMessageChannelBinderConfiguration.java
+++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-local/src/main/java/org/springframework/cloud/stream/binder/local/config/LocalMessageChannelBinderConfiguration.java
@@ -44,10 +44,10 @@ public class LocalMessageChannelBinderConfiguration {
public LocalMessageChannelBinder localMessageChannelBinder() {
LocalMessageChannelBinder localMessageChannelBinder = new LocalMessageChannelBinder();
- localMessageChannelBinder.setExecutorCorePoolSize(localExecutorConfigurationProperties.getExecutorCorePoolSize());
- localMessageChannelBinder.setExecutorKeepAliveSeconds(localExecutorConfigurationProperties.getExecutorKeepAliveSeconds());
- localMessageChannelBinder.setExecutorMaxPoolSize(localExecutorConfigurationProperties.getExecutorMaxPoolSize());
- localMessageChannelBinder.setExecutorQueueSize(localExecutorConfigurationProperties.getExecutorQueueSize());
+ localMessageChannelBinder.setExecutorCorePoolSize(localExecutorConfigurationProperties.getCorePoolSize());
+ localMessageChannelBinder.setExecutorKeepAliveSeconds(localExecutorConfigurationProperties.getKeepAliveSeconds());
+ localMessageChannelBinder.setExecutorMaxPoolSize(localExecutorConfigurationProperties.getMaxPoolSize());
+ localMessageChannelBinder.setExecutorQueueSize(localExecutorConfigurationProperties.getQueueSize());
if (polling > 0) {
PollerMetadata pollerMetadata = new PollerMetadata();
diff --git a/spring-cloud-stream-module-launcher/.gitignore b/spring-cloud-stream-module-launcher/.gitignore
deleted file mode 100644
index 44898dfcf..000000000
--- a/spring-cloud-stream-module-launcher/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-artifacts
\ No newline at end of file
diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/boot/loader/ModuleJarLauncher.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/boot/loader/ModuleJarLauncher.java
index c580d750e..5bb462a38 100644
--- a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/boot/loader/ModuleJarLauncher.java
+++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/boot/loader/ModuleJarLauncher.java
@@ -84,6 +84,7 @@ public class ModuleJarLauncher extends ExecutableArchiveLauncher {
if (systemClassLoader instanceof URLClassLoader) {
// add the URLs of the application classloader to the created classloader
// to compensate for LaunchedURLClassLoader not delegating to parent to retrieve resources
+ @SuppressWarnings("resource")
URLClassLoader systemUrlClassLoader = (URLClassLoader) systemClassLoader;
URL[] mergedUrls = new URL[urls.length + systemUrlClassLoader.getURLs().length];
System.arraycopy(urls, 0, mergedUrls, 0, urls.length);
@@ -91,9 +92,8 @@ public class ModuleJarLauncher extends ExecutableArchiveLauncher {
systemUrlClassLoader.getURLs().length);
// add the extension classloader as parent to the created context, if accessible
return new LaunchedURLClassLoader(mergedUrls, systemUrlClassLoader.getParent());
- } else {
- return new LaunchedURLClassLoader(urls, systemClassLoader);
}
+ return new LaunchedURLClassLoader(urls, systemClassLoader);
}
}
diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/adapter/ChannelBinding.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ArchiveMatchingEntryFilter.java
similarity index 50%
rename from spring-cloud-stream/src/main/java/org/springframework/cloud/stream/adapter/ChannelBinding.java
rename to spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ArchiveMatchingEntryFilter.java
index 3a59fb4a9..419c8597a 100644
--- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/adapter/ChannelBinding.java
+++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ArchiveMatchingEntryFilter.java
@@ -14,37 +14,26 @@
* limitations under the License.
*/
-package org.springframework.cloud.stream.adapter;
+package org.springframework.cloud.stream.module.launcher;
+
+import org.springframework.boot.loader.archive.Archive;
+import org.springframework.boot.loader.util.AsciiBytes;
/**
- * Represents a binding between a local and remote message channel.
- *
- * @author Dave Syer
- * @author Mark Fisher
+ * @author Marius Bogoevici
*/
-public abstract class ChannelBinding {
+class ArchiveMatchingEntryFilter implements Archive.EntryFilter {
- private String localName;
- private String remoteName;
+ public static final ArchiveMatchingEntryFilter FILTER = new ArchiveMatchingEntryFilter();
- protected ChannelBinding() {
- this(null);
+ private static final AsciiBytes LIB = new AsciiBytes("lib/");
+
+ @Override
+ public boolean matches(Archive.Entry entry) {
+ return isNestedArchive(entry);
}
- protected ChannelBinding(String localName) {
- this.localName = localName;
+ protected boolean isNestedArchive(Archive.Entry entry) {
+ return !entry.isDirectory() && entry.getName().startsWith(LIB);
}
-
- public String getLocalName() {
- return this.localName;
- }
-
- public String getRemoteName() {
- return this.remoteName;
- }
-
- public void setRemoteName(String name) {
- this.remoteName = name;
- }
-
}
diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncher.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncher.java
index 31999202a..0c38f36b5 100644
--- a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncher.java
+++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncher.java
@@ -17,6 +17,8 @@
package org.springframework.cloud.stream.module.launcher;
import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -24,11 +26,19 @@ import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.boot.loader.LaunchedURLClassLoader;
import org.springframework.boot.loader.ModuleJarLauncher;
+import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.JarFileArchive;
import org.springframework.cloud.stream.module.resolver.ModuleResolver;
+import org.springframework.cloud.stream.module.utils.ClassloaderUtils;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
@@ -42,6 +52,14 @@ import org.springframework.util.StringUtils;
*/
public class ModuleLauncher {
+ public static final String AGGREGATE_APPLICATION_CLASS = "org.springframework.cloud.stream.aggregate.AggregateApplication";
+
+ public static final String AGGREGATE_APPLICATION_RUN_METHOD = "run";
+
+ public static final String MODULE_AGGREGATOR_RUNNER_THREAD_NAME = "module-aggregator-runner";
+
+ private Log log = LogFactory.getLog(ModuleLauncher.class);
+
private static final String DEFAULT_EXTENSION = "jar";
private static final String DEFAULT_CLASSIFIER = "exec";
@@ -68,15 +86,26 @@ public class ModuleLauncher {
* <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>
*
* @param moduleLaunchRequests a list of modules with their arguments
+ * @param aggregate whether the modules should be aggregated at launch
+ * @param parentArgs a list of arguments for the whole aggregate
*/
- public void launch(List moduleLaunchRequests) {
+ public void launch(List moduleLaunchRequests, boolean aggregate, String parentArgs[]) {
List reversed = new ArrayList<>(moduleLaunchRequests);
Collections.reverse(reversed);
- for (ModuleLaunchRequest moduleLaunchRequest : reversed) {
- String module = moduleLaunchRequest.getModule();
- moduleLaunchRequest.addArgument("spring.jmx.default-domain", module.replace("/", ".").replace(":", "."));
- launchModule(module, toArgArray(moduleLaunchRequest.getArguments()));
+ if (moduleLaunchRequests.size() == 1 || !aggregate) {
+ launchIndividualModules(moduleLaunchRequests);
}
+ else {
+ launchAggregatedModules(moduleLaunchRequests, parentArgs);
+ }
+ }
+
+ public void launch(List moduleLaunchRequests, boolean aggregate) {
+ this.launch(moduleLaunchRequests, aggregate, new String[0]);
+ }
+
+ public void launch(List moduleLaunchRequests) {
+ this.launch(moduleLaunchRequests, false);
}
/**
@@ -92,6 +121,56 @@ public class ModuleLauncher {
return result;
}
+ public void launchAggregatedModules(List moduleLaunchRequests, final String[] parentArgs) {
+ try {
+ List mainClassNames = new ArrayList<>();
+ List jarURLs = new ArrayList<>();
+ List seenArchives = new ArrayList<>();
+ final List arguments = new ArrayList<>();
+ // aggregate jars from all modules and extract their main Classes
+ for (ModuleLaunchRequest moduleLaunchRequest : moduleLaunchRequests) {
+ Resource resource = resolveModule(moduleLaunchRequest.getModule());
+ JarFileArchive jarFileArchive = new JarFileArchive(resource.getFile());
+ jarURLs.add(jarFileArchive.getUrl());
+ for (Archive archive : jarFileArchive.getNestedArchives(ArchiveMatchingEntryFilter.FILTER)) {
+ // avoid duplication based on unique JAR names
+ // TODO - read the metadata from the JARs, do proper version resolution on merge
+ String urlAsString = archive.getUrl().toString();
+ String jarNameWithExtension = urlAsString.substring(0, urlAsString.lastIndexOf("!/"));
+ String jarNameWithoutExtension = jarNameWithExtension.substring(jarNameWithExtension.lastIndexOf("/") + 1);
+ if (!seenArchives.contains(jarNameWithoutExtension)) {
+ seenArchives.add(jarNameWithoutExtension);
+ jarURLs.add(archive.getUrl());
+ }
+ }
+ mainClassNames.add(jarFileArchive.getMainClass());
+ arguments.add(toArgArray(moduleLaunchRequest.getArguments()));
+ }
+ final ClassLoader classLoader = new LaunchedURLClassLoader(jarURLs.toArray(new URL[jarURLs.size()]),
+ ClassloaderUtils.getExtensionClassloader());
+ final List> mainClasses = new ArrayList<>();
+ for (String mainClass : mainClassNames) {
+ mainClasses.add(ClassUtils.forName(mainClass, classLoader));
+ }
+ Runnable moduleAggregatorRunner = new ModuleAggregatorRunner(classLoader, mainClasses, parentArgs, arguments);
+
+ Thread moduleAggregatorRunnerThread = new Thread(moduleAggregatorRunner);
+ moduleAggregatorRunnerThread.setContextClassLoader(classLoader);
+ moduleAggregatorRunnerThread.setName(MODULE_AGGREGATOR_RUNNER_THREAD_NAME);
+ moduleAggregatorRunnerThread.start();
+ } catch (Exception e) {
+ throw new RuntimeException("failed to start aggregated modules: " + StringUtils.collectionToCommaDelimitedString(moduleLaunchRequests), e);
+ }
+ }
+
+ public void launchIndividualModules(List reversed) {
+ for (ModuleLaunchRequest moduleLaunchRequest : reversed) {
+ String module = moduleLaunchRequest.getModule();
+ moduleLaunchRequest.addArgument("spring.jmx.default-domain", module.replace("/", ".").replace(":", "."));
+ launchModule(module, toArgArray(moduleLaunchRequest.getArguments()));
+ }
+ }
+
private void launchModule(String module, String[] args) {
try {
Resource resource = resolveModule(module);
@@ -116,4 +195,37 @@ public class ModuleLauncher {
return this.moduleResolver.resolve(groupId, artifactId, extension, classifier, version);
}
+ private class ModuleAggregatorRunner implements Runnable {
+
+ private final ClassLoader classLoader;
+
+ private final String[] parentArgs;
+
+ private final List> mainClasses;
+
+ private final List arguments;
+
+ public ModuleAggregatorRunner(ClassLoader classLoader, List> mainClasses, String[] parentArgs, List moduleArguments) {
+ this.classLoader = classLoader;
+ this.parentArgs = parentArgs;
+ this.mainClasses = mainClasses;
+ this.arguments = moduleArguments;
+ }
+
+ @Override
+ public void run() {
+ try {
+ // we expect the class and method to be found on the module classpath
+ Class> moduleAggregatorClass = ClassUtils.forName(AGGREGATE_APPLICATION_CLASS, classLoader);
+ Method aggregateMethod = ReflectionUtils.findMethod(moduleAggregatorClass,
+ AGGREGATE_APPLICATION_RUN_METHOD, Class[].class, String[].class, String[][].class);
+ aggregateMethod.invoke(null,
+ mainClasses.toArray(new Class>[mainClasses.size()]),
+ parentArgs, arguments.toArray(new String[][] {}));
+ } catch (Exception e) {
+ log.error("failed to launch aggregated modules :" + StringUtils.collectionToCommaDelimitedString(mainClasses), e);
+ throw new RuntimeException(e);
+ }
+ }
+ }
}
diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherProperties.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherProperties.java
index 21100e1ea..3c7b8a237 100644
--- a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherProperties.java
+++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherProperties.java
@@ -49,12 +49,17 @@ import org.springframework.core.env.Environment;
public class ModuleLauncherProperties {
/**
- * Array of coordinates for modules that need to be launched.
+ * True if aggregating multiple modules when launched together
+ */
+ private boolean aggregate;
+
+ /**
+ * File path to a locally available maven repository, where modules will be downloaded.
*/
private String[] modules;
/**
- * Map of arguments, keyed by the 0-based index in the {@kink #modules array}.
+ * Map of arguments, keyed by the 0-based index in the {@link #modules array}.
*/
private Map> args = new HashMap<>();
@@ -62,6 +67,14 @@ public class ModuleLauncherProperties {
this.modules = modules;
}
+ public boolean isAggregate() {
+ return aggregate;
+ }
+
+ public void setAggregate(boolean aggregate) {
+ this.aggregate = aggregate;
+ }
+
@NotEmpty(message = "A list of modules must be specified.")
public String[] getModules() {
return modules;
diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherRunner.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherRunner.java
index 1b5a2850a..09790202b 100644
--- a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherRunner.java
+++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherRunner.java
@@ -58,7 +58,9 @@ public class ModuleLauncherRunner implements CommandLineRunner {
}
log.info(sb.toString());
}
- this.moduleLauncher.launch(launchRequests);
+ this.moduleLauncher.launch(launchRequests,
+ moduleLauncherProperties.isAggregate(),
+ moduleLauncherProperties.isAggregate() ? extractAggregateProperties(args) : new String[0]);
}
private List generateModuleLaunchRequests() {
@@ -71,4 +73,15 @@ public class ModuleLauncherRunner implements CommandLineRunner {
}
return requests;
}
+
+ private String[] extractAggregateProperties(String[] args) {
+ List filteredProperties = new ArrayList<>();
+ for (String arg : args) {
+ if (arg.startsWith("--spring.")) {
+ filteredProperties.add(arg);
+ }
+ }
+ return filteredProperties.toArray(new String[filteredProperties.size()]);
+ }
+
}
diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolver.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolver.java
index 827080173..b9bc712b8 100644
--- a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolver.java
+++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolver.java
@@ -36,6 +36,6 @@ public interface ModuleResolver {
* @param version the version
* @return the resource
*/
- public Resource resolve(String groupId, String artifactId, String extension, String classifier, String version);
+ Resource resolve(String groupId, String artifactId, String extension, String classifier, String version);
}
diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolverProperties.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolverProperties.java
index 434c99516..0036a0412 100644
--- a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolverProperties.java
+++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolverProperties.java
@@ -5,7 +5,7 @@
* 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
+ * 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,
diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/utils/ClassloaderUtils.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/utils/ClassloaderUtils.java
new file mode 100644
index 000000000..c45098d02
--- /dev/null
+++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/utils/ClassloaderUtils.java
@@ -0,0 +1,39 @@
+/*
+ * 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.module.utils;
+
+/**
+ * @author Marius Bogoevici
+ */
+public class ClassloaderUtils {
+
+ /**
+ * Retrieves the extension classloader of the current JVM, if accessible. In general the extension classloader is
+ * found in a hierarchy as the parent of the application classloader. If such a hierarchy does not exist, it will
+ * return the application classloader itself.
+ *
+ * @return the classloader
+ */
+ public static ClassLoader getExtensionClassloader() {
+ ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
+ // try to retrieve the extension classloader
+ ClassLoader extensionClassLoader = systemClassLoader != null ? systemClassLoader.getParent() : null;
+ // set the classloader for the module as the extension classloader if available
+ // fall back to the system classloader (which can also be null) if not available
+ return extensionClassLoader != null ? extensionClassLoader : systemClassLoader;
+ }
+}
diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/adapter/ChannelLocator.java b/spring-cloud-stream-samples/double/src/main/java/config/sink/SinkApplication.java
similarity index 78%
rename from spring-cloud-stream/src/main/java/org/springframework/cloud/stream/adapter/ChannelLocator.java
rename to spring-cloud-stream-samples/double/src/main/java/config/sink/SinkApplication.java
index 88e53fe29..e093d1ff9 100644
--- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/adapter/ChannelLocator.java
+++ b/spring-cloud-stream-samples/double/src/main/java/config/sink/SinkApplication.java
@@ -14,16 +14,13 @@
* limitations under the License.
*/
-package org.springframework.cloud.stream.adapter;
+package config.sink;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
- * @author Dave Syer
- *
+ * @author Marius Bogoevici
*/
-public interface ChannelLocator {
-
- String locate(String name);
-
- String tap(String name);
-
+@SpringBootApplication
+public class SinkApplication {
}
diff --git a/spring-cloud-stream-samples/double/src/main/java/config/SinkModuleDefinition.java b/spring-cloud-stream-samples/double/src/main/java/config/sink/SinkModuleDefinition.java
similarity index 98%
rename from spring-cloud-stream-samples/double/src/main/java/config/SinkModuleDefinition.java
rename to spring-cloud-stream-samples/double/src/main/java/config/sink/SinkModuleDefinition.java
index 5276a61bf..6f6d4002a 100644
--- a/spring-cloud-stream-samples/double/src/main/java/config/SinkModuleDefinition.java
+++ b/spring-cloud-stream-samples/double/src/main/java/config/sink/SinkModuleDefinition.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package config;
+package config.sink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/adapter/InputChannelBinding.java b/spring-cloud-stream-samples/double/src/main/java/config/source/SourceApplication.java
similarity index 71%
rename from spring-cloud-stream/src/main/java/org/springframework/cloud/stream/adapter/InputChannelBinding.java
rename to spring-cloud-stream-samples/double/src/main/java/config/source/SourceApplication.java
index 74a386622..f1d907786 100644
--- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/adapter/InputChannelBinding.java
+++ b/spring-cloud-stream-samples/double/src/main/java/config/source/SourceApplication.java
@@ -14,19 +14,13 @@
* limitations under the License.
*/
-package org.springframework.cloud.stream.adapter;
+package config.source;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
- * @author Dave Syer
+ * @author Marius Bogoevici
*/
-public class InputChannelBinding extends ChannelBinding {
-
- protected InputChannelBinding() {
- super(null);
- }
-
- public InputChannelBinding(String localName) {
- super(localName);
- }
-
+@SpringBootApplication
+public class SourceApplication {
}
diff --git a/spring-cloud-stream-samples/double/src/main/java/config/SourceModuleDefinition.java b/spring-cloud-stream-samples/double/src/main/java/config/source/SourceModuleDefinition.java
similarity index 86%
rename from spring-cloud-stream-samples/double/src/main/java/config/SourceModuleDefinition.java
rename to spring-cloud-stream-samples/double/src/main/java/config/source/SourceModuleDefinition.java
index a3c6c6d6b..8d04e6a4f 100644
--- a/spring-cloud-stream-samples/double/src/main/java/config/SourceModuleDefinition.java
+++ b/spring-cloud-stream-samples/double/src/main/java/config/source/SourceModuleDefinition.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package config;
+package config.source;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -35,11 +35,10 @@ import org.springframework.messaging.support.GenericMessage;
@EnableModule(Source.class)
public class SourceModuleDefinition {
- @Value("${format:YYYY/MM/dd hh:mm:ss}")
- private String format;
+ private String format = "yyyy-MM-dd HH:mm:ss";
@Bean
- @InboundChannelAdapter(value = Source.OUTPUT, autoStartup = "false", poller = @Poller(fixedDelay = "${fixedDelay}", maxMessagesPerPoll = "1"))
+ @InboundChannelAdapter(value = Source.OUTPUT, poller = @Poller(fixedDelay = "${fixedDelay}", maxMessagesPerPoll = "1"))
public MessageSource timerMessageSource() {
return () -> new GenericMessage<>(new SimpleDateFormat(this.format).format(new Date()));
}
diff --git a/spring-cloud-stream-samples/double/src/main/java/demo/DoubleApplication.java b/spring-cloud-stream-samples/double/src/main/java/demo/DoubleApplication.java
index e66b398f7..3ebe00bd1 100644
--- a/spring-cloud-stream-samples/double/src/main/java/demo/DoubleApplication.java
+++ b/spring-cloud-stream-samples/double/src/main/java/demo/DoubleApplication.java
@@ -16,25 +16,17 @@
package demo;
-import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cloud.stream.aggregate.AggregateBuilder;
-import org.springframework.cloud.stream.aggregate.AggregateConfigurer;
+import org.springframework.cloud.stream.aggregate.AggregateApplication;
-import config.SinkModuleDefinition;
-import config.SourceModuleDefinition;
+import config.sink.SinkApplication;
+import config.source.SourceApplication;
@SpringBootApplication
-public class DoubleApplication implements AggregateConfigurer {
-
- @Override
- public void configure(AggregateBuilder builder) {
- builder.from(SourceModuleDefinition.class).as("source")
- .to(SinkModuleDefinition.class).as("sink");
- }
+public class DoubleApplication {
public static void main(String[] args) {
- SpringApplication.run(DoubleApplication.class, args);
+ AggregateApplication.run(SourceApplication.class, SinkApplication.class);
}
}
diff --git a/spring-cloud-stream-samples/double/src/main/resources/application.yml b/spring-cloud-stream-samples/double/src/main/resources/application.yml
index 136d06384..d84e8d13d 100644
--- a/spring-cloud-stream-samples/double/src/main/resources/application.yml
+++ b/spring-cloud-stream-samples/double/src/main/resources/application.yml
@@ -1 +1 @@
-
\ No newline at end of file
+fixedDelay: 1000
diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/adapter/ChannelBindingAdapter.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/adapter/ChannelBindingAdapter.java
deleted file mode 100644
index 9f6aa939b..000000000
--- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/adapter/ChannelBindingAdapter.java
+++ /dev/null
@@ -1,425 +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.adapter;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.springframework.beans.BeansException;
-import org.springframework.cloud.stream.binder.Binder;
-import org.springframework.cloud.stream.binder.BinderHeaders;
-import org.springframework.cloud.stream.config.ChannelBindingProperties;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
-import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.context.SmartLifecycle;
-import org.springframework.integration.channel.ChannelInterceptorAware;
-import org.springframework.integration.channel.DirectChannel;
-import org.springframework.integration.channel.interceptor.WireTap;
-import org.springframework.integration.support.DefaultMessageBuilderFactory;
-import org.springframework.integration.support.MessageBuilderFactory;
-import org.springframework.jmx.export.annotation.ManagedAttribute;
-import org.springframework.jmx.export.annotation.ManagedOperation;
-import org.springframework.jmx.export.annotation.ManagedResource;
-import org.springframework.messaging.Message;
-import org.springframework.messaging.MessageChannel;
-import org.springframework.messaging.core.DestinationResolver;
-import org.springframework.messaging.support.ChannelInterceptorAdapter;
-import org.springframework.util.Assert;
-import org.springframework.util.StringUtils;
-
-/**
- * Binds input/output channels.
- *
- * @author Mark Fisher
- * @author Dave Syer
- * @author Marius Bogoevici
- * @author Ilayaperumal Gopinathan
- */
-@ManagedResource
-public class ChannelBindingAdapter implements SmartLifecycle, ApplicationContextAware {
-
- private static Logger logger = LoggerFactory.getLogger(ChannelBindingAdapter.class);
-
- private Binder binder;
-
- private MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory();
-
- private Collection outputChannels = Collections.emptySet();
-
- private Collection inputChannels = Collections.emptySet();
-
- private boolean running = false;
-
- private final AtomicBoolean active = new AtomicBoolean(false);
-
- private boolean trackHistory = false;
-
- private ChannelBindingProperties module;
-
- private ConfigurableApplicationContext applicationContext;
-
- private ChannelLocator channelLocator;
-
- private DestinationResolver channelResolver;
-
- private Map bindings = new HashMap();
-
- public ChannelBindingAdapter(ChannelBindingProperties module, Binder binder) {
- this.module = module;
- this.binder = binder;
- }
-
- public void setChannelLocator(ChannelLocator channelLocator) {
- this.channelLocator = channelLocator;
- }
-
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- this.applicationContext = (ConfigurableApplicationContext) applicationContext;
- }
-
- public void setChannelResolver(DestinationResolver channelResolver) {
- this.channelResolver = channelResolver;
- }
-
- public void setMessageBuilderFactory(MessageBuilderFactory messageBuilderFactory) {
- this.messageBuilderFactory = messageBuilderFactory;
- }
-
- public void setTrackHistory(boolean trackHistory) {
- this.trackHistory = trackHistory;
- }
-
- public void setOutputChannels(Collection outputChannels) {
- this.outputChannels = new LinkedHashSet(outputChannels);
- }
-
- public void setInputChannels(Collection inputChannels) {
- this.inputChannels = new LinkedHashSet(inputChannels);
- }
-
- public ChannelsMetadata getChannelsMetadata() {
- ChannelsMetadata channels = new ChannelsMetadata();
- channels.setModule(this.module);
- channels.setInputChannels(new LinkedHashSet(this.inputChannels));
- channels.setOutputChannels(new LinkedHashSet(this.outputChannels));
- return channels;
- }
-
- public OutputChannelBinding getOutputChannel(String name) {
- if (name == null) {
- return null;
- }
- for (OutputChannelBinding binding : this.outputChannels) {
- if (name.equals(binding.getRemoteName())) {
- return binding;
- }
- }
- for (OutputChannelBinding binding : this.outputChannels) {
- if (name.equals(binding.getLocalName())) {
- return binding;
- }
- }
- return null;
- }
-
- public InputChannelBinding getInputChannel(String name) {
- if (name == null) {
- return null;
- }
- for (InputChannelBinding binding : this.inputChannels) {
- if (name.equals(binding.getRemoteName())) {
- return binding;
- }
- }
- for (InputChannelBinding binding : this.inputChannels) {
- if (name.equals(binding.getLocalName())) {
- return binding;
- }
- }
- return null;
- }
-
- public void tap(String outputChannel) {
- OutputChannelBinding channel = getOutputChannel(outputChannel);
- if (channel == null || channel.isTapped()) {
- return;
- }
- createAndBindTapChannel(channel.getTapChannelName(), channel.getLocalName());
- channel.setTapped(true);
- }
-
- public void untap(String outputChannel) {
- OutputChannelBinding channel = getOutputChannel(outputChannel);
- if (channel == null || !channel.isTapped()) {
- return;
- }
- String tapChannelName = channel.getTapChannelName();
- this.binder.unbindProducers(tapChannelName);
- channel.setTapped(false);
- }
-
- @ManagedOperation
- public void rebind() {
- boolean runnable = locateChannels();
- if (runnable && !this.running) {
- start();
- }
- if (!runnable && this.running) {
- stop();
- }
- }
-
- @Override
- @ManagedOperation
- public void start() {
- if (!this.running) {
- // Start everything, but don't call ourselves
- if (!this.active.get()) {
- if (this.active.compareAndSet(false, true)) {
- boolean ready = bindChannels();
- if (ready) {
- this.running = true;
- this.applicationContext.start();
- }
- this.active.set(false);
- }
- }
- }
- }
-
- @Override
- @ManagedOperation
- public void stop() {
- if (this.running) {
- if (!this.active.get()) {
- if (this.active.compareAndSet(false, true)) {
- unbindChannels();
- this.applicationContext.stop();
- this.active.set(false);
- }
- }
- }
- this.running = false;
- }
-
- @Override
- @ManagedAttribute
- public boolean isRunning() {
- return this.running && this.applicationContext.isRunning();
- }
-
- protected final void unbindChannels() {
- for (InputChannelBinding binding : this.inputChannels) {
- String name = this.bindings.get(binding.getRemoteName());
- if (name == null) {
- continue;
- }
- this.binder.unbindConsumers(name);
- }
- for (OutputChannelBinding binding : this.outputChannels) {
- String name = this.bindings.get(binding.getRemoteName());
- if (name == null) {
- continue;
- }
- this.binder.unbindProducers(name);
- if (binding.isTapped()) {
- String tapChannelName = binding.getTapChannelName();
- this.binder.unbindProducers(tapChannelName);
- }
- }
- }
-
- protected final boolean bindChannels() {
- if (!locateChannels()) {
- return false;
- }
- Map historyProperties = new LinkedHashMap();
- if (this.trackHistory) {
- // TODO: addHistoryTag();
- }
- for (OutputChannelBinding binding : this.outputChannels) {
- String name = binding.getRemoteName();
- MessageChannel outputChannel = this.channelResolver.resolveDestination(binding.getLocalName());
- bindMessageProducer(outputChannel, name, this.module.getProducerProperties());
- if (binding.isTapped()) {
- String tapChannelName = this.channelLocator.tap(name);
- binding.setTapChannelName(tapChannelName);
- // tappableChannels.put(tapChannelName, outputChannel);
- // if (isTapActive(tapChannelName)) {
- createAndBindTapChannel(tapChannelName, name);
- // }
- }
- if (this.trackHistory) {
- historyProperties.put("outputChannel", name);
- track(outputChannel, historyProperties);
- }
- }
- for (InputChannelBinding binding : this.inputChannels) {
- String name = binding.getRemoteName();
- MessageChannel inputChannel = this.channelResolver.resolveDestination(binding.getLocalName());
- bindMessageConsumer(inputChannel, name, this.module.getConsumerProperties());
- if (this.trackHistory && this.outputChannels.size() != 1) {
- historyProperties.put("inputChannel", name);
- track(inputChannel, historyProperties);
- }
- }
- return true;
- }
-
- private boolean locateChannels() {
- logger.info("Locating channels");
- boolean located = true;
- for (OutputChannelBinding binding : this.outputChannels) {
- String name = this.channelLocator.locate(binding.getLocalName());
- if (name == null) {
- logger.info("No channel found for: " + binding.getLocalName());
- located = false;
- }
- binding.setRemoteName(name);
- this.bindings.put(binding.getRemoteName(), name);
- }
- for (InputChannelBinding binding : this.inputChannels) {
- String name = this.channelLocator.locate(binding.getLocalName());
- if (name == null) {
- logger.info("No channel found for: " + binding.getLocalName());
- located = false;
- }
- binding.setRemoteName(name);
- this.bindings.put(binding.getRemoteName(), name);
- }
- return located;
- }
-
- /*
- * Following methods copied from parent to support the bindChannels() method above
- */
-
- private void bindMessageConsumer(MessageChannel inputChannel,
- String inputChannelName, Properties consumerProperties) {
- if (isChannelPubSub(inputChannelName)) {
- this.binder.bindPubSubConsumer(inputChannelName, inputChannel, consumerProperties);
- }
- else {
- this.binder.bindConsumer(inputChannelName, inputChannel, consumerProperties);
- }
- }
-
- private void bindMessageProducer(MessageChannel outputChannel,
- String outputChannelName, Properties producerProperties) {
- if (isChannelPubSub(outputChannelName)) {
- this.binder.bindPubSubProducer(outputChannelName, outputChannel, producerProperties);
- }
- else {
- this.binder.bindProducer(outputChannelName, outputChannel, producerProperties);
- }
- }
-
- private boolean isChannelPubSub(String channelName) {
- Assert.isTrue(StringUtils.hasText(channelName), "Channel name should not be empty/null.");
- return (channelName.startsWith("tap:") || channelName.startsWith("topic:"));
- }
-
- /**
- * Creates a wiretap on the output channel and binds the tap channel to
- * {@link org.springframework.cloud.stream.binder.Binder}'s message target.
- *
- * @param tapChannelName the name of the tap channel
- * @param localName the channel to tap
- */
- private void createAndBindTapChannel(String tapChannelName, String localName) {
- logger.info("creating and binding tap channel for {}", tapChannelName);
- MessageChannel channel = this.channelResolver.resolveDestination(localName);
- if (channel instanceof ChannelInterceptorAware) {
- DirectChannel tapChannel = new DirectChannel();
- tapChannel.setBeanName(tapChannelName + ".tap.bridge");
- this.binder.bindPubSubProducer(tapChannelName, tapChannel, null); // TODO
- // tap
- // producer
- // props
- tapOutputChannel(tapChannel, (ChannelInterceptorAware) channel);
- }
- else {
- if (logger.isDebugEnabled()) {
- logger.debug("output channel is not interceptor aware. Tap will not be created.");
- }
- }
- }
-
- private MessageChannel tapOutputChannel(MessageChannel tapChannel, ChannelInterceptorAware outputChannel) {
- outputChannel.addInterceptor(new WireTap(tapChannel));
- return tapChannel;
- }
-
- private void track(MessageChannel channel, final Map historyProps) {
- if (channel instanceof ChannelInterceptorAware) {
- ((ChannelInterceptorAware) channel)
- .addInterceptor(new ChannelInterceptorAdapter() {
-
- @Override
- public Message> preSend(Message> message, MessageChannel channel) {
- @SuppressWarnings("unchecked")
- Collection