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