This project has retired. For details please refer to its Attic page.
Maven2RepositoryStorage xref
View Javadoc
1   package org.apache.archiva.metadata.repository.storage.maven2;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.archiva.checksum.ChecksumAlgorithm;
23  import org.apache.archiva.checksum.ChecksummedFile;
24  import org.apache.archiva.common.Try;
25  import org.apache.archiva.common.utils.VersionUtil;
26  import org.apache.archiva.filter.Filter;
27  import org.apache.archiva.maven2.metadata.MavenMetadataReader;
28  import org.apache.archiva.metadata.model.ArtifactMetadata;
29  import org.apache.archiva.metadata.model.ProjectMetadata;
30  import org.apache.archiva.metadata.model.ProjectVersionMetadata;
31  import org.apache.archiva.metadata.model.facets.RepositoryProblemFacet;
32  import org.apache.archiva.metadata.repository.storage.*;
33  import org.apache.archiva.model.ArchivaRepositoryMetadata;
34  import org.apache.archiva.model.ArtifactReference;
35  import org.apache.archiva.model.SnapshotVersion;
36  import org.apache.archiva.policies.ProxyDownloadException;
37  import org.apache.archiva.proxy.ProxyRegistry;
38  import org.apache.archiva.proxy.maven.WagonFactory;
39  import org.apache.archiva.proxy.model.NetworkProxy;
40  import org.apache.archiva.proxy.model.ProxyConnector;
41  import org.apache.archiva.proxy.model.RepositoryProxyHandler;
42  import org.apache.archiva.repository.*;
43  import org.apache.archiva.repository.content.PathParser;
44  import org.apache.archiva.repository.maven2.MavenSystemManager;
45  import org.apache.archiva.repository.storage.StorageAsset;
46  import org.apache.archiva.xml.XMLException;
47  import org.apache.commons.lang3.ArrayUtils;
48  import org.apache.commons.lang3.StringUtils;
49  import org.apache.maven.model.*;
50  import org.apache.maven.model.building.*;
51  import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
52  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
53  import org.slf4j.Logger;
54  import org.slf4j.LoggerFactory;
55  import org.springframework.context.ApplicationContext;
56  import org.springframework.stereotype.Service;
57  
58  import javax.annotation.PostConstruct;
59  import javax.inject.Inject;
60  import javax.inject.Named;
61  import java.io.FileNotFoundException;
62  import java.io.IOException;
63  import java.io.Reader;
64  import java.nio.channels.Channels;
65  import java.nio.charset.Charset;
66  import java.nio.file.NoSuchFileException;
67  import java.time.ZoneId;
68  import java.time.ZonedDateTime;
69  import java.util.*;
70  import java.util.function.Predicate;
71  import java.util.stream.Collectors;
72  
73  // import java.io.FileNotFoundException;
74  
75  /**
76   * <p>
77   * Maven 2 repository format storage implementation. This class currently takes parameters to indicate the repository to
78   * deal with rather than being instantiated per-repository.
79   * FIXME: instantiate one per repository and allocate permanently from a factory (which can be obtained within the session).
80   * </p>
81   * <p>
82   * The session is passed in as an argument to obtain any necessary resources, rather than the class being instantiated
83   * within the session in the context of a single managed repository's resolution needs.
84   * </p>
85   */
86  @Service("repositoryStorage#maven2")
87  public class Maven2RepositoryStorage
88          implements RepositoryStorage {
89  
90      private static final Logger LOGGER = LoggerFactory.getLogger(Maven2RepositoryStorage.class);
91  
92      private ModelBuilder builder;
93  
94      @Inject
95      RepositoryRegistry repositoryRegistry;
96  
97      @Inject
98      @Named("repositoryPathTranslator#maven2")
99      private RepositoryPathTranslator pathTranslator;
100 
101     @Inject
102     private WagonFactory wagonFactory;
103 
104     @Inject
105     private ApplicationContext applicationContext;
106 
107     @Inject
108     @Named("pathParser#default")
109     private PathParser pathParser;
110 
111     @Inject
112     private ProxyRegistry proxyRegistry;
113 
114     @Inject
115     private MavenSystemManager mavenSystemManager;
116 
117     private static final String METADATA_FILENAME_START = "maven-metadata";
118 
119     private static final String METADATA_FILENAME = METADATA_FILENAME_START + ".xml";
120 
121     // This array must be lexically sorted
122     private static final String[] IGNORED_FILES = {METADATA_FILENAME, "resolver-status.properties"};
123 
124     private static final MavenXpp3Reader MAVEN_XPP_3_READER = new MavenXpp3Reader();
125 
126 
127     @PostConstruct
128     public void initialize() {
129         builder = new DefaultModelBuilderFactory().newInstance();
130 
131     }
132 
133     @Override
134     public ProjectMetadata readProjectMetadata(String repoId, String namespace, String projectId) {
135         // TODO: could natively implement the "shared model" concept from the browse action to avoid needing it there?
136         return null;
137     }
138 
139     @Override
140     public ProjectVersionMetadata readProjectVersionMetadata(ReadMetadataRequest readMetadataRequest)
141             throws RepositoryStorageMetadataNotFoundException, RepositoryStorageMetadataInvalidException,
142             RepositoryStorageRuntimeException {
143 
144         ManagedRepository managedRepository = repositoryRegistry.getManagedRepository(readMetadataRequest.getRepositoryId());
145         boolean isReleases = managedRepository.getActiveReleaseSchemes().contains(ReleaseScheme.RELEASE);
146         boolean isSnapshots = managedRepository.getActiveReleaseSchemes().contains(ReleaseScheme.SNAPSHOT);
147         String artifactVersion = readMetadataRequest.getProjectVersion();
148         // olamy: in case of browsing via the ui we can mix repos (parent of a SNAPSHOT can come from release repo)
149         if (!readMetadataRequest.isBrowsingRequest()) {
150             if (VersionUtil.isSnapshot(artifactVersion)) {
151                 // skygo trying to improve speed by honoring managed configuration MRM-1658
152                 if (isReleases && !isSnapshots) {
153                     throw new RepositoryStorageRuntimeException("lookforsnaponreleaseonly",
154                             "managed repo is configured for release only");
155                 }
156             } else {
157                 if (!isReleases && isSnapshots) {
158                     throw new RepositoryStorageRuntimeException("lookforsreleaseonsneponly",
159                             "managed repo is configured for snapshot only");
160                 }
161             }
162         }
163         StorageAsset basedir = managedRepository.getAsset("");
164         if (VersionUtil.isSnapshot(artifactVersion)) {
165             StorageAsset metadataFile = pathTranslator.toFile(basedir, readMetadataRequest.getNamespace(),
166                     readMetadataRequest.getProjectId(), artifactVersion,
167                     METADATA_FILENAME);
168             try {
169                 ArchivaRepositoryMetadata metadata = MavenMetadataReader.read(metadataFile);
170 
171                 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
172                 SnapshotVersion snapshotVersion = metadata.getSnapshotVersion();
173                 if (snapshotVersion != null) {
174                     artifactVersion =
175                             artifactVersion.substring(0, artifactVersion.length() - 8); // remove SNAPSHOT from end
176                     artifactVersion =
177                             artifactVersion + snapshotVersion.getTimestamp() + "-" + snapshotVersion.getBuildNumber();
178                 }
179             } catch (XMLException | IOException e) {
180                 // unable to parse metadata - LOGGER it, and continue with the version as the original SNAPSHOT version
181                 LOGGER.warn("Invalid metadata: {} - {}", metadataFile, e.getMessage());
182             }
183         }
184 
185         // TODO: won't work well with some other layouts, might need to convert artifact parts to ID by path translator
186         String id = readMetadataRequest.getProjectId() + "-" + artifactVersion + ".pom";
187         StorageAsset file =
188                 pathTranslator.toFile(basedir, readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(),
189                         readMetadataRequest.getProjectVersion(), id);
190 
191         if (!file.exists()) {
192             // metadata could not be resolved
193             throw new RepositoryStorageMetadataNotFoundException(
194                     "The artifact's POM file '" + file.getPath() + "' was missing");
195         }
196 
197         // TODO: this is a workaround until we can properly resolve using proxies as well - this doesn't cache
198         //       anything locally!
199         List<RemoteRepository> remoteRepositories = new ArrayList<>();
200         Map<String, NetworkProxy> networkProxies = new HashMap<>();
201 
202         Map<String, List<ProxyConnector>> proxyConnectorsMap = proxyRegistry.getProxyConnectorAsMap();
203         List<ProxyConnector> proxyConnectors = proxyConnectorsMap.get(readMetadataRequest.getRepositoryId());
204         if (proxyConnectors != null) {
205             for (ProxyConnector proxyConnector : proxyConnectors) {
206                 RemoteRepository remoteRepoConfig =
207                         repositoryRegistry.getRemoteRepository(proxyConnector.getTargetRepository().getId());
208 
209                 if (remoteRepoConfig != null) {
210                     remoteRepositories.add(remoteRepoConfig);
211 
212                     NetworkProxy networkProxyConfig =
213                             proxyRegistry.getNetworkProxy(proxyConnector.getProxyId());
214 
215                     if (networkProxyConfig != null) {
216                         // key/value: remote repo ID/proxy info
217                         networkProxies.put(proxyConnector.getTargetRepository().getId(), networkProxyConfig);
218                     }
219                 }
220             }
221         }
222 
223         // That's a browsing request so we can a mix of SNAPSHOT and release artifacts (especially with snapshots which
224         // can have released parent pom
225         if (readMetadataRequest.isBrowsingRequest()) {
226             remoteRepositories.addAll(repositoryRegistry.getRemoteRepositories());
227         }
228 
229         ModelBuildingRequest req =
230                 new DefaultModelBuildingRequest().setProcessPlugins(false).setPomFile(file.getFilePath().toFile()).setTwoPhaseBuilding(
231                         false).setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);
232 
233         //MRM-1607. olamy this will resolve jdk profiles on the current running archiva jvm
234         req.setSystemProperties(System.getProperties());
235 
236         // MRM-1411
237         req.setModelResolver(
238                 new RepositoryModelResolver(managedRepository, pathTranslator, wagonFactory, remoteRepositories,
239                         networkProxies, managedRepository, mavenSystemManager));
240 
241         Model model;
242         try {
243             model = builder.build(req).getEffectiveModel();
244         } catch (ModelBuildingException e) {
245             String msg = "The artifact's POM file '" + file + "' was invalid: " + e.getMessage();
246 
247             List<ModelProblem> modelProblems = e.getProblems();
248             for (ModelProblem problem : modelProblems) {
249                 // MRM-1411, related to MRM-1335
250                 // this means that the problem was that the parent wasn't resolved!
251                 // olamy really hackhish but fail with java profile so use error message
252                 // || ( StringUtils.startsWith( problem.getMessage(), "Failed to determine Java version for profile" ) )
253                 // but setTwoPhaseBuilding(true) fix that
254                 if (((problem.getException() instanceof FileNotFoundException
255                         || problem.getException() instanceof NoSuchFileException
256                 ) && e.getModelId() != null &&
257                         !e.getModelId().equals(problem.getModelId()))) {
258                     LOGGER.warn("The artifact's parent POM file '{}' cannot be resolved. "
259                             + "Using defaults for project version metadata..", file);
260 
261                     ProjectVersionMetadatajectVersionMetadata.html#ProjectVersionMetadata">ProjectVersionMetadata metadata = new ProjectVersionMetadata();
262                     metadata.setId(readMetadataRequest.getProjectVersion());
263 
264                     MavenProjectFacetepository/storage/maven2/MavenProjectFacet.html#MavenProjectFacet">MavenProjectFacet facet = new MavenProjectFacet();
265                     facet.setGroupId(readMetadataRequest.getNamespace());
266                     facet.setArtifactId(readMetadataRequest.getProjectId());
267                     facet.setPackaging("jar");
268                     metadata.addFacet(facet);
269 
270                     String errMsg =
271                             "Error in resolving artifact's parent POM file. " + (problem.getException() == null
272                                     ? problem.getMessage()
273                                     : problem.getException().getMessage());
274                     RepositoryProblemFacetsitoryProblemFacet.html#RepositoryProblemFacet">RepositoryProblemFacet repoProblemFacet = new RepositoryProblemFacet();
275                     repoProblemFacet.setRepositoryId(readMetadataRequest.getRepositoryId());
276                     repoProblemFacet.setId(readMetadataRequest.getRepositoryId());
277                     repoProblemFacet.setMessage(errMsg);
278                     repoProblemFacet.setProblem(errMsg);
279                     repoProblemFacet.setProject(readMetadataRequest.getProjectId());
280                     repoProblemFacet.setVersion(readMetadataRequest.getProjectVersion());
281                     repoProblemFacet.setNamespace(readMetadataRequest.getNamespace());
282 
283                     metadata.addFacet(repoProblemFacet);
284 
285                     return metadata;
286                 }
287             }
288 
289             throw new RepositoryStorageMetadataInvalidException("invalid-pom", msg, e);
290         }
291 
292         // Check if the POM is in the correct location
293         boolean correctGroupId = readMetadataRequest.getNamespace().equals(model.getGroupId());
294         boolean correctArtifactId = readMetadataRequest.getProjectId().equals(model.getArtifactId());
295         boolean correctVersion = readMetadataRequest.getProjectVersion().equals(model.getVersion());
296         if (!correctGroupId || !correctArtifactId || !correctVersion) {
297             StringBuilder message = new StringBuilder("Incorrect POM coordinates in '" + file + "':");
298             if (!correctGroupId) {
299                 message.append("\nIncorrect group ID: ").append(model.getGroupId());
300             }
301             if (!correctArtifactId) {
302                 message.append("\nIncorrect artifact ID: ").append(model.getArtifactId());
303             }
304             if (!correctVersion) {
305                 message.append("\nIncorrect version: ").append(model.getVersion());
306             }
307 
308             throw new RepositoryStorageMetadataInvalidException("mislocated-pom", message.toString());
309         }
310 
311         ProjectVersionMetadatajectVersionMetadata.html#ProjectVersionMetadata">ProjectVersionMetadata metadata = new ProjectVersionMetadata();
312         metadata.setCiManagement(convertCiManagement(model.getCiManagement()));
313         metadata.setDescription(model.getDescription());
314         metadata.setId(readMetadataRequest.getProjectVersion());
315         metadata.setIssueManagement(convertIssueManagement(model.getIssueManagement()));
316         metadata.setLicenses(convertLicenses(model.getLicenses()));
317         metadata.setMailingLists(convertMailingLists(model.getMailingLists()));
318         metadata.setDependencies(convertDependencies(model.getDependencies()));
319         metadata.setName(model.getName());
320         metadata.setOrganization(convertOrganization(model.getOrganization()));
321         metadata.setScm(convertScm(model.getScm()));
322         metadata.setUrl(model.getUrl());
323         metadata.setProperties(model.getProperties());
324 
325         MavenProjectFacetepository/storage/maven2/MavenProjectFacet.html#MavenProjectFacet">MavenProjectFacet facet = new MavenProjectFacet();
326         facet.setGroupId(model.getGroupId() != null ? model.getGroupId() : model.getParent().getGroupId());
327         facet.setArtifactId(model.getArtifactId());
328         facet.setPackaging(model.getPackaging());
329         if (model.getParent() != null) {
330             MavenProjectParentository/storage/maven2/MavenProjectParent.html#MavenProjectParent">MavenProjectParent parent = new MavenProjectParent();
331             parent.setGroupId(model.getParent().getGroupId());
332             parent.setArtifactId(model.getParent().getArtifactId());
333             parent.setVersion(model.getParent().getVersion());
334             facet.setParent(parent);
335         }
336         metadata.addFacet(facet);
337 
338         return metadata;
339 
340 
341     }
342 
343     public void setWagonFactory(WagonFactory wagonFactory) {
344         this.wagonFactory = wagonFactory;
345     }
346 
347     private List<org.apache.archiva.metadata.model.Dependency> convertDependencies(List<Dependency> dependencies) {
348         List<org.apache.archiva.metadata.model.Dependency> l = new ArrayList<>();
349         for (Dependency dependency : dependencies) {
350             org.apache.archiva.metadata.model.Dependency newDependency =
351                     new org.apache.archiva.metadata.model.Dependency();
352             newDependency.setArtifactId(dependency.getArtifactId());
353             newDependency.setClassifier(dependency.getClassifier());
354             newDependency.setNamespace(dependency.getGroupId());
355             newDependency.setOptional(dependency.isOptional());
356             newDependency.setScope(dependency.getScope());
357             newDependency.setSystemPath(dependency.getSystemPath());
358             newDependency.setType(dependency.getType());
359             newDependency.setVersion(dependency.getVersion());
360             l.add(newDependency);
361         }
362         return l;
363     }
364 
365     private org.apache.archiva.metadata.model.Scm convertScm(Scm scm) {
366         org.apache.archiva.metadata.model.Scm newScm = null;
367         if (scm != null) {
368             newScm = new org.apache.archiva.metadata.model.Scm();
369             newScm.setConnection(scm.getConnection());
370             newScm.setDeveloperConnection(scm.getDeveloperConnection());
371             newScm.setUrl(scm.getUrl());
372         }
373         return newScm;
374     }
375 
376     private org.apache.archiva.metadata.model.Organization convertOrganization(Organization organization) {
377         org.apache.archiva.metadata.model.Organization org = null;
378         if (organization != null) {
379             org = new org.apache.archiva.metadata.model.Organization();
380             org.setName(organization.getName());
381             org.setUrl(organization.getUrl());
382         }
383         return org;
384     }
385 
386     private List<org.apache.archiva.metadata.model.License> convertLicenses(List<License> licenses) {
387         List<org.apache.archiva.metadata.model.License> l = new ArrayList<>();
388         for (License license : licenses) {
389             org.apache.archiva.metadata.model.License newLicense = new org.apache.archiva.metadata.model.License();
390             newLicense.setName(license.getName());
391             newLicense.setUrl(license.getUrl());
392             l.add(newLicense);
393         }
394         return l;
395     }
396 
397     private List<org.apache.archiva.metadata.model.MailingList> convertMailingLists(List<MailingList> mailingLists) {
398         List<org.apache.archiva.metadata.model.MailingList> l = new ArrayList<>();
399         for (MailingList mailingList : mailingLists) {
400             org.apache.archiva.metadata.model.MailingList newMailingList =
401                     new org.apache.archiva.metadata.model.MailingList();
402             newMailingList.setName(mailingList.getName());
403             newMailingList.setMainArchiveUrl(mailingList.getArchive());
404             newMailingList.setPostAddress(mailingList.getPost());
405             newMailingList.setSubscribeAddress(mailingList.getSubscribe());
406             newMailingList.setUnsubscribeAddress(mailingList.getUnsubscribe());
407             newMailingList.setOtherArchives(mailingList.getOtherArchives());
408             l.add(newMailingList);
409         }
410         return l;
411     }
412 
413     private org.apache.archiva.metadata.model.IssueManagement convertIssueManagement(IssueManagement issueManagement) {
414         org.apache.archiva.metadata.model.IssueManagement im = null;
415         if (issueManagement != null) {
416             im = new org.apache.archiva.metadata.model.IssueManagement();
417             im.setSystem(issueManagement.getSystem());
418             im.setUrl(issueManagement.getUrl());
419         }
420         return im;
421     }
422 
423     private org.apache.archiva.metadata.model.CiManagement convertCiManagement(CiManagement ciManagement) {
424         org.apache.archiva.metadata.model.CiManagement ci = null;
425         if (ciManagement != null) {
426             ci = new org.apache.archiva.metadata.model.CiManagement();
427             ci.setSystem(ciManagement.getSystem());
428             ci.setUrl(ciManagement.getUrl());
429         }
430         return ci;
431     }
432 
433     @Override
434     public Collection<String> listRootNamespaces(String repoId, Filter<String> filter)
435             throws RepositoryStorageRuntimeException {
436         StorageAsset dir = getRepositoryBasedir(repoId);
437 
438         return getSortedFiles(dir, filter);
439     }
440 
441     private static Collection<String> getSortedFiles(StorageAsset dir, Filter<String> filter) {
442 
443         final Predicate<StorageAsset> dFilter = new DirectoryFilter(filter);
444         return dir.list().stream().filter(f -> f.isContainer())
445                 .filter(dFilter)
446                 .map(path -> path.getName().toString())
447                 .sorted().collect(Collectors.toList());
448 
449     }
450 
451     private StorageAsset getRepositoryBasedir(String repoId)
452             throws RepositoryStorageRuntimeException {
453         ManagedRepository repositoryConfiguration = repositoryRegistry.getManagedRepository(repoId);
454 
455         return repositoryConfiguration.getAsset("");
456     }
457 
458     @Override
459     public Collection<String> listNamespaces(String repoId, String namespace, Filter<String> filter)
460             throws RepositoryStorageRuntimeException {
461         StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace);
462         if (!(dir.exists()) && !dir.isContainer()) {
463             return Collections.emptyList();
464         }
465         // scan all the directories which are potential namespaces. Any directories known to be projects are excluded
466         Predicate<StorageAsset> dFilter = new DirectoryFilter(filter);
467         return dir.list().stream().filter(dFilter).filter(path -> !isProject(path, filter)).map(path -> path.getName().toString())
468                 .sorted().collect(Collectors.toList());
469     }
470 
471     @Override
472     public Collection<String> listProjects(String repoId, String namespace, Filter<String> filter)
473             throws RepositoryStorageRuntimeException {
474         StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace);
475         if (!(dir.exists() && dir.isContainer())) {
476             return Collections.emptyList();
477         }
478         // scan all directories in the namespace, and only include those that are known to be projects
479         final Predicate<StorageAsset> dFilter = new DirectoryFilter(filter);
480         return dir.list().stream().filter(dFilter).filter(path -> isProject(path, filter)).map(path -> path.getName().toString())
481                 .sorted().collect(Collectors.toList());
482 
483     }
484 
485     @Override
486     public Collection<String> listProjectVersions(String repoId, String namespace, String projectId,
487                                                   Filter<String> filter)
488             throws RepositoryStorageRuntimeException {
489         StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace, projectId);
490         if (!(dir.exists() && dir.isContainer())) {
491             return Collections.emptyList();
492         }
493 
494         // all directories in a project directory can be considered a version
495         return getSortedFiles(dir, filter);
496     }
497 
498     @Override
499     public Collection<ArtifactMetadata> readArtifactsMetadata(ReadMetadataRequest readMetadataRequest)
500             throws RepositoryStorageRuntimeException {
501         StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(readMetadataRequest.getRepositoryId()),
502                 readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(),
503                 readMetadataRequest.getProjectVersion());
504         if (!(dir.exists() && dir.isContainer())) {
505             return Collections.emptyList();
506         }
507 
508         // all files that are not metadata and not a checksum / signature are considered artifacts
509         final Predicate<StorageAsset> dFilter = new ArtifactDirectoryFilter(readMetadataRequest.getFilter());
510         // Returns a map TRUE -> (success values), FALSE -> (Exceptions)
511         Map<Boolean, List<Try<ArtifactMetadata>>> result = dir.list().stream().filter(dFilter).map(path -> {
512                     try {
513                         return Try.success(getArtifactFromFile(readMetadataRequest.getRepositoryId(), readMetadataRequest.getNamespace(),
514                                 readMetadataRequest.getProjectId(), readMetadataRequest.getProjectVersion(),
515                                 path));
516                     } catch (Exception e) {
517                         LOGGER.debug("Could not create metadata for {}:  {}", path, e.getMessage(), e);
518                         return Try.<ArtifactMetadata>failure(e);
519                     }
520                 }
521         ).collect(Collectors.groupingBy(Try::isSuccess));
522         if (result.containsKey(Boolean.FALSE) && result.get(Boolean.FALSE).size() > 0 && (!result.containsKey(Boolean.TRUE) || result.get(Boolean.TRUE).size() == 0)) {
523             LOGGER.error("Could not get artifact metadata. Directory: {}. Number of errors {}.", dir, result.get(Boolean.FALSE).size());
524             Try<ArtifactMetadata> failure = result.get(Boolean.FALSE).get(0);
525             LOGGER.error("Sample exception {}", failure.getError().getMessage(), failure.getError());
526             throw new RepositoryStorageRuntimeException(readMetadataRequest.getRepositoryId(), "Could not retrieve metadata of the files");
527         } else {
528             if (!result.containsKey(Boolean.TRUE) || result.get(Boolean.TRUE) == null) {
529                 return Collections.emptyList();
530             }
531             return result.get(Boolean.TRUE).stream().map(tr -> tr.get()).collect(Collectors.toList());
532         }
533 
534     }
535 
536     @Override
537     public ArtifactMetadata readArtifactMetadataFromPath(String repoId, String path)
538             throws RepositoryStorageRuntimeException {
539         ArtifactMetadata metadata = pathTranslator.getArtifactForPath(repoId, path);
540 
541         try {
542             populateArtifactMetadataFromFile(metadata, getRepositoryBasedir(repoId).resolve(path));
543         } catch (IOException e) {
544             throw new RepositoryStorageRuntimeException(repoId, "Error during metadata retrieval of " + path + " :" + e.getMessage(), e);
545         }
546 
547         return metadata;
548     }
549 
550     private ArtifactMetadata getArtifactFromFile(String repoId, String namespace, String projectId,
551                                                  String projectVersion, StorageAsset file) throws IOException {
552         ArtifactMetadata metadata =
553                 pathTranslator.getArtifactFromId(repoId, namespace, projectId, projectVersion, file.getName());
554 
555         populateArtifactMetadataFromFile(metadata, file);
556 
557         return metadata;
558     }
559 
560     @Override
561     public void applyServerSideRelocation(ManagedRepository managedRepository, ArtifactReference artifact)
562             throws ProxyDownloadException {
563         if ("pom".equals(artifact.getType())) {
564             return;
565         }
566 
567         // Build the artifact POM reference
568         ArtifactReferenceerence.html#ArtifactReference">ArtifactReference pomReference = new ArtifactReference();
569         pomReference.setGroupId(artifact.getGroupId());
570         pomReference.setArtifactId(artifact.getArtifactId());
571         pomReference.setVersion(artifact.getVersion());
572         pomReference.setType("pom");
573 
574         RepositoryType repositoryType = managedRepository.getType();
575         if (!proxyRegistry.hasHandler(repositoryType)) {
576             throw new ProxyDownloadException("No proxy handler found for repository type " + repositoryType, new HashMap<>());
577         }
578 
579         RepositoryProxyHandler proxyHandler = proxyRegistry.getHandler(repositoryType).get(0);
580 
581         // Get the artifact POM from proxied repositories if needed
582         proxyHandler.fetchFromProxies(managedRepository, pomReference);
583 
584         // Open and read the POM from the managed repo
585         StorageAsset pom = managedRepository.getContent().toFile(pomReference);
586 
587         if (!pom.exists()) {
588             return;
589         }
590 
591         try {
592             // MavenXpp3Reader leaves the file open, so we need to close it ourselves.
593 
594             Model model;
595             try (Reader reader = Channels.newReader(pom.getReadChannel(), Charset.defaultCharset().name())) {
596                 model = MAVEN_XPP_3_READER.read(reader);
597             }
598 
599             DistributionManagement dist = model.getDistributionManagement();
600             if (dist != null) {
601                 Relocation relocation = dist.getRelocation();
602                 if (relocation != null) {
603                     // artifact is relocated : update the repositoryPath
604                     if (relocation.getGroupId() != null) {
605                         artifact.setGroupId(relocation.getGroupId());
606                     }
607                     if (relocation.getArtifactId() != null) {
608                         artifact.setArtifactId(relocation.getArtifactId());
609                     }
610                     if (relocation.getVersion() != null) {
611                         artifact.setVersion(relocation.getVersion());
612                     }
613                 }
614             }
615         } catch (IOException e) {
616             // Unable to read POM : ignore.
617         } catch (XmlPullParserException e) {
618             // Invalid POM : ignore
619         }
620     }
621 
622 
623     @Override
624     public String getFilePath(String requestPath, org.apache.archiva.repository.ManagedRepository managedRepository) {
625         // managedRepository can be null
626         // extract artifact reference from url
627         // groupId:artifactId:version:packaging:classifier
628         //org/apache/archiva/archiva-checksum/1.4-M4-SNAPSHOT/archiva-checksum-1.4-M4-SNAPSHOT.jar
629         String logicalResource = null;
630         String requestPathInfo = StringUtils.defaultString(requestPath);
631 
632         //remove prefix ie /repository/blah becomes /blah
633         requestPathInfo = removePrefix(requestPathInfo);
634 
635         // Remove prefixing slash as the repository id doesn't contain it;
636         if (requestPathInfo.startsWith("/")) {
637             requestPathInfo = requestPathInfo.substring(1);
638         }
639 
640         int slash = requestPathInfo.indexOf('/');
641         if (slash > 0) {
642             logicalResource = requestPathInfo.substring(slash);
643 
644             if (logicalResource.endsWith("/..")) {
645                 logicalResource += "/";
646             }
647 
648             if (logicalResource != null && logicalResource.startsWith("//")) {
649                 logicalResource = logicalResource.substring(1);
650             }
651 
652             if (logicalResource == null) {
653                 logicalResource = "/";
654             }
655         } else {
656             logicalResource = "/";
657         }
658         return logicalResource;
659 
660     }
661 
662     @Override
663     public String getFilePathWithVersion(final String requestPath, ManagedRepositoryContent managedRepositoryContent)
664             throws RelocationException, XMLException, IOException {
665 
666         if (StringUtils.endsWith(requestPath, METADATA_FILENAME)) {
667             return getFilePath(requestPath, managedRepositoryContent.getRepository());
668         }
669 
670         String filePath = getFilePath(requestPath, managedRepositoryContent.getRepository());
671 
672         ArtifactReference artifactReference = null;
673         try {
674             artifactReference = pathParser.toArtifactReference(filePath);
675         } catch (LayoutException e) {
676             return filePath;
677         }
678 
679         if (StringUtils.endsWith(artifactReference.getVersion(), VersionUtil.SNAPSHOT)) {
680             // read maven metadata to get last timestamp
681             StorageAsset metadataDir = managedRepositoryContent.getRepository().getAsset(filePath).getParent();
682             if (!metadataDir.exists()) {
683                 return filePath;
684             }
685             StorageAsset metadataFile = metadataDir.resolve(METADATA_FILENAME);
686             if (!metadataFile.exists()) {
687                 return filePath;
688             }
689             ArchivaRepositoryMetadata archivaRepositoryMetadata = MavenMetadataReader.read(metadataFile);
690             int buildNumber = archivaRepositoryMetadata.getSnapshotVersion().getBuildNumber();
691             String timestamp = archivaRepositoryMetadata.getSnapshotVersion().getTimestamp();
692 
693             // MRM-1846
694             if (buildNumber < 1 && timestamp == null) {
695                 return filePath;
696             }
697 
698             // org/apache/archiva/archiva-checksum/1.4-M4-SNAPSHOT/archiva-checksum-1.4-M4-SNAPSHOT.jar
699             // ->  archiva-checksum-1.4-M4-20130425.081822-1.jar
700 
701             filePath = StringUtils.replace(filePath, //
702                     artifactReference.getArtifactId() //
703                             + "-" + artifactReference.getVersion(), //
704                     artifactReference.getArtifactId() //
705                             + "-" + StringUtils.remove(artifactReference.getVersion(),
706                             "-" + VersionUtil.SNAPSHOT) //
707                             + "-" + timestamp //
708                             + "-" + buildNumber);
709 
710             throw new RelocationException("/repository/" + managedRepositoryContent.getRepository().getId() +
711                     (StringUtils.startsWith(filePath, "/") ? "" : "/") + filePath,
712                     RelocationException.RelocationType.TEMPORARY);
713 
714         }
715 
716         return filePath;
717     }
718 
719     //-----------------------------
720     // internal
721     //-----------------------------
722 
723     /**
724      * FIXME remove
725      *
726      * @param href
727      * @return
728      */
729     private static String removePrefix(final String href) {
730         String[] parts = StringUtils.split(href, '/');
731         parts = (String[]) ArrayUtils.subarray(parts, 1, parts.length);
732         if (parts == null || parts.length == 0) {
733             return "/";
734         }
735 
736         String joinedString = StringUtils.join(parts, '/');
737         if (href.endsWith("/")) {
738             joinedString = joinedString + "/";
739         }
740 
741         return joinedString;
742     }
743 
744     private static void populateArtifactMetadataFromFile(ArtifactMetadata metadata, StorageAsset file) throws IOException {
745         metadata.setWhenGathered(ZonedDateTime.now(ZoneId.of("GMT")));
746         metadata.setFileLastModified(file.getModificationTime().toEpochMilli());
747         ChecksummedFileedFile.html#ChecksummedFile">ChecksummedFile checksummedFile = new ChecksummedFile(file.getFilePath());
748         try {
749             metadata.setMd5(checksummedFile.calculateChecksum(ChecksumAlgorithm.MD5));
750         } catch (IOException e) {
751             LOGGER.error("Unable to checksum file {}: {},MD5", file, e.getMessage());
752         }
753         try {
754             metadata.setSha1(checksummedFile.calculateChecksum(ChecksumAlgorithm.SHA1));
755         } catch (IOException e) {
756             LOGGER.error("Unable to checksum file {}: {},SHA1", file, e.getMessage());
757         }
758         metadata.setSize(file.getSize());
759     }
760 
761     private boolean isProject(StorageAsset dir, Filter<String> filter) {
762         // scan directories for a valid project version subdirectory, meaning this must be a project directory
763         final Predicate<StorageAsset> dFilter = new DirectoryFilter(filter);
764         boolean projFound = dir.list().stream().filter(dFilter)
765                 .anyMatch(path -> isProjectVersion(path));
766         if (projFound) {
767             return true;
768         }
769 
770         // if a metadata file is present, check if this is the "artifactId" directory, marking it as a project
771         ArchivaRepositoryMetadata metadata = readMetadata(dir);
772         if (metadata != null && dir.getName().toString().equals(metadata.getArtifactId())) {
773             return true;
774         }
775 
776         return false;
777     }
778 
779     private boolean isProjectVersion(StorageAsset dir) {
780         final String artifactId = dir.getParent().getName();
781         final String projectVersion = dir.getName();
782 
783         // check if there is a POM artifact file to ensure it is a version directory
784 
785         Predicate<StorageAsset> filter;
786         if (VersionUtil.isSnapshot(projectVersion)) {
787             filter = new PomFilenameFilter(artifactId, projectVersion);
788         } else {
789             final String pomFile = artifactId + "-" + projectVersion + ".pom";
790             filter = new PomFileFilter(pomFile);
791         }
792         if (dir.list().stream().filter(f -> !f.isContainer()).anyMatch(filter)) {
793             return true;
794         }
795         // if a metadata file is present, check if this is the "version" directory, marking it as a project version
796         ArchivaRepositoryMetadata metadata = readMetadata(dir);
797         if (metadata != null && projectVersion.equals(metadata.getVersion())) {
798             return true;
799         }
800 
801         return false;
802     }
803 
804     private ArchivaRepositoryMetadata readMetadata(StorageAsset directory) {
805         ArchivaRepositoryMetadata metadata = null;
806         StorageAsset metadataFile = directory.resolve(METADATA_FILENAME);
807         if (metadataFile.exists()) {
808             try {
809                 metadata = MavenMetadataReader.read(metadataFile);
810             } catch (XMLException | IOException e) {
811                 // ignore missing or invalid metadata
812             }
813         }
814         return metadata;
815     }
816 
817     private static class DirectoryFilter
818             implements Predicate<StorageAsset> {
819         private final Filter<String> filter;
820 
821         public DirectoryFilter(Filter<String> filter) {
822             this.filter = filter;
823         }
824 
825         @Override
826         public boolean test(StorageAsset dir) {
827             final String name = dir.getName();
828             if (!filter.accept(name)) {
829                 return false;
830             } else if (name.startsWith(".")) {
831                 return false;
832             } else if (!dir.isContainer()) {
833                 return false;
834             }
835             return true;
836         }
837     }
838 
839     private static class ArtifactDirectoryFilter
840             implements Predicate<StorageAsset> {
841         private final Filter<String> filter;
842 
843         private ArtifactDirectoryFilter(Filter<String> filter) {
844             this.filter = filter;
845         }
846 
847         @Override
848         public boolean test(StorageAsset file) {
849             final Set<String> checksumExts = ChecksumAlgorithm.getAllExtensions();
850             final String path = file.getPath();
851             final String name = file.getName();
852             final String extension = StringUtils.substringAfterLast(name, ".").toLowerCase();
853             // TODO compare to logic in maven-repository-layer
854             if (file.isContainer()) {
855                 return false;
856             } else if (!filter.accept(name)) {
857                 return false;
858             } else if (name.startsWith(".") || path.contains("/.") ) {
859                 return false;
860             } else if (checksumExts.contains(extension)) {
861                 return false;
862             } else if (Arrays.binarySearch(IGNORED_FILES, name) >= 0) {
863                 return false;
864             }
865             // some files from remote repositories can have name like maven-metadata-archiva-vm-all-public.xml
866             else if (StringUtils.startsWith(name, METADATA_FILENAME_START) && StringUtils.endsWith(name, ".xml")) {
867                 return false;
868             }
869 
870             return true;
871 
872         }
873     }
874 
875 
876     private static final class PomFilenameFilter
877             implements Predicate<StorageAsset> {
878 
879         private final String artifactId, projectVersion;
880 
881         private PomFilenameFilter(String artifactId, String projectVersion) {
882             this.artifactId = artifactId;
883             this.projectVersion = projectVersion;
884         }
885 
886         @Override
887         public boolean test(StorageAsset dir) {
888             final String name = dir.getName();
889             if (name.startsWith(artifactId + "-") && name.endsWith(".pom")) {
890                 String v = name.substring(artifactId.length() + 1, name.length() - 4);
891                 v = VersionUtil.getBaseVersion(v);
892                 if (v.equals(projectVersion)) {
893                     return true;
894                 }
895             }
896             return false;
897         }
898 
899     }
900 
901     private static class PomFileFilter
902             implements Predicate<StorageAsset> {
903         private final String pomFile;
904 
905         private PomFileFilter(String pomFile) {
906             this.pomFile = pomFile;
907         }
908 
909         @Override
910         public boolean test(StorageAsset dir) {
911             return pomFile.equals(dir.getName());
912         }
913     }
914 
915 
916     public PathParser getPathParser() {
917         return pathParser;
918     }
919 
920     public void setPathParser(PathParser pathParser) {
921         this.pathParser = pathParser;
922     }
923 }