blob: 91f188ee6601cc761fbb3ed78ffa3be89bc76e92 [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.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.zip.ZipEntry;
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.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.osgi.impl.bundle.obr.resource.Manifest;
/**
* Generates the features XML file
*
* @version $Revision: 1.1 $
* @goal generate-features-file
* @phase compile
* @execute phase="compile"
* @requiresDependencyResolution runtime
* @inheritByDefault true
* @description Generates the features XML file
*/
@SuppressWarnings("unchecked")
public class GenerateFeaturesFileMojo extends MojoSupport {
protected static final String SEPARATOR = "/";
/**
* The file to generate
*
* @parameter default-value="${project.build.directory}/classes/feature.xml"
*/
private File outputFile;
/**
* The name of the feature, which defaults to the artifact ID if its not
* specified
*
* @parameter default-value="${project.artifactId}"
*/
private String featureName;
/**
* 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";
/**
* Should we generate a <feature> for the current project?
*
* @parameter default-value="false"
*/
private boolean includeProject = false;
/**
* Should we generate a <feature> for the current project's <dependency>s?
*
* @parameter default-value="true"
*/
private boolean includeDependencies = true;
/**
* The kernel version for which to generate the bundle
*
* @parameter
*/
private String kernelVersion;
/**
* A properties file containing bundle translations
*
* @parameter
*/
private File translation;
/*
* The translations
*/
private Map<String, Map<VersionRange, String>> translations = new HashMap<String, Map<VersionRange,String>>() {
@Override
public Map<VersionRange, String> get(Object key) {
if (super.get(key) == null) {
super.put(key.toString(), new HashMap<VersionRange, String>());
}
return super.get(key);
}
};
/*
* These bundles are the features that will be built
*/
private Set<Artifact> features = new HashSet<Artifact>();
/*
* These bundles are provided by SMX4 and will be excluded from <feature/>
* generation
*/
private Set<Artifact> provided = new HashSet<Artifact>();
/*
* List of bundles included in the current feature
*/
private Set<Artifact> currentFeature = new HashSet<Artifact>();
/*
* List of missing bundles
*/
private Set<Artifact> missingBundles = new TreeSet<Artifact>();
public void execute() throws MojoExecutionException, MojoFailureException {
OutputStream out = null;
try {
prepare();
getLog().info(String.format("-- Start generating %s --", outputFile.getAbsolutePath()));
outputFile.getParentFile().mkdirs();
out = new FileOutputStream(outputFile);
PrintStream printer = new PrintStream(out);
populateProperties(printer);
getLog().info(String.format("-- Done generating %s --", outputFile.getAbsolutePath()));
// now lets attach it
projectHelper.attachArtifact(project, attachmentArtifactType, attachmentArtifactClassifier, outputFile);
} catch (Exception e) {
throw new MojoExecutionException("Unable to create dependencies file: " + e, e);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
getLog().info("Failed to close: " + outputFile + ". Reason: " + e, e);
}
}
}
}
protected void populateProperties(PrintStream out) throws ArtifactResolutionException, ArtifactNotFoundException, IOException {
out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
out.println("<features>");
if (includeProject) {
writeCurrentProjectFeature(out);
}
if (includeDependencies) {
writeProjectDependencyFeatures(out);
}
out.println("</features>");
}
private void prepare() throws ArtifactResolutionException, ArtifactNotFoundException, IOException, InvalidVersionSpecificationException {
if (translation != null) {
InputStream stream = null;
try {
stream = new BufferedInputStream(new FileInputStream(translation));
Properties file = new Properties();
file.load(stream);
ArrayList<String> stringNames = getStringNames(file);
for (String key : stringNames) {
String[] elements = key.split("/");
translations.get(String.format("%s/%s", elements[0], elements[1]))
.put(VersionRange.createFromVersionSpec(elements[2]), file.getProperty(key));
}
getLog().info("Loaded " + translations.size() + " bundle name translation rules from " + translation.getAbsolutePath());
} finally {
if (stream != null) {
stream.close();
}
}
}
Artifact kernel = factory.createArtifact("org.apache.servicemix.kernel",
"apache-servicemix-kernel",
kernelVersion, Artifact.SCOPE_PROVIDED, "pom");
resolver.resolve(kernel, remoteRepos, localRepo);
getLog().info("-- List of bundles provided by ServiceMix Kernel " + kernelVersion + " --");
for (Artifact artifact : getDependencies(kernel)) {
getLog().info(" " + artifact);
provided.add(artifact);
}
getLog().info("-- <end of list> --");
}
private ArrayList<String> getStringNames(Properties file) {
// this method simulate the Properties.stringPropertyNames() of JDK6 in order to make this class
// compile with jdk5
ArrayList<String> ret = new ArrayList<String>();
Enumeration<?> name = file.propertyNames();
while (name.hasMoreElements()) {
Object ele = name.nextElement();
if (ele instanceof String && file.get(ele) instanceof String) {
ret.add((String)ele);
}
}
return ret;
}
private void writeProjectDependencyFeatures(PrintStream out) {
Set<Artifact> dependencies = (Set<Artifact>)project.getDependencyArtifacts();
dependencies.removeAll(provided);
for (Artifact artifact : dependencies) {
getLog().info(" Generating feature " + artifact.getArtifactId() + " from " + artifact);
out.println(" <feature name='" + artifact.getArtifactId() + "'>");
currentFeature.clear();
writeBundle(out, artifact);
features.add(artifact);
out.println(" </feature>");
}
if (missingBundles.size() > 0) {
getLog().info("-- Some bundles were missing --");
for (Artifact artifact : missingBundles) {
getLog().info(String.format(" %s", artifact));
}
}
}
private void writeBundle(PrintStream out, Artifact artifact) {
Artifact replacement = getReplacement(artifact);
if (replacement != null) {
writeBundle(out, replacement);
return;
}
if (isProvided(artifact)) {
getLog().debug(String.format("Skipping '%s' -- bundle will be provided at runtime", artifact));
return;
}
if (features.contains(artifact)) {
// if we already created a feature for this one, just add that instead of the bundle
out.println(String.format(" <feature>%s</feature>", artifact.getArtifactId()));
return;
}
// first write the dependencies
for (Artifact dependency : getDependencies(artifact)) {
if (dependency.isOptional() || Artifact.SCOPE_TEST.equals(dependency.getScope())) {
// omit optional dependencies
getLog().debug(String.format("Omitting optional and/or test scoped dependency '%s' for '%s'",
dependency, artifact));
continue;
}
getLog().debug(String.format("Adding '%s' as a dependency for '%s'", dependency, artifact));
writeBundle(out, dependency);
}
// skip the bundle if it was already added to this feature previously
if (!currentFeature.add(artifact)) {
getLog().debug(String.format("Artifact '%s' was already added to the current feature", artifact));
return;
}
// and then write the bundle itself
if (isBundle(artifact)) {
getLog().info(String.format(" adding bundle %s", artifact));
writeBundle(out, artifact.getGroupId(), artifact.getArtifactId(), artifact.getBaseVersion());
} else {
Artifact wrapper = findServicemixBundle(artifact);
if (wrapper != null) {
getLog().info(String.format(" adding bundle %s (for %s)", wrapper, artifact));
writeBundle(out, wrapper.getGroupId(), wrapper.getArtifactId(), wrapper.getBaseVersion());
} else {
getLog().error(String.format(" unable to find suitable bundle for artifact '%s'", artifact));
missingBundles.add(artifact);
}
}
}
private Artifact getReplacement(Artifact artifact) {
String key = String.format("%s/%s", artifact.getGroupId(), artifact.getArtifactId());
String bundle = null;
for (VersionRange range : translations.get(key).keySet()) {
try {
if (range.containsVersion(artifact.getSelectedVersion())) {
bundle = translations.get(key).get(range);
break;
}
} catch (OverConstrainedVersionException e) {
bundle = null;
}
}
if (bundle != null) {
String[] split = bundle.split("/");
return factory.createArtifact(split[0], split[1], split[2], Artifact.SCOPE_PROVIDED, artifact.getArtifactHandler().getPackaging());
} else {
return null;
}
}
private Artifact findServicemixBundle(Artifact artifact) {
Artifact noVersionWrapper = factory.createArtifact("org.apache.servicemix.bundles",
"org.apache.servicemix.bundles." + artifact.getArtifactId(),
"",
artifact.getScope(), artifact.getType());
try {
List versions = artifactMetadataSource.retrieveAvailableVersions(noVersionWrapper, localRepo, remoteRepos);
Artifact wrapper = factory.createArtifact("org.apache.servicemix.bundles",
"org.apache.servicemix.bundles." + artifact.getArtifactId(),
getBestVersionForArtifact(artifact, versions),
artifact.getScope(), artifact.getType());
// let's check if the servicemix bundle for this artifact exists
resolver.resolve(wrapper, remoteRepos, localRepo);
for (Artifact dependency : getDependencies(wrapper)) {
//some of these wrapper bundles provide for multiple JAR files, no need to include any of them after adding the wrapper
getLog().debug(String.format("'%s' also provides '%s'", wrapper, dependency));
currentFeature.add(dependency);
}
return wrapper;
} catch (ArtifactResolutionException e) {
getLog().debug("Couldn't find a ServiceMix bundle for " + artifact, e);
} catch (ArtifactNotFoundException e) {
getLog().debug("Couldn't find a ServiceMix bundle for " + artifact, e);
} catch (ArtifactMetadataRetrievalException e) {
getLog().debug("Couldn't find a ServiceMix bundle for " + artifact, e);
}
if (artifact.getArtifactId().contains("-")) {
//let's try to see if we can't find a bundle wrapping multiple artifacts (e.g. mina -> mina-core, mina-codec, ...)
return findServicemixBundle(factory.createArtifact(artifact.getGroupId(), artifact.getArtifactId().split("-")[0],
artifact.getVersion(), artifact.getScope(), artifact.getType()));
} else {
return null;
}
}
protected String getBestVersionForArtifact(Artifact artifact, List<ArtifactVersion> versions) throws ArtifactMetadataRetrievalException {
if (versions.size() == 0) {
throw new ArtifactMetadataRetrievalException("No wrapper bundle available for " + artifact);
}
Collections.sort(versions, Collections.reverseOrder());
//check for same version
for (ArtifactVersion version : versions) {
if (version.toString().startsWith(artifact.getVersion())) {
return version.toString();
}
}
//check for same major/minor version
for (ArtifactVersion version : versions) {
String[] elements = version.toString().split("\\.");
if (elements.length >= 2 && artifact.getVersion().startsWith(elements[0] + "." + elements[1])) {
return version.toString();
}
}
throw new ArtifactMetadataRetrievalException("No suitable version found for " + artifact + " wrapper bundle");
}
private boolean isProvided(Artifact bundle) {
for (Artifact artifact : provided) {
if (bundle.getArtifactId().equals(artifact.getArtifactId())
&& bundle.getGroupId().equals(artifact.getGroupId())) {
return true;
}
}
return false;
}
private boolean isBundle(Artifact artifact) {
if (artifact.getArtifactHandler().getPackaging().equals("bundle")) {
return true;
} else {
try {
resolver.resolve(artifact, remoteRepos, localRepo);
ZipFile file = new ZipFile(artifact.getFile());
ZipEntry entry = file.getEntry("META-INF/MANIFEST.MF");
Manifest manifest = new Manifest(file.getInputStream(entry));
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().warn("Unable to determine if " + artifact + " is a bundle; defaulting to false", e);
} catch (IOException e) {
getLog().warn("Unable to determine if " + artifact + " is a bundle; defaulting to false", e);
} catch (Exception e) {
getLog().warn("Unable to determine if " + artifact + " is a bundle; defaulting to false", e);
}
}
return false;
}
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;
}
private void writeCurrentProjectFeature(PrintStream out) {
out.println(" <feature name='" + featureName + "'>");
writeBundle(out, project.getGroupId(), project.getArtifactId(), project.getVersion());
out.println();
Iterator iterator = project.getDependencies().iterator();
while (iterator.hasNext()) {
Dependency dependency = (Dependency)iterator.next();
if (isValidDependency(dependency)) {
out.print(" ");
writeBundle(out, dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion());
}
}
out.println(" </feature>");
}
protected boolean isValidDependency(Dependency dependency) {
// TODO filter out only compile time dependencies which are OSGi
// bundles?
return true;
}
protected void writeBundle(PrintStream out, String groupId, String artifactId, String version) {
out.print(" <bundle>mvn:");
out.print(groupId);
out.print("/");
out.print(artifactId);
out.print("/");
out.print(version);
out.print("</bundle>");
out.println();
}
}