View Javadoc
1   package org.apache.maven.artifact.repository.metadata;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.FileNotFoundException;
24  import java.io.IOException;
25  import java.io.Reader;
26  import java.io.Writer;
27  import java.util.Date;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  
32  import org.apache.maven.artifact.metadata.ArtifactMetadata;
33  import org.apache.maven.artifact.repository.ArtifactRepository;
34  import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
35  import org.apache.maven.artifact.repository.DefaultRepositoryRequest;
36  import org.apache.maven.artifact.repository.RepositoryRequest;
37  import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
38  import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer;
39  import org.apache.maven.repository.legacy.UpdateCheckManager;
40  import org.apache.maven.repository.legacy.WagonManager;
41  import org.apache.maven.wagon.ResourceDoesNotExistException;
42  import org.apache.maven.wagon.TransferFailedException;
43  import org.codehaus.plexus.component.annotations.Component;
44  import org.codehaus.plexus.component.annotations.Requirement;
45  import org.codehaus.plexus.logging.AbstractLogEnabled;
46  import org.codehaus.plexus.util.IOUtil;
47  import org.codehaus.plexus.util.ReaderFactory;
48  import org.codehaus.plexus.util.WriterFactory;
49  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
50  
51  /**
52   * @author Jason van Zyl
53   */
54  @Component( role = RepositoryMetadataManager.class )
55  public class DefaultRepositoryMetadataManager
56      extends AbstractLogEnabled
57      implements RepositoryMetadataManager
58  {
59      @Requirement
60      private WagonManager wagonManager;
61  
62      @Requirement
63      private UpdateCheckManager updateCheckManager;
64  
65      public void resolve( RepositoryMetadata metadata, List<ArtifactRepository> remoteRepositories,
66                           ArtifactRepository localRepository )
67          throws RepositoryMetadataResolutionException
68      {
69          RepositoryRequest request = new DefaultRepositoryRequest();
70          request.setLocalRepository( localRepository );
71          request.setRemoteRepositories( remoteRepositories );
72          resolve( metadata, request );
73      }
74  
75      public void resolve( RepositoryMetadata metadata, RepositoryRequest request )
76          throws RepositoryMetadataResolutionException
77      {
78          ArtifactRepository localRepo = request.getLocalRepository();
79          List<ArtifactRepository> remoteRepositories = request.getRemoteRepositories();
80  
81          if ( !request.isOffline() )
82          {
83              Date localCopyLastModified = null;
84              if ( metadata.getBaseVersion() != null )
85              {
86                  localCopyLastModified = getLocalCopyLastModified( localRepo, metadata );
87              }
88  
89              for ( ArtifactRepository repository : remoteRepositories )
90              {
91                  ArtifactRepositoryPolicy policy = metadata.getPolicy( repository );
92  
93                  File file =
94                      new File( localRepo.getBasedir(), localRepo.pathOfLocalRepositoryMetadata( metadata, repository ) );
95                  boolean update;
96  
97                  if ( !policy.isEnabled() )
98                  {
99                      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 }