81c7a2aba7c919b931c0c1c290d3ae12e06964c9
[odlparent.git] / karaf-plugin / src / main / java / org / opendaylight / odlparent / FeatureUtil.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.odlparent;
10
11 import java.io.File;
12 import java.io.FileInputStream;
13 import java.io.FileNotFoundException;
14 import java.net.MalformedURLException;
15 import java.net.URL;
16 import java.nio.file.Path;
17 import java.util.ArrayList;
18 import java.util.LinkedHashSet;
19 import java.util.List;
20 import java.util.Set;
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;
35
36 public final class FeatureUtil {
37     private static final Logger LOG = LoggerFactory.getLogger(FeatureUtil.class);
38
39     private static final Pattern MVN_PATTERN = Pattern.compile("mvn:", Pattern.LITERAL);
40     private static final Pattern WRAP_PATTERN = Pattern.compile("wrap:", Pattern.LITERAL);
41
42     @RegEx
43     private static final String VERSION_STRIP_PATTERN_STR = "\\$.*$";
44     private static final Pattern VERSION_STRIP_PATTERN = Pattern.compile(VERSION_STRIP_PATTERN_STR);
45
46     private final AetherUtil aetherUtil;
47     private final File localRepo;
48
49     public FeatureUtil(AetherUtil aetherUtil, File localRepo) {
50         this.aetherUtil = aetherUtil;
51         this.localRepo = localRepo;
52     }
53
54     /**
55      * Converts the given list of URLs to artifact coordinates.
56      *
57      * @param urls The URLs.
58      * @return The corresponding artifact coordinates.
59      * @throws MalformedURLException if a URL is malformed.
60      */
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));
65         }
66         LOG.trace("toCoord({}) returns {}", urls, result);
67         return result;
68     }
69
70     /**
71      * Converts the given URL to artifact coordinates.
72      *
73      * @param url The URL.
74      * @return The corresponding artifact coordinates.
75      * @throws MalformedURLException if the URL is malformed.
76      */
77     public static String toCoord(final URL url) throws MalformedURLException {
78         String repository = url.toString();
79         String unwrappedRepo = WRAP_PATTERN.matcher(repository).replaceFirst("");
80
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";
86         }
87
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();
92         }
93         if (parser.getClassifier() != null) {
94             coord = coord + ":" + parser.getClassifier();
95         }
96         coord = coord + ":" + VERSION_STRIP_PATTERN.matcher(parser.getVersion()).replaceAll("");
97         LOG.trace("toCoord({}) returns {}", url, coord);
98         return coord;
99     }
100
101     /**
102      * Parses the given repository as URLs and converts them to artifact coordinates.
103      *
104      * @param repository The repository (list of URLs).
105      * @return The corresponding artifact coordinates.
106      * @throws MalformedURLException if a URL is malformed.
107      */
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)));
112         }
113         LOG.trace("mvnUrlsToCoord({}) returns {}", repository, result);
114         return result;
115     }
116
117     /**
118      * Converts the given features' repository to artifact coordinates.
119      *
120      * @param features The features.
121      * @return The corresponding artifact coordinates.
122      * @throws MalformedURLException if a URL is malformed.
123      */
124     public static Set<String> featuresRepositoryToCoords(final Features features) throws MalformedURLException {
125         return mvnUrlsToCoord(features.getRepository());
126     }
127
128     /**
129      * Converts all the given features' repositories to artifact coordinates.
130      *
131      * @param features The features.
132      * @return The corresponding artifact coordinates.
133      * @throws MalformedURLException if a URL is malformed.
134      */
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));
139         }
140         LOG.trace("featuresRepositoryToCoords({}) returns {}", features, result);
141         return result;
142     }
143
144     /**
145      * Lists the artifact coordinates of the given feature's bundles and configuration files.
146      *
147      * @param feature The feature.
148      * @return The corresponding coordinates.
149      * @throws MalformedURLException if a URL is malformed.
150      */
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()));
155         }
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())));
161                     }
162                 }
163             }
164             // TODO Dependencies
165         }
166         if (feature.getConfigfile() != null) {
167             result.addAll(configFilesToCoords(feature.getConfigfile()));
168         }
169         LOG.trace("featureToCoords({}) returns {}", feature.getName(), result);
170         return result;
171     }
172
173     /**
174      * Lists the artifact coordinates of the given configuration files.
175      *
176      * @param configfiles The configuration files.
177      * @return The corresponding coordinates.
178      * @throws MalformedURLException if a URL is malformed.
179      */
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())));
184         }
185         LOG.trace("configFilesToCoords({}) returns {}", configfiles, result);
186         return result;
187     }
188
189     /**
190      * Lists the artifact coordinates of the given bundles.
191      *
192      * @param bundles The bundles.
193      * @return The corresponding coordinates.
194      * @throws MalformedURLException if a URL is malformed.
195      */
196     public static Set<String> bundlesToCoords(final List<Bundle> bundles) throws MalformedURLException {
197         Set<String> result = new LinkedHashSet<>();
198         for (Bundle bundle : bundles) {
199             try {
200                 result.add(toCoord(new URL(bundle.getLocation())));
201             } catch (MalformedURLException e) {
202                 LOG.error("Invalid URL {}", bundle.getLocation(), e);
203                 throw e;
204             }
205         }
206         LOG.trace("bundlesToCoords({}) returns {}", bundles, result);
207         return result;
208     }
209
210     /**
211      * Extracts all the artifact coordinates for the given features (repositories, bundles, configuration files).
212      *
213      * @param features The feature.
214      * @return The artifact coordinates.
215      * @throws MojoExecutionException if an error occurs during processing.
216      */
217     public static Set<String> featuresToCoords(final Features features) throws MojoExecutionException {
218         Set<String> result = new LinkedHashSet<>();
219         if (features.getRepository() != null) {
220             try {
221                 result.addAll(featuresRepositoryToCoords(features));
222             } catch (MalformedURLException e) {
223                 throw new MojoExecutionException("Feature " + features.getName() + " has an invalid repository URL", e);
224             }
225         }
226         if (features.getFeature() != null) {
227             for (Feature feature : features.getFeature()) {
228                 try {
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);
233                 }
234             }
235         }
236         LOG.trace("featuresToCoords({}) returns {}", features.getName(), result);
237         return result;
238     }
239
240     /**
241      * Extracts all the artifact coordinates for the given set of features (repositories, bundles, configuration
242      * files).
243      *
244      * @param features The features.
245      * @return The artifact coordinates.
246      * @throws MojoExecutionException if an error occurs during processing.
247      */
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));
252         }
253         LOG.trace("featuresToCoords({}) returns {}", features, result);
254         return result;
255     }
256
257     /**
258      * Unmarshal all the features in the given artifacts.
259      *
260      * @param featureArtifacts The artifacts.
261      * @return The features.
262      * @throws FileNotFoundException if a file is missing.
263      */
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));
268         }
269         LOG.trace("readFeatures({}) returns {}", featureArtifacts, result);
270         return result;
271     }
272
273     /**
274      * Unmarshal the features in the given artifact.
275      *
276      * @param artifact The artifact.
277      * @return The features.
278      * @throws FileNotFoundException if a file is missing.
279      */
280     public Features readFeature(final Artifact artifact) throws FileNotFoundException {
281         return readFeature(artifact.getFile());
282     }
283
284     /**
285      * Unmarshal the features in the given file.
286      *
287      * @param file The file.
288      * @return The features.
289      * @throws FileNotFoundException if a file is missing.
290      */
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());
296         return result;
297     }
298
299     /**
300      * Unmarshal the features matching the given artifact coordinates.
301      *
302      * @param coords The artifact coordinates.
303      * @return The features.
304      * @throws FileNotFoundException if a file is missing.
305      */
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());
310         return result;
311     }
312
313     /**
314      * Unmarshals all the features starting from the given feature.
315      *
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.
321      */
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);
333                 result.add(feature);
334                 LOG.debug("findAllFeaturesRecursively() added {}", coord);
335                 result.addAll(findAllFeaturesRecursively(feature, existingCoords));
336             } else {
337                 LOG.trace("findAllFeaturesRecursively() skips known {}", coord);
338             }
339         }
340         return result;
341     }
342
343     /**
344      * Unmarshals all the features starting from the given features.
345      *
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.
351      */
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));
357         }
358         return result;
359     }
360
361     /**
362      * Unmarshals all the features (including known ones) starting from the given features.
363      *
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.
368      */
369     public Set<Features> findAllFeaturesRecursively(final Set<Features> features)
370             throws MalformedURLException, FileNotFoundException {
371         return findAllFeaturesRecursively(features, new LinkedHashSet<>());
372     }
373
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()) {
380                 return candidate;
381             }
382             parent = parent.getParent();
383         }
384         return null;
385     }
386 }