View Javadoc

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