/* * Copyright 2007 Alin Dreghiciu. * Copyright (C) 2014 Guillaume Nodet * * 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.ops4j.pax.url.mvn.internal.config; import java.io.File; import java.net.Authenticator; import java.net.MalformedURLException; import java.net.PasswordAuthentication; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.maven.settings.Profile; import org.apache.maven.settings.Repository; import org.apache.maven.settings.Settings; import org.apache.maven.settings.building.DefaultSettingsBuilder; 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.building.SettingsBuildingResult; import org.ops4j.lang.NullArgumentException; import org.ops4j.pax.url.mvn.ServiceConstants; import org.ops4j.util.property.PropertyResolver; import org.ops4j.util.property.PropertyStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Service Configuration implementation. * * @author Alin Dreghiciu * @author Guillaume Nodet * @see MavenConfiguration * @since August 11, 2007 */ public class MavenConfigurationImpl extends PropertyStore implements MavenConfiguration { /** * Logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(MavenConfigurationImpl.class); /** * The character that should be the first character in repositories property in order to be * appended with the repositories from settings.xml. */ private final static String REPOSITORIES_APPEND_SIGN = "+"; /** * Repositories separator. */ private final static String REPOSITORIES_SEPARATOR = ","; /** * Use a default timeout of 5 seconds. */ private final static String DEFAULT_TIMEOUT = "5000"; /** * Configuration PID. Cannot be null or empty. */ private final String m_pid; /** * Property resolver. Cannot be null. */ private final PropertyResolver m_propertyResolver; private Settings settings; /** * Creates a new service configuration. * * @param propertyResolver * propertyResolver used to resolve properties; mandatory * @param pid * configuration PID; mandatory */ public MavenConfigurationImpl(final PropertyResolver propertyResolver, final String pid) { NullArgumentException.validateNotNull(propertyResolver, "Property resolver"); m_pid = pid == null ? "" : pid + "."; m_propertyResolver = propertyResolver; settings = buildSettings(getLocalRepoPath(propertyResolver), getSettingsPath(), useFallbackRepositories()); } @Override public PropertyResolver getPropertyResolver() { return m_propertyResolver; } public boolean isValid() { return m_propertyResolver.get(m_pid + ServiceConstants.REQUIRE_CONFIG_ADMIN_CONFIG) == null; } /** * @see MavenConfiguration#isOffline() */ public boolean isOffline() { if (!contains(m_pid + ServiceConstants.PROPERTY_OFFLINE)) { return set( m_pid + ServiceConstants.PROPERTY_OFFLINE, Boolean.valueOf(m_propertyResolver.get(m_pid + ServiceConstants.PROPERTY_OFFLINE))); } return get(m_pid + ServiceConstants.PROPERTY_OFFLINE); } /** * @see MavenConfiguration#getCertificateCheck() */ public Boolean getCertificateCheck() { if (!contains(m_pid + ServiceConstants.PROPERTY_CERTIFICATE_CHECK)) { return set( m_pid + ServiceConstants.PROPERTY_CERTIFICATE_CHECK, Boolean.valueOf(m_propertyResolver.get(m_pid + ServiceConstants.PROPERTY_CERTIFICATE_CHECK))); } return get(m_pid + ServiceConstants.PROPERTY_CERTIFICATE_CHECK); } /** * Returns the URL of settings file. Will try first to use the url as is. If a malformed url * encountered then will try to use the url as a file path. If still not valid will throw the * original Malformed URL exception. * * @see MavenConfiguration#getSettingsFileUrl() */ public URL getSettingsFileUrl() { if (!contains(m_pid + ServiceConstants.PROPERTY_SETTINGS_FILE)) { String spec = m_propertyResolver.get(m_pid + ServiceConstants.PROPERTY_SETTINGS_FILE); if (spec == null) { spec = safeGetFile(System.getProperty("user.home") + "/.m2/settings.xml"); } if (spec == null) { spec = safeGetFile(System.getProperty("maven.home") + "/conf/settings.xml"); } if (spec == null) { spec = safeGetFile(System.getenv("M2_HOME") + "/conf/settings.xml"); } if (spec != null) { try { return set(m_pid + ServiceConstants.PROPERTY_SETTINGS_FILE, new URL(spec)); } catch (MalformedURLException e) { File file = new File(spec); if (file.exists()) { try { return set(m_pid + ServiceConstants.PROPERTY_SETTINGS_FILE, file.toURI() .toURL()); } catch (MalformedURLException ignore) { // ignore as it usually should not happen since we already have a file } } else { LOGGER .warn("Settings file [" + spec + "] cannot be used and will be skipped (malformed url or file does not exist)"); set(m_pid + ServiceConstants.PROPERTY_SETTINGS_FILE, null); } } } } return get(m_pid + ServiceConstants.PROPERTY_SETTINGS_FILE); } private String safeGetFile(String path) { if (path != null) { File file = new File(path); if (file.exists() && file.canRead() && file.isFile()) { try { return file.toURI().toURL().toExternalForm(); } catch (MalformedURLException e) { // Ignore } } } return null; } /** * Repository is a comma separated list of repositories to be used. If repository acces requests * authentication the user name and password must be specified in the repository url as for * example http://user:password@repository.ops4j.org/maven2.<br/> * If the repository from 1/2 bellow starts with a plus (+) the option 3 is also used and the * repositories from settings.xml will be cummulated.<br/> * Repository resolution:<br/> * 1. looks for a configuration property named repository;<br/> * 2. looks for a framework property/system setting repository;<br/> * 3. looks in settings.xml (see settings.xml resolution). in this case all configured * repositories will be used including configured user/password. In this case the central * repository is also added. Note that the local repository is added as the first repository if * exists. * * @see MavenConfiguration#getRepositories() * @see MavenConfiguration#getLocalRepository() */ public List<MavenRepositoryURL> getDefaultRepositories() throws MalformedURLException { if (!contains(m_pid + ServiceConstants.PROPERTY_DEFAULT_REPOSITORIES)) { // look for repositories property String defaultRepositoriesProp = m_propertyResolver.get(m_pid + ServiceConstants.PROPERTY_DEFAULT_REPOSITORIES); // build repositories list final List<MavenRepositoryURL> defaultRepositoriesProperty = new ArrayList<MavenRepositoryURL>(); if (defaultRepositoriesProp != null && defaultRepositoriesProp.trim().length() > 0) { String[] repositories = defaultRepositoriesProp.split(REPOSITORIES_SEPARATOR); for (String repositoryURL : repositories) { defaultRepositoriesProperty.add(new MavenRepositoryURL(repositoryURL.trim())); } } LOGGER.trace("Using default repositories [" + defaultRepositoriesProperty + "]"); return set(m_pid + ServiceConstants.PROPERTY_DEFAULT_REPOSITORIES, defaultRepositoriesProperty); } return get(m_pid + ServiceConstants.PROPERTY_DEFAULT_REPOSITORIES); } /** * Repository is a comma separated list of repositories to be used. If repository access requests * authentication the user name and password must be specified in the repository url as for * example http://user:password@repository.ops4j.org/maven2.<br/> * If the repository from 1/2 bellow starts with a plus (+) the option 3 is also used and the * repositories from settings.xml will be cummulated.<br/> * Repository resolution:<br/> * 1. looks for a configuration property named repository;<br/> * 2. looks for a framework property/system setting repository;<br/> * 3. looks in settings.xml (see settings.xml resolution). in this case all configured * repositories will be used including configured user/password. In this case the central * repository is also added. Note that the local repository is added as the first repository if * exists. * * @see MavenConfiguration#getRepositories() * @see MavenConfiguration#getLocalRepository() */ public List<MavenRepositoryURL> getRepositories() throws MalformedURLException { if (!contains(m_pid + ServiceConstants.PROPERTY_REPOSITORIES)) { // look for repositories property String repositoriesProp = m_propertyResolver.get(m_pid + ServiceConstants.PROPERTY_REPOSITORIES); // if not set or starting with a plus (+) get repositories from settings xml if ((repositoriesProp == null || repositoriesProp.startsWith(REPOSITORIES_APPEND_SIGN)) && settings != null) { String init = (repositoriesProp == null) ? "" : repositoriesProp.substring(1); StringBuilder builder = new StringBuilder(init); Map<String, Profile> profiles = settings.getProfilesAsMap(); for (String activeProfile : getActiveProfiles(true)) { Profile profile = profiles.get(activeProfile); if (profile == null) { continue; } for (org.apache.maven.settings.Repository repo : profile.getRepositories()) { if (builder.length() > 0) { builder.append(REPOSITORIES_SEPARATOR); } builder.append(repo.getUrl()); builder.append(ServiceConstants.SEPARATOR_OPTIONS); builder.append(ServiceConstants.OPTION_ID); builder.append("="); builder.append(repo.getId()); if (repo.getReleases() != null) { if (!repo.getReleases().isEnabled()) { builder.append(ServiceConstants.SEPARATOR_OPTIONS); builder.append(ServiceConstants.OPTION_DISALLOW_RELEASES); } addPolicy(builder, repo.getReleases().getUpdatePolicy(), ServiceConstants.OPTION_RELEASES_UPDATE); addPolicy(builder, repo.getReleases().getChecksumPolicy(), ServiceConstants.OPTION_RELEASES_CHECKSUM); } if (repo.getSnapshots() != null) { if (repo.getSnapshots().isEnabled()) { builder.append(ServiceConstants.SEPARATOR_OPTIONS); builder.append(ServiceConstants.OPTION_ALLOW_SNAPSHOTS); } addPolicy(builder, repo.getSnapshots().getUpdatePolicy(), ServiceConstants.OPTION_SNAPSHOTS_UPDATE); addPolicy(builder, repo.getSnapshots().getChecksumPolicy(), ServiceConstants.OPTION_SNAPSHOTS_CHECKSUM); } } } repositoriesProp = builder.toString(); } // build repositories list final List<MavenRepositoryURL> repositoriesProperty = new ArrayList<MavenRepositoryURL>(); if (m_propertyResolver.get(m_pid + ServiceConstants.PROPERTY_LOCAL_REPO_AS_REMOTE) != null) { MavenRepositoryURL localRepository = getDefaultLocalRepository(); if (localRepository != null) { repositoriesProperty.add(localRepository); } } if (repositoriesProp != null && repositoriesProp.trim().length() > 0) { String[] repositories = repositoriesProp.split(REPOSITORIES_SEPARATOR); for (String repositoryURL : repositories) { repositoriesProperty.add(new MavenRepositoryURL(repositoryURL.trim())); } } LOGGER.trace("Using remote repositories [" + repositoriesProperty + "]"); return set(m_pid + ServiceConstants.PROPERTY_REPOSITORIES, repositoriesProperty); } return get(m_pid + ServiceConstants.PROPERTY_REPOSITORIES); } /** * Returns active profile names from current settings * @param alsoActiveByDefault if <code>true</code>, also return these profile names that are * <code><activeByDefault></code> * @return */ private Collection<String> getActiveProfiles(boolean alsoActiveByDefault) { Set<String> profileNames = new LinkedHashSet<String>(settings.getActiveProfiles()); if (alsoActiveByDefault) { for (Profile profile : settings.getProfiles()) { if (profile.getActivation() != null && profile.getActivation().isActiveByDefault()) { // TODO: check other activations - file/jdk/os/property? profileNames.add(profile.getId()); } } } return profileNames; } private void addPolicy(StringBuilder builder, String policy, String option) { if (policy != null && !policy.isEmpty()) { builder.append(ServiceConstants.SEPARATOR_OPTIONS); builder.append(option); builder.append("="); builder.append(policy); } } public String getGlobalUpdatePolicy() { final String propertyName = m_pid + ServiceConstants.PROPERTY_GLOBAL_UPDATE_POLICY; if (contains(propertyName)) { return get(propertyName); } final String propertyValue = m_propertyResolver.get(propertyName); if (propertyValue != null) { set(propertyName, propertyValue); return propertyValue; } return null; } public String getGlobalChecksumPolicy() { final String propertyName = m_pid + ServiceConstants.PROPERTY_GLOBAL_CHECKSUM_POLICY; if (contains(propertyName)) { return get(propertyName); } final String propertyValue = m_propertyResolver.get(propertyName); if (propertyValue != null) { set(propertyName, propertyValue); return propertyValue; } return null; } /** * Resolves local repository directory by using the following resolution:<br/> * 1. looks for a configuration property named localRepository; 2. looks for a framework * property/system setting localRepository;<br/> * 3. looks in settings.xml (see settings.xml resolution);<br/> * 4. falls back to ${user.home}/.m2/repository. * * @see MavenConfiguration#getLocalRepository() */ public MavenRepositoryURL getLocalRepository() { if (!contains(m_pid + ServiceConstants.PROPERTY_LOCAL_REPOSITORY)) { // look for a local repository property String spec = m_propertyResolver.get(m_pid + ServiceConstants.PROPERTY_LOCAL_REPOSITORY); // if not set get local repository from maven settings if (spec == null && settings != null) { spec = settings.getLocalRepository(); } if (spec == null) { spec = System.getProperty("user.home") + "/.m2/repository"; } if (!spec.toLowerCase().contains("@snapshots")) { spec += "@snapshots"; } spec += "@id=local"; // check if we have a valid url try { return set(m_pid + ServiceConstants.PROPERTY_LOCAL_REPOSITORY, new MavenRepositoryURL(spec)); } catch (MalformedURLException e) { // maybe is just a file? try { return set(m_pid + ServiceConstants.PROPERTY_LOCAL_REPOSITORY, new MavenRepositoryURL(new File(spec).toURI().toASCIIString())); } catch (MalformedURLException ignore) { LOGGER.warn("Local repository [" + spec + "] cannot be used and will be skipped"); return set(m_pid + ServiceConstants.PROPERTY_LOCAL_REPOSITORY, null); } } } return get(m_pid + ServiceConstants.PROPERTY_LOCAL_REPOSITORY); } public MavenRepositoryURL getDefaultLocalRepository() { if (settings != null) { String spec = settings.getLocalRepository(); if (spec == null) { spec = System.getProperty("user.home") + "/.m2/repository"; } if (!spec.toLowerCase().contains("@snapshots")) { spec += "@snapshots"; } spec += "@id=defaultlocal"; // check if we have a valid url try { return new MavenRepositoryURL(spec); } catch (MalformedURLException e) { // maybe is just a file? try { return new MavenRepositoryURL(new File(spec).toURI().toASCIIString()); } catch (MalformedURLException ignore) { LOGGER.warn("Local repository [" + spec + "] cannot be used and will be skipped"); return null; } } } return null; } public Integer getTimeout() { if (!contains(m_pid + ServiceConstants.PROPERTY_TIMEOUT)) { String timeout = m_propertyResolver.get(m_pid + ServiceConstants.PROPERTY_TIMEOUT); return set(m_pid + ServiceConstants.PROPERTY_TIMEOUT, Integer.valueOf(timeout == null ? DEFAULT_TIMEOUT : timeout)); } return get(m_pid + ServiceConstants.PROPERTY_TIMEOUT); } /** * {@inheritDoc} */ public Boolean useFallbackRepositories() { if (!contains(m_pid + ServiceConstants.PROPERTY_USE_FALLBACK_REPOSITORIES)) { String useFallbackRepoProp = m_propertyResolver.get(m_pid + ServiceConstants.PROPERTY_USE_FALLBACK_REPOSITORIES); return set(m_pid + ServiceConstants.PROPERTY_USE_FALLBACK_REPOSITORIES, Boolean.valueOf(useFallbackRepoProp == null ? "true" : useFallbackRepoProp)); } return get(m_pid + ServiceConstants.PROPERTY_USE_FALLBACK_REPOSITORIES); } /** * Enables the proxy server for a given URL. * * @deprecated This method has side-effects and is only used in the "old" resolver. */ public void enableProxy(URL url) { final String protocol = url.getProtocol(); Map<String, String> proxyDetails = getProxySettings(url.getProtocol()).get(protocol); if (proxyDetails != null) { LOGGER.trace("Enabling proxy [" + proxyDetails + "]"); final String user = proxyDetails.get("user"); final String pass = proxyDetails.get("pass"); Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(user, pass.toCharArray()); } }); System.setProperty(protocol + ".proxyHost", proxyDetails.get("host")); System.setProperty(protocol + ".proxyPort", proxyDetails.get("port")); System.setProperty(protocol + ".nonProxyHosts", proxyDetails.get("nonProxyHosts")); set(m_pid + ServiceConstants.PROPERTY_PROXY_SUPPORT, protocol); } } private boolean isProtocolSupportEnabled(String... protocols) { final String proxySupport = m_propertyResolver.get(m_pid + ServiceConstants.PROPERTY_PROXY_SUPPORT); if (proxySupport == null) { return ServiceConstants.PROPERTY_PROXY_SUPPORT_DEFAULT; } // simple cases: if ("true".equalsIgnoreCase(proxySupport)) { return true; } if ("false".equalsIgnoreCase(proxySupport)) { return false; } // giving no protocols to test against, default to true. if (protocols.length == 0) { return true; } // differentiate by protocol: for (String protocol : protocols) { if (proxySupport.contains(protocol)) { return true; } } // not in list appearingly. return false; } public Map<String, Map<String, String>> getProxySettings(String... protocols) { Map<String, Map<String, String>> pr = new HashMap<String, Map<String, String>>(); if (isProtocolSupportEnabled(protocols)) { parseSystemWideProxySettings(pr); parseProxiesFromProperty( m_propertyResolver.get(m_pid + ServiceConstants.PROPERTY_PROXIES), pr); // if( pr.isEmpty() ) { // if( m_settings == null ) { return Collections.emptyMap(); } // // return m_settings.getProxySettings(); // } } return pr; } private void parseSystemWideProxySettings(Map<String, Map<String, String>> pr) { String httpHost = m_propertyResolver.get("http.proxyHost"); String httpPort = m_propertyResolver.get("http.proxyPort"); String httpnonProxyHosts = m_propertyResolver.get("http.nonProxyHosts"); if (httpHost != null) { parseProxiesFromProperty("http:host=" + httpHost + ",port=" + httpPort + ",nonProxyHosts=" + httpnonProxyHosts, pr); } } // example: http:host=foo,port=8080;https:host=bar,port=9090 private void parseProxiesFromProperty(String proxySettings, Map<String, Map<String, String>> pr) { // TODO maybe make the parsing more clever via regex ;) Or not. try { if (proxySettings != null) { String[] protocols = proxySettings.split(";"); for (String protocolSection : protocols) { String[] section = protocolSection.split(":"); String protocolName = section[0]; Map<String, String> keyvalue = new HashMap<String, String>(); // set some defaults: keyvalue.put("protocol", protocolName); keyvalue.put("nonProxyHosts", ""); keyvalue.put("host", "localhost"); keyvalue.put("port", "80"); for (String keyvalueList : section[1].split(",")) { String[] kv = keyvalueList.split("="); String key = kv[0]; String value = kv[1]; keyvalue.put(key, value); } pr.put(protocolName, keyvalue); } } } catch (ArrayIndexOutOfBoundsException ex) { throw new IllegalArgumentException( "Proxy setting is set to " + proxySettings + ". But it should have this format: <protocol>:<key>=<value>,<key=value>;protocol:<key>=<value>,.."); } } private String getSettingsPath() { URL url = getSettingsFileUrl(); return url == null ? null : url.getPath(); } private String getLocalRepoPath(PropertyResolver props) { return props.get(ServiceConstants.PID + "." + ServiceConstants.PROPERTY_LOCAL_REPOSITORY); } private Settings buildSettings(String localRepoPath, String settingsPath, boolean useFallbackRepositories) { Settings settings; if (settingsPath == null) { settings = new Settings(); } else { DefaultSettingsBuilderFactory factory = new DefaultSettingsBuilderFactory(); DefaultSettingsBuilder builder = factory.newInstance(); SettingsBuildingRequest request = new DefaultSettingsBuildingRequest(); request.setUserSettingsFile(new File(settingsPath)); try { SettingsBuildingResult result = builder.build(request); settings = result.getEffectiveSettings(); } catch (SettingsBuildingException exc) { throw new AssertionError("cannot build settings", exc); } } if (useFallbackRepositories) { Profile fallbackProfile = new Profile(); Repository central = new Repository(); central.setId("central"); central.setUrl("http://repo1.maven.org/maven2"); fallbackProfile.setId("fallback"); fallbackProfile.setRepositories(Arrays.asList(central)); settings.addProfile(fallbackProfile); settings.addActiveProfile("fallback"); } if (localRepoPath != null) { settings.setLocalRepository(localRepoPath); } return settings; } public Map<String, Map<String, String>> getMirrors() { // DO support mirrors via properties (just like we do for proxies. // if( m_settings == null ) { return Collections.emptyMap(); } // return m_settings.getMirrorSettings(); return Collections.emptyMap(); } public Settings getSettings() { return settings; } public void setSettings(Settings settings) { this.settings = settings; } public String getSecuritySettings() { String key = m_pid + ServiceConstants.PROPERTY_SECURITY; if (!contains(key)) { String spec = m_propertyResolver.get(key); if (spec == null) { spec = new File(System.getProperty("user.home"), ".m2/settings-security.xml") .getPath(); } return set(key, spec); } return get(key); } @Override public <T> T getProperty(String name, T defaultValue, Class<T> clazz) { if (!contains(m_pid + name)) { String value = m_propertyResolver.get(m_pid + name); return set(m_pid + name, value == null ? defaultValue : convert(value, clazz)); } return get(m_pid + name); } @Override public String getPid() { return m_pid; } /** * Supports String to [ Integer, Long, String, Boolean ] conversion * @param value * @param clazz * @param <T> * @return */ @SuppressWarnings("unchecked") private <T> T convert(String value, Class<T> clazz) { if (String.class == clazz) { return (T) value; } if (Integer.class == clazz) { return (T) Integer.valueOf(value); } if (Long.class == clazz) { return (T) Long.valueOf(value); } if (Boolean.class == clazz) { return (T) Boolean.valueOf("true".equals(value)); } throw new IllegalArgumentException("Can't convert \"" + value + "\" to " + clazz + "."); } }