diff --git a/support-maven/bundle-auto-version/pom.xml b/support-maven/bundle-auto-version/pom.xml
new file mode 100644
index 0000000..79cd140
--- /dev/null
+++ b/support-maven/bundle-auto-version/pom.xml
@@ -0,0 +1,84 @@
+
+
+
+
+ ddf.support
+ support-maven
+ 2.3.18-SNAPSHOT
+
+ 4.0.0
+ maven-plugin
+
+ bundle-auto-version
+
+ Bundle Import Auto Versioning Plugin
+ Utility for enforcing OSGi bundle import versions automatically
+
+
+ 1.8
+ 1.8
+ 3.6.0
+ 3.6.0
+ 3.6.0
+ 3.5
+
+
+
+
+ org.apache.maven
+ maven-plugin-api
+ ${maven.plugin.api.version}
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+ ${maven.plugin.annotations.version}
+
+
+ org.apache.maven
+ maven-core
+ ${maven.core.version}
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-plugin-plugin
+ ${maven.plugin.plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven.surefire.plugin.version}
+
+
+ org.apache.maven.surefire
+ surefire-junit47
+ ${maven.surefire.plugin.version}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/support-maven/bundle-auto-version/src/main/java/org/codice/bundle/auto/version/BundleAutoVersionPlugin.java b/support-maven/bundle-auto-version/src/main/java/org/codice/bundle/auto/version/BundleAutoVersionPlugin.java
new file mode 100644
index 0000000..b051070
--- /dev/null
+++ b/support-maven/bundle-auto-version/src/main/java/org/codice/bundle/auto/version/BundleAutoVersionPlugin.java
@@ -0,0 +1,244 @@
+/**
+ * Copyright (c) Codice Foundation
+ *
+ *
This is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General private License as published by the Free Software Foundation, either version 3 of
+ * the License, or any later version.
+ *
+ *
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General private License for more details. A copy of the GNU Lesser General private
+ * License is distributed along with this program and can be found at
+ * .
+ */
+package org.codice.bundle.auto.version;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.jar.Manifest;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+import org.apache.maven.model.Build;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
+import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
+import org.apache.maven.plugin.AbstractMojo;
+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.project.MavenProject;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+
+@Mojo(
+ name = "bundle-auto-version",
+ defaultPhase = LifecyclePhase.PREPARE_PACKAGE,
+ threadSafe = true)
+public class BundleAutoVersionPlugin extends AbstractMojo {
+
+ private static final String COPYRIGHT_NOTICE =
+ "";
+
+ private static final Pattern IMPORT_VALUE_PATTERN =
+ Pattern.compile("(?:[^,\\\"]+|(?:\\\"[^\\\"]*\\\"))+|[^,]+");
+
+ private static final String IMPORT_PACKAGE_PROP = "Import-Package";
+ private static final String MAVEN_BUNDLE_PLUGIN_ARTIFACT_ID = "maven-bundle-plugin";
+ private static final String MAVEN_CONFIG_INSTRUCTIONS = "instructions";
+
+ @Parameter(defaultValue = "${project}", required = true, readonly = true)
+ private MavenProject mavenProject;
+
+ @Parameter(property = "excludeModules", defaultValue = "${}")
+ private List excludeModules;
+
+ @Override
+ public void execute() {
+ updateAndSaveModulePom(mavenProject.getModel(), mavenProject.getModel().getProjectDirectory());
+ }
+
+ private void updateAndSaveModulePom(Model model, File basePath) {
+ if (excludeModules.contains(model.getArtifactId())) {
+ getLog().info("Skipping bundle version update for excluded module " + model.getArtifactId());
+ return;
+ }
+
+ // Does this model have the maven-bundle-plugin
+ Plugin mavenBundlePlugin = getMavenBundlePlugin(model);
+ Consumer updateSubFunction = subModel -> updateAndSaveModulePom(subModel, basePath);
+
+ if (null == mavenBundlePlugin) {
+ getLog()
+ .warn(
+ "No "
+ + MAVEN_BUNDLE_PLUGIN_ARTIFACT_ID
+ + " configuration found for "
+ + model.getName());
+ getSubModuleModels(model, basePath).stream().forEach(updateSubFunction);
+ return;
+ }
+
+ List manifestImportsList = importStringToList(getManifestPackageImports(model));
+
+ if (null == manifestImportsList || manifestImportsList.isEmpty()) {
+ getLog()
+ .warn("No " + IMPORT_PACKAGE_PROP + " directive was found in 'MANIFEST.MF', skipping");
+
+ return;
+ }
+
+ try (FileReader pomFileReader = new FileReader(model.getPomFile())) {
+ model = new MavenXpp3Reader().read(pomFileReader);
+ } catch (IOException | XmlPullParserException e) {
+ getLog().error("Error parsing model for Maven project", e);
+ }
+
+ // The model loaded from pom is the one we're manipulating that's why we're re-getting the
+ // plugin configuration...
+ // to get a reference to the correct one
+ Xpp3Dom configInstructions = getPluginConfiguration(getMavenBundlePlugin(model));
+
+ if (configInstructions == null)
+ throw new RuntimeException("Unable to locate configuration for " + mavenBundlePlugin);
+
+ // Is there an `Import-Package` directive? If not, skip
+ if (null == configInstructions.getChild(IMPORT_PACKAGE_PROP)) {
+ getLog()
+ .info(
+ "No "
+ + IMPORT_PACKAGE_PROP
+ + " found in "
+ + MAVEN_BUNDLE_PLUGIN_ARTIFACT_ID
+ + " configuration, skipping");
+ return;
+ }
+
+ List pomImportsList =
+ importStringToList(configInstructions.getChild(IMPORT_PACKAGE_PROP).getValue());
+
+ if (pomImportsList.equals(manifestImportsList)) {
+ getLog().info("Package imports between pom.xml and MANIFEST.MF match, skipping");
+ return;
+ }
+
+ String manifestImportsString = manifestImportsList.stream().collect(Collectors.joining(",\n"));
+ configInstructions.getChild(IMPORT_PACKAGE_PROP).setValue(manifestImportsString);
+
+ saveProjectModel(model, mavenProject.getModel().getPomFile());
+
+ getSubModuleModels(model, basePath).stream().forEach(updateSubFunction);
+ }
+
+ private List getSubModuleModels(Model parentModel, File parentDirectory) {
+ return parentModel.getModules().stream()
+ .map(submodule -> new SubModel(parentDirectory.getAbsolutePath(), submodule, getLog()))
+ .map(SubModel::getModel)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ private List importStringToList(String packageImport) {
+ return StreamSupport.stream(
+ new MatchIterator(IMPORT_VALUE_PATTERN.matcher(packageImport)), false)
+ .sorted()
+ .collect(Collectors.toList());
+ }
+
+ private void saveProjectModel(Model model, File pomFile) {
+ try (FileWriter pomFileWriter = new FileWriter(pomFile)) {
+ new MavenXpp3Writer().write(pomFileWriter, model);
+ addCopyrightNotice(pomFile);
+ } catch (IOException e) {
+ getLog().error("Error saving project model to pom file", e);
+ }
+ }
+
+ private void addCopyrightNotice(File pomFile) throws IOException {
+ List pomFileLines = Files.lines(pomFile.toPath()).collect(Collectors.toList());
+
+ pomFileLines.set(0, appendCopyrightNotice(pomFileLines.get(0)));
+
+ Files.write(pomFile.toPath(), pomFileLines, Charset.forName("UTF-8"));
+ }
+
+ private String appendCopyrightNotice(String line) {
+ return line + "\n" + COPYRIGHT_NOTICE;
+ }
+
+ private Model readProjectPom(File pomFile) {
+ try (FileReader pomFileReader = new FileReader(pomFile)) {
+ return new MavenXpp3Reader().read(pomFileReader);
+ } catch (IOException | XmlPullParserException e) {
+ getLog().error("Error reading project model from pom file", e);
+ }
+
+ return null;
+ }
+
+ private String getManifestPackageImports(Model model) {
+ Path manifestFilePath =
+ Paths.get(model.getBuild().getOutputDirectory() + "/META-INF/MANIFEST.MF");
+
+ if (!manifestFilePath.toFile().exists()) return "";
+
+ Manifest manifest = null;
+
+ try (FileInputStream manifestInputStream = new FileInputStream(manifestFilePath.toString())) {
+ manifest = new Manifest(manifestInputStream);
+ } catch (IOException e) {
+ getLog().error("Error reading 'MANIFEST.MF' for the project", e);
+ }
+
+ if (manifest == null) throw new RuntimeException("Unable to locate generated 'MANIFEST.MF'");
+
+ return Optional.ofNullable(manifest)
+ .map(Manifest::getMainAttributes)
+ .map(attributes -> attributes.getValue(IMPORT_PACKAGE_PROP))
+ .orElse("");
+ }
+
+ private Plugin getMavenBundlePlugin(Model model) {
+ return Optional.ofNullable(model.getBuild()).map(Build::getPlugins)
+ .orElse(Collections.emptyList()).stream()
+ .filter(plugin -> MAVEN_BUNDLE_PLUGIN_ARTIFACT_ID.equals(plugin.getArtifactId()))
+ .findFirst()
+ .orElse(null);
+ }
+
+ private Xpp3Dom getPluginConfiguration(Plugin plugin) {
+ Xpp3Dom configuration =
+ Optional.ofNullable(plugin.getConfiguration()).map(Xpp3Dom.class::cast).orElse(null);
+
+ return Arrays.stream(configuration.getChildren())
+ .filter(entry -> MAVEN_CONFIG_INSTRUCTIONS.equals(entry.getName()))
+ .findFirst()
+ .orElse(null);
+ }
+}
diff --git a/support-maven/bundle-auto-version/src/main/java/org/codice/bundle/auto/version/MatchIterator.java b/support-maven/bundle-auto-version/src/main/java/org/codice/bundle/auto/version/MatchIterator.java
new file mode 100644
index 0000000..f7167ae
--- /dev/null
+++ b/support-maven/bundle-auto-version/src/main/java/org/codice/bundle/auto/version/MatchIterator.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) Codice Foundation
+ *
+ * This is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General private License as published by the Free Software Foundation, either version 3 of
+ * the License, or any later version.
+ *
+ *
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General private License for more details. A copy of the GNU Lesser General private
+ * License is distributed along with this program and can be found at
+ * .
+ */
+package org.codice.bundle.auto.version;
+
+import java.util.Spliterators;
+import java.util.function.Consumer;
+import java.util.regex.Matcher;
+
+class MatchIterator extends Spliterators.AbstractSpliterator {
+ private final Matcher matcher;
+
+ MatchIterator(Matcher m) {
+ super(m.regionEnd() - m.regionStart(), ORDERED | NONNULL);
+ matcher = m;
+ }
+
+ @Override
+ public boolean tryAdvance(Consumer super String> action) {
+ if (!matcher.find()) return false;
+ action.accept(matcher.group());
+ return true;
+ }
+}
diff --git a/support-maven/bundle-auto-version/src/main/java/org/codice/bundle/auto/version/SubModel.java b/support-maven/bundle-auto-version/src/main/java/org/codice/bundle/auto/version/SubModel.java
new file mode 100644
index 0000000..39acbf3
--- /dev/null
+++ b/support-maven/bundle-auto-version/src/main/java/org/codice/bundle/auto/version/SubModel.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) Codice Foundation
+ *
+ * This is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General private License as published by the Free Software Foundation, either version 3 of
+ * the License, or any later version.
+ *
+ *
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General private License for more details. A copy of the GNU Lesser General private
+ * License is distributed along with this program and can be found at
+ * .
+ */
+package org.codice.bundle.auto.version;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
+import org.apache.maven.plugin.logging.Log;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+
+public class SubModel {
+
+ private final String name;
+ private final Path path;
+ private final Log log;
+
+ SubModel(String parentPath, String submoduleName, Log log) {
+ this.path = Paths.get(parentPath, submoduleName);
+ this.name = submoduleName;
+ this.log = log;
+ }
+
+ Model getModel() {
+ try {
+ readProjectModel();
+ } catch (IOException | XmlPullParserException e) {
+ log.error("Unable to read model for " + name, e);
+ }
+
+ return null;
+ }
+
+ private Model readProjectModel() throws XmlPullParserException, IOException {
+ FileReader pomFileReader = new FileReader(new File(path.toFile(), "pom.xml"));
+ return new MavenXpp3Reader().read(pomFileReader);
+ }
+}
diff --git a/support-maven/pom.xml b/support-maven/pom.xml
index 0a2b8c5..3a93592 100644
--- a/support-maven/pom.xml
+++ b/support-maven/pom.xml
@@ -28,6 +28,7 @@
version-validation-plugin
artifact-size-enforcer
bundle-validation-plugin
+ bundle-auto-version
\ No newline at end of file