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