committed by
Marius Bogoevici
parent
4dfd217db6
commit
00a5ac4341
3
pom.xml
3
pom.xml
@@ -19,7 +19,6 @@
|
||||
</scm>
|
||||
<properties>
|
||||
<java.version>1.7</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
@@ -40,11 +39,9 @@
|
||||
<module>spring-cloud-stream-tuple</module>
|
||||
<module>spring-cloud-stream-starters</module>
|
||||
<module>spring-cloud-stream-rxjava</module>
|
||||
<module>spring-cloud-stream-module-launcher</module>
|
||||
<module>spring-cloud-stream-test-support</module>
|
||||
<module>spring-cloud-stream-test-support-internal</module>
|
||||
<module>spring-cloud-stream-integration-tests</module>
|
||||
<module>spring-cloud-stream-configuration-metadata</module>
|
||||
<module>spring-cloud-stream-docs</module>
|
||||
</modules>
|
||||
<build>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-stream-configuration-metadata</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-module-launcher</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-metadata</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,157 +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.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.ConfigurationMetadataGroup;
|
||||
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 (as groups) that are documented via
|
||||
* <a href="http://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html">
|
||||
* Spring Boot configuration metadata</a> and visible in a module.
|
||||
*
|
||||
* @param module a Spring Cloud Stream module; typically a Boot uberjar,
|
||||
* but directories are supported as well
|
||||
*/
|
||||
public List<ConfigurationMetadataGroup> listPropertyGroups(Resource module) {
|
||||
List<ConfigurationMetadataGroup> 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 (ConfigurationMetadataGroup group : builder.build().getAllGroups().values()) {
|
||||
result.add(group);
|
||||
}
|
||||
}
|
||||
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 metadata about configuration properties that are documented via
|
||||
* <a href="http://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html">
|
||||
* Spring Boot configuration metadata</a> and visible in a module.
|
||||
*
|
||||
* @param module a Spring Cloud Stream module; typically a Boot uberjar,
|
||||
* but directories are supported as well
|
||||
*/
|
||||
public List<ConfigurationMetadataProperty> listProperties(Resource module) {
|
||||
List<ConfigurationMetadataProperty> 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<Archive> classPathArchives = getClassPathArchives();
|
||||
return createClassLoader(classPathArchives);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +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.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();
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration:\
|
||||
org.springframework.cloud.stream.configuration.metadata.ModuleConfigurationMetadataResolverAutoConfiguration
|
||||
@@ -73,11 +73,6 @@
|
||||
<artifactId>spring-cloud-stream-binder-test</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-module-launcher</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-tuple</artifactId>
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
# Module Launcher
|
||||
|
||||
The Module Launcher provides a single entry point that bootstraps module JARs located in a Maven repository. A single Docker image can then be used to launch any of those JARs based on an environment variable. When running standalone, a system property may be used instead of an environment variable, so that multiple instances of the Module Launcher may run on a single machine. The following examples demonstrate running the modules for the *ticktock* stream (`time-source | log-sink`).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1: clone and build the spring-cloud-stream project:
|
||||
|
||||
````
|
||||
git clone https://github.com/spring-cloud/spring-cloud-stream.git
|
||||
cd spring-cloud-stream
|
||||
mvn package
|
||||
cd ..
|
||||
````
|
||||
|
||||
2: start redis locally via `redis-server` or `docker-compose` (there's a `docker-compose.yml` in `spring-cloud-stream-samples`). Optionally start `redis-cli` and use the `MONITOR` command to watch activity.
|
||||
|
||||
*NOTE:* redis.conf (on OSX it is found here: /usr/local/etc/redis.conf) may need to be updated to set the binding to an address other than 127.0.0.1 else the docker instances will fail to connect. For example: bind 0.0.0.0
|
||||
|
||||
## Running Standalone
|
||||
|
||||
From the `spring-cloud-stream/spring-cloud-stream-module-launcher` directory:
|
||||
|
||||
````
|
||||
java -Dmodules=org.springframework.cloud.stream.module:time-source:jar:exec:1.0.0.BUILD-SNAPSHOT -Dspring.cloud.stream.bindings.output=ticktock -jar target/spring-cloud-stream-module-launcher-1.0.0.BUILD-SNAPSHOT.jar
|
||||
java -Dmodules=org.springframework.cloud.stream.module:log-sink:jar:exec:1.0.0.BUILD-SNAPSHOT -Dargs.0.server.port=8081 -Dspring.cloud.stream.bindings.input=ticktock -jar target/spring-cloud-stream-module-launcher-1.0.0.BUILD-SNAPSHOT.jar
|
||||
````
|
||||
|
||||
Note that `server.port` needs to be specified explicitly for the log sink module as the time source module already uses the default port `8080`.
|
||||
The module launcher is able to launch several modules, hence the `args.0.` prefix.
|
||||
The binding property is set to use the same name `ticktock` for both the output/input bindings of source/sink modules so that the log sink receives messages from the time source.
|
||||
|
||||
The time messages will be emitted every second. The console for the log module will display each:
|
||||
|
||||
````
|
||||
2015-08-26 14:21:44.546 INFO 35725 --- [hannel-adapter1] o.s.cloud.stream.module.log.LogSink : Received: 2015-08-26 14:21:44
|
||||
2015-08-26 14:21:45.548 INFO 35725 --- [hannel-adapter1] o.s.cloud.stream.module.log.LogSink : Received: 2015-08-26 14:21:45
|
||||
2015-08-26 14:21:46.550 INFO 35725 --- [hannel-adapter1] o.s.cloud.stream.module.log.LogSink : Received: 2015-08-26 14:21:46
|
||||
````
|
||||
|
||||
*NOTE:* the two modules will be launched within a single process if both are provided (comma-delimited) via `-Dmodules`
|
||||
|
||||
## Running with Docker
|
||||
|
||||
The easiest way to get a demo working is to use `docker-compose` (From the `spring-cloud-stream/spring-cloud-stream-module-launcher` directory):
|
||||
|
||||
Make sure to set `DOCKER_HOST`. If you are running a `boot2docker` VM, $(boot2docker shellinit) would set that up.
|
||||
|
||||
```
|
||||
$ mvn package docker:build
|
||||
$ docker-compose up
|
||||
...
|
||||
logsink_1 | 2015-08-11 08:25:49.909 INFO 1 --- [hannel-adapter1] o.s.cloud.stream.module.log.LogSink : Received: 2015-08-11 08:25:49
|
||||
logsink_1 | 2015-08-11 08:25:50.909 INFO 1 --- [hannel-adapter1] o.s.cloud.stream.module.log.LogSink : Received: 2015-08-11 08:25:50
|
||||
...
|
||||
```
|
||||
|
||||
You can also run each module individually as a Docker process by passing environment variables for the module name as well as the host machine's IP address for the redis connection to be established within the container:
|
||||
To find out redis host IP:
|
||||
```
|
||||
Get the container ID of redis: `docker ps`
|
||||
Get the IP address by inspecting the container: `docker inspect <containerID>`
|
||||
|
||||
```
|
||||
To run the modules individually on docker:
|
||||
````
|
||||
docker run -p 8080:8080 -e MODULES=org.springframework.cloud.stream.module:time-source:jar:exec:1.0.0.BUILD-SNAPSHOT \
|
||||
-e spring.cloud.stream.bindings.output=ticktock -e SPRING_REDIS_HOST=<Redis-Host-IP> springcloud/stream-module-launcher
|
||||
|
||||
docker run -p 8081:8080 -e MODULES=org.springframework.cloud.stream.module:log-sink:jar:exec:1.0.0.BUILD-SNAPSHOT \
|
||||
-e spring.cloud.stream.bindings.input=ticktock -e SPRING_REDIS_HOST=<Redis-Host-IP> springcloud/stream-module-launcher
|
||||
````
|
||||
Note the binding name `ticktock` is specified for the source's output and sink's input.
|
||||
The port mapping is done so that individual modules' http endpoints can be accessed via the docker VM port.
|
||||
|
||||
To run pub/sub modules individually on docker, the binding name has to start with `topic:`.
|
||||
|
||||
````
|
||||
docker run -p 8080:8080 -e MODULES=org.springframework.cloud.stream.module:time-source:jar:exec:1.0.0.BUILD-SNAPSHOT \
|
||||
-e spring.cloud.stream.bindings.output=topic:foo -e SPRING_REDIS_HOST=<Redis-Host-IP> springcloud/stream-module-launcher
|
||||
|
||||
docker run -p 8081:8080 -e MODULES=org.springframework.cloud.stream.module:log-sink:jar:exec:1.0.0.BUILD-SNAPSHOT \
|
||||
-e spring.cloud.stream.bindings.input=topic:foo -e SPRING_REDIS_HOST=<Redis-Host-IP> springcloud/stream-module-launcher
|
||||
|
||||
docker run -p 8082:8080 -e MODULES=org.springframework.cloud.stream.module:log-sink:jar:exec:1.0.0.BUILD-SNAPSHOT \
|
||||
-e spring.cloud.stream.bindings.input=topic:foo -e SPRING_REDIS_HOST=<Redis-Host-IP> springcloud/stream-module-launcher
|
||||
````
|
||||
In the above scenario, both the sink modules receive the same messages from the time source.
|
||||
@@ -1,24 +0,0 @@
|
||||
redis:
|
||||
image: redis
|
||||
|
||||
timesource:
|
||||
image: springcloud/stream-module-launcher
|
||||
links:
|
||||
- redis
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
SPRING_REDIS_HOST: redis
|
||||
MODULES: org.springframework.cloud.stream.module:time-source:1.0.0.BUILD-SNAPSHOT
|
||||
SPRING_CLOUD_STREAM_BINDINGS_OUTPUT: ticktock
|
||||
|
||||
logsink:
|
||||
image: springcloud/stream-module-launcher
|
||||
links:
|
||||
- redis
|
||||
ports:
|
||||
- "8081:8080"
|
||||
environment:
|
||||
SPRING_REDIS_HOST: redis
|
||||
MODULES: org.springframework.cloud.stream.module:log-sink:1.0.0.BUILD-SNAPSHOT
|
||||
SPRING_CLOUD_STREAM_BINDINGS_INPUT: ticktock
|
||||
@@ -1,154 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-stream-module-launcher</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>spring-cloud-stream-module-launcher</name>
|
||||
<description>application for launching spring-cloud-stream modules</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<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>
|
||||
<docker.image.prefix>springcloud</docker.image.prefix>
|
||||
<docker.image.name>stream-module-launcher</docker.image.name>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-loader</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-impl</artifactId>
|
||||
<version>${aether.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-connector-basic</artifactId>
|
||||
<version>${aether.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-transport-file</artifactId>
|
||||
<version>${aether.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-transport-http</artifactId>
|
||||
<version>${aether.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-aether-provider</artifactId>
|
||||
<version>${maven.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock</artifactId>
|
||||
<version>${wiremock.version}</version>
|
||||
<scope>test</scope>
|
||||
<!-- Include everything below here if you have dependency conflicts -->
|
||||
<classifier>standalone</classifier>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.mortbay.jetty</groupId>
|
||||
<artifactId>jetty</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.skyscreamer</groupId>
|
||||
<artifactId>jsonassert</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>xmlunit</groupId>
|
||||
<artifactId>xmlunit</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.jayway.jsonpath</groupId>
|
||||
<artifactId>json-path</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>net.sf.jopt-simple</groupId>
|
||||
<artifactId>jopt-simple</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.spotify</groupId>
|
||||
<artifactId>docker-maven-plugin</artifactId>
|
||||
<version>0.2.3</version>
|
||||
<configuration>
|
||||
<imageName>${docker.image.prefix}/${docker.image.name}</imageName>
|
||||
<dockerDirectory>src/main/docker</dockerDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<targetPath>/</targetPath>
|
||||
<directory>${project.build.directory}</directory>
|
||||
<include>${project.build.finalName}.jar</include>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,5 +0,0 @@
|
||||
FROM anapsix/alpine-java:8
|
||||
VOLUME /tmp
|
||||
ADD *.jar module-launcher.jar
|
||||
RUN bash -c 'touch /module-launcher.jar'
|
||||
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/module-launcher.jar"]
|
||||
@@ -1,39 +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.module.launcher;
|
||||
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.boot.loader.util.AsciiBytes;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
class ArchiveMatchingEntryFilter implements Archive.EntryFilter {
|
||||
|
||||
public static final ArchiveMatchingEntryFilter FILTER = new ArchiveMatchingEntryFilter();
|
||||
|
||||
private static final AsciiBytes LIB = new AsciiBytes("lib/");
|
||||
|
||||
@Override
|
||||
public boolean matches(Archive.Entry entry) {
|
||||
return isNestedArchive(entry);
|
||||
}
|
||||
|
||||
protected boolean isNestedArchive(Archive.Entry entry) {
|
||||
return !entry.isDirectory() && entry.getName().startsWith(LIB);
|
||||
}
|
||||
}
|
||||
@@ -1,71 +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.module.launcher;
|
||||
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
|
||||
import org.springframework.boot.loader.JarLauncher;
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.cloud.stream.module.utils.ClassloaderUtils;
|
||||
import org.springframework.util.ClassUtils;
|
||||
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.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class ModuleJarLauncher extends JarLauncher {
|
||||
|
||||
public ModuleJarLauncher(Archive archive) {
|
||||
super(archive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(String[] args) {
|
||||
super.launch(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
|
||||
throws Exception {
|
||||
if (ClassUtils.isPresent(
|
||||
"org.apache.catalina.webresources.TomcatURLStreamHandlerFactory",
|
||||
classLoader)) {
|
||||
// Ensure the method is invoked on a class that is loaded by the provided
|
||||
// class loader (not the current context class loader):
|
||||
Method method = ReflectionUtils
|
||||
.findMethod(classLoader.loadClass("org.apache.catalina.webresources.TomcatURLStreamHandlerFactory"),
|
||||
"disable");
|
||||
ReflectionUtils.invokeMethod(method, null);
|
||||
}
|
||||
super.launch(args, mainClass, classLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
|
||||
return ClassloaderUtils.createModuleClassloader(urls);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +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.module.launcher;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Encapsulates a reference to a module (as maven coordinates) and a set of "arguments" that must be passed to it.
|
||||
* Those arguments will be passed to the module by the launcher.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class ModuleLaunchRequest {
|
||||
|
||||
private final String module;
|
||||
|
||||
private final Map<String, String> arguments;
|
||||
|
||||
public ModuleLaunchRequest(String module, Map<String, String> arguments) {
|
||||
this.module = module;
|
||||
this.arguments = arguments != null ? new HashMap<>(arguments) : new HashMap<String, String>();
|
||||
}
|
||||
|
||||
public String getModule() {
|
||||
return module;
|
||||
}
|
||||
|
||||
public Map<String, String> getArguments() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
public void addArgument(String name, String value) {
|
||||
this.arguments.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s with arguments %s", module, arguments);
|
||||
}
|
||||
}
|
||||
@@ -1,321 +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.module.launcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
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.archive.Archive;
|
||||
import org.springframework.boot.loader.archive.JarFileArchive;
|
||||
import org.springframework.cloud.stream.module.resolver.Coordinates;
|
||||
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;
|
||||
|
||||
/**
|
||||
* A component that launches one or more modules, delegating their resolution to an
|
||||
* underlying {@link ModuleResolver}.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Marius Bogoevici
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
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 static Log log = LogFactory.getLog(ModuleLauncher.class);
|
||||
|
||||
private static final String DEFAULT_EXTENSION = "jar";
|
||||
|
||||
private static final String DEFAULT_CLASSIFIER = "";
|
||||
|
||||
private static final Pattern COORDINATES_PATTERN =
|
||||
Pattern.compile("([^: ]+):([^: ]+)(:([^: ]*)(:([^: ]+))?)?:([^: ]+)");
|
||||
|
||||
private static final String INCLUDE_DEPENDENCIES_ARG = "includes";
|
||||
|
||||
private static final String EXCLUDE_DEPENDENCIES_ARG = "excludes";
|
||||
|
||||
private final ModuleResolver moduleResolver;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Modules must be passed in "natural" left to right order.
|
||||
*
|
||||
* The format of each module must conform to the <a href="http://www.eclipse.org/aether">Aether</a> convention:
|
||||
* <code><groupId>:<artifactId>[:<extension>[:<classifier>]]:<version></code>
|
||||
*
|
||||
* @param moduleLaunchRequests a list of modules with their arguments
|
||||
* @param aggregate whether the modules should be aggregated at launch
|
||||
* @param aggregateArgs a list of arguments for the whole aggregate
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void launch(List<ModuleLaunchRequest> moduleLaunchRequests, boolean aggregate, Map<String,String> aggregateArgs) {
|
||||
if (moduleLaunchRequests.size() == 1 || !aggregate) {
|
||||
launchIndividualModules(moduleLaunchRequests);
|
||||
}
|
||||
else {
|
||||
launchAggregatedModules(moduleLaunchRequests, aggregateArgs != null ? aggregateArgs : Collections.EMPTY_MAP);
|
||||
}
|
||||
}
|
||||
|
||||
public void launch(List<ModuleLaunchRequest> moduleLaunchRequests, boolean aggregate) {
|
||||
this.launch(moduleLaunchRequests, aggregate, Collections.<String,String>emptyMap());
|
||||
}
|
||||
|
||||
public void launch(List<ModuleLaunchRequest> moduleLaunchRequests) {
|
||||
this.launch(moduleLaunchRequests, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a set of semantic program arguments to "command line program arguments" that is, to the
|
||||
* {@literal --foo=bar} form.
|
||||
*/
|
||||
private String[] toArgArray(Map<String, String> args) {
|
||||
if (args != null) {
|
||||
String[] result = new String[args.size()];
|
||||
int i = 0;
|
||||
for (Map.Entry<String, String> kv : args.entrySet()) {
|
||||
result[i++] = String.format("--%s=%s", kv.getKey(), kv.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return new String[0];
|
||||
}
|
||||
}
|
||||
|
||||
public void launchAggregatedModules(List<ModuleLaunchRequest> moduleLaunchRequests, Map<String,String> aggregateArgs) {
|
||||
try {
|
||||
List<String> mainClassNames = new ArrayList<>();
|
||||
LinkedHashSet<URL> jarURLs = new LinkedHashSet<>();
|
||||
List<String> seenArchives = new ArrayList<>();
|
||||
final List<String[]> arguments = new ArrayList<>();
|
||||
final ClassLoader classLoader;
|
||||
if (!(aggregateArgs.containsKey(EXCLUDE_DEPENDENCIES_ARG) ||
|
||||
aggregateArgs.containsKey(INCLUDE_DEPENDENCIES_ARG))) {
|
||||
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
|
||||
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()));
|
||||
}
|
||||
classLoader = ClassloaderUtils.createModuleClassloader(jarURLs.toArray(new URL[jarURLs.size()]));
|
||||
} else {
|
||||
// First, resolve modules and extract main classes - while slightly less efficient than just
|
||||
// doing the same processing after resolution, this ensures that module artifacts are processed
|
||||
// correctly for extracting their main class names. It is not possible in the general case to
|
||||
// identify, after resolution, whether a resource represents a module artifact which was part of the
|
||||
// original request or not. We will include the first module as root and the next as direct dependencies
|
||||
Coordinates root = null;
|
||||
ArrayList<Coordinates> includeCoordinates = new ArrayList<>();
|
||||
for (ModuleLaunchRequest moduleLaunchRequest : moduleLaunchRequests) {
|
||||
Coordinates moduleCoordinates
|
||||
= toCoordinates(moduleLaunchRequest.getModule());
|
||||
if (root == null) {
|
||||
root = moduleCoordinates;
|
||||
}
|
||||
else {
|
||||
includeCoordinates.add(toCoordinates(moduleLaunchRequest.getModule()));
|
||||
}
|
||||
Resource moduleResource = resolveModule(moduleLaunchRequest.getModule());
|
||||
JarFileArchive moduleArchive = new JarFileArchive(moduleResource.getFile());
|
||||
mainClassNames.add(moduleArchive.getMainClass());
|
||||
arguments.add(toArgArray(moduleLaunchRequest.getArguments()));
|
||||
}
|
||||
for (String include :
|
||||
StringUtils.commaDelimitedListToStringArray(aggregateArgs.get(INCLUDE_DEPENDENCIES_ARG))) {
|
||||
includeCoordinates.add(toCoordinates(include));
|
||||
}
|
||||
// Resolve all artifacts - since modules have been specified as direct dependencies, they will take
|
||||
// precedence in the resolution order, ensuring that the already resolved artifacts will be returned as
|
||||
// part of the response.
|
||||
Resource[] libraries = moduleResolver.resolve(root,
|
||||
includeCoordinates.toArray(new Coordinates[includeCoordinates.size()]),
|
||||
StringUtils.commaDelimitedListToStringArray(aggregateArgs.get(EXCLUDE_DEPENDENCIES_ARG)));
|
||||
for (Resource library : libraries) {
|
||||
jarURLs.add(library.getURL());
|
||||
}
|
||||
classLoader = new URLClassLoader(jarURLs.toArray(new URL[jarURLs.size()]));
|
||||
}
|
||||
|
||||
final List<Class<?>> mainClasses = new ArrayList<>();
|
||||
for (String mainClass : mainClassNames) {
|
||||
mainClasses.add(ClassUtils.forName(mainClass, classLoader));
|
||||
}
|
||||
Runnable moduleAggregatorRunner = new ModuleAggregatorRunner(classLoader, mainClasses,
|
||||
toArgArray(aggregateArgs), 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<ModuleLaunchRequest> moduleLaunchRequests) {
|
||||
List<ModuleLaunchRequest> reversed = new ArrayList<>(moduleLaunchRequests);
|
||||
Collections.reverse(reversed);
|
||||
for (ModuleLaunchRequest moduleLaunchRequest : reversed) {
|
||||
String module = moduleLaunchRequest.getModule();
|
||||
Map<String, String> arguments = moduleLaunchRequest.getArguments();
|
||||
if (arguments.containsKey(INCLUDE_DEPENDENCIES_ARG) || arguments.containsKey(EXCLUDE_DEPENDENCIES_ARG)) {
|
||||
String includes = arguments.get(INCLUDE_DEPENDENCIES_ARG);
|
||||
String excludes = arguments.get(EXCLUDE_DEPENDENCIES_ARG);
|
||||
launchModuleWithDependencies(module, toArgArray(arguments),
|
||||
StringUtils.commaDelimitedListToStringArray(includes),
|
||||
StringUtils.commaDelimitedListToStringArray(excludes));
|
||||
} else {
|
||||
launchModule(module, toArgArray(arguments));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void launchModule(String module, String[] args) {
|
||||
try {
|
||||
Resource resource = resolveModule(module);
|
||||
JarFileArchive jarFileArchive = new JarFileArchive(resource.getFile());
|
||||
ModuleJarLauncher jarLauncher = new ModuleJarLauncher(jarFileArchive);
|
||||
jarLauncher.launch(args);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException("failed to launch module: " + module, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void launchModuleWithDependencies(String module, String[] args, String[] includes, String[] excludes) {
|
||||
try {
|
||||
Resource[] libraries = this.moduleResolver.resolve(toCoordinates(module),
|
||||
toCoordinateArray(includes), excludes);
|
||||
List<Archive> archives = new ArrayList<>();
|
||||
for (Resource library : libraries) {
|
||||
archives.add(new JarFileArchive(library.getFile()));
|
||||
}
|
||||
MultiArchiveLauncher jarLauncher = new MultiArchiveLauncher(archives);
|
||||
jarLauncher.launch(args);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException("failed to launch module: " + module, e);
|
||||
}
|
||||
}
|
||||
|
||||
private Resource resolveModule(String moduleCoordinates) {
|
||||
Coordinates coordinates = toCoordinates(moduleCoordinates);
|
||||
return this.moduleResolver.resolve(coordinates);
|
||||
}
|
||||
|
||||
private Coordinates toCoordinates(String coordinates) {
|
||||
Matcher matcher = COORDINATES_PATTERN.matcher(coordinates);
|
||||
Assert.isTrue(matcher.matches(), "Bad artifact coordinates " + coordinates
|
||||
+ ", expected format is <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>");
|
||||
String groupId = matcher.group(1);
|
||||
String artifactId = matcher.group(2);
|
||||
String extension = StringUtils.hasLength(matcher.group(4)) ? matcher.group(4) : DEFAULT_EXTENSION;
|
||||
String classifier = StringUtils.hasLength(matcher.group(6)) ? matcher.group(6) : DEFAULT_CLASSIFIER;
|
||||
String version = matcher.group(7);
|
||||
return new Coordinates(groupId, artifactId, extension, classifier, version);
|
||||
}
|
||||
|
||||
private Coordinates[] toCoordinateArray(String[] coordinateList) {
|
||||
List<Coordinates> result = new ArrayList<>();
|
||||
for (String coordinates : coordinateList) {
|
||||
result.add(toCoordinates(coordinates));
|
||||
}
|
||||
return result.toArray(new Coordinates[result.size()]);
|
||||
}
|
||||
|
||||
private class ModuleAggregatorRunner implements Runnable {
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
private final String[] parentArgs;
|
||||
|
||||
private final List<Class<?>> mainClasses;
|
||||
|
||||
private final List<String[]> arguments;
|
||||
|
||||
public ModuleAggregatorRunner(ClassLoader classLoader, List<Class<?>> mainClasses, String[] parentArgs,
|
||||
List<String[]> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 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.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 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 ModuleLauncherProperties for module and argument structure and
|
||||
* format
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class ModuleLauncherApplication {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
List<String> argsList = new ArrayList<>(Arrays.asList(args));
|
||||
if (!argsList.contains("spring.jmx.default-domain")) {
|
||||
argsList.add("--spring.jmx.default-domain=module-launcher");
|
||||
}
|
||||
SpringApplication.run(ModuleLauncherApplication.class, argsList.toArray(new String[0]));
|
||||
}
|
||||
}
|
||||
@@ -1,41 +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.module.launcher;
|
||||
|
||||
import org.springframework.cloud.stream.module.resolver.ModuleResolver;
|
||||
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.
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@Configuration
|
||||
@Import(ModuleResolverConfiguration.class)
|
||||
public class ModuleLauncherConfiguration {
|
||||
|
||||
@Bean
|
||||
public ModuleLauncher moduleLauncher(ModuleResolver moduleResolver) {
|
||||
return new ModuleLauncher(moduleResolver);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,102 +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.module.launcher;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* Configuration properties for {@link ModuleLauncher}.
|
||||
*
|
||||
* <p>Expects the following keys (resolved from the {@link Environment}, so this could take many forms _ System
|
||||
* properties, environment variables, program arguments, <i>etc.</i> _):<ul>
|
||||
* <li>{@literal modules = <list>}: an ordered list of maven coordinates of modules to launch</li>
|
||||
* <li>{@literal args[<index>][<key>] = <value>}: key/value pairs that will become module arguments,
|
||||
* where {@literal <index>} is the 0-based index of the module in the list above and '*' can be used for passing
|
||||
* arguments to all modules</li>
|
||||
* <li>{@literal aggregate = true | false}</li>: whether multiple modules launched together should be aggregated,
|
||||
* case in which they will be launched as a single individual unit, and {@literal args['aggregate'][<key>] = <value>}
|
||||
* can be used for passing arguments to the aggregate;
|
||||
* </ul>
|
||||
*
|
||||
* As an example, this is how one would launch the {@literal time --fixedDelay=4 | log} canonical example:
|
||||
* <pre>
|
||||
* modules = org.springframework.cloud.modules:time-source:1.0.0-SNAPSHOT,org.springframework.cloud.modules:log-sink:1.0.0-SNAPSHOT
|
||||
* args.0.fixedDelay=4
|
||||
* </pre>
|
||||
*
|
||||
* And this is how one would launch the {@literal time --fixedDelay=4 | log} example as an aggregate:
|
||||
* <pre>
|
||||
* modules = org.springframework.cloud.modules:time-source:1.0.0-SNAPSHOT,org.springframework.cloud.modules:log-sink:1.0.0-SNAPSHOT
|
||||
* args.0.fixedDelay=4
|
||||
* aggregate=true
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Marius Bogoevici
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@ConfigurationProperties
|
||||
public class ModuleLauncherProperties {
|
||||
|
||||
/**
|
||||
* 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 {@link #modules array}, allowing for an additional generic
|
||||
* key for global arguments.
|
||||
*/
|
||||
private Map<String, Map<String, String>> args = new HashMap<>();
|
||||
|
||||
public boolean isAggregate() {
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
public void setAggregate(boolean aggregate) {
|
||||
this.aggregate = aggregate;
|
||||
}
|
||||
|
||||
public void setModules(String[] modules) {
|
||||
this.modules = modules;
|
||||
}
|
||||
|
||||
@NotEmpty(message = "A list of modules must be specified.")
|
||||
public String[] getModules() {
|
||||
return modules;
|
||||
}
|
||||
|
||||
public void setArgs(Map<String, Map<String, String>> args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public Map<String, Map<String, String>> getArgs() {
|
||||
return args;
|
||||
}
|
||||
}
|
||||
@@ -1,98 +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.module.launcher;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Spring boot {@link ApplicationRunner} that triggers {@link ModuleLauncher} to launch the modules.
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@Component
|
||||
@EnableConfigurationProperties(ModuleLauncherProperties.class)
|
||||
public class ModuleLauncherRunner implements CommandLineRunner {
|
||||
|
||||
private final static Log log = LogFactory.getLog(ModuleLauncherRunner.class);
|
||||
|
||||
private final static String GLOBAL_ARGS_KEY = "*";
|
||||
|
||||
private final static String AGGREGATE_ARGS_KEY = "aggregate";
|
||||
|
||||
@Autowired
|
||||
private ModuleLauncherProperties moduleLauncherProperties;
|
||||
|
||||
@Autowired
|
||||
private ModuleLauncher moduleLauncher;
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
List<ModuleLaunchRequest> launchRequests = generateModuleLaunchRequests();
|
||||
if (log.isInfoEnabled()) {
|
||||
StringBuilder sb = new StringBuilder("Launching\n");
|
||||
for (ModuleLaunchRequest moduleLaunchRequest : launchRequests) {
|
||||
sb.append('\t').append(moduleLaunchRequest).append('\n');
|
||||
}
|
||||
log.info(sb.toString());
|
||||
}
|
||||
this.moduleLauncher.launch(launchRequests,
|
||||
moduleLauncherProperties.isAggregate(),
|
||||
moduleLauncherProperties.isAggregate() ?
|
||||
moduleLauncherProperties.getArgs().get(AGGREGATE_ARGS_KEY) :
|
||||
Collections.<String, String>emptyMap());
|
||||
}
|
||||
|
||||
private List<ModuleLaunchRequest> generateModuleLaunchRequests() {
|
||||
List<ModuleLaunchRequest> requests = new ArrayList<>();
|
||||
String[] modules = this.moduleLauncherProperties.getModules();
|
||||
Map<String, Map<String, String>> arguments = this.moduleLauncherProperties.getArgs();
|
||||
for (int i = 0; i < modules.length; i++) {
|
||||
// merge the global arguments with the module specific arguments
|
||||
Map<String, String> moduleArguments = new HashMap<>();
|
||||
if (arguments != null) {
|
||||
// by inserting the global args first and the module specific args next
|
||||
// we ensure that the latter have precedence
|
||||
Map<String, String> globalArgs = arguments.get(GLOBAL_ARGS_KEY);
|
||||
if (globalArgs != null) {
|
||||
moduleArguments.putAll(globalArgs);
|
||||
}
|
||||
Map<String, String> moduleSpecificArgs = arguments.get(Integer.toString(i));
|
||||
if (moduleSpecificArgs != null) {
|
||||
moduleArguments.putAll(moduleSpecificArgs);
|
||||
}
|
||||
}
|
||||
ModuleLaunchRequest moduleLaunchRequest = new ModuleLaunchRequest(modules[i], moduleArguments);
|
||||
requests.add(moduleLaunchRequest);
|
||||
}
|
||||
return requests;
|
||||
}
|
||||
}
|
||||
@@ -1,99 +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.module.launcher;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.loader.Launcher;
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.cloud.stream.module.utils.ClassloaderUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* A {@link Launcher} for multiple independent JAR archives (which aren't nested in an uber jar). This class
|
||||
* supports adding includes and excludes to a module's classpath.
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class MultiArchiveLauncher extends Launcher {
|
||||
|
||||
private static Log log = LogFactory.getLog(MultiArchiveLauncher.class);
|
||||
|
||||
private final List<Archive> archives;
|
||||
|
||||
/**
|
||||
* A list of archives, the first of which is expected to be a Spring boot uberJar
|
||||
*
|
||||
* @param archives
|
||||
*/
|
||||
public MultiArchiveLauncher(List<Archive> archives) {
|
||||
Assert.notEmpty(archives, "A list of archives must be provided");
|
||||
this.archives = archives;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMainClass() throws Exception {
|
||||
return archives.get(0).getManifest().getMainAttributes().getValue("Start-Class");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Archive> getClassPathArchives() throws Exception {
|
||||
return archives;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(String[] args) {
|
||||
super.launch(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
|
||||
throws Exception {
|
||||
if (log.isDebugEnabled()) {
|
||||
for (Archive archive : archives) {
|
||||
log.debug("Launching with: " + archive.getUrl().toString());
|
||||
}
|
||||
}
|
||||
if (ClassUtils.isPresent(
|
||||
"org.apache.catalina.webresources.TomcatURLStreamHandlerFactory",
|
||||
classLoader)) {
|
||||
// Ensure the method is invoked on a class that is loaded by the provided
|
||||
// class loader (not the current context class loader):
|
||||
Method method = ReflectionUtils
|
||||
.findMethod(
|
||||
classLoader
|
||||
.loadClass("org.apache.catalina.webresources.TomcatURLStreamHandlerFactory"),
|
||||
"disable");
|
||||
ReflectionUtils.invokeMethod(method, null);
|
||||
}
|
||||
super.launch(args, mainClass, classLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
|
||||
return ClassloaderUtils.createModuleClassloader(urls);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 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.io.File;
|
||||
import java.util.ArrayList;
|
||||
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;
|
||||
import org.eclipse.aether.RepositorySystemSession;
|
||||
import org.eclipse.aether.artifact.Artifact;
|
||||
import org.eclipse.aether.artifact.DefaultArtifact;
|
||||
import org.eclipse.aether.collection.CollectRequest;
|
||||
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
|
||||
import org.eclipse.aether.graph.Dependency;
|
||||
import org.eclipse.aether.impl.DefaultServiceLocator;
|
||||
import org.eclipse.aether.repository.Authentication;
|
||||
import org.eclipse.aether.repository.AuthenticationContext;
|
||||
import org.eclipse.aether.repository.AuthenticationDigest;
|
||||
import org.eclipse.aether.repository.LocalRepository;
|
||||
import org.eclipse.aether.repository.Proxy;
|
||||
import org.eclipse.aether.repository.RemoteRepository;
|
||||
import org.eclipse.aether.resolution.ArtifactRequest;
|
||||
import org.eclipse.aether.resolution.ArtifactResolutionException;
|
||||
import org.eclipse.aether.resolution.ArtifactResult;
|
||||
import org.eclipse.aether.resolution.DependencyRequest;
|
||||
import org.eclipse.aether.resolution.DependencyResolutionException;
|
||||
import org.eclipse.aether.resolution.DependencyResult;
|
||||
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
|
||||
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
|
||||
import org.eclipse.aether.transport.file.FileTransporterFactory;
|
||||
import org.eclipse.aether.transport.http.HttpTransporterFactory;
|
||||
import org.eclipse.aether.util.artifact.JavaScopes;
|
||||
import org.eclipse.aether.util.repository.DefaultProxySelector;
|
||||
|
||||
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.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An implementation of ModuleResolver using <a href="http://www.eclipse.org/aether/>aether</a> to resolve the module
|
||||
* artifact (uber jar) in a local Maven repository, downloading the latest update from a remote repository if
|
||||
* necessary.
|
||||
*
|
||||
* @author David Turanski
|
||||
* @author Mark Fisher
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
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;
|
||||
|
||||
private final List<RemoteRepository> remoteRepositories;
|
||||
|
||||
private final RepositorySystem repositorySystem;
|
||||
|
||||
private volatile boolean offline = false;
|
||||
|
||||
private final AetherProxyProperties proxyProperties;
|
||||
|
||||
private Authentication authentication;
|
||||
|
||||
/**
|
||||
* Create an instance specifying the locations of the local and remote repositories.
|
||||
* @param localRepository the root path of the local maven repository
|
||||
* @param remoteRepositories a Map containing pairs of (repository ID,repository URL). This
|
||||
* may be null or empty if the local repository is off line.
|
||||
* @param proxyProperties the proxy properties for the maven proxy settings.
|
||||
*/
|
||||
public AetherModuleResolver(File localRepository, Map<String, String> remoteRepositories,
|
||||
final AetherProxyProperties proxyProperties) {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
this.proxyProperties = proxyProperties;
|
||||
if (isProxyEnabled() && proxyHasCredentials()) {
|
||||
this.authentication = new Authentication() {
|
||||
@Override
|
||||
public void fill(AuthenticationContext context, String key, Map<String, String> data) {
|
||||
context.put(context.USERNAME, proxyProperties.getAuth().getUsername());
|
||||
context.put(context.PASSWORD, proxyProperties.getAuth().getPassword());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void digest(AuthenticationDigest digest) {
|
||||
digest.update(AuthenticationContext.USERNAME, proxyProperties.getAuth().getUsername(),
|
||||
AuthenticationContext.PASSWORD, proxyProperties.getAuth().getPassword());
|
||||
}
|
||||
};
|
||||
}
|
||||
if (!localRepository.exists()) {
|
||||
Assert.isTrue(localRepository.mkdirs(),
|
||||
"Unable to create directory for local repository: " + localRepository);
|
||||
}
|
||||
this.localRepository = localRepository;
|
||||
this.remoteRepositories = new LinkedList<>();
|
||||
if (!CollectionUtils.isEmpty(remoteRepositories)) {
|
||||
for (Map.Entry<String, String> remoteRepo : remoteRepositories.entrySet()) {
|
||||
RemoteRepository.Builder remoteRepositoryBuilder = new RemoteRepository.Builder(remoteRepo.getKey(),
|
||||
DEFAULT_CONTENT_TYPE, remoteRepo.getValue());
|
||||
if (this.authentication != null) {
|
||||
//todo: Set direct authentication for the remote repositories
|
||||
remoteRepositoryBuilder.setProxy(new Proxy(proxyProperties.getProtocol(), proxyProperties.getHost(),
|
||||
proxyProperties.getPort(), authentication));
|
||||
}
|
||||
this.remoteRepositories.add(remoteRepositoryBuilder.build());
|
||||
}
|
||||
}
|
||||
repositorySystem = newRepositorySystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the proxy settings are provided.
|
||||
*
|
||||
* @return boolean true if the proxy settings are provided.
|
||||
*/
|
||||
private boolean isProxyEnabled() {
|
||||
return (this.proxyProperties != null && this.proxyProperties.getHost() != null && proxyProperties.getPort() > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the proxy setting has username/password set.
|
||||
*
|
||||
* @return boolean true if both the username/password are set
|
||||
*/
|
||||
private boolean proxyHasCredentials() {
|
||||
return (this.proxyProperties != null && this.proxyProperties.getAuth() != null &&
|
||||
this.proxyProperties.getAuth().getUsername() != null && this.proxyProperties.getAuth().getPassword() != null);
|
||||
}
|
||||
|
||||
public void setOffline(boolean offline) {
|
||||
this.offline = offline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an artifact and return its location in the local repository. Aether performs the normal
|
||||
* Maven resolution process ensuring that the latest update is cached to the local repository.
|
||||
* @param coordinates the Maven coordinates of the artifact
|
||||
* @return a {@link FileSystemResource} representing the resolved artifact in the local repository
|
||||
* @throws RuntimeException if the artifact does not exist or the resolution fails
|
||||
*/
|
||||
@Override
|
||||
public Resource resolve(Coordinates coordinates) {
|
||||
return this.resolve(coordinates, null, null)[0];
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a session to manage remote and local synchronization.
|
||||
*/
|
||||
private DefaultRepositorySystemSession newRepositorySystemSession(RepositorySystem system, String localRepoPath) {
|
||||
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
|
||||
LocalRepository localRepo = new LocalRepository(localRepoPath);
|
||||
session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo));
|
||||
session.setOffline(this.offline);
|
||||
if (isProxyEnabled()) {
|
||||
DefaultProxySelector proxySelector = new DefaultProxySelector();
|
||||
Proxy proxy = new Proxy(proxyProperties.getProtocol(), proxyProperties.getHost(), proxyProperties.getPort(),
|
||||
authentication);
|
||||
proxySelector.add(proxy, proxyProperties.getNonProxyHosts());
|
||||
session.setProxySelector(proxySelector);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
/*
|
||||
* Aether's components implement {@link org.eclipse.aether.spi.locator.Service} to ease manual wiring.
|
||||
* Using the prepopulated {@link DefaultServiceLocator}, we need to register the repository connector
|
||||
* and transporter factories
|
||||
*/
|
||||
private RepositorySystem newRepositorySystem() {
|
||||
DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
|
||||
locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
|
||||
locator.addService(TransporterFactory.class, FileTransporterFactory.class);
|
||||
locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
|
||||
locator.setErrorHandler(new DefaultServiceLocator.ErrorHandler() {
|
||||
@Override
|
||||
public void serviceCreationFailed(Class<?> type, Class<?> impl, Throwable exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
});
|
||||
return locator.getService(RepositorySystem.class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolve a set of artifacts based on their coordinates, including their dependencies, and return the locations of
|
||||
* the transitive set in the local repository. Aether performs the normal Maven resolution process ensuring that the
|
||||
* latest update is cached to the local repository. A number of additional includes and excludes can be specified,
|
||||
* allowing to override the transitive dependencies of the original set. Includes and their transitive dependencies
|
||||
* will always
|
||||
*
|
||||
* @param root the Maven coordinates of the artifacts
|
||||
* @return a {@link FileSystemResource} representing the resolved artifact in the local repository
|
||||
* @throws RuntimeException if the artifact does not exist or the resolution fails
|
||||
*/
|
||||
@Override
|
||||
public Resource[] resolve(Coordinates root, Coordinates[] includes, String[] excludePatterns) {
|
||||
Assert.notNull(root, "Root cannot be null");
|
||||
validateCoordinates(root);
|
||||
if (!ObjectUtils.isEmpty(includes)) {
|
||||
for (Coordinates include : includes) {
|
||||
Assert.notNull(include, "Includes cannot be null");
|
||||
validateCoordinates(include);
|
||||
}
|
||||
}
|
||||
List<Resource> result = new ArrayList<>();
|
||||
Artifact rootArtifact = toArtifact(root);
|
||||
RepositorySystemSession session = newRepositorySystemSession(repositorySystem,
|
||||
localRepository.getAbsolutePath());
|
||||
if (ObjectUtils.isEmpty(includes) && ObjectUtils.isEmpty(excludePatterns)) {
|
||||
ArtifactResult resolvedArtifact;
|
||||
try {
|
||||
resolvedArtifact = repositorySystem.resolveArtifact(session,
|
||||
new ArtifactRequest(rootArtifact, remoteRepositories, JavaScopes.RUNTIME));
|
||||
}
|
||||
catch (ArtifactResolutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
result.add(toResource(resolvedArtifact));
|
||||
}
|
||||
else {
|
||||
try {
|
||||
CollectRequest collectRequest = new CollectRequest();
|
||||
collectRequest.setRepositories(remoteRepositories);
|
||||
collectRequest.setRoot(new Dependency(rootArtifact, JavaScopes.RUNTIME));
|
||||
Artifact[] includeArtifacts = new Artifact[!ObjectUtils.isEmpty(includes) ? includes.length : 0];
|
||||
int i = 0;
|
||||
for (Coordinates include : includes) {
|
||||
Artifact includedArtifact = toArtifact(include);
|
||||
collectRequest.addDependency(new Dependency(includedArtifact, JavaScopes.RUNTIME));
|
||||
includeArtifacts[i++] = includedArtifact;
|
||||
}
|
||||
DependencyResult dependencyResult =
|
||||
repositorySystem.resolveDependencies(session,
|
||||
new DependencyRequest(collectRequest,
|
||||
new ModuleDependencyFilter(includeArtifacts, excludePatterns)));
|
||||
for (ArtifactResult artifactResult : dependencyResult.getArtifactResults()) {
|
||||
// we are only interested in the jars
|
||||
if ("jar".equalsIgnoreCase(artifactResult.getArtifact().getExtension())) {
|
||||
result.add(toResource(artifactResult));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (DependencyResolutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return result.toArray(new Resource[result.size()]);
|
||||
}
|
||||
|
||||
private void validateCoordinates(Coordinates coordinates) {
|
||||
Assert.hasText(coordinates.getGroupId(), "'groupId' cannot be blank.");
|
||||
Assert.hasText(coordinates.getArtifactId(), "'artifactId' cannot be blank.");
|
||||
Assert.hasText(coordinates.getExtension(), "'extension' cannot be blank.");
|
||||
Assert.hasText(coordinates.getVersion(), "'version' cannot be blank.");
|
||||
}
|
||||
|
||||
public FileSystemResource toResource(ArtifactResult resolvedArtifact) {
|
||||
return new FileSystemResource(resolvedArtifact.getArtifact().getFile());
|
||||
}
|
||||
|
||||
private Artifact toArtifact(Coordinates root) {
|
||||
return new DefaultArtifact(root.getGroupId(),
|
||||
root.getArtifactId(),
|
||||
root.getClassifier() != null ? root.getClassifier() : "",
|
||||
root.getExtension(),
|
||||
root.getVersion());
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 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 org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Proxy properties for the Aether Module Resolver.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "aether.proxy")
|
||||
public class AetherProxyProperties {
|
||||
/**
|
||||
* Protocol to use for proxy settings.
|
||||
*/
|
||||
private String protocol = "http";
|
||||
|
||||
/**
|
||||
* Host for the proxy.
|
||||
*/
|
||||
private String host;
|
||||
|
||||
/**
|
||||
* Port for the proxy.
|
||||
*/
|
||||
private int port;
|
||||
|
||||
/**
|
||||
* List of non proxy hosts.
|
||||
*/
|
||||
private String nonProxyHosts;
|
||||
|
||||
private Authentication auth;
|
||||
|
||||
public String getProtocol() {
|
||||
return this.protocol;
|
||||
}
|
||||
|
||||
public void setProtocol(String protocol) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return this.host;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getNonProxyHosts() {
|
||||
return this.nonProxyHosts;
|
||||
}
|
||||
|
||||
public void setNonProxyHosts(String nonProxyHosts) {
|
||||
this.nonProxyHosts = nonProxyHosts;
|
||||
}
|
||||
|
||||
public Authentication getAuth() {
|
||||
return this.auth;
|
||||
}
|
||||
|
||||
public void setAuth(Authentication auth) {
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
public static class Authentication {
|
||||
|
||||
/**
|
||||
* Username for the proxy.
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* Password for the proxy.
|
||||
*/
|
||||
private String password;
|
||||
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,71 +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.module.resolver;
|
||||
|
||||
/**
|
||||
* Encapsulates Maven coordinates.
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class Coordinates {
|
||||
|
||||
private final String groupId;
|
||||
|
||||
private final String artifactId;
|
||||
|
||||
private final String extension;
|
||||
|
||||
private String classifier;
|
||||
|
||||
private final String version;
|
||||
|
||||
/**
|
||||
* @param groupId the groupId
|
||||
* @param artifactId the artifactId
|
||||
* @param extension the file extension
|
||||
* @param classifier classifier
|
||||
* @param version the version
|
||||
*/
|
||||
public Coordinates(String groupId, String artifactId, String extension, String classifier, String version) {
|
||||
this.groupId = groupId;
|
||||
this.artifactId = artifactId;
|
||||
this.extension = extension;
|
||||
this.classifier = classifier;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public String getArtifactId() {
|
||||
return artifactId;
|
||||
}
|
||||
|
||||
public String getExtension() {
|
||||
return extension;
|
||||
}
|
||||
|
||||
public String getClassifier() {
|
||||
return classifier;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -1,78 +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.module.resolver;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.aether.artifact.Artifact;
|
||||
import org.eclipse.aether.graph.DependencyFilter;
|
||||
import org.eclipse.aether.graph.DependencyNode;
|
||||
import org.eclipse.aether.util.filter.PatternExclusionsDependencyFilter;
|
||||
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* A {@link DependencyFilter} that uses a list of explicit includes and pattern-based excludes. Any items that match
|
||||
* the exclusion pattern will be rejected, unless they are accepted explicitly
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class ModuleDependencyFilter implements DependencyFilter {
|
||||
|
||||
private final PatternExclusionsDependencyFilter patternExclusionsDependencyFilter;
|
||||
|
||||
private final Artifact[] includes;
|
||||
|
||||
private boolean acceptOptional = false;
|
||||
|
||||
public ModuleDependencyFilter(Artifact[] includes, String... excludes) {
|
||||
this.patternExclusionsDependencyFilter = new PatternExclusionsDependencyFilter(excludes);
|
||||
this.includes = includes != null ? includes : new Artifact[0];
|
||||
}
|
||||
|
||||
public void setAcceptOptional(boolean acceptOptional) {
|
||||
this.acceptOptional = acceptOptional;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(DependencyNode node, List<DependencyNode> parents) {
|
||||
// optional nodes are rejected conditionally
|
||||
// nodes included explicitly are always accepted
|
||||
return (acceptOptional || !node.getDependency().isOptional())
|
||||
&& (isIncludedDirectly(node) || patternExclusionsDependencyFilter.accept(node, parents));
|
||||
}
|
||||
|
||||
private boolean isIncludedDirectly(DependencyNode node) {
|
||||
if (node.getArtifact() != null) {
|
||||
for (Artifact include : includes) {
|
||||
Artifact nodeArtifact = node.getArtifact();
|
||||
// we check if this was a specifically included artifact by checking its group, artifactId, extension and
|
||||
// classifier. The version is left out in the case when resolution produces a different artifact version
|
||||
// (there cannot be two artifacts with the same group, artifactId, extension and classifier but different
|
||||
// version in the resolved group)
|
||||
if (ObjectUtils.nullSafeEquals(include.getGroupId(), nodeArtifact.getGroupId())
|
||||
&& ObjectUtils.nullSafeEquals(include.getArtifactId(), nodeArtifact.getArtifactId())
|
||||
&& ObjectUtils.nullSafeEquals(include.getClassifier(), nodeArtifact.getClassifier())
|
||||
&& ObjectUtils.nullSafeEquals(include.getExtension(), nodeArtifact.getExtension())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +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.module.resolver;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* Interface used to return a {@link Resource} that provides access to a module
|
||||
* uber-jar based on its Maven coordinates.
|
||||
*
|
||||
* @author David Turanski
|
||||
* @author Marius Bogoevici
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public interface ModuleResolver {
|
||||
|
||||
/**
|
||||
* Retrieve a resource given its coordinates.
|
||||
*
|
||||
* @param coordinates the coordinates of a resource
|
||||
* @return the resource
|
||||
*/
|
||||
Resource resolve(Coordinates coordinates);
|
||||
|
||||
/**
|
||||
* Retrieve a set of resources given their coordinates, along with additional dependencies.
|
||||
* Exclusion rules patterns (conforming to {@link org.eclipse.aether.util.filter.PatternExclusionsDependencyFilter}
|
||||
* can be provided as well.
|
||||
*
|
||||
* @param root the coordinates of the main resource
|
||||
* @param includes a list of coordinates to include along the main resource
|
||||
* @param excludePatterns a list of exclusion patterns
|
||||
* @see org.eclipse.aether.util.filter.PatternExclusionsDependencyFilter
|
||||
* @return the main resource and the additional dependencies
|
||||
*/
|
||||
Resource[] resolve(Coordinates root, Coordinates[] includes, String[] excludePatterns);
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 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
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@EnableConfigurationProperties({ModuleResolverProperties.class, AetherProxyProperties.class})
|
||||
public class ModuleResolverConfiguration {
|
||||
|
||||
@Autowired
|
||||
private ModuleResolverProperties properties;
|
||||
|
||||
@Autowired
|
||||
private AetherProxyProperties proxyProperties;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ModuleResolver.class)
|
||||
public ModuleResolver moduleResolver() {
|
||||
int i = 1;
|
||||
Map<String, String> repositoriesMap = new HashMap<>();
|
||||
for (String repository : properties.getRemoteRepositories()) {
|
||||
repositoriesMap.put("repository " + i++, repository);
|
||||
}
|
||||
AetherModuleResolver aetherModuleResolver = new AetherModuleResolver(properties.getLocalRepository(),
|
||||
repositoriesMap, proxyProperties);
|
||||
aetherModuleResolver.setOffline(properties.isOffline());
|
||||
return aetherModuleResolver;
|
||||
}
|
||||
}
|
||||
@@ -1,70 +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.module.resolver;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Configuration properties for {@link ModuleResolver}.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@ConfigurationProperties
|
||||
public class ModuleResolverProperties {
|
||||
|
||||
/**
|
||||
* File path to a locally available maven repository, where modules will be downloaded.
|
||||
*/
|
||||
private File localRepository = new File(System.getProperty("user.home") + File.separator + ".m2" +
|
||||
File.separator + "repository");
|
||||
|
||||
/**
|
||||
* Location of comma separated remote maven repositories from which modules will be downloaded, if not available locally.
|
||||
*/
|
||||
private String[] remoteRepositories = new String[]{"https://repo.spring.io/libs-snapshot"};
|
||||
|
||||
/**
|
||||
* Whether the resolver should operate in offline mode.
|
||||
*/
|
||||
private boolean offline = false;
|
||||
|
||||
public void setRemoteRepositories(String[] remoteRepositories) {
|
||||
this.remoteRepositories = remoteRepositories;
|
||||
}
|
||||
|
||||
public String[] getRemoteRepositories() {
|
||||
return remoteRepositories;
|
||||
}
|
||||
|
||||
public void setLocalRepository(File localRepository) {
|
||||
this.localRepository = localRepository;
|
||||
}
|
||||
|
||||
public File getLocalRepository() {
|
||||
return localRepository;
|
||||
}
|
||||
|
||||
public boolean isOffline() {
|
||||
return offline;
|
||||
}
|
||||
|
||||
public void setOffline(boolean offline) {
|
||||
this.offline = offline;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* Root package of the ModuleResolver support.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.stream.module.resolver;
|
||||
@@ -1,71 +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.module.utils;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.loader.LaunchedURLClassLoader;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class ClassloaderUtils {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ClassloaderUtils.class);
|
||||
|
||||
/**
|
||||
* Creates a ClassLoader for the launched modules by merging the URLs supplied as argument with the URLs that
|
||||
* make up the additional classpath of the launched JVM (retrieved from the application classloader), and
|
||||
* setting the extension classloader of the JVM as parent, if accessible.
|
||||
*
|
||||
* @param urls a list of library URLs
|
||||
* @return the resulting classloader
|
||||
*/
|
||||
public static ClassLoader createModuleClassloader(URL[] urls) {
|
||||
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("systemClassLoader is " + systemClassLoader);
|
||||
}
|
||||
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];
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Original URLs: " +
|
||||
StringUtils.arrayToCommaDelimitedString(urls));
|
||||
log.debug("Java Classpath URLs: " +
|
||||
StringUtils.arrayToCommaDelimitedString(systemUrlClassLoader.getURLs()));
|
||||
}
|
||||
System.arraycopy(urls, 0, mergedUrls, 0, urls.length);
|
||||
System.arraycopy(systemUrlClassLoader.getURLs(), 0, mergedUrls, urls.length,
|
||||
systemUrlClassLoader.getURLs().length);
|
||||
// add the extension classloader as parent to the created context, if accessible
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Classloader URLs: " + StringUtils.arrayToCommaDelimitedString(mergedUrls));
|
||||
}
|
||||
return new LaunchedURLClassLoader(mergedUrls, systemUrlClassLoader.getParent());
|
||||
}
|
||||
return new LaunchedURLClassLoader(urls, systemClassLoader);
|
||||
}
|
||||
}
|
||||
@@ -1,150 +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.module.resolver;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.get;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
|
||||
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
|
||||
import static org.hamcrest.Matchers.arrayWithSize;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.object.HasToString.hasToString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.eclipse.aether.resolution.ArtifactResolutionException;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
||||
|
||||
/**
|
||||
* @author David Turanski
|
||||
*/
|
||||
public class AetherModuleResolverTests {
|
||||
|
||||
private int port = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
@Rule
|
||||
public WireMockRule wireMockRule = new WireMockRule(port);
|
||||
|
||||
@Test
|
||||
public void testResolveLocal() throws IOException {
|
||||
ClassPathResource cpr = new ClassPathResource("local-repo");
|
||||
File localRepository = cpr.getFile();
|
||||
AetherModuleResolver defaultModuleResolver = new AetherModuleResolver(localRepository, null, null);
|
||||
Resource resource = defaultModuleResolver.resolve(new Coordinates("foo.bar", "foo-bar", "jar", "", "1.0.0"));
|
||||
assertTrue(resource.exists());
|
||||
assertEquals(resource.getFile().getName(), "foo-bar-1.0.0.jar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveLocalWithIncludes() throws IOException {
|
||||
ClassPathResource cpr = new ClassPathResource("local-repo");
|
||||
File localRepository = cpr.getFile();
|
||||
AetherModuleResolver defaultModuleResolver = new AetherModuleResolver(localRepository, null, null);
|
||||
Resource[] resources = defaultModuleResolver.resolve(
|
||||
new Coordinates("foo.bar", "foo-bar", "jar", "", "1.0.0"),
|
||||
new Coordinates[]{new Coordinates("qux.bar", "qux-bar", "jar", "", "1.0.0")},new String[]{});
|
||||
assertThat(resources, arrayWithSize(2));
|
||||
assertTrue(resources[0].exists());
|
||||
assertTrue(resources[1].exists());
|
||||
// the optional dependency 'foo.baz:foo-baz:1.0.0'of 'foo.bar:foo-bar' is not included
|
||||
assertThat(resources, arrayContainingInAnyOrder(
|
||||
hasToString(containsString("foo-bar-1.0.0.jar")),
|
||||
hasToString(containsString("qux-bar-1.0.0.jar"))));
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void testResolveDoesNotExist() throws IOException {
|
||||
ClassPathResource cpr = new ClassPathResource("local-repo");
|
||||
File localRepository = cpr.getFile();
|
||||
AetherModuleResolver defaultModuleResolver = new AetherModuleResolver(localRepository, null, null);
|
||||
defaultModuleResolver.resolve(new Coordinates("niente", "nada", "jar", "", "zilch"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testResolveRemote() throws IOException {
|
||||
File localRepository = Files.createTempDirectory(UUID.randomUUID().toString()).toFile();
|
||||
localRepository.deleteOnExit();
|
||||
Map<String, String> remoteRepos = new HashMap<>();
|
||||
remoteRepos.put("modules", "http://repo.spring.io/libs-snapshot");
|
||||
AetherModuleResolver defaultModuleResolver = new AetherModuleResolver(localRepository, remoteRepos, null);
|
||||
Resource resource = defaultModuleResolver.resolve(
|
||||
new Coordinates("org.springframework.cloud.stream.module", "time-source", "jar", "exec", "1.0.0.BUILD-SNAPSHOT"));
|
||||
assertTrue(resource.exists());
|
||||
assertEquals(resource.getFile().getName(), "time-source-1.0.0.BUILD-SNAPSHOT-exec.jar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveMockRemote() throws IOException {
|
||||
File localRepository = Files.createTempDirectory(UUID.randomUUID().toString()).toFile();
|
||||
localRepository.deleteOnExit();
|
||||
ClassPathResource stubJarResource = new ClassPathResource("__files/foo.jar");
|
||||
String stubFileName = stubJarResource.getFile().getName();
|
||||
Map<String, String> remoteRepos = new HashMap<>();
|
||||
remoteRepos.put("repo0", "http://localhost:" + port + "/repo0");
|
||||
remoteRepos.put("repo1", "http://localhost:" + port + "/repo1");
|
||||
stubFor(get(urlEqualTo("/repo1/org/bar/foo/1.0.0/foo-1.0.0.jar"))
|
||||
.willReturn(aResponse()
|
||||
.withStatus(200)
|
||||
.withBodyFile(stubFileName)));
|
||||
AetherModuleResolver defaultModuleResolver = new AetherModuleResolver(localRepository, remoteRepos, null);
|
||||
Resource resource = defaultModuleResolver.resolve(new Coordinates("org.bar", "foo", "jar", "", "1.0.0"));
|
||||
assertTrue(resource.exists());
|
||||
assertEquals(resource.getFile().getName(), "foo-1.0.0.jar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveOfflineWithMockRemote() throws IOException {
|
||||
try {
|
||||
File localRepository = Files.createTempDirectory(UUID.randomUUID().toString()).toFile();
|
||||
localRepository.deleteOnExit();
|
||||
ClassPathResource stubJarResource = new ClassPathResource("__files/foo.jar");
|
||||
String stubFileName = stubJarResource.getFile().getName();
|
||||
Map<String, String> remoteRepos = new HashMap<>();
|
||||
remoteRepos.put("repo0", "http://localhost:" + port + "/repo0");
|
||||
remoteRepos.put("repo1", "http://localhost:" + port + "/repo1");
|
||||
stubFor(get(urlEqualTo("/repo1/org/bar/foo/1.0.0/foo-1.0.0.jar"))
|
||||
.willReturn(aResponse()
|
||||
.withStatus(200)
|
||||
.withBodyFile(stubFileName)));
|
||||
AetherModuleResolver defaultModuleResolver = new AetherModuleResolver(localRepository, remoteRepos, null);
|
||||
defaultModuleResolver.setOffline(true);
|
||||
defaultModuleResolver.resolve(new Coordinates("org.bar", "foo", "jar", "", "1.0.0"));
|
||||
} catch (RuntimeException e) {
|
||||
// remote resolution fails because the resolver is operating offline
|
||||
assertThat(e.getCause(), instanceOf(ArtifactResolutionException.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>foo.bar</groupId>
|
||||
<artifactId>foo-bar</artifactId>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
<name>foo-bar</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>foo.baz</groupId>
|
||||
<artifactId>foo-baz</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>foo.baz</groupId>
|
||||
<artifactId>foo-baz</artifactId>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
<name>foo-baz</name>
|
||||
|
||||
</project>
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>qux-bar</groupId>
|
||||
<artifactId>qux-bar</artifactId>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
<name>qux-bar</name>
|
||||
|
||||
</project>
|
||||
Reference in New Issue
Block a user