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 javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.File;
26  import java.io.FileNotFoundException;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.io.OutputStream;
30  import java.nio.file.Files;
31  import java.util.Date;
32  import java.util.HashMap;
33  import java.util.List;
34  import java.util.Map;
35  
36  import org.apache.maven.artifact.metadata.ArtifactMetadata;
37  import org.apache.maven.artifact.repository.ArtifactRepository;
38  import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
39  import org.apache.maven.artifact.repository.DefaultRepositoryRequest;
40  import org.apache.maven.artifact.repository.RepositoryRequest;
41  import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
42  import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer;
43  import org.apache.maven.repository.legacy.UpdateCheckManager;
44  import org.apache.maven.repository.legacy.WagonManager;
45  import org.apache.maven.wagon.ResourceDoesNotExistException;
46  import org.apache.maven.wagon.TransferFailedException;
47  import org.codehaus.plexus.logging.AbstractLogEnabled;
48  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
49  
50  /**
51   * @author Jason van Zyl
52   */
53  @Named
54  @Singleton
55  public class DefaultRepositoryMetadataManager extends AbstractLogEnabled implements RepositoryMetadataManager {
56      @Inject
57      private WagonManager wagonManager;
58  
59      @Inject
60      private UpdateCheckManager updateCheckManager;
61  
62      public void resolve(
63              RepositoryMetadata metadata,
64              List<ArtifactRepository> remoteRepositories,
65              ArtifactRepository localRepository)
66              throws RepositoryMetadataResolutionException {
67          RepositoryRequest request = new DefaultRepositoryRequest();
68          request.setLocalRepository(localRepository);
69          request.setRemoteRepositories(remoteRepositories);
70          resolve(metadata, request);
71      }
72  
73      public void resolve(RepositoryMetadata metadata, RepositoryRequest request)
74              throws RepositoryMetadataResolutionException {
75          ArtifactRepository localRepo = request.getLocalRepository();
76          List<ArtifactRepository> remoteRepositories = request.getRemoteRepositories();
77  
78          if (!request.isOffline()) {
79              Date localCopyLastModified = null;
80              if (metadata.getBaseVersion() != null) {
81                  localCopyLastModified = getLocalCopyLastModified(localRepo, metadata);
82              }
83  
84              for (ArtifactRepository repository : remoteRepositories) {
85                  ArtifactRepositoryPolicy policy = metadata.getPolicy(repository);
86  
87                  File file =
88                          new File(localRepo.getBasedir(), localRepo.pathOfLocalRepositoryMetadata(metadata, repository));
89                  boolean update;
90  
91                  if (!policy.isEnabled()) {
92                      update = false;
93  
94                      if (getLogger().isDebugEnabled()) {
95                          getLogger()
96                                  .debug("Skipping update check for " + metadata.getKey() + " (" + file
97                                          + ") from disabled repository " + repository.getId() + " ("
98                                          + repository.getUrl() + ")");
99                      }
100                 } else if (request.isForceUpdate()) {
101                     update = true;
102                 } else if (localCopyLastModified != null && !policy.checkOutOfDate(localCopyLastModified)) {
103                     update = false;
104 
105                     if (getLogger().isDebugEnabled()) {
106                         getLogger()
107                                 .debug("Skipping update check for " + metadata.getKey() + " (" + file
108                                         + ") from repository " + repository.getId() + " (" + repository.getUrl()
109                                         + ") in favor of local copy");
110                     }
111                 } else {
112                     update = updateCheckManager.isUpdateRequired(metadata, repository, file);
113                 }
114 
115                 if (update) {
116                     getLogger().info(metadata.getKey() + ": checking for updates from " + repository.getId());
117                     try {
118                         wagonManager.getArtifactMetadata(metadata, repository, file, policy.getChecksumPolicy());
119                     } catch (ResourceDoesNotExistException e) {
120                         getLogger().debug(metadata + " could not be found on repository: " + repository.getId());
121 
122                         // delete the local copy so the old details aren't used.
123                         if (file.exists()) {
124                             if (!file.delete()) {
125                                 // sleep for 10ms just in case this is windows holding a file lock
126                                 try {
127                                     Thread.sleep(10);
128                                 } catch (InterruptedException ie) {
129                                     // ignore
130                                 }
131                                 file.delete(); // if this fails, forget about it
132                             }
133                         }
134                     } catch (TransferFailedException e) {
135                         getLogger()
136                                 .warn(metadata + " could not be retrieved from repository: " + repository.getId()
137                                         + " due to an error: " + e.getMessage());
138                         getLogger().debug("Exception", e);
139                     } finally {
140                         updateCheckManager.touch(metadata, repository, file);
141                     }
142                 }
143 
144                 // TODO should this be inside the above check?
145                 // touch file so that this is not checked again until interval has passed
146                 if (file.exists()) {
147                     file.setLastModified(System.currentTimeMillis());
148                 }
149             }
150         }
151 
152         try {
153             mergeMetadata(metadata, remoteRepositories, localRepo);
154         } catch (RepositoryMetadataStoreException e) {
155             throw new RepositoryMetadataResolutionException(
156                     "Unable to store local copy of metadata: " + e.getMessage(), e);
157         }
158     }
159 
160     private Date getLocalCopyLastModified(ArtifactRepository localRepository, RepositoryMetadata metadata) {
161         String metadataPath = localRepository.pathOfLocalRepositoryMetadata(metadata, localRepository);
162         File metadataFile = new File(localRepository.getBasedir(), metadataPath);
163         return metadataFile.isFile() ? new Date(metadataFile.lastModified()) : null;
164     }
165 
166     private void mergeMetadata(
167             RepositoryMetadata metadata,
168             List<ArtifactRepository> remoteRepositories,
169             ArtifactRepository localRepository)
170             throws RepositoryMetadataStoreException {
171         // TODO currently this is first wins, but really we should take the latest by comparing either the
172         // snapshot timestamp, or some other timestamp later encoded into the metadata.
173         // TODO this needs to be repeated here so the merging doesn't interfere with the written metadata
174         //  - we'd be much better having a pristine input, and an ongoing metadata for merging instead
175 
176         Map<ArtifactRepository, Metadata> previousMetadata = new HashMap<>();
177         ArtifactRepository selected = null;
178         for (ArtifactRepository repository : remoteRepositories) {
179             ArtifactRepositoryPolicy policy = metadata.getPolicy(repository);
180 
181             if (policy.isEnabled() && loadMetadata(metadata, repository, localRepository, previousMetadata)) {
182                 metadata.setRepository(repository);
183                 selected = repository;
184             }
185         }
186         if (loadMetadata(metadata, localRepository, localRepository, previousMetadata)) {
187             metadata.setRepository(null);
188             selected = localRepository;
189         }
190 
191         updateSnapshotMetadata(metadata, previousMetadata, selected, localRepository);
192     }
193 
194     private void updateSnapshotMetadata(
195             RepositoryMetadata metadata,
196             Map<ArtifactRepository, Metadata> previousMetadata,
197             ArtifactRepository selected,
198             ArtifactRepository localRepository)
199             throws RepositoryMetadataStoreException {
200         // TODO this could be a lot nicer... should really be in the snapshot transformation?
201         if (metadata.isSnapshot()) {
202             Metadata prevMetadata = metadata.getMetadata();
203 
204             for (ArtifactRepository repository : previousMetadata.keySet()) {
205                 Metadata m = previousMetadata.get(repository);
206                 if (repository.equals(selected)) {
207                     if (m.getVersioning() == null) {
208                         m.setVersioning(new Versioning());
209                     }
210 
211                     if (m.getVersioning().getSnapshot() == null) {
212                         m.getVersioning().setSnapshot(new Snapshot());
213                     }
214                 } else {
215                     if ((m.getVersioning() != null)
216                             && (m.getVersioning().getSnapshot() != null)
217                             && m.getVersioning().getSnapshot().isLocalCopy()) {
218                         m.getVersioning().getSnapshot().setLocalCopy(false);
219                         metadata.setMetadata(m);
220                         metadata.storeInLocalRepository(localRepository, repository);
221                     }
222                 }
223             }
224 
225             metadata.setMetadata(prevMetadata);
226         }
227     }
228 
229     private boolean loadMetadata(
230             RepositoryMetadata repoMetadata,
231             ArtifactRepository remoteRepository,
232             ArtifactRepository localRepository,
233             Map<ArtifactRepository, Metadata> previousMetadata) {
234         boolean setRepository = false;
235 
236         File metadataFile = new File(
237                 localRepository.getBasedir(),
238                 localRepository.pathOfLocalRepositoryMetadata(repoMetadata, remoteRepository));
239 
240         if (metadataFile.exists()) {
241             Metadata metadata;
242 
243             try {
244                 metadata = readMetadata(metadataFile);
245             } catch (RepositoryMetadataReadException e) {
246                 if (getLogger().isDebugEnabled()) {
247                     getLogger().warn(e.getMessage(), e);
248                 } else {
249                     getLogger().warn(e.getMessage());
250                 }
251                 return setRepository;
252             }
253 
254             if (repoMetadata.isSnapshot() && (previousMetadata != null)) {
255                 previousMetadata.put(remoteRepository, metadata);
256             }
257 
258             if (repoMetadata.getMetadata() != null) {
259                 setRepository = repoMetadata.getMetadata().merge(metadata);
260             } else {
261                 repoMetadata.setMetadata(metadata);
262                 setRepository = true;
263             }
264         }
265         return setRepository;
266     }
267 
268     /*
269      * TODO share with DefaultPluginMappingManager.
270      */
271     protected Metadata readMetadata(File mappingFile) throws RepositoryMetadataReadException {
272 
273         MetadataXpp3Reader mappingReader = new MetadataXpp3Reader();
274         try (InputStream in = Files.newInputStream(mappingFile.toPath())) {
275             Metadata result = mappingReader.read(in, false);
276             return result;
277         } catch (FileNotFoundException e) {
278             throw new RepositoryMetadataReadException("Cannot read metadata from '" + mappingFile + "'", e);
279         } catch (IOException | XmlPullParserException e) {
280             throw new RepositoryMetadataReadException(
281                     "Cannot read metadata from '" + mappingFile + "': " + e.getMessage(), e);
282         }
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 (OutputStream out = Files.newOutputStream(metadataFile.toPath())) {
314                 new MetadataXpp3Writer().write(out, 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 }