<parent>
<groupId>org.opendaylight.odlparent</groupId>
- <artifactId>single-feature-parent</artifactId>
+ <artifactId>template-feature-parent</artifactId>
<version>11.0.1-SNAPSHOT</version>
- <relativePath>../../single-feature-parent</relativePath>
+ <relativePath>../../template-feature-parent</relativePath>
</parent>
- <groupId>org.opendaylight.odlparent</groupId>
<artifactId>odl-antlr4</artifactId>
- <version>11.0.1-SNAPSHOT</version>
<packaging>feature</packaging>
<name>OpenDaylight :: ANTLRv4 Runtime</name>
<description>ANTLR v4</description>
- <properties>
- <checkDependencyChange>true</checkDependencyChange>
- <failOnDependencyChange>true</failOnDependencyChange>
- </properties>
-
<dependencies>
<dependency>
<groupId>org.antlr</groupId>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.6.0" name="odl-antlr4">
+ <feature name="odl-antlr4">
+ <bundle>mvn:org.antlr/antlr4-runtime/{{versionAsInProject}}</bundle>
+ </feature>
+</features>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.5.0" name="odl-antlr4">
- <feature version="0.0.0">
- <bundle>mvn:org.antlr/antlr4-runtime/4.9.3</bundle>
- </feature>
-</features>
<module>bundle-parent</module>
<module>abstract-feature-parent</module>
<module>single-feature-parent</module>
+ <module>template-feature-parent</module>
<module>feature-repo-parent</module>
<module>odlparent</module>
<module>odlparent-lite</module>
+ <!-- Plugin for processing templates for Karaf features -->
+ <module>template-feature-plugin</module>
+
<!-- File copying plugin -->
<module>copy-files-plugin</module>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright © 2021 PANTHEON.tech, s.r.o. 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>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>abstract-feature-parent</artifactId>
+ <version>11.0.1-SNAPSHOT</version>
+ <relativePath>../abstract-feature-parent</relativePath>
+ </parent>
+
+ <artifactId>template-feature-parent</artifactId>
+ <packaging>pom</packaging>
+ <name>ODL :: odlparent :: ${project.artifactId}</name>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>template-feature-plugin</artifactId>
+ <version>11.0.1-SNAPSHOT</version>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+
+ <plugins>
+ <plugin>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>template-feature-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>generate-feature</id>
+ <goals>
+ <goal>generate-feature</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.karaf.tooling</groupId>
+ <artifactId>karaf-maven-plugin</artifactId>
+ <version>${karaf.version}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <enableGeneration>false</enableGeneration>
+ <inputFile>${project.build.directory}/feature/templated-feature.xml</inputFile>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright © 2021 PANTHEON.tech, s.r.o. 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>11.0.1-SNAPSHOT</version>
+ <relativePath>../odlparent/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>template-feature-plugin</artifactId>
+ <packaging>maven-plugin</packaging>
+ <name>ODL :: odlparent :: ${project.artifactId}</name>
+
+ <prerequisites>
+ <maven>3.8.3</maven>
+ </prerequisites>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-artifact</artifactId>
+ <version>3.8.3</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-core</artifactId>
+ <version>3.8.3</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-model</artifactId>
+ <version>3.8.3</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-plugin-api</artifactId>
+ <version>3.8.3</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.plugin-tools</groupId>
+ <artifactId>maven-plugin-annotations</artifactId>
+ <version>3.6.2</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.karaf.features</groupId>
+ <artifactId>org.apache.karaf.features.core</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-plugin-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>mojo-descriptor</id>
+ <goals>
+ <goal>descriptor</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. 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.template.feature.plugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.regex.Pattern;
+import javax.xml.bind.JAXBException;
+import org.apache.felix.utils.version.VersionCleaner;
+import org.apache.karaf.features.internal.model.Dependency;
+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.artifact.versioning.DefaultArtifactVersion;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.maven.project.MavenProject;
+import org.eclipse.jdt.annotation.Nullable;
+
+@Mojo(name = "generate-feature", defaultPhase = LifecyclePhase.GENERATE_SOURCES,
+ requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true)
+public class GenerateFeatureMojo extends AbstractMojo {
+ // Common groups
+ private static final String LEAD = "lead";
+
+ // Groups in <bundle> and <repository> lines
+ private static final String GROUP_ID = "groupId";
+ private static final String ARTIFACT_ID = "artifactId";
+ private static final String TYPE = "type";
+ private static final String CLASSIFIER = "classifier";
+
+ // Version specification as they appear in raw features and bundles
+ private static final String VERSION_AS_IN_PROJECT = "{{versionAsInProject}}";
+ private static final String SEM_VER_RANGE = "{{semVerRange}}";
+ private static final String PROJECT_VERSION = "{{projectVersion}}";
+
+ // Version specifications as they appear in features after being scrubbed by Karaf marshaller
+ private static final String VERSION_AS_IN_PROJECT_CLEAN = VersionCleaner.clean(VERSION_AS_IN_PROJECT);
+ private static final String SEM_VER_RANGE_CLEAN = VersionCleaner.clean(SEM_VER_RANGE);
+ private static final String PROJECT_VERSION_CLEAN = VersionCleaner.clean(PROJECT_VERSION);
+
+ // mvn:org.opendaylight.genius/lockmanager-api/{{versionAsInProject}}
+ // mvn:org.opendaylight.odlparent/odl-guava/10.0.0-SNAPSHOT/xml/features
+ private static final Pattern MVNURL_PATTERN = Pattern.compile("^(?<" + LEAD + ">(wrap:)?mvn:)"
+ + "(?<" + GROUP_ID + ">[^/]+)/(?<" + ARTIFACT_ID + ">[^/]+)/\\{\\{versionAsInProject\\}\\}"
+ + "(/(?<" + TYPE + ">[^/]+)(/(?<" + CLASSIFIER + ">[^/]+))?)?$");
+
+ private static final String FEATURES_TYPE = "xml";
+ private static final String FEATURES_CLASSIFIER = "features";
+
+ @Parameter(defaultValue = "${project.basedir}/src/main/feature/template.xml")
+ private File inputFile;
+
+ @Parameter(defaultValue = "${project.build.directory}/feature/templated-feature.xml")
+ private File outputFile;
+
+ @Parameter(required = true, defaultValue = "${project}", readonly = true)
+ private MavenProject mavenProject;
+
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ if (!"feature".equals(mavenProject.getPackaging())) {
+ getLog().info("Project packaging is not 'feature', skipping execution");
+ return;
+ }
+
+ // read, process and write the feature
+ final Features features = readFeature(inputFile.toPath());
+ processFeatures(features);
+ writeFeature(features, outputFile.toPath());
+ }
+
+ // Visible for testing
+ void processFeatures(final Features features) throws MojoFailureException {
+ // Process feature repository references, dancing around encapsulation
+ final var featRepos = features.getRepository();
+ final var newRepos = new ArrayList<String>(featRepos.size());
+ for (var repo : featRepos) {
+ newRepos.add(processReference(repo));
+ }
+ featRepos.clear();
+ featRepos.addAll(newRepos);
+
+ // Process all features in-place
+ for (var feature : features.getFeature()) {
+ processFeature(feature);
+ }
+ }
+
+ private void processFeature(final Feature feature) throws MojoFailureException {
+ final var artifactFeature = feature.getName().equals(mavenProject.getArtifactId());
+
+ // Update feature version if needed
+ if (feature.hasVersion()) {
+ feature.setVersion(processVersion(feature));
+ } else if (artifactFeature) {
+ feature.setVersion(osgiVersion(mavenProject.getVersion()));
+ } else {
+ throw new MojoFailureException("Feature \"" + feature.getName() + "\" does not define a version");
+ }
+
+ // Fill in other details if not provided
+ if (artifactFeature) {
+ if (feature.getDescription() == null) {
+ feature.setDescription(mavenProject.getName());
+ }
+ if (feature.getDetails() == null) {
+ feature.setDetails(mavenProject.getDescription());
+ }
+ }
+
+ // Process feature dependencies, updating versions as needed
+ for (var dependency : feature.getFeature()) {
+ if (dependency.hasVersion()) {
+ dependency.setVersion(processVersion(dependency));
+ }
+ }
+
+ // Process feature bundles, updating versions as needed
+ for (var bundle : feature.getBundle()) {
+ bundle.setLocation(processReference(bundle.getLocation()));
+ }
+ }
+
+ private String processReference(final String repository) throws MojoFailureException {
+ final var matcher = MVNURL_PATTERN.matcher(repository);
+ if (!matcher.matches()) {
+ return repository;
+ }
+
+ final var groupId = matcher.group(GROUP_ID);
+ final var artifactId = matcher.group(ARTIFACT_ID);
+ final var type = matcher.group(TYPE);
+ final var classifier = matcher.group(CLASSIFIER);
+ final var version = dependencyVersion(groupId, artifactId, type, classifier);
+
+ final var sb = new StringBuilder()
+ .append(matcher.group(LEAD)).append(groupId).append('/').append(artifactId).append('/').append(version);
+ if (type != null) {
+ sb.append('/').append(type);
+ if (classifier != null) {
+ sb.append('/').append(classifier);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ private String processVersion(final Dependency dependency) throws MojoFailureException {
+ final var version = dependency.getVersion();
+ return switch (version) {
+ case PROJECT_VERSION -> osgiVersion(mavenProject.getVersion());
+ case SEM_VER_RANGE -> semVerRange(featureVersion(dependency.getName()));
+ case VERSION_AS_IN_PROJECT -> osgiVersion(featureVersion(dependency.getName()));
+ default -> version;
+ };
+ }
+
+ private String processVersion(final Feature feature) throws MojoFailureException {
+ // We really would want a switch expression, but alas that is not to be: the input is processed by unmarshaller
+ // and scrubbed in ways that are not compile-time constants
+ final String version = feature.getVersion();
+ if (PROJECT_VERSION_CLEAN.equals(version)) {
+ return osgiVersion(mavenProject.getVersion());
+ } else if (SEM_VER_RANGE_CLEAN.equals(version)) {
+ return semVerRange(featureVersion(feature.getName()));
+ } else if (VERSION_AS_IN_PROJECT_CLEAN.equals(version)) {
+ return osgiVersion(featureVersion(feature.getName()));
+ } else {
+ return version;
+ }
+ }
+
+ private String dependencyVersion(final String groupId, final String artifactId, final @Nullable String type,
+ final @Nullable String classifier) throws MojoFailureException {
+ for (var dep : mavenProject.getDependencies()) {
+ if (artifactId.equals(dep.getArtifactId()) && groupId.equals(dep.getGroupId())
+ && (type == null || type.equals(dep.getType()))
+ && (classifier == null || classifier.equals(dep.getClassifier()))) {
+ return dep.getVersion();
+ }
+ }
+
+ throw new MojoFailureException("Dependency \"" + groupId + ":" + artifactId + "\" not found");
+ }
+
+ private String featureVersion(final String featureName) throws MojoFailureException {
+ // This feature's version
+ if (featureName.equals(mavenProject.getArtifactId())) {
+ return mavenProject.getVersion();
+ }
+
+ for (var dependency : mavenProject.getDependencies()) {
+ if (featureName.equals(dependency.getArtifactId()) && FEATURES_CLASSIFIER.equals(dependency.getClassifier())
+ && FEATURES_TYPE.equals(dependency.getType())) {
+ return dependency.getVersion();
+ }
+ }
+
+ throw new MojoFailureException("Dependency matching feature \"" + featureName + "\" not found");
+ }
+
+ // Visible for testing
+ static String semVerRange(final String version) {
+ final var semVer = new DefaultArtifactVersion(version);
+ final var major = semVer.getMajorVersion();
+ final var minor = semVer.getMinorVersion();
+ final var patch = semVer.getIncrementalVersion();
+
+ final var sb = new StringBuilder() .append('[').append(major);
+ if (minor != 0 || patch != 0) {
+ sb.append('.').append(minor);
+ if (patch != 0) {
+ sb.append('.').append(patch);
+ }
+ }
+ return sb.append(',').append(major + 1).append(')').toString();
+ }
+
+ private static String osgiVersion(final String version) {
+ // Sufficient for now
+ return version.replace('-', '.');
+ }
+
+ private static Features readFeature(final Path path) throws MojoExecutionException {
+ try (var is = Files.newInputStream(path)) {
+ return readFeature(path.toUri().toString(), is);
+ } catch (IOException e) {
+ throw new MojoExecutionException("Failed to read input " + path, e);
+ }
+ }
+
+ // Visible for testing
+ static Features readFeature(final String uri, final InputStream input) throws IOException {
+ return JaxbUtil.unmarshal(uri, input, true);
+ }
+
+ private static void writeFeature(final Features feature, final Path path) throws MojoExecutionException {
+ final var parent = path.getParent();
+ try {
+ Files.createDirectories(parent);
+ } catch (IOException e) {
+ throw new MojoExecutionException("Failed to create parent directory " + parent, e);
+ }
+
+ try (var os = Files.newOutputStream(path)) {
+ writeFeature(feature, os);
+ } catch (IOException | JAXBException e) {
+ throw new MojoExecutionException("Failed to write output " + path, e);
+ }
+ }
+
+ // Visible for testing
+ static void writeFeature(final Features feature, final OutputStream output) throws JAXBException {
+ JaxbUtil.marshal(feature, output);
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+ Copyright (c) 2022 PANTHEON.tech, s.r.o. 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
+-->
+<lifecycleMappingMetadata>
+ <pluginExecutions>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <goals>
+ <goal>generate-feature</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <execute>
+ <runOnIncremental>true</runOnIncremental>
+ <runOnConfiguration>true</runOnConfiguration>
+ </execute>
+ </action>
+ </pluginExecution>
+ </pluginExecutions>
+</lifecycleMappingMetadata>
--- /dev/null
+/*
+ * Copyright (c) 2022 PANTHEON.tech, s.r.o. 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.template.feature.plugin;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import javax.xml.bind.JAXBException;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.project.MavenProject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+public class GenerateFeatureMojoTest {
+ @InjectMocks
+ private GenerateFeatureMojo mojo;
+ @Mock
+ private MavenProject mavenProject;
+ @Mock
+ private Dependency dependency;
+
+ @Test
+ public void testSemVerRange() {
+ assertEquals("[0,1)", GenerateFeatureMojo.semVerRange("0"));
+ assertEquals("[0.1,1)", GenerateFeatureMojo.semVerRange("0.1"));
+ assertEquals("[0.0.1,1)", GenerateFeatureMojo.semVerRange("0.0.1"));
+ assertEquals("[0.1,1)", GenerateFeatureMojo.semVerRange("0.1.0"));
+ assertEquals("[0.1.1,1)", GenerateFeatureMojo.semVerRange("0.1.1"));
+ assertEquals("[1.2.3,2)", GenerateFeatureMojo.semVerRange("1.2.3"));
+ }
+
+ @Test
+ public void testProcessBundle() throws MojoFailureException {
+ doReturn("org.opendaylight.genius").when(dependency).getGroupId();
+ doReturn("lockmanager-api").when(dependency).getArtifactId();
+ doReturn("1.2.3").when(dependency).getVersion();
+
+ doReturn("odl-yangtools-util").when(mavenProject).getArtifactId();
+ doReturn("8.0.0-SNAPSHOT").when(mavenProject).getVersion();
+ doReturn(List.of(dependency)).when(mavenProject).getDependencies();
+
+ assertProcessFeature(
+ """
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ <features xmlns="http://karaf.apache.org/xmlns/features/v1.6.0" name="odl-yangtools-util">
+ <feature name="odl-yangtools-util" version="8.0.0.SNAPSHOT">
+ <bundle>mvn:org.opendaylight.genius/lockmanager-api/1.2.3</bundle>
+ </feature>
+ </features>
+ """,
+ """
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ <features xmlns="http://karaf.apache.org/xmlns/features/v1.6.0" name="odl-yangtools-util">
+ <feature name="odl-yangtools-util">
+ <bundle>mvn:org.opendaylight.genius/lockmanager-api/{{versionAsInProject}}</bundle>
+ </feature>
+ </features>
+ """);
+ }
+
+ @Test
+ public void testProcessFeature() throws MojoFailureException {
+ doReturn("odl-apache-commons-net").when(dependency).getArtifactId();
+ doReturn("1.2.3").when(dependency).getVersion();
+ doReturn("xml").when(dependency).getType();
+ doReturn("features").when(dependency).getClassifier();
+
+ doReturn("odl-yangtools-util").when(mavenProject).getArtifactId();
+ doReturn("8.0.0-SNAPSHOT").when(mavenProject).getVersion();
+ doReturn(List.of(dependency)).when(mavenProject).getDependencies();
+
+ assertProcessFeature(
+ """
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ <features xmlns="http://karaf.apache.org/xmlns/features/v1.6.0" name="odl-yangtools-util">
+ <feature name="odl-yangtools-util" version="8.0.0.SNAPSHOT">
+ <feature version="[1.2.3,2)">odl-apache-commons-net</feature>
+ </feature>
+ </features>
+ """,
+ """
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ <features xmlns="http://karaf.apache.org/xmlns/features/v1.6.0" name="odl-yangtools-util">
+ <feature name="odl-yangtools-util">
+ <feature version="{{semVerRange}}">odl-apache-commons-net</feature>
+ </feature>
+ </features>
+ """);
+ assertProcessFeature(
+ """
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ <features xmlns="http://karaf.apache.org/xmlns/features/v1.6.0" name="odl-yangtools-util">
+ <feature name="odl-yangtools-util" version="8.0.0.SNAPSHOT">
+ <feature version="1.2.3">odl-apache-commons-net</feature>
+ </feature>
+ </features>
+ """,
+ """
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ <features xmlns="http://karaf.apache.org/xmlns/features/v1.6.0" name="odl-yangtools-util">
+ <feature name="odl-yangtools-util">
+ <feature version="{{versionAsInProject}}">odl-apache-commons-net</feature>
+ </feature>
+ </features>
+ """);
+ }
+
+ @Test
+ public void testProcessFeatureProjectVersion() throws MojoFailureException {
+ doReturn("1.2.3-SNAPSHOT").when(mavenProject).getVersion();
+
+ assertProcessFeature(
+ """
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ <features xmlns="http://karaf.apache.org/xmlns/features/v1.6.0" name="odl-yangtools-util">
+ <feature name="self" version="1.2.3.SNAPSHOT"/>
+ </features>
+ """,
+ """
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ <features xmlns="http://karaf.apache.org/xmlns/features/v1.6.0" name="odl-yangtools-util">
+ <feature name="self" version="{{projectVersion}}"/>
+ </features>
+ """);
+ }
+
+ @Test
+ public void testProcessRepository() throws MojoFailureException {
+ doReturn("example").when(dependency).getGroupId();
+ doReturn("example").when(dependency).getArtifactId();
+ doReturn("10.0.0-SNAPSHOT").when(dependency).getVersion();
+ doReturn("xml").when(dependency).getType();
+ doReturn("features").when(dependency).getClassifier();
+
+ doReturn(List.of(dependency)).when(mavenProject).getDependencies();
+
+ assertProcessFeature(
+ """
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ <features xmlns="http://karaf.apache.org/xmlns/features/v1.6.0" name="odl-yangtools-util">
+ <repository>mvn:example/example/10.0.0-SNAPSHOT/xml/features</repository>
+ </features>
+ """,
+ """
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ <features xmlns="http://karaf.apache.org/xmlns/features/v1.6.0" name="odl-yangtools-util">
+ <repository>mvn:example/example/{{versionAsInProject}}/xml/features</repository>
+ </features>
+ """);
+ }
+
+ @Test
+ public void testFullTranslation() throws MojoFailureException {
+ doReturn("odl-yangtools-util").when(mavenProject).getArtifactId();
+ doReturn("8.0.0-SNAPSHOT").when(mavenProject).getVersion();
+
+ final var trieMap = mock(Dependency.class);
+ doReturn("tech.pantheon.triemap").when(trieMap).getGroupId();
+ doReturn("pt-triemap").when(trieMap).getArtifactId();
+ doReturn("1.2.0").when(trieMap).getVersion();
+ doReturn("xml").when(trieMap).getType();
+ doReturn("features").when(trieMap).getClassifier();
+
+ final var concepts = mock(Dependency.class);
+ doReturn("org.opendaylight.yangtools").when(concepts).getGroupId();
+ doReturn("concepts").when(concepts).getArtifactId();
+ doReturn("8.0.0-SNAPSHOT").when(concepts).getVersion();
+
+ final var util = mock(Dependency.class);
+ doReturn("org.opendaylight.yangtools").when(util).getGroupId();
+ doReturn("util").when(util).getArtifactId();
+ doReturn("8.0.0-SNAPSHOT").when(util).getVersion();
+
+ doReturn(List.of(trieMap, concepts, util)).when(mavenProject).getDependencies();
+
+ assertProcessFeature(
+ """
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ <features xmlns="http://karaf.apache.org/xmlns/features/v1.6.0" name="odl-yangtools-util">
+ <repository>mvn:tech.pantheon.triemap/pt-triemap/1.2.0/xml/features</repository>
+ <feature name="odl-yangtools-util" description="Utilities" version="8.0.0.SNAPSHOT">
+ <details>YANG Tools common concepts and utilities</details>
+ <feature version="[1.2,2)">pt-triemap</feature>
+ <bundle>mvn:org.opendaylight.yangtools/concepts/8.0.0-SNAPSHOT</bundle>
+ <bundle>mvn:org.opendaylight.yangtools/util/8.0.0-SNAPSHOT</bundle>
+ </feature>
+ </features>
+ """,
+ """
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ <features xmlns="http://karaf.apache.org/xmlns/features/v1.6.0" name="odl-yangtools-util">
+ <repository>mvn:tech.pantheon.triemap/pt-triemap/{{versionAsInProject}}/xml/features</repository>
+ <feature name="odl-yangtools-util" description="Utilities" version="{{projectVersion}}">
+ <details>YANG Tools common concepts and utilities</details>
+ <feature version="{{semVerRange}}">pt-triemap</feature>
+ <bundle>mvn:org.opendaylight.yangtools/concepts/{{versionAsInProject}}</bundle>
+ <bundle>mvn:org.opendaylight.yangtools/util/{{versionAsInProject}}</bundle>
+ </feature>
+ </features>
+ """);
+ }
+
+ private void assertProcessFeature(final String expected, final String input) {
+ try {
+ final var features = GenerateFeatureMojo.readFeature(null,
+ new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)));
+ mojo.processFeatures(features);
+
+ final var output = new ByteArrayOutputStream();
+ GenerateFeatureMojo.writeFeature(features, output);
+ assertEquals(expected, output.toString(StandardCharsets.UTF_8));
+ } catch (IOException | JAXBException | MojoFailureException e) {
+ throw new AssertionError("Failed to process " + input, e);
+ }
+ }
+}