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.admin.model.beans.ManagedRepository; 023import org.apache.archiva.admin.model.beans.NetworkProxy; 024import org.apache.archiva.admin.model.beans.RemoteRepository; 025import org.apache.archiva.common.utils.VersionUtil; 026import org.apache.archiva.maven2.metadata.MavenMetadataReader; 027import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator; 028import org.apache.archiva.model.ArchivaRepositoryMetadata; 029import org.apache.archiva.model.SnapshotVersion; 030import org.apache.archiva.proxy.common.WagonFactory; 031import org.apache.archiva.proxy.common.WagonFactoryException; 032import org.apache.archiva.proxy.common.WagonFactoryRequest; 033import org.apache.archiva.xml.XMLException; 034import org.apache.commons.io.FileUtils; 035import org.apache.commons.lang.StringUtils; 036import org.apache.maven.model.Repository; 037import org.apache.maven.model.building.FileModelSource; 038import org.apache.maven.model.building.ModelSource; 039import org.apache.maven.model.resolution.InvalidRepositoryException; 040import org.apache.maven.model.resolution.ModelResolver; 041import org.apache.maven.model.resolution.UnresolvableModelException; 042import org.apache.maven.wagon.ConnectionException; 043import org.apache.maven.wagon.ResourceDoesNotExistException; 044import org.apache.maven.wagon.TransferFailedException; 045import org.apache.maven.wagon.Wagon; 046import org.apache.maven.wagon.authentication.AuthenticationException; 047import org.apache.maven.wagon.authentication.AuthenticationInfo; 048import org.apache.maven.wagon.authorization.AuthorizationException; 049import org.apache.maven.wagon.proxy.ProxyInfo; 050import org.slf4j.Logger; 051import org.slf4j.LoggerFactory; 052 053import java.io.File; 054import java.io.IOException; 055import java.nio.file.Files; 056import java.util.List; 057import java.util.Map; 058 059public class RepositoryModelResolver 060 implements ModelResolver 061{ 062 private File basedir; 063 064 private RepositoryPathTranslator pathTranslator; 065 066 private WagonFactory wagonFactory; 067 068 private List<RemoteRepository> remoteRepositories; 069 070 private ManagedRepository targetRepository; 071 072 private static final Logger log = LoggerFactory.getLogger( RepositoryModelResolver.class ); 073 074 private static final String METADATA_FILENAME = "maven-metadata.xml"; 075 076 // key/value: remote repo ID/network proxy 077 Map<String, NetworkProxy> networkProxyMap; 078 079 private ManagedRepository managedRepository; 080 081 public RepositoryModelResolver( File basedir, RepositoryPathTranslator pathTranslator ) 082 { 083 this.basedir = basedir; 084 085 this.pathTranslator = pathTranslator; 086 } 087 088 public RepositoryModelResolver( ManagedRepository managedRepository, RepositoryPathTranslator pathTranslator, 089 WagonFactory wagonFactory, List<RemoteRepository> remoteRepositories, 090 Map<String, NetworkProxy> networkProxiesMap, ManagedRepository targetRepository ) 091 { 092 this( new File( managedRepository.getLocation() ), pathTranslator ); 093 094 this.managedRepository = managedRepository; 095 096 this.wagonFactory = wagonFactory; 097 098 this.remoteRepositories = remoteRepositories; 099 100 this.networkProxyMap = networkProxiesMap; 101 102 this.targetRepository = targetRepository; 103 } 104 105 @Override 106 public ModelSource resolveModel( String groupId, String artifactId, String version ) 107 throws UnresolvableModelException 108 { 109 String filename = artifactId + "-" + version + ".pom"; 110 // TODO: we need to convert 1.0-20091120.112233-1 type paths to baseVersion for the below call - add a test 111 112 File model = pathTranslator.toFile( basedir, groupId, artifactId, version, filename ); 113 114 if ( !model.exists() ) 115 { 116 /** 117 * 118 */ 119 // is a SNAPSHOT ? so we can try to find locally before asking remote repositories. 120 if ( StringUtils.contains( version, VersionUtil.SNAPSHOT ) ) 121 { 122 File localSnapshotModel = findTimeStampedSnapshotPom( groupId, artifactId, version, model.getParent() ); 123 if ( localSnapshotModel != null ) 124 { 125 return new FileModelSource( localSnapshotModel ); 126 } 127 128 } 129 130 for ( RemoteRepository remoteRepository : remoteRepositories ) 131 { 132 try 133 { 134 boolean success = getModelFromProxy( remoteRepository, groupId, artifactId, version, filename ); 135 if ( success && model.exists() ) 136 { 137 log.info( "Model '{}' successfully retrieved from remote repository '{}'", 138 model.getAbsolutePath(), remoteRepository.getId() ); 139 break; 140 } 141 } 142 catch ( ResourceDoesNotExistException e ) 143 { 144 log.info( 145 "An exception was caught while attempting to retrieve model '{}' from remote repository '{}'.Reason:{}", 146 model.getAbsolutePath(), remoteRepository.getId(), e.getMessage() ); 147 } 148 catch ( Exception e ) 149 { 150 log.warn( 151 "An exception was caught while attempting to retrieve model '{}' from remote repository '{}'.Reason:{}", 152 model.getAbsolutePath(), remoteRepository.getId(), e.getMessage() ); 153 154 continue; 155 } 156 } 157 } 158 159 return new FileModelSource( model ); 160 } 161 162 protected File findTimeStampedSnapshotPom( String groupId, String artifactId, String version, 163 String parentDirectory ) 164 { 165 166 // reading metadata if there 167 File mavenMetadata = new File( parentDirectory, METADATA_FILENAME ); 168 if ( mavenMetadata.exists() ) 169 { 170 try 171 { 172 ArchivaRepositoryMetadata archivaRepositoryMetadata = MavenMetadataReader.read( mavenMetadata ); 173 SnapshotVersion snapshotVersion = archivaRepositoryMetadata.getSnapshotVersion(); 174 if ( snapshotVersion != null ) 175 { 176 String lastVersion = snapshotVersion.getTimestamp(); 177 int buildNumber = snapshotVersion.getBuildNumber(); 178 String snapshotPath = 179 StringUtils.replaceChars( groupId, '.', '/' ) + '/' + artifactId + '/' + version + '/' 180 + artifactId + '-' + StringUtils.remove( version, "-" + VersionUtil.SNAPSHOT ) + '-' 181 + lastVersion + '-' + buildNumber + ".pom"; 182 183 log.debug( "use snapshot path {} for maven coordinate {}:{}:{}", snapshotPath, groupId, artifactId, 184 version ); 185 186 File model = new File( basedir, snapshotPath ); 187 //model = pathTranslator.toFile( basedir, groupId, artifactId, lastVersion, filename ); 188 if ( model.exists() ) 189 { 190 return model; 191 } 192 } 193 } 194 catch ( XMLException e ) 195 { 196 log.warn( "fail to read {}, {}", mavenMetadata.getAbsolutePath(), e.getCause() ); 197 } 198 } 199 200 return null; 201 } 202 203 @Override 204 public void addRepository( Repository repository ) 205 throws InvalidRepositoryException 206 { 207 // we just ignore repositories outside of the current one for now 208 // 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 209 // ID since they will rarely match 210 } 211 212 @Override 213 public ModelResolver newCopy() 214 { 215 return new RepositoryModelResolver( managedRepository, pathTranslator, wagonFactory, remoteRepositories, 216 networkProxyMap, targetRepository ); 217 } 218 219 // FIXME: we need to do some refactoring, we cannot re-use the proxy components of archiva-proxy in maven2-repository 220 // because it's causing a cyclic dependency 221 private boolean getModelFromProxy( RemoteRepository remoteRepository, String groupId, String artifactId, 222 String version, String filename ) 223 throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException, WagonFactoryException, 224 XMLException, IOException 225 { 226 boolean success = false; 227 File tmpMd5 = null; 228 File tmpSha1 = null; 229 File tmpResource = null; 230 String artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename ); 231 File resource = new File( targetRepository.getLocation(), artifactPath ); 232 233 File workingDirectory = createWorkingDirectory( targetRepository.getLocation() ); 234 try 235 { 236 Wagon wagon = null; 237 try 238 { 239 String protocol = getProtocol( remoteRepository.getUrl() ); 240 final NetworkProxy networkProxy = this.networkProxyMap.get( remoteRepository.getId() ); 241 242 wagon = wagonFactory.getWagon( 243 new WagonFactoryRequest( "wagon#" + protocol, remoteRepository.getExtraHeaders() ).networkProxy( 244 networkProxy ) 245 ); 246 247 if ( wagon == null ) 248 { 249 throw new RuntimeException( "Unsupported remote repository protocol: " + protocol ); 250 } 251 252 boolean connected = connectToRepository( wagon, remoteRepository ); 253 if ( connected ) 254 { 255 tmpResource = new File( workingDirectory, filename ); 256 257 if ( VersionUtil.isSnapshot( version ) ) 258 { 259 // get the metadata first! 260 File tmpMetadataResource = new File( workingDirectory, METADATA_FILENAME ); 261 262 String metadataPath = 263 StringUtils.substringBeforeLast( artifactPath, "/" ) + "/" + METADATA_FILENAME; 264 265 wagon.get( addParameters( metadataPath, remoteRepository ), tmpMetadataResource ); 266 267 log.debug( "Successfully downloaded metadata." ); 268 269 ArchivaRepositoryMetadata metadata = MavenMetadataReader.read( tmpMetadataResource ); 270 271 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename 272 SnapshotVersion snapshotVersion = metadata.getSnapshotVersion(); 273 String timestampVersion = version; 274 if ( snapshotVersion != null ) 275 { 276 timestampVersion = timestampVersion.substring( 0, timestampVersion.length() 277 - 8 ); // remove SNAPSHOT from end 278 timestampVersion = timestampVersion + snapshotVersion.getTimestamp() + "-" 279 + snapshotVersion.getBuildNumber(); 280 281 filename = artifactId + "-" + timestampVersion + ".pom"; 282 283 artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename ); 284 285 log.debug( "New artifactPath :{}", artifactPath ); 286 } 287 } 288 289 log.info( "Retrieving {} from {}", artifactPath, remoteRepository.getName() ); 290 291 wagon.get( addParameters( artifactPath, remoteRepository ), tmpResource ); 292 293 log.debug( "Downloaded successfully." ); 294 295 tmpSha1 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory, 296 ".sha1" ); 297 tmpMd5 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory, 298 ".md5" ); 299 } 300 } 301 finally 302 { 303 if ( wagon != null ) 304 { 305 try 306 { 307 wagon.disconnect(); 308 } 309 catch ( ConnectionException e ) 310 { 311 log.warn( "Unable to disconnect wagon.", e ); 312 } 313 } 314 } 315 316 if ( resource != null ) 317 { 318 synchronized ( resource.getAbsolutePath().intern() ) 319 { 320 File directory = resource.getParentFile(); 321 moveFileIfExists( tmpMd5, directory ); 322 moveFileIfExists( tmpSha1, directory ); 323 moveFileIfExists( tmpResource, directory ); 324 success = true; 325 } 326 } 327 } 328 finally 329 { 330 FileUtils.deleteQuietly( workingDirectory ); 331 } 332 333 // do we still need to execute the consumers? 334 335 return success; 336 } 337 338 /** 339 * Using wagon, connect to the remote repository. 340 * 341 * @param wagon the wagon instance to establish the connection on. 342 * @return true if the connection was successful. false if not connected. 343 */ 344 private boolean connectToRepository( Wagon wagon, RemoteRepository remoteRepository ) 345 { 346 boolean connected; 347 348 final NetworkProxy proxyConnector = this.networkProxyMap.get( remoteRepository.getId() ); 349 ProxyInfo networkProxy = null; 350 if ( proxyConnector != null ) 351 { 352 networkProxy = new ProxyInfo(); 353 networkProxy.setType( proxyConnector.getProtocol() ); 354 networkProxy.setHost( proxyConnector.getHost() ); 355 networkProxy.setPort( proxyConnector.getPort() ); 356 networkProxy.setUserName( proxyConnector.getUsername() ); 357 networkProxy.setPassword( proxyConnector.getPassword() ); 358 359 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort() 360 + " to connect to remote repository " + remoteRepository.getUrl(); 361 if ( networkProxy.getNonProxyHosts() != null ) 362 { 363 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts(); 364 } 365 366 if ( StringUtils.isNotBlank( networkProxy.getUserName() ) ) 367 { 368 msg += "; as user: " + networkProxy.getUserName(); 369 } 370 371 log.debug( msg ); 372 } 373 374 AuthenticationInfo authInfo = null; 375 String username = remoteRepository.getUserName(); 376 String password = remoteRepository.getPassword(); 377 378 if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) ) 379 { 380 log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getUrl() ); 381 authInfo = new AuthenticationInfo(); 382 authInfo.setUserName( username ); 383 authInfo.setPassword( password ); 384 } 385 386 // Convert seconds to milliseconds 387 int timeoutInMilliseconds = remoteRepository.getTimeout() * 1000; 388 // FIXME olamy having 2 config values 389 // Set timeout 390 wagon.setReadTimeout( timeoutInMilliseconds ); 391 wagon.setTimeout( timeoutInMilliseconds ); 392 393 try 394 { 395 org.apache.maven.wagon.repository.Repository wagonRepository = 396 new org.apache.maven.wagon.repository.Repository( remoteRepository.getId(), remoteRepository.getUrl() ); 397 if ( networkProxy != null ) 398 { 399 wagon.connect( wagonRepository, authInfo, networkProxy ); 400 } 401 else 402 { 403 wagon.connect( wagonRepository, authInfo ); 404 } 405 connected = true; 406 } 407 catch ( ConnectionException | AuthenticationException e ) 408 { 409 log.error( "Could not connect to {}:{} ", remoteRepository.getName(), e.getMessage() ); 410 connected = false; 411 } 412 413 return connected; 414 } 415 416 /** 417 * 418 * @param wagon The wagon instance that should be connected. 419 * @param remoteRepository The repository from where the checksum file should be retrieved 420 * @param remotePath The remote path of the artifact (without extension) 421 * @param resource The local artifact (without extension) 422 * @param workingDir The working directory where the downloaded file should be placed to 423 * @param ext The extension of th checksum file 424 * @return The file where the data has been downloaded to. 425 * @throws AuthorizationException 426 * @throws TransferFailedException 427 * @throws ResourceDoesNotExistException 428 */ 429 private File transferChecksum( final Wagon wagon, final RemoteRepository remoteRepository, 430 final String remotePath, final File resource, 431 final File workingDir, final String ext ) 432 throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException 433 { 434 File destFile = new File( workingDir, resource.getName() + ext ); 435 String remoteChecksumPath = remotePath + ext; 436 437 log.info( "Retrieving {} from {}", remoteChecksumPath, remoteRepository.getName() ); 438 439 wagon.get( addParameters( remoteChecksumPath, remoteRepository ), destFile ); 440 441 log.debug( "Downloaded successfully." ); 442 443 return destFile; 444 } 445 446 private String getProtocol( String url ) 447 { 448 String protocol = StringUtils.substringBefore( url, ":" ); 449 450 return protocol; 451 } 452 453 private File createWorkingDirectory( String targetRepository ) 454 throws IOException 455 { 456 return Files.createTempDirectory( "temp" ).toFile(); 457 } 458 459 private void moveFileIfExists( File fileToMove, File directory ) 460 { 461 if ( fileToMove != null && fileToMove.exists() ) 462 { 463 File newLocation = new File( directory, fileToMove.getName() ); 464 if ( newLocation.exists() && !newLocation.delete() ) 465 { 466 throw new RuntimeException( 467 "Unable to overwrite existing target file: " + newLocation.getAbsolutePath() ); 468 } 469 470 newLocation.getParentFile().mkdirs(); 471 if ( !fileToMove.renameTo( newLocation ) ) 472 { 473 log.warn( "Unable to rename tmp file to its final name... resorting to copy command." ); 474 475 try 476 { 477 FileUtils.copyFile( fileToMove, newLocation ); 478 } 479 catch ( IOException e ) 480 { 481 if ( newLocation.exists() ) 482 { 483 log.error( "Tried to copy file {} to {} but file with this name already exists.", 484 fileToMove.getName(), newLocation.getAbsolutePath() ); 485 } 486 else 487 { 488 throw new RuntimeException( 489 "Cannot copy tmp file " + fileToMove.getAbsolutePath() + " to its final location", e ); 490 } 491 } 492 finally 493 { 494 FileUtils.deleteQuietly( fileToMove ); 495 } 496 } 497 } 498 } 499 500 protected String addParameters( String path, RemoteRepository remoteRepository ) 501 { 502 if ( remoteRepository.getExtraParameters().isEmpty() ) 503 { 504 return path; 505 } 506 507 boolean question = false; 508 509 StringBuilder res = new StringBuilder( path == null ? "" : path ); 510 511 for ( Map.Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() ) 512 { 513 if ( !question ) 514 { 515 res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() ); 516 } 517 } 518 519 return res.toString(); 520 } 521}