FELIX-308: initial implementation to support embedding dependencies
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@568873 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/org/apache/felix/bundleplugin/BundlePlugin.java b/bundleplugin/src/main/java/org/apache/felix/bundleplugin/BundlePlugin.java
index 4f36806..486d912 100644
--- a/bundleplugin/src/main/java/org/apache/felix/bundleplugin/BundlePlugin.java
+++ b/bundleplugin/src/main/java/org/apache/felix/bundleplugin/BundlePlugin.java
@@ -221,6 +221,13 @@
builder.setProperties(properties);
builder.setClasspath(classpath);
+ Collection embeddableArtifacts = getEmbeddableArtifacts(properties);
+ if (embeddableArtifacts.size() > 0)
+ {
+ // add BND instructions to embed selected dependencies
+ new DependencyEmbedder(embeddableArtifacts).processHeaders(properties);
+ }
+
builder.build();
Jar jar = builder.getJar();
this.doMavenMetadata(project, jar);
@@ -538,4 +545,19 @@
return resourcePaths.toString();
}
+
+ Collection getEmbeddableArtifacts(Properties properties)
+ {
+ String embedTransitive = properties.getProperty(DependencyEmbedder.EMBED_TRANSITIVE);
+ if (Boolean.valueOf(embedTransitive).booleanValue())
+ {
+ // includes transitive dependencies
+ return project.getArtifacts();
+ }
+ else
+ {
+ // only includes direct dependencies
+ return project.getDependencyArtifacts();
+ }
+ }
}
diff --git a/bundleplugin/src/main/java/org/apache/felix/bundleplugin/DependencyEmbedder.java b/bundleplugin/src/main/java/org/apache/felix/bundleplugin/DependencyEmbedder.java
new file mode 100644
index 0000000..7d107a8
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/felix/bundleplugin/DependencyEmbedder.java
@@ -0,0 +1,362 @@
+/*
+ * 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.
+ */
+package org.apache.felix.bundleplugin;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.MojoExecutionException;
+
+import aQute.lib.osgi.Analyzer;
+import aQute.lib.osgi.Instruction;
+import aQute.lib.osgi.header.OSGiHeader;
+
+/**
+ * Add BND directives to embed selected dependencies inside a bundle
+ *
+ * @author stuart.mcculloch@jayway.net (Stuart McCulloch)
+ */
+public class DependencyEmbedder
+{
+ public static String EMBED_DEPENDENCY = "Embed-Dependency";
+ public static String EMBED_DIRECTORY = "Embed-Directory";
+ public static String EMBED_STRIP_GROUP = "Embed-StripGroup";
+ public static String EMBED_STRIP_VERSION = "Embed-StripVersion";
+ public static String EMBED_TRANSITIVE = "Embed-Transitive";
+
+ /**
+ * Dependency artifacts.
+ */
+ private final Collection dependencyArtifacts;
+
+ /**
+ * Inlined artifacts.
+ */
+ private final Collection inlinedArtifacts;
+
+ /**
+ * Embedded artifacts.
+ */
+ private final Collection embeddedArtifacts;
+
+ public DependencyEmbedder(Collection dependencyArtifacts)
+ {
+ this.dependencyArtifacts = dependencyArtifacts;
+
+ this.inlinedArtifacts = new HashSet();
+ this.embeddedArtifacts = new HashSet();
+ }
+
+ public void processHeaders(Properties properties)
+ throws MojoExecutionException
+ {
+ this.inlinedArtifacts.clear();
+ this.embeddedArtifacts.clear();
+
+ String embedDependencyHeader = properties.getProperty(EMBED_DEPENDENCY);
+ if (null != embedDependencyHeader && embedDependencyHeader.length() > 0)
+ {
+ Map embedInstructions = OSGiHeader.parseHeader(embedDependencyHeader);
+ this.processEmbedInstructions(embedInstructions);
+
+ for (Iterator i = this.inlinedArtifacts.iterator(); i.hasNext();)
+ {
+ DependencyEmbedder.inlineDependency(properties, (Artifact)i.next());
+ }
+ for (Iterator i = this.embeddedArtifacts.iterator(); i.hasNext();)
+ {
+ DependencyEmbedder.embedDependency(properties, (Artifact)i.next());
+ }
+ }
+ }
+
+ protected static abstract class DependencyFilter
+ {
+ Instruction instruction;
+ String defaultValue;
+
+ public DependencyFilter(String expression)
+ {
+ this(expression, "");
+ }
+
+ public DependencyFilter(String expression, String defaultValue)
+ {
+ this.instruction = Instruction.getPattern(expression);
+ this.defaultValue = defaultValue;
+ }
+
+ public void filter(Collection dependencies)
+ {
+ for (Iterator i = dependencies.iterator(); i.hasNext();)
+ {
+ if (false == matches((Artifact)i.next()))
+ {
+ i.remove();
+ }
+ }
+ }
+
+ abstract boolean matches(Artifact dependency);
+
+ boolean matches(String text)
+ {
+ if (null == text)
+ {
+ text = defaultValue;
+ }
+ boolean result = this.instruction.matches(text);
+ return this.instruction.isNegated() ? !result : result;
+ }
+ }
+
+ protected void processEmbedInstructions(Map embedInstructions)
+ throws MojoExecutionException
+ {
+ DependencyFilter filter;
+ for (Iterator clauseIterator = embedInstructions.entrySet().iterator(); clauseIterator.hasNext();)
+ {
+ boolean inline = false;
+
+ // must use a fresh *modifiable* collection for each unique clause
+ Collection filteredDependencies = new HashSet(this.dependencyArtifacts);
+
+ // CLAUSE: REGEXP --> { ATTRIBUTE MAP }
+ Map.Entry clause = (Map.Entry)clauseIterator.next();
+
+ filter = new DependencyFilter((String)clause.getKey())
+ {
+ boolean matches(Artifact dependency)
+ {
+ return super.matches(dependency.getArtifactId());
+ }
+ };
+
+ // FILTER ON MAIN CLAUSE
+ filter.filter(filteredDependencies);
+
+ for (Iterator attrIterator = ((Map)clause.getValue()).entrySet().iterator(); attrIterator.hasNext();)
+ {
+ // ATTRIBUTE: KEY --> REGEXP
+ Map.Entry attr = (Map.Entry)attrIterator.next();
+
+ if ("groupId".equals(attr.getKey()))
+ {
+ filter = new DependencyFilter((String)attr.getValue())
+ {
+ boolean matches(Artifact dependency)
+ {
+ return super.matches(dependency.getGroupId());
+ }
+ };
+ }
+ else if ("artifactId".equals(attr.getKey()))
+ {
+ filter = new DependencyFilter((String)attr.getValue())
+ {
+ boolean matches(Artifact dependency)
+ {
+ return super.matches(dependency.getArtifactId());
+ }
+ };
+ }
+ else if ("version".equals(attr.getKey()))
+ {
+ filter = new DependencyFilter((String)attr.getValue())
+ {
+ boolean matches(Artifact dependency)
+ {
+ try
+ {
+ // use the symbolic version if available (ie. 1.0.0-SNAPSHOT)
+ return super.matches(dependency.getSelectedVersion().toString());
+ }
+ catch (Exception e)
+ {
+ return super.matches(dependency.getVersion());
+ }
+ }
+ };
+ }
+ else if ("scope".equals(attr.getKey()))
+ {
+ filter = new DependencyFilter((String)attr.getValue(), "compile")
+ {
+ boolean matches(Artifact dependency)
+ {
+ return super.matches(dependency.getScope());
+ }
+ };
+ }
+ else if ("type".equals(attr.getKey()))
+ {
+ filter = new DependencyFilter((String)attr.getValue(), "jar")
+ {
+ boolean matches(Artifact dependency)
+ {
+ return super.matches(dependency.getType());
+ }
+ };
+ }
+ else if ("classifier".equals(attr.getKey()))
+ {
+ filter = new DependencyFilter((String)attr.getValue())
+ {
+ boolean matches(Artifact dependency)
+ {
+ return super.matches(dependency.getClassifier());
+ }
+ };
+ }
+ else if ("optional".equals(attr.getKey()))
+ {
+ filter = new DependencyFilter((String)attr.getValue(), "false")
+ {
+ boolean matches(Artifact dependency)
+ {
+ return super.matches(""+dependency.isOptional());
+ }
+ };
+ }
+ else if ("inline".equals(attr.getKey()))
+ {
+ inline = Boolean.valueOf((String)attr.getValue()).booleanValue();
+
+ continue;
+ }
+ else
+ {
+ throw new MojoExecutionException("Unexpected attribute " + attr.getKey());
+ }
+
+ // FILTER ON EACH ATTRIBUTE
+ filter.filter(filteredDependencies);
+ }
+
+ if (inline)
+ {
+ this.inlinedArtifacts.addAll(filteredDependencies);
+ }
+ else
+ {
+ this.embeddedArtifacts.addAll(filteredDependencies);
+ }
+ }
+
+ // remove any inlined artifacts from the embedded list
+ this.embeddedArtifacts.removeAll(this.inlinedArtifacts);
+ }
+
+ public static void embedDependency(Properties properties, Artifact dependency)
+ {
+ File jarFile = dependency.getFile();
+
+ if (null != jarFile && jarFile.exists())
+ {
+ String bundleClassPath = properties.getProperty(Analyzer.BUNDLE_CLASSPATH);
+ String includeResource = properties.getProperty(Analyzer.INCLUDE_RESOURCE);
+
+ if (null == bundleClassPath)
+ {
+ bundleClassPath = ".,";
+ }
+ else if (bundleClassPath.length() > 0)
+ {
+ bundleClassPath += ",";
+ }
+
+ if (null == includeResource)
+ {
+ includeResource = "";
+ }
+ else if (includeResource.length() > 0)
+ {
+ includeResource += ",";
+ }
+
+ String embedDirectory = properties.getProperty(EMBED_DIRECTORY);
+ String embedStripGroup = properties.getProperty(EMBED_STRIP_GROUP);
+ String embedStripVersion = properties.getProperty(EMBED_STRIP_VERSION);
+
+ if ("".equals(embedDirectory) || ".".equals(embedDirectory))
+ {
+ embedDirectory = null;
+ }
+
+ if (false == Boolean.valueOf(embedStripGroup).booleanValue())
+ {
+ embedDirectory = new File(embedDirectory, dependency.getGroupId()).getPath();
+ }
+
+ File targetFile;
+ if (Boolean.valueOf(embedStripVersion).booleanValue())
+ {
+ targetFile = new File(embedDirectory, dependency.getArtifactId() + ".jar");
+ }
+ else
+ {
+ targetFile = new File(embedDirectory, jarFile.getName());
+ }
+
+ bundleClassPath += targetFile;
+ includeResource += targetFile + "=" + jarFile;
+
+ properties.setProperty(Analyzer.BUNDLE_CLASSPATH, bundleClassPath);
+ properties.setProperty(Analyzer.INCLUDE_RESOURCE, includeResource);
+ }
+ }
+
+ public static void inlineDependency(Properties properties, Artifact dependency)
+ {
+ File jarFile = dependency.getFile();
+
+ if (null != jarFile && jarFile.exists())
+ {
+ String includeResource = properties.getProperty(Analyzer.INCLUDE_RESOURCE);
+
+ if (null == includeResource)
+ {
+ includeResource = "";
+ }
+ else if (includeResource.length() > 0)
+ {
+ includeResource += ",";
+ }
+
+ includeResource += "@" + jarFile;
+
+ properties.setProperty(Analyzer.INCLUDE_RESOURCE, includeResource);
+ }
+ }
+
+ public Collection getInlinedArtifacts()
+ {
+ return this.inlinedArtifacts;
+ }
+
+ public Collection getEmbeddedArtifacts()
+ {
+ return this.embeddedArtifacts;
+ }
+}