001package org.apache.maven.artifact.repository.metadata; 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 java.io.File; 023import java.io.FileNotFoundException; 024import java.io.IOException; 025import java.io.Reader; 026import java.io.Writer; 027import java.util.Date; 028import java.util.HashMap; 029import java.util.List; 030import java.util.Map; 031 032import org.apache.maven.artifact.metadata.ArtifactMetadata; 033import org.apache.maven.artifact.repository.ArtifactRepository; 034import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy; 035import org.apache.maven.artifact.repository.DefaultRepositoryRequest; 036import org.apache.maven.artifact.repository.RepositoryRequest; 037import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader; 038import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer; 039import org.apache.maven.repository.legacy.UpdateCheckManager; 040import org.apache.maven.repository.legacy.WagonManager; 041import org.apache.maven.wagon.ResourceDoesNotExistException; 042import org.apache.maven.wagon.TransferFailedException; 043import org.codehaus.plexus.component.annotations.Component; 044import org.codehaus.plexus.component.annotations.Requirement; 045import org.codehaus.plexus.logging.AbstractLogEnabled; 046import org.codehaus.plexus.util.IOUtil; 047import org.codehaus.plexus.util.ReaderFactory; 048import org.codehaus.plexus.util.WriterFactory; 049import org.codehaus.plexus.util.xml.pull.XmlPullParserException; 050 051/** 052 * @author Jason van Zyl 053 */ 054@Component( role = RepositoryMetadataManager.class ) 055public class DefaultRepositoryMetadataManager 056 extends AbstractLogEnabled 057 implements RepositoryMetadataManager 058{ 059 @Requirement 060 private WagonManager wagonManager; 061 062 @Requirement 063 private UpdateCheckManager updateCheckManager; 064 065 public void resolve( RepositoryMetadata metadata, List<ArtifactRepository> remoteRepositories, 066 ArtifactRepository localRepository ) 067 throws RepositoryMetadataResolutionException 068 { 069 RepositoryRequest request = new DefaultRepositoryRequest(); 070 request.setLocalRepository( localRepository ); 071 request.setRemoteRepositories( remoteRepositories ); 072 resolve( metadata, request ); 073 } 074 075 public void resolve( RepositoryMetadata metadata, RepositoryRequest request ) 076 throws RepositoryMetadataResolutionException 077 { 078 ArtifactRepository localRepo = request.getLocalRepository(); 079 List<ArtifactRepository> remoteRepositories = request.getRemoteRepositories(); 080 081 if ( !request.isOffline() ) 082 { 083 Date localCopyLastModified = null; 084 if ( metadata.getBaseVersion() != null ) 085 { 086 localCopyLastModified = getLocalCopyLastModified( localRepo, metadata ); 087 } 088 089 for ( ArtifactRepository repository : remoteRepositories ) 090 { 091 ArtifactRepositoryPolicy policy = metadata.getPolicy( repository ); 092 093 File file = 094 new File( localRepo.getBasedir(), localRepo.pathOfLocalRepositoryMetadata( metadata, repository ) ); 095 boolean update; 096 097 if ( !policy.isEnabled() ) 098 { 099 update = false; 100 101 if ( getLogger().isDebugEnabled() ) 102 { 103 getLogger().debug( 104 "Skipping update check for " + metadata.getKey() + " (" + file 105 + ") from disabled repository " + repository.getId() + " (" 106 + repository.getUrl() + ")" ); 107 } 108 } 109 else if ( request.isForceUpdate() ) 110 { 111 update = true; 112 } 113 else if ( localCopyLastModified != null && !policy.checkOutOfDate( localCopyLastModified ) ) 114 { 115 update = false; 116 117 if ( getLogger().isDebugEnabled() ) 118 { 119 getLogger().debug( 120 "Skipping update check for " + metadata.getKey() + " (" + file 121 + ") from repository " + repository.getId() + " (" + repository.getUrl() 122 + ") in favor of local copy" ); 123 } 124 } 125 else 126 { 127 update = updateCheckManager.isUpdateRequired( metadata, repository, file ); 128 } 129 130 if ( update ) 131 { 132 getLogger().info( metadata.getKey() + ": checking for updates from " + repository.getId() ); 133 try 134 { 135 wagonManager.getArtifactMetadata( metadata, repository, file, policy.getChecksumPolicy() ); 136 } 137 catch ( ResourceDoesNotExistException e ) 138 { 139 getLogger().debug( metadata + " could not be found on repository: " + repository.getId() ); 140 141 // delete the local copy so the old details aren't used. 142 if ( file.exists() ) 143 { 144 if ( !file.delete() ) 145 { 146 // sleep for 10ms just in case this is windows holding a file lock 147 try 148 { 149 Thread.sleep( 10 ); 150 } 151 catch ( InterruptedException ie ) 152 { 153 // ignore 154 } 155 file.delete(); // if this fails, forget about it 156 } 157 } 158 } 159 catch ( TransferFailedException e ) 160 { 161 getLogger().warn( metadata + " could not be retrieved from repository: " + repository.getId() 162 + " due to an error: " + e.getMessage() ); 163 getLogger().debug( "Exception", e ); 164 } 165 finally 166 { 167 updateCheckManager.touch( metadata, repository, file ); 168 } 169 } 170 171 // TODO: should this be inside the above check? 172 // touch file so that this is not checked again until interval has passed 173 if ( file.exists() ) 174 { 175 file.setLastModified( System.currentTimeMillis() ); 176 } 177 } 178 } 179 180 try 181 { 182 mergeMetadata( metadata, remoteRepositories, localRepo ); 183 } 184 catch ( RepositoryMetadataStoreException e ) 185 { 186 throw new RepositoryMetadataResolutionException( "Unable to store local copy of metadata: " 187 + e.getMessage(), e ); 188 } 189 } 190 191 private Date getLocalCopyLastModified( ArtifactRepository localRepository, RepositoryMetadata metadata ) 192 { 193 String metadataPath = localRepository.pathOfLocalRepositoryMetadata( metadata, localRepository ); 194 File metadataFile = new File( localRepository.getBasedir(), metadataPath ); 195 return metadataFile.isFile() ? new Date( metadataFile.lastModified() ) : null; 196 } 197 198 private void mergeMetadata( RepositoryMetadata metadata, List<ArtifactRepository> remoteRepositories, 199 ArtifactRepository localRepository ) 200 throws RepositoryMetadataStoreException 201 { 202 // TODO: currently this is first wins, but really we should take the latest by comparing either the 203 // snapshot timestamp, or some other timestamp later encoded into the metadata. 204 // TODO: this needs to be repeated here so the merging doesn't interfere with the written metadata 205 // - we'd be much better having a pristine input, and an ongoing metadata for merging instead 206 207 Map<ArtifactRepository, Metadata> previousMetadata = new HashMap<ArtifactRepository, Metadata>(); 208 ArtifactRepository selected = null; 209 for ( ArtifactRepository repository : remoteRepositories ) 210 { 211 ArtifactRepositoryPolicy policy = metadata.getPolicy( repository ); 212 213 if ( policy.isEnabled() && loadMetadata( metadata, repository, localRepository, previousMetadata ) ) 214 { 215 metadata.setRepository( repository ); 216 selected = repository; 217 } 218 } 219 if ( loadMetadata( metadata, localRepository, localRepository, previousMetadata ) ) 220 { 221 metadata.setRepository( null ); 222 selected = localRepository; 223 } 224 225 updateSnapshotMetadata( metadata, previousMetadata, selected, localRepository ); 226 } 227 228 private void updateSnapshotMetadata( RepositoryMetadata metadata, 229 Map<ArtifactRepository, Metadata> previousMetadata, 230 ArtifactRepository selected, ArtifactRepository localRepository ) 231 throws RepositoryMetadataStoreException 232 { 233 // TODO: this could be a lot nicer... should really be in the snapshot transformation? 234 if ( metadata.isSnapshot() ) 235 { 236 Metadata prevMetadata = metadata.getMetadata(); 237 238 for ( ArtifactRepository repository : previousMetadata.keySet() ) 239 { 240 Metadata m = previousMetadata.get( repository ); 241 if ( repository.equals( selected ) ) 242 { 243 if ( m.getVersioning() == null ) 244 { 245 m.setVersioning( new Versioning() ); 246 } 247 248 if ( m.getVersioning().getSnapshot() == null ) 249 { 250 m.getVersioning().setSnapshot( new Snapshot() ); 251 } 252 } 253 else 254 { 255 if ( ( m.getVersioning() != null ) && ( m.getVersioning().getSnapshot() != null ) 256 && m.getVersioning().getSnapshot().isLocalCopy() ) 257 { 258 m.getVersioning().getSnapshot().setLocalCopy( false ); 259 metadata.setMetadata( m ); 260 metadata.storeInLocalRepository( localRepository, repository ); 261 } 262 } 263 } 264 265 metadata.setMetadata( prevMetadata ); 266 } 267 } 268 269 private boolean loadMetadata( RepositoryMetadata repoMetadata, ArtifactRepository remoteRepository, 270 ArtifactRepository localRepository, Map<ArtifactRepository, 271 Metadata> previousMetadata ) 272 { 273 boolean setRepository = false; 274 275 File metadataFile = 276 new File( localRepository.getBasedir(), localRepository.pathOfLocalRepositoryMetadata( repoMetadata, 277 remoteRepository ) ); 278 279 if ( metadataFile.exists() ) 280 { 281 Metadata metadata; 282 283 try 284 { 285 metadata = readMetadata( metadataFile ); 286 } 287 catch ( RepositoryMetadataReadException e ) 288 { 289 if ( getLogger().isDebugEnabled() ) 290 { 291 getLogger().warn( e.getMessage(), e ); 292 } 293 else 294 { 295 getLogger().warn( e.getMessage() ); 296 } 297 return setRepository; 298 } 299 300 if ( repoMetadata.isSnapshot() && ( previousMetadata != null ) ) 301 { 302 previousMetadata.put( remoteRepository, metadata ); 303 } 304 305 if ( repoMetadata.getMetadata() != null ) 306 { 307 setRepository = repoMetadata.getMetadata().merge( metadata ); 308 } 309 else 310 { 311 repoMetadata.setMetadata( metadata ); 312 setRepository = true; 313 } 314 } 315 return setRepository; 316 } 317 318 /** @todo share with DefaultPluginMappingManager. */ 319 protected Metadata readMetadata( File mappingFile ) 320 throws RepositoryMetadataReadException 321 { 322 Metadata result; 323 324 Reader reader = null; 325 try 326 { 327 reader = ReaderFactory.newXmlReader( mappingFile ); 328 329 MetadataXpp3Reader mappingReader = new MetadataXpp3Reader(); 330 331 result = mappingReader.read( reader, false ); 332 } 333 catch ( FileNotFoundException e ) 334 { 335 throw new RepositoryMetadataReadException( "Cannot read metadata from '" + mappingFile + "'", e ); 336 } 337 catch ( IOException e ) 338 { 339 throw new RepositoryMetadataReadException( "Cannot read metadata from '" + mappingFile + "': " 340 + e.getMessage(), e ); 341 } 342 catch ( XmlPullParserException e ) 343 { 344 throw new RepositoryMetadataReadException( "Cannot read metadata from '" + mappingFile + "': " 345 + e.getMessage(), e ); 346 } 347 finally 348 { 349 IOUtil.close( reader ); 350 } 351 352 return result; 353 } 354 355 /** 356 * Ensures the last updated timestamp of the specified metadata does not refer to the future and fixes the local 357 * metadata if necessary to allow proper merging/updating of metadata during deployment. 358 */ 359 private void fixTimestamp( File metadataFile, Metadata metadata, Metadata reference ) 360 { 361 boolean changed = false; 362 363 if ( metadata != null && reference != null ) 364 { 365 Versioning versioning = metadata.getVersioning(); 366 Versioning versioningRef = reference.getVersioning(); 367 if ( versioning != null && versioningRef != null ) 368 { 369 String lastUpdated = versioning.getLastUpdated(); 370 String now = versioningRef.getLastUpdated(); 371 if ( lastUpdated != null && now != null && now.compareTo( lastUpdated ) < 0 ) 372 { 373 getLogger().warn( 374 "The last updated timestamp in " + metadataFile + " refers to the future (now = " 375 + now + ", lastUpdated = " + lastUpdated 376 + "). Please verify that the clocks of all" 377 + " deploying machines are reasonably synchronized." ); 378 versioning.setLastUpdated( now ); 379 changed = true; 380 } 381 } 382 } 383 384 if ( changed ) 385 { 386 getLogger().debug( "Repairing metadata in " + metadataFile ); 387 388 Writer writer = null; 389 try 390 { 391 writer = WriterFactory.newXmlWriter( metadataFile ); 392 new MetadataXpp3Writer().write( writer, metadata ); 393 } 394 catch ( IOException e ) 395 { 396 String msg = "Could not write fixed metadata to " + metadataFile + ": " + e.getMessage(); 397 if ( getLogger().isDebugEnabled() ) 398 { 399 getLogger().warn( msg, e ); 400 } 401 else 402 { 403 getLogger().warn( msg ); 404 } 405 } 406 finally 407 { 408 IOUtil.close( writer ); 409 } 410 } 411 } 412 413 public void resolveAlways( RepositoryMetadata metadata, ArtifactRepository localRepository, 414 ArtifactRepository remoteRepository ) 415 throws RepositoryMetadataResolutionException 416 { 417 File file; 418 try 419 { 420 file = getArtifactMetadataFromDeploymentRepository( metadata, localRepository, remoteRepository ); 421 } 422 catch ( TransferFailedException e ) 423 { 424 throw new RepositoryMetadataResolutionException( metadata + " could not be retrieved from repository: " 425 + remoteRepository.getId() + " due to an error: " + e.getMessage(), e ); 426 } 427 428 try 429 { 430 if ( file.exists() ) 431 { 432 Metadata prevMetadata = readMetadata( file ); 433 metadata.setMetadata( prevMetadata ); 434 } 435 } 436 catch ( RepositoryMetadataReadException e ) 437 { 438 throw new RepositoryMetadataResolutionException( e.getMessage(), e ); 439 } 440 } 441 442 private File getArtifactMetadataFromDeploymentRepository( ArtifactMetadata metadata, 443 ArtifactRepository localRepo, 444 ArtifactRepository remoteRepository ) 445 throws TransferFailedException 446 { 447 File file = 448 new File( localRepo.getBasedir(), localRepo.pathOfLocalRepositoryMetadata( metadata, remoteRepository ) ); 449 450 try 451 { 452 wagonManager.getArtifactMetadataFromDeploymentRepository( metadata, remoteRepository, file, 453 ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN ); 454 } 455 catch ( ResourceDoesNotExistException e ) 456 { 457 getLogger().info( metadata + " could not be found on repository: " + remoteRepository.getId() 458 + ", so will be created" ); 459 460 // delete the local copy so the old details aren't used. 461 if ( file.exists() ) 462 { 463 if ( !file.delete() ) 464 { 465 // sleep for 10ms just in case this is windows holding a file lock 466 try 467 { 468 Thread.sleep( 10 ); 469 } 470 catch ( InterruptedException ie ) 471 { 472 // ignore 473 } 474 file.delete(); // if this fails, forget about it 475 } 476 } 477 } 478 finally 479 { 480 if ( metadata instanceof RepositoryMetadata ) 481 { 482 updateCheckManager.touch( (RepositoryMetadata) metadata, remoteRepository, file ); 483 } 484 } 485 return file; 486 } 487 488 public void deploy( ArtifactMetadata metadata, ArtifactRepository localRepository, 489 ArtifactRepository deploymentRepository ) 490 throws RepositoryMetadataDeploymentException 491 { 492 File file; 493 if ( metadata instanceof RepositoryMetadata ) 494 { 495 getLogger().info( "Retrieving previous metadata from " + deploymentRepository.getId() ); 496 try 497 { 498 file = getArtifactMetadataFromDeploymentRepository( metadata, localRepository, deploymentRepository ); 499 } 500 catch ( TransferFailedException e ) 501 { 502 throw new RepositoryMetadataDeploymentException( metadata + " could not be retrieved from repository: " 503 + deploymentRepository.getId() + " due to an error: " + e.getMessage(), e ); 504 } 505 506 if ( file.isFile() ) 507 { 508 try 509 { 510 fixTimestamp( file, readMetadata( file ), ( (RepositoryMetadata) metadata ).getMetadata() ); 511 } 512 catch ( RepositoryMetadataReadException e ) 513 { 514 // will be reported via storeInlocalRepository 515 } 516 } 517 } 518 else 519 { 520 // It's a POM - we don't need to retrieve it first 521 file = 522 new File( localRepository.getBasedir(), 523 localRepository.pathOfLocalRepositoryMetadata( metadata, deploymentRepository ) ); 524 } 525 526 try 527 { 528 metadata.storeInLocalRepository( localRepository, deploymentRepository ); 529 } 530 catch ( RepositoryMetadataStoreException e ) 531 { 532 throw new RepositoryMetadataDeploymentException( "Error installing metadata: " + e.getMessage(), e ); 533 } 534 535 try 536 { 537 wagonManager.putArtifactMetadata( file, metadata, deploymentRepository ); 538 } 539 catch ( TransferFailedException e ) 540 { 541 throw new RepositoryMetadataDeploymentException( "Error while deploying metadata: " + e.getMessage(), e ); 542 } 543 } 544 545 public void install( ArtifactMetadata metadata, ArtifactRepository localRepository ) 546 throws RepositoryMetadataInstallationException 547 { 548 try 549 { 550 metadata.storeInLocalRepository( localRepository, localRepository ); 551 } 552 catch ( RepositoryMetadataStoreException e ) 553 { 554 throw new RepositoryMetadataInstallationException( "Error installing metadata: " + e.getMessage(), e ); 555 } 556 } 557 558}