Remove local artifacts from install request
[odlparent.git] / karaf-plugin / src / main / java / org / opendaylight / odlparent / FeatureUtil.java
index 0e6ca993d196d88435bcb5f170a77cd72173e1c9..ff26875ed9fda1488e6e6bc0ab907b916eb8fe45 100644 (file)
@@ -13,23 +13,43 @@ import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
-
+import java.util.regex.Pattern;
+import javax.annotation.RegEx;
+import org.apache.karaf.features.BundleInfo;
+import org.apache.karaf.features.Conditional;
 import org.apache.karaf.features.internal.model.Bundle;
 import org.apache.karaf.features.internal.model.ConfigFile;
 import org.apache.karaf.features.internal.model.Feature;
 import org.apache.karaf.features.internal.model.Features;
 import org.apache.karaf.features.internal.model.JaxbUtil;
+import org.apache.maven.plugin.MojoExecutionException;
 import org.eclipse.aether.artifact.Artifact;
-import org.eclipse.aether.resolution.ArtifactResolutionException;
 import org.ops4j.pax.url.mvn.internal.Parser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public final class FeatureUtil {
-    private FeatureUtil() {
-        throw new UnsupportedOperationException();
+    private static final Logger LOG = LoggerFactory.getLogger(FeatureUtil.class);
+
+    private static final Pattern MVN_PATTERN = Pattern.compile("mvn:", Pattern.LITERAL);
+    private static final Pattern WRAP_PATTERN = Pattern.compile("wrap:", Pattern.LITERAL);
+
+    @RegEx
+    private static final String VERSION_STRIP_PATTERN_STR = "\\$.*$";
+    private static final Pattern VERSION_STRIP_PATTERN = Pattern.compile(VERSION_STRIP_PATTERN_STR);
+
+    private final AetherUtil aetherUtil;
+    private final File localRepo;
+
+    public FeatureUtil(AetherUtil aetherUtil, File localRepo) {
+        this.aetherUtil = aetherUtil;
+        this.localRepo = localRepo;
     }
 
     /**
@@ -39,11 +59,12 @@ public final class FeatureUtil {
      * @return The corresponding artifact coordinates.
      * @throws MalformedURLException if a URL is malformed.
      */
-    public static List<String> toCoord(List<URL> urls) throws MalformedURLException {
+    public static List<String> toCoord(final List<URL> urls) throws MalformedURLException {
         List<String> result = new ArrayList<>();
         for (URL url : urls) {
             result.add(toCoord(url));
         }
+        LOG.trace("toCoord({}) returns {}", urls, result);
         return result;
     }
 
@@ -54,18 +75,27 @@ public final class FeatureUtil {
      * @return The corresponding artifact coordinates.
      * @throws MalformedURLException if the URL is malformed.
      */
-    public static String toCoord(URL url) throws MalformedURLException {
+    public static String toCoord(final URL url) throws MalformedURLException {
         String repository = url.toString();
-        String unwrappedRepo = repository.replaceFirst("wrap:", "");
+        String unwrappedRepo = WRAP_PATTERN.matcher(repository).replaceFirst("");
+
+        // FIXME: this is a hack to deal with broken upstream repo and should be removed once karaf moves
+        //        to hibernate-validator-osgi-features-5.4.2+
+        if ("mvn:com.thoughtworks.paranamer:paranamer:2.8".equals(unwrappedRepo)) {
+            LOG.info("Working around broken hibernate-validator-osgi-karaf-features...");
+            unwrappedRepo = "mvn:com.thoughtworks.paranamer/paranamer/2.8";
+        }
+
         Parser parser = new Parser(unwrappedRepo);
-        String coord = parser.getGroup().replace("mvn:", "") + ":" + parser.getArtifact();
+        String coord = MVN_PATTERN.matcher(parser.getGroup()).replaceFirst("") + ":" + parser.getArtifact();
         if (parser.getType() != null) {
             coord = coord + ":" + parser.getType();
         }
         if (parser.getClassifier() != null) {
             coord = coord + ":" + parser.getClassifier();
         }
-        coord = coord + ":" + parser.getVersion().replaceAll("\\$.*$", "");
+        coord = coord + ":" + VERSION_STRIP_PATTERN.matcher(parser.getVersion()).replaceAll("");
+        LOG.trace("toCoord({}) returns {}", url, coord);
         return coord;
     }
 
@@ -76,11 +106,12 @@ public final class FeatureUtil {
      * @return The corresponding artifact coordinates.
      * @throws MalformedURLException if a URL is malformed.
      */
-    public static Set<String> mvnUrlsToCoord(List<String> repository) throws MalformedURLException {
+    public static Set<String> mvnUrlsToCoord(final List<String> repository) throws MalformedURLException {
         Set<String> result = new LinkedHashSet<>();
         for (String url : repository) {
             result.add(toCoord(new URL(url)));
         }
+        LOG.trace("mvnUrlsToCoord({}) returns {}", repository, result);
         return result;
     }
 
@@ -91,7 +122,7 @@ public final class FeatureUtil {
      * @return The corresponding artifact coordinates.
      * @throws MalformedURLException if a URL is malformed.
      */
-    public static Set<String> featuresRepositoryToCoords(Features features) throws MalformedURLException {
+    public static Set<String> featuresRepositoryToCoords(final Features features) throws MalformedURLException {
         return mvnUrlsToCoord(features.getRepository());
     }
 
@@ -102,11 +133,12 @@ public final class FeatureUtil {
      * @return The corresponding artifact coordinates.
      * @throws MalformedURLException if a URL is malformed.
      */
-    public static Set<String> featuresRepositoryToCoords(Set<Features> features) throws MalformedURLException {
+    public static Set<String> featuresRepositoryToCoords(final Set<Features> features) throws MalformedURLException {
         Set<String> result = new LinkedHashSet<>();
         for (Features feature : features) {
             result.addAll(featuresRepositoryToCoords(feature));
         }
+        LOG.trace("featuresRepositoryToCoords({}) returns {}", features, result);
         return result;
     }
 
@@ -117,14 +149,25 @@ public final class FeatureUtil {
      * @return The corresponding coordinates.
      * @throws MalformedURLException if a URL is malformed.
      */
-    public static Set<String> featureToCoords(Feature feature) throws MalformedURLException {
+    public static Set<String> featureToCoords(final Feature feature) throws MalformedURLException {
         Set<String> result = new LinkedHashSet<>();
         if (feature.getBundle() != null) {
             result.addAll(bundlesToCoords(feature.getBundle()));
         }
+        if (feature.getConditional() != null) {
+            for (Conditional conditional : feature.getConditional()) {
+                if (conditional.getBundles() != null) {
+                    for (BundleInfo bundleInfo : conditional.getBundles()) {
+                        result.add(toCoord(new URL(bundleInfo.getLocation())));
+                    }
+                }
+            }
+            // TODO Dependencies
+        }
         if (feature.getConfigfile() != null) {
             result.addAll(configFilesToCoords(feature.getConfigfile()));
         }
+        LOG.trace("featureToCoords({}) returns {}", feature.getName(), result);
         return result;
     }
 
@@ -135,11 +178,12 @@ public final class FeatureUtil {
      * @return The corresponding coordinates.
      * @throws MalformedURLException if a URL is malformed.
      */
-    public static Set<String> configFilesToCoords(List<ConfigFile> configfiles) throws MalformedURLException {
+    public static Set<String> configFilesToCoords(final List<ConfigFile> configfiles) throws MalformedURLException {
         Set<String> result = new LinkedHashSet<>();
         for (ConfigFile configFile : configfiles) {
             result.add(toCoord(new URL(configFile.getLocation())));
         }
+        LOG.trace("configFilesToCoords({}) returns {}", configfiles, result);
         return result;
     }
 
@@ -150,11 +194,17 @@ public final class FeatureUtil {
      * @return The corresponding coordinates.
      * @throws MalformedURLException if a URL is malformed.
      */
-    public static Set<String> bundlesToCoords(List<Bundle> bundles) throws MalformedURLException {
+    public static Set<String> bundlesToCoords(final List<Bundle> bundles) throws MalformedURLException {
         Set<String> result = new LinkedHashSet<>();
         for (Bundle bundle : bundles) {
-            result.add(toCoord(new URL(bundle.getLocation())));
+            try {
+                result.add(toCoord(new URL(bundle.getLocation())));
+            } catch (MalformedURLException e) {
+                LOG.error("Invalid URL {}", bundle.getLocation(), e);
+                throw e;
+            }
         }
+        LOG.trace("bundlesToCoords({}) returns {}", bundles, result);
         return result;
     }
 
@@ -163,18 +213,28 @@ public final class FeatureUtil {
      *
      * @param features The feature.
      * @return The artifact coordinates.
-     * @throws MalformedURLException if a URL is malformed.
+     * @throws MojoExecutionException if an error occurs during processing.
      */
-    public static Set<String> featuresToCoords(Features features) throws MalformedURLException {
+    public static Set<String> featuresToCoords(final Features features) throws MojoExecutionException {
         Set<String> result = new LinkedHashSet<>();
         if (features.getRepository() != null) {
-            result.addAll(featuresRepositoryToCoords(features));
+            try {
+                result.addAll(featuresRepositoryToCoords(features));
+            } catch (MalformedURLException e) {
+                throw new MojoExecutionException("Feature " + features.getName() + " has an invalid repository URL", e);
+            }
         }
         if (features.getFeature() != null) {
             for (Feature feature : features.getFeature()) {
-                result.addAll(featureToCoords(feature));
+                try {
+                    result.addAll(featureToCoords(feature));
+                } catch (MalformedURLException e) {
+                    throw new MojoExecutionException("Feature " + feature.getName() + " in " + features.getName()
+                            + " contains an invalid or missing URL", e);
+                }
             }
         }
+        LOG.trace("featuresToCoords({}) returns {}", features.getName(), result);
         return result;
     }
 
@@ -184,13 +244,14 @@ public final class FeatureUtil {
      *
      * @param features The features.
      * @return The artifact coordinates.
-     * @throws MalformedURLException if a URL is malformed.
+     * @throws MojoExecutionException if an error occurs during processing.
      */
-    public static Set<String> featuresToCoords(Set<Features> features) throws MalformedURLException {
+    public static Set<String> featuresToCoords(final Set<Features> features) throws MojoExecutionException {
         Set<String> result = new LinkedHashSet<>();
         for (Features feature : features) {
             result.addAll(featuresToCoords(feature));
         }
+        LOG.trace("featuresToCoords({}) returns {}", features, result);
         return result;
     }
 
@@ -201,11 +262,12 @@ public final class FeatureUtil {
      * @return The features.
      * @throws FileNotFoundException if a file is missing.
      */
-    public static Set<Features> readFeatures(Set<Artifact> featureArtifacts) throws FileNotFoundException {
+    public Set<Features> readFeatures(final Set<Artifact> featureArtifacts) throws FileNotFoundException {
         Set<Features> result = new LinkedHashSet<>();
         for (Artifact artifact : featureArtifacts) {
             result.add(readFeature(artifact));
         }
+        LOG.trace("readFeatures({}) returns {}", featureArtifacts, result);
         return result;
     }
 
@@ -216,50 +278,64 @@ public final class FeatureUtil {
      * @return The features.
      * @throws FileNotFoundException if a file is missing.
      */
-    public static Features readFeature(Artifact artifact) throws FileNotFoundException {
-        File file = artifact.getFile();
-        FileInputStream stream = new FileInputStream(file);
-        return JaxbUtil.unmarshal(stream, false);
+    public Features readFeature(final Artifact artifact) throws FileNotFoundException {
+        return readFeature(artifact.getFile());
+    }
+
+    /**
+     * Unmarshal the features in the given file.
+     *
+     * @param file The file.
+     * @return The features.
+     * @throws FileNotFoundException if a file is missing.
+     */
+    public Features readFeature(final File file) throws FileNotFoundException {
+        File localFile = getFileInLocalRepo(file);
+        FileInputStream stream = new FileInputStream(localFile != null ? localFile : file);
+        Features result = JaxbUtil.unmarshal(file.toURI().toString(), stream, false);
+        LOG.trace("readFeature({}) returns {} without resolving first", file, result.getName());
+        return result;
     }
 
     /**
      * Unmarshal the features matching the given artifact coordinates.
      *
-     * @param aetherUtil The Aether resolver.
      * @param coords The artifact coordinates.
      * @return The features.
-     * @throws ArtifactResolutionException if the coordinates can't be resolved.
      * @throws FileNotFoundException if a file is missing.
      */
-    public static Features readFeature(AetherUtil aetherUtil, String coords)
-            throws ArtifactResolutionException, FileNotFoundException {
+    public Features readFeature(final String coords) throws FileNotFoundException {
         Artifact artifact = aetherUtil.resolveArtifact(coords);
-        return readFeature(artifact);
+        Features result = readFeature(artifact);
+        LOG.trace("readFeature({}) returns {} after resolving first", coords, result.getName());
+        return result;
     }
 
     /**
      * Unmarshals all the features starting from the given feature.
      *
-     * @param aetherUtil The Aether resolver.
      * @param features The starting features.
      * @param existingCoords The artifact coordinates which have already been unmarshalled.
      * @return The features.
      * @throws MalformedURLException if a URL is malformed.
      * @throws FileNotFoundException if a file is missing.
-     * @throws ArtifactResolutionException if artifact coordinates can't be resolved.
      */
-    public static Set<Features> findAllFeaturesRecursively(
-            AetherUtil aetherUtil, Features features, Set<String> existingCoords)
-            throws MalformedURLException, FileNotFoundException, ArtifactResolutionException {
+    public Set<Features> findAllFeaturesRecursively(final Features features, final Set<String> existingCoords)
+            throws MalformedURLException, FileNotFoundException {
+        LOG.debug("findAllFeaturesRecursively({}) starts", features.getName());
+        LOG.trace("findAllFeaturesRecursively knows about these coords: {}", existingCoords);
         Set<Features> result = new LinkedHashSet<>();
         Set<String> coords = FeatureUtil.featuresRepositoryToCoords(features);
         for (String coord : coords) {
             if (!existingCoords.contains(coord)) {
+                LOG.trace("findAllFeaturesRecursively() going to add {}", coord);
                 existingCoords.add(coord);
-                Features feature = FeatureUtil.readFeature(aetherUtil, coord);
+                Features feature = readFeature(coord);
                 result.add(feature);
-                result.addAll(findAllFeaturesRecursively(aetherUtil, FeatureUtil.readFeature(aetherUtil, coord),
-                        existingCoords));
+                LOG.debug("findAllFeaturesRecursively() added {}", coord);
+                result.addAll(findAllFeaturesRecursively(feature, existingCoords));
+            } else {
+                LOG.trace("findAllFeaturesRecursively() skips known {}", coord);
             }
         }
         return result;
@@ -268,22 +344,57 @@ public final class FeatureUtil {
     /**
      * Unmarshals all the features starting from the given features.
      *
-     * @param aetherUtil The Aether resolver.
      * @param features The starting features.
      * @param existingCoords The artifact coordinates which have already been unmarshalled.
      * @return The features.
      * @throws MalformedURLException if a URL is malformed.
      * @throws FileNotFoundException if a file is missing.
-     * @throws ArtifactResolutionException if artifact coordinates can't be resolved.
      */
-    public static Set<Features> findAllFeaturesRecursively(
-            AetherUtil aetherUtil, Set<Features> features, Set<String> existingCoords)
-            throws MalformedURLException, FileNotFoundException, ArtifactResolutionException {
+    public Set<Features> findAllFeaturesRecursively(final Set<Features> features, final Set<String> existingCoords)
+            throws MalformedURLException, FileNotFoundException {
         Set<Features> result = new LinkedHashSet<>();
         for (Features feature : features) {
-            result.addAll(findAllFeaturesRecursively(aetherUtil, feature, existingCoords));
+            result.addAll(findAllFeaturesRecursively(feature, existingCoords));
         }
         return result;
     }
 
+    /**
+     * Unmarshals all the features (including known ones) starting from the given features.
+     *
+     * @param features The starting features.
+     * @return The features.
+     * @throws MalformedURLException if a URL is malformed.
+     * @throws FileNotFoundException if a file is missing.
+     */
+    public Set<Features> findAllFeaturesRecursively(final Set<Features> features)
+            throws MalformedURLException, FileNotFoundException {
+        return findAllFeaturesRecursively(features, new LinkedHashSet<>());
+    }
+
+    void removeLocalArtifacts(Set<Artifact> artifacts) {
+        if (localRepo != null) {
+            Iterator<Artifact> it = artifacts.iterator();
+            while (it.hasNext()) {
+                Artifact artifact = it.next();
+                if (getFileInLocalRepo(artifact.getFile()) != null) {
+                    LOG.trace("Removing artifact {}", artifact);
+                    it.remove();
+                }
+            }
+        }
+    }
+
+    private File getFileInLocalRepo(File file) {
+        Path filePath = file.toPath();
+        Path parent = filePath.getParent();
+        while (parent != null) {
+            File candidate = new File(localRepo, parent.relativize(filePath).toString());
+            if (candidate.exists()) {
+                return candidate;
+            }
+            parent = parent.getParent();
+        }
+        return null;
+    }
 }