XD-3270 - Implementation of AetherModuleResolver
This commit is contained in:
committed by
Mark Fisher
parent
219d790910
commit
262d82387e
@@ -17,6 +17,9 @@
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<start-class>org.springframework.cloud.stream.module.launcher.ModuleLauncher</start-class>
|
||||
<aether.version>1.0.2.v20150114</aether.version>
|
||||
<maven.version>3.1.0</maven.version>
|
||||
<wiremock.version>1.57</wiremock.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -28,11 +31,97 @@
|
||||
<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>
|
||||
<version>${spring-integration.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</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>
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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 java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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.connector.basic.BasicRepositoryConnectorFactory;
|
||||
import org.eclipse.aether.impl.DefaultServiceLocator;
|
||||
import org.eclipse.aether.repository.LocalRepository;
|
||||
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.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.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* An implementation of ModuleResolver using <a href="http://www.eclipse.org/aether/>aether</a> resolve the module
|
||||
* artifact (uber jar) in a local Maven repository, downloading the latest update from a remote repository if
|
||||
* necessary.
|
||||
* @author David Turanski
|
||||
*/
|
||||
public class AetherModuleResolver implements ModuleResolver {
|
||||
private static final String DEFAULT_CONTENT_TYPE = "default";
|
||||
|
||||
private static final String DEFAULT_CLASSIFIER = "";
|
||||
|
||||
private static final String DEFAULT_EXTENSION = "jar";
|
||||
|
||||
private final File localRepository;
|
||||
|
||||
private final List<RemoteRepository> remoteRepositories;
|
||||
|
||||
private final RepositorySystem repositorySystem;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public AetherModuleResolver(File localRepository, Map<String, String> remoteRepositories) {
|
||||
Assert.isTrue(localRepository.exists(), "File " + localRepository + " does not exist.");
|
||||
this.localRepository = localRepository;
|
||||
this.remoteRepositories = new LinkedList<>();
|
||||
if (!CollectionUtils.isEmpty(remoteRepositories)) {
|
||||
for (Map.Entry<String, String> remoteRepo : remoteRepositories.entrySet()) {
|
||||
RemoteRepository remoteRepository = new RemoteRepository.Builder(remoteRepo.getKey(),
|
||||
DEFAULT_CONTENT_TYPE, remoteRepo.getValue()).build();
|
||||
this.remoteRepositories.add(remoteRepository);
|
||||
}
|
||||
}
|
||||
repositorySystem = newRepositorySystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 groupId the groupId
|
||||
* @param artifactId the artifactId
|
||||
* @param version the version
|
||||
* @return a {@ link FileSystemResource} representing the resolved artifact in the local repository.
|
||||
* @throws a RuntimeException if the artifact does not exist or the resolution fails.
|
||||
*/
|
||||
@Override
|
||||
public Resource resolve(String groupId, String artifactId, String version) {
|
||||
Artifact artifact = new DefaultArtifact(groupId, artifactId, DEFAULT_CLASSIFIER, DEFAULT_EXTENSION, version);
|
||||
RepositorySystemSession session = newRepositorySystemSession(repositorySystem,
|
||||
localRepository.getAbsolutePath());
|
||||
ArtifactResult result;
|
||||
|
||||
try {
|
||||
result = repositorySystem.resolveArtifact(session, new ArtifactRequest(artifact, remoteRepositories,
|
||||
"runtime"));
|
||||
}
|
||||
catch (ArtifactResolutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return new FileSystemResource(result.getArtifact().getFile());
|
||||
}
|
||||
|
||||
/*
|
||||
* 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));
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
||||
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 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.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @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);
|
||||
Resource resource = defaultModuleResolver.resolve("foo.bar", "foo-bar", "1.0.0");
|
||||
assertTrue(resource.exists());
|
||||
assertEquals(resource.getFile().getName(), "foo-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);
|
||||
defaultModuleResolver.resolve("niente", "nada", "zilch");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testResolveRemote() throws IOException {
|
||||
ClassPathResource cpr = new ClassPathResource("local-repo");
|
||||
File localRepository = cpr.getFile();
|
||||
|
||||
Map<String, String> remoteRepos = new HashMap<>();
|
||||
|
||||
remoteRepos.put("spring", "http://repo.spring.io/release");
|
||||
remoteRepos.put("spring-snap", "http://repo.spring.io/snapshot");
|
||||
remoteRepos.put("spring-ms", "http://repo.spring.io/milestone");
|
||||
|
||||
AetherModuleResolver defaultModuleResolver = new AetherModuleResolver(localRepository, remoteRepos);
|
||||
Resource resource = defaultModuleResolver.resolve("org.springframework", "spring-core", "4.1.6.RELEASE");
|
||||
assertTrue(resource.exists());
|
||||
assertEquals(resource.getFile().getName(), "spring-core-4.1.6.RELEASE.jar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveMockRemote() throws IOException {
|
||||
ClassPathResource cpr = new ClassPathResource("local-repo");
|
||||
File localRepository = cpr.getFile();
|
||||
|
||||
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);
|
||||
Resource resource = defaultModuleResolver.resolve("org.bar", "foo", "1.0.0");
|
||||
assertTrue(resource.exists());
|
||||
assertEquals(resource.getFile().getName(), "foo-1.0.0.jar");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user