From b98d91da394d4d98dbf08e3cb1bd30455825c76d Mon Sep 17 00:00:00 2001 From: Ilayaperumal Gopinathan Date: Wed, 20 Jan 2016 16:49:16 +0530 Subject: [PATCH] Support proxy settings for Aether Module resolver - Add `AetherProxyProperties` as a ConfigurationProperties for the proxy settings needed for the ProxySelector in Aether system - Update proxy settings in two places: 1) Where the remote repositories are setup 2) WHere the repository system session is created to resolve the artifacts This resolves #272 Set authentication only for proxy settings Set port type to `int` --- .../module/resolver/AetherModuleResolver.java | 72 ++++++++++- .../resolver/AetherProxyProperties.java | 118 ++++++++++++++++++ .../resolver/ModuleResolverConfiguration.java | 13 +- .../resolver/ModuleResolverProperties.java | 1 + .../resolver/AetherModuleResolverTests.java | 16 +-- 5 files changed, 200 insertions(+), 20 deletions(-) create mode 100644 spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/AetherProxyProperties.java diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/AetherModuleResolver.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/AetherModuleResolver.java index bbd42e644..e0ea3f498 100644 --- a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/AetherModuleResolver.java +++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/AetherModuleResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * 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. @@ -34,7 +34,11 @@ 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; @@ -47,6 +51,7 @@ 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; @@ -63,6 +68,7 @@ import org.springframework.util.StringUtils; * @author David Turanski * @author Mark Fisher * @author Marius Bogoevici + * @author Ilayaperumal Gopinathan */ public class AetherModuleResolver implements ModuleResolver { @@ -78,13 +84,19 @@ public class AetherModuleResolver implements ModuleResolver { 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 remoteRepositories) { + public AetherModuleResolver(File localRepository, Map remoteRepositories, + final AetherProxyProperties proxyProperties) { Assert.notNull(localRepository, "Local repository path cannot be null"); if (log.isDebugEnabled()) { log.debug("Local repository: " + localRepository); @@ -93,6 +105,22 @@ public class AetherModuleResolver implements ModuleResolver { 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 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); @@ -101,14 +129,38 @@ public class AetherModuleResolver implements ModuleResolver { this.remoteRepositories = new LinkedList<>(); if (!CollectionUtils.isEmpty(remoteRepositories)) { for (Map.Entry remoteRepo : remoteRepositories.entrySet()) { - RemoteRepository remoteRepository = new RemoteRepository.Builder(remoteRepo.getKey(), - DEFAULT_CONTENT_TYPE, remoteRepo.getValue()).build(); - this.remoteRepositories.add(remoteRepository); + 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; } @@ -133,6 +185,13 @@ public class AetherModuleResolver implements ModuleResolver { 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; } @@ -214,7 +273,8 @@ public class AetherModuleResolver implements ModuleResolver { result.add(toResource(artifactResult)); } } - } catch (DependencyResolutionException e) { + } + catch (DependencyResolutionException e) { throw new RuntimeException(e); } } diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/AetherProxyProperties.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/AetherProxyProperties.java new file mode 100644 index 000000000..df4086d86 --- /dev/null +++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/AetherProxyProperties.java @@ -0,0 +1,118 @@ +/* + * 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; + } + + } +} diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolverConfiguration.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolverConfiguration.java index 6f403ca67..c1a951a1d 100644 --- a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolverConfiguration.java +++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolverConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * 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. @@ -28,22 +28,27 @@ 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) +@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 repositoriesMap = new HashMap<>(); - for (String repository: properties.getRemoteRepositories()) { + for (String repository : properties.getRemoteRepositories()) { repositoriesMap.put("repository " + i++, repository); } - AetherModuleResolver aetherModuleResolver = new AetherModuleResolver(properties.getLocalRepository(), repositoriesMap); + AetherModuleResolver aetherModuleResolver = new AetherModuleResolver(properties.getLocalRepository(), + repositoriesMap, proxyProperties); aetherModuleResolver.setOffline(properties.isOffline()); return aetherModuleResolver; } diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolverProperties.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolverProperties.java index afe93e9d7..9b09b6578 100644 --- a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolverProperties.java +++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/resolver/ModuleResolverProperties.java @@ -51,6 +51,7 @@ public class ModuleResolverProperties { public String[] getRemoteRepositories() { return remoteRepositories; } + public void setLocalRepository(File localRepository) { this.localRepository = localRepository; } diff --git a/spring-cloud-stream-module-launcher/src/test/java/org/springframework/cloud/stream/module/resolver/AetherModuleResolverTests.java b/spring-cloud-stream-module-launcher/src/test/java/org/springframework/cloud/stream/module/resolver/AetherModuleResolverTests.java index d291c3d7d..5cb755e7a 100644 --- a/spring-cloud-stream-module-launcher/src/test/java/org/springframework/cloud/stream/module/resolver/AetherModuleResolverTests.java +++ b/spring-cloud-stream-module-launcher/src/test/java/org/springframework/cloud/stream/module/resolver/AetherModuleResolverTests.java @@ -20,14 +20,10 @@ 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.arrayContaining; import static org.hamcrest.Matchers.arrayContainingInAnyOrder; import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.beans.HasPropertyWithValue.hasProperty; import static org.hamcrest.object.HasToString.hasToString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -65,7 +61,7 @@ public class AetherModuleResolverTests { public void testResolveLocal() throws IOException { ClassPathResource cpr = new ClassPathResource("local-repo"); File localRepository = cpr.getFile(); - AetherModuleResolver defaultModuleResolver = new AetherModuleResolver(localRepository, null); + 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"); @@ -75,7 +71,7 @@ public class AetherModuleResolverTests { public void testResolveLocalWithIncludes() throws IOException { ClassPathResource cpr = new ClassPathResource("local-repo"); File localRepository = cpr.getFile(); - AetherModuleResolver defaultModuleResolver = new AetherModuleResolver(localRepository, null); + 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[]{}); @@ -92,7 +88,7 @@ public class AetherModuleResolverTests { public void testResolveDoesNotExist() throws IOException { ClassPathResource cpr = new ClassPathResource("local-repo"); File localRepository = cpr.getFile(); - AetherModuleResolver defaultModuleResolver = new AetherModuleResolver(localRepository, null); + AetherModuleResolver defaultModuleResolver = new AetherModuleResolver(localRepository, null, null); defaultModuleResolver.resolve(new Coordinates("niente", "nada", "jar", "", "zilch")); } @@ -103,7 +99,7 @@ public class AetherModuleResolverTests { localRepository.deleteOnExit(); Map remoteRepos = new HashMap<>(); remoteRepos.put("modules", "http://repo.spring.io/libs-snapshot"); - AetherModuleResolver defaultModuleResolver = new AetherModuleResolver(localRepository, remoteRepos); + 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()); @@ -123,7 +119,7 @@ public class AetherModuleResolverTests { .willReturn(aResponse() .withStatus(200) .withBodyFile(stubFileName))); - AetherModuleResolver defaultModuleResolver = new AetherModuleResolver(localRepository, remoteRepos); + 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"); @@ -143,7 +139,7 @@ public class AetherModuleResolverTests { .willReturn(aResponse() .withStatus(200) .withBodyFile(stubFileName))); - AetherModuleResolver defaultModuleResolver = new AetherModuleResolver(localRepository, remoteRepos); + AetherModuleResolver defaultModuleResolver = new AetherModuleResolver(localRepository, remoteRepos, null); defaultModuleResolver.setOffline(true); defaultModuleResolver.resolve(new Coordinates("org.bar", "foo", "jar", "", "1.0.0")); } catch (RuntimeException e) {