1 package org.apache.archiva.metadata.repository.storage.maven2;
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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
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.maven2.metadata.MavenMetadataReader;
27 import org.apache.archiva.metadata.model.ArtifactMetadata;
28 import org.apache.archiva.metadata.model.ProjectMetadata;
29 import org.apache.archiva.metadata.model.ProjectVersionMetadata;
30 import org.apache.archiva.metadata.model.facets.RepositoryProblemFacet;
31 import org.apache.archiva.metadata.repository.filter.Filter;
32 import org.apache.archiva.metadata.repository.storage.ReadMetadataRequest;
33 import org.apache.archiva.metadata.repository.storage.RelocationException;
34 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
35 import org.apache.archiva.metadata.repository.storage.RepositoryStorage;
36 import org.apache.archiva.metadata.repository.storage.RepositoryStorageMetadataInvalidException;
37 import org.apache.archiva.metadata.repository.storage.RepositoryStorageMetadataNotFoundException;
38 import org.apache.archiva.metadata.repository.storage.RepositoryStorageRuntimeException;
39 import org.apache.archiva.model.ArchivaRepositoryMetadata;
40 import org.apache.archiva.model.ArtifactReference;
41 import org.apache.archiva.model.SnapshotVersion;
42 import org.apache.archiva.policies.ProxyDownloadException;
43 import org.apache.archiva.proxy.ProxyRegistry;
44 import org.apache.archiva.proxy.maven.WagonFactory;
45 import org.apache.archiva.proxy.model.NetworkProxy;
46 import org.apache.archiva.proxy.model.ProxyConnector;
47 import org.apache.archiva.proxy.model.RepositoryProxyHandler;
48 import org.apache.archiva.repository.*;
49 import org.apache.archiva.repository.content.PathParser;
50 import org.apache.archiva.repository.maven2.MavenSystemManager;
51 import org.apache.archiva.xml.XMLException;
52 import org.apache.commons.lang.ArrayUtils;
53 import org.apache.commons.lang.StringUtils;
54 import org.apache.maven.model.CiManagement;
55 import org.apache.maven.model.Dependency;
56 import org.apache.maven.model.DistributionManagement;
57 import org.apache.maven.model.IssueManagement;
58 import org.apache.maven.model.License;
59 import org.apache.maven.model.MailingList;
60 import org.apache.maven.model.Model;
61 import org.apache.maven.model.Organization;
62 import org.apache.maven.model.Relocation;
63 import org.apache.maven.model.Scm;
64 import org.apache.maven.model.building.DefaultModelBuilderFactory;
65 import org.apache.maven.model.building.DefaultModelBuildingRequest;
66 import org.apache.maven.model.building.ModelBuilder;
67 import org.apache.maven.model.building.ModelBuildingException;
68 import org.apache.maven.model.building.ModelBuildingRequest;
69 import org.apache.maven.model.building.ModelProblem;
70 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
71 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74 import org.springframework.context.ApplicationContext;
75 import org.springframework.stereotype.Service;
77 import javax.annotation.PostConstruct;
78 import javax.inject.Inject;
79 import javax.inject.Named;
80 import java.io.FileNotFoundException;
81 import java.io.IOException;
82 import java.io.Reader;
83 import java.nio.charset.Charset;
84 import java.nio.file.Files;
85 import java.nio.file.NoSuchFileException;
86 import java.nio.file.Path;
87 import java.nio.file.Paths;
88 import java.util.ArrayList;
89 import java.util.Arrays;
90 import java.util.Collection;
91 import java.util.Collections;
92 import java.util.Date;
93 import java.util.HashMap;
94 import java.util.List;
96 import java.util.function.Predicate;
97 import java.util.stream.Collectors;
98 import java.util.stream.Stream;
100 // import java.io.FileNotFoundException;
104 * Maven 2 repository format storage implementation. This class currently takes parameters to indicate the repository to
105 * deal with rather than being instantiated per-repository.
106 * FIXME: instantiate one per repository and allocate permanently from a factory (which can be obtained within the session).
109 * The session is passed in as an argument to obtain any necessary resources, rather than the class being instantiated
110 * within the session in the context of a single managed repository's resolution needs.
113 @Service("repositoryStorage#maven2")
114 public class Maven2RepositoryStorage
115 implements RepositoryStorage {
117 private static final Logger LOGGER = LoggerFactory.getLogger(Maven2RepositoryStorage.class);
119 private ModelBuilder builder;
122 RepositoryRegistry repositoryRegistry;
125 @Named("repositoryPathTranslator#maven2")
126 private RepositoryPathTranslator pathTranslator;
129 private WagonFactory wagonFactory;
132 private ApplicationContext applicationContext;
135 @Named("pathParser#default")
136 private PathParser pathParser;
139 private ProxyRegistry proxyRegistry;
142 private MavenSystemManager mavenSystemManager;
144 private static final String METADATA_FILENAME_START = "maven-metadata";
146 private static final String METADATA_FILENAME = METADATA_FILENAME_START + ".xml";
148 // This array must be lexically sorted
149 private static final String[] IGNORED_FILES = {METADATA_FILENAME, "resolver-status.properties"};
151 private static final MavenXpp3Reader MAVEN_XPP_3_READER = new MavenXpp3Reader();
155 public void initialize() {
156 builder = new DefaultModelBuilderFactory().newInstance();
161 public ProjectMetadata readProjectMetadata(String repoId, String namespace, String projectId) {
162 // TODO: could natively implement the "shared model" concept from the browse action to avoid needing it there?
167 public ProjectVersionMetadata readProjectVersionMetadata(ReadMetadataRequest readMetadataRequest)
168 throws RepositoryStorageMetadataNotFoundException, RepositoryStorageMetadataInvalidException,
169 RepositoryStorageRuntimeException {
171 ManagedRepository managedRepository = repositoryRegistry.getManagedRepository(readMetadataRequest.getRepositoryId());
172 boolean isReleases = managedRepository.getActiveReleaseSchemes().contains(ReleaseScheme.RELEASE);
173 boolean isSnapshots = managedRepository.getActiveReleaseSchemes().contains(ReleaseScheme.SNAPSHOT);
174 String artifactVersion = readMetadataRequest.getProjectVersion();
175 // olamy: in case of browsing via the ui we can mix repos (parent of a SNAPSHOT can come from release repo)
176 if (!readMetadataRequest.isBrowsingRequest()) {
177 if (VersionUtil.isSnapshot(artifactVersion)) {
178 // skygo trying to improve speed by honoring managed configuration MRM-1658
179 if (isReleases && !isSnapshots) {
180 throw new RepositoryStorageRuntimeException("lookforsnaponreleaseonly",
181 "managed repo is configured for release only");
184 if (!isReleases && isSnapshots) {
185 throw new RepositoryStorageRuntimeException("lookforsreleaseonsneponly",
186 "managed repo is configured for snapshot only");
190 Path basedir = Paths.get(managedRepository.getLocation());
191 if (VersionUtil.isSnapshot(artifactVersion)) {
192 Path metadataFile = pathTranslator.toFile(basedir, readMetadataRequest.getNamespace(),
193 readMetadataRequest.getProjectId(), artifactVersion,
196 ArchivaRepositoryMetadata metadata = MavenMetadataReader.read(metadataFile);
198 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
199 SnapshotVersion snapshotVersion = metadata.getSnapshotVersion();
200 if (snapshotVersion != null) {
202 artifactVersion.substring(0, artifactVersion.length() - 8); // remove SNAPSHOT from end
204 artifactVersion + snapshotVersion.getTimestamp() + "-" + snapshotVersion.getBuildNumber();
206 } catch (XMLException e) {
207 // unable to parse metadata - LOGGER it, and continue with the version as the original SNAPSHOT version
208 LOGGER.warn("Invalid metadata: {} - {}", metadataFile, e.getMessage());
212 // TODO: won't work well with some other layouts, might need to convert artifact parts to ID by path translator
213 String id = readMetadataRequest.getProjectId() + "-" + artifactVersion + ".pom";
215 pathTranslator.toFile(basedir, readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(),
216 readMetadataRequest.getProjectVersion(), id);
218 if (!Files.exists(file)) {
219 // metadata could not be resolved
220 throw new RepositoryStorageMetadataNotFoundException(
221 "The artifact's POM file '" + file.toAbsolutePath() + "' was missing");
224 // TODO: this is a workaround until we can properly resolve using proxies as well - this doesn't cache
226 List<RemoteRepository> remoteRepositories = new ArrayList<>();
227 Map<String, NetworkProxy> networkProxies = new HashMap<>();
229 Map<String, List<ProxyConnector>> proxyConnectorsMap = proxyRegistry.getProxyConnectorAsMap();
230 List<ProxyConnector> proxyConnectors = proxyConnectorsMap.get(readMetadataRequest.getRepositoryId());
231 if (proxyConnectors != null) {
232 for (ProxyConnector proxyConnector : proxyConnectors) {
233 RemoteRepository remoteRepoConfig =
234 repositoryRegistry.getRemoteRepository(proxyConnector.getTargetRepository().getId());
236 if (remoteRepoConfig != null) {
237 remoteRepositories.add(remoteRepoConfig);
239 NetworkProxy networkProxyConfig =
240 proxyRegistry.getNetworkProxy(proxyConnector.getProxyId());
242 if (networkProxyConfig != null) {
243 // key/value: remote repo ID/proxy info
244 networkProxies.put(proxyConnector.getTargetRepository().getId(), networkProxyConfig);
250 // That's a browsing request so we can a mix of SNAPSHOT and release artifacts (especially with snapshots which
251 // can have released parent pom
252 if (readMetadataRequest.isBrowsingRequest()) {
253 remoteRepositories.addAll(repositoryRegistry.getRemoteRepositories());
256 ModelBuildingRequest req =
257 new DefaultModelBuildingRequest().setProcessPlugins(false).setPomFile(file.toFile()).setTwoPhaseBuilding(
258 false).setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);
260 //MRM-1607. olamy this will resolve jdk profiles on the current running archiva jvm
261 req.setSystemProperties(System.getProperties());
264 req.setModelResolver(
265 new RepositoryModelResolver(managedRepository, pathTranslator, wagonFactory, remoteRepositories,
266 networkProxies, managedRepository, mavenSystemManager));
270 model = builder.build(req).getEffectiveModel();
271 } catch (ModelBuildingException e) {
272 String msg = "The artifact's POM file '" + file + "' was invalid: " + e.getMessage();
274 List<ModelProblem> modelProblems = e.getProblems();
275 for (ModelProblem problem : modelProblems) {
276 // MRM-1411, related to MRM-1335
277 // this means that the problem was that the parent wasn't resolved!
278 // olamy really hackhish but fail with java profile so use error message
279 // || ( StringUtils.startsWith( problem.getMessage(), "Failed to determine Java version for profile" ) )
280 // but setTwoPhaseBuilding(true) fix that
281 if (((problem.getException() instanceof FileNotFoundException
282 || problem.getException() instanceof NoSuchFileException
283 ) && e.getModelId() != null &&
284 !e.getModelId().equals(problem.getModelId()))) {
285 LOGGER.warn("The artifact's parent POM file '{}' cannot be resolved. "
286 + "Using defaults for project version metadata..", file);
288 ProjectVersionMetadata metadata = new ProjectVersionMetadata();
289 metadata.setId(readMetadataRequest.getProjectVersion());
291 MavenProjectFacet facet = new MavenProjectFacet();
292 facet.setGroupId(readMetadataRequest.getNamespace());
293 facet.setArtifactId(readMetadataRequest.getProjectId());
294 facet.setPackaging("jar");
295 metadata.addFacet(facet);
298 "Error in resolving artifact's parent POM file. " + (problem.getException() == null
299 ? problem.getMessage()
300 : problem.getException().getMessage());
301 RepositoryProblemFacet repoProblemFacet = new RepositoryProblemFacet();
302 repoProblemFacet.setRepositoryId(readMetadataRequest.getRepositoryId());
303 repoProblemFacet.setId(readMetadataRequest.getRepositoryId());
304 repoProblemFacet.setMessage(errMsg);
305 repoProblemFacet.setProblem(errMsg);
306 repoProblemFacet.setProject(readMetadataRequest.getProjectId());
307 repoProblemFacet.setVersion(readMetadataRequest.getProjectVersion());
308 repoProblemFacet.setNamespace(readMetadataRequest.getNamespace());
310 metadata.addFacet(repoProblemFacet);
316 throw new RepositoryStorageMetadataInvalidException("invalid-pom", msg, e);
319 // Check if the POM is in the correct location
320 boolean correctGroupId = readMetadataRequest.getNamespace().equals(model.getGroupId());
321 boolean correctArtifactId = readMetadataRequest.getProjectId().equals(model.getArtifactId());
322 boolean correctVersion = readMetadataRequest.getProjectVersion().equals(model.getVersion());
323 if (!correctGroupId || !correctArtifactId || !correctVersion) {
324 StringBuilder message = new StringBuilder("Incorrect POM coordinates in '" + file + "':");
325 if (!correctGroupId) {
326 message.append("\nIncorrect group ID: ").append(model.getGroupId());
328 if (!correctArtifactId) {
329 message.append("\nIncorrect artifact ID: ").append(model.getArtifactId());
331 if (!correctVersion) {
332 message.append("\nIncorrect version: ").append(model.getVersion());
335 throw new RepositoryStorageMetadataInvalidException("mislocated-pom", message.toString());
338 ProjectVersionMetadata metadata = new ProjectVersionMetadata();
339 metadata.setCiManagement(convertCiManagement(model.getCiManagement()));
340 metadata.setDescription(model.getDescription());
341 metadata.setId(readMetadataRequest.getProjectVersion());
342 metadata.setIssueManagement(convertIssueManagement(model.getIssueManagement()));
343 metadata.setLicenses(convertLicenses(model.getLicenses()));
344 metadata.setMailingLists(convertMailingLists(model.getMailingLists()));
345 metadata.setDependencies(convertDependencies(model.getDependencies()));
346 metadata.setName(model.getName());
347 metadata.setOrganization(convertOrganization(model.getOrganization()));
348 metadata.setScm(convertScm(model.getScm()));
349 metadata.setUrl(model.getUrl());
350 metadata.setProperties(model.getProperties());
352 MavenProjectFacet facet = new MavenProjectFacet();
353 facet.setGroupId(model.getGroupId() != null ? model.getGroupId() : model.getParent().getGroupId());
354 facet.setArtifactId(model.getArtifactId());
355 facet.setPackaging(model.getPackaging());
356 if (model.getParent() != null) {
357 MavenProjectParent parent = new MavenProjectParent();
358 parent.setGroupId(model.getParent().getGroupId());
359 parent.setArtifactId(model.getParent().getArtifactId());
360 parent.setVersion(model.getParent().getVersion());
361 facet.setParent(parent);
363 metadata.addFacet(facet);
370 public void setWagonFactory(WagonFactory wagonFactory) {
371 this.wagonFactory = wagonFactory;
374 private List<org.apache.archiva.metadata.model.Dependency> convertDependencies(List<Dependency> dependencies) {
375 List<org.apache.archiva.metadata.model.Dependency> l = new ArrayList<>();
376 for (Dependency dependency : dependencies) {
377 org.apache.archiva.metadata.model.Dependency newDependency =
378 new org.apache.archiva.metadata.model.Dependency();
379 newDependency.setArtifactId(dependency.getArtifactId());
380 newDependency.setClassifier(dependency.getClassifier());
381 newDependency.setGroupId(dependency.getGroupId());
382 newDependency.setOptional(dependency.isOptional());
383 newDependency.setScope(dependency.getScope());
384 newDependency.setSystemPath(dependency.getSystemPath());
385 newDependency.setType(dependency.getType());
386 newDependency.setVersion(dependency.getVersion());
387 l.add(newDependency);
392 private org.apache.archiva.metadata.model.Scm convertScm(Scm scm) {
393 org.apache.archiva.metadata.model.Scm newScm = null;
395 newScm = new org.apache.archiva.metadata.model.Scm();
396 newScm.setConnection(scm.getConnection());
397 newScm.setDeveloperConnection(scm.getDeveloperConnection());
398 newScm.setUrl(scm.getUrl());
403 private org.apache.archiva.metadata.model.Organization convertOrganization(Organization organization) {
404 org.apache.archiva.metadata.model.Organization org = null;
405 if (organization != null) {
406 org = new org.apache.archiva.metadata.model.Organization();
407 org.setName(organization.getName());
408 org.setUrl(organization.getUrl());
413 private List<org.apache.archiva.metadata.model.License> convertLicenses(List<License> licenses) {
414 List<org.apache.archiva.metadata.model.License> l = new ArrayList<>();
415 for (License license : licenses) {
416 org.apache.archiva.metadata.model.License newLicense = new org.apache.archiva.metadata.model.License();
417 newLicense.setName(license.getName());
418 newLicense.setUrl(license.getUrl());
424 private List<org.apache.archiva.metadata.model.MailingList> convertMailingLists(List<MailingList> mailingLists) {
425 List<org.apache.archiva.metadata.model.MailingList> l = new ArrayList<>();
426 for (MailingList mailingList : mailingLists) {
427 org.apache.archiva.metadata.model.MailingList newMailingList =
428 new org.apache.archiva.metadata.model.MailingList();
429 newMailingList.setName(mailingList.getName());
430 newMailingList.setMainArchiveUrl(mailingList.getArchive());
431 newMailingList.setPostAddress(mailingList.getPost());
432 newMailingList.setSubscribeAddress(mailingList.getSubscribe());
433 newMailingList.setUnsubscribeAddress(mailingList.getUnsubscribe());
434 newMailingList.setOtherArchives(mailingList.getOtherArchives());
435 l.add(newMailingList);
440 private org.apache.archiva.metadata.model.IssueManagement convertIssueManagement(IssueManagement issueManagement) {
441 org.apache.archiva.metadata.model.IssueManagement im = null;
442 if (issueManagement != null) {
443 im = new org.apache.archiva.metadata.model.IssueManagement();
444 im.setSystem(issueManagement.getSystem());
445 im.setUrl(issueManagement.getUrl());
450 private org.apache.archiva.metadata.model.CiManagement convertCiManagement(CiManagement ciManagement) {
451 org.apache.archiva.metadata.model.CiManagement ci = null;
452 if (ciManagement != null) {
453 ci = new org.apache.archiva.metadata.model.CiManagement();
454 ci.setSystem(ciManagement.getSystem());
455 ci.setUrl(ciManagement.getUrl());
461 public Collection<String> listRootNamespaces(String repoId, Filter<String> filter)
462 throws RepositoryStorageRuntimeException {
463 Path dir = getRepositoryBasedir(repoId);
465 return getSortedFiles(dir, filter);
468 private static Collection<String> getSortedFiles(Path dir, Filter<String> filter) {
470 try (Stream<Path> stream = Files.list(dir)) {
471 final Predicate<Path> dFilter = new DirectoryFilter(filter);
472 return stream.filter(Files::isDirectory)
474 .map(path -> path.getFileName().toString())
475 .sorted().collect(Collectors.toList());
477 } catch (IOException e) {
478 LOGGER.error("Could not read directory list {}: {}", dir, e.getMessage(), e);
479 return Collections.emptyList();
483 private Path getRepositoryBasedir(String repoId)
484 throws RepositoryStorageRuntimeException {
485 ManagedRepository repositoryConfiguration = repositoryRegistry.getManagedRepository(repoId);
487 return Paths.get(repositoryConfiguration.getLocation());
491 public Collection<String> listNamespaces(String repoId, String namespace, Filter<String> filter)
492 throws RepositoryStorageRuntimeException {
493 Path dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace);
494 if (!(Files.exists(dir) && Files.isDirectory(dir))) {
495 return Collections.emptyList();
497 // scan all the directories which are potential namespaces. Any directories known to be projects are excluded
498 Predicate<Path> dFilter = new DirectoryFilter(filter);
499 try (Stream<Path> stream = Files.list(dir)) {
500 return stream.filter(dFilter).filter(path -> !isProject(path, filter)).map(path -> path.getFileName().toString())
501 .sorted().collect(Collectors.toList());
502 } catch (IOException e) {
503 LOGGER.error("Could not read directory {}: {}", dir, e.getMessage(), e);
504 return Collections.emptyList();
509 public Collection<String> listProjects(String repoId, String namespace, Filter<String> filter)
510 throws RepositoryStorageRuntimeException {
511 Path dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace);
512 if (!(Files.exists(dir) && Files.isDirectory(dir))) {
513 return Collections.emptyList();
515 // scan all directories in the namespace, and only include those that are known to be projects
516 final Predicate<Path> dFilter = new DirectoryFilter(filter);
517 try (Stream<Path> stream = Files.list(dir)) {
518 return stream.filter(dFilter).filter(path -> isProject(path, filter)).map(path -> path.getFileName().toString())
519 .sorted().collect(Collectors.toList());
520 } catch (IOException e) {
521 LOGGER.error("Could not read directory {}: {}", dir, e.getMessage(), e);
522 return Collections.emptyList();
528 public Collection<String> listProjectVersions(String repoId, String namespace, String projectId,
529 Filter<String> filter)
530 throws RepositoryStorageRuntimeException {
531 Path dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace, projectId);
532 if (!(Files.exists(dir) && Files.isDirectory(dir))) {
533 return Collections.emptyList();
536 // all directories in a project directory can be considered a version
537 return getSortedFiles(dir, filter);
541 public Collection<ArtifactMetadata> readArtifactsMetadata(ReadMetadataRequest readMetadataRequest)
542 throws RepositoryStorageRuntimeException {
543 Path dir = pathTranslator.toFile(getRepositoryBasedir(readMetadataRequest.getRepositoryId()),
544 readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(),
545 readMetadataRequest.getProjectVersion());
546 if (!(Files.exists(dir) && Files.isDirectory(dir))) {
547 return Collections.emptyList();
550 // all files that are not metadata and not a checksum / signature are considered artifacts
551 final Predicate<Path> dFilter = new ArtifactDirectoryFilter(readMetadataRequest.getFilter());
552 try (Stream<Path> stream = Files.list(dir)) {
553 // Returns a map TRUE -> (success values), FALSE -> (Exceptions)
554 Map<Boolean, List<Try<ArtifactMetadata>>> result = stream.filter(dFilter).map(path -> {
556 return Try.success(getArtifactFromFile(readMetadataRequest.getRepositoryId(), readMetadataRequest.getNamespace(),
557 readMetadataRequest.getProjectId(), readMetadataRequest.getProjectVersion(),
559 } catch (Exception e) {
560 LOGGER.debug("Could not create metadata for {}: {}", path, e.getMessage(), e);
561 return Try.<ArtifactMetadata>failure(e);
564 ).collect(Collectors.groupingBy(Try::isSuccess));
565 if (result.containsKey(Boolean.FALSE) && result.get(Boolean.FALSE).size() > 0 && (!result.containsKey(Boolean.TRUE) || result.get(Boolean.TRUE).size() == 0)) {
566 LOGGER.error("Could not get artifact metadata. Directory: {}. Number of errors {}.", dir, result.get(Boolean.FALSE).size());
567 Try<ArtifactMetadata> failure = result.get(Boolean.FALSE).get(0);
568 LOGGER.error("Sample exception {}", failure.getError().getMessage(), failure.getError());
569 throw new RepositoryStorageRuntimeException(readMetadataRequest.getRepositoryId(), "Could not retrieve metadata of the files");
571 if (!result.containsKey(Boolean.TRUE) || result.get(Boolean.TRUE) == null) {
572 return Collections.emptyList();
574 return result.get(Boolean.TRUE).stream().map(tr -> tr.get()).collect(Collectors.toList());
576 } catch (IOException e) {
577 LOGGER.error("Could not read directory {}: {}", dir, e.getMessage(), e);
579 return Collections.emptyList();
584 public ArtifactMetadata readArtifactMetadataFromPath(String repoId, String path)
585 throws RepositoryStorageRuntimeException {
586 ArtifactMetadata metadata = pathTranslator.getArtifactForPath(repoId, path);
589 populateArtifactMetadataFromFile(metadata, getRepositoryBasedir(repoId).resolve(path));
590 } catch (IOException e) {
591 throw new RepositoryStorageRuntimeException(repoId, "Error during metadata retrieval of " + path + " :" + e.getMessage(), e);
597 private ArtifactMetadata getArtifactFromFile(String repoId, String namespace, String projectId,
598 String projectVersion, Path file) throws IOException {
599 ArtifactMetadata metadata =
600 pathTranslator.getArtifactFromId(repoId, namespace, projectId, projectVersion, file.getFileName().toString());
602 populateArtifactMetadataFromFile(metadata, file);
608 public void applyServerSideRelocation(ManagedRepositoryContent managedRepository, ArtifactReference artifact)
609 throws ProxyDownloadException {
610 if ("pom".equals(artifact.getType())) {
614 // Build the artifact POM reference
615 ArtifactReference pomReference = new ArtifactReference();
616 pomReference.setGroupId(artifact.getGroupId());
617 pomReference.setArtifactId(artifact.getArtifactId());
618 pomReference.setVersion(artifact.getVersion());
619 pomReference.setType("pom");
621 RepositoryType repositoryType = managedRepository.getRepository().getType();
622 if (!proxyRegistry.hasHandler(repositoryType)) {
623 throw new ProxyDownloadException("No proxy handler found for repository type "+repositoryType, new HashMap<>());
626 RepositoryProxyHandler proxyHandler = proxyRegistry.getHandler(repositoryType).get(0);
628 // Get the artifact POM from proxied repositories if needed
629 proxyHandler.fetchFromProxies(managedRepository, pomReference);
631 // Open and read the POM from the managed repo
632 Path pom = managedRepository.toFile(pomReference);
634 if (!Files.exists(pom)) {
639 // MavenXpp3Reader leaves the file open, so we need to close it ourselves.
642 try (Reader reader = Files.newBufferedReader(pom, Charset.defaultCharset())) {
643 model = MAVEN_XPP_3_READER.read(reader);
646 DistributionManagement dist = model.getDistributionManagement();
648 Relocation relocation = dist.getRelocation();
649 if (relocation != null) {
650 // artifact is relocated : update the repositoryPath
651 if (relocation.getGroupId() != null) {
652 artifact.setGroupId(relocation.getGroupId());
654 if (relocation.getArtifactId() != null) {
655 artifact.setArtifactId(relocation.getArtifactId());
657 if (relocation.getVersion() != null) {
658 artifact.setVersion(relocation.getVersion());
662 } catch (IOException e) {
663 // Unable to read POM : ignore.
664 } catch (XmlPullParserException e) {
665 // Invalid POM : ignore
671 public String getFilePath(String requestPath, org.apache.archiva.repository.ManagedRepository managedRepository) {
672 // managedRepository can be null
673 // extract artifact reference from url
674 // groupId:artifactId:version:packaging:classifier
675 //org/apache/archiva/archiva-checksum/1.4-M4-SNAPSHOT/archiva-checksum-1.4-M4-SNAPSHOT.jar
676 String logicalResource = null;
677 String requestPathInfo = StringUtils.defaultString(requestPath);
679 //remove prefix ie /repository/blah becomes /blah
680 requestPathInfo = removePrefix(requestPathInfo);
682 // Remove prefixing slash as the repository id doesn't contain it;
683 if (requestPathInfo.startsWith("/")) {
684 requestPathInfo = requestPathInfo.substring(1);
687 int slash = requestPathInfo.indexOf('/');
689 logicalResource = requestPathInfo.substring(slash);
691 if (logicalResource.endsWith("/..")) {
692 logicalResource += "/";
695 if (logicalResource != null && logicalResource.startsWith("//")) {
696 logicalResource = logicalResource.substring(1);
699 if (logicalResource == null) {
700 logicalResource = "/";
703 logicalResource = "/";
705 return logicalResource;
710 public String getFilePathWithVersion(final String requestPath, ManagedRepositoryContent managedRepositoryContent)
711 throws XMLException, RelocationException {
713 if (StringUtils.endsWith(requestPath, METADATA_FILENAME)) {
714 return getFilePath(requestPath, managedRepositoryContent.getRepository());
717 String filePath = getFilePath(requestPath, managedRepositoryContent.getRepository());
719 ArtifactReference artifactReference = null;
721 artifactReference = pathParser.toArtifactReference(filePath);
722 } catch (LayoutException e) {
726 if (StringUtils.endsWith(artifactReference.getVersion(), VersionUtil.SNAPSHOT)) {
727 // read maven metadata to get last timestamp
728 Path metadataDir = Paths.get(managedRepositoryContent.getRepoRoot(), filePath).getParent();
729 if (!Files.exists(metadataDir)) {
732 Path metadataFile = metadataDir.resolve(METADATA_FILENAME);
733 if (!Files.exists(metadataFile)) {
736 ArchivaRepositoryMetadata archivaRepositoryMetadata = MavenMetadataReader.read(metadataFile);
737 int buildNumber = archivaRepositoryMetadata.getSnapshotVersion().getBuildNumber();
738 String timestamp = archivaRepositoryMetadata.getSnapshotVersion().getTimestamp();
741 if (buildNumber < 1 && timestamp == null) {
745 // org/apache/archiva/archiva-checksum/1.4-M4-SNAPSHOT/archiva-checksum-1.4-M4-SNAPSHOT.jar
746 // -> archiva-checksum-1.4-M4-20130425.081822-1.jar
748 filePath = StringUtils.replace(filePath, //
749 artifactReference.getArtifactId() //
750 + "-" + artifactReference.getVersion(), //
751 artifactReference.getArtifactId() //
752 + "-" + StringUtils.remove(artifactReference.getVersion(),
753 "-" + VersionUtil.SNAPSHOT) //
755 + "-" + buildNumber);
757 throw new RelocationException("/repository/" + managedRepositoryContent.getRepository().getId() +
758 (StringUtils.startsWith(filePath, "/") ? "" : "/") + filePath,
759 RelocationException.RelocationType.TEMPORARY);
766 //-----------------------------
768 //-----------------------------
776 private static String removePrefix(final String href) {
777 String[] parts = StringUtils.split(href, '/');
778 parts = (String[]) ArrayUtils.subarray(parts, 1, parts.length);
779 if (parts == null || parts.length == 0) {
783 String joinedString = StringUtils.join(parts, '/');
784 if (href.endsWith("/")) {
785 joinedString = joinedString + "/";
791 private static void populateArtifactMetadataFromFile(ArtifactMetadata metadata, Path file) throws IOException {
792 metadata.setWhenGathered(new Date());
793 metadata.setFileLastModified(Files.getLastModifiedTime(file).toMillis());
794 ChecksummedFile checksummedFile = new ChecksummedFile(file);
796 metadata.setMd5(checksummedFile.calculateChecksum(ChecksumAlgorithm.MD5));
797 } catch (IOException e) {
798 LOGGER.error("Unable to checksum file {}: {},MD5", file, e.getMessage());
801 metadata.setSha1(checksummedFile.calculateChecksum(ChecksumAlgorithm.SHA1));
802 } catch (IOException e) {
803 LOGGER.error("Unable to checksum file {}: {},SHA1", file, e.getMessage());
805 metadata.setSize(Files.size(file));
808 private boolean isProject(Path dir, Filter<String> filter) {
809 // scan directories for a valid project version subdirectory, meaning this must be a project directory
810 final Predicate<Path> dFilter = new DirectoryFilter(filter);
811 try (Stream<Path> stream = Files.list(dir)) {
812 boolean projFound = stream.filter(dFilter)
813 .anyMatch(path -> isProjectVersion(path));
817 } catch (IOException e) {
818 LOGGER.error("Could not read directory list {}: {}", dir, e.getMessage(), e);
821 // if a metadata file is present, check if this is the "artifactId" directory, marking it as a project
822 ArchivaRepositoryMetadata metadata = readMetadata(dir);
823 if (metadata != null && dir.getFileName().toString().equals(metadata.getArtifactId())) {
830 private boolean isProjectVersion(Path dir) {
831 final String artifactId = dir.getParent().getFileName().toString();
832 final String projectVersion = dir.getFileName().toString();
834 // check if there is a POM artifact file to ensure it is a version directory
836 Predicate<Path> filter;
837 if (VersionUtil.isSnapshot(projectVersion)) {
838 filter = new PomFilenameFilter(artifactId, projectVersion);
840 final String pomFile = artifactId + "-" + projectVersion + ".pom";
841 filter = new PomFileFilter(pomFile);
843 try (Stream<Path> stream = Files.list(dir)) {
844 if (stream.filter(Files::isRegularFile).anyMatch(filter)) {
847 } catch (IOException e) {
848 LOGGER.error("Could not list directory {}: {}", dir, e.getMessage(), e);
851 // if a metadata file is present, check if this is the "version" directory, marking it as a project version
852 ArchivaRepositoryMetadata metadata = readMetadata(dir);
853 if (metadata != null && projectVersion.equals(metadata.getVersion())) {
860 private ArchivaRepositoryMetadata readMetadata(Path directory) {
861 ArchivaRepositoryMetadata metadata = null;
862 Path metadataFile = directory.resolve(METADATA_FILENAME);
863 if (Files.exists(metadataFile)) {
865 metadata = MavenMetadataReader.read(metadataFile);
866 } catch (XMLException e) {
867 // ignore missing or invalid metadata
873 private static class DirectoryFilter
874 implements Predicate<Path> {
875 private final Filter<String> filter;
877 public DirectoryFilter(Filter<String> filter) {
878 this.filter = filter;
882 public boolean test(Path dir) {
883 final String name = dir.getFileName().toString();
884 if (!filter.accept(name)) {
886 } else if (name.startsWith(".")) {
888 } else if (!Files.isDirectory(dir)) {
895 private static class ArtifactDirectoryFilter
896 implements Predicate<Path> {
897 private final Filter<String> filter;
899 private ArtifactDirectoryFilter(Filter<String> filter) {
900 this.filter = filter;
904 public boolean test(Path dir) {
905 final String name = dir.getFileName().toString();
906 // TODO compare to logic in maven-repository-layer
907 if (!filter.accept(name)) {
909 } else if (name.startsWith(".")) {
911 } else if (name.endsWith(".md5") || name.endsWith(".sha1") || name.endsWith(".asc")) {
913 } else if (Arrays.binarySearch(IGNORED_FILES, name) >= 0) {
915 } else if (Files.isDirectory(dir)) {
918 // some files from remote repositories can have name like maven-metadata-archiva-vm-all-public.xml
919 else if (StringUtils.startsWith(name, METADATA_FILENAME_START) && StringUtils.endsWith(name, ".xml")) {
929 private static final class PomFilenameFilter
930 implements Predicate<Path> {
932 private final String artifactId, projectVersion;
934 private PomFilenameFilter(String artifactId, String projectVersion) {
935 this.artifactId = artifactId;
936 this.projectVersion = projectVersion;
940 public boolean test(Path dir) {
941 final String name = dir.getFileName().toString();
942 if (name.startsWith(artifactId + "-") && name.endsWith(".pom")) {
943 String v = name.substring(artifactId.length() + 1, name.length() - 4);
944 v = VersionUtil.getBaseVersion(v);
945 if (v.equals(projectVersion)) {
954 private static class PomFileFilter
955 implements Predicate<Path> {
956 private final String pomFile;
958 private PomFileFilter(String pomFile) {
959 this.pomFile = pomFile;
963 public boolean test(Path dir) {
964 return pomFile.equals(dir.getFileName().toString());
969 public PathParser getPathParser() {
973 public void setPathParser(PathParser pathParser) {
974 this.pathParser = pathParser;