From 84937551787072a4befac29fb48436b3187ac4c6 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 14 Jul 2015 12:01:55 +0100 Subject: [PATCH] Configure CLI with repositories from active profiles in settings.xml This commit enhances the CLI to use the repositories configured in the profiles declared in a user's Maven settings.xml file during dependency resolution. A profile must be active for its repositories to be used. Closes gh-2703 Closes gh-3483 --- .../boot/cli/compiler/MavenSettings.java | 283 ++++++++++++++++++ .../cli/compiler/MavenSettingsReader.java | 120 ++++++++ .../RepositoryConfigurationFactory.java | 59 ++-- ...ositorySystemSessionAutoConfiguration.java | 145 +-------- .../RepositoryConfigurationFactoryTests.java | 106 +++++++ ...rySystemSessionAutoConfigurationTests.java | 34 ++- .../boot/cli/util/SystemProperties.java | 61 ++++ .../src/main/asciidoc/spring-boot-cli.adoc | 18 ++ 8 files changed, 643 insertions(+), 183 deletions(-) create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/MavenSettings.java create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/MavenSettingsReader.java create mode 100644 spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/RepositoryConfigurationFactoryTests.java create mode 100644 spring-boot-cli/src/test/java/org/springframework/boot/cli/util/SystemProperties.java diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/MavenSettings.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/MavenSettings.java new file mode 100644 index 00000000000..2c822dfad7d --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/MavenSettings.java @@ -0,0 +1,283 @@ +/* + * Copyright 2012-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.boot.cli.compiler; + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.maven.model.ActivationFile; +import org.apache.maven.model.ActivationOS; +import org.apache.maven.model.ActivationProperty; +import org.apache.maven.model.building.ModelProblemCollector; +import org.apache.maven.model.building.ModelProblemCollectorRequest; +import org.apache.maven.model.profile.DefaultProfileSelector; +import org.apache.maven.model.profile.ProfileActivationContext; +import org.apache.maven.model.profile.activation.FileProfileActivator; +import org.apache.maven.model.profile.activation.JdkVersionProfileActivator; +import org.apache.maven.model.profile.activation.OperatingSystemProfileActivator; +import org.apache.maven.model.profile.activation.PropertyProfileActivator; +import org.apache.maven.settings.Activation; +import org.apache.maven.settings.Mirror; +import org.apache.maven.settings.Profile; +import org.apache.maven.settings.Proxy; +import org.apache.maven.settings.Server; +import org.apache.maven.settings.Settings; +import org.apache.maven.settings.crypto.SettingsDecryptionResult; +import org.eclipse.aether.repository.Authentication; +import org.eclipse.aether.repository.AuthenticationSelector; +import org.eclipse.aether.repository.MirrorSelector; +import org.eclipse.aether.repository.ProxySelector; +import org.eclipse.aether.util.repository.AuthenticationBuilder; +import org.eclipse.aether.util.repository.ConservativeAuthenticationSelector; +import org.eclipse.aether.util.repository.DefaultAuthenticationSelector; +import org.eclipse.aether.util.repository.DefaultMirrorSelector; +import org.eclipse.aether.util.repository.DefaultProxySelector; + +/** + * An encapsulation of settings read from a user's Maven settings.xml. + * + * @author Andy Wilkinson + * @since 1.3.0 + * @see MavenSettingsReader + */ +public class MavenSettings { + + private final boolean offline; + + private final MirrorSelector mirrorSelector; + + private final AuthenticationSelector authenticationSelector; + + private final ProxySelector proxySelector; + + private final String localRepository; + + private final List activeProfiles; + + /** + * @param settings + * @param decryptedSettings + */ + public MavenSettings(Settings settings, SettingsDecryptionResult decryptedSettings) { + this.offline = settings.isOffline(); + this.mirrorSelector = createMirrorSelector(settings); + this.authenticationSelector = createAuthenticationSelector(decryptedSettings); + this.proxySelector = createProxySelector(decryptedSettings); + this.localRepository = settings.getLocalRepository(); + this.activeProfiles = determineActiveProfiles(settings); + } + + private MirrorSelector createMirrorSelector(Settings settings) { + DefaultMirrorSelector selector = new DefaultMirrorSelector(); + for (Mirror mirror : settings.getMirrors()) { + selector.add(mirror.getId(), mirror.getUrl(), mirror.getLayout(), false, + mirror.getMirrorOf(), mirror.getMirrorOfLayouts()); + } + return selector; + } + + private AuthenticationSelector createAuthenticationSelector( + SettingsDecryptionResult decryptedSettings) { + DefaultAuthenticationSelector selector = new DefaultAuthenticationSelector(); + for (Server server : decryptedSettings.getServers()) { + AuthenticationBuilder auth = new AuthenticationBuilder(); + auth.addUsername(server.getUsername()).addPassword(server.getPassword()); + auth.addPrivateKey(server.getPrivateKey(), server.getPassphrase()); + selector.add(server.getId(), auth.build()); + } + return new ConservativeAuthenticationSelector(selector); + } + + private ProxySelector createProxySelector(SettingsDecryptionResult decryptedSettings) { + DefaultProxySelector selector = new DefaultProxySelector(); + for (Proxy proxy : decryptedSettings.getProxies()) { + Authentication authentication = new AuthenticationBuilder() + .addUsername(proxy.getUsername()).addPassword(proxy.getPassword()) + .build(); + selector.add(new org.eclipse.aether.repository.Proxy(proxy.getProtocol(), + proxy.getHost(), proxy.getPort(), authentication), proxy + .getNonProxyHosts()); + } + return selector; + } + + private List determineActiveProfiles(Settings settings) { + SpringBootCliModelProblemCollector problemCollector = new SpringBootCliModelProblemCollector(); + List activeModelProfiles = createProfileSelector() + .getActiveProfiles( + createModelProfiles(settings.getProfiles()), + new SpringBootCliProfileActivationContext(settings + .getActiveProfiles()), problemCollector); + if (!problemCollector.getProblems().isEmpty()) { + throw new IllegalStateException(createFailureMessage(problemCollector)); + } + List activeProfiles = new ArrayList(); + Map profiles = settings.getProfilesAsMap(); + for (org.apache.maven.model.Profile modelProfile : activeModelProfiles) { + activeProfiles.add(profiles.get(modelProfile.getId())); + } + return activeProfiles; + } + + private String createFailureMessage( + SpringBootCliModelProblemCollector problemCollector) { + StringWriter message = new StringWriter(); + PrintWriter printer = new PrintWriter(message); + printer.println("Failed to determine active profiles:"); + for (ModelProblemCollectorRequest problem : problemCollector.getProblems()) { + printer.println(" " + problem.getMessage() + " at " + + problem.getLocation()); + } + return message.toString(); + } + + private DefaultProfileSelector createProfileSelector() { + DefaultProfileSelector selector = new DefaultProfileSelector(); + selector.addProfileActivator(new FileProfileActivator()); + selector.addProfileActivator(new JdkVersionProfileActivator()); + selector.addProfileActivator(new PropertyProfileActivator()); + selector.addProfileActivator(new OperatingSystemProfileActivator()); + return selector; + } + + private List createModelProfiles( + List profiles) { + List modelProfiles = new ArrayList(); + for (Profile profile : profiles) { + org.apache.maven.model.Profile modelProfile = new org.apache.maven.model.Profile(); + modelProfile.setId(profile.getId()); + modelProfile.setActivation(createModelActivation(profile.getActivation())); + modelProfiles.add(modelProfile); + } + return modelProfiles; + } + + private org.apache.maven.model.Activation createModelActivation(Activation activation) { + org.apache.maven.model.Activation modelActivation = new org.apache.maven.model.Activation(); + modelActivation.setActiveByDefault(activation.isActiveByDefault()); + if (activation.getFile() != null) { + ActivationFile activationFile = new ActivationFile(); + activationFile.setExists(activation.getFile().getExists()); + activationFile.setMissing(activation.getFile().getMissing()); + modelActivation.setFile(activationFile); + } + modelActivation.setJdk(activation.getJdk()); + if (activation.getOs() != null) { + ActivationOS os = new ActivationOS(); + os.setArch(activation.getOs().getArch()); + os.setFamily(activation.getOs().getFamily()); + os.setName(activation.getOs().getName()); + os.setVersion(activation.getOs().getVersion()); + modelActivation.setOs(os); + } + if (activation.getProperty() != null) { + ActivationProperty property = new ActivationProperty(); + property.setName(activation.getProperty().getName()); + property.setValue(activation.getProperty().getValue()); + modelActivation.setProperty(property); + } + return modelActivation; + } + + public boolean getOffline() { + return this.offline; + } + + public MirrorSelector getMirrorSelector() { + return this.mirrorSelector; + } + + public AuthenticationSelector getAuthenticationSelector() { + return this.authenticationSelector; + } + + public ProxySelector getProxySelector() { + return this.proxySelector; + } + + public String getLocalRepository() { + return this.localRepository; + } + + public List getActiveProfiles() { + return this.activeProfiles; + } + + private static final class SpringBootCliProfileActivationContext implements + ProfileActivationContext { + + private final List activeProfiles; + + SpringBootCliProfileActivationContext(List activeProfiles) { + this.activeProfiles = activeProfiles; + } + + @Override + public List getActiveProfileIds() { + return this.activeProfiles; + } + + @Override + public List getInactiveProfileIds() { + return Collections.emptyList(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public Map getSystemProperties() { + return (Map) System.getProperties(); + } + + @Override + public Map getUserProperties() { + return Collections.emptyMap(); + } + + @Override + public File getProjectDirectory() { + return new File("."); + } + + @Override + public Map getProjectProperties() { + return Collections.emptyMap(); + } + + } + + private static final class SpringBootCliModelProblemCollector implements + ModelProblemCollector { + + private final List problems = new ArrayList(); + + @Override + public void add(ModelProblemCollectorRequest req) { + this.problems.add(req); + } + + List getProblems() { + return this.problems; + } + + } + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/MavenSettingsReader.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/MavenSettingsReader.java new file mode 100644 index 00000000000..795aebfe7a5 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/MavenSettingsReader.java @@ -0,0 +1,120 @@ +/* + * Copyright 2012-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.boot.cli.compiler; + +import java.io.File; +import java.lang.reflect.Field; + +import org.apache.maven.settings.Settings; +import org.apache.maven.settings.building.DefaultSettingsBuilderFactory; +import org.apache.maven.settings.building.DefaultSettingsBuildingRequest; +import org.apache.maven.settings.building.SettingsBuildingException; +import org.apache.maven.settings.building.SettingsBuildingRequest; +import org.apache.maven.settings.crypto.DefaultSettingsDecrypter; +import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest; +import org.apache.maven.settings.crypto.SettingsDecrypter; +import org.apache.maven.settings.crypto.SettingsDecryptionResult; +import org.sonatype.plexus.components.cipher.DefaultPlexusCipher; +import org.sonatype.plexus.components.cipher.PlexusCipherException; +import org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher; +import org.springframework.boot.cli.util.Log; + +/** + * {@code MavenSettingsReader} reads settings from a user's Maven settings.xml file, + * decrypting them if necessary using settings-security.xml. + * + * @author Andy Wilkinson + * @since 1.3.0 + */ +public class MavenSettingsReader { + + private final String homeDir; + + public MavenSettingsReader() { + this(System.getProperty("user.home")); + } + + public MavenSettingsReader(String homeDir) { + this.homeDir = homeDir; + } + + public MavenSettings readSettings() { + Settings settings = loadSettings(); + SettingsDecryptionResult decrypted = decryptSettings(settings); + if (!decrypted.getProblems().isEmpty()) { + Log.error("Maven settings decryption failed. Some Maven repositories may be inaccessible"); + // Continue - the encrypted credentials may not be used + } + return new MavenSettings(settings, decrypted); + } + + private Settings loadSettings() { + File settingsFile = new File(this.homeDir, ".m2/settings.xml"); + SettingsBuildingRequest request = new DefaultSettingsBuildingRequest(); + request.setUserSettingsFile(settingsFile); + request.setSystemProperties(System.getProperties()); + try { + return new DefaultSettingsBuilderFactory().newInstance().build(request) + .getEffectiveSettings(); + } + catch (SettingsBuildingException ex) { + throw new IllegalStateException("Failed to build settings from " + + settingsFile, ex); + } + } + + private SettingsDecryptionResult decryptSettings(Settings settings) { + DefaultSettingsDecryptionRequest request = new DefaultSettingsDecryptionRequest( + settings); + + return createSettingsDecrypter().decrypt(request); + } + + private SettingsDecrypter createSettingsDecrypter() { + SettingsDecrypter settingsDecrypter = new DefaultSettingsDecrypter(); + setField(DefaultSettingsDecrypter.class, "securityDispatcher", settingsDecrypter, + new SpringBootSecDispatcher()); + return settingsDecrypter; + } + + private void setField(Class clazz, String fieldName, Object target, Object value) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } + catch (Exception e) { + throw new IllegalStateException("Failed to set field '" + fieldName + + "' on '" + target + "'", e); + } + } + + private class SpringBootSecDispatcher extends DefaultSecDispatcher { + + public SpringBootSecDispatcher() { + this._configurationFile = new File(MavenSettingsReader.this.homeDir, + ".m2/settings-security.xml").getAbsolutePath(); + try { + this._cipher = new DefaultPlexusCipher(); + } + catch (PlexusCipherException e) { + throw new IllegalStateException(e); + } + } + } + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/RepositoryConfigurationFactory.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/RepositoryConfigurationFactory.java index 12b491c4904..71b58dd5ff7 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/RepositoryConfigurationFactory.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/RepositoryConfigurationFactory.java @@ -21,11 +21,8 @@ import java.net.URI; import java.util.ArrayList; import java.util.List; -import org.apache.maven.settings.Settings; -import org.apache.maven.settings.building.DefaultSettingsBuilderFactory; -import org.apache.maven.settings.building.DefaultSettingsBuildingRequest; -import org.apache.maven.settings.building.SettingsBuildingException; -import org.apache.maven.settings.building.SettingsBuildingRequest; +import org.apache.maven.settings.Profile; +import org.apache.maven.settings.Repository; import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration; import org.springframework.util.StringUtils; @@ -46,58 +43,54 @@ public final class RepositoryConfigurationFactory { private static final RepositoryConfiguration SPRING_SNAPSHOT = new RepositoryConfiguration( "spring-snapshot", URI.create("http://repo.spring.io/snapshot"), true); + private RepositoryConfigurationFactory() { + + } + /** * @return the newly-created default repository configuration */ public static List createDefaultRepositoryConfiguration() { + MavenSettings mavenSettings = new MavenSettingsReader().readSettings(); List repositoryConfiguration = new ArrayList(); - repositoryConfiguration.add(MAVEN_CENTRAL); - if (!Boolean.getBoolean("disableSpringSnapshotRepos")) { repositoryConfiguration.add(SPRING_MILESTONE); repositoryConfiguration.add(SPRING_SNAPSHOT); } - - addDefaultCacheAsRepository(repositoryConfiguration); + addDefaultCacheAsRepository(mavenSettings.getLocalRepository(), + repositoryConfiguration); + addActiveProfileRepositories(mavenSettings.getActiveProfiles(), + repositoryConfiguration); return repositoryConfiguration; } - /** - * Add the default local M2 cache directory as a remote repository. Only do this if - * the local cache location has been changed from the default. - * @param repositoryConfiguration - */ - public static void addDefaultCacheAsRepository( + private static void addDefaultCacheAsRepository(String localRepository, List repositoryConfiguration) { RepositoryConfiguration repository = new RepositoryConfiguration("local", - getLocalRepositoryDirectory().toURI(), true); + getLocalRepositoryDirectory(localRepository).toURI(), true); if (!repositoryConfiguration.contains(repository)) { repositoryConfiguration.add(0, repository); } } - private static File getLocalRepositoryDirectory() { - String localRepository = loadSettings().getLocalRepository(); - if (StringUtils.hasText(localRepository)) { - return new File(localRepository); + private static void addActiveProfileRepositories(List activeProfiles, + List repositoryConfiguration) { + for (Profile activeProfile : activeProfiles) { + for (Repository repository : activeProfile.getRepositories()) { + repositoryConfiguration.add(new RepositoryConfiguration(repository + .getId(), URI.create(repository.getUrl()), repository + .getSnapshots() != null ? repository.getSnapshots().isEnabled() + : false)); + } } - return new File(getM2HomeDirectory(), "repository"); } - private static Settings loadSettings() { - File settingsFile = new File(System.getProperty("user.home"), ".m2/settings.xml"); - SettingsBuildingRequest request = new DefaultSettingsBuildingRequest(); - request.setUserSettingsFile(settingsFile); - request.setSystemProperties(System.getProperties()); - try { - return new DefaultSettingsBuilderFactory().newInstance().build(request) - .getEffectiveSettings(); - } - catch (SettingsBuildingException ex) { - throw new IllegalStateException("Failed to build settings from " - + settingsFile, ex); + private static File getLocalRepositoryDirectory(String localRepository) { + if (StringUtils.hasText(localRepository)) { + return new File(localRepository); } + return new File(getM2HomeDirectory(), "repository"); } private static File getM2HomeDirectory() { diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/SettingsXmlRepositorySystemSessionAutoConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/SettingsXmlRepositorySystemSessionAutoConfiguration.java index 877580100a6..711521a4093 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/SettingsXmlRepositorySystemSessionAutoConfiguration.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/SettingsXmlRepositorySystemSessionAutoConfiguration.java @@ -16,38 +16,11 @@ package org.springframework.boot.cli.compiler.grape; -import java.io.File; -import java.lang.reflect.Field; -import java.util.List; - -import org.apache.maven.settings.Mirror; -import org.apache.maven.settings.Proxy; -import org.apache.maven.settings.Server; -import org.apache.maven.settings.Settings; -import org.apache.maven.settings.building.DefaultSettingsBuilderFactory; -import org.apache.maven.settings.building.DefaultSettingsBuildingRequest; -import org.apache.maven.settings.building.SettingsBuildingException; -import org.apache.maven.settings.building.SettingsBuildingRequest; -import org.apache.maven.settings.crypto.DefaultSettingsDecrypter; -import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest; -import org.apache.maven.settings.crypto.SettingsDecrypter; -import org.apache.maven.settings.crypto.SettingsDecryptionResult; import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.RepositorySystem; -import org.eclipse.aether.repository.Authentication; -import org.eclipse.aether.repository.AuthenticationSelector; import org.eclipse.aether.repository.LocalRepository; -import org.eclipse.aether.repository.MirrorSelector; -import org.eclipse.aether.repository.ProxySelector; -import org.eclipse.aether.util.repository.AuthenticationBuilder; -import org.eclipse.aether.util.repository.ConservativeAuthenticationSelector; -import org.eclipse.aether.util.repository.DefaultAuthenticationSelector; -import org.eclipse.aether.util.repository.DefaultMirrorSelector; -import org.eclipse.aether.util.repository.DefaultProxySelector; -import org.sonatype.plexus.components.cipher.DefaultPlexusCipher; -import org.sonatype.plexus.components.cipher.PlexusCipherException; -import org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher; -import org.springframework.boot.cli.util.Log; +import org.springframework.boot.cli.compiler.MavenSettings; +import org.springframework.boot.cli.compiler.MavenSettingsReader; /** * Auto-configuration for a RepositorySystemSession that uses Maven's settings.xml to @@ -58,32 +31,16 @@ import org.springframework.boot.cli.util.Log; public class SettingsXmlRepositorySystemSessionAutoConfiguration implements RepositorySystemSessionAutoConfiguration { - private final String homeDir; - - public SettingsXmlRepositorySystemSessionAutoConfiguration() { - this(System.getProperty("user.home")); - } - - SettingsXmlRepositorySystemSessionAutoConfiguration(String homeDir) { - this.homeDir = homeDir; - } - @Override public void apply(DefaultRepositorySystemSession session, RepositorySystem repositorySystem) { - Settings settings = loadSettings(); - SettingsDecryptionResult decryptionResult = decryptSettings(settings); - if (!decryptionResult.getProblems().isEmpty()) { - Log.error("Maven settings decryption failed. Some Maven repositories may be inaccessible"); - // Continue - the encrypted credentials may not be used - } + MavenSettings settings = new MavenSettingsReader().readSettings(); - session.setOffline(settings.isOffline()); - session.setMirrorSelector(createMirrorSelector(settings)); - session.setAuthenticationSelector(createAuthenticationSelector(decryptionResult - .getServers())); - session.setProxySelector(createProxySelector(decryptionResult.getProxies())); + session.setOffline(settings.getOffline()); + session.setMirrorSelector(settings.getMirrorSelector()); + session.setAuthenticationSelector(settings.getAuthenticationSelector()); + session.setProxySelector(settings.getProxySelector()); String localRepository = settings.getLocalRepository(); if (localRepository != null) { @@ -92,92 +49,4 @@ public class SettingsXmlRepositorySystemSessionAutoConfiguration implements } } - private Settings loadSettings() { - File settingsFile = new File(this.homeDir, ".m2/settings.xml"); - SettingsBuildingRequest request = new DefaultSettingsBuildingRequest(); - request.setUserSettingsFile(settingsFile); - request.setSystemProperties(System.getProperties()); - try { - return new DefaultSettingsBuilderFactory().newInstance().build(request) - .getEffectiveSettings(); - } - catch (SettingsBuildingException ex) { - throw new IllegalStateException("Failed to build settings from " - + settingsFile, ex); - } - } - - private SettingsDecryptionResult decryptSettings(Settings settings) { - DefaultSettingsDecryptionRequest request = new DefaultSettingsDecryptionRequest( - settings); - - return createSettingsDecrypter().decrypt(request); - } - - private SettingsDecrypter createSettingsDecrypter() { - SettingsDecrypter settingsDecrypter = new DefaultSettingsDecrypter(); - setField(DefaultSettingsDecrypter.class, "securityDispatcher", settingsDecrypter, - new SpringBootSecDispatcher()); - return settingsDecrypter; - } - - private void setField(Class clazz, String fieldName, Object target, Object value) { - try { - Field field = clazz.getDeclaredField(fieldName); - field.setAccessible(true); - field.set(target, value); - } - catch (Exception e) { - throw new IllegalStateException("Failed to set field '" + fieldName - + "' on '" + target + "'", e); - } - } - - private MirrorSelector createMirrorSelector(Settings settings) { - DefaultMirrorSelector selector = new DefaultMirrorSelector(); - for (Mirror mirror : settings.getMirrors()) { - selector.add(mirror.getId(), mirror.getUrl(), mirror.getLayout(), false, - mirror.getMirrorOf(), mirror.getMirrorOfLayouts()); - } - return selector; - } - - private AuthenticationSelector createAuthenticationSelector(List servers) { - DefaultAuthenticationSelector selector = new DefaultAuthenticationSelector(); - for (Server server : servers) { - AuthenticationBuilder auth = new AuthenticationBuilder(); - auth.addUsername(server.getUsername()).addPassword(server.getPassword()); - auth.addPrivateKey(server.getPrivateKey(), server.getPassphrase()); - selector.add(server.getId(), auth.build()); - } - return new ConservativeAuthenticationSelector(selector); - } - - private ProxySelector createProxySelector(List proxies) { - DefaultProxySelector selector = new DefaultProxySelector(); - for (Proxy proxy : proxies) { - Authentication authentication = new AuthenticationBuilder() - .addUsername(proxy.getUsername()).addPassword(proxy.getPassword()) - .build(); - selector.add(new org.eclipse.aether.repository.Proxy(proxy.getProtocol(), - proxy.getHost(), proxy.getPort(), authentication), proxy - .getNonProxyHosts()); - } - return selector; - } - - private class SpringBootSecDispatcher extends DefaultSecDispatcher { - - public SpringBootSecDispatcher() { - this._configurationFile = new File( - SettingsXmlRepositorySystemSessionAutoConfiguration.this.homeDir, - ".m2/settings-security.xml").getAbsolutePath(); - try { - this._cipher = new DefaultPlexusCipher(); - } - catch (PlexusCipherException e) { - throw new IllegalStateException(e); - } - } - } } diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/RepositoryConfigurationFactoryTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/RepositoryConfigurationFactoryTests.java new file mode 100644 index 00000000000..66ef44d31e6 --- /dev/null +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/RepositoryConfigurationFactoryTests.java @@ -0,0 +1,106 @@ +/* + * Copyright 2012-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.boot.cli.compiler; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.Test; +import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration; +import org.springframework.boot.cli.util.SystemProperties; + +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link RepositoryConfigurationFactory} + * + * @author Andy Wilkinson + */ +public class RepositoryConfigurationFactoryTests { + + @Test + public void defaultRepositories() { + SystemProperties.doWithSystemProperties(new Runnable() { + @Override + public void run() { + List repositoryConfiguration = RepositoryConfigurationFactory + .createDefaultRepositoryConfiguration(); + assertRepositoryConfiguration(repositoryConfiguration, "central", + "local", "spring-snapshot", "spring-milestone"); + } + }, "user.home:src/test/resources/maven-settings/basic"); + } + + @Test + public void snapshotRepositoriesDisabled() { + SystemProperties.doWithSystemProperties( + new Runnable() { + @Override + public void run() { + List repositoryConfiguration = RepositoryConfigurationFactory + .createDefaultRepositoryConfiguration(); + assertRepositoryConfiguration(repositoryConfiguration, "central", + "local"); + } + }, "user.home:src/test/resources/maven-settings/basic", + "disableSpringSnapshotRepos:true"); + } + + @Test + public void activeByDefaultProfileRepositories() { + SystemProperties.doWithSystemProperties(new Runnable() { + @Override + public void run() { + List repositoryConfiguration = RepositoryConfigurationFactory + .createDefaultRepositoryConfiguration(); + assertRepositoryConfiguration(repositoryConfiguration, "central", + "local", "spring-snapshot", "spring-milestone", + "active-by-default"); + } + }, "user.home:src/test/resources/maven-settings/active-profile-repositories"); + } + + @Test + public void activeByPropertyProfileRepositories() { + SystemProperties.doWithSystemProperties( + new Runnable() { + @Override + public void run() { + List repositoryConfiguration = RepositoryConfigurationFactory + .createDefaultRepositoryConfiguration(); + assertRepositoryConfiguration(repositoryConfiguration, "central", + "local", "spring-snapshot", "spring-milestone", + "active-by-property"); + } + }, + "user.home:src/test/resources/maven-settings/active-profile-repositories", + "foo:bar"); + } + + private void assertRepositoryConfiguration( + List configurations, String... expectedNames) { + assertThat(configurations, hasSize(expectedNames.length)); + Set actualNames = new HashSet(); + for (RepositoryConfiguration configuration : configurations) { + actualNames.add(configuration.getName()); + } + assertThat(actualNames, hasItems(expectedNames)); + } +} diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/SettingsXmlRepositorySystemSessionAutoConfigurationTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/SettingsXmlRepositorySystemSessionAutoConfigurationTests.java index 2c237893586..e3764b0345d 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/SettingsXmlRepositorySystemSessionAutoConfigurationTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/SettingsXmlRepositorySystemSessionAutoConfigurationTests.java @@ -37,6 +37,7 @@ import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; +import org.springframework.boot.cli.util.SystemProperties; import static org.hamcrest.Matchers.endsWith; import static org.junit.Assert.assertEquals; @@ -89,25 +90,33 @@ public class SettingsXmlRepositorySystemSessionAutoConfigurationTests { } }); - System.setProperty("foo", "bar"); - try { - new SettingsXmlRepositorySystemSessionAutoConfiguration( - "src/test/resources/maven-settings/property-interpolation").apply( - session, this.repositorySystem); - } - finally { - System.clearProperty("foo"); - } + SystemProperties.doWithSystemProperties( + new Runnable() { + @Override + public void run() { + new SettingsXmlRepositorySystemSessionAutoConfiguration() + .apply(session, + SettingsXmlRepositorySystemSessionAutoConfigurationTests.this.repositorySystem); + } + }, "user.home:src/test/resources/maven-settings/property-interpolation", + "foo:bar"); assertThat(session.getLocalRepository().getBasedir().getAbsolutePath(), endsWith(File.separatorChar + "bar" + File.separatorChar + "repository")); } private void assertSessionCustomization(String userHome) { - DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession(); + final DefaultRepositorySystemSession session = MavenRepositorySystemUtils + .newSession(); - new SettingsXmlRepositorySystemSessionAutoConfiguration(userHome).apply(session, - this.repositorySystem); + SystemProperties.doWithSystemProperties(new Runnable() { + @Override + public void run() { + new SettingsXmlRepositorySystemSessionAutoConfiguration() + .apply(session, + SettingsXmlRepositorySystemSessionAutoConfigurationTests.this.repositorySystem); + } + }, "user.home:" + userHome); RemoteRepository repository = new RemoteRepository.Builder("my-server", "default", "http://maven.example.com").build(); @@ -151,4 +160,5 @@ public class SettingsXmlRepositorySystemSessionAutoConfigurationTests { assertEquals("tester", authenticationContext.get(AuthenticationContext.USERNAME)); assertEquals("secret", authenticationContext.get(AuthenticationContext.PASSWORD)); } + } diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/util/SystemProperties.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/util/SystemProperties.java new file mode 100644 index 00000000000..217691b4c73 --- /dev/null +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/util/SystemProperties.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-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.boot.cli.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Utilities for working with System properties in unit tests + * + * @author Andy Wilkinson + */ +public class SystemProperties { + + /** + * Performs the given {@code action} with the given system properties set. System + * properties are restored to their previous values once the action has run. + * + * @param action The action to perform + * @param systemPropertyPairs The system properties, each in the form + * {@code key:value} + */ + public static void doWithSystemProperties(Runnable action, + String... systemPropertyPairs) { + Map originalValues = new HashMap(); + for (String pair : systemPropertyPairs) { + String[] components = pair.split(":"); + String key = components[0]; + String value = components[1]; + originalValues.put(key, System.setProperty(key, value)); + } + try { + action.run(); + } + finally { + for (Entry entry : originalValues.entrySet()) { + if (entry.getValue() == null) { + System.clearProperty(entry.getKey()); + } + else { + System.setProperty(entry.getKey(), entry.getValue()); + } + } + } + } +} diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-cli.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-cli.adoc index 9473a3221fb..a077f7f5b4f 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-cli.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-cli.adoc @@ -490,6 +490,24 @@ the top level, or you can put the beans DSL in a separate file if you prefer. +[[cli-maven-settings]] +== Configuring the CLI with settings.xml +The Spring Boot CLI uses Aether, Maven's dependency resolution engine, to resolve +dependencies. The CLI makes use of the Maven configuration found in `~/.m2/settings.xml` +to configure Aether. The following configuration settings are honored by the CLI: + + * Offline + * Mirrors + * Servers + * Proxies + * Profiles + ** Activation + ** Repositories + * Active profiles + +Please refer to https://maven.apache.org/settings.html[Maven's settings documentation] for +further information. + [[cli-whats-next]] == What to read next There are some {github-code}/spring-boot-cli/samples[sample groovy