BUG-4219: add karaf4-plugin 15/48415/3
authorStephen Kitt <skitt@redhat.com>
Mon, 7 Nov 2016 17:03:17 +0000 (18:03 +0100)
committerRobert Varga <nite@hq.sk>
Mon, 21 Nov 2016 15:47:35 +0000 (15:47 +0000)
Change-Id: Ic7c497521b5683f452100dddd243b7513573d48f
Signed-off-by: Stephen Kitt <skitt@redhat.com>
12 files changed:
karaf/karaf4-parent/pom.xml
karaf4-plugin/pom.xml [new file with mode: 0644]
karaf4-plugin/src/it/settings.xml [new file with mode: 0644]
karaf4-plugin/src/it/simple-it/pom.xml [new file with mode: 0644]
karaf4-plugin/src/it/simple-it/verify.groovy [new file with mode: 0644]
karaf4-plugin/src/main/java/org/opendaylight/odlparent/AetherUtil.java [new file with mode: 0644]
karaf4-plugin/src/main/java/org/opendaylight/odlparent/FeatureUtil.java [new file with mode: 0644]
karaf4-plugin/src/main/java/org/opendaylight/odlparent/KarafFeaturesDependencyFilter.java [new file with mode: 0644]
karaf4-plugin/src/main/java/org/opendaylight/odlparent/MvnToAetherMapper.java [new file with mode: 0644]
karaf4-plugin/src/main/java/org/opendaylight/odlparent/PopulateLocalRepoMojo.java [new file with mode: 0644]
odlparent/pom.xml
pom.xml

index 8c1bab45a55b74f7a5df714880b05059b5cb3c16..aa436e0804d4f8eb65ce8431fcb592865d96b705 100644 (file)
             <dependency>
                 <groupId>org.apache.karaf.features</groupId>
                 <artifactId>framework</artifactId>
-                <version>4.0.7</version>
+                <version>${karaf4.version}</version>
                 <type>kar</type>
             </dependency>
             <dependency>
                 <groupId>org.apache.karaf.features</groupId>
                 <artifactId>framework</artifactId>
-                <version>4.0.7</version>
+                <version>${karaf4.version}</version>
                 <classifier>features</classifier>
                 <type>xml</type>
             </dependency>
             <dependency>
                 <groupId>org.apache.karaf.features</groupId>
                 <artifactId>standard</artifactId>
-                <version>4.0.7</version>
+                <version>${karaf4.version}</version>
                 <classifier>features</classifier>
                 <type>xml</type>
             </dependency>
             <dependency>
                 <groupId>org.apache.karaf.features</groupId>
                 <artifactId>spring</artifactId>
-                <version>4.0.7</version>
+                <version>${karaf4.version}</version>
                 <classifier>features</classifier>
                 <type>xml</type>
             </dependency>
             <plugin>
                 <groupId>org.apache.karaf.tooling</groupId>
                 <artifactId>karaf-maven-plugin</artifactId>
-                <version>4.0.7</version>
+                <version>${karaf4.version}</version>
                 <executions>
                     <execution>
                         <id>process-resources</id>
                     <javase>1.8</javase>
                 </configuration>
             </plugin>
-
+            <plugin>
+                <groupId>org.opendaylight.odlparent</groupId>
+                <artifactId>karaf4-plugin</artifactId>
+                <version>1.8.0-SNAPSHOT</version>
+                <executions>
+                    <execution>
+                        <id>populate-local-repo</id>
+                        <goals>
+                            <goal>populate-local-repo</goal>
+                        </goals>
+                        <configuration>
+                            <localRepo>${project.build.directory}/assembly/system</localRepo>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
 
diff --git a/karaf4-plugin/pom.xml b/karaf4-plugin/pom.xml
new file mode 100644 (file)
index 0000000..df7947d
--- /dev/null
@@ -0,0 +1,174 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright © 2016 Red Hat, Inc. and others.  All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ and is available at http://www.eclipse.org/legal/epl-v10.html
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <artifactId>odlparent</artifactId>
+    <groupId>org.opendaylight.odlparent</groupId>
+    <version>1.8.0-SNAPSHOT</version>
+    <relativePath>../odlparent/</relativePath>
+  </parent>
+  <artifactId>karaf4-plugin</artifactId>
+  <packaging>maven-plugin</packaging>
+
+  <name>ODL :: odlparent :: ${project.artifactId}</name>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-plugin-api</artifactId>
+      <version>3.3.3</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-core</artifactId>
+      <version>3.3.3</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-model</artifactId>
+      <version>3.3.3</version>
+    </dependency>
+    <dependency>
+        <groupId>org.ops4j.pax.url</groupId>
+        <artifactId>pax-url-aether</artifactId>
+        <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.karaf.features</groupId>
+      <artifactId>org.apache.karaf.features.core</artifactId>
+      <version>${karaf4.version}</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.aether</groupId>
+      <artifactId>aether-api</artifactId>
+      <version>1.0.2.v20150114</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.odlparent</groupId>
+      <artifactId>features-test</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+      <scope>runtime</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>check-license</id>
+            <goals>
+              <goal>check</goal>
+            </goals>
+            <phase>process-sources</phase>
+            <configuration>
+              <configLocation>check-license.xml</configLocation>
+              <includeResources>false</includeResources>
+              <includeTestResources>false</includeTestResources>
+              <sourceDirectory>${project.build.sourceDirectory}</sourceDirectory>
+              <excludes>
+                <!-- Skip Apache Licensed files -->
+                org/opendaylight/odlparent/PopulateLocalRepoMojo.java
+              </excludes>
+              <failsOnError>false</failsOnError>
+              <consoleOutput>true</consoleOutput>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-plugin-plugin</artifactId>
+        <configuration>
+          <goalPrefix>karaf</goalPrefix>
+          <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
+        </configuration>
+        <executions>
+          <execution>
+            <id>mojo-descriptor</id>
+            <goals>
+              <goal>descriptor</goal>
+            </goals>
+          </execution>
+          <execution>
+            <id>help-goal</id>
+            <goals>
+              <goal>helpmojo</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <profiles>
+    <profile>
+      <id>run-its</id>
+      <build>
+
+        <plugins>
+          <plugin>
+            <artifactId>maven-invoker-plugin</artifactId>
+            <version>2.0.0</version>
+            <configuration>
+              <debug>true</debug>
+              <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
+              <pomIncludes>
+                <pomInclude>*/pom.xml</pomInclude>
+              </pomIncludes>
+              <postBuildHookScript>verify</postBuildHookScript>
+              <localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath>
+              <settingsFile>src/it/settings.xml</settingsFile>
+              <goals>
+                <goal>clean</goal>
+                <goal>test-compile</goal>
+              </goals>
+            </configuration>
+            <executions>
+              <execution>
+                <id>integration-test</id>
+                <goals>
+                  <goal>install</goal>
+                  <goal>integration-test</goal>
+                  <goal>verify</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+
+      </build>
+    </profile>
+  </profiles>
+
+  <!--
+    Maven Site Configuration
+
+    The following configuration is necessary for maven-site-plugin to
+    correctly identify the correct deployment path for OpenDaylight Maven
+    sites.
+  -->
+  <url>${odl.site.url}/${project.groupId}/${stream}/${project.artifactId}/</url>
+
+  <distributionManagement>
+    <site>
+      <id>opendaylight-site</id>
+      <url>${nexus.site.url}/${project.artifactId}/</url>
+    </site>
+  </distributionManagement>
+</project>
diff --git a/karaf4-plugin/src/it/settings.xml b/karaf4-plugin/src/it/settings.xml
new file mode 100644 (file)
index 0000000..fd7219c
--- /dev/null
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ The NOTICE file referred to in the license statement below is available
+ as Karaf-NOTICE at the root of this project.
+-->
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<settings>
+  <profiles>
+    <profile>
+      <id>it-repo</id>
+      <activation>
+        <activeByDefault>true</activeByDefault>
+      </activation>
+      <repositories>
+        <repository>
+          <id>local.central</id>
+          <url>@localRepositoryUrl@</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+      </repositories>
+      <pluginRepositories>
+        <pluginRepository>
+          <id>local.central</id>
+          <url>@localRepositoryUrl@</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </pluginRepository>
+      </pluginRepositories>
+    </profile>
+  </profiles>
+</settings>
diff --git a/karaf4-plugin/src/it/simple-it/pom.xml b/karaf4-plugin/src/it/simple-it/pom.xml
new file mode 100644 (file)
index 0000000..0b32081
--- /dev/null
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright © 2016 Red Hat, Inc. and others.  All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ and is available at http://www.eclipse.org/legal/epl-v10.html
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.opendaylight.odlparent.it</groupId>
+  <artifactId>simple-it</artifactId>
+  <version>1.2.0-SNAPSHOT</version>
+
+  <description>A simple IT verifying the basic use case.</description>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>@project.groupId@</groupId>
+        <artifactId>@project.artifactId@</artifactId>
+        <version>@project.version@</version>
+        <executions>
+          <execution>
+            <id>touch</id>
+            <phase>validate</phase>
+            <goals>
+              <goal>touch</goal>
+            </goals>
+            <configuration>
+                <featureRepos>
+                    <param>foo</param>
+                </featureRepos>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/karaf4-plugin/src/it/simple-it/verify.groovy b/karaf4-plugin/src/it/simple-it/verify.groovy
new file mode 100644 (file)
index 0000000..7b307c7
--- /dev/null
@@ -0,0 +1,3 @@
+File touchFile = new File( basedir, "target/touch.txt" );
+
+assert touchFile.isFile()
diff --git a/karaf4-plugin/src/main/java/org/opendaylight/odlparent/AetherUtil.java b/karaf4-plugin/src/main/java/org/opendaylight/odlparent/AetherUtil.java
new file mode 100644 (file)
index 0000000..293f758
--- /dev/null
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.odlparent;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.graph.DependencyFilter;
+import org.eclipse.aether.installation.InstallationException;
+import org.eclipse.aether.installation.InstallRequest;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.LocalRepositoryManager;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.ArtifactRequest;
+import org.eclipse.aether.resolution.ArtifactResolutionException;
+import org.eclipse.aether.resolution.ArtifactResult;
+import org.eclipse.aether.resolution.DependencyRequest;
+import org.eclipse.aether.resolution.DependencyResolutionException;
+import org.eclipse.aether.resolution.DependencyResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AetherUtil {
+    private static final Logger LOG = LoggerFactory.getLogger(AetherUtil.class);
+    private RepositorySystem repoSystem;
+
+    private RepositorySystemSession repoSession;
+
+    private List<RemoteRepository> remoteRepos;
+
+    protected File localRepository;
+
+    /**
+     * Create an instance for the given repositories.
+     *
+     * @param repoSystem The repository system.
+     * @param repoSession The repository session.
+     * @param remoteRepos The remote repositories.
+     * @param localRepository The local repository.
+     */
+    public AetherUtil(
+            RepositorySystem repoSystem, RepositorySystemSession repoSession, List<RemoteRepository> remoteRepos,
+            File localRepository) {
+        this.repoSystem = repoSystem;
+        this.repoSession = repoSession;
+        this.remoteRepos = remoteRepos;
+        this.localRepository = localRepository;
+    }
+
+    /**
+     * Resolves the given dependencies.
+     *
+     * @param dependencies The dependencies.
+     * @param filter The dependency filter.
+     * @return The corresponding artifacts.
+     * @throws DependencyResolutionException if an error occurs.
+     */
+    public Set<Artifact> resolveDependencies(List<Dependency> dependencies, DependencyFilter filter)
+            throws DependencyResolutionException {
+        Set<Artifact> artifacts = new LinkedHashSet<Artifact>();
+        CollectRequest collectRequest = new CollectRequest();
+        collectRequest.setDependencies(dependencies);
+        collectRequest.setRepositories(remoteRepos);
+        DependencyRequest request = new DependencyRequest(collectRequest, filter);
+        DependencyResult results = repoSystem.resolveDependencies(repoSession, request);
+        for (ArtifactResult artifactResult : results.getArtifactResults()) {
+            artifacts.add(artifactResult.getArtifact());
+        }
+        LOG.trace("resolveDependencies({}) returns {}", dependencies, artifacts);
+        return artifacts;
+    }
+
+    /**
+     * Resolves the given artifact.
+     *
+     * @param artifact The artifact.
+     * @return The resolved artifact, or {@code null} if it can't be resolved.
+     */
+    public Artifact resolveArtifact(Artifact artifact) {
+        ArtifactRequest request = new ArtifactRequest(artifact, remoteRepos, null);
+        ArtifactResult result;
+        try {
+            result = repoSystem.resolveArtifact(repoSession, request);
+        } catch (ArtifactResolutionException e) {
+            LOG.warn("Unable to resolve artifact: {}", e.getMessage(), e);
+            return null;
+        }
+        LOG.trace("resolveArtifacts({}) returns {}", artifact, result.getArtifact());
+        return result.getArtifact();
+    }
+
+    /**
+     * Resolves the given coordinates.
+     *
+     * @param coord The coordinates to resolve.
+     * @return The resolved artifact, or {@code null} if the coordinates can't be resolved.
+     */
+    public Artifact resolveArtifact(String coord) {
+        DefaultArtifact artifact = new DefaultArtifact(coord);
+        return resolveArtifact(artifact);
+    }
+
+    /**
+     * Resolves the given coordinates.
+     *
+     * @param coords The set of coordinates to resolve.
+     * @return The resolved artifacts. Unresolvable coordinates are skipped without error.
+     */
+    public Set<Artifact> resolveArtifacts(Set<String> coords) {
+        Set<Artifact> result = new LinkedHashSet<Artifact>();
+        for (String coord : coords) {
+            Artifact artifact = resolveArtifact(coord);
+            if (artifact != null) {
+                result.add(artifact);
+            }
+        }
+        LOG.trace("resolveArtifacts({}) returns {}", coords, result);
+        return result;
+    }
+
+    /**
+     * Converts the given artifact coordinates to a {@link Dependency} instance.
+     *
+     * @param coord The coordinates.
+     * @return The dependency.
+     */
+    public Dependency toDependency(String coord) {
+        Artifact artifact = new DefaultArtifact(coord);
+        return new Dependency(artifact, null);
+    }
+
+    /**
+     * Converts the given list of artifact coordinates to dependencies.
+     *
+     * @param coords The list of coordinates.
+     * @return The corresponding dependencies.
+     */
+    public List<Dependency> toDependencies(List<String> coords) {
+        List<Dependency> result = new ArrayList<Dependency>();
+        for (String coord : coords) {
+            result.add(toDependency(coord));
+        }
+        LOG.trace("toDependencies({}) returns {}", coords, result);
+        return result;
+    }
+
+    /**
+     * Installs the given artifacts.
+     *
+     * @param artifacts The artifacts to install.
+     * @throws InstallationException if an error occurs.
+     */
+    public void installArtifacts(Set<Artifact> artifacts) throws InstallationException {
+        LocalRepository localRepo = new LocalRepository(localRepository);
+        LocalRepositoryManager localManager = repoSystem.newLocalRepositoryManager(repoSession, localRepo);
+        DefaultRepositorySystemSession localSession = new DefaultRepositorySystemSession();
+        localSession.setLocalRepositoryManager(localManager);
+        InstallRequest installRequest = new InstallRequest();
+        for (Artifact featureArtifact : artifacts) {
+            installRequest.addArtifact(featureArtifact);
+        }
+        repoSystem.install(localSession, installRequest);
+    }
+}
diff --git a/karaf4-plugin/src/main/java/org/opendaylight/odlparent/FeatureUtil.java b/karaf4-plugin/src/main/java/org/opendaylight/odlparent/FeatureUtil.java
new file mode 100644 (file)
index 0000000..197799d
--- /dev/null
@@ -0,0 +1,327 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.odlparent;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+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.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 static final Logger LOG = LoggerFactory.getLogger(FeatureUtil.class);
+
+    private FeatureUtil() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Converts the given list of URLs to artifact coordinates.
+     *
+     * @param urls The URLs.
+     * @return The corresponding artifact coordinates.
+     * @throws MalformedURLException if a URL is malformed.
+     */
+    public static List<String> toCoord(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;
+    }
+
+    /**
+     * Converts the given URL to artifact coordinates.
+     *
+     * @param url The URL.
+     * @return The corresponding artifact coordinates.
+     * @throws MalformedURLException if the URL is malformed.
+     */
+    public static String toCoord(URL url) throws MalformedURLException {
+        String repository = url.toString();
+        String unwrappedRepo = repository.replaceFirst("wrap:", "");
+        Parser parser = new Parser(unwrappedRepo);
+        String coord = parser.getGroup().replace("mvn:", "") + ":" + parser.getArtifact();
+        if (parser.getType() != null) {
+            coord = coord + ":" + parser.getType();
+        }
+        if (parser.getClassifier() != null) {
+            coord = coord + ":" + parser.getClassifier();
+        }
+        coord = coord + ":" + parser.getVersion().replaceAll("\\$.*$", "");
+        LOG.trace("toCoord({}) returns {}", url, coord);
+        return coord;
+    }
+
+    /**
+     * Parses the given repository as URLs and converts them to artifact coordinates.
+     *
+     * @param repository The repository (list of URLs).
+     * @return The corresponding artifact coordinates.
+     * @throws MalformedURLException if a URL is malformed.
+     */
+    public static Set<String> mvnUrlsToCoord(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;
+    }
+
+    /**
+     * Converts the given features' repository to artifact coordinates.
+     *
+     * @param features The features.
+     * @return The corresponding artifact coordinates.
+     * @throws MalformedURLException if a URL is malformed.
+     */
+    public static Set<String> featuresRepositoryToCoords(Features features) throws MalformedURLException {
+        return mvnUrlsToCoord(features.getRepository());
+    }
+
+    /**
+     * Converts all the given features' repositories to artifact coordinates.
+     *
+     * @param features The features.
+     * @return The corresponding artifact coordinates.
+     * @throws MalformedURLException if a URL is malformed.
+     */
+    public static Set<String> featuresRepositoryToCoords(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;
+    }
+
+    /**
+     * Lists the artifact coordinates of the given feature's bundles and configuration files.
+     *
+     * @param feature The feature.
+     * @return The corresponding coordinates.
+     * @throws MalformedURLException if a URL is malformed.
+     */
+    public static Set<String> featureToCoords(Feature feature) throws MalformedURLException {
+        Set<String> result = new LinkedHashSet<>();
+        if (feature.getBundle() != null) {
+            result.addAll(bundlesToCoords(feature.getBundle()));
+        }
+        if (feature.getConfigfile() != null) {
+            result.addAll(configFilesToCoords(feature.getConfigfile()));
+        }
+        LOG.trace("featureToCoords({}) returns {}", feature.getName(), result);
+        return result;
+    }
+
+    /**
+     * Lists the artifact coordinates of the given configuration files.
+     *
+     * @param configfiles The configuration files.
+     * @return The corresponding coordinates.
+     * @throws MalformedURLException if a URL is malformed.
+     */
+    public static Set<String> configFilesToCoords(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;
+    }
+
+    /**
+     * Lists the artifact coordinates of the given bundles.
+     *
+     * @param bundles The bundles.
+     * @return The corresponding coordinates.
+     * @throws MalformedURLException if a URL is malformed.
+     */
+    public static Set<String> bundlesToCoords(List<Bundle> bundles) throws MalformedURLException {
+        Set<String> result = new LinkedHashSet<>();
+        for (Bundle bundle : bundles) {
+            result.add(toCoord(new URL(bundle.getLocation())));
+        }
+        LOG.trace("bundlesToCoords({}) returns {}", bundles, result);
+        return result;
+    }
+
+    /**
+     * Extracts all the artifact coordinates for the given features (repositories, bundles, configuration files).
+     *
+     * @param features The feature.
+     * @return The artifact coordinates.
+     * @throws MalformedURLException if a URL is malformed.
+     */
+    public static Set<String> featuresToCoords(Features features) throws MalformedURLException {
+        Set<String> result = new LinkedHashSet<>();
+        if (features.getRepository() != null) {
+            result.addAll(featuresRepositoryToCoords(features));
+        }
+        if (features.getFeature() != null) {
+            for (Feature feature : features.getFeature()) {
+                result.addAll(featureToCoords(feature));
+            }
+        }
+        LOG.trace("featuresToCoords({}) returns {}", features.getName(), result);
+        return result;
+    }
+
+    /**
+     * Extracts all the artifact coordinates for the given set of features (repositories, bundles, configuration
+     * files).
+     *
+     * @param features The features.
+     * @return The artifact coordinates.
+     * @throws MalformedURLException if a URL is malformed.
+     */
+    public static Set<String> featuresToCoords(Set<Features> features) throws MalformedURLException {
+        Set<String> result = new LinkedHashSet<>();
+        for (Features feature : features) {
+            result.addAll(featuresToCoords(feature));
+        }
+        LOG.trace("featuresToCoords({}) returns {}", features, result);
+        return result;
+    }
+
+    /**
+     * Unmarshal all the features in the given artifacts.
+     *
+     * @param featureArtifacts The artifacts.
+     * @return The features.
+     * @throws FileNotFoundException if a file is missing.
+     */
+    public static Set<Features> readFeatures(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;
+    }
+
+    /**
+     * Unmarshal the features in the given artifact.
+     *
+     * @param artifact The artifact.
+     * @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);
+        Features result = JaxbUtil.unmarshal(file.toURI().toString(), stream, false);
+        LOG.trace("readFeature({}) returns {} without resolving first", artifact, 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 {
+        Artifact artifact = aetherUtil.resolveArtifact(coords);
+        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 {
+        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);
+                result.add(feature);
+                LOG.debug("findAllFeaturesRecursively() added {}", coord);
+                result.addAll(findAllFeaturesRecursively(aetherUtil, FeatureUtil.readFeature(aetherUtil, coord),
+                        existingCoords));
+            } else {
+                LOG.trace("findAllFeaturesRecursively() skips known {}", coord);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 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 {
+        Set<Features> result = new LinkedHashSet<>();
+        for (Features feature : features) {
+            result.addAll(findAllFeaturesRecursively(aetherUtil, feature, existingCoords));
+        }
+        return result;
+    }
+
+    /**
+     * Unmarshals all the features (including known ones) starting from the given features.
+     *
+     * @param aetherUtil The Aether resolver.
+     * @param features The starting features.
+     * @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)
+            throws MalformedURLException, FileNotFoundException, ArtifactResolutionException {
+        return findAllFeaturesRecursively(aetherUtil, features, new LinkedHashSet<String>());
+    }
+
+}
diff --git a/karaf4-plugin/src/main/java/org/opendaylight/odlparent/KarafFeaturesDependencyFilter.java b/karaf4-plugin/src/main/java/org/opendaylight/odlparent/KarafFeaturesDependencyFilter.java
new file mode 100644 (file)
index 0000000..c97011c
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.odlparent;
+
+import java.util.List;
+import org.eclipse.aether.graph.DependencyFilter;
+import org.eclipse.aether.graph.DependencyNode;
+
+public class KarafFeaturesDependencyFilter implements DependencyFilter {
+
+    /**
+     * Accepts only Karaf features.
+     *
+     * @param node The dependency node.
+     * @param parents The parents (ignored).
+     * @return {@code true} if the dependency is a Karaf feature, {@code false} otherwise.
+     */
+    @Override
+    public boolean accept(DependencyNode node, List<DependencyNode> parents) {
+        return node != null
+                && node.getArtifact() != null
+                && node.getDependency() != null
+                && node.getDependency().getScope() != null
+                && node.getArtifact().getClassifier().equals("features")
+                && node.getArtifact().getExtension().equals("xml");
+    }
+
+}
diff --git a/karaf4-plugin/src/main/java/org/opendaylight/odlparent/MvnToAetherMapper.java b/karaf4-plugin/src/main/java/org/opendaylight/odlparent/MvnToAetherMapper.java
new file mode 100644 (file)
index 0000000..dafbcb6
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.odlparent;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.artifact.DefaultArtifactType;
+import org.eclipse.aether.graph.Dependency;
+
+public final class MvnToAetherMapper {
+    private MvnToAetherMapper() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Converts a Maven model dependency to an Aether dependency.
+     *
+     * @param dependency The Maven model dependency.
+     * @return The Aether dependency.
+     */
+    public static Dependency toAether(org.apache.maven.model.Dependency dependency) {
+        DefaultArtifact artifact = new DefaultArtifact(dependency.getGroupId(),
+                dependency.getArtifactId(),
+                dependency.getClassifier(),
+                null,
+                dependency.getVersion(),
+                new DefaultArtifactType(dependency.getType()));
+        return new Dependency(artifact, null);
+    }
+
+    /**
+     * Converts a list of Maven model dependencies to a list of Aether dependencies.
+     *
+     * @param dependencies The Maven model dependencies.
+     * @return The Aether dependencies.
+     */
+    public static List<Dependency> toAether(List<org.apache.maven.model.Dependency> dependencies) {
+        List<Dependency> result = new ArrayList<>();
+        for (org.apache.maven.model.Dependency dependency : dependencies) {
+            result.add(toAether(dependency));
+        }
+        return result;
+    }
+}
diff --git a/karaf4-plugin/src/main/java/org/opendaylight/odlparent/PopulateLocalRepoMojo.java b/karaf4-plugin/src/main/java/org/opendaylight/odlparent/PopulateLocalRepoMojo.java
new file mode 100644 (file)
index 0000000..fe7235e
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opendaylight.odlparent;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.karaf.features.internal.model.Features;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.project.MavenProject;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.ArtifactResolutionException;
+import org.opendaylight.odlparent.featuretest.CustomBundleUrlStreamHandlerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+// TODO: Use org.apache.maven.plugin.annotations.* to allow more flexibility for non-ODL consumers.
+
+/**
+ * Mojo populating the local repository by delegating to Aether.
+ *
+ * @goal populate-local-repo
+ * @phase prepare-package
+ */
+public class PopulateLocalRepoMojo
+        extends AbstractMojo {
+    private static final Logger LOG = LoggerFactory.getLogger(PopulateLocalRepoMojo.class);
+
+    static {
+        // Static initialization, as we may be invoked multiple times
+        // karaf-maven-plugin defines its own URLStreamHandlerFactory for install-kars, so we may find a factory
+        // already defined (but it handles "mvn:" and "wrap:mvn:" so we should be OK)
+        try {
+            URL.setURLStreamHandlerFactory(new CustomBundleUrlStreamHandlerFactory());
+        } catch (Error e) {
+            LOG.warn("populate-local-repo: URL factory is already defined");
+        }
+    }
+
+    /**
+     * The Maven project being built.
+     *
+     * @component
+     * @required
+     * @readonly
+     */
+    private MavenProject project;
+
+    /**
+     * The entry point to Aether, i.e. the component doing all the work.
+     *
+     * @component
+     */
+    private RepositorySystem repoSystem;
+
+    /**
+     * The current repository/network configuration of Maven.
+     *
+     * @parameter default-value="${repositorySystemSession}"
+     * @readonly
+     */
+    private RepositorySystemSession repoSession;
+
+    /**
+     * The project's remote repositories to use for the resolution of plugins and their dependencies.
+     *
+     * @parameter default-value="${project.remoteProjectRepositories}"
+     * @readonly
+     */
+    private List<RemoteRepository> remoteRepos;
+
+    /**
+     * The local repository to use for the resolution of plugins and their dependencies.
+     *
+     * @parameter
+     */
+    private File localRepo;
+
+    private AetherUtil aetherUtil;
+
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+
+        aetherUtil = new AetherUtil(repoSystem, repoSession, remoteRepos, localRepo);
+        try {
+            Set<Artifact> startupArtifacts = readStartupProperties();
+            aetherUtil.installArtifacts(startupArtifacts);
+            Set<Artifact> featureArtifacts = readFeatureCfg();
+            featureArtifacts.addAll(
+                    aetherUtil.resolveDependencies(MvnToAetherMapper.toAether(project.getDependencies()),
+                            new KarafFeaturesDependencyFilter()));
+            Set<Features> features = FeatureUtil.readFeatures(featureArtifacts);
+            // Do not provide FeatureUtil.featuresRepositoryToCoords(features)) as existingCoords
+            // to findAllFeaturesRecursively, as those coords are not resolved yet, and it would lead to Bug 6187.
+            features.addAll(FeatureUtil.findAllFeaturesRecursively(aetherUtil, features));
+            for (Features feature : features) {
+                LOG.info("Feature repository discovered recursively: {}", feature.getName());
+            }
+            Set<Artifact> artifacts = aetherUtil.resolveArtifacts(FeatureUtil.featuresToCoords(features));
+            artifacts.addAll(featureArtifacts);
+
+            for (Artifact artifact : artifacts) {
+                LOG.debug("Artifact to be installed: {}", artifact.toString());
+            }
+            if (localRepo != null) {
+                aetherUtil.installArtifacts(artifacts);
+            }
+        } catch (Exception e) {
+            throw new MojoExecutionException("Failed to execute", e);
+        }
+    }
+
+    private Set<Artifact> readFeatureCfg() throws ArtifactResolutionException {
+        Set<Artifact> artifacts = new LinkedHashSet<>();
+        File file = new File(localRepo.getParentFile().toString() + "/etc/org.apache.karaf.features.cfg");
+        Properties prop = new Properties();
+        try {
+            prop.load(new FileInputStream(file));
+            String featuresRepositories = prop.getProperty("featuresRepositories");
+            List<String> result = Arrays.asList(featuresRepositories.split(","));
+            for (String mvnUrl : result) {
+                artifacts.add(aetherUtil.resolveArtifact(FeatureUtil.toCoord(new URL(mvnUrl))));
+            }
+        } catch (FileNotFoundException e) {
+            LOG.info("Could not find properties file: {}", file.getAbsolutePath(), e);
+        } catch (IOException e) {
+            LOG.info("Could not read properties file: {}", file.getAbsolutePath(), e);
+        }
+
+        return artifacts;
+    }
+
+    private Set<Artifact> readStartupProperties() throws ArtifactResolutionException {
+        Set<Artifact> artifacts = new LinkedHashSet<>();
+        File file = new File(localRepo.getParentFile().toString() + "/etc/startup.properties");
+        Properties prop = new Properties();
+        try {
+            prop.load(new FileInputStream(file));
+            Enumeration<Object> mvnUrls = prop.keys();
+            while(mvnUrls.hasMoreElements()) {
+                String mvnUrl = (String)mvnUrls.nextElement();
+                Artifact artifact = aetherUtil.resolveArtifact(FeatureUtil.toCoord(new URL(mvnUrl)));
+                artifacts.add(artifact);
+            }
+        } catch (FileNotFoundException e) {
+            LOG.info("Could not find properties file: {}", file.getAbsolutePath(), e);
+        } catch (IOException e) {
+            LOG.info("Could not read properties file: {}", file.getAbsolutePath(), e);
+        }
+
+        return artifacts;
+    }
+}
index 99b8998eb7d1cf99f5331bc1a92db9091f6e596d..1a9f22ac4529de2dd9e81eac24e9959b04b2c863 100644 (file)
     <!-- Used in aaa, controller, coretutorials, integration/distribution, l2switch, lacp, lispflowmapping, netide,
          nic, openflowplugin, ovsdb, persistence, sfc, snbi, snmp4sdn?, sxp, topoprocessing, unimgr, vtn -->
     <karaf.version>3.0.8</karaf.version>
+    <karaf4.version>4.0.7</karaf4.version>
     <!-- Used in lispflowmapping, snmp4sdn, vtn -->
     <projectinfo>2.8.1</projectinfo>
     <!-- Used in of-config; see also snmp4sdn -->
diff --git a/pom.xml b/pom.xml
index 5c156d9186db95d22cedfb01c1f0449021fa73bd..f0fa12f0c3ae2c49e91b8a82081d47b9d164d8e8 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -36,6 +36,7 @@
         <!-- Karaf integration -->
         <module>karaf</module>
         <module>karaf-plugin</module>
+        <module>karaf4-plugin</module>
 
         <!-- Parent POMs -->
         <module>bundle-parent</module>