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      @Override
63      public void resolve(
64              RepositoryMetadata metadata,
65              List<ArtifactRepository> remoteRepositories,
66              ArtifactRepository localRepository)
67              throws RepositoryMetadataResolutionException {
68          RepositoryRequest request = new DefaultRepositoryRequest();
69          request.setLocalRepository(localRepository);
70          request.setRemoteRepositories(remoteRepositories);
71          resolve(metadata, request);
72      }
73  
74      @Override
75      public void resolve(RepositoryMetadata metadata, RepositoryRequest request)
76              throws RepositoryMetadataResolutionException {
77          ArtifactRepository localRepo = request.getLocalRepository();
78          List<ArtifactRepository> remoteRepositories = request.getRemoteRepositories();
79  
80          if (!request.isOffline()) {
81              Date localCopyLastModified = null;
82              if (metadata.getBaseVersion() != null) {
83                  localCopyLastModified = getLocalCopyLastModified(localRepo, metadata);
84              }
85  
86              for (ArtifactRepository repository : remoteRepositories) {
87                  ArtifactRepositoryPolicy policy = metadata.getPolicy(repository);
88  
89                  File file =
90                          new File(localRepo.getBasedir(), localRepo.pathOfLocalRepositoryMetadata(metadata, repository));
91                  boolean update;
92  
93                  if (!policy.isEnabled()) {
94                      update = false;
95  
96                      if (getLogger().isDebugEnabled()) {
97                          getLogger()
98                                  .debug("Skipping update check for " + metadata.getKey() + " (" + file
99                                          + ") from disabled repository " + repository.getId() + " ("
100                                         + repository.getUrl() + ")");
101                     }
102                 } else if (request.isForceUpdate()) {
103                     update = true;
104                 } else if (localCopyLastModified != null && !policy.checkOutOfDate(localCopyLastModified)) {
105                     update = false;
106 
107                     if (getLogger().isDebugEnabled()) {
108                         getLogger()
109                                 .debug("Skipping update check for " + metadata.getKey() + " (" + file
110                                         + ") from repository " + repository.getId() + " (" + repository.getUrl()
111                                         + ") in favor of local copy");
112                     }
113                 } else {
114                     update = updateCheckManager.isUpdateRequired(metadata, repository, file);
115                 }
116 
117                 if (update) {
118                     getLogger().info(metadata.getKey() + ": checking for updates from " + repository.getId());
119                     try {
120                         wagonManager.getArtifactMetadata(metadata, repository, file, policy.getChecksumPolicy());
121                     } catch (ResourceDoesNotExistException e) {
122                         getLogger().debug(metadata + " could not be found on repository: " + repository.getId());
123 
124                         // delete the local copy so the old details aren't used.
125                         if (file.exists()) {
126                             if (!file.delete()) {
127                                 // sleep for 10ms just in case this is windows holding a file lock
128                                 try {
129                                     Thread.sleep(10);
130                                 } catch (InterruptedException ie) {
131                                     // ignore
132                                 }
133                                 file.delete(); // if this fails, forget about it
134                             }
135                         }
136                     } catch (TransferFailedException e) {
137                         getLogger()
138                                 .warn(metadata + " could not be retrieved from repository: " + repository.getId()
139                                         + " due to an error: " + e.getMessage());
140                         getLogger().debug("Exception", e);
141                     } finally {
142                         updateCheckManager.touch(metadata, repository, file);
143                     }
144                 }
145 
146                 // TODO should this be inside the above check?
147                 // touch file so that this is not checked again until interval has passed
148                 if (file.exists()) {
149                     file.setLastModified(System.currentTimeMillis());
150                 }
151             }
152         }
153 
154         try {
155             mergeMetadata(metadata, remoteRepositories, localRepo);
156         } catch (RepositoryMetadataStoreException e) {
157             throw new RepositoryMetadataResolutionException(
158                     "Unable to store local copy of metadata: " + e.getMessage(), e);
159         }
160     }
161 
162     private Date getLocalCopyLastModified(ArtifactRepository localRepository, RepositoryMetadata metadata) {
163         String metadataPath = localRepository.pathOfLocalRepositoryMetadata(metadata, localRepository);
164         File metadataFile = new File(localRepository.getBasedir(), metadataPath);
165         return metadataFile.isFile() ? new Date(metadataFile.lastModified()) : null;
166     }
167 
168     private void mergeMetadata(
169             RepositoryMetadata metadata,
170             List<ArtifactRepository> remoteRepositories,
171             ArtifactRepository localRepository)
172             throws RepositoryMetadataStoreException {
173         // TODO currently this is first wins, but really we should take the latest by comparing either the
174         // snapshot timestamp, or some other timestamp later encoded into the metadata.
175         // TODO this needs to be repeated here so the merging doesn't interfere with the written metadata
176         //  - we'd be much better having a pristine input, and an ongoing metadata for merging instead
177 
178         Map<ArtifactRepository, Metadata> previousMetadata = new HashMap<>();
179         ArtifactRepository selected = null;
180         for (ArtifactRepository repository : remoteRepositories) {
181             ArtifactRepositoryPolicy policy = metadata.getPolicy(repository);
182 
183             if (policy.isEnabled() && loadMetadata(metadata, repository, localRepository, previousMetadata)) {
184                 metadata.setRepository(repository);
185                 selected = repository;
186             }
187         }
188         if (loadMetadata(metadata, localRepository, localRepository, previousMetadata)) {
189             metadata.setRepository(null);
190             selected = localRepository;
191         }
192 
193         updateSnapshotMetadata(metadata, previousMetadata, selected, localRepository);
194     }
195 
196     private void updateSnapshotMetadata(
197             RepositoryMetadata metadata,
198             Map<ArtifactRepository, Metadata> previousMetadata,
199             ArtifactRepository selected,
200             ArtifactRepository localRepository)
201             throws RepositoryMetadataStoreException {
202         // TODO this could be a lot nicer... should really be in the snapshot transformation?
203         if (metadata.isSnapshot()) {
204             Metadata prevMetadata = metadata.getMetadata();
205 
206             for (ArtifactRepository repository : previousMetadata.keySet()) {
207                 Metadata m = previousMetadata.get(repository);
208                 if (repository.equals(selected)) {
209                     if (m.getVersioning() == null) {
210                         m.setVersioning(new Versioning());
211                     }
212 
213                     if (m.getVersioning().getSnapshot() == null) {
214                         m.getVersioning().setSnapshot(new Snapshot());
215                     }
216                 } else {
217                     if ((m.getVersioning() != null)
218                             && (m.getVersioning().getSnapshot() != null)
219                             && m.getVersioning().getSnapshot().isLocalCopy()) {
220                         m.getVersioning().getSnapshot().setLocalCopy(false);
221                         metadata.setMetadata(m);
222                         metadata.storeInLocalRepository(localRepository, repository);
223                     }
224                 }
225             }
226 
227             metadata.setMetadata(prevMetadata);
228         }
229     }
230 
231     private boolean loadMetadata(
232             RepositoryMetadata repoMetadata,
233             ArtifactRepository remoteRepository,
234             ArtifactRepository localRepository,
235             Map<ArtifactRepository, Metadata> previousMetadata) {
236         boolean setRepository = false;
237 
238         File metadataFile = new File(
239                 localRepository.getBasedir(),
240                 localRepository.pathOfLocalRepositoryMetadata(repoMetadata, remoteRepository));
241 
242         if (metadataFile.exists()) {
243             Metadata metadata;
244 
245             try {
246                 metadata = readMetadata(metadataFile);
247             } catch (RepositoryMetadataReadException e) {
248                 if (getLogger().isDebugEnabled()) {
249                     getLogger().warn(e.getMessage(), e);
250                 } else {
251                     getLogger().warn(e.getMessage());
252                 }
253                 return setRepository;
254             }
255 
256             if (repoMetadata.isSnapshot() && (previousMetadata != null)) {
257                 previousMetadata.put(remoteRepository, metadata);
258             }
259 
260             if (repoMetadata.getMetadata() != null) {
261                 setRepository = repoMetadata.getMetadata().merge(metadata);
262             } else {
263                 repoMetadata.setMetadata(metadata);
264                 setRepository = true;
265             }
266         }
267         return setRepository;
268     }
269 
270     /*
271      * TODO share with DefaultPluginMappingManager.
272      */
273     protected Metadata readMetadata(File mappingFile) throws RepositoryMetadataReadException {
274 
275         try (InputStream in = Files.newInputStream(mappingFile.toPath())) {
276             return new Metadata(new MetadataStaxReader().read(in, false));
277         } catch (FileNotFoundException e) {
278             throw new RepositoryMetadataReadException("Cannot read metadata from '" + mappingFile + "'", e);
279         } catch (IOException | XMLStreamException 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 MetadataStaxWriter().write(out, metadata.getDelegate());
315             } catch (IOException | XMLStreamException 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     @Override
327     public void resolveAlways(
328             RepositoryMetadata metadata, ArtifactRepository localRepository, ArtifactRepository remoteRepository)
329             throws RepositoryMetadataResolutionException {
330         File file;
331         try {
332             file = getArtifactMetadataFromDeploymentRepository(metadata, localRepository, remoteRepository);
333         } catch (TransferFailedException e) {
334             throw new RepositoryMetadataResolutionException(
335                     metadata + " could not be retrieved from repository: " + remoteRepository.getId()
336                             + " due to an error: " + e.getMessage(),
337                     e);
338         }
339 
340         try {
341             if (file.exists()) {
342                 Metadata prevMetadata = readMetadata(file);
343                 metadata.setMetadata(prevMetadata);
344             }
345         } catch (RepositoryMetadataReadException e) {
346             throw new RepositoryMetadataResolutionException(e.getMessage(), e);
347         }
348     }
349 
350     private File getArtifactMetadataFromDeploymentRepository(
351             ArtifactMetadata metadata, ArtifactRepository localRepo, ArtifactRepository remoteRepository)
352             throws TransferFailedException {
353         File file =
354                 new File(localRepo.getBasedir(), localRepo.pathOfLocalRepositoryMetadata(metadata, remoteRepository));
355 
356         try {
357             wagonManager.getArtifactMetadataFromDeploymentRepository(
358                     metadata, remoteRepository, file, ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN);
359         } catch (ResourceDoesNotExistException e) {
360             getLogger()
361                     .info(metadata + " could not be found on repository: " + remoteRepository.getId()
362                             + ", so will be created");
363 
364             // delete the local copy so the old details aren't used.
365             if (file.exists()) {
366                 if (!file.delete()) {
367                     // sleep for 10ms just in case this is windows holding a file lock
368                     try {
369                         Thread.sleep(10);
370                     } catch (InterruptedException ie) {
371                         // ignore
372                     }
373                     file.delete(); // if this fails, forget about it
374                 }
375             }
376         } finally {
377             if (metadata instanceof RepositoryMetadata repositoryMetadata) {
378                 updateCheckManager.touch(repositoryMetadata, remoteRepository, file);
379             }
380         }
381         return file;
382     }
383 
384     @Override
385     public void deploy(
386             ArtifactMetadata metadata, ArtifactRepository localRepository, ArtifactRepository deploymentRepository)
387             throws RepositoryMetadataDeploymentException {
388         File file;
389         if (metadata instanceof RepositoryMetadata repositoryMetadata) {
390             getLogger().info("Retrieving previous metadata from " + deploymentRepository.getId());
391             try {
392                 file = getArtifactMetadataFromDeploymentRepository(metadata, localRepository, deploymentRepository);
393             } catch (TransferFailedException e) {
394                 throw new RepositoryMetadataDeploymentException(
395                         metadata + " could not be retrieved from repository: " + deploymentRepository.getId()
396                                 + " due to an error: " + e.getMessage(),
397                         e);
398             }
399 
400             if (file.isFile()) {
401                 try {
402                     fixTimestamp(file, readMetadata(file), repositoryMetadata.getMetadata());
403                 } catch (RepositoryMetadataReadException e) {
404                     // will be reported via storeInlocalRepository
405                 }
406             }
407         } else {
408             // It's a POM - we don't need to retrieve it first
409             file = new File(
410                     localRepository.getBasedir(),
411                     localRepository.pathOfLocalRepositoryMetadata(metadata, deploymentRepository));
412         }
413 
414         try {
415             metadata.storeInLocalRepository(localRepository, deploymentRepository);
416         } catch (RepositoryMetadataStoreException e) {
417             throw new RepositoryMetadataDeploymentException("Error installing metadata: " + e.getMessage(), e);
418         }
419 
420         try {
421             wagonManager.putArtifactMetadata(file, metadata, deploymentRepository);
422         } catch (TransferFailedException e) {
423             throw new RepositoryMetadataDeploymentException("Error while deploying metadata: " + e.getMessage(), e);
424         }
425     }
426 
427     @Override
428     public void install(ArtifactMetadata metadata, ArtifactRepository localRepository)
429             throws RepositoryMetadataInstallationException {
430         try {
431             metadata.storeInLocalRepository(localRepository, localRepository);
432         } catch (RepositoryMetadataStoreException e) {
433             throw new RepositoryMetadataInstallationException("Error installing metadata: " + e.getMessage(), e);
434         }
435     }
436 }