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.InputStream;
25  import java.io.OutputStream;
26  import java.nio.file.Files;
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.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 
270         MetadataXpp3Reader mappingReader = new MetadataXpp3Reader();
271         try (InputStream in = Files.newInputStream(mappingFile.toPath())) {
272             Metadata result = mappingReader.read(in, false);
273             return result;
274         } catch (FileNotFoundException e) {
275             throw new RepositoryMetadataReadException("Cannot read metadata from '" + mappingFile + "'", e);
276         } catch (IOException | XmlPullParserException e) {
277             throw new RepositoryMetadataReadException(
278                     "Cannot read metadata from '" + mappingFile + "': " + e.getMessage(), e);
279         }
280     }
281 
282     /**
283      * Ensures the last updated timestamp of the specified metadata does not refer to the future and fixes the local
284      * metadata if necessary to allow proper merging/updating of metadata during deployment.
285      */
286     private void fixTimestamp(File metadataFile, Metadata metadata, Metadata reference) {
287         boolean changed = false;
288 
289         if (metadata != null && reference != null) {
290             Versioning versioning = metadata.getVersioning();
291             Versioning versioningRef = reference.getVersioning();
292             if (versioning != null && versioningRef != null) {
293                 String lastUpdated = versioning.getLastUpdated();
294                 String now = versioningRef.getLastUpdated();
295                 if (lastUpdated != null && now != null && now.compareTo(lastUpdated) < 0) {
296                     getLogger()
297                             .warn("The last updated timestamp in " + metadataFile + " refers to the future (now = "
298                                     + now
299                                     + ", lastUpdated = " + lastUpdated + "). Please verify that the clocks of all"
300                                     + " deploying machines are reasonably synchronized.");
301                     versioning.setLastUpdated(now);
302                     changed = true;
303                 }
304             }
305         }
306 
307         if (changed) {
308             getLogger().debug("Repairing metadata in " + metadataFile);
309 
310             try (OutputStream out = Files.newOutputStream(metadataFile.toPath())) {
311                 new MetadataXpp3Writer().write(out, metadata);
312             } catch (IOException e) {
313                 String msg = "Could not write fixed metadata to " + metadataFile + ": " + e.getMessage();
314                 if (getLogger().isDebugEnabled()) {
315                     getLogger().warn(msg, e);
316                 } else {
317                     getLogger().warn(msg);
318                 }
319             }
320         }
321     }
322 
323     public void resolveAlways(
324             RepositoryMetadata metadata, ArtifactRepository localRepository, ArtifactRepository remoteRepository)
325             throws RepositoryMetadataResolutionException {
326         File file;
327         try {
328             file = getArtifactMetadataFromDeploymentRepository(metadata, localRepository, remoteRepository);
329         } catch (TransferFailedException e) {
330             throw new RepositoryMetadataResolutionException(
331                     metadata + " could not be retrieved from repository: " + remoteRepository.getId()
332                             + " due to an error: " + e.getMessage(),
333                     e);
334         }
335 
336         try {
337             if (file.exists()) {
338                 Metadata prevMetadata = readMetadata(file);
339                 metadata.setMetadata(prevMetadata);
340             }
341         } catch (RepositoryMetadataReadException e) {
342             throw new RepositoryMetadataResolutionException(e.getMessage(), e);
343         }
344     }
345 
346     private File getArtifactMetadataFromDeploymentRepository(
347             ArtifactMetadata metadata, ArtifactRepository localRepo, ArtifactRepository remoteRepository)
348             throws TransferFailedException {
349         File file =
350                 new File(localRepo.getBasedir(), localRepo.pathOfLocalRepositoryMetadata(metadata, remoteRepository));
351 
352         try {
353             wagonManager.getArtifactMetadataFromDeploymentRepository(
354                     metadata, remoteRepository, file, ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN);
355         } catch (ResourceDoesNotExistException e) {
356             getLogger()
357                     .info(metadata + " could not be found on repository: " + remoteRepository.getId()
358                             + ", so will be created");
359 
360             // delete the local copy so the old details aren't used.
361             if (file.exists()) {
362                 if (!file.delete()) {
363                     // sleep for 10ms just in case this is windows holding a file lock
364                     try {
365                         Thread.sleep(10);
366                     } catch (InterruptedException ie) {
367                         // ignore
368                     }
369                     file.delete(); // if this fails, forget about it
370                 }
371             }
372         } finally {
373             if (metadata instanceof RepositoryMetadata) {
374                 updateCheckManager.touch((RepositoryMetadata) metadata, remoteRepository, file);
375             }
376         }
377         return file;
378     }
379 
380     public void deploy(
381             ArtifactMetadata metadata, ArtifactRepository localRepository, ArtifactRepository deploymentRepository)
382             throws RepositoryMetadataDeploymentException {
383         File file;
384         if (metadata instanceof RepositoryMetadata) {
385             getLogger().info("Retrieving previous metadata from " + deploymentRepository.getId());
386             try {
387                 file = getArtifactMetadataFromDeploymentRepository(metadata, localRepository, deploymentRepository);
388             } catch (TransferFailedException e) {
389                 throw new RepositoryMetadataDeploymentException(
390                         metadata + " could not be retrieved from repository: " + deploymentRepository.getId()
391                                 + " due to an error: " + e.getMessage(),
392                         e);
393             }
394 
395             if (file.isFile()) {
396                 try {
397                     fixTimestamp(file, readMetadata(file), ((RepositoryMetadata) metadata).getMetadata());
398                 } catch (RepositoryMetadataReadException e) {
399                     // will be reported via storeInlocalRepository
400                 }
401             }
402         } else {
403             // It's a POM - we don't need to retrieve it first
404             file = new File(
405                     localRepository.getBasedir(),
406                     localRepository.pathOfLocalRepositoryMetadata(metadata, deploymentRepository));
407         }
408 
409         try {
410             metadata.storeInLocalRepository(localRepository, deploymentRepository);
411         } catch (RepositoryMetadataStoreException e) {
412             throw new RepositoryMetadataDeploymentException("Error installing metadata: " + e.getMessage(), e);
413         }
414 
415         try {
416             wagonManager.putArtifactMetadata(file, metadata, deploymentRepository);
417         } catch (TransferFailedException e) {
418             throw new RepositoryMetadataDeploymentException("Error while deploying metadata: " + e.getMessage(), e);
419         }
420     }
421 
422     public void install(ArtifactMetadata metadata, ArtifactRepository localRepository)
423             throws RepositoryMetadataInstallationException {
424         try {
425             metadata.storeInLocalRepository(localRepository, localRepository);
426         } catch (RepositoryMetadataStoreException e) {
427             throw new RepositoryMetadataInstallationException("Error installing metadata: " + e.getMessage(), e);
428         }
429     }
430 }