<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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+File touchFile = new File( basedir, "target/touch.txt" );
+
+assert touchFile.isFile()
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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>());
+ }
+
+}
--- /dev/null
+/*
+ * 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");
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
<!-- 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 -->
<!-- Karaf integration -->
<module>karaf</module>
<module>karaf-plugin</module>
+ <module>karaf4-plugin</module>
<!-- Parent POMs -->
<module>bundle-parent</module>