View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a 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,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.artifact.repository.metadata;
20  
21  import java.io.File;
22  import java.io.FileNotFoundException;
23  import java.io.IOException;
24  import java.io.Reader;
25  import java.io.Writer;
26  import java.util.Date;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  
31  import org.apache.maven.artifact.metadata.ArtifactMetadata;
32  import org.apache.maven.artifact.repository.ArtifactRepository;
33  import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
34  import org.apache.maven.artifact.repository.DefaultRepositoryRequest;
35  import org.apache.maven.artifact.repository.RepositoryRequest;
36  import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
37  import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer;
38  import org.apache.maven.repository.legacy.UpdateCheckManager;
39  import org.apache.maven.repository.legacy.WagonManager;
40  import org.apache.maven.wagon.ResourceDoesNotExistException;
41  import org.apache.maven.wagon.TransferFailedException;
42  import org.codehaus.plexus.component.annotations.Component;
43  import org.codehaus.plexus.component.annotations.Requirement;
44  import org.codehaus.plexus.logging.AbstractLogEnabled;
45  import org.codehaus.plexus.util.ReaderFactory;
46  import org.codehaus.plexus.util.WriterFactory;
47  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
48  
49  /**
50   * @author Jason van Zyl
51   */
52  @Component(role = RepositoryMetadataManager.class)
53  public class DefaultRepositoryMetadataManager extends AbstractLogEnabled implements RepositoryMetadataManager {
54      @Requirement
55      private WagonManager wagonManager;
56  
57      @Requirement
58      private UpdateCheckManager updateCheckManager;
59  
60      public void resolve(
61              RepositoryMetadata metadata,
62              List<ArtifactRepository> remoteRepositories,
63              ArtifactRepository localRepository)
64              throws RepositoryMetadataResolutionException {
65          RepositoryRequest request = new DefaultRepositoryRequest();
66          request.setLocalRepository(localRepository);
67          request.setRemoteRepositories(remoteRepositories);
68          resolve(metadata, request);
69      }
70  
71      public void resolve(RepositoryMetadata metadata, RepositoryRequest request)
72              throws RepositoryMetadataResolutionException {
73          ArtifactRepository localRepo = request.getLocalRepository();
74          List<ArtifactRepository> remoteRepositories = request.getRemoteRepositories();
75  
76          if (!request.isOffline()) {
77              Date localCopyLastModified = null;
78              if (metadata.getBaseVersion() != null) {
79                  localCopyLastModified = getLocalCopyLastModified(localRepo, metadata);
80              }
81  
82              for (ArtifactRepository repository : remoteRepositories) {
83                  ArtifactRepositoryPolicy policy = metadata.getPolicy(repository);
84  
85                  File file =
86                          new File(localRepo.getBasedir(), localRepo.pathOfLocalRepositoryMetadata(metadata, repository));
87                  boolean update;
88  
89                  if (!policy.isEnabled()) {
90                      update = false;
91  
92                      if (getLogger().isDebugEnabled()) {
93                          getLogger()
94                                  .debug("Skipping update check for " + metadata.getKey() + " (" + file
95                                          + ") from disabled repository " + repository.getId() + " ("
96                                          + repository.getUrl() + ")");
97                      }
98                  } else if (request.isForceUpdate()) {
99                      update = true;
100                 } else if (localCopyLastModified != null && !policy.checkOutOfDate(localCopyLastModified)) {
101                     update = false;
102 
103                     if (getLogger().isDebugEnabled()) {
104                         getLogger()
105                                 .debug("Skipping update check for " + metadata.getKey() + " (" + file
106                                         + ") from repository " + repository.getId() + " (" + repository.getUrl()
107                                         + ") in favor of local copy");
108                     }
109                 } else {
110                     update = updateCheckManager.isUpdateRequired(metadata, repository, file);
111                 }
112 
113                 if (update) {
114                     getLogger().info(metadata.getKey() + ": checking for updates from " + repository.getId());
115                     try {
116                         wagonManager.getArtifactMetadata(metadata, repository, file, policy.getChecksumPolicy());
117                     } catch (ResourceDoesNotExistException e) {
118                         getLogger().debug(metadata + " could not be found on repository: " + repository.getId());
119 
120                         // delete the local copy so the old details aren't used.
121                         if (file.exists()) {
122                             if (!file.delete()) {
123                                 // sleep for 10ms just in case this is windows holding a file lock
124                                 try {
125                                     Thread.sleep(10);
126                                 } catch (InterruptedException ie) {
127                                     // ignore
128                                 }
129                                 file.delete(); // if this fails, forget about it
130                             }
131                         }
132                     } catch (TransferFailedException e) {
133                         getLogger()
134                                 .warn(metadata + " could not be retrieved from repository: " + repository.getId()
135                                         + " due to an error: " + e.getMessage());
136                         getLogger().debug("Exception", e);
137                     } finally {
138                         updateCheckManager.touch(metadata, repository, file);
139                     }
140                 }
141 
142                 // TODO should this be inside the above check?
143                 // touch file so that this is not checked again until interval has passed
144                 if (file.exists()) {
145                     file.setLastModified(System.currentTimeMillis());
146                 }
147             }
148         }
149 
150         try {
151             mergeMetadata(metadata, remoteRepositories, localRepo);
152         } catch (RepositoryMetadataStoreException e) {
153             throw new RepositoryMetadataResolutionException(
154                     "Unable to store local copy of metadata: " + e.getMessage(), e);
155         }
156     }
157 
158     private Date getLocalCopyLastModified(ArtifactRepository localRepository, RepositoryMetadata metadata) {
159         String metadataPath = localRepository.pathOfLocalRepositoryMetadata(metadata, localRepository);
160         File metadataFile = new File(localRepository.getBasedir(), metadataPath);
161         return metadataFile.isFile() ? new Date(metadataFile.lastModified()) : null;
162     }
163 
164     private void mergeMetadata(
165             RepositoryMetadata metadata,
166             List<ArtifactRepository> remoteRepositories,
167             ArtifactRepository localRepository)
168             throws RepositoryMetadataStoreException {
169         // TODO currently this is first wins, but really we should take the latest by comparing either the
170         // snapshot timestamp, or some other timestamp later encoded into the metadata.
171         // TODO this needs to be repeated here so the merging doesn't interfere with the written metadata
172         //  - we'd be much better having a pristine input, and an ongoing metadata for merging instead
173 
174         Map<ArtifactRepository, Metadata> previousMetadata = new HashMap<>();
175         ArtifactRepository selected = null;
176         for (ArtifactRepository repository : remoteRepositories) {
177             ArtifactRepositoryPolicy policy = metadata.getPolicy(repository);
178 
179             if (policy.isEnabled() && loadMetadata(metadata, repository, localRepository, previousMetadata)) {
180                 metadata.setRepository(repository);
181                 selected = repository;
182             }
183         }
184         if (loadMetadata(metadata, localRepository, localRepository, previousMetadata)) {
185             metadata.setRepository(null);
186             selected = localRepository;
187         }
188 
189         updateSnapshotMetadata(metadata, previousMetadata, selected, localRepository);
190     }
191 
192     private void updateSnapshotMetadata(
193             RepositoryMetadata metadata,
194             Map<ArtifactRepository, Metadata> previousMetadata,
195             ArtifactRepository selected,
196             ArtifactRepository localRepository)
197             throws RepositoryMetadataStoreException {
198         // TODO this could be a lot nicer... should really be in the snapshot transformation?
199         if (metadata.isSnapshot()) {
200             Metadata prevMetadata = metadata.getMetadata();
201 
202             for (ArtifactRepository repository : previousMetadata.keySet()) {
203                 Metadata m = previousMetadata.get(repository);
204                 if (repository.equals(selected)) {
205                     if (m.getVersioning() == null) {
206                         m.setVersioning(new Versioning());
207                     }
208 
209                     if (m.getVersioning().getSnapshot() == null) {
210                         m.getVersioning().setSnapshot(new Snapshot());
211                     }
212                 } else {
213                     if ((m.getVersioning() != null)
214                             && (m.getVersioning().getSnapshot() != null)
215                             && m.getVersioning().getSnapshot().isLocalCopy()) {
216                         m.getVersioning().getSnapshot().setLocalCopy(false);
217                         metadata.setMetadata(m);
218                         metadata.storeInLocalRepository(localRepository, repository);
219                     }
220                 }
221             }
222 
223             metadata.setMetadata(prevMetadata);
224         }
225     }
226 
227     private boolean loadMetadata(
228             RepositoryMetadata repoMetadata,
229             ArtifactRepository remoteRepository,
230             ArtifactRepository localRepository,
231             Map<ArtifactRepository, Metadata> previousMetadata) {
232         boolean setRepository = false;
233 
234         File metadataFile = new File(
235                 localRepository.getBasedir(),
236                 localRepository.pathOfLocalRepositoryMetadata(repoMetadata, remoteRepository));
237 
238         if (metadataFile.exists()) {
239             Metadata metadata;
240 
241             try {
242                 metadata = readMetadata(metadataFile);
243             } catch (RepositoryMetadataReadException e) {
244                 if (getLogger().isDebugEnabled()) {
245                     getLogger().warn(e.getMessage(), e);
246                 } else {
247                     getLogger().warn(e.getMessage());
248                 }
249                 return setRepository;
250             }
251 
252             if (repoMetadata.isSnapshot() && (previousMetadata != null)) {
253                 previousMetadata.put(remoteRepository, metadata);
254             }
255 
256             if (repoMetadata.getMetadata() != null) {
257                 setRepository = repoMetadata.getMetadata().merge(metadata);
258             } else {
259                 repoMetadata.setMetadata(metadata);
260                 setRepository = true;
261             }
262         }
263         return setRepository;
264     }
265 
266     /**
267      * TODO share with DefaultPluginMappingManager.
268      */
269     protected Metadata readMetadata(File mappingFile) throws RepositoryMetadataReadException {
270         Metadata result;
271 
272         try (Reader reader = ReaderFactory.newXmlReader(mappingFile)) {
273             MetadataXpp3Reader mappingReader = new MetadataXpp3Reader();
274 
275             result = mappingReader.read(reader, false);
276         } catch (FileNotFoundException e) {
277             throw new RepositoryMetadataReadException("Cannot read metadata from '" + mappingFile + "'", e);
278         } catch (IOException | XmlPullParserException e) {
279             throw new RepositoryMetadataReadException(
280                     "Cannot read metadata from '" + mappingFile + "': " + e.getMessage(), e);
281         }
282         return result;
283     }
284 
285     /**
286      * Ensures the last updated timestamp of the specified metadata does not refer to the future and fixes the local
287      * metadata if necessary to allow proper merging/updating of metadata during deployment.
288      */
289     private void fixTimestamp(File metadataFile, Metadata metadata, Metadata reference) {
290         boolean changed = false;
291 
292         if (metadata != null && reference != null) {
293             Versioning versioning = metadata.getVersioning();
294             Versioning versioningRef = reference.getVersioning();
295             if (versioning != null && versioningRef != null) {
296                 String lastUpdated = versioning.getLastUpdated();
297                 String now = versioningRef.getLastUpdated();
298                 if (lastUpdated != null && now != null && now.compareTo(lastUpdated) < 0) {
299                     getLogger()
300                             .warn("The last updated timestamp in " + metadataFile + " refers to the future (now = "
301                                     + now
302                                     + ", lastUpdated = " + lastUpdated + "). Please verify that the clocks of all"
303                                     + " deploying machines are reasonably synchronized.");
304                     versioning.setLastUpdated(now);
305                     changed = true;
306                 }
307             }
308         }
309 
310         if (changed) {
311             getLogger().debug("Repairing metadata in " + metadataFile);
312 
313             try (Writer writer = WriterFactory.newXmlWriter(metadataFile)) {
314                 new MetadataXpp3Writer().write(writer, metadata);
315             } catch (IOException e) {
316                 String msg = "Could not write fixed metadata to " + metadataFile + ": " + e.getMessage();
317                 if (getLogger().isDebugEnabled()) {
318                     getLogger().warn(msg, e);
319                 } else {
320                     getLogger().warn(msg);
321                 }
322             }
323         }
324     }
325 
326     public void resolveAlways(
327             RepositoryMetadata metadata, ArtifactRepository localRepository, ArtifactRepository remoteRepository)
328             throws RepositoryMetadataResolutionException {
329         File file;
330         try {
331             file = getArtifactMetadataFromDeploymentRepository(metadata, localRepository, remoteRepository);
332         } catch (TransferFailedException e) {
333             throw new RepositoryMetadataResolutionException(
334                     metadata + " could not be retrieved from repository: " + remoteRepository.getId()
335                             + " due to an error: " + e.getMessage(),
336                     e);
337         }
338 
339         try {
340             if (file.exists()) {
341                 Metadata prevMetadata = readMetadata(file);
342                 metadata.setMetadata(prevMetadata);
343             }
344         } catch (RepositoryMetadataReadException e) {
345             throw new RepositoryMetadataResolutionException(e.getMessage(), e);
346         }
347     }
348 
349     private File getArtifactMetadataFromDeploymentRepository(
350             ArtifactMetadata metadata, ArtifactRepository localRepo, ArtifactRepository remoteRepository)
351             throws TransferFailedException {
352         File file =
353                 new File(localRepo.getBasedir(), localRepo.pathOfLocalRepositoryMetadata(metadata, remoteRepository));
354 
355         try {
356             wagonManager.getArtifactMetadataFromDeploymentRepository(
357                     metadata, remoteRepository, file, ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN);
358         } catch (ResourceDoesNotExistException e) {
359             getLogger()
360                     .info(metadata + " could not be found on repository: " + remoteRepository.getId()
361                             + ", so will be created");
362 
363             // delete the local copy so the old details aren't used.
364             if (file.exists()) {
365                 if (!file.delete()) {
366                     // sleep for 10ms just in case this is windows holding a file lock
367                     try {
368                         Thread.sleep(10);
369                     } catch (InterruptedException ie) {
370                         // ignore
371                     }
372                     file.delete(); // if this fails, forget about it
373                 }
374             }
375         } finally {
376             if (metadata instanceof RepositoryMetadata) {
377                 updateCheckManager.touch((RepositoryMetadata) metadata, remoteRepository, file);
378             }
379         }
380         return file;
381     }
382 
383     public void deploy(
384             ArtifactMetadata metadata, ArtifactRepository localRepository, ArtifactRepository deploymentRepository)
385             throws RepositoryMetadataDeploymentException {
386         File file;
387         if (metadata instanceof RepositoryMetadata) {
388             getLogger().info("Retrieving previous metadata from " + deploymentRepository.getId());
389             try {
390                 file = getArtifactMetadataFromDeploymentRepository(metadata, localRepository, deploymentRepository);
391             } catch (TransferFailedException e) {
392                 throw new RepositoryMetadataDeploymentException(
393                         metadata + " could not be retrieved from repository: " + deploymentRepository.getId()
394                                 + " due to an error: " + e.getMessage(),
395                         e);
396             }
397 
398             if (file.isFile()) {
399                 try {
400                     fixTimestamp(file, readMetadata(file), ((RepositoryMetadata) metadata).getMetadata());
401                 } catch (RepositoryMetadataReadException e) {
402                     // will be reported via storeInlocalRepository
403                 }
404             }
405         } else {
406             // It's a POM - we don't need to retrieve it first
407             file = new File(
408                     localRepository.getBasedir(),
409                     localRepository.pathOfLocalRepositoryMetadata(metadata, deploymentRepository));
410         }
411 
412         try {
413             metadata.storeInLocalRepository(localRepository, deploymentRepository);
414         } catch (RepositoryMetadataStoreException e) {
415             throw new RepositoryMetadataDeploymentException("Error installing metadata: " + e.getMessage(), e);
416         }
417 
418         try {
419             wagonManager.putArtifactMetadata(file, metadata, deploymentRepository);
420         } catch (TransferFailedException e) {
421             throw new RepositoryMetadataDeploymentException("Error while deploying metadata: " + e.getMessage(), e);
422         }
423     }
424 
425     public void install(ArtifactMetadata metadata, ArtifactRepository localRepository)
426             throws RepositoryMetadataInstallationException {
427         try {
428             metadata.storeInLocalRepository(localRepository, localRepository);
429         } catch (RepositoryMetadataStoreException e) {
430             throw new RepositoryMetadataInstallationException("Error installing metadata: " + e.getMessage(), e);
431         }
432     }
433 }