2 * Copyright (c) 2024 PANTHEON.tech s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.odlparent.features.test.plugin;
10 import static org.opendaylight.odlparent.features.test.plugin.DependencyUtils.FEATURES;
11 import static org.opendaylight.odlparent.features.test.plugin.DependencyUtils.KARAF_VERSION;
12 import static org.opendaylight.odlparent.features.test.plugin.DependencyUtils.PAX_EXAM_VERSION;
13 import static org.opendaylight.odlparent.features.test.plugin.DependencyUtils.RELEASE_VERSION;
14 import static org.opendaylight.odlparent.features.test.plugin.DependencyUtils.TEST;
15 import static org.opendaylight.odlparent.features.test.plugin.DependencyUtils.XML;
16 import static org.opendaylight.odlparent.features.test.plugin.DependencyUtils.extractDependencies;
17 import static org.opendaylight.odlparent.features.test.plugin.DependencyUtils.identifierOf;
18 import static org.opendaylight.odlparent.features.test.plugin.DependencyUtils.isFeature;
19 import static org.opendaylight.odlparent.features.test.plugin.DependencyUtils.toAetherArtifact;
22 import java.io.FileInputStream;
23 import java.io.IOException;
24 import java.net.MalformedURLException;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.LinkedList;
28 import java.util.List;
31 import java.util.stream.Collectors;
32 import org.apache.karaf.features.Feature;
33 import org.apache.karaf.features.internal.model.Features;
34 import org.apache.karaf.features.internal.model.JaxbUtil;
35 import org.apache.maven.plugin.MojoExecutionException;
36 import org.eclipse.aether.RepositorySystem;
37 import org.eclipse.aether.RepositorySystemSession;
38 import org.eclipse.aether.artifact.Artifact;
39 import org.eclipse.aether.artifact.DefaultArtifact;
40 import org.eclipse.aether.repository.RemoteRepository;
41 import org.eclipse.aether.resolution.ArtifactRequest;
42 import org.eclipse.aether.resolution.ArtifactResolutionException;
43 import org.eclipse.aether.resolution.ArtifactResult;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 * Maven dependencies resolver for feature artifacts. The main goal of dependencies resolution
49 * is moving artifacts to local repositories so these artifacts became available for karaf deployer
50 * when test feature is being installed.
52 final class DependencyResolver {
53 private static final Logger LOG = LoggerFactory.getLogger(DependencyResolver.class);
55 private final Map<String, Artifact> resolvedArtifacts = new HashMap<>();
56 private final Map<String, Set<String>> resolvedFeatures = new HashMap<>();
57 private final RepositorySystemSession repoSession;
58 private final List<RemoteRepository> repositories;
59 private final RepositorySystem repoSystem;
61 DependencyResolver(final RepositorySystem repoSystem, final RepositorySystemSession repoSession,
62 final List<RemoteRepository> repositories) {
63 this.repoSession = repoSession;
64 this.repositories = repositories;
65 this.repoSystem = repoSystem;
69 * Iterates over artifacts, detects features, resolves dependencies on other artifacts.
71 * @param artifacts list of maven artifacts to check
72 * @return collection of feature descriptors for features from test scope, expected to be pre-installed
73 * @throws MojoExecutionException if any dependency resolution fails
75 Set<FeatureDependency> resolveFeatures(final Collection<org.apache.maven.artifact.Artifact> artifacts)
76 throws MojoExecutionException {
77 final var featureDependencies = new LinkedList<FeatureDependency>();
78 for (var mvnArtifact : artifacts) {
79 final var artifact = toAetherArtifact(mvnArtifact);
80 if (isFeature(artifact)) {
81 final var resolved = resolve(artifact);
82 final var featureNames = resolveFeatureFile(resolved.getFile());
83 if (TEST.equals(mvnArtifact.getScope())) {
84 featureDependencies.add(new FeatureDependency(resolved, featureNames));
88 return Set.copyOf(featureDependencies);
92 * Resolves dependencies on features required for proper plugin functionality.
94 * @return collection of feature descriptors for the features expected to be pre-installed
95 * @throws MojoExecutionException if any dependency resolution fails
97 Set<FeatureDependency> resolvePluginFeatures() throws MojoExecutionException {
99 // pax-exam features, installed by karaf container, no need explicit installation
100 final var paxExamFeaturesResolved = resolve(
101 new DefaultArtifact("org.ops4j.pax.exam", "pax-exam-features", null, XML, PAX_EXAM_VERSION));
102 resolveFeatureFile(paxExamFeaturesResolved.getFile());
105 final var karafFeatureResolved = resolve(
106 new DefaultArtifact("org.apache.karaf.features", "standard", FEATURES, XML, KARAF_VERSION));
107 resolveFeatureFile(karafFeatureResolved.getFile());
109 return Set.of(new FeatureDependency(karafFeatureResolved, Set.of("scr")));
112 String resolveKarafDistroUrl(final String groupId, final String artifactId, final String type)
113 throws MojoExecutionException {
114 final var resolved = resolve(new DefaultArtifact(groupId, artifactId, null, type, RELEASE_VERSION));
115 if (!resolved.getFile().exists()) {
116 throw new MojoExecutionException("No file for karaf distribution resolved " + resolved);
119 return resolved.getFile().toURI().toURL().toString();
120 } catch (MalformedURLException e) {
121 throw new MojoExecutionException("Could not get karaf distribution URL", e);
126 * Extracts features dependencies from feature file and resolves dependencies on maven artifacts including
129 * @param featureFile the xml file describing features
130 * @return Collection of feature names extracted from the file
131 * @throws MojoExecutionException if file cannot be parsed, or any dependency resolved
133 Set<String> resolveFeatureFile(final File featureFile) throws MojoExecutionException {
134 final var identifier = featureFile.getAbsolutePath();
135 LOG.debug("Resolving dependencies for feature file: {}", identifier);
136 final var cached = resolvedFeatures.get(identifier);
137 if (cached != null) {
140 if (!featureFile.exists()) {
141 LOG.debug("Feature file {} does not exist. Dependency resolution omitted.", identifier);
144 final Features features;
145 try (var inputStream = new FileInputStream(featureFile)) {
146 features = JaxbUtil.unmarshal(featureFile.toURI().toString(), inputStream, false);
147 } catch (IOException e) {
148 throw new MojoExecutionException("Could not read feature file " + featureFile, e);
150 for (var unresolved : extractDependencies(features)) {
151 final var resolved = resolve(unresolved);
152 if (isFeature(resolved)) {
153 resolveFeatureFile(resolved.getFile());
156 final var featureNames = features.getFeature().stream().map(Feature::getName).collect(Collectors.toSet());
157 resolvedFeatures.put(identifier, featureNames);
161 private Artifact resolve(final Artifact unresolved) throws MojoExecutionException {
162 final var identifier = identifierOf(unresolved);
163 final var cached = resolvedArtifacts.get(identifier);
164 if (cached != null) {
167 final var request = new ArtifactRequest().setRepositories(repositories).setArtifact(unresolved);
168 final ArtifactResult resolutionResult;
170 resolutionResult = repoSystem.resolveArtifact(repoSession, request);
171 } catch (ArtifactResolutionException e) {
172 throw new MojoExecutionException("Could not resolve artifact " + identifier, e);
174 final var resolved = resolutionResult.getArtifact();
175 LOG.debug("Dependency resolved for {}", identifier);
176 final var file = resolved.getFile();
177 if (file == null || !file.exists()) {
178 LOG.warn("Dependency artifact {} is resolved but has no attached file.", identifier);
180 resolvedArtifacts.put(identifier, resolved);