/* * 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 ); } }