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