Workaround broken upstream features
[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.util.ArrayList;
17 import java.util.LinkedHashSet;
18 import java.util.List;
19 import java.util.Set;
20 import java.util.regex.Pattern;
21 import javax.annotation.RegEx;
22 import org.apache.karaf.features.internal.model.Bundle;
23 import org.apache.karaf.features.internal.model.ConfigFile;
24 import org.apache.karaf.features.internal.model.Feature;
25 import org.apache.karaf.features.internal.model.Features;
26 import org.apache.karaf.features.internal.model.JaxbUtil;
27 import org.eclipse.aether.artifact.Artifact;
28 import org.eclipse.aether.resolution.ArtifactResolutionException;
29 import org.ops4j.pax.url.mvn.internal.Parser;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 public final class FeatureUtil {
34     private static final Logger LOG = LoggerFactory.getLogger(FeatureUtil.class);
35
36     private static final Pattern MVN_PATTERN = Pattern.compile("mvn:", Pattern.LITERAL);
37     private static final Pattern WRAP_PATTERN = Pattern.compile("wrap:", Pattern.LITERAL);
38
39     @RegEx
40     private static final String VERSION_STRIP_PATTERN_STR = "\\$.*$";
41     private static final Pattern VERSION_STRIP_PATTERN = Pattern.compile(VERSION_STRIP_PATTERN_STR);
42
43     private FeatureUtil() {
44         throw new UnsupportedOperationException();
45     }
46
47     /**
48      * Converts the given list of URLs to artifact coordinates.
49      *
50      * @param urls The URLs.
51      * @return The corresponding artifact coordinates.
52      * @throws MalformedURLException if a URL is malformed.
53      */
54     public static List<String> toCoord(final List<URL> urls) throws MalformedURLException {
55         List<String> result = new ArrayList<>();
56         for (URL url : urls) {
57             result.add(toCoord(url));
58         }
59         LOG.trace("toCoord({}) returns {}", urls, result);
60         return result;
61     }
62
63     /**
64      * Converts the given URL to artifact coordinates.
65      *
66      * @param url The URL.
67      * @return The corresponding artifact coordinates.
68      * @throws MalformedURLException if the URL is malformed.
69      */
70     public static String toCoord(final URL url) throws MalformedURLException {
71         String repository = url.toString();
72         String unwrappedRepo = WRAP_PATTERN.matcher(repository).replaceFirst("");
73
74         // FIXME: this is a hack to deal with broken upstream repo and should be removed once karaf moves
75         //        to hibernate-validator-osgi-features-5.4.2+
76         if ("mvn:com.thoughtworks.paranamer:paranamer:2.8".equals(unwrappedRepo)) {
77             LOG.info("Working around broken hibernate-validator-osgi-karaf-features...");
78             unwrappedRepo = "mvn:com.thoughtworks.paranamer/paranamer/2.8";
79         }
80
81         Parser parser = new Parser(unwrappedRepo);
82         String coord = MVN_PATTERN.matcher(parser.getGroup()).replaceFirst("") + ":" + parser.getArtifact();
83         if (parser.getType() != null) {
84             coord = coord + ":" + parser.getType();
85         }
86         if (parser.getClassifier() != null) {
87             coord = coord + ":" + parser.getClassifier();
88         }
89         coord = coord + ":" + VERSION_STRIP_PATTERN.matcher(parser.getVersion()).replaceAll("");
90         LOG.trace("toCoord({}) returns {}", url, coord);
91         return coord;
92     }
93
94     /**
95      * Parses the given repository as URLs and converts them to artifact coordinates.
96      *
97      * @param repository The repository (list of URLs).
98      * @return The corresponding artifact coordinates.
99      * @throws MalformedURLException if a URL is malformed.
100      */
101     public static Set<String> mvnUrlsToCoord(final List<String> repository) throws MalformedURLException {
102         Set<String> result = new LinkedHashSet<>();
103         for (String url : repository) {
104             result.add(toCoord(new URL(url)));
105         }
106         LOG.trace("mvnUrlsToCoord({}) returns {}", repository, result);
107         return result;
108     }
109
110     /**
111      * Converts the given features' repository to artifact coordinates.
112      *
113      * @param features The features.
114      * @return The corresponding artifact coordinates.
115      * @throws MalformedURLException if a URL is malformed.
116      */
117     public static Set<String> featuresRepositoryToCoords(final Features features) throws MalformedURLException {
118         return mvnUrlsToCoord(features.getRepository());
119     }
120
121     /**
122      * Converts all the given features' repositories to artifact coordinates.
123      *
124      * @param features The features.
125      * @return The corresponding artifact coordinates.
126      * @throws MalformedURLException if a URL is malformed.
127      */
128     public static Set<String> featuresRepositoryToCoords(final Set<Features> features) throws MalformedURLException {
129         Set<String> result = new LinkedHashSet<>();
130         for (Features feature : features) {
131             result.addAll(featuresRepositoryToCoords(feature));
132         }
133         LOG.trace("featuresRepositoryToCoords({}) returns {}", features, result);
134         return result;
135     }
136
137     /**
138      * Lists the artifact coordinates of the given feature's bundles and configuration files.
139      *
140      * @param feature The feature.
141      * @return The corresponding coordinates.
142      * @throws MalformedURLException if a URL is malformed.
143      */
144     public static Set<String> featureToCoords(final Feature feature) throws MalformedURLException {
145         Set<String> result = new LinkedHashSet<>();
146         if (feature.getBundle() != null) {
147             result.addAll(bundlesToCoords(feature.getBundle()));
148         }
149         if (feature.getConfigfile() != null) {
150             result.addAll(configFilesToCoords(feature.getConfigfile()));
151         }
152         LOG.trace("featureToCoords({}) returns {}", feature.getName(), result);
153         return result;
154     }
155
156     /**
157      * Lists the artifact coordinates of the given configuration files.
158      *
159      * @param configfiles The configuration files.
160      * @return The corresponding coordinates.
161      * @throws MalformedURLException if a URL is malformed.
162      */
163     public static Set<String> configFilesToCoords(final List<ConfigFile> configfiles) throws MalformedURLException {
164         Set<String> result = new LinkedHashSet<>();
165         for (ConfigFile configFile : configfiles) {
166             result.add(toCoord(new URL(configFile.getLocation())));
167         }
168         LOG.trace("configFilesToCoords({}) returns {}", configfiles, result);
169         return result;
170     }
171
172     /**
173      * Lists the artifact coordinates of the given bundles.
174      *
175      * @param bundles The bundles.
176      * @return The corresponding coordinates.
177      * @throws MalformedURLException if a URL is malformed.
178      */
179     public static Set<String> bundlesToCoords(final List<Bundle> bundles) throws MalformedURLException {
180         Set<String> result = new LinkedHashSet<>();
181         for (Bundle bundle : bundles) {
182             result.add(toCoord(new URL(bundle.getLocation())));
183         }
184         LOG.trace("bundlesToCoords({}) returns {}", bundles, result);
185         return result;
186     }
187
188     /**
189      * Extracts all the artifact coordinates for the given features (repositories, bundles, configuration files).
190      *
191      * @param features The feature.
192      * @return The artifact coordinates.
193      * @throws MalformedURLException if a URL is malformed.
194      */
195     public static Set<String> featuresToCoords(final Features features) throws MalformedURLException {
196         Set<String> result = new LinkedHashSet<>();
197         if (features.getRepository() != null) {
198             result.addAll(featuresRepositoryToCoords(features));
199         }
200         if (features.getFeature() != null) {
201             for (Feature feature : features.getFeature()) {
202                 result.addAll(featureToCoords(feature));
203             }
204         }
205         LOG.trace("featuresToCoords({}) returns {}", features.getName(), result);
206         return result;
207     }
208
209     /**
210      * Extracts all the artifact coordinates for the given set of features (repositories, bundles, configuration
211      * files).
212      *
213      * @param features The features.
214      * @return The artifact coordinates.
215      * @throws MalformedURLException if a URL is malformed.
216      */
217     public static Set<String> featuresToCoords(final Set<Features> features) throws MalformedURLException {
218         Set<String> result = new LinkedHashSet<>();
219         for (Features feature : features) {
220             result.addAll(featuresToCoords(feature));
221         }
222         LOG.trace("featuresToCoords({}) returns {}", features, result);
223         return result;
224     }
225
226     /**
227      * Unmarshal all the features in the given artifacts.
228      *
229      * @param featureArtifacts The artifacts.
230      * @return The features.
231      * @throws FileNotFoundException if a file is missing.
232      */
233     public static Set<Features> readFeatures(final Set<Artifact> featureArtifacts) throws FileNotFoundException {
234         Set<Features> result = new LinkedHashSet<>();
235         for (Artifact artifact : featureArtifacts) {
236             result.add(readFeature(artifact));
237         }
238         LOG.trace("readFeatures({}) returns {}", featureArtifacts, result);
239         return result;
240     }
241
242     /**
243      * Unmarshal the features in the given artifact.
244      *
245      * @param artifact The artifact.
246      * @return The features.
247      * @throws FileNotFoundException if a file is missing.
248      */
249     public static Features readFeature(final Artifact artifact) throws FileNotFoundException {
250         return readFeature(artifact.getFile());
251     }
252
253     /**
254      * Unmarshal the features in the given file.
255      *
256      * @param file The file.
257      * @return The features.
258      * @throws FileNotFoundException if a file is missing.
259      */
260     public static Features readFeature(final File file) throws FileNotFoundException {
261         FileInputStream stream = new FileInputStream(file);
262         Features result = JaxbUtil.unmarshal(file.toURI().toString(), stream, false);
263         LOG.trace("readFeature({}) returns {} without resolving first", file, result.getName());
264         return result;
265     }
266
267     /**
268      * Unmarshal the features matching the given artifact coordinates.
269      *
270      * @param aetherUtil The Aether resolver.
271      * @param coords The artifact coordinates.
272      * @return The features.
273      * @throws ArtifactResolutionException if the coordinates can't be resolved.
274      * @throws FileNotFoundException if a file is missing.
275      */
276     public static Features readFeature(final AetherUtil aetherUtil, final String coords)
277             throws ArtifactResolutionException, FileNotFoundException {
278         Artifact artifact = aetherUtil.resolveArtifact(coords);
279         Features result = readFeature(artifact);
280         LOG.trace("readFeature({}) returns {} after resolving first", coords, result.getName());
281         return result;
282     }
283
284     /**
285      * Unmarshals all the features starting from the given feature.
286      *
287      * @param aetherUtil The Aether resolver.
288      * @param features The starting features.
289      * @param existingCoords The artifact coordinates which have already been unmarshalled.
290      * @return The features.
291      * @throws MalformedURLException if a URL is malformed.
292      * @throws FileNotFoundException if a file is missing.
293      * @throws ArtifactResolutionException if artifact coordinates can't be resolved.
294      */
295     public static Set<Features> findAllFeaturesRecursively(
296             final AetherUtil aetherUtil, final Features features, final Set<String> existingCoords)
297             throws MalformedURLException, FileNotFoundException, ArtifactResolutionException {
298         LOG.debug("findAllFeaturesRecursively({}) starts", features.getName());
299         LOG.trace("findAllFeaturesRecursively knows about these coords: {}", existingCoords);
300         Set<Features> result = new LinkedHashSet<>();
301         Set<String> coords = FeatureUtil.featuresRepositoryToCoords(features);
302         for (String coord : coords) {
303             if (!existingCoords.contains(coord)) {
304                 LOG.trace("findAllFeaturesRecursively() going to add {}", coord);
305                 existingCoords.add(coord);
306                 Features feature = FeatureUtil.readFeature(aetherUtil, coord);
307                 result.add(feature);
308                 LOG.debug("findAllFeaturesRecursively() added {}", coord);
309                 result.addAll(findAllFeaturesRecursively(aetherUtil, FeatureUtil.readFeature(aetherUtil, coord),
310                         existingCoords));
311             } else {
312                 LOG.trace("findAllFeaturesRecursively() skips known {}", coord);
313             }
314         }
315         return result;
316     }
317
318     /**
319      * Unmarshals all the features starting from the given features.
320      *
321      * @param aetherUtil The Aether resolver.
322      * @param features The starting features.
323      * @param existingCoords The artifact coordinates which have already been unmarshalled.
324      * @return The features.
325      * @throws MalformedURLException if a URL is malformed.
326      * @throws FileNotFoundException if a file is missing.
327      * @throws ArtifactResolutionException if artifact coordinates can't be resolved.
328      */
329     public static Set<Features> findAllFeaturesRecursively(
330             final AetherUtil aetherUtil, final Set<Features> features, final Set<String> existingCoords)
331             throws MalformedURLException, FileNotFoundException, ArtifactResolutionException {
332         Set<Features> result = new LinkedHashSet<>();
333         for (Features feature : features) {
334             result.addAll(findAllFeaturesRecursively(aetherUtil, feature, existingCoords));
335         }
336         return result;
337     }
338
339     /**
340      * Unmarshals all the features (including known ones) starting from the given features.
341      *
342      * @param aetherUtil The Aether resolver.
343      * @param features The starting features.
344      * @return The features.
345      * @throws MalformedURLException if a URL is malformed.
346      * @throws FileNotFoundException if a file is missing.
347      * @throws ArtifactResolutionException if artifact coordinates can't be resolved.
348      */
349     public static Set<Features> findAllFeaturesRecursively(final AetherUtil aetherUtil, final Set<Features> features)
350             throws MalformedURLException, FileNotFoundException, ArtifactResolutionException {
351         return findAllFeaturesRecursively(aetherUtil, features, new LinkedHashSet<String>());
352     }
353
354 }