blob: 486d912383621061e3fb0adb7cec56f995710bb2 [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.felix.bundleplugin;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.zip.ZipException;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.License;
import org.apache.maven.model.Model;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.osgi.Maven2OsgiConverter;
import aQute.lib.osgi.Analyzer;
import aQute.lib.osgi.Builder;
import aQute.lib.osgi.EmbeddedResource;
import aQute.lib.osgi.FileResource;
import aQute.lib.osgi.Jar;
/**
* Create an OSGi bundle from Maven project
*
* @goal bundle
* @phase package
* @requiresDependencyResolution runtime
* @description build an OSGi bundle jar
*/
public class BundlePlugin extends AbstractMojo {
/**
* Project types which this plugin supports.
*
* @parameter
*/
private List supportedProjectTypes = Arrays.asList(new String[]{"jar","bundle"});
/**
* The directory for the generated bundles.
*
* @parameter expression="${project.build.outputDirectory}"
* @required
*/
private File outputDirectory;
/**
* The directory for the pom
*
* @parameter expression="${basedir}"
* @required
*/
private File baseDir;
/**
* The directory for the generated JAR.
*
* @parameter expression="${project.build.directory}"
* @required
*/
private String buildDirectory;
/**
* The Maven project.
*
* @parameter expression="${project}"
* @required
* @readonly
*/
private MavenProject project;
/**
* The name of the generated JAR file.
*
* @parameter
*/
private Map instructions = new HashMap();
/**
* @component
*/
private Maven2OsgiConverter maven2OsgiConverter;
protected Maven2OsgiConverter getMaven2OsgiConverter()
{
return this.maven2OsgiConverter;
}
void setMaven2OsgiConverter(Maven2OsgiConverter maven2OsgiConverter)
{
this.maven2OsgiConverter = maven2OsgiConverter;
}
protected MavenProject getProject()
{
return this.project;
}
/**
* @see org.apache.maven.plugin.AbstractMojo#execute()
*/
public void execute() throws MojoExecutionException
{
Properties properties = new Properties();
// ignore project types not supported, useful when the plugin is configured in the parent pom
if (!this.supportedProjectTypes.contains(this.getProject().getArtifact().getType()))
{
this.getLog().debug("Ignoring project " + this.getProject().getArtifact() + " : type " + this.getProject().getArtifact().getType() +
" is not supported by bundle plugin, supported types are " + this.supportedProjectTypes );
return;
}
this.execute(this.project, this.instructions, properties);
}
protected void execute(MavenProject project, Map instructions, Properties properties)
throws MojoExecutionException
{
try
{
this.execute(project, instructions, properties, this.getClasspath(project));
}
catch ( IOException e )
{
throw new MojoExecutionException("Error calculating classpath for project " + project, e);
}
}
/* transform directives from their XML form to the expected BND syntax (eg. _include becomes -include) */
protected Map transformDirectives(Map instructions)
{
Map transformedInstructions = new HashMap();
for (Iterator i = instructions.entrySet().iterator(); i.hasNext();)
{
Map.Entry e = (Map.Entry)i.next();
String key = (String)e.getKey();
if (key.startsWith("_"))
{
key = "-"+key.substring(1);
}
String value = (String)e.getValue();
if (null == value)
{
value = "";
}
else
{
value = value.replaceAll("[\r\n]", "");
}
transformedInstructions.put(key, value);
}
return transformedInstructions;
}
protected void execute(MavenProject project, Map instructions, Properties properties, Jar[] classpath)
throws MojoExecutionException
{
try
{
File jarFile = new File(this.getBuildDirectory(), this.getBundleName(project));
properties.putAll(this.getDefaultProperties(project));
String bsn = project.getGroupId() + "." + project.getArtifactId();
if (!instructions.containsKey(Analyzer.PRIVATE_PACKAGE))
{
properties.put(Analyzer.EXPORT_PACKAGE, bsn + ".*");
}
properties.putAll(this.transformDirectives(instructions));
// pass maven resource paths onto BND analyzer
String mavenResourcePaths = this.getMavenResourcePaths();
if (mavenResourcePaths.length() > 0)
{
final String includeResource = (String)properties.get(Analyzer.INCLUDE_RESOURCE);
if (includeResource != null)
{
this.getLog().warn(Analyzer.INCLUDE_RESOURCE + ": overriding " + mavenResourcePaths + " with " + includeResource );
} else
{
properties.put(Analyzer.INCLUDE_RESOURCE, mavenResourcePaths);
}
}
Builder builder = new Builder();
builder.setBase(this.baseDir);
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);
builder.setJar(jar);
List errors = builder.getErrors();
List warnings = builder.getWarnings();
if (errors.size() > 0)
{
jarFile.delete();
for (Iterator e = errors.iterator(); e.hasNext();)
{
String msg = (String) e.next();
this.getLog().error("Error building bundle " + project.getArtifact() + " : " + msg);
}
throw new MojoFailureException("Found errors, see log");
}
else
{
jarFile.getParentFile().mkdirs();
builder.getJar().write(jarFile);
project.getArtifact().setFile(jarFile);
}
for (Iterator w = warnings.iterator(); w.hasNext();)
{
String msg = (String) w.next();
this.getLog().warn("Warning building bundle " + project.getArtifact() + " : " + msg);
}
}
catch (Exception e)
{
throw new MojoExecutionException("Unknown error occurred", e);
}
}
private Map getProperies(Model projectModel, String prefix, Object model)
{
Map properties = new HashMap();
Method methods[] = Model.class.getDeclaredMethods();
for (int i = 0; i < methods.length; i++)
{
String name = methods[i].getName();
if ( name.startsWith("get") )
{
try
{
Object v = methods[i].invoke(projectModel, null );
if ( v != null )
{
name = prefix + Character.toLowerCase(name.charAt(3)) + name.substring(4);
if ( v.getClass().isArray() )
properties.put( name, Arrays.asList((Object[])v).toString() );
else
properties.put( name, v );
}
}
catch (Exception e)
{
// too bad
}
}
}
return properties;
}
private StringBuffer printLicenses(List licenses)
{
if (licenses == null || licenses.size() == 0)
return null;
StringBuffer sb = new StringBuffer();
String del = "";
for (Iterator i = licenses.iterator(); i.hasNext();)
{
License l = (License) i.next();
String url = l.getUrl();
sb.append(del);
sb.append(url);
del = ", ";
}
return sb;
}
/**
* @param jar
* @throws IOException
*/
private void doMavenMetadata(MavenProject project, Jar jar) throws IOException {
String path = "META-INF/maven/" + project.getGroupId() + "/"
+ project.getArtifactId();
File pomFile = new File(this.baseDir, "pom.xml");
jar.putResource(path + "/pom.xml", new FileResource(pomFile));
Properties p = new Properties();
p.put("version", project.getVersion());
p.put("groupId", project.getGroupId());
p.put("artifactId", project.getArtifactId());
ByteArrayOutputStream out = new ByteArrayOutputStream();
p.store(out, "Generated by org.apache.felix.plugin.bundle");
jar.putResource(path + "/pom.properties", new EmbeddedResource(out
.toByteArray(), System.currentTimeMillis()));
}
/**
* @return
* @throws ZipException
* @throws IOException
*/
protected Jar[] getClasspath(MavenProject project) throws ZipException, IOException
{
List list = new ArrayList();
if (this.getOutputDirectory() != null && this.getOutputDirectory().exists())
{
list.add(new Jar(".", this.getOutputDirectory()));
}
Set artifacts = project.getArtifacts();
for (Iterator it = artifacts.iterator(); it.hasNext();)
{
Artifact artifact = (Artifact) it.next();
if (artifact.getArtifactHandler().isAddedToClasspath())
{
if (Artifact.SCOPE_COMPILE.equals(artifact.getScope())
|| Artifact.SCOPE_SYSTEM.equals(artifact.getScope())
|| Artifact.SCOPE_PROVIDED.equals(artifact.getScope()))
{
File file = this.getFile(artifact);
if (file == null)
{
throw new RuntimeException("File is not available for artifact " + artifact + " in project " + project.getArtifact());
}
Jar jar = new Jar(artifact.getArtifactId(), file);
list.add(jar);
}
}
}
Jar[] cp = new Jar[list.size()];
list.toArray(cp);
return cp;
}
/**
* Get the file for an Artifact
*
* @param artifact
*/
protected File getFile(Artifact artifact)
{
return artifact.getFile();
}
private void header(Properties properties, String key, Object value)
{
if (value == null)
return;
if (value instanceof Collection && ((Collection) value).isEmpty())
return;
properties.put(key, value.toString());
}
/**
* Convert a Maven version into an OSGi compliant version
*
* @param version Maven version
* @return the OSGi version
*/
protected String convertVersionToOsgi(String version)
{
return this.getMaven2OsgiConverter().getVersion( version );
}
/**
* TODO this should return getMaven2Osgi().getBundleFileName( project.getArtifact() )
*/
protected String getBundleName(MavenProject project)
{
return project.getBuild().getFinalName() + ".jar";
}
public String getBuildDirectory()
{
return this.buildDirectory;
}
void setBuildDirectory(String buildirectory)
{
this.buildDirectory = buildirectory;
}
/**
* Get a list of packages inside a Jar
*
* @param jar
* @return list of package names
*/
public List getPackages(Jar jar)
{
List packages = new ArrayList();
for (Iterator p = jar.getDirectories().entrySet().iterator(); p.hasNext();)
{
Map.Entry directory = (Map.Entry) p.next();
String path = (String) directory.getKey();
String pack = path.replace('/', '.');
packages.add(pack);
}
return packages;
}
protected Properties getDefaultProperties(MavenProject project)
{
Properties properties = new Properties();
// Setup defaults
String bsn = project.getGroupId() + "." + project.getArtifactId();
properties.put(Analyzer.BUNDLE_SYMBOLICNAME, bsn);
properties.put(Analyzer.IMPORT_PACKAGE, "*");
String version = this.getMaven2OsgiConverter().getVersion( project.getVersion() );
properties.put(Analyzer.BUNDLE_VERSION, version);
this.header(properties, Analyzer.BUNDLE_DESCRIPTION, project
.getDescription());
this.header(properties, Analyzer.BUNDLE_LICENSE, this.printLicenses(project
.getLicenses()));
this.header(properties, Analyzer.BUNDLE_NAME, project.getName());
if (project.getOrganization() != null)
{
this.header(properties, Analyzer.BUNDLE_VENDOR, project
.getOrganization().getName());
if (project.getOrganization().getUrl() != null)
{
this.header(properties, Analyzer.BUNDLE_DOCURL, project
.getOrganization().getUrl());
}
}
properties.putAll(project.getProperties());
properties.putAll(project.getModel().getProperties());
properties.putAll( this.getProperies(project.getModel(), "project.build.", project.getBuild()));
properties.putAll( this.getProperies(project.getModel(), "pom.", project.getModel()));
properties.putAll( this.getProperies(project.getModel(), "project.", project));
properties.put("project.baseDir", this.baseDir );
properties.put("project.build.directory", this.getBuildDirectory() );
properties.put("project.build.outputdirectory", this.getOutputDirectory() );
return properties;
}
void setBasedir(File basedir)
{
this.baseDir = basedir;
}
File getOutputDirectory()
{
return this.outputDirectory;
}
void setOutputDirectory(File outputDirectory)
{
this.outputDirectory = outputDirectory;
}
String getMavenResourcePaths()
{
final String basePath = this.baseDir.getAbsolutePath();
StringBuffer resourcePaths = new StringBuffer();
for (Iterator i = this.project.getResources().iterator(); i.hasNext();)
{
org.apache.maven.model.Resource resource = (org.apache.maven.model.Resource)i.next();
final String sourcePath = resource.getDirectory();
final String targetPath = resource.getTargetPath();
// ignore empty or non-local resources
if (new File(sourcePath).exists() && ((targetPath == null) || (targetPath.indexOf("..") < 0)))
{
String path = sourcePath;
// make relative to basedir
if (path.startsWith(basePath))
{
path = path.substring(basePath.length() + 1);
}
if (targetPath != null)
{
path = targetPath + '=' + path;
}
if (resourcePaths.length() > 0)
{
resourcePaths.append(',');
}
if (resource.isFiltering())
{
resourcePaths.append('{');
resourcePaths.append(path);
resourcePaths.append('}');
}
else
{
resourcePaths.append(path);
}
}
}
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();
}
}
}