All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ops4j.pax.url.mvn.internal.AetherBasedResolver Maven / Gradle / Ivy

/*
 * 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> sessions
            = new ConcurrentHashMap>();

    /**
     * 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 remoteRepos ) {
        Map> map = new HashMap>();
        Map naming = new HashMap();
        boolean aggregateReleaseEnabled = false, aggregateSnapshotEnabled = false;
        String aggregateReleaseUpdateInterval = null, aggregateSnapshotUpdateInterval = null;
        String aggregateReleaseChecksumPolicy = null, aggregateSnapshotChecksumPolicy = null;

        List resultingRepos = new ArrayList();

        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() );
                }
                List 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 mirroredRepos = new ArrayList();

            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 authMap = new HashMap();

			@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 selectRepositories() {
        List list = new ArrayList();
        List 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 selectDefaultRepositories() {
        List list = new ArrayList();
        List 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 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(){
            @Override
            public int compare(File o1, File o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });
        return files;
    }

    private void addRepo( List 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 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 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 getRepositories() {
        List 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 defaultRepos = selectDefaultRepositories();
        List 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 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 defaultRepos,
                          List 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 messages = new ArrayList<>(e.getResult().getExceptions().size());
            List 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 requests = new ArrayList();
            // 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 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 VERSION_COMPARATOR = new Comparator() {
        @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 SNAPSHOT_VERSION_COMPARATOR = new Comparator() {
        @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 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 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 deque = sessions.get(repo);
        if (deque == null) {
            sessions.putIfAbsent(repo, new ConcurrentLinkedDeque());
            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 headers = new HashMap();
        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 );
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy