blob: dcf5e2c88b890f072dfa757b03634ce474811d94 [file] [log] [blame]
/**
*
* 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.servicemix.tooling.features;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Stack;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.InvalidArtifactRTException;
import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException;
import org.apache.maven.artifact.metadata.ResolutionGroup;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.DefaultArtifactCollector;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.shared.dependency.tree.DependencyNode;
import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor;
import org.osgi.impl.bundle.obr.resource.Manifest;
import org.osgi.impl.bundle.obr.resource.ManifestEntry;
import org.osgi.impl.bundle.obr.resource.VersionRange;
/**
* Generates the features XML file
*
* @version $Revision: 1.1 $
* @goal generate-features-xml
* @phase compile
* @execute phase="compile"
* @requiresDependencyResolution runtime
* @inheritByDefault true
* @description Generates the features XML file
*/
@SuppressWarnings("unchecked")
public class GenerateFeaturesXmlMojo extends MojoSupport {
protected static final String SEPARATOR = "/";
/**
* The dependency tree builder to use.
*
* @component
* @required
* @readonly
*/
private DependencyTreeBuilder dependencyTreeBuilder;
/**
* The file to generate
*
* @parameter default-value="${project.build.directory}/classes/feature.xml"
*/
private File outputFile;
/**
* The artifact type for attaching the generated file to the project
*
* @parameter default-value="xml"
*/
private String attachmentArtifactType = "xml";
/**
* The artifact classifier for attaching the generated file to the project
*
* @parameter default-value="features"
*/
private String attachmentArtifactClassifier = "features";
/**
* The kernel version for which to generate the bundle
*
* @parameter
*/
private String kernelVersion;
/*
* A list of packages exported by the kernel
*/
private Map<String, VersionRange> kernelExports = new HashMap<String, VersionRange>();
/**
* A file containing the list of bundles
*
* @parameter
*/
private File bundles;
/*
* A set of known bundles
*/
private Set<String> knownBundles = new HashSet<String>();
/*
* A list of exports by the bundles
*/
private Map<String, Map<VersionRange, Artifact>> bundleExports = new HashMap<String, Map<VersionRange, Artifact>>();
/*
* The set of system exports
*/
private List<String> systemExports = new LinkedList<String>();
/*
* These bundles are the features that will be built
*/
private Map<Artifact, Feature> features = new HashMap<Artifact, Feature>();
public void execute() throws MojoExecutionException, MojoFailureException {
PrintStream out = null;
try {
out = new PrintStream(new FileOutputStream(outputFile));
readSystemPackages();
readKernelBundles();
readBundles();
discoverBundles();
writeFeatures(out);
// now lets attach it
projectHelper.attachArtifact(project, attachmentArtifactType, attachmentArtifactClassifier, outputFile);
} catch (Exception e) {
getLog().error(e);
throw new MojoExecutionException("Unable to create features.xml file: " + e, e);
} finally {
if (out != null) {
out.close();
}
}
}
/*
* Read all the system provided packages from the <code>config.properties</code> file
*/
private void readSystemPackages() throws IOException {
Properties properties = new Properties();
properties.load(getClass().getClassLoader().getResourceAsStream("config.properties"));
readSystemPackages(properties, "jre-1.5");
readSystemPackages(properties, "osgi");
}
private void readSystemPackages(Properties properties, String key) {
String packages = (String) properties.get(key);
for (String pkg : packages.split(";")) {
systemExports.add(pkg.trim());
}
}
/*
* Download a Kernel distro and check the list of bundles provided by the Kernel
*/
private void readKernelBundles() throws ArtifactResolutionException, ArtifactNotFoundException, MojoExecutionException,
ZipException, IOException, DependencyTreeBuilderException {
final Collection<Artifact> kernelArtifacts;
if (kernelVersion == null) {
getLog().info("Step 1: Building list of provided bundle exports");
kernelArtifacts = new HashSet<Artifact>();
DependencyNode tree = dependencyTreeBuilder.buildDependencyTree(project, localRepo, factory, artifactMetadataSource, new ArtifactFilter() {
public boolean include(Artifact artifact) {
return true;
}
}, new DefaultArtifactCollector());
tree.accept(new DependencyNodeVisitor() {
public boolean endVisit(DependencyNode node) {
// we want the next sibling too
return true;
}
public boolean visit(DependencyNode node) {
if (node.getState() != DependencyNode.OMITTED_FOR_CONFLICT) {
Artifact artifact = node.getArtifact();
if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()) && !artifact.getType().equals("pom")) {
kernelArtifacts.add(artifact);
}
}
// we want the children too
return true;
}
});
} else {
getLog().info("Step 1 : Building list of kernel exports");
getLog().warn("Use of 'kernelVersion' is deprecated -- use a dependency with scope 'provided' instead");
Artifact kernel = factory.createArtifact("org.apache.servicemix.kernel", "apache-servicemix-kernel", kernelVersion, Artifact.SCOPE_PROVIDED, "pom");
resolver.resolve(kernel, remoteRepos, localRepo);
kernelArtifacts = getDependencies(kernel);
}
for (Artifact artifact : kernelArtifacts) {
registerKernelBundle(artifact);
}
getLog().info("...done!");
}
private void registerKernelBundle(Artifact artifact) throws ArtifactResolutionException, ArtifactNotFoundException, ZipException,
IOException {
Manifest manifest = getManifest(artifact);
if (manifest.getExports() != null) {
for (ManifestEntry entry : (List<ManifestEntry>)manifest.getExports()) {
kernelExports.put(entry.getName(), entry.getVersion());
getLog().debug(" adding kernel export " + entry.getName() + " (" + entry.getVersion() + ")");
}
}
registerBundle(artifact);
}
/*
* Read the list of bundles we can use to satisfy links
*/
private void readBundles() throws IOException, ArtifactResolutionException, ArtifactNotFoundException {
BufferedReader reader = null;
try {
if (bundles != null) {
getLog().info("Step 2 : Building a list of exports for bundles in " + bundles.getAbsolutePath());
reader = new BufferedReader(new FileReader(bundles));
String line = reader.readLine();
while (line != null) {
if (line.contains("/") && !line.startsWith("#")) {
String[] elements = line.split("/");
Artifact artifact = factory.createArtifact(elements[0], elements[1], elements[2], Artifact.SCOPE_PROVIDED,
elements[3]);
registerBundle(artifact);
}
line = reader.readLine();
}
} else {
getLog().info("Step 2 : No Bundle file supplied for building list of exports");
}
} finally {
if (reader != null) {
reader.close();
}
}
getLog().info("...done!");
}
/*
* Auto-discover bundles currently in the dependencies
*/
private void discoverBundles() throws ArtifactResolutionException, ArtifactNotFoundException, ZipException, IOException {
getLog().info("Step 3 : Discovering bundles in Maven dependencies");
for (Artifact dependency : (Set<Artifact>) project.getArtifacts()) {
// we will generate a feature for this afterwards
if (project.getDependencyArtifacts().contains(dependency)) {
continue;
}
// this is a provided bundle, has been handled in step 1
if (dependency.getScope().equals(Artifact.SCOPE_PROVIDED)) {
continue;
}
if (isDiscoverableBundle(dependency)) {
getLog().info(" Discovered " + dependency);
registerBundle(dependency);
}
}
getLog().info("...done!");
}
/*
* Write all project dependencies as feature
*/
private void writeFeatures(PrintStream out) throws ArtifactResolutionException, ArtifactNotFoundException,
ZipException, IOException {
getLog().info("Step 4 : Generating " + outputFile.getAbsolutePath());
out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
out.println("<features>");
Set<Artifact> dependencies = (Set<Artifact>)project.getDependencyArtifacts();
for (Artifact artifact : dependencies) {
if (!artifact.getScope().equals(Artifact.SCOPE_PROVIDED) && !artifact.getType().equals("pom")) {
getLog().info(" Generating feature " + artifact.getArtifactId() + " from " + artifact);
Feature feature = getFeature(artifact);
feature.write(out);
registerFeature(artifact, feature);
}
}
out.println("</features>");
getLog().info("...done!");
}
/*
* Get the feature for an artifact
*/
private Feature getFeature(Artifact artifact) throws ArtifactResolutionException, ArtifactNotFoundException, ZipException, IOException {
Feature feature = new Feature(artifact);
addRequirements(artifact, feature);
return feature;
}
/*
* Only auto-discover an OSGi bundle
* - if it is not already known as a feature itself
* - if it is not another version of an already known bundle
*/
private boolean isDiscoverableBundle(Artifact artifact) {
if (isBundle(artifact) && !isFeature(artifact) && !artifact.getScope().equals(Artifact.SCOPE_PROVIDED)) {
for (String known : knownBundles) {
String[] elements = known.split("/");
if (artifact.getGroupId().equals(elements[0]) &&
artifact.getArtifactId().equals(elements[1])) {
getLog().debug(String.format(" Avoid auto-discovery for %s because of existing bundle %s",
toString(artifact), known));
return false;
}
}
return true;
}
return false;
}
/*
* Check if the given artifact is a bundle
*/
private boolean isBundle(Artifact artifact) {
if (knownBundles.contains(toString(artifact)) || artifact.getArtifactHandler().getPackaging().equals("bundle")) {
return true;
} else {
try {
Manifest manifest = getManifest(artifact);
if (manifest.getBsn() != null) {
getLog().debug(String.format("MANIFEST.MF for '%s' contains Bundle-Name '%s'",
artifact, manifest.getBsn().getName()));
return true;
}
} catch (ZipException e) {
getLog().debug("Unable to determine if " + artifact + " is a bundle; defaulting to false", e);
} catch (IOException e) {
getLog().debug("Unable to determine if " + artifact + " is a bundle; defaulting to false", e);
} catch (Exception e) {
getLog().debug("Unable to determine if " + artifact + " is a bundle; defaulting to false", e);
}
}
return false;
}
/*
* Add requirements for an artifact to a feature
*/
private void addRequirements(Artifact artifact, Feature feature) throws ArtifactResolutionException, ArtifactNotFoundException, ZipException, IOException {
Manifest manifest = getManifest(artifact);
Collection<ManifestEntry> remaining = getRemainingImports(manifest);
Artifact previous = null;
for (ManifestEntry entry : remaining) {
Artifact add = null;
Map<VersionRange, Artifact> versions = bundleExports.get(entry.getName());
if (versions != null) {
for (VersionRange range : versions.keySet()) {
add = versions.get(range);
if (range.compareTo(entry.getVersion()) == 0) {
add = versions.get(range);
}
}
}
if (add == null) {
if (isOptional(entry)) {
// debug logging for optional dependency...
getLog().debug(String.format(" Unable to find suitable bundle for optional dependency %s (%s)",
entry.getName(), entry.getVersion()));
} else {
// ...but a warning for a mandatory dependency
getLog().warn(
String.format(" Unable to find suitable bundle for dependency %s (%s) (required by %s)",
entry.getName(), entry.getVersion(), artifact.getArtifactId()));
}
} else {
if (!add.equals(previous) && feature.push(add) && !isFeature(add)) {
//and get requirements for the bundle we just added
getLog().debug(" Getting requirements for " + add);
addRequirements(add, feature);
}
}
previous = add;
}
}
/*
* Check if a given bundle is itself being generated as a feature
*/
private boolean isFeature(Artifact artifact) {
return features.containsKey(artifact);
}
/*
* Check a manifest entry and check if the resolution for the import has been marked as optional
*/
private boolean isOptional(ManifestEntry entry) {
return entry.getAttributes() != null && entry.getAttributes().get("resolution:") != null
&& entry.getAttributes().get("resolution:").equals("optional");
}
/*
* Register a bundle, enlisting all packages it provides
*/
private void registerBundle(Artifact artifact) throws ArtifactResolutionException, ArtifactNotFoundException, ZipException,
IOException {
getLog().debug("Registering bundle " + artifact);
knownBundles.add(toString(artifact));
Manifest manifest = getManifest(artifact);
for (ManifestEntry entry : getManifestEntries(manifest.getExports())) {
Map<VersionRange, Artifact> versions = bundleExports.get(entry.getName());
if (versions == null) {
versions = new HashMap<VersionRange, Artifact>();
}
versions.put(entry.getVersion(), artifact);
getLog().debug(String.format(" %s exported by bundle %s", entry.getName(), artifact));
bundleExports.put(entry.getName(), versions);
}
}
/*
* Register a feature and also register the bundle for the feature
*/
private void registerFeature(Artifact artifact, Feature feature) throws ArtifactResolutionException, ArtifactNotFoundException, ZipException,
IOException {
features.put(artifact, feature);
registerBundle(artifact);
}
/*
* Determine the list of imports to be resolved
*/
private Collection<ManifestEntry> getRemainingImports(Manifest manifest) {
// take all imports
Collection<ManifestEntry> input = getManifestEntries(manifest.getImports());
Collection<ManifestEntry> output = new LinkedList<ManifestEntry>(input);
// remove imports satisfied by exports in the same bundle
for (ManifestEntry entry : input) {
for (ManifestEntry export : getManifestEntries(manifest.getExports())) {
if (entry.getName().equals(export.getName())) {
output.remove(entry);
}
}
}
// remove imports for packages exported by the kernel
for (ManifestEntry entry : input) {
for (String export : kernelExports.keySet()) {
if (entry.getName().equals(export)) {
output.remove(entry);
}
}
}
// remove imports for packages exported by the system bundle
for (ManifestEntry entry : input) {
if (systemExports.contains(entry.getName())) {
output.remove(entry);
}
}
return output;
}
private Collection<ManifestEntry> getManifestEntries(List imports) {
if (imports == null) {
return new LinkedList<ManifestEntry>();
} else {
return (Collection<ManifestEntry>)imports;
}
}
private Manifest getManifest(Artifact artifact) throws ArtifactResolutionException, ArtifactNotFoundException, ZipException,
IOException {
File localFile = new File(localRepo.pathOf(artifact));
ZipFile file;
if (localFile.exists()) {
//avoid going over to the repository if the file is already on the disk
file = new ZipFile(localFile);
} else {
resolver.resolve(artifact, remoteRepos, localRepo);
file = new ZipFile(artifact.getFile());
}
return new Manifest(file.getInputStream(file.getEntry("META-INF/MANIFEST.MF")));
}
private List<Artifact> getDependencies(Artifact artifact) {
List<Artifact> list = new ArrayList<Artifact>();
try {
ResolutionGroup pom = artifactMetadataSource.retrieve(artifact, localRepo, remoteRepos);
if (pom != null) {
list.addAll(pom.getArtifacts());
}
} catch (ArtifactMetadataRetrievalException e) {
getLog().warn("Unable to retrieve metadata for " + artifact + ", not including dependencies for it");
} catch (InvalidArtifactRTException e) {
getLog().warn("Unable to retrieve metadata for " + artifact + ", not including dependencies for it");
}
return list;
}
public static String toString(Artifact artifact) {
return String.format("%s/%s/%s", artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion());
}
private class Feature {
private Stack<Artifact> artifacts = new Stack<Artifact>();
private final Artifact artifact;
private Feature(Artifact artifact) {
super();
this.artifact = artifact;
artifacts.push(artifact);
}
public boolean push(Artifact item) {
if (artifacts.contains(item)) {
artifacts.remove(item);
artifacts.push(item);
return false;
}
if (!artifacts.contains(item)) {
artifacts.push(item);
return true;
}
return false;
}
public void write(PrintStream out) {
out.println(" <feature name='" + artifact.getArtifactId() + "' version='"
+ artifact.getBaseVersion() + "'>");
Stack<Artifact> resulting = new Stack<Artifact>();
resulting.addAll(artifacts);
// remove dependencies for included features
for (Artifact next : artifacts) {
if (isFeature(next)) {
resulting.removeAll(features.get(next).getDependencies());
}
}
while (!resulting.isEmpty()) {
Artifact next = resulting.pop();
if (isFeature(next)) {
out.println(" <feature version='"
+ next.getBaseVersion() + "'>" + String.format("%s</feature>", next.getArtifactId()));
} else {
out.println(String.format(" <bundle>mvn:%s/%s/%s</bundle>",
next.getGroupId(), next.getArtifactId(), next.getBaseVersion()));
}
}
out.println(" </feature>");
}
public List<Artifact> getDependencies() {
List<Artifact> dependencies = new LinkedList<Artifact>(artifacts);
dependencies.remove(artifact);
return dependencies;
}
}
}