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