1 package org.apache.archiva.repository.maven.metadata.storage;
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
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
21 import org.apache.archiva.checksum.ChecksumAlgorithm;
22 import org.apache.archiva.checksum.ChecksummedFile;
23 import org.apache.archiva.common.Try;
24 import org.apache.archiva.common.utils.VersionUtil;
25 import org.apache.archiva.filter.Filter;
26 import org.apache.archiva.metadata.maven.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.storage.*;
32 import org.apache.archiva.model.ArchivaRepositoryMetadata;
33 import org.apache.archiva.model.ArtifactReference;
34 import org.apache.archiva.model.SnapshotVersion;
35 import org.apache.archiva.policies.ProxyDownloadException;
36 import org.apache.archiva.proxy.ProxyRegistry;
37 import org.apache.archiva.proxy.maven.WagonFactory;
38 import org.apache.archiva.proxy.model.NetworkProxy;
39 import org.apache.archiva.proxy.model.ProxyConnector;
40 import org.apache.archiva.proxy.model.RepositoryProxyHandler;
41 import org.apache.archiva.repository.*;
42 import org.apache.archiva.repository.content.PathParser;
43 import org.apache.archiva.repository.maven.MavenSystemManager;
44 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
45 import org.apache.archiva.repository.storage.StorageAsset;
46 import org.apache.commons.lang3.ArrayUtils;
47 import org.apache.commons.lang3.StringUtils;
48 import org.apache.maven.model.*;
49 import org.apache.maven.model.building.*;
50 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
51 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54 import org.springframework.context.ApplicationContext;
55 import org.springframework.stereotype.Service;
57 import javax.annotation.PostConstruct;
58 import javax.inject.Inject;
59 import javax.inject.Named;
60 import java.io.FileNotFoundException;
61 import java.io.IOException;
62 import java.io.Reader;
63 import java.nio.channels.Channels;
64 import java.nio.charset.Charset;
65 import java.nio.file.NoSuchFileException;
66 import java.time.ZoneId;
67 import java.time.ZonedDateTime;
69 import java.util.function.Predicate;
70 import java.util.stream.Collectors;
72 // import java.io.FileNotFoundException;
76 * Maven 2 repository format storage implementation. This class currently takes parameters to indicate the repository to
77 * deal with rather than being instantiated per-repository.
78 * FIXME: instantiate one per repository and allocate permanently from a factory (which can be obtained within the session).
81 * The session is passed in as an argument to obtain any necessary resources, rather than the class being instantiated
82 * within the session in the context of a single managed repository's resolution needs.
85 @Service("repositoryStorage#maven2")
86 public class Maven2RepositoryStorage
87 implements RepositoryStorage {
89 private static final Logger log = LoggerFactory.getLogger(Maven2RepositoryStorage.class);
91 private ModelBuilder builder;
94 RepositoryRegistry repositoryRegistry;
97 @Named( "metadataReader#maven" )
98 MavenMetadataReader metadataReader;
101 @Named("repositoryPathTranslator#maven2")
102 private RepositoryPathTranslator pathTranslator;
105 private WagonFactory wagonFactory;
108 private ApplicationContext applicationContext;
111 @Named("pathParser#default")
112 private PathParser pathParser;
115 private ProxyRegistry proxyRegistry;
118 private MavenSystemManager mavenSystemManager;
120 private static final String METADATA_FILENAME_START = "maven-metadata";
122 private static final String METADATA_FILENAME = METADATA_FILENAME_START + ".xml";
124 // This array must be lexically sorted
125 private static final String[] IGNORED_FILES = {METADATA_FILENAME, "resolver-status.properties"};
127 private static final MavenXpp3Reader MAVEN_XPP_3_READER = new MavenXpp3Reader();
131 public void initialize() {
132 builder = new DefaultModelBuilderFactory().newInstance();
137 public ProjectMetadata readProjectMetadata(String repoId, String namespace, String projectId) {
138 // TODO: could natively implement the "shared model" concept from the browse action to avoid needing it there?
143 public ProjectVersionMetadata readProjectVersionMetadata(ReadMetadataRequest readMetadataRequest)
144 throws RepositoryStorageMetadataNotFoundException, RepositoryStorageMetadataInvalidException,
145 RepositoryStorageRuntimeException {
147 ManagedRepository managedRepository = repositoryRegistry.getManagedRepository(readMetadataRequest.getRepositoryId());
148 boolean isReleases = managedRepository.getActiveReleaseSchemes().contains(ReleaseScheme.RELEASE);
149 boolean isSnapshots = managedRepository.getActiveReleaseSchemes().contains(ReleaseScheme.SNAPSHOT);
150 String artifactVersion = readMetadataRequest.getProjectVersion();
151 // olamy: in case of browsing via the ui we can mix repos (parent of a SNAPSHOT can come from release repo)
152 if (!readMetadataRequest.isBrowsingRequest()) {
153 if (VersionUtil.isSnapshot(artifactVersion)) {
154 // skygo trying to improve speed by honoring managed configuration MRM-1658
155 if (isReleases && !isSnapshots) {
156 throw new RepositoryStorageRuntimeException("lookforsnaponreleaseonly",
157 "managed repo is configured for release only");
160 if (!isReleases && isSnapshots) {
161 throw new RepositoryStorageRuntimeException("lookforsreleaseonsneponly",
162 "managed repo is configured for snapshot only");
166 StorageAsset basedir = managedRepository.getAsset("");
167 if (VersionUtil.isSnapshot(artifactVersion)) {
168 StorageAsset metadataFile = pathTranslator.toFile(basedir, readMetadataRequest.getNamespace(),
169 readMetadataRequest.getProjectId(), artifactVersion,
172 ArchivaRepositoryMetadata metadata = metadataReader.read(metadataFile);
174 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
175 SnapshotVersion snapshotVersion = metadata.getSnapshotVersion();
176 if (snapshotVersion != null) {
178 artifactVersion.substring(0, artifactVersion.length() - 8); // remove SNAPSHOT from end
180 artifactVersion + snapshotVersion.getTimestamp() + "-" + snapshotVersion.getBuildNumber();
182 } catch ( RepositoryMetadataException e) {
183 // unable to parse metadata - LOGGER it, and continue with the version as the original SNAPSHOT version
184 log.warn("Invalid metadata: {} - {}", metadataFile, e.getMessage());
188 // TODO: won't work well with some other layouts, might need to convert artifact parts to ID by path translator
189 String id = readMetadataRequest.getProjectId() + "-" + artifactVersion + ".pom";
191 pathTranslator.toFile(basedir, readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(),
192 readMetadataRequest.getProjectVersion(), id);
194 if (!file.exists()) {
195 // metadata could not be resolved
196 throw new RepositoryStorageMetadataNotFoundException(
197 "The artifact's POM file '" + file.getPath() + "' was missing");
200 // TODO: this is a workaround until we can properly resolve using proxies as well - this doesn't cache
202 List<RemoteRepository> remoteRepositories = new ArrayList<>();
203 Map<String, NetworkProxy> networkProxies = new HashMap<>();
205 Map<String, List<ProxyConnector>> proxyConnectorsMap = proxyRegistry.getProxyConnectorAsMap();
206 List<ProxyConnector> proxyConnectors = proxyConnectorsMap.get(readMetadataRequest.getRepositoryId());
207 if (proxyConnectors != null) {
208 for (ProxyConnector proxyConnector : proxyConnectors) {
209 RemoteRepository remoteRepoConfig =
210 repositoryRegistry.getRemoteRepository(proxyConnector.getTargetRepository().getId());
212 if (remoteRepoConfig != null) {
213 remoteRepositories.add(remoteRepoConfig);
215 NetworkProxy networkProxyConfig =
216 proxyRegistry.getNetworkProxy(proxyConnector.getProxyId());
218 if (networkProxyConfig != null) {
219 // key/value: remote repo ID/proxy info
220 networkProxies.put(proxyConnector.getTargetRepository().getId(), networkProxyConfig);
226 // That's a browsing request so we can a mix of SNAPSHOT and release artifacts (especially with snapshots which
227 // can have released parent pom
228 if (readMetadataRequest.isBrowsingRequest()) {
229 remoteRepositories.addAll(repositoryRegistry.getRemoteRepositories());
232 ModelBuildingRequest req =
233 new DefaultModelBuildingRequest().setProcessPlugins(false).setPomFile(file.getFilePath().toFile()).setTwoPhaseBuilding(
234 false).setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);
236 //MRM-1607. olamy this will resolve jdk profiles on the current running archiva jvm
237 req.setSystemProperties(System.getProperties());
240 req.setModelResolver(
241 new RepositoryModelResolver(managedRepository, pathTranslator, wagonFactory, remoteRepositories,
242 networkProxies, managedRepository, mavenSystemManager, metadataReader));
246 model = builder.build(req).getEffectiveModel();
247 } catch (ModelBuildingException e) {
248 String msg = "The artifact's POM file '" + file + "' was invalid: " + e.getMessage();
250 List<ModelProblem> modelProblems = e.getProblems();
251 for (ModelProblem problem : modelProblems) {
252 // MRM-1411, related to MRM-1335
253 // this means that the problem was that the parent wasn't resolved!
254 // olamy really hackhish but fail with java profile so use error message
255 // || ( StringUtils.startsWith( problem.getMessage(), "Failed to determine Java version for profile" ) )
256 // but setTwoPhaseBuilding(true) fix that
257 if (((problem.getException() instanceof FileNotFoundException
258 || problem.getException() instanceof NoSuchFileException
259 ) && e.getModelId() != null &&
260 !e.getModelId().equals(problem.getModelId()))) {
261 log.warn("The artifact's parent POM file '{}' cannot be resolved. "
262 + "Using defaults for project version metadata..", file);
264 ProjectVersionMetadata metadata = new ProjectVersionMetadata();
265 metadata.setId(readMetadataRequest.getProjectVersion());
267 MavenProjectFacet facet = new MavenProjectFacet();
268 facet.setGroupId(readMetadataRequest.getNamespace());
269 facet.setArtifactId(readMetadataRequest.getProjectId());
270 facet.setPackaging("jar");
271 metadata.addFacet(facet);
274 "Error in resolving artifact's parent POM file. " + (problem.getException() == null
275 ? problem.getMessage()
276 : problem.getException().getMessage());
277 RepositoryProblemFacet repoProblemFacet = new RepositoryProblemFacet();
278 repoProblemFacet.setRepositoryId(readMetadataRequest.getRepositoryId());
279 repoProblemFacet.setId(readMetadataRequest.getRepositoryId());
280 repoProblemFacet.setMessage(errMsg);
281 repoProblemFacet.setProblem(errMsg);
282 repoProblemFacet.setProject(readMetadataRequest.getProjectId());
283 repoProblemFacet.setVersion(readMetadataRequest.getProjectVersion());
284 repoProblemFacet.setNamespace(readMetadataRequest.getNamespace());
286 metadata.addFacet(repoProblemFacet);
292 throw new RepositoryStorageMetadataInvalidException("invalid-pom", msg, e);
295 // Check if the POM is in the correct location
296 boolean correctGroupId = readMetadataRequest.getNamespace().equals(model.getGroupId());
297 boolean correctArtifactId = readMetadataRequest.getProjectId().equals(model.getArtifactId());
298 boolean correctVersion = readMetadataRequest.getProjectVersion().equals(model.getVersion());
299 if (!correctGroupId || !correctArtifactId || !correctVersion) {
300 StringBuilder message = new StringBuilder("Incorrect POM coordinates in '" + file + "':");
301 if (!correctGroupId) {
302 message.append("\nIncorrect group ID: ").append(model.getGroupId());
304 if (!correctArtifactId) {
305 message.append("\nIncorrect artifact ID: ").append(model.getArtifactId());
307 if (!correctVersion) {
308 message.append("\nIncorrect version: ").append(model.getVersion());
311 throw new RepositoryStorageMetadataInvalidException("mislocated-pom", message.toString());
314 ProjectVersionMetadata metadata = new ProjectVersionMetadata();
315 metadata.setCiManagement(convertCiManagement(model.getCiManagement()));
316 metadata.setDescription(model.getDescription());
317 metadata.setId(readMetadataRequest.getProjectVersion());
318 metadata.setIssueManagement(convertIssueManagement(model.getIssueManagement()));
319 metadata.setLicenses(convertLicenses(model.getLicenses()));
320 metadata.setMailingLists(convertMailingLists(model.getMailingLists()));
321 metadata.setDependencies(convertDependencies(model.getDependencies()));
322 metadata.setName(model.getName());
323 metadata.setOrganization(convertOrganization(model.getOrganization()));
324 metadata.setScm(convertScm(model.getScm()));
325 metadata.setUrl(model.getUrl());
326 metadata.setProperties(model.getProperties());
328 MavenProjectFacet facet = new MavenProjectFacet();
329 facet.setGroupId(model.getGroupId() != null ? model.getGroupId() : model.getParent().getGroupId());
330 facet.setArtifactId(model.getArtifactId());
331 facet.setPackaging(model.getPackaging());
332 if (model.getParent() != null) {
333 MavenProjectParent parent = new MavenProjectParent();
334 parent.setGroupId(model.getParent().getGroupId());
335 parent.setArtifactId(model.getParent().getArtifactId());
336 parent.setVersion(model.getParent().getVersion());
337 facet.setParent(parent);
339 metadata.addFacet(facet);
346 public void setWagonFactory(WagonFactory wagonFactory) {
347 this.wagonFactory = wagonFactory;
350 private List<org.apache.archiva.metadata.model.Dependency> convertDependencies(List<Dependency> dependencies) {
351 List<org.apache.archiva.metadata.model.Dependency> l = new ArrayList<>();
352 for (Dependency dependency : dependencies) {
353 org.apache.archiva.metadata.model.Dependency newDependency =
354 new org.apache.archiva.metadata.model.Dependency();
355 newDependency.setArtifactId(dependency.getArtifactId());
356 newDependency.setClassifier(dependency.getClassifier());
357 newDependency.setNamespace(dependency.getGroupId());
358 newDependency.setOptional(dependency.isOptional());
359 newDependency.setScope(dependency.getScope());
360 newDependency.setSystemPath(dependency.getSystemPath());
361 newDependency.setType(dependency.getType());
362 newDependency.setVersion(dependency.getVersion());
363 l.add(newDependency);
368 private org.apache.archiva.metadata.model.Scm convertScm(Scm scm) {
369 org.apache.archiva.metadata.model.Scm newScm = null;
371 newScm = new org.apache.archiva.metadata.model.Scm();
372 newScm.setConnection(scm.getConnection());
373 newScm.setDeveloperConnection(scm.getDeveloperConnection());
374 newScm.setUrl(scm.getUrl());
379 private org.apache.archiva.metadata.model.Organization convertOrganization(Organization organization) {
380 org.apache.archiva.metadata.model.Organization org = null;
381 if (organization != null) {
382 org = new org.apache.archiva.metadata.model.Organization();
383 org.setName(organization.getName());
384 org.setUrl(organization.getUrl());
389 private List<org.apache.archiva.metadata.model.License> convertLicenses(List<License> licenses) {
390 List<org.apache.archiva.metadata.model.License> l = new ArrayList<>();
391 for (License license : licenses) {
392 org.apache.archiva.metadata.model.License newLicense = new org.apache.archiva.metadata.model.License();
393 newLicense.setName(license.getName());
394 newLicense.setUrl(license.getUrl());
400 private List<org.apache.archiva.metadata.model.MailingList> convertMailingLists(List<MailingList> mailingLists) {
401 List<org.apache.archiva.metadata.model.MailingList> l = new ArrayList<>();
402 for (MailingList mailingList : mailingLists) {
403 org.apache.archiva.metadata.model.MailingList newMailingList =
404 new org.apache.archiva.metadata.model.MailingList();
405 newMailingList.setName(mailingList.getName());
406 newMailingList.setMainArchiveUrl(mailingList.getArchive());
407 newMailingList.setPostAddress(mailingList.getPost());
408 newMailingList.setSubscribeAddress(mailingList.getSubscribe());
409 newMailingList.setUnsubscribeAddress(mailingList.getUnsubscribe());
410 newMailingList.setOtherArchives(mailingList.getOtherArchives());
411 l.add(newMailingList);
416 private org.apache.archiva.metadata.model.IssueManagement convertIssueManagement(IssueManagement issueManagement) {
417 org.apache.archiva.metadata.model.IssueManagement im = null;
418 if (issueManagement != null) {
419 im = new org.apache.archiva.metadata.model.IssueManagement();
420 im.setSystem(issueManagement.getSystem());
421 im.setUrl(issueManagement.getUrl());
426 private org.apache.archiva.metadata.model.CiManagement convertCiManagement(CiManagement ciManagement) {
427 org.apache.archiva.metadata.model.CiManagement ci = null;
428 if (ciManagement != null) {
429 ci = new org.apache.archiva.metadata.model.CiManagement();
430 ci.setSystem(ciManagement.getSystem());
431 ci.setUrl(ciManagement.getUrl());
437 public Collection<String> listRootNamespaces(String repoId, Filter<String> filter)
438 throws RepositoryStorageRuntimeException {
439 StorageAsset dir = getRepositoryBasedir(repoId);
441 return getSortedFiles(dir, filter);
444 private static Collection<String> getSortedFiles(StorageAsset dir, Filter<String> filter) {
446 final Predicate<StorageAsset> dFilter = new DirectoryFilter(filter);
447 return dir.list().stream().filter(f -> f.isContainer())
449 .map(path -> path.getName().toString())
450 .sorted().collect(Collectors.toList());
454 private StorageAsset getRepositoryBasedir(String repoId)
455 throws RepositoryStorageRuntimeException {
456 ManagedRepository repositoryConfiguration = repositoryRegistry.getManagedRepository(repoId);
458 return repositoryConfiguration.getAsset("");
462 public Collection<String> listNamespaces(String repoId, String namespace, Filter<String> filter)
463 throws RepositoryStorageRuntimeException {
464 StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace);
465 if (!(dir.exists()) && !dir.isContainer()) {
466 return Collections.emptyList();
468 // scan all the directories which are potential namespaces. Any directories known to be projects are excluded
469 Predicate<StorageAsset> dFilter = new DirectoryFilter(filter);
470 return dir.list().stream().filter(dFilter).filter(path -> !isProject(path, filter)).map(path -> path.getName().toString())
471 .sorted().collect(Collectors.toList());
475 public Collection<String> listProjects(String repoId, String namespace, Filter<String> filter)
476 throws RepositoryStorageRuntimeException {
477 StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace);
478 if (!(dir.exists() && dir.isContainer())) {
479 return Collections.emptyList();
481 // scan all directories in the namespace, and only include those that are known to be projects
482 final Predicate<StorageAsset> dFilter = new DirectoryFilter(filter);
483 return dir.list().stream().filter(dFilter).filter(path -> isProject(path, filter)).map(path -> path.getName().toString())
484 .sorted().collect(Collectors.toList());
489 public Collection<String> listProjectVersions(String repoId, String namespace, String projectId,
490 Filter<String> filter)
491 throws RepositoryStorageRuntimeException {
492 StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace, projectId);
493 if (!(dir.exists() && dir.isContainer())) {
494 return Collections.emptyList();
497 // all directories in a project directory can be considered a version
498 return getSortedFiles(dir, filter);
502 public Collection<ArtifactMetadata> readArtifactsMetadata(ReadMetadataRequest readMetadataRequest)
503 throws RepositoryStorageRuntimeException {
504 StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(readMetadataRequest.getRepositoryId()),
505 readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(),
506 readMetadataRequest.getProjectVersion());
507 if (!(dir.exists() && dir.isContainer())) {
508 return Collections.emptyList();
511 // all files that are not metadata and not a checksum / signature are considered artifacts
512 final Predicate<StorageAsset> dFilter = new ArtifactDirectoryFilter(readMetadataRequest.getFilter());
513 // Returns a map TRUE -> (success values), FALSE -> (Exceptions)
514 Map<Boolean, List<Try<ArtifactMetadata>>> result = dir.list().stream().filter(dFilter).map(path -> {
516 return Try.success(getArtifactFromFile(readMetadataRequest.getRepositoryId(), readMetadataRequest.getNamespace(),
517 readMetadataRequest.getProjectId(), readMetadataRequest.getProjectVersion(),
519 } catch (Exception e) {
520 log.debug("Could not create metadata for {}: {}", path, e.getMessage(), e);
521 return Try.<ArtifactMetadata>failure(e);
524 ).collect(Collectors.groupingBy(Try::isSuccess));
525 if (result.containsKey(Boolean.FALSE) && result.get(Boolean.FALSE).size() > 0 && (!result.containsKey(Boolean.TRUE) || result.get(Boolean.TRUE).size() == 0)) {
526 log.error("Could not get artifact metadata. Directory: {}. Number of errors {}.", dir, result.get(Boolean.FALSE).size());
527 Try<ArtifactMetadata> failure = result.get(Boolean.FALSE).get(0);
528 log.error("Sample exception {}", failure.getError().getMessage(), failure.getError());
529 throw new RepositoryStorageRuntimeException(readMetadataRequest.getRepositoryId(), "Could not retrieve metadata of the files");
531 if (!result.containsKey(Boolean.TRUE) || result.get(Boolean.TRUE) == null) {
532 return Collections.emptyList();
534 return result.get(Boolean.TRUE).stream().map(tr -> tr.get()).collect(Collectors.toList());
540 public ArtifactMetadata readArtifactMetadataFromPath(String repoId, String path)
541 throws RepositoryStorageRuntimeException {
542 ArtifactMetadata metadata = pathTranslator.getArtifactForPath(repoId, path);
545 populateArtifactMetadataFromFile(metadata, getRepositoryBasedir(repoId).resolve(path));
546 } catch (IOException e) {
547 throw new RepositoryStorageRuntimeException(repoId, "Error during metadata retrieval of " + path + " :" + e.getMessage(), e);
553 private ArtifactMetadata getArtifactFromFile(String repoId, String namespace, String projectId,
554 String projectVersion, StorageAsset file) throws IOException {
555 ArtifactMetadata metadata =
556 pathTranslator.getArtifactFromId(repoId, namespace, projectId, projectVersion, file.getName());
558 populateArtifactMetadataFromFile(metadata, file);
564 public void applyServerSideRelocation(ManagedRepository managedRepository, ArtifactReference artifact)
565 throws ProxyDownloadException {
566 if ("pom".equals(artifact.getType())) {
570 // Build the artifact POM reference
571 ArtifactReference pomReference = new ArtifactReference();
572 pomReference.setGroupId(artifact.getGroupId());
573 pomReference.setArtifactId(artifact.getArtifactId());
574 pomReference.setVersion(artifact.getVersion());
575 pomReference.setType("pom");
577 RepositoryType repositoryType = managedRepository.getType();
578 if (!proxyRegistry.hasHandler(repositoryType)) {
579 throw new ProxyDownloadException("No proxy handler found for repository type " + repositoryType, new HashMap<>());
582 RepositoryProxyHandler proxyHandler = proxyRegistry.getHandler(repositoryType).get(0);
584 // Get the artifact POM from proxied repositories if needed
585 proxyHandler.fetchFromProxies(managedRepository, pomReference);
587 // Open and read the POM from the managed repo
588 StorageAsset pom = null;
591 pom = managedRepository.getContent().getLayout( BaseRepositoryContentLayout.class ).toFile(pomReference);
593 catch ( LayoutException e )
595 throw new ProxyDownloadException( "Cannot convert layout ", new HashMap<>( ) );
603 // MavenXpp3Reader leaves the file open, so we need to close it ourselves.
606 try (Reader reader = Channels.newReader(pom.getReadChannel(), Charset.defaultCharset().name())) {
607 model = MAVEN_XPP_3_READER.read(reader);
610 DistributionManagement dist = model.getDistributionManagement();
612 Relocation relocation = dist.getRelocation();
613 if (relocation != null) {
614 // artifact is relocated : update the repositoryPath
615 if (relocation.getGroupId() != null) {
616 artifact.setGroupId(relocation.getGroupId());
618 if (relocation.getArtifactId() != null) {
619 artifact.setArtifactId(relocation.getArtifactId());
621 if (relocation.getVersion() != null) {
622 artifact.setVersion(relocation.getVersion());
626 } catch (IOException e) {
627 // Unable to read POM : ignore.
628 } catch (XmlPullParserException e) {
629 // Invalid POM : ignore
635 public String getFilePath(String requestPath, org.apache.archiva.repository.ManagedRepository managedRepository) {
636 // managedRepository can be null
637 // extract artifact reference from url
638 // groupId:artifactId:version:packaging:classifier
639 //org/apache/archiva/archiva-checksum/1.4-M4-SNAPSHOT/archiva-checksum-1.4-M4-SNAPSHOT.jar
640 String logicalResource = null;
641 String requestPathInfo = StringUtils.defaultString(requestPath);
643 //remove prefix ie /repository/blah becomes /blah
644 requestPathInfo = removePrefix(requestPathInfo);
646 // Remove prefixing slash as the repository id doesn't contain it;
647 if (requestPathInfo.startsWith("/")) {
648 requestPathInfo = requestPathInfo.substring(1);
651 int slash = requestPathInfo.indexOf('/');
653 logicalResource = requestPathInfo.substring(slash);
655 if (logicalResource.endsWith("/..")) {
656 logicalResource += "/";
659 if (logicalResource != null && logicalResource.startsWith("//")) {
660 logicalResource = logicalResource.substring(1);
663 if (logicalResource == null) {
664 logicalResource = "/";
667 logicalResource = "/";
669 return logicalResource;
674 public String getFilePathWithVersion(final String requestPath, ManagedRepositoryContent managedRepositoryContent)
675 throws RelocationException
678 if (StringUtils.endsWith(requestPath, METADATA_FILENAME)) {
679 return getFilePath(requestPath, managedRepositoryContent.getRepository());
682 String filePath = getFilePath(requestPath, managedRepositoryContent.getRepository());
684 ArtifactReference artifactReference = null;
686 artifactReference = pathParser.toArtifactReference(filePath);
687 } catch (LayoutException e) {
691 if (StringUtils.endsWith(artifactReference.getVersion(), VersionUtil.SNAPSHOT)) {
692 // read maven metadata to get last timestamp
693 StorageAsset metadataDir = managedRepositoryContent.getRepository().getAsset(filePath).getParent();
694 if (!metadataDir.exists()) {
697 StorageAsset metadataFile = metadataDir.resolve(METADATA_FILENAME);
698 if (!metadataFile.exists()) {
701 ArchivaRepositoryMetadata archivaRepositoryMetadata = null;
704 archivaRepositoryMetadata = metadataReader.read(metadataFile);
706 catch ( RepositoryMetadataException e )
708 log.error( "Could not read metadata {}", e.getMessage( ), e );
711 int buildNumber = archivaRepositoryMetadata.getSnapshotVersion().getBuildNumber();
712 String timestamp = archivaRepositoryMetadata.getSnapshotVersion().getTimestamp();
715 if (buildNumber < 1 && timestamp == null) {
719 // org/apache/archiva/archiva-checksum/1.4-M4-SNAPSHOT/archiva-checksum-1.4-M4-SNAPSHOT.jar
720 // -> archiva-checksum-1.4-M4-20130425.081822-1.jar
722 filePath = StringUtils.replace(filePath, //
723 artifactReference.getArtifactId() //
724 + "-" + artifactReference.getVersion(), //
725 artifactReference.getArtifactId() //
726 + "-" + StringUtils.remove(artifactReference.getVersion(),
727 "-" + VersionUtil.SNAPSHOT) //
729 + "-" + buildNumber);
731 throw new RelocationException("/repository/" + managedRepositoryContent.getRepository().getId() +
732 (StringUtils.startsWith(filePath, "/") ? "" : "/") + filePath,
733 RelocationException.RelocationType.TEMPORARY);
740 //-----------------------------
742 //-----------------------------
750 private static String removePrefix(final String href) {
751 String[] parts = StringUtils.split(href, '/');
752 parts = (String[]) ArrayUtils.subarray(parts, 1, parts.length);
753 if (parts == null || parts.length == 0) {
757 String joinedString = StringUtils.join(parts, '/');
758 if (href.endsWith("/")) {
759 joinedString = joinedString + "/";
765 private static void populateArtifactMetadataFromFile(ArtifactMetadata metadata, StorageAsset file) throws IOException {
766 metadata.setWhenGathered(ZonedDateTime.now(ZoneId.of("GMT")));
767 metadata.setFileLastModified(file.getModificationTime().toEpochMilli());
768 ChecksummedFile checksummedFile = new ChecksummedFile(file.getFilePath());
770 metadata.setMd5(checksummedFile.calculateChecksum(ChecksumAlgorithm.MD5));
771 } catch (IOException e) {
772 log.error("Unable to checksum file {}: {},MD5", file, e.getMessage());
775 metadata.setSha1(checksummedFile.calculateChecksum(ChecksumAlgorithm.SHA1));
776 } catch (IOException e) {
777 log.error("Unable to checksum file {}: {},SHA1", file, e.getMessage());
779 metadata.setSize(file.getSize());
782 private boolean isProject(StorageAsset dir, Filter<String> filter) {
783 // scan directories for a valid project version subdirectory, meaning this must be a project directory
784 final Predicate<StorageAsset> dFilter = new DirectoryFilter(filter);
785 boolean projFound = dir.list().stream().filter(dFilter)
786 .anyMatch(path -> isProjectVersion(path));
791 // if a metadata file is present, check if this is the "artifactId" directory, marking it as a project
792 ArchivaRepositoryMetadata metadata = readMetadata(dir);
793 if (metadata != null && dir.getName().toString().equals(metadata.getArtifactId())) {
800 private boolean isProjectVersion(StorageAsset dir) {
801 final String artifactId = dir.getParent().getName();
802 final String projectVersion = dir.getName();
804 // check if there is a POM artifact file to ensure it is a version directory
806 Predicate<StorageAsset> filter;
807 if (VersionUtil.isSnapshot(projectVersion)) {
808 filter = new PomFilenameFilter(artifactId, projectVersion);
810 final String pomFile = artifactId + "-" + projectVersion + ".pom";
811 filter = new PomFileFilter(pomFile);
813 if (dir.list().stream().filter(f -> !f.isContainer()).anyMatch(filter)) {
816 // if a metadata file is present, check if this is the "version" directory, marking it as a project version
817 ArchivaRepositoryMetadata metadata = readMetadata(dir);
818 if (metadata != null && projectVersion.equals(metadata.getVersion())) {
825 private ArchivaRepositoryMetadata readMetadata(StorageAsset directory) {
826 ArchivaRepositoryMetadata metadata = null;
827 StorageAsset metadataFile = directory.resolve(METADATA_FILENAME);
828 if (metadataFile.exists()) {
830 metadata = metadataReader.read(metadataFile);
831 } catch ( RepositoryMetadataException e )
833 // Ignore missing or invalid metadata
839 private static class DirectoryFilter
840 implements Predicate<StorageAsset> {
841 private final Filter<String> filter;
843 public DirectoryFilter(Filter<String> filter) {
844 this.filter = filter;
848 public boolean test(StorageAsset dir) {
849 final String name = dir.getName();
850 if (!filter.accept(name)) {
852 } else if (name.startsWith(".")) {
854 } else if (!dir.isContainer()) {
861 private static class ArtifactDirectoryFilter
862 implements Predicate<StorageAsset> {
863 private final Filter<String> filter;
865 private ArtifactDirectoryFilter(Filter<String> filter) {
866 this.filter = filter;
870 public boolean test(StorageAsset file) {
871 final Set<String> checksumExts = ChecksumAlgorithm.getAllExtensions();
872 final String path = file.getPath();
873 final String name = file.getName();
874 final String extension = StringUtils.substringAfterLast(name, ".").toLowerCase();
875 // TODO compare to logic in maven-repository-layer
876 if (file.isContainer()) {
878 } else if (!filter.accept(name)) {
880 } else if (name.startsWith(".") || path.contains("/.") ) {
882 } else if (checksumExts.contains(extension)) {
884 } else if (Arrays.binarySearch(IGNORED_FILES, name) >= 0) {
887 // some files from remote repositories can have name like maven-metadata-archiva-vm-all-public.xml
888 else if (StringUtils.startsWith(name, METADATA_FILENAME_START) && StringUtils.endsWith(name, ".xml")) {
898 private static final class PomFilenameFilter
899 implements Predicate<StorageAsset> {
901 private final String artifactId, projectVersion;
903 private PomFilenameFilter(String artifactId, String projectVersion) {
904 this.artifactId = artifactId;
905 this.projectVersion = projectVersion;
909 public boolean test(StorageAsset dir) {
910 final String name = dir.getName();
911 if (name.startsWith(artifactId + "-") && name.endsWith(".pom")) {
912 String v = name.substring(artifactId.length() + 1, name.length() - 4);
913 v = VersionUtil.getBaseVersion(v);
914 if (v.equals(projectVersion)) {
923 private static class PomFileFilter
924 implements Predicate<StorageAsset> {
925 private final String pomFile;
927 private PomFileFilter(String pomFile) {
928 this.pomFile = pomFile;
932 public boolean test(StorageAsset dir) {
933 return pomFile.equals(dir.getName());
938 public PathParser getPathParser() {
942 public void setPathParser(PathParser pathParser) {
943 this.pathParser = pathParser;