diff --git a/spring-cloud-stream-module-launcher/pom.xml b/spring-cloud-stream-module-launcher/pom.xml index 4914e5df3..cc2258252 100644 --- a/spring-cloud-stream-module-launcher/pom.xml +++ b/spring-cloud-stream-module-launcher/pom.xml @@ -16,7 +16,7 @@ UTF-8 - org.springframework.cloud.stream.module.launcher.ModuleLauncher + org.springframework.cloud.stream.module.launcher.ModuleLauncherApplication 1.0.2.v20150114 3.1.0 1.57 @@ -27,6 +27,10 @@ org.springframework spring-core + + org.springframework.boot + spring-boot-starter-actuator + org.springframework.boot spring-boot-loader 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 a55984fcf..071f756cf 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 @@ -21,7 +21,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -34,13 +33,8 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** - * Bootstrap for launching one or more modules. The module coordinates must be provided via the "modules" system - * property or "MODULES" environment variable as a comma-delimited list. The format of each module must conform to the - * Aether convention: - * <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version> - *

- * To pass args to a module, prefix with the module name and a dot. The arg name will be de-qualified and passed along. - * For example: --foo.bar=123 becomes --bar=123 and is only passed to the 'foo' module. + * A component that launches one or more modules, delegating their resolution to an + * underlying {@link ModuleResolver}. * * @author Mark Fisher * @author Ilayaperumal Gopinathan @@ -48,12 +42,6 @@ import org.springframework.util.StringUtils; */ public class ModuleLauncher { - // TODO ensure that this properly supports Windows too - private static final String DEFAULT_LOCAL_REPO = - System.getProperty("user.home") + File.separator + ".m2" + File.separator + "repository"; - - private static final String DEFAULT_REMOTE_REPO = "http://repo.spring.io/spring-cloud-stream-modules"; - private static final String DEFAULT_EXTENSION = "jar"; private static final String DEFAULT_CLASSIFIER = "exec"; @@ -63,15 +51,32 @@ public class ModuleLauncher { private final ModuleResolver moduleResolver; + @Deprecated public ModuleLauncher() { - this(determineLocalRepositoryLocation(), - Collections.singletonMap("spring-cloud-stream-modules", determineRemoteRepositoryLocation())); + this(new AetherModuleResolver(new File(ModuleLauncherConfiguration.DEFAULT_LOCAL_REPO), + Collections.singletonMap("spring-cloud-stream-modules", ModuleLauncherConfiguration.DEFAULT_REMOTE_REPO))); } - public ModuleLauncher(String localRepository, Map remoteRepositories) { - this.moduleResolver = new AetherModuleResolver(new File(localRepository), remoteRepositories); + /** + * Creates a module launcher using the provided module resolver + * @param moduleResolver the module resolver instance to use + */ + public ModuleLauncher(ModuleResolver moduleResolver) { + this.moduleResolver = moduleResolver; } + /** + * Launches one or more modules, with the corresponding arguments, if any. + * + * The format of each module must conform to the Aether convention: + * <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version> + * + * To pass arguments to a module, prefix with the module name and a dot. The arg name will be de-qualified and passed along. + * For example: ---Dorg.springframework.cloud.stream.module:time-source:1.0.0.BUILD-SNAPSHOT.bar=123 becomes --bar=123 and is only passed to the 'org.springframework.cloud.stream.module:time-source:1.0.0.BUILD-SNAPSHOT' module. + * + * @param modules a list of modules + * @param args a list of arguments, prefixed with the module name + */ public void launch(String[] modules, String[] args) { for (String module : modules) { List moduleArgs = new ArrayList<>(); @@ -109,38 +114,4 @@ public class ModuleLauncher { return moduleResolver.resolve(groupId, artifactId, extension, classifier, version); } - private static String determineLocalRepositoryLocation() { - String localRepository = System.getProperty("local.repository"); - if (localRepository == null) { - localRepository = System.getenv("LOCAL_REPOSITORY"); - } - if (localRepository == null) { - localRepository = DEFAULT_LOCAL_REPO; - } - return localRepository; - } - - private static String determineRemoteRepositoryLocation() { - String remoteRepository = System.getProperty("remote.repository"); - if (remoteRepository == null) { - remoteRepository = System.getenv("REMOTE_REPOSITORY"); - } - if (remoteRepository == null) { - remoteRepository = DEFAULT_REMOTE_REPO; - } - return remoteRepository; - } - - public static void main(String[] args) throws Exception { - String modules = System.getProperty("modules"); - if (modules == null) { - modules = System.getenv("MODULES"); - } - if (modules == null) { - System.err.println("Either the 'modules' system property or 'MODULES' environment variable is required."); - System.exit(1); - } - ModuleLauncher launcher = new ModuleLauncher(); - launcher.launch(modules.split(","), args); - } } diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherApplication.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherApplication.java new file mode 100644 index 000000000..19450b92c --- /dev/null +++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherApplication.java @@ -0,0 +1,48 @@ +/* + * 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.launcher; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +/** + * Bootstrap for launching one or more modules, provided via the "modules" system property or "MODULES" environment variable as a comma-delimited list, with the arguments provided at + * launch. + * + * @see ModuleLauncher#launch(String[], String[]) for module and argument structure and format + * + * @author Marius Bogoevici + */ +@SpringBootApplication +@ComponentScan(basePackageClasses = ModuleLauncherApplication.class) +public class ModuleLauncherApplication { + + private static final Log log = LogFactory.getLog(ModuleLauncherApplication.class); + + public static void main(String[] args) throws Exception { + try { + SpringApplication.run(ModuleLauncherApplication.class, args); + } + catch (Exception e) { + log.error("Failed to launch module ", e); + } + } +} 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 new file mode 100644 index 000000000..828cf2c91 --- /dev/null +++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherConfiguration.java @@ -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 org.springframework.cloud.stream.module.launcher; + +import java.io.File; +import java.util.Collections; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.boot.ApplicationRunner; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.stream.module.resolver.AetherModuleResolver; +import org.springframework.cloud.stream.module.resolver.ModuleResolver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Marius Bogoevici + */ +@Configuration +@ConfigurationProperties +public class ModuleLauncherConfiguration { + + public static final String DEFAULT_LOCAL_REPO = + System.getProperty("user.home") + File.separator + ".m2" + File.separator + "repository"; + + public static final String DEFAULT_REMOTE_REPO = "https://repo.spring.io/libs-snapshot"; + + private File localRepository = new File(DEFAULT_LOCAL_REPO); + + private String remoteRepository = DEFAULT_REMOTE_REPO; + + public void setLocalRepository(File localRepository) { + this.localRepository = localRepository; + } + + public void setRemoteRepository(String remoteRepository) { + this.remoteRepository = remoteRepository; + } + + /** + * Sets up the default Aether-based module resolver, unless overridden + */ + @Bean + @ConditionalOnMissingBean(ModuleResolver.class) + public ModuleResolver moduleResolver() { + return new AetherModuleResolver(localRepository, Collections.singletonMap("remoteRepository", remoteRepository)); + } + + @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/launcher/ModuleLauncherRunner.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherRunner.java new file mode 100644 index 000000000..a18d8c560 --- /dev/null +++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncherRunner.java @@ -0,0 +1,64 @@ +/* + * 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.launcher; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * @author Marius Bogoevici + */ +@Component +@ConfigurationProperties +public class ModuleLauncherRunner implements ApplicationRunner, InitializingBean { + + private final static Log log = LogFactory.getLog(ModuleLauncherRunner.class); + + @Autowired + private ModuleLauncher moduleLauncher; + + private String modules; + + public void setModules(String modules) { + this.modules = modules; + } + + @Override + public void afterPropertiesSet() throws Exception { + Assert.hasText(modules, "A list of modules must be specified"); + } + + @Override + public void run(ApplicationArguments applicationArguments) throws Exception { + String[] launchedModules = modules.split(","); + if (log.isInfoEnabled()) { + log.info("Launching: " + modules + " with arguments: " + + StringUtils.arrayToCommaDelimitedString(applicationArguments.getSourceArgs())); + } + moduleLauncher.launch(launchedModules, applicationArguments.getSourceArgs()); + } +} diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/AetherModuleResolver.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/AetherModuleResolver.java index df642cccb..0e6795eaf 100644 --- a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/AetherModuleResolver.java +++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/AetherModuleResolver.java @@ -20,6 +20,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.maven.repository.internal.MavenRepositorySystemUtils; import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.RepositorySystem; @@ -42,6 +44,7 @@ import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; /** * An implementation of ModuleResolver using remoteRepositories) { + Assert.notNull(localRepository, "Local repository path cannot be null"); + if (log.isDebugEnabled()) { + log.debug("Local repository: " + localRepository); + if (!CollectionUtils.isEmpty(remoteRepositories)) { + // just listing the values, ids are simply informative + log.debug("Remote repositories: " + StringUtils.collectionToCommaDelimitedString(remoteRepositories.values())); + } + } if (!localRepository.exists()) { Assert.isTrue(localRepository.mkdirs(), "Unable to create directory for local repository: " + localRepository);