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 file.delete(); 145 } 146 } 147 catch ( TransferFailedException e ) 148 { 149 getLogger().warn( metadata + " could not be retrieved from repository: " + repository.getId() 150 + " due to an error: " + e.getMessage() ); 151 getLogger().debug( "Exception", e ); 152 } 153 finally 154 { 155 updateCheckManager.touch( metadata, repository, file ); 156 } 157 } 158 159 // TODO: should this be inside the above check? 160 // touch file so that this is not checked again until interval has passed 161 if ( file.exists() ) 162 { 163 file.setLastModified( System.currentTimeMillis() ); 164 } 165 } 166 } 167 168 try 169 { 170 mergeMetadata( metadata, remoteRepositories, localRepo ); 171 } 172 catch ( RepositoryMetadataStoreException e ) 173 { 174 throw new RepositoryMetadataResolutionException( "Unable to store local copy of metadata: " 175 + e.getMessage(), e ); 176 } 177 } 178 179 private Date getLocalCopyLastModified( ArtifactRepository localRepository, RepositoryMetadata metadata ) 180 { 181 String metadataPath = localRepository.pathOfLocalRepositoryMetadata( metadata, localRepository ); 182 File metadataFile = new File( localRepository.getBasedir(), metadataPath ); 183 return metadataFile.isFile() ? new Date( metadataFile.lastModified() ) : null; 184 } 185 186 private void mergeMetadata( RepositoryMetadata metadata, List<ArtifactRepository> remoteRepositories, 187 ArtifactRepository localRepository ) 188 throws RepositoryMetadataStoreException 189 { 190 // TODO: currently this is first wins, but really we should take the latest by comparing either the 191 // snapshot timestamp, or some other timestamp later encoded into the metadata. 192 // TODO: this needs to be repeated here so the merging doesn't interfere with the written metadata 193 // - we'd be much better having a pristine input, and an ongoing metadata for merging instead 194 195 Map<ArtifactRepository, Metadata> previousMetadata = new HashMap<ArtifactRepository, Metadata>(); 196 ArtifactRepository selected = null; 197 for ( ArtifactRepository repository : remoteRepositories ) 198 { 199 ArtifactRepositoryPolicy policy = metadata.getPolicy( repository ); 200 201 if ( policy.isEnabled() && loadMetadata( metadata, repository, localRepository, previousMetadata ) ) 202 { 203 metadata.setRepository( repository ); 204 selected = repository; 205 } 206 } 207 if ( loadMetadata( metadata, localRepository, localRepository, previousMetadata ) ) 208 { 209 metadata.setRepository( null ); 210 selected = localRepository; 211 } 212 213 updateSnapshotMetadata( metadata, previousMetadata, selected, localRepository ); 214 } 215 216 private void updateSnapshotMetadata( RepositoryMetadata metadata, 217 Map<ArtifactRepository, Metadata> previousMetadata, 218 ArtifactRepository selected, ArtifactRepository localRepository ) 219 throws RepositoryMetadataStoreException 220 { 221 // TODO: this could be a lot nicer... should really be in the snapshot transformation? 222 if ( metadata.isSnapshot() ) 223 { 224 Metadata prevMetadata = metadata.getMetadata(); 225 226 for ( ArtifactRepository repository : previousMetadata.keySet() ) 227 { 228 Metadata m = previousMetadata.get( repository ); 229 if ( repository.equals( selected ) ) 230 { 231 if ( m.getVersioning() == null ) 232 { 233 m.setVersioning( new Versioning() ); 234 } 235 236 if ( m.getVersioning().getSnapshot() == null ) 237 { 238 m.getVersioning().setSnapshot( new Snapshot() ); 239 } 240 } 241 else 242 { 243 if ( ( m.getVersioning() != null ) && ( m.getVersioning().getSnapshot() != null ) 244 && m.getVersioning().getSnapshot().isLocalCopy() ) 245 { 246 m.getVersioning().getSnapshot().setLocalCopy( false ); 247 metadata.setMetadata( m ); 248 metadata.storeInLocalRepository( localRepository, repository ); 249 } 250 } 251 } 252 253 metadata.setMetadata( prevMetadata ); 254 } 255 } 256 257 private boolean loadMetadata( RepositoryMetadata repoMetadata, ArtifactRepository remoteRepository, 258 ArtifactRepository localRepository, Map<ArtifactRepository, 259 Metadata> previousMetadata ) 260 { 261 boolean setRepository = false; 262 263 File metadataFile = 264 new File( localRepository.getBasedir(), localRepository.pathOfLocalRepositoryMetadata( repoMetadata, 265 remoteRepository ) ); 266 267 if ( metadataFile.exists() ) 268 { 269 Metadata metadata; 270 271 try 272 { 273 metadata = readMetadata( metadataFile ); 274 } 275 catch ( RepositoryMetadataReadException e ) 276 { 277 if ( getLogger().isDebugEnabled() ) 278 { 279 getLogger().warn( e.getMessage(), e ); 280 } 281 else 282 { 283 getLogger().warn( e.getMessage() ); 284 } 285 return setRepository; 286 } 287 288 if ( repoMetadata.isSnapshot() && ( previousMetadata != null ) ) 289 { 290 previousMetadata.put( remoteRepository, metadata ); 291 } 292 293 if ( repoMetadata.getMetadata() != null ) 294 { 295 setRepository = repoMetadata.getMetadata().merge( metadata ); 296 } 297 else 298 { 299 repoMetadata.setMetadata( metadata ); 300 setRepository = true; 301 } 302 } 303 return setRepository; 304 } 305 306 /** @todo share with DefaultPluginMappingManager. */ 307 protected Metadata readMetadata( File mappingFile ) 308 throws RepositoryMetadataReadException 309 { 310 Metadata result; 311 312 Reader reader = null; 313 try 314 { 315 reader = ReaderFactory.newXmlReader( mappingFile ); 316 317 MetadataXpp3Reader mappingReader = new MetadataXpp3Reader(); 318 319 result = mappingReader.read( reader, false ); 320 } 321 catch ( FileNotFoundException e ) 322 { 323 throw new RepositoryMetadataReadException( "Cannot read metadata from '" + mappingFile + "'", e ); 324 } 325 catch ( IOException e ) 326 { 327 throw new RepositoryMetadataReadException( "Cannot read metadata from '" + mappingFile + "': " 328 + e.getMessage(), e ); 329 } 330 catch ( XmlPullParserException e ) 331 { 332 throw new RepositoryMetadataReadException( "Cannot read metadata from '" + mappingFile + "': " 333 + e.getMessage(), e ); 334 } 335 finally 336 { 337 IOUtil.close( reader ); 338 } 339 340 return result; 341 } 342 343 /** 344 * Ensures the last updated timestamp of the specified metadata does not refer to the future and fixes the local 345 * metadata if necessary to allow proper merging/updating of metadata during deployment. 346 */ 347 private void fixTimestamp( File metadataFile, Metadata metadata, Metadata reference ) 348 { 349 boolean changed = false; 350 351 if ( metadata != null && reference != null ) 352 { 353 Versioning versioning = metadata.getVersioning(); 354 Versioning versioningRef = reference.getVersioning(); 355 if ( versioning != null && versioningRef != null ) 356 { 357 String lastUpdated = versioning.getLastUpdated(); 358 String now = versioningRef.getLastUpdated(); 359 if ( lastUpdated != null && now != null && now.compareTo( lastUpdated ) < 0 ) 360 { 361 getLogger().warn( 362 "The last updated timestamp in " + metadataFile + " refers to the future (now = " 363 + now + ", lastUpdated = " + lastUpdated 364 + "). Please verify that the clocks of all" 365 + " deploying machines are reasonably synchronized." ); 366 versioning.setLastUpdated( now ); 367 changed = true; 368 } 369 } 370 } 371 372 if ( changed ) 373 { 374 getLogger().debug( "Repairing metadata in " + metadataFile ); 375 376 Writer writer = null; 377 try 378 { 379 writer = WriterFactory.newXmlWriter( metadataFile ); 380 new MetadataXpp3Writer().write( writer, metadata ); 381 } 382 catch ( IOException e ) 383 { 384 String msg = "Could not write fixed metadata to " + metadataFile + ": " + e.getMessage(); 385 if ( getLogger().isDebugEnabled() ) 386 { 387 getLogger().warn( msg, e ); 388 } 389 else 390 { 391 getLogger().warn( msg ); 392 } 393 } 394 finally 395 { 396 IOUtil.close( writer ); 397 } 398 } 399 } 400 401 public void resolveAlways( RepositoryMetadata metadata, ArtifactRepository localRepository, 402 ArtifactRepository remoteRepository ) 403 throws RepositoryMetadataResolutionException 404 { 405 File file; 406 try 407 { 408 file = getArtifactMetadataFromDeploymentRepository( metadata, localRepository, remoteRepository ); 409 } 410 catch ( TransferFailedException e ) 411 { 412 throw new RepositoryMetadataResolutionException( metadata + " could not be retrieved from repository: " 413 + remoteRepository.getId() + " due to an error: " + e.getMessage(), e ); 414 } 415 416 try 417 { 418 if ( file.exists() ) 419 { 420 Metadata prevMetadata = readMetadata( file ); 421 metadata.setMetadata( prevMetadata ); 422 } 423 } 424 catch ( RepositoryMetadataReadException e ) 425 { 426 throw new RepositoryMetadataResolutionException( e.getMessage(), e ); 427 } 428 } 429 430 private File getArtifactMetadataFromDeploymentRepository( ArtifactMetadata metadata, 431 ArtifactRepository localRepo, 432 ArtifactRepository remoteRepository ) 433 throws TransferFailedException 434 { 435 File file = 436 new File( localRepo.getBasedir(), localRepo.pathOfLocalRepositoryMetadata( metadata, remoteRepository ) ); 437 438 try 439 { 440 wagonManager.getArtifactMetadataFromDeploymentRepository( metadata, remoteRepository, file, 441 ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN ); 442 } 443 catch ( ResourceDoesNotExistException e ) 444 { 445 getLogger().info( metadata + " could not be found on repository: " + remoteRepository.getId() 446 + ", so will be created" ); 447 448 // delete the local copy so the old details aren't used. 449 if ( file.exists() ) 450 { 451 file.delete(); 452 } 453 } 454 finally 455 { 456 if ( metadata instanceof RepositoryMetadata ) 457 { 458 updateCheckManager.touch( (RepositoryMetadata) metadata, remoteRepository, file ); 459 } 460 } 461 return file; 462 } 463 464 public void deploy( ArtifactMetadata metadata, ArtifactRepository localRepository, 465 ArtifactRepository deploymentRepository ) 466 throws RepositoryMetadataDeploymentException 467 { 468 File file; 469 if ( metadata instanceof RepositoryMetadata ) 470 { 471 getLogger().info( "Retrieving previous metadata from " + deploymentRepository.getId() ); 472 try 473 { 474 file = getArtifactMetadataFromDeploymentRepository( metadata, localRepository, deploymentRepository ); 475 } 476 catch ( TransferFailedException e ) 477 { 478 throw new RepositoryMetadataDeploymentException( metadata + " could not be retrieved from repository: " 479 + deploymentRepository.getId() + " due to an error: " + e.getMessage(), e ); 480 } 481 482 if ( file.isFile() ) 483 { 484 try 485 { 486 fixTimestamp( file, readMetadata( file ), ( (RepositoryMetadata) metadata ).getMetadata() ); 487 } 488 catch ( RepositoryMetadataReadException e ) 489 { 490 // will be reported via storeInlocalRepository 491 } 492 } 493 } 494 else 495 { 496 // It's a POM - we don't need to retrieve it first 497 file = 498 new File( localRepository.getBasedir(), 499 localRepository.pathOfLocalRepositoryMetadata( metadata, deploymentRepository ) ); 500 } 501 502 try 503 { 504 metadata.storeInLocalRepository( localRepository, deploymentRepository ); 505 } 506 catch ( RepositoryMetadataStoreException e ) 507 { 508 throw new RepositoryMetadataDeploymentException( "Error installing metadata: " + e.getMessage(), e ); 509 } 510 511 try 512 { 513 wagonManager.putArtifactMetadata( file, metadata, deploymentRepository ); 514 } 515 catch ( TransferFailedException e ) 516 { 517 throw new RepositoryMetadataDeploymentException( "Error while deploying metadata: " + e.getMessage(), e ); 518 } 519 } 520 521 public void install( ArtifactMetadata metadata, ArtifactRepository localRepository ) 522 throws RepositoryMetadataInstallationException 523 { 524 try 525 { 526 metadata.storeInLocalRepository( localRepository, localRepository ); 527 } 528 catch ( RepositoryMetadataStoreException e ) 529 { 530 throw new RepositoryMetadataInstallationException( "Error installing metadata: " + e.getMessage(), e ); 531 } 532 } 533 534}