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