1 package org.apache.archiva.repository.maven.metadata.storage;
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
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
21 import org.apache.archiva.common.utils.VersionUtil;
22 import org.apache.archiva.metadata.maven.MavenMetadataReader;
23 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
24 import org.apache.archiva.model.ArchivaRepositoryMetadata;
25 import org.apache.archiva.model.SnapshotVersion;
26 import org.apache.archiva.proxy.maven.WagonFactory;
27 import org.apache.archiva.proxy.maven.WagonFactoryException;
28 import org.apache.archiva.proxy.maven.WagonFactoryRequest;
29 import org.apache.archiva.proxy.model.NetworkProxy;
30 import org.apache.archiva.repository.ManagedRepository;
31 import org.apache.archiva.repository.RemoteRepository;
32 import org.apache.archiva.repository.RepositoryCredentials;
33 import org.apache.archiva.repository.maven.MavenSystemManager;
34 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
35 import org.apache.archiva.repository.storage.StorageAsset;
36 import org.apache.commons.lang3.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.util.HashMap;
68 import java.util.List;
71 public class RepositoryModelResolver
72 implements ModelResolver
75 private final Map<String, NetworkProxy> networkProxyMap = new HashMap<>();
77 private RepositorySystemSession session;
78 private VersionRangeResolver versionRangeResolver;
80 private StorageAsset basedir;
82 private RepositoryPathTranslator pathTranslator;
84 private WagonFactory wagonFactory;
86 private List<RemoteRepository> remoteRepositories;
88 private ManagedRepository targetRepository;
90 private static final Logger log = LoggerFactory.getLogger( RepositoryModelResolver.class );
92 private static final String METADATA_FILENAME = "maven-metadata.xml";
94 private MavenSystemManager mavenSystemManager;
96 private MavenMetadataReader metadataReader;
100 private ManagedRepository managedRepository;
102 public RepositoryModelResolver(StorageAsset basedir, RepositoryPathTranslator pathTranslator)
104 this.basedir = basedir;
106 this.pathTranslator = pathTranslator;
110 public RepositoryModelResolver(ManagedRepository managedRepository, RepositoryPathTranslator pathTranslator,
111 WagonFactory wagonFactory, List<RemoteRepository> remoteRepositories,
112 Map<String, NetworkProxy> networkProxiesMap, ManagedRepository targetRepository,
113 MavenSystemManager mavenSystemManager, MavenMetadataReader metadataReader)
115 this( managedRepository.getRoot(), pathTranslator );
117 this.managedRepository = managedRepository;
119 this.wagonFactory = wagonFactory;
121 this.remoteRepositories = remoteRepositories;
123 this.networkProxyMap.clear();
124 this.networkProxyMap.putAll(networkProxiesMap);
126 this.targetRepository = targetRepository;
128 this.session = MavenSystemManager.newRepositorySystemSession( managedRepository.getRoot().getFilePath().toString() );
130 this.versionRangeResolver = mavenSystemManager.getLocator().getService(VersionRangeResolver.class);
132 this.mavenSystemManager = mavenSystemManager;
133 this.metadataReader = metadataReader;
138 public ModelSource resolveModel( String groupId, String artifactId, String version )
139 throws UnresolvableModelException
141 String filename = artifactId + "-" + version + ".pom";
142 // TODO: we need to convert 1.0-20091120.112233-1 type paths to baseVersion for the below call - add a test
144 StorageAsset model = pathTranslator.toFile( basedir, groupId, artifactId, version, filename );
146 if ( !model.exists() )
151 // is a SNAPSHOT ? so we can try to find locally before asking remote repositories.
152 if ( StringUtils.contains( version, VersionUtil.SNAPSHOT ) )
154 Path localSnapshotModel = findTimeStampedSnapshotPom( groupId, artifactId, version, model.getParent().getFilePath() );
155 if ( localSnapshotModel != null )
157 return new FileModelSource( localSnapshotModel.toFile() );
162 for ( RemoteRepository remoteRepository : remoteRepositories )
166 boolean success = getModelFromProxy( remoteRepository, groupId, artifactId, version, filename );
167 if ( success && model.exists() )
169 log.info( "Model '{}' successfully retrieved from remote repository '{}'",
170 model.getPath(), remoteRepository.getId() );
174 catch ( ResourceDoesNotExistException e )
177 "An exception was caught while attempting to retrieve model '{}' from remote repository '{}'.Reason:{}",
178 model.getPath(), remoteRepository.getId(), e.getMessage() );
180 catch ( Exception e )
183 "An exception was caught while attempting to retrieve model '{}' from remote repository '{}'.Reason:{}",
184 model.getPath(), remoteRepository.getId(), e.getMessage() );
191 return new FileModelSource( model.getFilePath().toFile() );
194 public ModelSource resolveModel(Parent parent) throws UnresolvableModelException {
196 Artifact artifact = new DefaultArtifact(parent.getGroupId(), parent.getArtifactId(), "", "pom", parent.getVersion());
197 VersionRangeRequest versionRangeRequest;
198 versionRangeRequest = new VersionRangeRequest(artifact, null, null);
199 VersionRangeResult versionRangeResult = this.versionRangeResolver.resolveVersionRange(this.session, versionRangeRequest);
200 if (versionRangeResult.getHighestVersion() == null) {
201 throw new UnresolvableModelException(String.format("No versions matched the requested parent version range '%s'", parent.getVersion()), parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
202 } else if (versionRangeResult.getVersionConstraint() != null && versionRangeResult.getVersionConstraint().getRange() != null && versionRangeResult.getVersionConstraint().getRange().getUpperBound() == null) {
203 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());
205 parent.setVersion(versionRangeResult.getHighestVersion().toString());
206 return this.resolveModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
208 } catch ( VersionRangeResolutionException var5) {
209 throw new UnresolvableModelException(var5.getMessage(), parent.getGroupId(), parent.getArtifactId(), parent.getVersion(), var5);
213 public ModelSource resolveModel(Dependency dependency) throws UnresolvableModelException {
215 Artifact artifact = new DefaultArtifact(dependency.getGroupId(), dependency.getArtifactId(), "", "pom", dependency.getVersion());
216 VersionRangeRequest versionRangeRequest = new VersionRangeRequest(artifact, null, null);
217 VersionRangeResult versionRangeResult = this.versionRangeResolver.resolveVersionRange(this.session, versionRangeRequest);
218 if (versionRangeResult.getHighestVersion() == null) {
219 throw new UnresolvableModelException(String.format("No versions matched the requested dependency version range '%s'", dependency.getVersion()), dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion());
220 } else if (versionRangeResult.getVersionConstraint() != null && versionRangeResult.getVersionConstraint().getRange() != null && versionRangeResult.getVersionConstraint().getRange().getUpperBound() == null) {
221 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());
223 dependency.setVersion(versionRangeResult.getHighestVersion().toString());
224 return this.resolveModel(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion());
226 } catch (VersionRangeResolutionException var5) {
227 throw new UnresolvableModelException(var5.getMessage(), dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), var5);
231 protected Path findTimeStampedSnapshotPom( String groupId, String artifactId, String version,
232 Path parentDirectory )
235 // reading metadata if there
236 Path mavenMetadata = parentDirectory.resolve( METADATA_FILENAME );
237 if ( Files.exists(mavenMetadata) )
241 ArchivaRepositoryMetadata archivaRepositoryMetadata = metadataReader.read( mavenMetadata );
242 SnapshotVersion snapshotVersion = archivaRepositoryMetadata.getSnapshotVersion();
243 if ( snapshotVersion != null )
245 String lastVersion = snapshotVersion.getTimestamp();
246 int buildNumber = snapshotVersion.getBuildNumber();
247 String snapshotPath =
248 StringUtils.replaceChars( groupId, '.', '/' ) + '/' + artifactId + '/' + version + '/'
249 + artifactId + '-' + StringUtils.remove( version, "-" + VersionUtil.SNAPSHOT ) + '-'
250 + lastVersion + '-' + buildNumber + ".pom";
252 log.debug( "use snapshot path {} for maven coordinate {}:{}:{}", snapshotPath, groupId, artifactId,
255 StorageAsset model = basedir.resolve( snapshotPath );
256 //model = pathTranslator.toFile( basedir, groupId, artifactId, lastVersion, filename );
257 if ( model.exists() )
259 return model.getFilePath();
263 catch ( RepositoryMetadataException e )
265 log.warn( "fail to read {}, {}", mavenMetadata.toAbsolutePath(), e.getCause() );
273 public void addRepository( Repository repository )
274 throws InvalidRepositoryException
276 // we just ignore repositories outside of the current one for now
277 // 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
278 // ID since they will rarely match
282 public void addRepository( Repository repository, boolean b ) throws InvalidRepositoryException
288 public ModelResolver newCopy()
290 return new RepositoryModelResolver( managedRepository, pathTranslator, wagonFactory, remoteRepositories,
291 networkProxyMap, targetRepository, mavenSystemManager, metadataReader);
294 // FIXME: we need to do some refactoring, we cannot re-use the proxy components of archiva-proxy in maven2-repository
295 // because it's causing a cyclic dependency
296 private boolean getModelFromProxy( RemoteRepository remoteRepository, String groupId, String artifactId,
297 String version, String filename )
298 throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException, WagonFactoryException,
299 IOException, RepositoryMetadataException
301 boolean success = false;
304 Path tmpResource = null;
305 String artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename );
306 Path resource = targetRepository.getRoot().getFilePath().resolve( artifactPath );
308 Path workingDirectory = createWorkingDirectory( targetRepository.getLocation().toString() );
314 String protocol = getProtocol( remoteRepository.getLocation().toString() );
315 final NetworkProxy networkProxy = this.networkProxyMap.get( remoteRepository.getId() );
317 wagon = wagonFactory.getWagon(
318 new WagonFactoryRequest( "wagon#" + protocol, remoteRepository.getExtraHeaders() ).networkProxy(
324 throw new RuntimeException( "Unsupported remote repository protocol: " + protocol );
327 boolean connected = connectToRepository( wagon, remoteRepository );
330 tmpResource = workingDirectory.resolve( filename );
332 if ( VersionUtil.isSnapshot( version ) )
334 // get the metadata first!
335 Path tmpMetadataResource = workingDirectory.resolve( METADATA_FILENAME );
337 String metadataPath =
338 StringUtils.substringBeforeLast( artifactPath, "/" ) + "/" + METADATA_FILENAME;
340 wagon.get( addParameters( metadataPath, remoteRepository ), tmpMetadataResource.toFile() );
342 log.debug( "Successfully downloaded metadata." );
344 ArchivaRepositoryMetadata metadata = metadataReader.read( tmpMetadataResource );
346 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
347 SnapshotVersion snapshotVersion = metadata.getSnapshotVersion();
348 String timestampVersion = version;
349 if ( snapshotVersion != null )
351 timestampVersion = timestampVersion.substring( 0, timestampVersion.length()
352 - 8 ); // remove SNAPSHOT from end
353 timestampVersion = timestampVersion + snapshotVersion.getTimestamp() + "-"
354 + snapshotVersion.getBuildNumber();
356 filename = artifactId + "-" + timestampVersion + ".pom";
358 artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename );
360 log.debug( "New artifactPath :{}", artifactPath );
364 log.info( "Retrieving {} from {}", artifactPath, remoteRepository.getName() );
366 wagon.get( addParameters( artifactPath, remoteRepository ), tmpResource.toFile() );
368 log.debug( "Downloaded successfully." );
370 tmpSha1 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
372 tmpMd5 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
384 catch ( ConnectionException e )
386 log.warn( "Unable to disconnect wagon.", e );
391 if ( resource != null )
393 synchronized ( resource.toAbsolutePath().toString().intern() )
395 Path directory = resource.getParent();
396 moveFileIfExists( tmpMd5, directory );
397 moveFileIfExists( tmpSha1, directory );
398 moveFileIfExists( tmpResource, directory );
405 org.apache.archiva.common.utils.FileUtils.deleteQuietly( workingDirectory );
408 // do we still need to execute the consumers?
414 * Using wagon, connect to the remote repository.
416 * @param wagon the wagon instance to establish the connection on.
417 * @return true if the connection was successful. false if not connected.
419 private boolean connectToRepository( Wagon wagon, RemoteRepository remoteRepository )
423 final NetworkProxy proxyConnector = this.networkProxyMap.get( remoteRepository.getId() );
424 ProxyInfo networkProxy = null;
425 if ( proxyConnector != null )
427 networkProxy = new ProxyInfo();
428 networkProxy.setType( proxyConnector.getProtocol() );
429 networkProxy.setHost( proxyConnector.getHost() );
430 networkProxy.setPort( proxyConnector.getPort() );
431 networkProxy.setUserName( proxyConnector.getUsername() );
432 networkProxy.setPassword( new String(proxyConnector.getPassword()) );
434 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
435 + " to connect to remote repository " + remoteRepository.getLocation();
436 if ( networkProxy.getNonProxyHosts() != null )
438 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
441 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
443 msg += "; as user: " + networkProxy.getUserName();
449 AuthenticationInfo authInfo = null;
450 RepositoryCredentials creds = remoteRepository.getLoginCredentials();
451 String username = "";
452 String password = "";
453 if (creds instanceof UsernamePasswordCredentials) {
454 UsernamePasswordCredentials c = (UsernamePasswordCredentials) creds;
455 username = c.getUserName();
456 password = c.getPassword();
459 if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
461 log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getLocation() );
462 authInfo = new AuthenticationInfo();
463 authInfo.setUserName( username );
464 authInfo.setPassword( password );
467 int timeoutInMilliseconds = ((int)remoteRepository.getTimeout().getSeconds())*1000;
468 // FIXME olamy having 2 config values
470 wagon.setReadTimeout( timeoutInMilliseconds );
471 wagon.setTimeout( timeoutInMilliseconds );
475 org.apache.maven.wagon.repository.Repository wagonRepository =
476 new org.apache.maven.wagon.repository.Repository( remoteRepository.getId(), remoteRepository.getLocation().toString() );
477 if ( networkProxy != null )
479 wagon.connect( wagonRepository, authInfo, networkProxy );
483 wagon.connect( wagonRepository, authInfo );
487 catch ( ConnectionException | AuthenticationException e )
489 log.error( "Could not connect to {}:{} ", remoteRepository.getName(), e.getMessage() );
498 * @param wagon The wagon instance that should be connected.
499 * @param remoteRepository The repository from where the checksum file should be retrieved
500 * @param remotePath The remote path of the artifact (without extension)
501 * @param resource The local artifact (without extension)
502 * @param workingDir The working directory where the downloaded file should be placed to
503 * @param ext The extension of th checksum file
504 * @return The file where the data has been downloaded to.
505 * @throws AuthorizationException
506 * @throws TransferFailedException
507 * @throws ResourceDoesNotExistException
509 private Path transferChecksum( final Wagon wagon, final RemoteRepository remoteRepository,
510 final String remotePath, final Path resource,
511 final Path workingDir, final String ext )
512 throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException
514 Path destFile = workingDir.resolve( resource.getFileName() + ext );
515 String remoteChecksumPath = remotePath + ext;
517 log.info( "Retrieving {} from {}", remoteChecksumPath, remoteRepository.getName() );
519 wagon.get( addParameters( remoteChecksumPath, remoteRepository ), destFile.toFile() );
521 log.debug( "Downloaded successfully." );
526 private String getProtocol( String url )
528 String protocol = StringUtils.substringBefore( url, ":" );
533 private Path createWorkingDirectory( String targetRepository )
536 return Files.createTempDirectory( "temp" );
539 private void moveFileIfExists( Path fileToMove, Path directory )
541 if ( fileToMove != null && Files.exists(fileToMove) )
543 Path newLocation = directory.resolve( fileToMove.getFileName() );
545 Files.deleteIfExists(newLocation);
546 } catch (IOException e) {
547 throw new RuntimeException(
548 "Unable to overwrite existing target file: " + newLocation.toAbsolutePath(), e );
552 Files.createDirectories(newLocation.getParent());
553 } catch (IOException e) {
557 Files.move(fileToMove, newLocation );
558 } catch (IOException e) {
560 Files.copy(fileToMove, newLocation);
561 } catch (IOException e1) {
562 if (Files.exists(newLocation)) {
563 log.error( "Tried to copy file {} to {} but file with this name already exists.",
564 fileToMove.getFileName(), newLocation.toAbsolutePath() );
566 throw new RuntimeException(
567 "Cannot copy tmp file " + fileToMove.toAbsolutePath() + " to its final location", e );
571 org.apache.archiva.common.utils.FileUtils.deleteQuietly(fileToMove);
576 protected String addParameters( String path, RemoteRepository remoteRepository )
578 if ( remoteRepository.getExtraParameters().isEmpty() )
583 boolean question = false;
585 StringBuilder res = new StringBuilder( path == null ? "" : path );
587 for ( Map.Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
591 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
595 return res.toString();