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