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