org.wisdom.maven.osgi.BundlePackager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of wisdom-maven-plugin Show documentation
Show all versions of wisdom-maven-plugin Show documentation
The Maven Wisdom Plugin allows building applications for Wisdom.
The newest version!
/*
* #%L
* Wisdom-Framework
* %%
* Copyright (C) 2013 - 2014 Wisdom Framework
* %%
* Licensed 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.
* #L%
*/
package org.wisdom.maven.osgi;
import aQute.bnd.osgi.*;
import aQute.libg.reporter.ReporterAdapter;
import com.google.common.base.Joiner;
import org.apache.commons.io.IOUtils;
import org.apache.felix.ipojo.manipulator.Pojoization;
import org.apache.felix.ipojo.manipulator.util.Classpath;
import org.wisdom.bnd.plugins.ImportedPackageRangeFixer;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*;
/**
* Packages the bundle using BND.
*/
public final class BundlePackager implements org.wisdom.maven.Constants {
private BundlePackager() {
//Hide default constructor
}
/**
* Creates the bundle.
*
* @param basedir the project's base directory
* @param output the output file
* @throws IOException occurs when the bundle cannot be built correctly.
*/
public static void bundle(File basedir, File output, Reporter reporter) throws IOException {
ProjectScanner scanner = new ProjectScanner(basedir);
// Loads the properties inherited from Maven.
Properties instructions = readMavenProperties(basedir);
// Loads the properties from the BND file.
Properties fromBnd = readInstructionsFromBndFile(basedir);
if (fromBnd == null) {
// No bnd files, use default instructions
instructions = populatePropertiesWithDefaults(basedir, instructions, scanner);
} else {
// We have a BND file.
// Do we have to merge ?
String noDefaultValue = fromBnd.getProperty("-no-default");
if (!"true".equalsIgnoreCase(noDefaultValue)) {
// So we need to merge the default with the bnd files
// 1) merge the instructions from the bnd files with the default
// 2) merge the resulting set of instruction onto the maven properties
// (and override the default)
instructions = Instructions.mergeAndOverrideExisting(
instructions,
populatePropertiesWithDefaults(basedir, fromBnd, scanner));
} else {
instructions = Instructions.mergeAndOverrideExisting(instructions, fromBnd);
}
}
// Manage Embedded dependencies
DependencyEmbedder ed = new DependencyEmbedder(instructions, reporter);
instructions = ed.generate(instructions, org.wisdom.maven.osgi.Classpath.load(basedir));
// Integrate custom headers added by other plugins.
instructions = mergeExtraHeaders(basedir, instructions);
// For debugging purpose, dump the instructions to target/osgi/instructions.properties
FileOutputStream fos = null;
try {
File out = new File(basedir, "target/osgi/instructions.properties");
fos = new FileOutputStream(out);
instructions.store(fos, "Wisdom BND Instructions");
} catch (IOException e) { // NOSONAR
// Ignore it.
} finally {
IOUtils.closeQuietly(fos);
}
// Instructions loaded, start the build sequence.
final Jar[] jars = org.wisdom.maven.osgi.Classpath.computeClassPath(basedir);
File bnd;
File ipojo;
Builder builder = null;
try {
builder = getOSGiBuilder(basedir, instructions, jars);
// The next sequence is weird
// First build the bundle with the given instruction
// Then analyze to apply the plugin and fix
// finally, rebuild with the updated metadata
// Without the first build, embedded dependencies and private packages from classpath are not analyzed.
builder.build();
builder.analyze();
builder.build();
reportErrors(builder.getWarnings(), builder.getErrors(), reporter);
bnd = File.createTempFile("bnd-", ".jar");
ipojo = File.createTempFile("ipojo-", ".jar");
builder.getJar().write(bnd);
} catch (Exception e) {
throw new IOException("Cannot build the OSGi bundle", e);
} finally {
if (builder != null) {
builder.close();
}
}
final Set elements = org.wisdom.maven.osgi.Classpath.computeClassPathElement(basedir);
Classpath classpath = new Classpath(elements);
Pojoization pojoization = new Pojoization();
pojoization.pojoization(bnd, ipojo, new File(basedir, "src/main/resources"),
classpath.createClassLoader());
reportErrors(pojoization.getWarnings(), pojoization.getErrors(), reporter);
Files.move(Paths.get(ipojo.getPath()), Paths.get(output.getPath()),
StandardCopyOption.REPLACE_EXISTING);
}
/**
* If a bundle has added extra headers, they are added to the bundle manifest.
*
* @param baseDir the project directory
* @param properties the current set of properties in which the read metadata are written
* @return the merged set of properties
*/
private static Properties mergeExtraHeaders(File baseDir, Properties properties) throws IOException {
File extra = new File(baseDir, EXTRA_HEADERS_FILE);
return Instructions.merge(properties, extra);
}
/**
* This method is used by plugin willing to add custom header to the bundle manifest.
*
* @param baseDir the project directory
* @param header the header to add
* @param value the value to write
* @throws IOException if the header cannot be added
*/
public static void addExtraHeaderToBundleManifest(File baseDir, String header, String value) throws IOException {
Properties props = new Properties();
File extra = new File(baseDir, EXTRA_HEADERS_FILE);
extra.getParentFile().mkdirs();
// If the file exist it loads it, if not nothing happens.
props = Instructions.merge(props, extra);
if (value != null) {
props.setProperty(header, value);
} else {
props.remove(header);
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(extra);
props.store(fos, "");
} finally {
IOUtils.closeQuietly(fos);
}
}
/**
* We should have generated a {@code target/osgi/osgi.properties} file with the metadata we inherit from Maven.
*
* @param baseDir the project directory
* @return the computed set of properties
*/
public static Properties readMavenProperties(File baseDir) throws IOException {
return Instructions.load(new File(baseDir, org.wisdom.maven.Constants.OSGI_PROPERTIES));
}
/**
* Populates the given properties object with our BND default instructions (computed for the current project).
* Entries are not added if the given properties file already contains these values.
*
* @param basedir the project's base directory
* @param instructions the current set of properties in which the read metadata are written
* @param scanner the project scanner to retrieve information about the sources and resources contained in the
* project
* @return the final set of instructions (containing the default instructions merged into the
* given set of properties)
* @throws IOException if something wrong happens
*/
protected static Properties populatePropertiesWithDefaults(File basedir, Properties instructions,
ProjectScanner scanner) throws IOException {
Properties defaultInstructions = new Properties();
List privates = new ArrayList<>();
List exports = new ArrayList<>();
// Do local resources
String resources = getLocalResources(basedir, false, scanner);
if (!resources.isEmpty()) {
defaultInstructions.put(Analyzer.INCLUDE_RESOURCE, resources);
}
defaultInstructions.put(Constants.IMPORT_PACKAGE, "*");
for (String s : scanner.getPackagesFromMainSources()) {
if (Packages.shouldBeExported(s)) {
exports.add(s);
} else {
if (!s.isEmpty() && !s.equals(".")) {
privates.add(s + ";-split-package:=merge-first");
}
}
}
defaultInstructions.put(Constants.PRIVATE_PACKAGE, Packages.toClause(privates));
defaultInstructions.put(Constants.EXPORT_PACKAGE, Packages.toClause(exports));
return Instructions.mergeAndSkipExisting(instructions, defaultInstructions);
}
/**
* Gets local resources.
*
* @param basedir the project's base directory
* @param test whether or not we compute the test resources
* @param scanner the project scanner to retrieve information about the sources and resources contained in the
* project
* @return the resource clause
*/
public static String getLocalResources(File basedir, boolean test, ProjectScanner scanner) {
final String basePath = basedir.getAbsolutePath();
String target = "target/classes";
if (test) {
target = "target/test-classes";
}
Set files = scanner.getLocalResources(test);
Set pathSet = new LinkedHashSet<>();
for (String name : files) {
String path = target + '/' + name;
// make relative to project
if (path.startsWith(basePath)) {
if (path.length() == basePath.length()) {
path = ".";
} else {
path = path.substring(basePath.length() + 1);
}
}
// replace windows backslash with a slash
// this is a workaround for a problem with bnd 0.0.189
if (File.separatorChar != '/') {
name = name.replace(File.separatorChar, '/');
path = path.replace(File.separatorChar, '/');
}
// copy to correct place
path = name + '=' + path;
pathSet.add(path);
}
return Joiner.on(", ").join(pathSet);
}
private static Builder getOSGiBuilder(File basedir, Properties properties,
Jar[] classpath) {
Builder builder = new Builder();
synchronized (BundlePackager.class) {
builder.setBase(basedir);
}
// Add the range fixer plugin
final ImportedPackageRangeFixer plugin = new ImportedPackageRangeFixer();
plugin.setReporter(builder);
plugin.setProperties(Collections.emptyMap());
builder.addBasicPlugin(plugin);
builder.setProperties(Instructions.sanitize(properties));
if (classpath != null) {
builder.setClasspath(classpath);
}
return builder;
}
private static Properties readInstructionsFromBndFile(File basedir) throws IOException {
File instructionFile = new File(basedir, INSTRUCTIONS_FILE);
if (!instructionFile.isFile()) {
return null;
}
return Instructions.load(instructionFile);
}
private static boolean reportErrors(List warnings, List errors, Reporter reporter) {
for (String msg : warnings) {
reporter.warn(msg);
}
boolean hasErrors = false;
String fileNotFound = "Input file does not exist: ";
for (String msg : errors) {
if (msg.startsWith(fileNotFound) && msg.endsWith("~")) {
// treat as warning; this error happens when you have duplicate entries in Include-Resource
String duplicate = Processor.removeDuplicateMarker(msg.substring(fileNotFound.length()));
reporter.warn("Duplicate path '" + duplicate + "' in Include-Resource");
} else {
reporter.error(msg);
hasErrors = true;
}
}
return hasErrors;
}
}