diff --git a/pom.xml b/pom.xml
index 1df2739f3..c4aabad8a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,6 +33,7 @@
spring-cloud-stream-module-launcher
docs
spring-cloud-stream-test-support
+ spring-cloud-stream-configuration-metadata
diff --git a/spring-cloud-stream-configuration-metadata/pom.xml b/spring-cloud-stream-configuration-metadata/pom.xml
new file mode 100644
index 000000000..f2ab74863
--- /dev/null
+++ b/spring-cloud-stream-configuration-metadata/pom.xml
@@ -0,0 +1,26 @@
+
+
+ 4.0.0
+
+ org.springframework.cloud
+ spring-cloud-stream-parent
+ 1.0.0.BUILD-SNAPSHOT
+
+ spring-cloud-stream-configuration-metadata
+
+ UTF-8
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-stream-module-launcher
+
+
+ org.springframework.boot
+ spring-boot-configuration-metadata
+
+
+
diff --git a/spring-cloud-stream-configuration-metadata/src/main/java/org/springframework/cloud/stream/configuration/metadata/ModuleConfigurationMetadataResolver.java b/spring-cloud-stream-configuration-metadata/src/main/java/org/springframework/cloud/stream/configuration/metadata/ModuleConfigurationMetadataResolver.java
new file mode 100644
index 000000000..a8e0a3f34
--- /dev/null
+++ b/spring-cloud-stream-configuration-metadata/src/main/java/org/springframework/cloud/stream/configuration/metadata/ModuleConfigurationMetadataResolver.java
@@ -0,0 +1,116 @@
+/*
+ * 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.configuration.metadata;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
+import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder;
+import org.springframework.boot.loader.archive.Archive;
+import org.springframework.boot.loader.archive.ExplodedArchive;
+import org.springframework.boot.loader.archive.JarFileArchive;
+import org.springframework.boot.loader.jar.JarFile;
+import org.springframework.cloud.stream.module.launcher.ModuleJarLauncher;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+
+/**
+ * Used to retrieve metadata about the configuration properties that can alter a module behavior.
+ *
+ * @author Eric Bottard
+ */
+public class ModuleConfigurationMetadataResolver {
+
+ public ModuleConfigurationMetadataResolver() {
+ JarFile.registerUrlProtocolHandler();
+ }
+
+ /**
+ * Return metadata about configuration properties that are documented via
+ *
+ * Spring Boot configuration metadata and visible in a module.
+ *
+ * @param module a Spring Cloud Stream module; typically a Boot uberjar,
+ * but directories are supported as well
+ */
+ public List listProperties(Resource module) {
+ List result = new ArrayList<>();
+ ClassLoader classLoader = null;
+ try {
+ File moduleFile = module.getFile();
+ Archive archive = moduleFile.isDirectory() ? new ExplodedArchive(moduleFile) : new JarFileArchive(moduleFile);
+ classLoader = createClassLoader(archive);
+ ConfigurationMetadataRepositoryJsonBuilder builder = ConfigurationMetadataRepositoryJsonBuilder.create();
+ ResourcePatternResolver moduleResourceLoader = new PathMatchingResourcePatternResolver(classLoader);
+ for (Resource r : moduleResourceLoader.getResources("classpath*:/META-INF/*spring-configuration-metadata.json")) {
+ builder.withJsonResource(r.getInputStream());
+ }
+ for (ConfigurationMetadataProperty property : builder.build().getAllProperties().values()) {
+ result.add(property);
+ }
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Exception trying to list configuration properties for module " + module, e);
+ }
+ finally {
+ if (classLoader instanceof Closeable) {
+ try {
+ ((Closeable) classLoader).close();
+ }
+ catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Return a {@link ClassLoader} for accessing resources in the provided
+ * {@link Archive}. The caller is responsible for disposing of the
+ * class loader.
+ *
+ * @param archive the archive for which to return a class loader
+ * @return class loader for the given archive
+ * @throws Exception if the class loader cannot be created
+ */
+ protected ClassLoader createClassLoader(Archive archive) throws Exception {
+ return new ClassLoaderExposingJarLauncher(archive).createClassLoader();
+ }
+
+
+ /**
+ * Extension of {@link ModuleJarLauncher} used for exposing a {@link ClassLoader}
+ * for the provided {@link Archive}.
+ */
+ private static class ClassLoaderExposingJarLauncher extends ModuleJarLauncher {
+
+ public ClassLoaderExposingJarLauncher(Archive archive) {
+ super(archive);
+ }
+
+ protected ClassLoader createClassLoader() throws Exception {
+ List classPathArchives = getClassPathArchives();
+ return createClassLoader(classPathArchives);
+ }
+ }
+}
diff --git a/spring-cloud-stream-configuration-metadata/src/main/java/org/springframework/cloud/stream/configuration/metadata/ModuleConfigurationMetadataResolverAutoConfiguration.java b/spring-cloud-stream-configuration-metadata/src/main/java/org/springframework/cloud/stream/configuration/metadata/ModuleConfigurationMetadataResolverAutoConfiguration.java
new file mode 100644
index 000000000..7aa664710
--- /dev/null
+++ b/spring-cloud-stream-configuration-metadata/src/main/java/org/springframework/cloud/stream/configuration/metadata/ModuleConfigurationMetadataResolverAutoConfiguration.java
@@ -0,0 +1,36 @@
+/*
+ * 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.configuration.metadata;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * Automatically exposes a ModuleConfigurationMetadataResolver if none is already registered.
+ *
+ * @author Eric Bottard
+ */
+@Configuration
+public class ModuleConfigurationMetadataResolverAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean(ModuleConfigurationMetadataResolver.class)
+ public ModuleConfigurationMetadataResolver metadataResolver() {
+ return new ModuleConfigurationMetadataResolver();
+ }
+}
diff --git a/spring-cloud-stream-configuration-metadata/src/main/resources/META-INF/spring.factories b/spring-cloud-stream-configuration-metadata/src/main/resources/META-INF/spring.factories
new file mode 100644
index 000000000..c53b5c782
--- /dev/null
+++ b/spring-cloud-stream-configuration-metadata/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration:\
+org.springframework.cloud.stream.configuration.metadata.ModuleConfigurationMetadataResolverAutoConfiguration
diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleJarLauncher.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleJarLauncher.java
index bd497aa4e..e01a3fecc 100644
--- a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleJarLauncher.java
+++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleJarLauncher.java
@@ -29,8 +29,8 @@ import org.springframework.util.ReflectionUtils;
/**
* A {@link JarLauncher} that supports launching module archive.
*
- * Also, it restricts the classloader of the launched application to the contents of the uber-jar and the
- * extension classloader of the JVM.
+ * Also, it restricts the classloader of the launched application to the contents
+ * of the uber-jar and the extension classloader of the JVM.
*
* @author Mark Fisher
* @author Marius Bogoevici
diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherConfiguration.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherConfiguration.java
index 8584e4ce0..370ca3120 100644
--- a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherConfiguration.java
+++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherConfiguration.java
@@ -16,17 +16,11 @@
package org.springframework.cloud.stream.module.launcher;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.cloud.stream.module.resolver.AetherModuleResolver;
import org.springframework.cloud.stream.module.resolver.ModuleResolver;
-import org.springframework.cloud.stream.module.resolver.ModuleResolverProperties;
+import org.springframework.cloud.stream.module.resolver.ModuleResolverConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
/**
* Configuration class that has the beans required for module launcher.
@@ -36,28 +30,9 @@ import org.springframework.context.annotation.Configuration;
* @author Eric Bottard
*/
@Configuration
-@EnableConfigurationProperties(ModuleResolverProperties.class)
+@Import(ModuleResolverConfiguration.class)
public class ModuleLauncherConfiguration {
- @Autowired
- private ModuleResolverProperties properties;
-
- /**
- * Sets up the default Aether-based module resolver, unless overridden.
- */
- @Bean
- @ConditionalOnMissingBean(ModuleResolver.class)
- public ModuleResolver moduleResolver() {
- int i = 1;
- Map repositoriesMap = new HashMap<>();
- for (String repository: properties.getRemoteRepositories()) {
- repositoriesMap.put("repository " + i++, repository);
- }
- AetherModuleResolver aetherModuleResolver = new AetherModuleResolver(properties.getLocalRepository(), repositoriesMap);
- aetherModuleResolver.setOffline(properties.isOffline());
- return aetherModuleResolver;
- }
-
@Bean
public ModuleLauncher moduleLauncher(ModuleResolver moduleResolver) {
return new ModuleLauncher(moduleResolver);
diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolverConfiguration.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolverConfiguration.java
new file mode 100644
index 000000000..6f403ca67
--- /dev/null
+++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolverConfiguration.java
@@ -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.stream.module.resolver;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * Sets up the default Aether-based module resolver, unless overridden.
+ *
+ * @author Eric Bottard
+ */
+@EnableConfigurationProperties(ModuleResolverProperties.class)
+public class ModuleResolverConfiguration {
+
+ @Autowired
+ private ModuleResolverProperties properties;
+
+ @Bean
+ @ConditionalOnMissingBean(ModuleResolver.class)
+ public ModuleResolver moduleResolver() {
+ int i = 1;
+ Map repositoriesMap = new HashMap<>();
+ for (String repository: properties.getRemoteRepositories()) {
+ repositoriesMap.put("repository " + i++, repository);
+ }
+ AetherModuleResolver aetherModuleResolver = new AetherModuleResolver(properties.getLocalRepository(), repositoriesMap);
+ aetherModuleResolver.setOffline(properties.isOffline());
+ return aetherModuleResolver;
+ }
+}