From 9b1e41841161e1f9115423995ae4537800c56ccb Mon Sep 17 00:00:00 2001 From: Eric Bottard Date: Tue, 15 Sep 2015 09:34:45 -0400 Subject: [PATCH] XD-3355, XD-3582: Support options metadata --- pom.xml | 1 + .../pom.xml | 26 ++++ .../ModuleConfigurationMetadataResolver.java | 116 ++++++++++++++++++ ...tionMetadataResolverAutoConfiguration.java | 36 ++++++ .../main/resources/META-INF/spring.factories | 2 + .../module/launcher/ModuleJarLauncher.java | 4 +- .../launcher/ModuleLauncherConfiguration.java | 31 +---- .../resolver/ModuleResolverConfiguration.java | 50 ++++++++ 8 files changed, 236 insertions(+), 30 deletions(-) create mode 100644 spring-cloud-stream-configuration-metadata/pom.xml create mode 100644 spring-cloud-stream-configuration-metadata/src/main/java/org/springframework/cloud/stream/configuration/metadata/ModuleConfigurationMetadataResolver.java create mode 100644 spring-cloud-stream-configuration-metadata/src/main/java/org/springframework/cloud/stream/configuration/metadata/ModuleConfigurationMetadataResolverAutoConfiguration.java create mode 100644 spring-cloud-stream-configuration-metadata/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolverConfiguration.java 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; + } +}