boot-based ModuleLauncher

deprecates default constructor for ModuleLauncher until dependencies are refactored
This commit is contained in:
Marius Bogoevici
2015-08-03 09:48:42 -04:00
committed by Mark Fisher
parent 95a34f6ab0
commit 31daf48cc2
6 changed files with 226 additions and 53 deletions

View File

@@ -16,7 +16,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>org.springframework.cloud.stream.module.launcher.ModuleLauncher</start-class>
<start-class>org.springframework.cloud.stream.module.launcher.ModuleLauncherApplication</start-class>
<aether.version>1.0.2.v20150114</aether.version>
<maven.version>3.1.0</maven.version>
<wiremock.version>1.57</wiremock.version>
@@ -27,6 +27,10 @@
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>

View File

@@ -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
* <a href="http://www.eclipse.org/aether">Aether</a> convention:
* <code>&lt;groupId&gt;:&lt;artifactId&gt;[:&lt;extension&gt;[:&lt;classifier&gt;]]:&lt;version&gt;</code>
* <p>
* 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: <code>--foo.bar=123</code> becomes <code>--bar=123</code> 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<String, String> 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 <a href="http://www.eclipse.org/aether">Aether</a> convention:
* <code>&lt;groupId&gt;:&lt;artifactId&gt;[:&lt;extension&gt;[:&lt;classifier&gt;]]:&lt;version&gt;</code>
*
* 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: <code>---Dorg.springframework.cloud.stream.module:time-source:1.0.0.BUILD-SNAPSHOT.bar=123</code> becomes <code>--bar=123</code> 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<String> 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);
}
}

View File

@@ -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);
}
}
}

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 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);
}
}

View File

@@ -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());
}
}

View File

@@ -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 <a href="http://www.eclipse.org/aether/>aether</a> to resolve the module
@@ -50,9 +53,12 @@ import org.springframework.util.CollectionUtils;
*
* @author David Turanski
* @author Mark Fisher
* @author Marius Bogoevici
*/
public class AetherModuleResolver implements ModuleResolver {
private static final Log log = LogFactory.getLog(AetherModuleResolver.class);
private static final String DEFAULT_CONTENT_TYPE = "default";
private final File localRepository;
@@ -68,6 +74,14 @@ public class AetherModuleResolver implements ModuleResolver {
* may be null or empty if the local repository is off line.
*/
public AetherModuleResolver(File localRepository, Map<String, String> 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);