org.ops4j.pax.url.mvn.internal.AetherBasedResolver.java Source code

Java tutorial

Introduction

Here is the source code for org.ops4j.pax.url.mvn.internal.AetherBasedResolver.java

Source

/*
 * Copyright (C) 2010 Toni Menzel
 * 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;

import static org.eclipse.aether.repository.RepositoryPolicy.CHECKSUM_POLICY_FAIL;
import static org.eclipse.aether.repository.RepositoryPolicy.CHECKSUM_POLICY_IGNORE;
import static org.eclipse.aether.repository.RepositoryPolicy.CHECKSUM_POLICY_WARN;
import static org.eclipse.aether.repository.RepositoryPolicy.UPDATE_POLICY_ALWAYS;
import static org.eclipse.aether.repository.RepositoryPolicy.UPDATE_POLICY_DAILY;
import static org.eclipse.aether.repository.RepositoryPolicy.UPDATE_POLICY_INTERVAL;
import static org.eclipse.aether.repository.RepositoryPolicy.UPDATE_POLICY_NEVER;
import static org.ops4j.pax.url.mvn.internal.Parser.VERSION_LATEST;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.NoRouteToHostException;
import java.net.SocketTimeoutException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentMap;

import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.maven.artifact.repository.metadata.SnapshotVersion;
import org.apache.maven.artifact.repository.metadata.Versioning;
import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.apache.maven.settings.Mirror;
import org.apache.maven.settings.Server;
import org.apache.maven.settings.Settings;
import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
import org.apache.maven.settings.crypto.SettingsDecrypter;
import org.apache.maven.settings.crypto.SettingsDecryptionRequest;
import org.apache.maven.settings.crypto.SettingsDecryptionResult;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.aether.ConfigurationProperties;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositoryException;
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.installation.InstallRequest;
import org.eclipse.aether.internal.impl.PaxLocalRepositoryManager;
import org.eclipse.aether.internal.impl.slf4j.Slf4jLoggerFactory;
import org.eclipse.aether.metadata.DefaultMetadata;
import org.eclipse.aether.metadata.Metadata;
import org.eclipse.aether.repository.Authentication;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.MirrorSelector;
import org.eclipse.aether.repository.Proxy;
import org.eclipse.aether.repository.ProxySelector;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.MetadataRequest;
import org.eclipse.aether.resolution.MetadataResult;
import org.eclipse.aether.resolution.VersionRangeRequest;
import org.eclipse.aether.resolution.VersionRangeResolutionException;
import org.eclipse.aether.resolution.VersionRangeResult;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory;
import org.eclipse.aether.transfer.ArtifactNotFoundException;
import org.eclipse.aether.transfer.ArtifactTransferException;
import org.eclipse.aether.transfer.MetadataNotFoundException;
import org.eclipse.aether.transfer.MetadataTransferException;
import org.eclipse.aether.transport.wagon.WagonProvider;
import org.eclipse.aether.transport.wagon.WagonTransporterFactory;
import org.eclipse.aether.util.repository.AuthenticationBuilder;
import org.eclipse.aether.util.repository.DefaultMirrorSelector;
import org.eclipse.aether.util.repository.DefaultProxySelector;
import org.eclipse.aether.util.version.GenericVersionScheme;
import org.eclipse.aether.version.InvalidVersionSpecificationException;
import org.eclipse.aether.version.Version;
import org.eclipse.aether.version.VersionConstraint;
import org.ops4j.lang.NullArgumentException;
import org.ops4j.pax.url.mvn.MavenResolver;
import org.ops4j.pax.url.mvn.MirrorInfo;
import org.ops4j.pax.url.mvn.ServiceConstants;
import org.ops4j.pax.url.mvn.internal.config.MavenConfiguration;
import org.ops4j.pax.url.mvn.internal.config.MavenRepositoryURL;
import org.slf4j.LoggerFactory;
import org.sonatype.plexus.components.cipher.DefaultPlexusCipher;
import org.sonatype.plexus.components.cipher.PlexusCipherException;

/**
 * Aether based, drop in replacement for mvn protocol
 */
public class AetherBasedResolver implements MavenResolver {

    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(AetherBasedResolver.class);
    private static final String LATEST_VERSION_RANGE = "(0.0,]";
    private static final String REPO_TYPE = "default";
    private static final String SCHEMA_HTTP = "http";
    private static final String SCHEMA_HTTPS = "https";
    private static final String PROXY_HOST = "proxyHost";
    private static final String PROXY_PORT = "proxyPort";
    private static final String PROXY_USER = "proxyUser";
    private static final String PROXY_PASSWORD = "proxyPassword";
    private static final String NON_PROXY_HOSTS = "nonProxyHosts";

    final private RepositorySystem m_repoSystem;
    final private MavenConfiguration m_config;
    final private MirrorSelector m_mirrorSelector;
    final private ProxySelector m_proxySelector;
    final private CloseableHttpClient m_client;
    private Settings m_settings;
    private ConfigurableSettingsDecrypter decrypter;

    private LocalRepository localRepository;
    private final ConcurrentMap<LocalRepository, Deque<RepositorySystemSession>> sessions = new ConcurrentHashMap<LocalRepository, Deque<RepositorySystemSession>>();

    /**
     * Create a AetherBasedResolver
     *
     * @param configuration (must be not null)
     */
    public AetherBasedResolver(final MavenConfiguration configuration) {
        this(configuration, null);
    }

    /**
     * Create a AetherBasedResolver
     *
     * @param configuration (must be not null)
     */
    public AetherBasedResolver(final MavenConfiguration configuration, final MirrorInfo mirror) {
        NullArgumentException.validateNotNull(configuration, "Maven configuration");
        m_client = HttpClients.createClient(configuration.getPropertyResolver(), configuration.getPid());
        m_config = configuration;
        m_settings = configuration.getSettings();
        m_repoSystem = newRepositorySystem();
        decryptSettings();
        m_proxySelector = selectProxies();
        m_mirrorSelector = selectMirrors(mirror);
    }

    @Override
    public void close() throws IOException {
        m_client.close();
    }

    private void decryptSettings() {
        SettingsDecryptionRequest request = new DefaultSettingsDecryptionRequest(m_settings);
        SettingsDecryptionResult result = decrypter.decrypt(request);
        m_settings.setProxies(result.getProxies());
        m_settings.setServers(result.getServers());
    }

    private void assignProxyAndMirrors(List<RemoteRepository> remoteRepos) {
        Map<String, List<String>> map = new HashMap<String, List<String>>();
        Map<String, RemoteRepository> naming = new HashMap<String, RemoteRepository>();
        boolean aggregateReleaseEnabled = false, aggregateSnapshotEnabled = false;
        String aggregateReleaseUpdateInterval = null, aggregateSnapshotUpdateInterval = null;
        String aggregateReleaseChecksumPolicy = null, aggregateSnapshotChecksumPolicy = null;

        List<RemoteRepository> resultingRepos = new ArrayList<RemoteRepository>();

        for (RemoteRepository r : remoteRepos) {
            naming.put(r.getId(), r);

            RemoteRepository rProxy = new RemoteRepository.Builder(r).setProxy(m_proxySelector.getProxy(r)).build();
            resultingRepos.add(rProxy);

            RemoteRepository mirror = m_mirrorSelector.getMirror(r);
            if (mirror != null) {
                String key = mirror.getId();
                naming.put(key, mirror);
                if (!map.containsKey(key)) {
                    map.put(key, new ArrayList<String>());
                }
                List<String> mirrored = map.get(key);
                mirrored.add(r.getId());

                // Aggregate policy settings of the mirror repos.
                aggregateReleaseEnabled |= r.getPolicy(false).isEnabled();
                aggregateSnapshotEnabled |= r.getPolicy(true).isEnabled();
                aggregateReleaseUpdateInterval = minUpdateInterval(aggregateReleaseUpdateInterval,
                        r.getPolicy(false).getUpdatePolicy());
                aggregateSnapshotUpdateInterval = minUpdateInterval(aggregateSnapshotUpdateInterval,
                        r.getPolicy(true).getUpdatePolicy());
                aggregateReleaseChecksumPolicy = aggregateChecksumPolicy(aggregateReleaseChecksumPolicy,
                        r.getPolicy(false).getChecksumPolicy());
                aggregateSnapshotChecksumPolicy = aggregateChecksumPolicy(aggregateSnapshotChecksumPolicy,
                        r.getPolicy(true).getChecksumPolicy());
            }
        }

        for (String mirrorId : map.keySet()) {
            RemoteRepository mirror = naming.get(mirrorId);
            List<RemoteRepository> mirroredRepos = new ArrayList<RemoteRepository>();

            for (String rep : map.get(mirrorId)) {
                mirroredRepos.add(naming.get(rep));
            }
            RepositoryPolicy releasePolicy = new RepositoryPolicy(aggregateReleaseEnabled,
                    aggregateReleaseUpdateInterval, aggregateReleaseChecksumPolicy);
            RepositoryPolicy snapshotPolicy = new RepositoryPolicy(aggregateSnapshotEnabled,
                    aggregateSnapshotUpdateInterval, aggregateSnapshotChecksumPolicy);
            mirror = new RemoteRepository.Builder(mirror).setMirroredRepositories(mirroredRepos)
                    .setProxy(m_proxySelector.getProxy(mirror)).setReleasePolicy(releasePolicy)
                    .setSnapshotPolicy(snapshotPolicy).build();
            resultingRepos.removeAll(mirroredRepos);
            resultingRepos.add(0, mirror);
        }

        remoteRepos.clear();
        remoteRepos.addAll(resultingRepos);
    }

    private String minUpdateInterval(String interval1, String interval2) {
        LOG.debug("interval1: {}, interval2: {}", interval1, interval2);
        if (interval1 == null) {
            return interval2;
        } else if (interval2 == null) {
            return interval1;
        }

        int interval1InMin = getIntervalInMinutes(interval1);
        int interval2InMin = getIntervalInMinutes(interval2);
        if (interval1InMin <= interval2InMin) {
            return getUpdatePolicyInterval(interval1InMin);
        } else {
            return getUpdatePolicyInterval(interval2InMin);
        }
    }

    private int getIntervalInMinutes(String interval) {
        int intervalInMin;
        if (interval.equals(UPDATE_POLICY_NEVER)) {
            intervalInMin = Integer.MAX_VALUE;
        } else if (interval.equals(UPDATE_POLICY_DAILY)) {
            intervalInMin = 24 * 60;
        } else if (interval.equals(UPDATE_POLICY_ALWAYS)) {
            intervalInMin = Integer.MIN_VALUE;
        } else if (interval.startsWith(UPDATE_POLICY_INTERVAL + ":")) {
            try {
                intervalInMin = Integer.parseInt(interval.substring(UPDATE_POLICY_INTERVAL.length() + 1));
            } catch (NumberFormatException e) {
                LOG.warn("unable to parse update policy interval: \"{}\"", interval);
                intervalInMin = 24 * 60;
            }
        } else {
            throw new IllegalArgumentException(String.format("Invalid update policy \"%s\"", interval));
        }
        return intervalInMin;
    }

    private String getUpdatePolicyInterval(int intervalInMin) {
        switch (intervalInMin) {
        case Integer.MAX_VALUE:
            return UPDATE_POLICY_NEVER;
        case Integer.MIN_VALUE:
            return UPDATE_POLICY_ALWAYS;
        case 24 * 60:
            return UPDATE_POLICY_DAILY;
        default:
            return String.format("%s:%d", UPDATE_POLICY_INTERVAL, intervalInMin);
        }
    }

    private String aggregateChecksumPolicy(String policy1, String policy2) {
        if (policy1 == null) {
            return policy2;
        }
        if (policy2 == null) {
            return policy1;
        }
        if (policy1.equals(CHECKSUM_POLICY_FAIL) || policy2.equals(CHECKSUM_POLICY_FAIL)) {
            return CHECKSUM_POLICY_FAIL;
        } else if (policy1.equals(CHECKSUM_POLICY_WARN) || policy2.equals(CHECKSUM_POLICY_WARN)) {
            return CHECKSUM_POLICY_WARN;
        } else {
            return CHECKSUM_POLICY_IGNORE;
        }
    }

    private ProxySelector selectProxies() {
        DefaultProxySelector proxySelector = new DefaultProxySelector();
        for (org.apache.maven.settings.Proxy proxy : m_settings.getProxies()) {
            if (!proxy.isActive()) {
                continue;
            }
            String nonProxyHosts = proxy.getNonProxyHosts();
            Proxy proxyObj = new Proxy(proxy.getProtocol(), proxy.getHost(), proxy.getPort(),
                    getAuthentication(proxy));
            proxySelector.add(proxyObj, nonProxyHosts);
        }

        if (m_settings.getProxies().size() == 0) {
            javaDefaultProxy(proxySelector);
        }
        return proxySelector;
    }

    private void javaDefaultProxy(DefaultProxySelector proxySelector) {
        // Prefer https
        String proxyHost = System.getProperty(SCHEMA_HTTPS + "." + PROXY_HOST);
        String schema = (proxyHost != null) ? SCHEMA_HTTPS : SCHEMA_HTTP;
        if (proxyHost == null) {
            proxyHost = System.getProperty(schema + "." + PROXY_HOST);
        }
        if (proxyHost == null) {
            return;
        }

        String proxyUser = System.getProperty(schema + "." + PROXY_USER);
        String proxyPassword = System.getProperty(schema + "." + PROXY_PASSWORD);
        int proxyPort = Integer.parseInt(System.getProperty(schema + "." + PROXY_PORT, "8080"));
        String nonProxyHosts = System.getProperty(schema + "." + NON_PROXY_HOSTS);

        Authentication authentication = createAuthentication(proxyUser, proxyPassword);
        Proxy proxyObj = new Proxy(schema, proxyHost, proxyPort, authentication);
        proxySelector.add(proxyObj, nonProxyHosts);
    }

    private Authentication createAuthentication(String proxyUser, String proxyPassword) {
        Authentication authentication = null;
        if (proxyUser != null) {
            authentication = new AuthenticationBuilder().addUsername(proxyUser).addPassword(proxyPassword).build();
        }
        return authentication;
    }

    private MirrorSelector selectMirrors(MirrorInfo mirror) {
        // configure mirror

        // The class org.eclipse.aether.util.repository.DefaultMirrorSelector is final therefore it needs to be
        // wrapped to fix PAXURL-289.
        class DefaultMirrorSelectorWrapper implements MirrorSelector {
            final DefaultMirrorSelector delegate = new DefaultMirrorSelector();
            final Map<String, Authentication> authMap = new HashMap<String, Authentication>();

            @Override
            public RemoteRepository getMirror(RemoteRepository repository) {
                RemoteRepository repo = delegate.getMirror(repository);
                if (repo != null) {
                    Authentication mirrorAuth = authMap.get(repo.getId());
                    if (mirrorAuth != null) {
                        RemoteRepository.Builder builder = new RemoteRepository.Builder(repo);
                        repo = builder.setAuthentication(mirrorAuth).build();
                    }
                }
                return repo;
            }

            public DefaultMirrorSelector add(String id, String url, String type, boolean repositoryManager,
                    String mirrorOfIds, String mirrorOfTypes, Authentication authentication) {
                LOG.trace("adding mirror {} auth = {}", id, authentication != null);
                if (authentication != null) {
                    authMap.put(id, authentication);
                }
                return delegate.add(id, url, type, repositoryManager, mirrorOfIds, mirrorOfTypes);
            }
        }

        DefaultMirrorSelectorWrapper selector = new DefaultMirrorSelectorWrapper();
        for (Mirror m : m_settings.getMirrors()) {
            selector.add(m.getId(), m.getUrl(), null, false, m.getMirrorOf(), "*", getAuthentication(m.getId()));
        }
        if (mirror != null) {
            selector.add(mirror.getId(), mirror.getUrl(), null, false, mirror.getMirrorOf(), "*",
                    getAuthentication(mirror.getId()));
        }
        return selector;
    }

    private List<RemoteRepository> selectRepositories() {
        List<RemoteRepository> list = new ArrayList<RemoteRepository>();
        List<MavenRepositoryURL> urls = Collections.emptyList();
        try {
            urls = m_config.getRepositories();
        } catch (MalformedURLException exc) {
            LOG.error("invalid repository URLs", exc);
        }
        for (MavenRepositoryURL r : urls) {
            if (r.isMulti()) {
                addSubDirs(list, r.getFile());
            } else {
                addRepo(list, r);
            }
        }

        return list;
    }

    List<LocalRepository> selectDefaultRepositories() {
        List<LocalRepository> list = new ArrayList<LocalRepository>();
        List<MavenRepositoryURL> urls = Collections.emptyList();
        try {
            urls = m_config.getDefaultRepositories();
        } catch (MalformedURLException exc) {
            LOG.error("invalid repository URLs", exc);
        }
        for (MavenRepositoryURL r : urls) {
            if (r.isMulti()) {
                addLocalSubDirs(list, r.getFile());
            } else {
                addLocalRepo(list, r);
            }
        }

        return list;
    }

    private void addSubDirs(List<RemoteRepository> list, File parentDir) {
        if (!parentDir.isDirectory()) {
            LOG.debug("Repository marked with @multi does not resolve to a directory: " + parentDir);
            return;
        }

        for (File repo : getSortedChildDirectories(parentDir)) {
            try {
                String repoURI = repo.toURI().toString() + "@id=" + repo.getName();
                LOG.debug("Adding repo from inside multi dir: " + repoURI);
                addRepo(list, new MavenRepositoryURL(repoURI));
            } catch (MalformedURLException e) {
                LOG.error("Error resolving repo url of a multi repo " + repo.toURI());
            }
        }
    }

    /**
     * For the given parent, we find all child files, and then
     * sort those files by their name (not absolute path).
     *
     * The sorted list is returned, or an empty list if listFiles returns
     * null.
     * @param parent A non-null parent File for which you want to get the sorted list of child directories.
     * @return The alphabetically sorted list of files, or an empty list if parent.listFiles() returns null.
     */
    private static File[] getSortedChildDirectories(File parent) {
        File[] files = parent.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.isDirectory();
            }
        });
        if (files == null) {
            return new File[0];
        }
        Arrays.sort(files, new Comparator<File>() {
            @Override
            public int compare(File o1, File o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });
        return files;
    }

    private void addRepo(List<RemoteRepository> list, MavenRepositoryURL repo) {
        String releasesUpdatePolicy = repo.getReleasesUpdatePolicy();
        if (releasesUpdatePolicy == null || releasesUpdatePolicy.isEmpty()) {
            releasesUpdatePolicy = UPDATE_POLICY_DAILY;
        }
        String releasesChecksumPolicy = repo.getReleasesChecksumPolicy();
        if (releasesChecksumPolicy == null || releasesChecksumPolicy.isEmpty()) {
            releasesChecksumPolicy = CHECKSUM_POLICY_WARN;
        }
        String snapshotsUpdatePolicy = repo.getSnapshotsUpdatePolicy();
        if (snapshotsUpdatePolicy == null || snapshotsUpdatePolicy.isEmpty()) {
            snapshotsUpdatePolicy = UPDATE_POLICY_DAILY;
        }
        String snapshotsChecksumPolicy = repo.getSnapshotsChecksumPolicy();
        if (snapshotsChecksumPolicy == null || snapshotsChecksumPolicy.isEmpty()) {
            snapshotsChecksumPolicy = CHECKSUM_POLICY_WARN;
        }
        RemoteRepository.Builder builder = new RemoteRepository.Builder(repo.getId(), REPO_TYPE,
                repo.getURL().toExternalForm());
        RepositoryPolicy releasePolicy = new RepositoryPolicy(repo.isReleasesEnabled(), releasesUpdatePolicy,
                releasesChecksumPolicy);
        builder.setReleasePolicy(releasePolicy);
        RepositoryPolicy snapshotPolicy = new RepositoryPolicy(repo.isSnapshotsEnabled(), snapshotsUpdatePolicy,
                snapshotsChecksumPolicy);
        builder.setSnapshotPolicy(snapshotPolicy);
        Authentication authentication = getAuthentication(repo.getId());
        if (authentication != null) {
            builder.setAuthentication(authentication);
        }
        list.add(builder.build());
    }

    private void addLocalSubDirs(List<LocalRepository> list, File parentDir) {
        if (!parentDir.isDirectory()) {
            LOG.debug("Repository marked with @multi does not resolve to a directory: " + parentDir);
            return;
        }
        for (File repo : getSortedChildDirectories(parentDir)) {
            try {
                String repoURI = repo.toURI().toString() + "@id=" + repo.getName();
                LOG.debug("Adding repo from inside multi dir: " + repoURI);
                addLocalRepo(list, new MavenRepositoryURL(repoURI));
            } catch (MalformedURLException e) {
                LOG.error("Error resolving repo url of a multi repo " + repo.toURI());
            }
        }
    }

    private void addLocalRepo(List<LocalRepository> list, MavenRepositoryURL repo) {
        if (repo.getFile() != null) {
            LocalRepository local = new LocalRepository(repo.getFile(), "simple");
            list.add(local);
        }
    }

    public RepositorySystem getRepositorySystem() {
        return m_repoSystem;
    }

    public List<RemoteRepository> getRepositories() {
        List<RemoteRepository> repos = selectRepositories();
        assignProxyAndMirrors(repos);
        return repos;
    }

    @Override
    public File resolve(String url) throws IOException {
        return resolve(url, null);
    }

    @Override
    public File resolve(String url, Exception previousException) throws IOException {
        if (!url.startsWith(ServiceConstants.PROTOCOL + ":")) {
            throw new IllegalArgumentException("url should be a mvn based url");
        }
        url = url.substring((ServiceConstants.PROTOCOL + ":").length());
        Parser parser = new Parser(url);
        return resolve(parser.getGroup(), parser.getArtifact(), parser.getClassifier(), parser.getType(),
                parser.getVersion(), parser.getRepositoryURL(), previousException);
    }

    /**
     * Resolve maven artifact as file in repository.
     */
    @Override
    public File resolve(String groupId, String artifactId, String classifier, String extension, String version)
            throws IOException {
        return resolve(groupId, artifactId, classifier, extension, version, null, null);
    }

    @Override
    public File resolve(String groupId, String artifactId, String classifier, String extension, String version,
            Exception previousException) throws IOException {
        return resolve(groupId, artifactId, classifier, extension, version, null, previousException);
    }

    /**
     * Resolve maven artifact as file in repository.
     */
    public File resolve(String groupId, String artifactId, String classifier, String extension, String version,
            MavenRepositoryURL repositoryURL, Exception previousException) throws IOException {
        Artifact artifact = new DefaultArtifact(groupId, artifactId, classifier, extension, version);
        return resolve(artifact, repositoryURL, previousException);
    }

    /**
     * Resolve maven artifact as file in repository.
     */
    public File resolve(Artifact artifact) throws IOException {
        return resolve(artifact, null, null);
    }

    /**
     * Resolve maven artifact as file in repository.
     */
    public File resolve(Artifact artifact, MavenRepositoryURL repositoryURL, Exception previousException)
            throws IOException {

        List<LocalRepository> defaultRepos = selectDefaultRepositories();
        List<RemoteRepository> remoteRepos = selectRepositories();
        if (repositoryURL != null) {
            addRepo(remoteRepos, repositoryURL);
        }

        // PAXURL-337: use previousException as hint to alter remote repositories to query
        if (previousException != null) {
            // we'll try using previous repositories, without these that will fail again anyway
            List<RemoteRepository> altered = new LinkedList<>();
            RepositoryException repositoryException = findAetherException(previousException);
            if (repositoryException instanceof ArtifactResolutionException) {
                // check only this aggregate exception and assume it's related to current artifact
                ArtifactResult result = ((ArtifactResolutionException) repositoryException).getResult();
                if (result != null && result.getRequest() != null
                        && result.getRequest().getArtifact().equals(artifact)) {
                    // one exception per repository checked
                    // consider only ArtifactTransferException:
                    //  - they may be recoverable
                    //  - these exceptions contain repository that was checked
                    for (Exception exception : result.getExceptions()) {
                        RepositoryException singleException = findAetherException(exception);
                        if (singleException instanceof ArtifactTransferException) {
                            RemoteRepository repository = ((ArtifactTransferException) singleException)
                                    .getRepository();
                            if (repository != null) {
                                RetryChance chance = isRetryableException(singleException);
                                if (chance == RetryChance.NEVER) {
                                    LOG.debug("Removing " + repository
                                            + " from list of repositories, previous exception: "
                                            + singleException.getClass().getName() + ": "
                                            + singleException.getMessage());
                                } else {
                                    altered.add(repository);
                                }
                            }
                        }
                    }

                    // swap list of repos now
                    remoteRepos = altered;
                }
            }
        }

        assignProxyAndMirrors(remoteRepos);
        File resolved = resolve(defaultRepos, remoteRepos, artifact);

        LOG.debug("Resolved ({}) as {}", artifact.toString(), resolved.getAbsolutePath());
        return resolved;
    }

    private File resolve(List<LocalRepository> defaultRepos, List<RemoteRepository> remoteRepos, Artifact artifact)
            throws IOException {

        if (artifact.getExtension().isEmpty()) {
            artifact = new DefaultArtifact(artifact.getGroupId(), artifact.getArtifactId(),
                    artifact.getClassifier(), "jar", artifact.getVersion());
        }

        // Try with default repositories
        try {
            VersionConstraint vc = new GenericVersionScheme().parseVersionConstraint(artifact.getVersion());
            if (vc.getVersion() != null) {
                for (LocalRepository repo : defaultRepos) {
                    RepositorySystemSession session = newSession(repo);
                    try {
                        return m_repoSystem.resolveArtifact(session, new ArtifactRequest(artifact, null, null))
                                .getArtifact().getFile();
                    } catch (ArtifactResolutionException e) {
                        // Ignore
                    } finally {
                        releaseSession(session);
                    }
                }
            }
        } catch (InvalidVersionSpecificationException e) {
            // Should not happen
        }
        RepositorySystemSession session = newSession(null);
        try {
            artifact = resolveLatestVersionRange(session, remoteRepos, artifact);
            return m_repoSystem.resolveArtifact(session, new ArtifactRequest(artifact, remoteRepos, null))
                    .getArtifact().getFile();
        } catch (ArtifactResolutionException e) {
            // we know there's one ArtifactResult, because there was one ArtifactRequest
            ArtifactResolutionException original = new ArtifactResolutionException(e.getResults(),
                    "Error resolving artifact " + artifact.toString(), null);
            original.setStackTrace(e.getStackTrace());

            List<String> messages = new ArrayList<>(e.getResult().getExceptions().size());
            List<Exception> suppressed = new ArrayList<>();
            for (Exception ex : e.getResult().getExceptions()) {
                messages.add(ex.getMessage() == null ? ex.getClass().getName() : ex.getMessage());
                suppressed.add(ex);
            }
            IOException exception = new IOException(original.getMessage() + ": " + messages, original);
            for (Exception ex : suppressed) {
                exception.addSuppressed(ex);
            }
            LOG.warn(exception.getMessage(), exception);

            throw exception;
        } catch (RepositoryException e) {
            throw new IOException("Error resolving artifact " + artifact.toString(), e);
        } finally {
            releaseSession(session);
        }
    }

    @Override
    public File resolveMetadata(String groupId, String artifactId, String type, String version) throws IOException {
        return resolveMetadata(groupId, artifactId, type, version, null);
    }

    @Override
    public File resolveMetadata(String groupId, String artifactId, String type, String version,
            Exception previousException) throws IOException {
        RepositorySystem system = getRepositorySystem();
        RepositorySystemSession session = newSession();
        try {
            Metadata metadata = new DefaultMetadata(groupId, artifactId, version, type,
                    Metadata.Nature.RELEASE_OR_SNAPSHOT);
            List<MetadataRequest> requests = new ArrayList<MetadataRequest>();
            // TODO: previousException may be a hint to alter remote repository list to query
            for (RemoteRepository repository : getRepositories()) {
                MetadataRequest request = new MetadataRequest(metadata, repository, null);
                request.setFavorLocalRepository(false);
                requests.add(request);
            }
            MetadataRequest request = new MetadataRequest(metadata, null, null);
            request.setFavorLocalRepository(true);
            requests.add(request);
            org.apache.maven.artifact.repository.metadata.Metadata mr = new org.apache.maven.artifact.repository.metadata.Metadata();
            mr.setModelVersion("1.1.0");
            mr.setGroupId(metadata.getGroupId());
            mr.setArtifactId(metadata.getArtifactId());
            mr.setVersioning(new Versioning());
            boolean merged = false;
            List<MetadataResult> results = system.resolveMetadata(session, requests);
            for (MetadataResult result : results) {
                if (result.getMetadata() != null && result.getMetadata().getFile() != null) {
                    FileInputStream fis = new FileInputStream(result.getMetadata().getFile());
                    org.apache.maven.artifact.repository.metadata.Metadata m = new MetadataXpp3Reader().read(fis,
                            false);
                    fis.close();
                    if (m.getVersioning() != null) {
                        mr.getVersioning().setLastUpdated(latestTimestamp(mr.getVersioning().getLastUpdated(),
                                m.getVersioning().getLastUpdated()));
                        mr.getVersioning().setLatest(
                                latestVersion(mr.getVersioning().getLatest(), m.getVersioning().getLatest()));
                        mr.getVersioning().setRelease(
                                latestVersion(mr.getVersioning().getRelease(), m.getVersioning().getRelease()));
                        for (String v : m.getVersioning().getVersions()) {
                            if (!mr.getVersioning().getVersions().contains(v)) {
                                mr.getVersioning().getVersions().add(v);
                            }
                        }
                        mr.getVersioning().getSnapshotVersions().addAll(m.getVersioning().getSnapshotVersions());
                    }
                    merged = true;
                }
            }
            if (merged) {
                Collections.sort(mr.getVersioning().getVersions(), VERSION_COMPARATOR);
                Collections.sort(mr.getVersioning().getSnapshotVersions(), SNAPSHOT_VERSION_COMPARATOR);
                File tmpFile = Files.createTempFile("mvn-", ".tmp").toFile();
                FileOutputStream fos = new FileOutputStream(tmpFile);
                try {
                    new MetadataXpp3Writer().write(fos, mr);
                } finally {
                    fos.close();
                }
                return tmpFile;
            }
            return null;
        } catch (Exception e) {
            throw new IOException("Unable to resolve metadata", e);
        } finally {
            releaseSession(session);
        }
    }

    @Override
    public void upload(String groupId, String artifactId, String classifier, String extension, String version,
            File file) throws IOException {
        RepositorySystem system = getRepositorySystem();
        RepositorySystemSession session = newSession();
        try {
            Artifact artifact = new DefaultArtifact(groupId, artifactId, classifier, extension, version, null,
                    file);
            InstallRequest request = new InstallRequest();
            request.addArtifact(artifact);
            system.install(session, request);
        } catch (Exception e) {
            throw new IOException("Unable to install artifact", e);
        } finally {
            releaseSession(session);
        }
    }

    @Override
    public void uploadMetadata(String groupId, String artifactId, String type, String version, File file)
            throws IOException {
        RepositorySystem system = getRepositorySystem();
        RepositorySystemSession session = newSession();
        try {
            Metadata metadata = new DefaultMetadata(groupId, artifactId, version, type,
                    Metadata.Nature.RELEASE_OR_SNAPSHOT, file);
            InstallRequest request = new InstallRequest();
            request.addMetadata(metadata);
            system.install(session, request);
        } catch (Exception e) {
            throw new IOException("Unable to install metadata", e);
        } finally {
            releaseSession(session);
        }
    }

    @Override
    public RetryChance isRetryableException(Exception exception) {
        RetryChance retry = RetryChance.NEVER;

        RepositoryException aetherException = findAetherException(exception);

        if (aetherException instanceof ArtifactResolutionException) {
            // aggregate case - exception that contains exceptions - usually per repository
            ArtifactResolutionException resolutionException = (ArtifactResolutionException) aetherException;
            if (resolutionException.getResult() != null) {
                for (Exception ex : resolutionException.getResult().getExceptions()) {
                    RetryChance singleRetry = isRetryableException(ex);
                    if (retry.chance() < singleRetry.chance()) {
                        retry = singleRetry;
                    }
                }
            }
        } else if (aetherException != null) {
            // single exception case

            if (aetherException instanceof ArtifactNotFoundException) {
                // very little chance we'll find the artifact next time
                retry = RetryChance.NEVER;
            } else if (aetherException instanceof MetadataNotFoundException) {
                retry = RetryChance.NEVER;
            } else if (aetherException instanceof ArtifactTransferException
                    || aetherException instanceof MetadataTransferException) {
                // we could try again
                Throwable root = rootException(aetherException);
                if (root instanceof SocketTimeoutException) {
                    // we could try again - but without assuming we'll succeed eventually
                    retry = RetryChance.LOW;
                } else if (root instanceof ConnectException) {
                    // "connection refused" - not retryable
                    retry = RetryChance.NEVER;
                } else if (root instanceof NoRouteToHostException) {
                    // not retryable
                    retry = RetryChance.NEVER;
                }
            } else {
                // general aether exception - let's fallback to NEVER, as retryable cases should be
                // handled explicitly
                retry = RetryChance.NEVER;
            }
        } else {
            // we don't know about non-aether exceptions, so let's allow
            retry = RetryChance.UNKNOWN;
        }

        return retry;
    }

    /**
     * Find top-most Aether exception
     * @param e
     * @return
     */
    protected RepositoryException findAetherException(Exception e) {
        Throwable ex = e;
        while (ex != null && !(ex instanceof RepositoryException)) {
            ex = ex.getCause();
        }
        return ex == null ? null : (RepositoryException) ex;
    }

    /**
     * Find root exception
     * @param ex
     * @return
     */
    protected Throwable rootException(Exception ex) {
        Throwable root = ex;
        while (true) {
            if (root.getCause() != null) {
                root = root.getCause();
            } else {
                break;
            }
        }
        return root;
    }

    private Comparator<String> VERSION_COMPARATOR = new Comparator<String>() {
        @Override
        public int compare(String v1, String v2) {
            try {
                Version vv1 = new GenericVersionScheme().parseVersion(v1);
                Version vv2 = new GenericVersionScheme().parseVersion(v2);
                return vv1.compareTo(vv2);
            } catch (Exception e) {
                return v1.compareTo(v2);
            }
        }
    };

    private Comparator<SnapshotVersion> SNAPSHOT_VERSION_COMPARATOR = new Comparator<SnapshotVersion>() {
        @Override
        public int compare(SnapshotVersion o1, SnapshotVersion o2) {
            int c = VERSION_COMPARATOR.compare(o1.getVersion(), o2.getVersion());
            if (c == 0) {
                c = o1.getExtension().compareTo(o2.getExtension());
            }
            if (c == 0) {
                c = o1.getClassifier().compareTo(o2.getClassifier());
            }
            return c;
        }
    };

    private String latestTimestamp(String t1, String t2) {
        if (t1 == null) {
            return t2;
        } else if (t2 == null) {
            return t1;
        } else {
            return t1.compareTo(t2) < 0 ? t2 : t1;
        }
    }

    private String latestVersion(String v1, String v2) {
        if (v1 == null) {
            return v2;
        } else if (v2 == null) {
            return v1;
        } else {
            return VERSION_COMPARATOR.compare(v1, v2) < 0 ? v2 : v1;
        }
    }

    /**
     * Tries to resolve versions = LATEST using an open range version query. If it succeeds, version
     * of artifact is set to the highest available version.
     *
     * @param session
     *            to be used.
     * @param artifact
     *            to be used
     *
     * @return an artifact with version set properly (highest if available)
     *
     * @throws org.eclipse.aether.resolution.VersionRangeResolutionException
     *             in case of resolver errors.
     */
    private Artifact resolveLatestVersionRange(RepositorySystemSession session, List<RemoteRepository> remoteRepos,
            Artifact artifact) throws VersionRangeResolutionException {
        if (artifact.getVersion().equals(VERSION_LATEST)) {
            artifact = artifact.setVersion(LATEST_VERSION_RANGE);
        }

        VersionRangeResult versionResult = m_repoSystem.resolveVersionRange(session,
                new VersionRangeRequest(artifact, remoteRepos, null));
        if (versionResult != null) {
            Version v = versionResult.getHighestVersion();
            if (v != null) {

                artifact = artifact.setVersion(v.toString());
            } else {
                throw new VersionRangeResolutionException(versionResult,
                        "No highest version found for " + artifact);
            }
        }
        return artifact;
    }

    public RepositorySystemSession newSession() {
        return newSession(null);
    }

    private RepositorySystemSession newSession(LocalRepository repo) {
        if (repo == null) {
            repo = getLocalRepository();
        }
        Deque<RepositorySystemSession> deque = sessions.get(repo);
        RepositorySystemSession session = null;
        if (deque != null) {
            session = deque.pollFirst();
        }
        if (session == null) {
            session = createSession(repo);
        }
        return session;
    }

    /**
     * @see org.eclipse.aether.internal.impl.DefaultUpdateCheckManager#SESSION_CHECKS
     */
    private static final String SESSION_CHECKS = "updateCheckManager.checks";

    private void releaseSession(RepositorySystemSession session) {
        LocalRepository repo = session.getLocalRepository();
        Deque<RepositorySystemSession> deque = sessions.get(repo);
        if (deque == null) {
            sessions.putIfAbsent(repo, new ConcurrentLinkedDeque<RepositorySystemSession>());
            deque = sessions.get(repo);
        }
        session.getData().set(SESSION_CHECKS, null);
        deque.add(session);
    }

    private RepositorySystemSession createSession(LocalRepository repo) {
        DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();

        if (repo != null) {
            session.setLocalRepositoryManager(m_repoSystem.newLocalRepositoryManager(session, repo));
        } else {
            session.setLocalRepositoryManager(
                    m_repoSystem.newLocalRepositoryManager(session, getLocalRepository()));
        }

        session.setMirrorSelector(m_mirrorSelector);
        session.setProxySelector(m_proxySelector);

        String updatePolicy = m_config.getGlobalUpdatePolicy();
        if (null != updatePolicy) {
            session.setUpdatePolicy(updatePolicy);
        }

        String checksumPolicy = m_config.getGlobalChecksumPolicy();
        if (null != checksumPolicy) {
            session.setChecksumPolicy(checksumPolicy);
        }

        for (Server server : m_settings.getServers()) {
            if (server.getConfiguration() != null
                    && ((Xpp3Dom) server.getConfiguration()).getChild("httpHeaders") != null) {
                addServerConfig(session, server);
            }
        }

        // org.eclipse.aether.transport.wagon.WagonTransporter.connectWagon() sets connection timeout
        // as max of connection timeout and request timeout taken from aether session config
        // but on the other hand, request timeout is used in org.eclipse.aether.connector.basic.PartialFile.Factory
        //
        // because real socket read timeout is used again (correctly) in
        // org.ops4j.pax.url.mvn.internal.wagon.ConfigurableHttpWagon.execute() - directly from wagon configuration
        // (from org.apache.maven.wagon.AbstractWagon.getReadTimeout()), we explicitly configure aether session
        // config with: PartialFile.Factory timeout == connection timeout
        int defaultTimeut = m_config.getTimeout();
        Integer timeout = m_config.getProperty(ServiceConstants.PROPERTY_SOCKET_CONNECTION_TIMEOUT, defaultTimeut,
                Integer.class);
        session.setConfigProperty(ConfigurationProperties.CONNECT_TIMEOUT, timeout);
        session.setConfigProperty(ConfigurationProperties.REQUEST_TIMEOUT, timeout);

        session.setOffline(m_config.isOffline());

        // PAXURL-322
        boolean updateReleases = m_config.getProperty(ServiceConstants.PROPERTY_UPDATE_RELEASES, false,
                Boolean.class);
        session.setConfigProperty(PaxLocalRepositoryManager.PROPERTY_UPDATE_RELEASES, updateReleases);

        return session;
    }

    private LocalRepository getLocalRepository() {
        if (localRepository == null) {
            File local;
            if (m_config.getLocalRepository() != null) {
                local = m_config.getLocalRepository().getFile();
            } else {
                local = new File(System.getProperty("user.home"), ".m2/repository");
            }
            localRepository = new LocalRepository(local, "simple");
        }
        return localRepository;
    }

    private void addServerConfig(DefaultRepositorySystemSession session, Server server) {
        Map<String, String> headers = new HashMap<String, String>();
        Xpp3Dom configuration = (Xpp3Dom) server.getConfiguration();
        Xpp3Dom httpHeaders = configuration.getChild("httpHeaders");
        for (Xpp3Dom httpHeader : httpHeaders.getChildren("httpHeader")) {
            Xpp3Dom name = httpHeader.getChild("name");
            String headerName = name.getValue();
            Xpp3Dom value = httpHeader.getChild("value");
            String headerValue = value.getValue();
            headers.put(headerName, headerValue);
        }
        session.setConfigProperty(String.format("%s.%s", ConfigurationProperties.HTTP_HEADERS, server.getId()),
                headers);
    }

    private Authentication getAuthentication(org.apache.maven.settings.Proxy proxy) {
        // user, pass
        if (proxy.getUsername() != null) {
            return new AuthenticationBuilder().addUsername(proxy.getUsername()).addPassword(proxy.getPassword())
                    .build();
        }
        return null;
    }

    private Authentication getAuthentication(String repoId) {
        Server server = m_settings.getServer(repoId);
        if (server != null && server.getUsername() != null) {
            AuthenticationBuilder authBuilder = new AuthenticationBuilder();
            authBuilder.addUsername(server.getUsername()).addPassword(server.getPassword());
            return authBuilder.build();
        }
        return null;
    }

    private RepositorySystem newRepositorySystem() {
        DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();

        // default timeout (both connection and read timeouts)
        int defaultTimeout = m_config.getTimeout();
        // connection timeout
        int connectionTimeout = m_config.getProperty(ServiceConstants.PROPERTY_SOCKET_CONNECTION_TIMEOUT,
                defaultTimeout, Integer.class);
        // read timeout
        int soTimeout = m_config.getProperty(ServiceConstants.PROPERTY_SOCKET_SO_TIMEOUT, defaultTimeout,
                Integer.class);
        locator.setServices(WagonProvider.class, new ManualWagonProvider(m_client, soTimeout, connectionTimeout));
        locator.addService(TransporterFactory.class, WagonTransporterFactory.class);
        locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);

        decrypter = new ConfigurableSettingsDecrypter();
        PaxUrlSecDispatcher secDispatcher = new PaxUrlSecDispatcher();
        try {
            secDispatcher.setCipher(new DefaultPlexusCipher());
        } catch (PlexusCipherException exc) {
            throw new IllegalStateException(exc);
        }
        secDispatcher.setConfigurationFile(m_config.getSecuritySettings());
        decrypter.setSecurityDispatcher(secDispatcher);

        locator.setServices(SettingsDecrypter.class, decrypter);

        locator.setService(LocalRepositoryManagerFactory.class, PaxLocalRepositoryManagerFactory.class);
        locator.setService(org.eclipse.aether.spi.log.LoggerFactory.class, Slf4jLoggerFactory.class);

        return locator.getService(RepositorySystem.class);
    }
}