1 package org.apache.archiva.metadata.repository.storage.maven2;
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
22 import org.apache.archiva.common.utils.VersionUtil;
23 import org.apache.archiva.maven2.metadata.MavenMetadataReader;
24 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
25 import org.apache.archiva.model.ArchivaRepositoryMetadata;
26 import org.apache.archiva.model.SnapshotVersion;
27 import org.apache.archiva.proxy.maven.WagonFactory;
28 import org.apache.archiva.proxy.maven.WagonFactoryException;
29 import org.apache.archiva.proxy.maven.WagonFactoryRequest;
30 import org.apache.archiva.proxy.model.NetworkProxy;
31 import org.apache.archiva.repository.ManagedRepository;
32 import org.apache.archiva.repository.RemoteRepository;
33 import org.apache.archiva.repository.RepositoryCredentials;
34 import org.apache.archiva.repository.maven2.MavenSystemManager;
35 import org.apache.archiva.xml.XMLException;
36 import org.apache.commons.lang.StringUtils;
37 import org.apache.http.auth.UsernamePasswordCredentials;
38 import org.apache.maven.model.Dependency;
39 import org.apache.maven.model.Parent;
40 import org.apache.maven.model.Repository;
41 import org.apache.maven.model.building.FileModelSource;
42 import org.apache.maven.model.building.ModelSource;
43 import org.apache.maven.model.resolution.InvalidRepositoryException;
44 import org.apache.maven.model.resolution.ModelResolver;
45 import org.apache.maven.model.resolution.UnresolvableModelException;
46 import org.apache.maven.wagon.ConnectionException;
47 import org.apache.maven.wagon.ResourceDoesNotExistException;
48 import org.apache.maven.wagon.TransferFailedException;
49 import org.apache.maven.wagon.Wagon;
50 import org.apache.maven.wagon.authentication.AuthenticationException;
51 import org.apache.maven.wagon.authentication.AuthenticationInfo;
52 import org.apache.maven.wagon.authorization.AuthorizationException;
53 import org.apache.maven.wagon.proxy.ProxyInfo;
54 import org.eclipse.aether.RepositorySystemSession;
55 import org.eclipse.aether.artifact.Artifact;
56 import org.eclipse.aether.artifact.DefaultArtifact;
57 import org.eclipse.aether.impl.VersionRangeResolver;
58 import org.eclipse.aether.resolution.VersionRangeRequest;
59 import org.eclipse.aether.resolution.VersionRangeResolutionException;
60 import org.eclipse.aether.resolution.VersionRangeResult;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
64 import java.io.IOException;
65 import java.nio.file.Files;
66 import java.nio.file.Path;
67 import java.nio.file.Paths;
68 import java.util.HashMap;
69 import java.util.List;
72 public class RepositoryModelResolver
73 implements ModelResolver
76 private final Map<String, NetworkProxy> networkProxyMap = new HashMap<>();
78 private RepositorySystemSession session;
79 private VersionRangeResolver versionRangeResolver;
83 private RepositoryPathTranslator pathTranslator;
85 private WagonFactory wagonFactory;
87 private List<RemoteRepository> remoteRepositories;
89 private ManagedRepository targetRepository;
91 private static final Logger log = LoggerFactory.getLogger( RepositoryModelResolver.class );
93 private static final String METADATA_FILENAME = "maven-metadata.xml";
95 private MavenSystemManager mavenSystemManager;
99 private ManagedRepository managedRepository;
101 public RepositoryModelResolver( Path basedir, RepositoryPathTranslator pathTranslator )
103 this.basedir = basedir;
105 this.pathTranslator = pathTranslator;
108 public RepositoryModelResolver(ManagedRepository managedRepository, RepositoryPathTranslator pathTranslator,
109 WagonFactory wagonFactory, List<RemoteRepository> remoteRepositories,
110 Map<String, NetworkProxy> networkProxiesMap, ManagedRepository targetRepository,
111 MavenSystemManager mavenSystemManager)
113 this( Paths.get( managedRepository.getLocation() ), pathTranslator );
115 this.managedRepository = managedRepository;
117 this.wagonFactory = wagonFactory;
119 this.remoteRepositories = remoteRepositories;
121 this.networkProxyMap.clear();
122 this.networkProxyMap.putAll(networkProxiesMap);
124 this.targetRepository = targetRepository;
126 this.session = MavenSystemManager.newRepositorySystemSession( managedRepository.getAsset("").getFilePath().toString() );
128 this.versionRangeResolver = mavenSystemManager.getLocator().getService(VersionRangeResolver.class);
130 this.mavenSystemManager = mavenSystemManager;
135 public ModelSource resolveModel( String groupId, String artifactId, String version )
136 throws UnresolvableModelException
138 String filename = artifactId + "-" + version + ".pom";
139 // TODO: we need to convert 1.0-20091120.112233-1 type paths to baseVersion for the below call - add a test
141 Path model = pathTranslator.toFile( basedir, groupId, artifactId, version, filename );
143 if ( !Files.exists(model) )
148 // is a SNAPSHOT ? so we can try to find locally before asking remote repositories.
149 if ( StringUtils.contains( version, VersionUtil.SNAPSHOT ) )
151 Path localSnapshotModel = findTimeStampedSnapshotPom( groupId, artifactId, version, model.getParent().toString() );
152 if ( localSnapshotModel != null )
154 return new FileModelSource( localSnapshotModel.toFile() );
159 for ( RemoteRepository remoteRepository : remoteRepositories )
163 boolean success = getModelFromProxy( remoteRepository, groupId, artifactId, version, filename );
164 if ( success && Files.exists(model) )
166 log.info( "Model '{}' successfully retrieved from remote repository '{}'",
167 model.toAbsolutePath(), remoteRepository.getId() );
171 catch ( ResourceDoesNotExistException e )
174 "An exception was caught while attempting to retrieve model '{}' from remote repository '{}'.Reason:{}",
175 model.toAbsolutePath(), remoteRepository.getId(), e.getMessage() );
177 catch ( Exception e )
180 "An exception was caught while attempting to retrieve model '{}' from remote repository '{}'.Reason:{}",
181 model.toAbsolutePath(), remoteRepository.getId(), e.getMessage() );
188 return new FileModelSource( model.toFile() );
191 public ModelSource resolveModel(Parent parent) throws UnresolvableModelException {
193 Artifact artifact = new DefaultArtifact(parent.getGroupId(), parent.getArtifactId(), "", "pom", parent.getVersion());
194 VersionRangeRequest versionRangeRequest;
195 versionRangeRequest = new VersionRangeRequest(artifact, null, null);
196 VersionRangeResult versionRangeResult = this.versionRangeResolver.resolveVersionRange(this.session, versionRangeRequest);
197 if (versionRangeResult.getHighestVersion() == null) {
198 throw new UnresolvableModelException(String.format("No versions matched the requested parent version range '%s'", parent.getVersion()), parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
199 } else if (versionRangeResult.getVersionConstraint() != null && versionRangeResult.getVersionConstraint().getRange() != null && versionRangeResult.getVersionConstraint().getRange().getUpperBound() == null) {
200 throw new UnresolvableModelException(String.format("The requested parent version range '%s' does not specify an upper bound", parent.getVersion()), parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
202 parent.setVersion(versionRangeResult.getHighestVersion().toString());
203 return this.resolveModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
205 } catch ( VersionRangeResolutionException var5) {
206 throw new UnresolvableModelException(var5.getMessage(), parent.getGroupId(), parent.getArtifactId(), parent.getVersion(), var5);
210 public ModelSource resolveModel(Dependency dependency) throws UnresolvableModelException {
212 Artifact artifact = new DefaultArtifact(dependency.getGroupId(), dependency.getArtifactId(), "", "pom", dependency.getVersion());
213 VersionRangeRequest versionRangeRequest = new VersionRangeRequest(artifact, null, null);
214 VersionRangeResult versionRangeResult = this.versionRangeResolver.resolveVersionRange(this.session, versionRangeRequest);
215 if (versionRangeResult.getHighestVersion() == null) {
216 throw new UnresolvableModelException(String.format("No versions matched the requested dependency version range '%s'", dependency.getVersion()), dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion());
217 } else if (versionRangeResult.getVersionConstraint() != null && versionRangeResult.getVersionConstraint().getRange() != null && versionRangeResult.getVersionConstraint().getRange().getUpperBound() == null) {
218 throw new UnresolvableModelException(String.format("The requested dependency version range '%s' does not specify an upper bound", dependency.getVersion()), dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion());
220 dependency.setVersion(versionRangeResult.getHighestVersion().toString());
221 return this.resolveModel(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion());
223 } catch (VersionRangeResolutionException var5) {
224 throw new UnresolvableModelException(var5.getMessage(), dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), var5);
228 protected Path findTimeStampedSnapshotPom( String groupId, String artifactId, String version,
229 String parentDirectory )
232 // reading metadata if there
233 Path mavenMetadata = Paths.get( parentDirectory, METADATA_FILENAME );
234 if ( Files.exists(mavenMetadata) )
238 ArchivaRepositoryMetadata archivaRepositoryMetadata = MavenMetadataReader.read( mavenMetadata);
239 SnapshotVersion snapshotVersion = archivaRepositoryMetadata.getSnapshotVersion();
240 if ( snapshotVersion != null )
242 String lastVersion = snapshotVersion.getTimestamp();
243 int buildNumber = snapshotVersion.getBuildNumber();
244 String snapshotPath =
245 StringUtils.replaceChars( groupId, '.', '/' ) + '/' + artifactId + '/' + version + '/'
246 + artifactId + '-' + StringUtils.remove( version, "-" + VersionUtil.SNAPSHOT ) + '-'
247 + lastVersion + '-' + buildNumber + ".pom";
249 log.debug( "use snapshot path {} for maven coordinate {}:{}:{}", snapshotPath, groupId, artifactId,
252 Path model = basedir.resolve( snapshotPath );
253 //model = pathTranslator.toFile( basedir, groupId, artifactId, lastVersion, filename );
254 if ( Files.exists(model) )
260 catch ( XMLException e )
262 log.warn( "fail to read {}, {}", mavenMetadata.toAbsolutePath(), e.getCause() );
270 public void addRepository( Repository repository )
271 throws InvalidRepositoryException
273 // we just ignore repositories outside of the current one for now
274 // TODO: it'd be nice to look them up from Archiva's set, but we want to do that by URL / mapping, not just the
275 // ID since they will rarely match
279 public void addRepository( Repository repository, boolean b ) throws InvalidRepositoryException
285 public ModelResolver newCopy()
287 return new RepositoryModelResolver( managedRepository, pathTranslator, wagonFactory, remoteRepositories,
288 networkProxyMap, targetRepository, mavenSystemManager);
291 // FIXME: we need to do some refactoring, we cannot re-use the proxy components of archiva-proxy in maven2-repository
292 // because it's causing a cyclic dependency
293 private boolean getModelFromProxy( RemoteRepository remoteRepository, String groupId, String artifactId,
294 String version, String filename )
295 throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException, WagonFactoryException,
296 XMLException, IOException
298 boolean success = false;
301 Path tmpResource = null;
302 String artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename );
303 Path resource = targetRepository.getAsset("").getFilePath().resolve( artifactPath );
305 Path workingDirectory = createWorkingDirectory( targetRepository.getLocation().toString() );
311 String protocol = getProtocol( remoteRepository.getLocation().toString() );
312 final NetworkProxy networkProxy = this.networkProxyMap.get( remoteRepository.getId() );
314 wagon = wagonFactory.getWagon(
315 new WagonFactoryRequest( "wagon#" + protocol, remoteRepository.getExtraHeaders() ).networkProxy(
321 throw new RuntimeException( "Unsupported remote repository protocol: " + protocol );
324 boolean connected = connectToRepository( wagon, remoteRepository );
327 tmpResource = workingDirectory.resolve( filename );
329 if ( VersionUtil.isSnapshot( version ) )
331 // get the metadata first!
332 Path tmpMetadataResource = workingDirectory.resolve( METADATA_FILENAME );
334 String metadataPath =
335 StringUtils.substringBeforeLast( artifactPath, "/" ) + "/" + METADATA_FILENAME;
337 wagon.get( addParameters( metadataPath, remoteRepository ), tmpMetadataResource.toFile() );
339 log.debug( "Successfully downloaded metadata." );
341 ArchivaRepositoryMetadata metadata = MavenMetadataReader.read( tmpMetadataResource );
343 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
344 SnapshotVersion snapshotVersion = metadata.getSnapshotVersion();
345 String timestampVersion = version;
346 if ( snapshotVersion != null )
348 timestampVersion = timestampVersion.substring( 0, timestampVersion.length()
349 - 8 ); // remove SNAPSHOT from end
350 timestampVersion = timestampVersion + snapshotVersion.getTimestamp() + "-"
351 + snapshotVersion.getBuildNumber();
353 filename = artifactId + "-" + timestampVersion + ".pom";
355 artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename );
357 log.debug( "New artifactPath :{}", artifactPath );
361 log.info( "Retrieving {} from {}", artifactPath, remoteRepository.getName() );
363 wagon.get( addParameters( artifactPath, remoteRepository ), tmpResource.toFile() );
365 log.debug( "Downloaded successfully." );
367 tmpSha1 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
369 tmpMd5 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
381 catch ( ConnectionException e )
383 log.warn( "Unable to disconnect wagon.", e );
388 if ( resource != null )
390 synchronized ( resource.toAbsolutePath().toString().intern() )
392 Path directory = resource.getParent();
393 moveFileIfExists( tmpMd5, directory );
394 moveFileIfExists( tmpSha1, directory );
395 moveFileIfExists( tmpResource, directory );
402 org.apache.archiva.common.utils.FileUtils.deleteQuietly( workingDirectory );
405 // do we still need to execute the consumers?
411 * Using wagon, connect to the remote repository.
413 * @param wagon the wagon instance to establish the connection on.
414 * @return true if the connection was successful. false if not connected.
416 private boolean connectToRepository( Wagon wagon, RemoteRepository remoteRepository )
420 final NetworkProxy proxyConnector = this.networkProxyMap.get( remoteRepository.getId() );
421 ProxyInfo networkProxy = null;
422 if ( proxyConnector != null )
424 networkProxy = new ProxyInfo();
425 networkProxy.setType( proxyConnector.getProtocol() );
426 networkProxy.setHost( proxyConnector.getHost() );
427 networkProxy.setPort( proxyConnector.getPort() );
428 networkProxy.setUserName( proxyConnector.getUsername() );
429 networkProxy.setPassword( proxyConnector.getPassword() );
431 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
432 + " to connect to remote repository " + remoteRepository.getLocation();
433 if ( networkProxy.getNonProxyHosts() != null )
435 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
438 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
440 msg += "; as user: " + networkProxy.getUserName();
446 AuthenticationInfo authInfo = null;
447 RepositoryCredentials creds = remoteRepository.getLoginCredentials();
448 String username = "";
449 String password = "";
450 if (creds instanceof UsernamePasswordCredentials) {
451 UsernamePasswordCredentials c = (UsernamePasswordCredentials) creds;
452 username = c.getUserName();
453 password = c.getPassword();
456 if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
458 log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getLocation() );
459 authInfo = new AuthenticationInfo();
460 authInfo.setUserName( username );
461 authInfo.setPassword( password );
464 int timeoutInMilliseconds = ((int)remoteRepository.getTimeout().getSeconds())*1000;
465 // FIXME olamy having 2 config values
467 wagon.setReadTimeout( timeoutInMilliseconds );
468 wagon.setTimeout( timeoutInMilliseconds );
472 org.apache.maven.wagon.repository.Repository wagonRepository =
473 new org.apache.maven.wagon.repository.Repository( remoteRepository.getId(), remoteRepository.getLocation().toString() );
474 if ( networkProxy != null )
476 wagon.connect( wagonRepository, authInfo, networkProxy );
480 wagon.connect( wagonRepository, authInfo );
484 catch ( ConnectionException | AuthenticationException e )
486 log.error( "Could not connect to {}:{} ", remoteRepository.getName(), e.getMessage() );
495 * @param wagon The wagon instance that should be connected.
496 * @param remoteRepository The repository from where the checksum file should be retrieved
497 * @param remotePath The remote path of the artifact (without extension)
498 * @param resource The local artifact (without extension)
499 * @param workingDir The working directory where the downloaded file should be placed to
500 * @param ext The extension of th checksum file
501 * @return The file where the data has been downloaded to.
502 * @throws AuthorizationException
503 * @throws TransferFailedException
504 * @throws ResourceDoesNotExistException
506 private Path transferChecksum( final Wagon wagon, final RemoteRepository remoteRepository,
507 final String remotePath, final Path resource,
508 final Path workingDir, final String ext )
509 throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException
511 Path destFile = workingDir.resolve( resource.getFileName() + ext );
512 String remoteChecksumPath = remotePath + ext;
514 log.info( "Retrieving {} from {}", remoteChecksumPath, remoteRepository.getName() );
516 wagon.get( addParameters( remoteChecksumPath, remoteRepository ), destFile.toFile() );
518 log.debug( "Downloaded successfully." );
523 private String getProtocol( String url )
525 String protocol = StringUtils.substringBefore( url, ":" );
530 private Path createWorkingDirectory( String targetRepository )
533 return Files.createTempDirectory( "temp" );
536 private void moveFileIfExists( Path fileToMove, Path directory )
538 if ( fileToMove != null && Files.exists(fileToMove) )
540 Path newLocation = directory.resolve( fileToMove.getFileName() );
542 Files.deleteIfExists(newLocation);
543 } catch (IOException e) {
544 throw new RuntimeException(
545 "Unable to overwrite existing target file: " + newLocation.toAbsolutePath(), e );
549 Files.createDirectories(newLocation.getParent());
550 } catch (IOException e) {
554 Files.move(fileToMove, newLocation );
555 } catch (IOException e) {
557 Files.copy(fileToMove, newLocation);
558 } catch (IOException e1) {
559 if (Files.exists(newLocation)) {
560 log.error( "Tried to copy file {} to {} but file with this name already exists.",
561 fileToMove.getFileName(), newLocation.toAbsolutePath() );
563 throw new RuntimeException(
564 "Cannot copy tmp file " + fileToMove.toAbsolutePath() + " to its final location", e );
568 org.apache.archiva.common.utils.FileUtils.deleteQuietly(fileToMove);
573 protected String addParameters( String path, RemoteRepository remoteRepository )
575 if ( remoteRepository.getExtraParameters().isEmpty() )
580 boolean question = false;
582 StringBuilder res = new StringBuilder( path == null ? "" : path );
584 for ( Map.Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
588 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
592 return res.toString();