001package org.apache.archiva.metadata.repository.storage.maven2; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import org.apache.archiva.common.utils.VersionUtil; 023import org.apache.archiva.maven2.metadata.MavenMetadataReader; 024import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator; 025import org.apache.archiva.model.ArchivaRepositoryMetadata; 026import org.apache.archiva.model.SnapshotVersion; 027import org.apache.archiva.proxy.maven.WagonFactory; 028import org.apache.archiva.proxy.maven.WagonFactoryException; 029import org.apache.archiva.proxy.maven.WagonFactoryRequest; 030import org.apache.archiva.proxy.model.NetworkProxy; 031import org.apache.archiva.repository.ManagedRepository; 032import org.apache.archiva.repository.RemoteRepository; 033import org.apache.archiva.repository.RepositoryCredentials; 034import org.apache.archiva.repository.maven2.MavenSystemManager; 035import org.apache.archiva.repository.storage.StorageAsset; 036import org.apache.archiva.xml.XMLException; 037import org.apache.commons.lang3.StringUtils; 038import org.apache.http.auth.UsernamePasswordCredentials; 039import org.apache.maven.model.Dependency; 040import org.apache.maven.model.Parent; 041import org.apache.maven.model.Repository; 042import org.apache.maven.model.building.FileModelSource; 043import org.apache.maven.model.building.ModelSource; 044import org.apache.maven.model.resolution.InvalidRepositoryException; 045import org.apache.maven.model.resolution.ModelResolver; 046import org.apache.maven.model.resolution.UnresolvableModelException; 047import org.apache.maven.wagon.ConnectionException; 048import org.apache.maven.wagon.ResourceDoesNotExistException; 049import org.apache.maven.wagon.TransferFailedException; 050import org.apache.maven.wagon.Wagon; 051import org.apache.maven.wagon.authentication.AuthenticationException; 052import org.apache.maven.wagon.authentication.AuthenticationInfo; 053import org.apache.maven.wagon.authorization.AuthorizationException; 054import org.apache.maven.wagon.proxy.ProxyInfo; 055import org.eclipse.aether.RepositorySystemSession; 056import org.eclipse.aether.artifact.Artifact; 057import org.eclipse.aether.artifact.DefaultArtifact; 058import org.eclipse.aether.impl.VersionRangeResolver; 059import org.eclipse.aether.resolution.VersionRangeRequest; 060import org.eclipse.aether.resolution.VersionRangeResolutionException; 061import org.eclipse.aether.resolution.VersionRangeResult; 062import org.slf4j.Logger; 063import org.slf4j.LoggerFactory; 064 065import java.io.IOException; 066import java.nio.file.Files; 067import java.nio.file.Path; 068import java.nio.file.Paths; 069import java.util.HashMap; 070import java.util.List; 071import java.util.Map; 072 073public class RepositoryModelResolver 074 implements ModelResolver 075{ 076 077 private final Map<String, NetworkProxy> networkProxyMap = new HashMap<>(); 078 079 private RepositorySystemSession session; 080 private VersionRangeResolver versionRangeResolver; 081 082 private StorageAsset basedir; 083 084 private RepositoryPathTranslator pathTranslator; 085 086 private WagonFactory wagonFactory; 087 088 private List<RemoteRepository> remoteRepositories; 089 090 private ManagedRepository targetRepository; 091 092 private static final Logger log = LoggerFactory.getLogger( RepositoryModelResolver.class ); 093 094 private static final String METADATA_FILENAME = "maven-metadata.xml"; 095 096 private MavenSystemManager mavenSystemManager; 097 098 099 100 private ManagedRepository managedRepository; 101 102 public RepositoryModelResolver(StorageAsset basedir, RepositoryPathTranslator pathTranslator ) 103 { 104 this.basedir = basedir; 105 106 this.pathTranslator = pathTranslator; 107 } 108 109 public RepositoryModelResolver(ManagedRepository managedRepository, RepositoryPathTranslator pathTranslator, 110 WagonFactory wagonFactory, List<RemoteRepository> remoteRepositories, 111 Map<String, NetworkProxy> networkProxiesMap, ManagedRepository targetRepository, 112 MavenSystemManager mavenSystemManager) 113 { 114 this( managedRepository.getAsset(""), pathTranslator ); 115 116 this.managedRepository = managedRepository; 117 118 this.wagonFactory = wagonFactory; 119 120 this.remoteRepositories = remoteRepositories; 121 122 this.networkProxyMap.clear(); 123 this.networkProxyMap.putAll(networkProxiesMap); 124 125 this.targetRepository = targetRepository; 126 127 this.session = MavenSystemManager.newRepositorySystemSession( managedRepository.getAsset("").getFilePath().toString() ); 128 129 this.versionRangeResolver = mavenSystemManager.getLocator().getService(VersionRangeResolver.class); 130 131 this.mavenSystemManager = mavenSystemManager; 132 } 133 134 135 @Override 136 public ModelSource resolveModel( String groupId, String artifactId, String version ) 137 throws UnresolvableModelException 138 { 139 String filename = artifactId + "-" + version + ".pom"; 140 // TODO: we need to convert 1.0-20091120.112233-1 type paths to baseVersion for the below call - add a test 141 142 StorageAsset model = pathTranslator.toFile( basedir, groupId, artifactId, version, filename ); 143 144 if ( !model.exists() ) 145 { 146 /** 147 * 148 */ 149 // is a SNAPSHOT ? so we can try to find locally before asking remote repositories. 150 if ( StringUtils.contains( version, VersionUtil.SNAPSHOT ) ) 151 { 152 Path localSnapshotModel = findTimeStampedSnapshotPom( groupId, artifactId, version, model.getParent().getFilePath() ); 153 if ( localSnapshotModel != null ) 154 { 155 return new FileModelSource( localSnapshotModel.toFile() ); 156 } 157 158 } 159 160 for ( RemoteRepository remoteRepository : remoteRepositories ) 161 { 162 try 163 { 164 boolean success = getModelFromProxy( remoteRepository, groupId, artifactId, version, filename ); 165 if ( success && model.exists() ) 166 { 167 log.info( "Model '{}' successfully retrieved from remote repository '{}'", 168 model.getPath(), remoteRepository.getId() ); 169 break; 170 } 171 } 172 catch ( ResourceDoesNotExistException e ) 173 { 174 log.info( 175 "An exception was caught while attempting to retrieve model '{}' from remote repository '{}'.Reason:{}", 176 model.getPath(), remoteRepository.getId(), e.getMessage() ); 177 } 178 catch ( Exception e ) 179 { 180 log.warn( 181 "An exception was caught while attempting to retrieve model '{}' from remote repository '{}'.Reason:{}", 182 model.getPath(), remoteRepository.getId(), e.getMessage() ); 183 184 continue; 185 } 186 } 187 } 188 189 return new FileModelSource( model.getFilePath().toFile() ); 190 } 191 192 public ModelSource resolveModel(Parent parent) throws UnresolvableModelException { 193 try { 194 Artifact artifact = new DefaultArtifact(parent.getGroupId(), parent.getArtifactId(), "", "pom", parent.getVersion()); 195 VersionRangeRequest versionRangeRequest; 196 versionRangeRequest = new VersionRangeRequest(artifact, null, null); 197 VersionRangeResult versionRangeResult = this.versionRangeResolver.resolveVersionRange(this.session, versionRangeRequest); 198 if (versionRangeResult.getHighestVersion() == null) { 199 throw new UnresolvableModelException(String.format("No versions matched the requested parent version range '%s'", parent.getVersion()), parent.getGroupId(), parent.getArtifactId(), parent.getVersion()); 200 } else if (versionRangeResult.getVersionConstraint() != null && versionRangeResult.getVersionConstraint().getRange() != null && versionRangeResult.getVersionConstraint().getRange().getUpperBound() == null) { 201 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 } else { 203 parent.setVersion(versionRangeResult.getHighestVersion().toString()); 204 return this.resolveModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion()); 205 } 206 } catch ( VersionRangeResolutionException var5) { 207 throw new UnresolvableModelException(var5.getMessage(), parent.getGroupId(), parent.getArtifactId(), parent.getVersion(), var5); 208 } 209 } 210 211 public ModelSource resolveModel(Dependency dependency) throws UnresolvableModelException { 212 try { 213 Artifact artifact = new DefaultArtifact(dependency.getGroupId(), dependency.getArtifactId(), "", "pom", dependency.getVersion()); 214 VersionRangeRequest versionRangeRequest = new VersionRangeRequest(artifact, null, null); 215 VersionRangeResult versionRangeResult = this.versionRangeResolver.resolveVersionRange(this.session, versionRangeRequest); 216 if (versionRangeResult.getHighestVersion() == null) { 217 throw new UnresolvableModelException(String.format("No versions matched the requested dependency version range '%s'", dependency.getVersion()), dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion()); 218 } else if (versionRangeResult.getVersionConstraint() != null && versionRangeResult.getVersionConstraint().getRange() != null && versionRangeResult.getVersionConstraint().getRange().getUpperBound() == null) { 219 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 } else { 221 dependency.setVersion(versionRangeResult.getHighestVersion().toString()); 222 return this.resolveModel(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion()); 223 } 224 } catch (VersionRangeResolutionException var5) { 225 throw new UnresolvableModelException(var5.getMessage(), dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), var5); 226 } 227 } 228 229 protected Path findTimeStampedSnapshotPom( String groupId, String artifactId, String version, 230 Path parentDirectory ) 231 { 232 233 // reading metadata if there 234 Path mavenMetadata = parentDirectory.resolve( METADATA_FILENAME ); 235 if ( Files.exists(mavenMetadata) ) 236 { 237 try 238 { 239 ArchivaRepositoryMetadata archivaRepositoryMetadata = MavenMetadataReader.read( mavenMetadata); 240 SnapshotVersion snapshotVersion = archivaRepositoryMetadata.getSnapshotVersion(); 241 if ( snapshotVersion != null ) 242 { 243 String lastVersion = snapshotVersion.getTimestamp(); 244 int buildNumber = snapshotVersion.getBuildNumber(); 245 String snapshotPath = 246 StringUtils.replaceChars( groupId, '.', '/' ) + '/' + artifactId + '/' + version + '/' 247 + artifactId + '-' + StringUtils.remove( version, "-" + VersionUtil.SNAPSHOT ) + '-' 248 + lastVersion + '-' + buildNumber + ".pom"; 249 250 log.debug( "use snapshot path {} for maven coordinate {}:{}:{}", snapshotPath, groupId, artifactId, 251 version ); 252 253 StorageAsset model = basedir.resolve( snapshotPath ); 254 //model = pathTranslator.toFile( basedir, groupId, artifactId, lastVersion, filename ); 255 if ( model.exists() ) 256 { 257 return model.getFilePath(); 258 } 259 } 260 } 261 catch (XMLException e ) 262 { 263 log.warn( "fail to read {}, {}", mavenMetadata.toAbsolutePath(), e.getCause() ); 264 } 265 } 266 267 return null; 268 } 269 270 @Override 271 public void addRepository( Repository repository ) 272 throws InvalidRepositoryException 273 { 274 // we just ignore repositories outside of the current one for now 275 // 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 276 // ID since they will rarely match 277 } 278 279 @Override 280 public void addRepository( Repository repository, boolean b ) throws InvalidRepositoryException 281 { 282 283 } 284 285 @Override 286 public ModelResolver newCopy() 287 { 288 return new RepositoryModelResolver( managedRepository, pathTranslator, wagonFactory, remoteRepositories, 289 networkProxyMap, targetRepository, mavenSystemManager); 290 } 291 292 // FIXME: we need to do some refactoring, we cannot re-use the proxy components of archiva-proxy in maven2-repository 293 // because it's causing a cyclic dependency 294 private boolean getModelFromProxy( RemoteRepository remoteRepository, String groupId, String artifactId, 295 String version, String filename ) 296 throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException, WagonFactoryException, 297 XMLException, IOException 298 { 299 boolean success = false; 300 Path tmpMd5 = null; 301 Path tmpSha1 = null; 302 Path tmpResource = null; 303 String artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename ); 304 Path resource = targetRepository.getAsset("").getFilePath().resolve( artifactPath ); 305 306 Path workingDirectory = createWorkingDirectory( targetRepository.getLocation().toString() ); 307 try 308 { 309 Wagon wagon = null; 310 try 311 { 312 String protocol = getProtocol( remoteRepository.getLocation().toString() ); 313 final NetworkProxy networkProxy = this.networkProxyMap.get( remoteRepository.getId() ); 314 315 wagon = wagonFactory.getWagon( 316 new WagonFactoryRequest( "wagon#" + protocol, remoteRepository.getExtraHeaders() ).networkProxy( 317 networkProxy ) 318 ); 319 320 if ( wagon == null ) 321 { 322 throw new RuntimeException( "Unsupported remote repository protocol: " + protocol ); 323 } 324 325 boolean connected = connectToRepository( wagon, remoteRepository ); 326 if ( connected ) 327 { 328 tmpResource = workingDirectory.resolve( filename ); 329 330 if ( VersionUtil.isSnapshot( version ) ) 331 { 332 // get the metadata first! 333 Path tmpMetadataResource = workingDirectory.resolve( METADATA_FILENAME ); 334 335 String metadataPath = 336 StringUtils.substringBeforeLast( artifactPath, "/" ) + "/" + METADATA_FILENAME; 337 338 wagon.get( addParameters( metadataPath, remoteRepository ), tmpMetadataResource.toFile() ); 339 340 log.debug( "Successfully downloaded metadata." ); 341 342 ArchivaRepositoryMetadata metadata = MavenMetadataReader.read( tmpMetadataResource ); 343 344 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename 345 SnapshotVersion snapshotVersion = metadata.getSnapshotVersion(); 346 String timestampVersion = version; 347 if ( snapshotVersion != null ) 348 { 349 timestampVersion = timestampVersion.substring( 0, timestampVersion.length() 350 - 8 ); // remove SNAPSHOT from end 351 timestampVersion = timestampVersion + snapshotVersion.getTimestamp() + "-" 352 + snapshotVersion.getBuildNumber(); 353 354 filename = artifactId + "-" + timestampVersion + ".pom"; 355 356 artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename ); 357 358 log.debug( "New artifactPath :{}", artifactPath ); 359 } 360 } 361 362 log.info( "Retrieving {} from {}", artifactPath, remoteRepository.getName() ); 363 364 wagon.get( addParameters( artifactPath, remoteRepository ), tmpResource.toFile() ); 365 366 log.debug( "Downloaded successfully." ); 367 368 tmpSha1 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory, 369 ".sha1" ); 370 tmpMd5 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory, 371 ".md5" ); 372 } 373 } 374 finally 375 { 376 if ( wagon != null ) 377 { 378 try 379 { 380 wagon.disconnect(); 381 } 382 catch ( ConnectionException e ) 383 { 384 log.warn( "Unable to disconnect wagon.", e ); 385 } 386 } 387 } 388 389 if ( resource != null ) 390 { 391 synchronized ( resource.toAbsolutePath().toString().intern() ) 392 { 393 Path directory = resource.getParent(); 394 moveFileIfExists( tmpMd5, directory ); 395 moveFileIfExists( tmpSha1, directory ); 396 moveFileIfExists( tmpResource, directory ); 397 success = true; 398 } 399 } 400 } 401 finally 402 { 403 org.apache.archiva.common.utils.FileUtils.deleteQuietly( workingDirectory ); 404 } 405 406 // do we still need to execute the consumers? 407 408 return success; 409 } 410 411 /** 412 * Using wagon, connect to the remote repository. 413 * 414 * @param wagon the wagon instance to establish the connection on. 415 * @return true if the connection was successful. false if not connected. 416 */ 417 private boolean connectToRepository( Wagon wagon, RemoteRepository remoteRepository ) 418 { 419 boolean connected; 420 421 final NetworkProxy proxyConnector = this.networkProxyMap.get( remoteRepository.getId() ); 422 ProxyInfo networkProxy = null; 423 if ( proxyConnector != null ) 424 { 425 networkProxy = new ProxyInfo(); 426 networkProxy.setType( proxyConnector.getProtocol() ); 427 networkProxy.setHost( proxyConnector.getHost() ); 428 networkProxy.setPort( proxyConnector.getPort() ); 429 networkProxy.setUserName( proxyConnector.getUsername() ); 430 networkProxy.setPassword( new String(proxyConnector.getPassword()) ); 431 432 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort() 433 + " to connect to remote repository " + remoteRepository.getLocation(); 434 if ( networkProxy.getNonProxyHosts() != null ) 435 { 436 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts(); 437 } 438 439 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) ) 440 { 441 msg += "; as user: " + networkProxy.getUserName(); 442 } 443 444 log.debug( msg ); 445 } 446 447 AuthenticationInfo authInfo = null; 448 RepositoryCredentials creds = remoteRepository.getLoginCredentials(); 449 String username = ""; 450 String password = ""; 451 if (creds instanceof UsernamePasswordCredentials) { 452 UsernamePasswordCredentials c = (UsernamePasswordCredentials) creds; 453 username = c.getUserName(); 454 password = c.getPassword(); 455 } 456 457 if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) ) 458 { 459 log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getLocation() ); 460 authInfo = new AuthenticationInfo(); 461 authInfo.setUserName( username ); 462 authInfo.setPassword( password ); 463 } 464 465 int timeoutInMilliseconds = ((int)remoteRepository.getTimeout().getSeconds())*1000; 466 // FIXME olamy having 2 config values 467 // Set timeout 468 wagon.setReadTimeout( timeoutInMilliseconds ); 469 wagon.setTimeout( timeoutInMilliseconds ); 470 471 try 472 { 473 org.apache.maven.wagon.repository.Repository wagonRepository = 474 new org.apache.maven.wagon.repository.Repository( remoteRepository.getId(), remoteRepository.getLocation().toString() ); 475 if ( networkProxy != null ) 476 { 477 wagon.connect( wagonRepository, authInfo, networkProxy ); 478 } 479 else 480 { 481 wagon.connect( wagonRepository, authInfo ); 482 } 483 connected = true; 484 } 485 catch ( ConnectionException | AuthenticationException e ) 486 { 487 log.error( "Could not connect to {}:{} ", remoteRepository.getName(), e.getMessage() ); 488 connected = false; 489 } 490 491 return connected; 492 } 493 494 /** 495 * 496 * @param wagon The wagon instance that should be connected. 497 * @param remoteRepository The repository from where the checksum file should be retrieved 498 * @param remotePath The remote path of the artifact (without extension) 499 * @param resource The local artifact (without extension) 500 * @param workingDir The working directory where the downloaded file should be placed to 501 * @param ext The extension of th checksum file 502 * @return The file where the data has been downloaded to. 503 * @throws AuthorizationException 504 * @throws TransferFailedException 505 * @throws ResourceDoesNotExistException 506 */ 507 private Path transferChecksum( final Wagon wagon, final RemoteRepository remoteRepository, 508 final String remotePath, final Path resource, 509 final Path workingDir, final String ext ) 510 throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException 511 { 512 Path destFile = workingDir.resolve( resource.getFileName() + ext ); 513 String remoteChecksumPath = remotePath + ext; 514 515 log.info( "Retrieving {} from {}", remoteChecksumPath, remoteRepository.getName() ); 516 517 wagon.get( addParameters( remoteChecksumPath, remoteRepository ), destFile.toFile() ); 518 519 log.debug( "Downloaded successfully." ); 520 521 return destFile; 522 } 523 524 private String getProtocol( String url ) 525 { 526 String protocol = StringUtils.substringBefore( url, ":" ); 527 528 return protocol; 529 } 530 531 private Path createWorkingDirectory( String targetRepository ) 532 throws IOException 533 { 534 return Files.createTempDirectory( "temp" ); 535 } 536 537 private void moveFileIfExists( Path fileToMove, Path directory ) 538 { 539 if ( fileToMove != null && Files.exists(fileToMove) ) 540 { 541 Path newLocation = directory.resolve( fileToMove.getFileName() ); 542 try { 543 Files.deleteIfExists(newLocation); 544 } catch (IOException e) { 545 throw new RuntimeException( 546 "Unable to overwrite existing target file: " + newLocation.toAbsolutePath(), e ); 547 } 548 549 try { 550 Files.createDirectories(newLocation.getParent()); 551 } catch (IOException e) { 552 e.printStackTrace(); 553 } 554 try { 555 Files.move(fileToMove, newLocation ); 556 } catch (IOException e) { 557 try { 558 Files.copy(fileToMove, newLocation); 559 } catch (IOException e1) { 560 if (Files.exists(newLocation)) { 561 log.error( "Tried to copy file {} to {} but file with this name already exists.", 562 fileToMove.getFileName(), newLocation.toAbsolutePath() ); 563 } else { 564 throw new RuntimeException( 565 "Cannot copy tmp file " + fileToMove.toAbsolutePath() + " to its final location", e ); 566 } 567 } 568 } finally { 569 org.apache.archiva.common.utils.FileUtils.deleteQuietly(fileToMove); 570 } 571 } 572 } 573 574 protected String addParameters( String path, RemoteRepository remoteRepository ) 575 { 576 if ( remoteRepository.getExtraParameters().isEmpty() ) 577 { 578 return path; 579 } 580 581 boolean question = false; 582 583 StringBuilder res = new StringBuilder( path == null ? "" : path ); 584 585 for ( Map.Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() ) 586 { 587 if ( !question ) 588 { 589 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() ); 590 } 591 } 592 593 return res.toString(); 594 } 595}