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