2 * Copyright (c) 2015 Cisco Systems, Inc. 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
9 package org.opendaylight.odlparent;
12 import java.io.FileInputStream;
13 import java.io.FileNotFoundException;
14 import java.net.MalformedURLException;
16 import java.nio.file.Path;
17 import java.util.ArrayList;
18 import java.util.LinkedHashSet;
19 import java.util.List;
21 import java.util.regex.Pattern;
22 import javax.annotation.RegEx;
23 import org.apache.karaf.features.BundleInfo;
24 import org.apache.karaf.features.Conditional;
25 import org.apache.karaf.features.internal.model.Bundle;
26 import org.apache.karaf.features.internal.model.ConfigFile;
27 import org.apache.karaf.features.internal.model.Feature;
28 import org.apache.karaf.features.internal.model.Features;
29 import org.apache.karaf.features.internal.model.JaxbUtil;
30 import org.apache.maven.plugin.MojoExecutionException;
31 import org.eclipse.aether.artifact.Artifact;
32 import org.ops4j.pax.url.mvn.internal.Parser;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
36 public final class FeatureUtil {
37 private static final Logger LOG = LoggerFactory.getLogger(FeatureUtil.class);
39 private static final Pattern MVN_PATTERN = Pattern.compile("mvn:", Pattern.LITERAL);
40 private static final Pattern WRAP_PATTERN = Pattern.compile("wrap:", Pattern.LITERAL);
43 private static final String VERSION_STRIP_PATTERN_STR = "\\$.*$";
44 private static final Pattern VERSION_STRIP_PATTERN = Pattern.compile(VERSION_STRIP_PATTERN_STR);
46 private final AetherUtil aetherUtil;
47 private final File localRepo;
49 public FeatureUtil(AetherUtil aetherUtil, File localRepo) {
50 this.aetherUtil = aetherUtil;
51 this.localRepo = localRepo;
55 * Converts the given list of URLs to artifact coordinates.
57 * @param urls The URLs.
58 * @return The corresponding artifact coordinates.
59 * @throws MalformedURLException if a URL is malformed.
61 public static List<String> toCoord(final List<URL> urls) throws MalformedURLException {
62 List<String> result = new ArrayList<>();
63 for (URL url : urls) {
64 result.add(toCoord(url));
66 LOG.trace("toCoord({}) returns {}", urls, result);
71 * Converts the given URL to artifact coordinates.
74 * @return The corresponding artifact coordinates.
75 * @throws MalformedURLException if the URL is malformed.
77 public static String toCoord(final URL url) throws MalformedURLException {
78 String repository = url.toString();
79 String unwrappedRepo = WRAP_PATTERN.matcher(repository).replaceFirst("");
81 // FIXME: this is a hack to deal with broken upstream repo and should be removed once karaf moves
82 // to hibernate-validator-osgi-features-5.4.2+
83 if ("mvn:com.thoughtworks.paranamer:paranamer:2.8".equals(unwrappedRepo)) {
84 LOG.info("Working around broken hibernate-validator-osgi-karaf-features...");
85 unwrappedRepo = "mvn:com.thoughtworks.paranamer/paranamer/2.8";
88 Parser parser = new Parser(unwrappedRepo);
89 String coord = MVN_PATTERN.matcher(parser.getGroup()).replaceFirst("") + ":" + parser.getArtifact();
90 if (parser.getType() != null) {
91 coord = coord + ":" + parser.getType();
93 if (parser.getClassifier() != null) {
94 coord = coord + ":" + parser.getClassifier();
96 coord = coord + ":" + VERSION_STRIP_PATTERN.matcher(parser.getVersion()).replaceAll("");
97 LOG.trace("toCoord({}) returns {}", url, coord);
102 * Parses the given repository as URLs and converts them to artifact coordinates.
104 * @param repository The repository (list of URLs).
105 * @return The corresponding artifact coordinates.
106 * @throws MalformedURLException if a URL is malformed.
108 public static Set<String> mvnUrlsToCoord(final List<String> repository) throws MalformedURLException {
109 Set<String> result = new LinkedHashSet<>();
110 for (String url : repository) {
111 result.add(toCoord(new URL(url)));
113 LOG.trace("mvnUrlsToCoord({}) returns {}", repository, result);
118 * Converts the given features' repository to artifact coordinates.
120 * @param features The features.
121 * @return The corresponding artifact coordinates.
122 * @throws MalformedURLException if a URL is malformed.
124 public static Set<String> featuresRepositoryToCoords(final Features features) throws MalformedURLException {
125 return mvnUrlsToCoord(features.getRepository());
129 * Converts all the given features' repositories to artifact coordinates.
131 * @param features The features.
132 * @return The corresponding artifact coordinates.
133 * @throws MalformedURLException if a URL is malformed.
135 public static Set<String> featuresRepositoryToCoords(final Set<Features> features) throws MalformedURLException {
136 Set<String> result = new LinkedHashSet<>();
137 for (Features feature : features) {
138 result.addAll(featuresRepositoryToCoords(feature));
140 LOG.trace("featuresRepositoryToCoords({}) returns {}", features, result);
145 * Lists the artifact coordinates of the given feature's bundles and configuration files.
147 * @param feature The feature.
148 * @return The corresponding coordinates.
149 * @throws MalformedURLException if a URL is malformed.
151 public static Set<String> featureToCoords(final Feature feature) throws MalformedURLException {
152 Set<String> result = new LinkedHashSet<>();
153 if (feature.getBundle() != null) {
154 result.addAll(bundlesToCoords(feature.getBundle()));
156 if (feature.getConditional() != null) {
157 for (Conditional conditional : feature.getConditional()) {
158 if (conditional.getBundles() != null) {
159 for (BundleInfo bundleInfo : conditional.getBundles()) {
160 result.add(toCoord(new URL(bundleInfo.getLocation())));
166 if (feature.getConfigfile() != null) {
167 result.addAll(configFilesToCoords(feature.getConfigfile()));
169 LOG.trace("featureToCoords({}) returns {}", feature.getName(), result);
174 * Lists the artifact coordinates of the given configuration files.
176 * @param configfiles The configuration files.
177 * @return The corresponding coordinates.
178 * @throws MalformedURLException if a URL is malformed.
180 public static Set<String> configFilesToCoords(final List<ConfigFile> configfiles) throws MalformedURLException {
181 Set<String> result = new LinkedHashSet<>();
182 for (ConfigFile configFile : configfiles) {
183 result.add(toCoord(new URL(configFile.getLocation())));
185 LOG.trace("configFilesToCoords({}) returns {}", configfiles, result);
190 * Lists the artifact coordinates of the given bundles.
192 * @param bundles The bundles.
193 * @return The corresponding coordinates.
194 * @throws MalformedURLException if a URL is malformed.
196 public static Set<String> bundlesToCoords(final List<Bundle> bundles) throws MalformedURLException {
197 Set<String> result = new LinkedHashSet<>();
198 for (Bundle bundle : bundles) {
200 result.add(toCoord(new URL(bundle.getLocation())));
201 } catch (MalformedURLException e) {
202 LOG.error("Invalid URL {}", bundle.getLocation(), e);
206 LOG.trace("bundlesToCoords({}) returns {}", bundles, result);
211 * Extracts all the artifact coordinates for the given features (repositories, bundles, configuration files).
213 * @param features The feature.
214 * @return The artifact coordinates.
215 * @throws MojoExecutionException if an error occurs during processing.
217 public static Set<String> featuresToCoords(final Features features) throws MojoExecutionException {
218 Set<String> result = new LinkedHashSet<>();
219 if (features.getRepository() != null) {
221 result.addAll(featuresRepositoryToCoords(features));
222 } catch (MalformedURLException e) {
223 throw new MojoExecutionException("Feature " + features.getName() + " has an invalid repository URL", e);
226 if (features.getFeature() != null) {
227 for (Feature feature : features.getFeature()) {
229 result.addAll(featureToCoords(feature));
230 } catch (MalformedURLException e) {
231 throw new MojoExecutionException("Feature " + feature.getName() + " in " + features.getName()
232 + " contains an invalid or missing URL", e);
236 LOG.trace("featuresToCoords({}) returns {}", features.getName(), result);
241 * Extracts all the artifact coordinates for the given set of features (repositories, bundles, configuration
244 * @param features The features.
245 * @return The artifact coordinates.
246 * @throws MojoExecutionException if an error occurs during processing.
248 public static Set<String> featuresToCoords(final Set<Features> features) throws MojoExecutionException {
249 Set<String> result = new LinkedHashSet<>();
250 for (Features feature : features) {
251 result.addAll(featuresToCoords(feature));
253 LOG.trace("featuresToCoords({}) returns {}", features, result);
258 * Unmarshal all the features in the given artifacts.
260 * @param featureArtifacts The artifacts.
261 * @return The features.
262 * @throws FileNotFoundException if a file is missing.
264 public Set<Features> readFeatures(final Set<Artifact> featureArtifacts) throws FileNotFoundException {
265 Set<Features> result = new LinkedHashSet<>();
266 for (Artifact artifact : featureArtifacts) {
267 result.add(readFeature(artifact));
269 LOG.trace("readFeatures({}) returns {}", featureArtifacts, result);
274 * Unmarshal the features in the given artifact.
276 * @param artifact The artifact.
277 * @return The features.
278 * @throws FileNotFoundException if a file is missing.
280 public Features readFeature(final Artifact artifact) throws FileNotFoundException {
281 return readFeature(artifact.getFile());
285 * Unmarshal the features in the given file.
287 * @param file The file.
288 * @return The features.
289 * @throws FileNotFoundException if a file is missing.
291 public Features readFeature(final File file) throws FileNotFoundException {
292 File localFile = getFileInLocalRepo(file);
293 FileInputStream stream = new FileInputStream(localFile != null ? localFile : file);
294 Features result = JaxbUtil.unmarshal(file.toURI().toString(), stream, false);
295 LOG.trace("readFeature({}) returns {} without resolving first", file, result.getName());
300 * Unmarshal the features matching the given artifact coordinates.
302 * @param coords The artifact coordinates.
303 * @return The features.
304 * @throws FileNotFoundException if a file is missing.
306 public Features readFeature(final String coords) throws FileNotFoundException {
307 Artifact artifact = aetherUtil.resolveArtifact(coords);
308 Features result = readFeature(artifact);
309 LOG.trace("readFeature({}) returns {} after resolving first", coords, result.getName());
314 * Unmarshals all the features starting from the given feature.
316 * @param features The starting features.
317 * @param existingCoords The artifact coordinates which have already been unmarshalled.
318 * @return The features.
319 * @throws MalformedURLException if a URL is malformed.
320 * @throws FileNotFoundException if a file is missing.
322 public Set<Features> findAllFeaturesRecursively(final Features features, final Set<String> existingCoords)
323 throws MalformedURLException, FileNotFoundException {
324 LOG.debug("findAllFeaturesRecursively({}) starts", features.getName());
325 LOG.trace("findAllFeaturesRecursively knows about these coords: {}", existingCoords);
326 Set<Features> result = new LinkedHashSet<>();
327 Set<String> coords = FeatureUtil.featuresRepositoryToCoords(features);
328 for (String coord : coords) {
329 if (!existingCoords.contains(coord)) {
330 LOG.trace("findAllFeaturesRecursively() going to add {}", coord);
331 existingCoords.add(coord);
332 Features feature = readFeature(coord);
334 LOG.debug("findAllFeaturesRecursively() added {}", coord);
335 result.addAll(findAllFeaturesRecursively(feature, existingCoords));
337 LOG.trace("findAllFeaturesRecursively() skips known {}", coord);
344 * Unmarshals all the features starting from the given features.
346 * @param features The starting features.
347 * @param existingCoords The artifact coordinates which have already been unmarshalled.
348 * @return The features.
349 * @throws MalformedURLException if a URL is malformed.
350 * @throws FileNotFoundException if a file is missing.
352 public Set<Features> findAllFeaturesRecursively(final Set<Features> features, final Set<String> existingCoords)
353 throws MalformedURLException, FileNotFoundException {
354 Set<Features> result = new LinkedHashSet<>();
355 for (Features feature : features) {
356 result.addAll(findAllFeaturesRecursively(feature, existingCoords));
362 * Unmarshals all the features (including known ones) starting from the given features.
364 * @param features The starting features.
365 * @return The features.
366 * @throws MalformedURLException if a URL is malformed.
367 * @throws FileNotFoundException if a file is missing.
369 public Set<Features> findAllFeaturesRecursively(final Set<Features> features)
370 throws MalformedURLException, FileNotFoundException {
371 return findAllFeaturesRecursively(features, new LinkedHashSet<>());
374 private File getFileInLocalRepo(File file) {
375 Path filePath = file.toPath();
376 Path parent = filePath.getParent();
377 while (parent != null) {
378 File candidate = new File(localRepo, parent.relativize(filePath).toString());
379 if (candidate.exists()) {
382 parent = parent.getParent();