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                             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 }