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 org.apache.maven.artifact.metadata.ArtifactMetadata;
23  import org.apache.maven.artifact.repository.ArtifactRepository;
24  import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
25  import org.apache.maven.artifact.repository.DefaultRepositoryRequest;
26  import org.apache.maven.artifact.repository.RepositoryRequest;
27  import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
28  import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer;
29  import org.apache.maven.repository.legacy.UpdateCheckManager;
30  import org.apache.maven.repository.legacy.WagonManager;
31  import org.apache.maven.wagon.ResourceDoesNotExistException;
32  import org.apache.maven.wagon.TransferFailedException;
33  import org.codehaus.plexus.component.annotations.Component;
34  import org.codehaus.plexus.component.annotations.Requirement;
35  import org.codehaus.plexus.logging.AbstractLogEnabled;
36  import org.codehaus.plexus.util.ReaderFactory;
37  import org.codehaus.plexus.util.WriterFactory;
38  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
39  
40  import java.io.File;
41  import java.io.FileNotFoundException;
42  import java.io.IOException;
43  import java.io.Reader;
44  import java.io.Writer;
45  import java.util.Date;
46  import java.util.HashMap;
47  import java.util.List;
48  import java.util.Map;
49  
50  /**
51   * @author Jason van Zyl
52   */
53  @Component( role = RepositoryMetadataManager.class )
54  public class DefaultRepositoryMetadataManager
55      extends AbstractLogEnabled
56      implements RepositoryMetadataManager
57  {
58      @Requirement
59      private WagonManager wagonManager;
60  
61      @Requirement
62      private UpdateCheckManager updateCheckManager;
63  
64      public void resolve( RepositoryMetadata metadata, List<ArtifactRepository> remoteRepositories,
65                           ArtifactRepository localRepository )
66          throws RepositoryMetadataResolutionException
67      {
68          RepositoryRequest request = new DefaultRepositoryRequest();
69          request.setLocalRepository( localRepository );
70          request.setRemoteRepositories( remoteRepositories );
71          resolve( metadata, request );
72      }
73  
74      public void resolve( RepositoryMetadata metadata, RepositoryRequest request )
75          throws RepositoryMetadataResolutionException
76      {
77          ArtifactRepository localRepo = request.getLocalRepository();
78          List<ArtifactRepository> remoteRepositories = request.getRemoteRepositories();
79  
80          if ( !request.isOffline() )
81          {
82              Date localCopyLastModified = null;
83              if ( metadata.getBaseVersion() != null )
84              {
85                  localCopyLastModified = getLocalCopyLastModified( localRepo, metadata );
86              }
87  
88              for ( ArtifactRepository repository : remoteRepositories )
89              {
90                  ArtifactRepositoryPolicy policy = metadata.getPolicy( repository );
91  
92                  File file =
93                      new File( localRepo.getBasedir(), localRepo.pathOfLocalRepositoryMetadata( metadata, repository ) );
94                  boolean update;
95  
96                  if ( !policy.isEnabled() )
97                  {
98                      update = false;
99  
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 }