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.Artifact;
43 import org.apache.archiva.repository.content.ContentItem;
44 import org.apache.archiva.repository.content.ItemSelector;
45 import org.apache.archiva.repository.content.PathParser;
46 import org.apache.archiva.repository.content.base.ArchivaItemSelector;
47 import org.apache.archiva.repository.maven.MavenSystemManager;
48 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
49 import org.apache.archiva.repository.storage.StorageAsset;
50 import org.apache.commons.lang3.ArrayUtils;
51 import org.apache.commons.lang3.StringUtils;
52 import org.apache.maven.model.*;
53 import org.apache.maven.model.building.*;
54 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
55 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58 import org.springframework.context.ApplicationContext;
59 import org.springframework.stereotype.Service;
61 import javax.annotation.PostConstruct;
62 import javax.inject.Inject;
63 import javax.inject.Named;
64 import java.io.FileNotFoundException;
65 import java.io.IOException;
66 import java.io.Reader;
67 import java.nio.channels.Channels;
68 import java.nio.charset.Charset;
69 import java.nio.file.NoSuchFileException;
70 import java.time.ZoneId;
71 import java.time.ZonedDateTime;
73 import java.util.function.Predicate;
74 import java.util.stream.Collectors;
76 // import java.io.FileNotFoundException;
80 * Maven 2 repository format storage implementation. This class currently takes parameters to indicate the repository to
81 * deal with rather than being instantiated per-repository.
82 * FIXME: instantiate one per repository and allocate permanently from a factory (which can be obtained within the session).
85 * The session is passed in as an argument to obtain any necessary resources, rather than the class being instantiated
86 * within the session in the context of a single managed repository's resolution needs.
89 @Service("repositoryStorage#maven2")
90 public class Maven2RepositoryStorage
91 implements RepositoryStorage {
93 private static final Logger log = LoggerFactory.getLogger(Maven2RepositoryStorage.class);
95 private ModelBuilder builder;
98 RepositoryRegistry repositoryRegistry;
101 @Named( "metadataReader#maven" )
102 MavenMetadataReader metadataReader;
105 @Named("repositoryPathTranslator#maven2")
106 private RepositoryPathTranslator pathTranslator;
109 private WagonFactory wagonFactory;
112 private ApplicationContext applicationContext;
115 @Named("pathParser#default")
116 private PathParser pathParser;
119 private ProxyRegistry proxyRegistry;
122 private MavenSystemManager mavenSystemManager;
124 private static final String METADATA_FILENAME_START = "maven-metadata";
126 private static final String METADATA_FILENAME = METADATA_FILENAME_START + ".xml";
128 // This array must be lexically sorted
129 private static final String[] IGNORED_FILES = {METADATA_FILENAME, "resolver-status.properties"};
131 private static final MavenXpp3Reader MAVEN_XPP_3_READER = new MavenXpp3Reader();
135 public void initialize() {
136 builder = new DefaultModelBuilderFactory().newInstance();
141 public ProjectMetadata readProjectMetadata(String repoId, String namespace, String projectId) {
142 // TODO: could natively implement the "shared model" concept from the browse action to avoid needing it there?
147 public ProjectVersionMetadata readProjectVersionMetadata(ReadMetadataRequest readMetadataRequest)
148 throws RepositoryStorageMetadataNotFoundException, RepositoryStorageMetadataInvalidException,
149 RepositoryStorageRuntimeException {
151 ManagedRepository managedRepository = repositoryRegistry.getManagedRepository(readMetadataRequest.getRepositoryId());
152 boolean isReleases = managedRepository.getActiveReleaseSchemes().contains(ReleaseScheme.RELEASE);
153 boolean isSnapshots = managedRepository.getActiveReleaseSchemes().contains(ReleaseScheme.SNAPSHOT);
154 String artifactVersion = readMetadataRequest.getProjectVersion();
155 // olamy: in case of browsing via the ui we can mix repos (parent of a SNAPSHOT can come from release repo)
156 if (!readMetadataRequest.isBrowsingRequest()) {
157 if (VersionUtil.isSnapshot(artifactVersion)) {
158 // skygo trying to improve speed by honoring managed configuration MRM-1658
159 if (isReleases && !isSnapshots) {
160 throw new RepositoryStorageRuntimeException("lookforsnaponreleaseonly",
161 "managed repo is configured for release only");
164 if (!isReleases && isSnapshots) {
165 throw new RepositoryStorageRuntimeException("lookforsreleaseonsneponly",
166 "managed repo is configured for snapshot only");
170 StorageAsset basedir = managedRepository.getRoot();
171 if (VersionUtil.isSnapshot(artifactVersion)) {
172 StorageAsset metadataFile = pathTranslator.toFile(basedir, readMetadataRequest.getNamespace(),
173 readMetadataRequest.getProjectId(), artifactVersion,
176 ArchivaRepositoryMetadata metadata = metadataReader.read(metadataFile);
178 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
179 SnapshotVersion snapshotVersion = metadata.getSnapshotVersion();
180 if (snapshotVersion != null) {
182 artifactVersion.substring(0, artifactVersion.length() - 8); // remove SNAPSHOT from end
184 artifactVersion + snapshotVersion.getTimestamp() + "-" + snapshotVersion.getBuildNumber();
186 } catch ( RepositoryMetadataException e) {
187 // unable to parse metadata - LOGGER it, and continue with the version as the original SNAPSHOT version
188 log.warn("Invalid metadata: {} - {}", metadataFile, e.getMessage());
192 // TODO: won't work well with some other layouts, might need to convert artifact parts to ID by path translator
193 String id = readMetadataRequest.getProjectId() + "-" + artifactVersion + ".pom";
195 pathTranslator.toFile(basedir, readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(),
196 readMetadataRequest.getProjectVersion(), id);
198 if (!file.exists()) {
199 // metadata could not be resolved
200 throw new RepositoryStorageMetadataNotFoundException(
201 "The artifact's POM file '" + file.getPath() + "' was missing");
204 // TODO: this is a workaround until we can properly resolve using proxies as well - this doesn't cache
206 List<RemoteRepository> remoteRepositories = new ArrayList<>();
207 Map<String, NetworkProxy> networkProxies = new HashMap<>();
209 Map<String, List<ProxyConnector>> proxyConnectorsMap = proxyRegistry.getProxyConnectorAsMap();
210 List<ProxyConnector> proxyConnectors = proxyConnectorsMap.get(readMetadataRequest.getRepositoryId());
211 if (proxyConnectors != null) {
212 for (ProxyConnector proxyConnector : proxyConnectors) {
213 RemoteRepository remoteRepoConfig =
214 repositoryRegistry.getRemoteRepository(proxyConnector.getTargetRepository().getId());
216 if (remoteRepoConfig != null) {
217 remoteRepositories.add(remoteRepoConfig);
219 NetworkProxy networkProxyConfig =
220 proxyRegistry.getNetworkProxy(proxyConnector.getProxyId());
222 if (networkProxyConfig != null) {
223 // key/value: remote repo ID/proxy info
224 networkProxies.put(proxyConnector.getTargetRepository().getId(), networkProxyConfig);
230 // That's a browsing request so we can a mix of SNAPSHOT and release artifacts (especially with snapshots which
231 // can have released parent pom
232 if (readMetadataRequest.isBrowsingRequest()) {
233 remoteRepositories.addAll(repositoryRegistry.getRemoteRepositories());
236 ModelBuildingRequest req =
237 new DefaultModelBuildingRequest().setProcessPlugins(false).setPomFile(file.getFilePath().toFile()).setTwoPhaseBuilding(
238 false).setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);
240 //MRM-1607. olamy this will resolve jdk profiles on the current running archiva jvm
241 req.setSystemProperties(System.getProperties());
244 req.setModelResolver(
245 new RepositoryModelResolver(managedRepository, pathTranslator, wagonFactory, remoteRepositories,
246 networkProxies, managedRepository, mavenSystemManager, metadataReader));
250 model = builder.build(req).getEffectiveModel();
251 } catch (ModelBuildingException e) {
252 String msg = "The artifact's POM file '" + file + "' was invalid: " + e.getMessage();
254 List<ModelProblem> modelProblems = e.getProblems();
255 for (ModelProblem problem : modelProblems) {
256 // MRM-1411, related to MRM-1335
257 // this means that the problem was that the parent wasn't resolved!
258 // olamy really hackhish but fail with java profile so use error message
259 // || ( StringUtils.startsWith( problem.getMessage(), "Failed to determine Java version for profile" ) )
260 // but setTwoPhaseBuilding(true) fix that
261 if (((problem.getException() instanceof FileNotFoundException
262 || problem.getException() instanceof NoSuchFileException
263 ) && e.getModelId() != null &&
264 !e.getModelId().equals(problem.getModelId()))) {
265 log.warn("The artifact's parent POM file '{}' cannot be resolved. "
266 + "Using defaults for project version metadata..", file);
268 ProjectVersionMetadata metadata = new ProjectVersionMetadata();
269 metadata.setId(readMetadataRequest.getProjectVersion());
271 MavenProjectFacet facet = new MavenProjectFacet();
272 facet.setGroupId(readMetadataRequest.getNamespace());
273 facet.setArtifactId(readMetadataRequest.getProjectId());
274 facet.setPackaging("jar");
275 metadata.addFacet(facet);
278 "Error in resolving artifact's parent POM file. " + (problem.getException() == null
279 ? problem.getMessage()
280 : problem.getException().getMessage());
281 RepositoryProblemFacet repoProblemFacet = new RepositoryProblemFacet();
282 repoProblemFacet.setRepositoryId(readMetadataRequest.getRepositoryId());
283 repoProblemFacet.setId(readMetadataRequest.getRepositoryId());
284 repoProblemFacet.setMessage(errMsg);
285 repoProblemFacet.setProblem(errMsg);
286 repoProblemFacet.setProject(readMetadataRequest.getProjectId());
287 repoProblemFacet.setVersion(readMetadataRequest.getProjectVersion());
288 repoProblemFacet.setNamespace(readMetadataRequest.getNamespace());
290 metadata.addFacet(repoProblemFacet);
296 throw new RepositoryStorageMetadataInvalidException("invalid-pom", msg, e);
299 // Check if the POM is in the correct location
300 boolean correctGroupId = readMetadataRequest.getNamespace().equals(model.getGroupId());
301 boolean correctArtifactId = readMetadataRequest.getProjectId().equals(model.getArtifactId());
302 boolean correctVersion = readMetadataRequest.getProjectVersion().equals(model.getVersion());
303 if (!correctGroupId || !correctArtifactId || !correctVersion) {
304 StringBuilder message = new StringBuilder("Incorrect POM coordinates in '" + file + "':");
305 if (!correctGroupId) {
306 message.append("\nIncorrect group ID: ").append(model.getGroupId());
308 if (!correctArtifactId) {
309 message.append("\nIncorrect artifact ID: ").append(model.getArtifactId());
311 if (!correctVersion) {
312 message.append("\nIncorrect version: ").append(model.getVersion());
315 throw new RepositoryStorageMetadataInvalidException("mislocated-pom", message.toString());
318 ProjectVersionMetadata metadata = new ProjectVersionMetadata();
319 metadata.setCiManagement(convertCiManagement(model.getCiManagement()));
320 metadata.setDescription(model.getDescription());
321 metadata.setId(readMetadataRequest.getProjectVersion());
322 metadata.setIssueManagement(convertIssueManagement(model.getIssueManagement()));
323 metadata.setLicenses(convertLicenses(model.getLicenses()));
324 metadata.setMailingLists(convertMailingLists(model.getMailingLists()));
325 metadata.setDependencies(convertDependencies(model.getDependencies()));
326 metadata.setName(model.getName());
327 metadata.setOrganization(convertOrganization(model.getOrganization()));
328 metadata.setScm(convertScm(model.getScm()));
329 metadata.setUrl(model.getUrl());
330 metadata.setProperties(model.getProperties());
332 MavenProjectFacet facet = new MavenProjectFacet();
333 facet.setGroupId(model.getGroupId() != null ? model.getGroupId() : model.getParent().getGroupId());
334 facet.setArtifactId(model.getArtifactId());
335 facet.setPackaging(model.getPackaging());
336 if (model.getParent() != null) {
337 MavenProjectParent parent = new MavenProjectParent();
338 parent.setGroupId(model.getParent().getGroupId());
339 parent.setArtifactId(model.getParent().getArtifactId());
340 parent.setVersion(model.getParent().getVersion());
341 facet.setParent(parent);
343 metadata.addFacet(facet);
350 public void setWagonFactory(WagonFactory wagonFactory) {
351 this.wagonFactory = wagonFactory;
354 private List<org.apache.archiva.metadata.model.Dependency> convertDependencies(List<Dependency> dependencies) {
355 List<org.apache.archiva.metadata.model.Dependency> l = new ArrayList<>();
356 for (Dependency dependency : dependencies) {
357 org.apache.archiva.metadata.model.Dependency newDependency =
358 new org.apache.archiva.metadata.model.Dependency();
359 newDependency.setArtifactId(dependency.getArtifactId());
360 newDependency.setClassifier(dependency.getClassifier());
361 newDependency.setNamespace(dependency.getGroupId());
362 newDependency.setOptional(dependency.isOptional());
363 newDependency.setScope(dependency.getScope());
364 newDependency.setSystemPath(dependency.getSystemPath());
365 newDependency.setType(dependency.getType());
366 newDependency.setVersion(dependency.getVersion());
367 l.add(newDependency);
372 private org.apache.archiva.metadata.model.Scm convertScm(Scm scm) {
373 org.apache.archiva.metadata.model.Scm newScm = null;
375 newScm = new org.apache.archiva.metadata.model.Scm();
376 newScm.setConnection(scm.getConnection());
377 newScm.setDeveloperConnection(scm.getDeveloperConnection());
378 newScm.setUrl(scm.getUrl());
383 private org.apache.archiva.metadata.model.Organization convertOrganization(Organization organization) {
384 org.apache.archiva.metadata.model.Organization org = null;
385 if (organization != null) {
386 org = new org.apache.archiva.metadata.model.Organization();
387 org.setName(organization.getName());
388 org.setUrl(organization.getUrl());
393 private List<org.apache.archiva.metadata.model.License> convertLicenses(List<License> licenses) {
394 List<org.apache.archiva.metadata.model.License> l = new ArrayList<>();
395 for (License license : licenses) {
396 org.apache.archiva.metadata.model.License newLicense = new org.apache.archiva.metadata.model.License();
397 newLicense.setName(license.getName());
398 newLicense.setUrl(license.getUrl());
404 private List<org.apache.archiva.metadata.model.MailingList> convertMailingLists(List<MailingList> mailingLists) {
405 List<org.apache.archiva.metadata.model.MailingList> l = new ArrayList<>();
406 for (MailingList mailingList : mailingLists) {
407 org.apache.archiva.metadata.model.MailingList newMailingList =
408 new org.apache.archiva.metadata.model.MailingList();
409 newMailingList.setName(mailingList.getName());
410 newMailingList.setMainArchiveUrl(mailingList.getArchive());
411 newMailingList.setPostAddress(mailingList.getPost());
412 newMailingList.setSubscribeAddress(mailingList.getSubscribe());
413 newMailingList.setUnsubscribeAddress(mailingList.getUnsubscribe());
414 newMailingList.setOtherArchives(mailingList.getOtherArchives());
415 l.add(newMailingList);
420 private org.apache.archiva.metadata.model.IssueManagement convertIssueManagement(IssueManagement issueManagement) {
421 org.apache.archiva.metadata.model.IssueManagement im = null;
422 if (issueManagement != null) {
423 im = new org.apache.archiva.metadata.model.IssueManagement();
424 im.setSystem(issueManagement.getSystem());
425 im.setUrl(issueManagement.getUrl());
430 private org.apache.archiva.metadata.model.CiManagement convertCiManagement(CiManagement ciManagement) {
431 org.apache.archiva.metadata.model.CiManagement ci = null;
432 if (ciManagement != null) {
433 ci = new org.apache.archiva.metadata.model.CiManagement();
434 ci.setSystem(ciManagement.getSystem());
435 ci.setUrl(ciManagement.getUrl());
441 public Collection<String> listRootNamespaces(String repoId, Filter<String> filter)
442 throws RepositoryStorageRuntimeException {
443 StorageAsset dir = getRepositoryBasedir(repoId);
445 return getSortedFiles(dir, filter);
448 private static Collection<String> getSortedFiles(StorageAsset dir, Filter<String> filter) {
450 final Predicate<StorageAsset> dFilter = new DirectoryFilter(filter);
451 return dir.list().stream().filter(f -> f.isContainer())
453 .map(path -> path.getName().toString())
454 .sorted().collect(Collectors.toList());
458 private StorageAsset getRepositoryBasedir(String repoId)
459 throws RepositoryStorageRuntimeException {
460 ManagedRepository repository = repositoryRegistry.getManagedRepository(repoId);
462 return repository.getRoot();
466 public Collection<String> listNamespaces(String repoId, String namespace, Filter<String> filter)
467 throws RepositoryStorageRuntimeException {
468 StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace);
469 if (!(dir.exists()) && !dir.isContainer()) {
470 return Collections.emptyList();
472 // scan all the directories which are potential namespaces. Any directories known to be projects are excluded
473 Predicate<StorageAsset> dFilter = new DirectoryFilter(filter);
474 return dir.list().stream().filter(dFilter).filter(path -> !isProject(path, filter)).map(path -> path.getName().toString())
475 .sorted().collect(Collectors.toList());
479 public Collection<String> listProjects(String repoId, String namespace, Filter<String> filter)
480 throws RepositoryStorageRuntimeException {
481 StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace);
482 if (!(dir.exists() && dir.isContainer())) {
483 return Collections.emptyList();
485 // scan all directories in the namespace, and only include those that are known to be projects
486 final Predicate<StorageAsset> dFilter = new DirectoryFilter(filter);
487 return dir.list().stream().filter(dFilter).filter(path -> isProject(path, filter)).map(path -> path.getName().toString())
488 .sorted().collect(Collectors.toList());
493 public Collection<String> listProjectVersions(String repoId, String namespace, String projectId,
494 Filter<String> filter)
495 throws RepositoryStorageRuntimeException {
496 StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace, projectId);
497 if (!(dir.exists() && dir.isContainer())) {
498 return Collections.emptyList();
501 // all directories in a project directory can be considered a version
502 return getSortedFiles(dir, filter);
506 public Collection<ArtifactMetadata> readArtifactsMetadata(ReadMetadataRequest readMetadataRequest)
507 throws RepositoryStorageRuntimeException {
508 StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(readMetadataRequest.getRepositoryId()),
509 readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(),
510 readMetadataRequest.getProjectVersion());
511 if (!(dir.exists() && dir.isContainer())) {
512 return Collections.emptyList();
515 // all files that are not metadata and not a checksum / signature are considered artifacts
516 final Predicate<StorageAsset> dFilter = new ArtifactDirectoryFilter(readMetadataRequest.getFilter());
517 // Returns a map TRUE -> (success values), FALSE -> (Exceptions)
518 Map<Boolean, List<Try<ArtifactMetadata>>> result = dir.list().stream().filter(dFilter).map(path -> {
520 return Try.success(getArtifactFromFile(readMetadataRequest.getRepositoryId(), readMetadataRequest.getNamespace(),
521 readMetadataRequest.getProjectId(), readMetadataRequest.getProjectVersion(),
523 } catch (Exception e) {
524 log.debug("Could not create metadata for {}: {}", path, e.getMessage(), e);
525 return Try.<ArtifactMetadata>failure(e);
528 ).collect(Collectors.groupingBy(Try::isSuccess));
529 if (result.containsKey(Boolean.FALSE) && result.get(Boolean.FALSE).size() > 0 && (!result.containsKey(Boolean.TRUE) || result.get(Boolean.TRUE).size() == 0)) {
530 log.error("Could not get artifact metadata. Directory: {}. Number of errors {}.", dir, result.get(Boolean.FALSE).size());
531 Try<ArtifactMetadata> failure = result.get(Boolean.FALSE).get(0);
532 log.error("Sample exception {}", failure.getError().getMessage(), failure.getError());
533 throw new RepositoryStorageRuntimeException(readMetadataRequest.getRepositoryId(), "Could not retrieve metadata of the files");
535 if (!result.containsKey(Boolean.TRUE) || result.get(Boolean.TRUE) == null) {
536 return Collections.emptyList();
538 return result.get(Boolean.TRUE).stream().map(tr -> tr.get()).collect(Collectors.toList());
544 public ArtifactMetadata readArtifactMetadataFromPath(String repoId, String path)
545 throws RepositoryStorageRuntimeException {
546 ArtifactMetadata metadata = pathTranslator.getArtifactForPath(repoId, path);
549 populateArtifactMetadataFromFile(metadata, getRepositoryBasedir(repoId).resolve(path));
550 } catch (IOException e) {
551 throw new RepositoryStorageRuntimeException(repoId, "Error during metadata retrieval of " + path + " :" + e.getMessage(), e);
557 private ArtifactMetadata getArtifactFromFile(String repoId, String namespace, String projectId,
558 String projectVersion, StorageAsset file) throws IOException {
559 ArtifactMetadata metadata =
560 pathTranslator.getArtifactFromId(repoId, namespace, projectId, projectVersion, file.getName());
562 populateArtifactMetadataFromFile(metadata, file);
568 public void applyServerSideRelocation(ManagedRepository managedRepository, ArtifactReference artifact)
569 throws ProxyDownloadException {
570 if ("pom".equals(artifact.getType())) {
574 // Build the artifact POM reference
575 ArtifactReference pomReference = new ArtifactReference();
576 pomReference.setGroupId(artifact.getGroupId());
577 pomReference.setArtifactId(artifact.getArtifactId());
578 pomReference.setVersion(artifact.getVersion());
579 pomReference.setProjectVersion( artifact.getProjectVersion() );
580 pomReference.setType("pom");
581 BaseRepositoryContentLayout layout;
584 layout = managedRepository.getContent( ).getLayout( BaseRepositoryContentLayout.class );
586 catch ( LayoutException e )
588 throw new ProxyDownloadException( "Could not set layout " + e.getMessage( ), new HashMap<>( ) );
591 RepositoryType repositoryType = managedRepository.getType();
592 if (!proxyRegistry.hasHandler(repositoryType)) {
593 throw new ProxyDownloadException("No proxy handler found for repository type " + repositoryType, new HashMap<>());
596 ItemSelector selector = ArchivaItemSelector.builder( )
597 .withNamespace( artifact.getGroupId( ) )
598 .withProjectId( artifact.getArtifactId( ) )
599 .withArtifactId( artifact.getArtifactId( ) )
600 .withVersion( artifact.getVersion( ) )
601 .withArtifactVersion( artifact.getVersion( ) )
602 .withType( "pom" ).build( );
604 Artifact pom = layout.getArtifact( selector );
606 RepositoryProxyHandler proxyHandler = proxyRegistry.getHandler(repositoryType).get(0);
608 // Get the artifact POM from proxied repositories if needed
609 proxyHandler.fetchFromProxies(managedRepository, pomReference);
611 // Open and read the POM from the managed repo
618 // MavenXpp3Reader leaves the file open, so we need to close it ourselves.
621 try (Reader reader = Channels.newReader(pom.getAsset().getReadChannel(), Charset.defaultCharset().name())) {
622 model = MAVEN_XPP_3_READER.read(reader);
625 DistributionManagement dist = model.getDistributionManagement();
627 Relocation relocation = dist.getRelocation();
628 if (relocation != null) {
629 // artifact is relocated : update the repositoryPath
630 if (relocation.getGroupId() != null) {
631 artifact.setGroupId(relocation.getGroupId());
633 if (relocation.getArtifactId() != null) {
634 artifact.setArtifactId(relocation.getArtifactId());
636 if (relocation.getVersion() != null) {
637 artifact.setVersion(relocation.getVersion());
641 } catch (IOException e) {
642 // Unable to read POM : ignore.
643 } catch (XmlPullParserException e) {
644 // Invalid POM : ignore
649 public ItemSelector applyServerSideRelocation(ManagedRepository managedRepository, ItemSelector artifactSelector)
650 throws ProxyDownloadException {
651 if ("pom".equals(artifactSelector.getType())) {
652 return artifactSelector;
655 // Build the artifact POM reference
656 BaseRepositoryContentLayout layout;
659 layout = managedRepository.getContent( ).getLayout( BaseRepositoryContentLayout.class );
661 catch ( LayoutException e )
663 throw new ProxyDownloadException( "Could not set layout " + e.getMessage( ), new HashMap<>( ) );
666 RepositoryType repositoryType = managedRepository.getType();
667 if (!proxyRegistry.hasHandler(repositoryType)) {
668 throw new ProxyDownloadException("No proxy handler found for repository type " + repositoryType, new HashMap<>());
673 ItemSelector selector = ArchivaItemSelector.builder( )
674 .withNamespace( artifactSelector.getNamespace( ) )
675 .withProjectId( artifactSelector.getArtifactId( ) )
676 .withArtifactId( artifactSelector.getArtifactId( ) )
677 .withVersion( artifactSelector.getVersion( ) )
678 .withArtifactVersion( artifactSelector.getVersion( ) )
679 .withType( "pom" ).build( );
681 Artifact pom = layout.getArtifact( selector );
683 RepositoryProxyHandler proxyHandler = proxyRegistry.getHandler(repositoryType).get(0);
685 // Get the artifact POM from proxied repositories if needed
686 proxyHandler.fetchFromProxies(managedRepository, pom);
688 // Open and read the POM from the managed repo
691 return artifactSelector;
695 // MavenXpp3Reader leaves the file open, so we need to close it ourselves.
698 try (Reader reader = Channels.newReader(pom.getAsset().getReadChannel(), Charset.defaultCharset().name())) {
699 model = MAVEN_XPP_3_READER.read(reader);
702 DistributionManagement dist = model.getDistributionManagement();
704 Relocation relocation = dist.getRelocation();
705 if (relocation != null) {
706 ArchivaItemSelector.Builder relocatedBuilder = ArchivaItemSelector.builder( );
707 // artifact is relocated : update the repositoryPath
708 if (relocation.getGroupId() != null) {
709 relocatedBuilder.withNamespace( relocation.getGroupId( ) );
711 relocatedBuilder.withNamespace( artifactSelector.getNamespace( ) );
713 if (relocation.getArtifactId() != null) {
714 relocatedBuilder.withArtifactId( relocation.getArtifactId( ) );
716 relocatedBuilder.withArtifactId( artifactSelector.getArtifactId( ) );
718 if (relocation.getVersion() != null)
720 relocatedBuilder.withVersion( relocation.getVersion( ) );
722 relocatedBuilder.withVersion( artifactSelector.getVersion( ) );
724 return relocatedBuilder.withArtifactVersion( artifactSelector.getArtifactVersion( ) )
725 .withClassifier( artifactSelector.getClassifier( ) )
726 .withType( artifactSelector.getType( ) )
727 .withProjectId( artifactSelector.getProjectId( ) )
728 .withExtension( artifactSelector.getExtension( ) )
732 } catch (IOException e) {
733 // Unable to read POM : ignore.
734 } catch (XmlPullParserException e) {
735 // Invalid POM : ignore
737 return artifactSelector;
742 public String getFilePath(String requestPath, org.apache.archiva.repository.ManagedRepository managedRepository) {
743 // managedRepository can be null
744 // extract artifact reference from url
745 // groupId:artifactId:version:packaging:classifier
746 //org/apache/archiva/archiva-checksum/1.4-M4-SNAPSHOT/archiva-checksum-1.4-M4-SNAPSHOT.jar
747 String logicalResource = null;
748 String requestPathInfo = StringUtils.defaultString(requestPath);
750 //remove prefix ie /repository/blah becomes /blah
751 requestPathInfo = removePrefix(requestPathInfo);
753 // Remove prefixing slash as the repository id doesn't contain it;
754 if (requestPathInfo.startsWith("/")) {
755 requestPathInfo = requestPathInfo.substring(1);
758 int slash = requestPathInfo.indexOf('/');
760 logicalResource = requestPathInfo.substring(slash);
762 if (logicalResource.endsWith("/..")) {
763 logicalResource += "/";
766 if (logicalResource != null && logicalResource.startsWith("//")) {
767 logicalResource = logicalResource.substring(1);
770 if (logicalResource == null) {
771 logicalResource = "/";
774 logicalResource = "/";
776 return logicalResource;
781 public String getFilePathWithVersion(final String requestPath, ManagedRepositoryContent managedRepositoryContent)
782 throws RelocationException
785 if (StringUtils.endsWith(requestPath, METADATA_FILENAME)) {
786 return getFilePath(requestPath, managedRepositoryContent.getRepository());
789 String filePath = getFilePath(requestPath, managedRepositoryContent.getRepository());
793 ContentItem item = managedRepositoryContent.toItem( filePath );
794 artifact = managedRepositoryContent.getLayout( BaseRepositoryContentLayout.class ).adaptItem( Artifact.class, item );
796 catch ( LayoutException e )
801 if (VersionUtil.isGenericSnapshot(artifact.getArtifactVersion())) {
802 // read maven metadata to get last timestamp
803 StorageAsset metadataDir = managedRepositoryContent.getRepository().getAsset(filePath).getParent();
804 if (!metadataDir.exists()) {
807 StorageAsset metadataFile = metadataDir.resolve(METADATA_FILENAME);
808 if (!metadataFile.exists()) {
811 ArchivaRepositoryMetadata archivaRepositoryMetadata = null;
814 archivaRepositoryMetadata = metadataReader.read(metadataFile);
816 catch ( RepositoryMetadataException e )
818 log.error( "Could not read metadata {}", e.getMessage( ), e );
821 int buildNumber = archivaRepositoryMetadata.getSnapshotVersion().getBuildNumber();
822 String timestamp = archivaRepositoryMetadata.getSnapshotVersion().getTimestamp();
825 if (buildNumber < 1 && timestamp == null) {
829 // org/apache/archiva/archiva-checksum/1.4-M4-SNAPSHOT/archiva-checksum-1.4-M4-SNAPSHOT.jar
830 // -> archiva-checksum-1.4-M4-20130425.081822-1.jar
832 filePath = StringUtils.replace(filePath, //
834 + "-" + artifact.getVersion().getId(), //
836 + "-" + StringUtils.remove(artifact.getArtifactVersion(),
837 "-" + VersionUtil.SNAPSHOT) //
839 + "-" + buildNumber);
841 throw new RelocationException("/repository/" + managedRepositoryContent.getRepository().getId() +
842 (StringUtils.startsWith(filePath, "/") ? "" : "/") + filePath,
843 RelocationException.RelocationType.TEMPORARY);
850 //-----------------------------
852 //-----------------------------
860 private static String removePrefix(final String href) {
861 String[] parts = StringUtils.split(href, '/');
862 parts = (String[]) ArrayUtils.subarray(parts, 1, parts.length);
863 if (parts == null || parts.length == 0) {
867 String joinedString = StringUtils.join(parts, '/');
868 if (href.endsWith("/")) {
869 joinedString = joinedString + "/";
875 private static void populateArtifactMetadataFromFile(ArtifactMetadata metadata, StorageAsset file) throws IOException {
876 metadata.setWhenGathered(ZonedDateTime.now(ZoneId.of("GMT")));
877 metadata.setFileLastModified(file.getModificationTime().toEpochMilli());
878 ChecksummedFile checksummedFile = new ChecksummedFile(file.getFilePath());
880 metadata.setMd5(checksummedFile.calculateChecksum(ChecksumAlgorithm.MD5));
881 } catch (IOException e) {
882 log.error("Unable to checksum file {}: {},MD5", file, e.getMessage());
885 metadata.setSha1(checksummedFile.calculateChecksum(ChecksumAlgorithm.SHA1));
886 } catch (IOException e) {
887 log.error("Unable to checksum file {}: {},SHA1", file, e.getMessage());
889 metadata.setSize(file.getSize());
892 private boolean isProject(StorageAsset dir, Filter<String> filter) {
893 // scan directories for a valid project version subdirectory, meaning this must be a project directory
894 final Predicate<StorageAsset> dFilter = new DirectoryFilter(filter);
895 boolean projFound = dir.list().stream().filter(dFilter)
896 .anyMatch(path -> isProjectVersion(path));
901 // if a metadata file is present, check if this is the "artifactId" directory, marking it as a project
902 ArchivaRepositoryMetadata metadata = readMetadata(dir);
903 if (metadata != null && dir.getName().toString().equals(metadata.getArtifactId())) {
910 private boolean isProjectVersion(StorageAsset dir) {
911 final String artifactId = dir.getParent().getName();
912 final String projectVersion = dir.getName();
914 // check if there is a POM artifact file to ensure it is a version directory
916 Predicate<StorageAsset> filter;
917 if (VersionUtil.isSnapshot(projectVersion)) {
918 filter = new PomFilenameFilter(artifactId, projectVersion);
920 final String pomFile = artifactId + "-" + projectVersion + ".pom";
921 filter = new PomFileFilter(pomFile);
923 if (dir.list().stream().filter(f -> !f.isContainer()).anyMatch(filter)) {
926 // if a metadata file is present, check if this is the "version" directory, marking it as a project version
927 ArchivaRepositoryMetadata metadata = readMetadata(dir);
928 if (metadata != null && projectVersion.equals(metadata.getVersion())) {
935 private ArchivaRepositoryMetadata readMetadata(StorageAsset directory) {
936 ArchivaRepositoryMetadata metadata = null;
937 StorageAsset metadataFile = directory.resolve(METADATA_FILENAME);
938 if (metadataFile.exists()) {
940 metadata = metadataReader.read(metadataFile);
941 } catch ( RepositoryMetadataException e )
943 // Ignore missing or invalid metadata
949 private static class DirectoryFilter
950 implements Predicate<StorageAsset> {
951 private final Filter<String> filter;
953 public DirectoryFilter(Filter<String> filter) {
954 this.filter = filter;
958 public boolean test(StorageAsset dir) {
959 final String name = dir.getName();
960 if (!filter.accept(name)) {
962 } else if (name.startsWith(".")) {
964 } else if (!dir.isContainer()) {
971 private static class ArtifactDirectoryFilter
972 implements Predicate<StorageAsset> {
973 private final Filter<String> filter;
975 private ArtifactDirectoryFilter(Filter<String> filter) {
976 this.filter = filter;
980 public boolean test(StorageAsset file) {
981 final Set<String> checksumExts = ChecksumAlgorithm.getAllExtensions();
982 final String path = file.getPath();
983 final String name = file.getName();
984 final String extension = StringUtils.substringAfterLast(name, ".").toLowerCase();
985 // TODO compare to logic in maven-repository-layer
986 if (file.isContainer()) {
988 } else if (!filter.accept(name)) {
990 } else if (name.startsWith(".") || path.contains("/.") ) {
992 } else if (checksumExts.contains(extension)) {
994 } else if (Arrays.binarySearch(IGNORED_FILES, name) >= 0) {
997 // some files from remote repositories can have name like maven-metadata-archiva-vm-all-public.xml
998 else if (StringUtils.startsWith(name, METADATA_FILENAME_START) && StringUtils.endsWith(name, ".xml")) {
1008 private static final class PomFilenameFilter
1009 implements Predicate<StorageAsset> {
1011 private final String artifactId, projectVersion;
1013 private PomFilenameFilter(String artifactId, String projectVersion) {
1014 this.artifactId = artifactId;
1015 this.projectVersion = projectVersion;
1019 public boolean test(StorageAsset dir) {
1020 final String name = dir.getName();
1021 if (name.startsWith(artifactId + "-") && name.endsWith(".pom")) {
1022 String v = name.substring(artifactId.length() + 1, name.length() - 4);
1023 v = VersionUtil.getBaseVersion(v);
1024 if (v.equals(projectVersion)) {
1033 private static class PomFileFilter
1034 implements Predicate<StorageAsset> {
1035 private final String pomFile;
1037 private PomFileFilter(String pomFile) {
1038 this.pomFile = pomFile;
1042 public boolean test(StorageAsset dir) {
1043 return pomFile.equals(dir.getName());
1048 public PathParser getPathParser() {
1052 public void setPathParser(PathParser pathParser) {
1053 this.pathParser = pathParser;