bndlib: A Swiss Army Knife for OSGi providing comprehensive bundle manipulation and analysis capabilities
—
Extensible plugin system for customizing build processes and analysis, enabling powerful extensions to the bndlib functionality.
Base interface for all BND plugins providing common configuration and reporting capabilities.
/**
* Base interface for all BND plugins
*/
public interface Plugin {
/** Set plugin properties from configuration */
public void setProperties(Map<String,String> map);
/** Set reporter for logging and error reporting */
public void setReporter(Reporter processor);
}Plugin interface for custom class analysis during bundle creation and JAR processing.
/**
* Plugin that can analyze classes during bundle creation
*/
public interface AnalyzerPlugin extends Plugin {
/** Analyze JAR and modify analyzer state */
public boolean analyzeJar(Analyzer analyzer) throws Exception;
/** Get plugin priority for execution order */
default int getPriority() { return 0; }
/** Check if plugin applies to current context */
default boolean applies(Analyzer analyzer) { return true; }
}Usage Examples:
import aQute.bnd.service.AnalyzerPlugin;
import aQute.bnd.osgi.Analyzer;
import aQute.bnd.osgi.Clazz;
// Custom analyzer plugin example
public class CustomAnalyzerPlugin implements AnalyzerPlugin {
private String prefix = "custom";
@Override
public void setProperties(Map<String, String> map) {
prefix = map.getOrDefault("prefix", "custom");
}
@Override
public void setReporter(Reporter reporter) {
this.reporter = reporter;
}
@Override
public boolean analyzeJar(Analyzer analyzer) throws Exception {
// Custom analysis logic
for (Clazz clazz : analyzer.getClassspace().values()) {
if (clazz.getClassName().toString().startsWith(prefix)) {
// Mark for special processing
analyzer.setProperty("Custom-Classes",
analyzer.getProperty("Custom-Classes", "") + "," + clazz.getClassName());
}
}
return false; // Don't stop processing other plugins
}
}
// Register and use plugin
Analyzer analyzer = new Analyzer();
analyzer.getPlugins().add(new CustomAnalyzerPlugin());
analyzer.analyze();Plugin interface for custom build processing and bundle manipulation.
/**
* Plugin for custom build processing
*/
public interface BuilderPlugin extends Plugin {
/** Process builder before build */
public void processBuild(Builder builder) throws Exception;
/** Post-process built JAR */
public void postProcess(Builder builder, Jar jar) throws Exception;
/** Get plugin execution phase */
default Phase getPhase() { return Phase.BUILD; }
}
/**
* Build phases for plugin execution
*/
public enum Phase {
PRE_BUILD, // Before build starts
BUILD, // During build
POST_BUILD // After build completes
}Plugin interface for custom OSGi framework launchers and runtime environments.
/**
* Plugin for custom OSGi launchers
*/
public interface LauncherPlugin extends Plugin {
/** Launch OSGi framework with configuration */
public int launch(ProjectLauncher launcher) throws Exception;
/** Get launcher type identifier */
public String getType();
/** Check if launcher is available in current environment */
public boolean isAvailable();
/** Get launcher configuration */
public Map<String, String> getConfiguration();
}
/**
* Project launcher configuration
*/
public class ProjectLauncher {
/** Get project being launched */
public Project getProject();
/** Get run bundles */
public Collection<String> getRunBundles();
/** Get framework */
public String getRunFramework();
/** Get JVM arguments */
public List<String> getRunJVMArgs();
/** Get program arguments */
public List<String> getRunProgramArgs();
/** Get run properties */
public Map<String, String> getRunProperties();
}Plugin interface for custom bundle verification and validation.
/**
* Plugin for custom bundle verification
*/
public interface VerifierPlugin extends Plugin {
/** Verify bundle and report issues */
public void verify(Analyzer analyzer) throws Exception;
/** Get verification severity level */
default Severity getSeverity() { return Severity.ERROR; }
/** Check if verifier applies to bundle */
default boolean applies(Analyzer analyzer) { return true; }
}
/**
* Verification severity levels
*/
public enum Severity {
INFO, // Informational
WARNING, // Warning but not blocking
ERROR // Error that blocks build
}Usage Examples:
import aQute.bnd.service.VerifierPlugin;
import aQute.bnd.osgi.Analyzer;
// Custom verifier plugin example
public class BundleNamingVerifier implements VerifierPlugin {
private String requiredPrefix;
@Override
public void setProperties(Map<String, String> map) {
requiredPrefix = map.get("required.prefix");
}
@Override
public void setReporter(Reporter reporter) {
this.reporter = reporter;
}
@Override
public void verify(Analyzer analyzer) throws Exception {
String bsn = analyzer.getBsn();
if (requiredPrefix != null && !bsn.startsWith(requiredPrefix)) {
analyzer.error("Bundle symbolic name must start with: " + requiredPrefix);
}
// Verify version format
String version = analyzer.getProperty("Bundle-Version");
if (version != null && !isValidVersion(version)) {
analyzer.warning("Bundle version format may not be semantic: " + version);
}
}
private boolean isValidVersion(String version) {
return version.matches("\\d+\\.\\d+\\.\\d+(\\..+)?");
}
}Plugin interface for adding custom commands to bnd tools.
/**
* Plugin for custom bnd commands
*/
public interface CommandPlugin extends Plugin {
/** Execute command with arguments */
public void execute(String command, String[] args) throws Exception;
/** Get supported commands */
public String[] getCommands();
/** Get command help text */
public String getHelp(String command);
/** Get command usage syntax */
public String getUsage(String command);
}Plugin interface for generating additional resources during build.
/**
* Plugin for generating additional resources
*/
public interface GeneratePlugin extends Plugin {
/** Generate resources for builder */
public void generate(Builder builder) throws Exception;
/** Get generator type */
public String getType();
/** Check if generator should run */
default boolean shouldGenerate(Builder builder) { return true; }
}Classes for managing plugin registration and lifecycle.
/**
* Registry for managing plugins
*/
public interface Registry {
/** Get plugins of specified type */
public <T extends Plugin> List<T> getPlugins(Class<T> type);
/** Add plugin to registry */
public void addPlugin(Plugin plugin);
/** Remove plugin from registry */
public void removePlugin(Plugin plugin);
/** Get plugin by name */
public Plugin getPlugin(String name);
/** List all registered plugins */
public List<Plugin> getPlugins();
}
/**
* Plugin manager for loading and configuring plugins
*/
public class PluginManager {
/** Load plugin from class name */
public static Plugin loadPlugin(String className, ClassLoader loader) throws Exception;
/** Configure plugin with properties */
public static void configurePlugin(Plugin plugin, Map<String, String> properties);
/** Validate plugin configuration */
public static List<String> validatePlugin(Plugin plugin);
/** Get plugin dependencies */
public static List<String> getPluginDependencies(Plugin plugin);
}Common built-in plugins that demonstrate plugin patterns.
/**
* Spring XML processing plugin
*/
public class SpringXMLType implements AnalyzerPlugin {
public boolean analyzeJar(Analyzer analyzer) throws Exception;
}
/**
* JPA entity analysis plugin
*/
public class JPAComponent implements AnalyzerPlugin {
public boolean analyzeJar(Analyzer analyzer) throws Exception;
}
/**
* Maven dependency plugin
*/
public class MavenDependencyPlugin implements BuilderPlugin {
public void processBuild(Builder builder) throws Exception;
}
/**
* Felix launcher plugin
*/
public class FelixLauncher implements LauncherPlugin {
public int launch(ProjectLauncher launcher) throws Exception;
public String getType() { return "felix"; }
}Complete Plugin Development Example:
import aQute.bnd.service.*;
import aQute.bnd.osgi.*;
// Complete custom plugin example
public class ComprehensivePlugin implements AnalyzerPlugin, BuilderPlugin, VerifierPlugin {
private Reporter reporter;
private Map<String, String> properties = new HashMap<>();
@Override
public void setProperties(Map<String, String> map) {
this.properties.putAll(map);
}
@Override
public void setReporter(Reporter reporter) {
this.reporter = reporter;
}
// AnalyzerPlugin implementation
@Override
public boolean analyzeJar(Analyzer analyzer) throws Exception {
reporter.progress(0.1f, "Starting custom analysis");
// Analyze classes for custom annotations
for (Clazz clazz : analyzer.getClassspace().values()) {
if (hasCustomAnnotation(clazz)) {
String packageName = clazz.getClassName().getPackage();
analyzer.getContained().put(
analyzer.getPackageRef(packageName),
new Attrs()
);
}
}
reporter.progress(1.0f, "Custom analysis complete");
return false;
}
// BuilderPlugin implementation
@Override
public void processBuild(Builder builder) throws Exception {
// Add custom manifest headers
String customValue = properties.getOrDefault("custom.header", "default");
builder.setProperty("X-Custom-Header", customValue);
// Generate additional resources
generateCustomResources(builder);
}
@Override
public void postProcess(Builder builder, Jar jar) throws Exception {
// Post-process the built JAR
addCustomResources(jar);
}
// VerifierPlugin implementation
@Override
public void verify(Analyzer analyzer) throws Exception {
// Verify custom requirements
String requiredHeader = properties.get("required.header");
if (requiredHeader != null) {
String value = analyzer.getProperty(requiredHeader);
if (value == null) {
analyzer.error("Required header missing: " + requiredHeader);
}
}
// Verify custom package exports
verifyCustomExports(analyzer);
}
private boolean hasCustomAnnotation(Clazz clazz) throws Exception {
// Check for custom annotation
return clazz.annotations() != null &&
clazz.annotations().contains("Lcom/example/CustomAnnotation;");
}
private void generateCustomResources(Builder builder) throws Exception {
// Generate custom configuration files
String config = generateConfiguration();
builder.getJar().putResource("META-INF/custom-config.properties",
new EmbeddedResource(config, System.currentTimeMillis()));
}
private void addCustomResources(Jar jar) throws Exception {
// Add additional resources to final JAR
String readme = generateReadme();
jar.putResource("README.txt",
new EmbeddedResource(readme, System.currentTimeMillis()));
}
private void verifyCustomExports(Analyzer analyzer) throws Exception {
// Verify that API packages are properly exported
Packages exports = analyzer.getExports();
Packages contained = analyzer.getContained();
for (PackageRef pkg : contained.keySet()) {
if (pkg.getFQN().contains(".api.") && !exports.containsKey(pkg)) {
analyzer.warning("API package not exported: " + pkg.getFQN());
}
}
}
private String generateConfiguration() {
StringBuilder config = new StringBuilder();
config.append("# Generated configuration\n");
config.append("plugin.name=").append(getClass().getSimpleName()).append("\n");
config.append("plugin.version=1.0.0\n");
for (Map.Entry<String, String> entry : properties.entrySet()) {
config.append(entry.getKey()).append("=").append(entry.getValue()).append("\n");
}
return config.toString();
}
private String generateReadme() {
return "This bundle was processed by " + getClass().getSimpleName() +
" plugin.\nGenerated at: " + new Date();
}
}
// Plugin usage in workspace/project
public class PluginUsageExample {
public void useCustomPlugin() throws Exception {
// In a workspace or project context
Workspace workspace = Workspace.getWorkspace(new File("."));
// Configure and add plugin
ComprehensivePlugin plugin = new ComprehensivePlugin();
Map<String, String> config = new HashMap<>();
config.put("custom.header", "MyCustomValue");
config.put("required.header", "Bundle-Category");
plugin.setProperties(config);
plugin.setReporter(workspace);
// Add to project
Project project = workspace.getProject("my.bundle");
project.addPlugin(plugin);
// Build with plugin
File[] built = project.build();
System.out.println("Built with custom plugin: " + Arrays.toString(built));
}
}Install with Tessl CLI
npx tessl i tessl/maven-biz-a-qute-bnd--biz-a-qute-bndlib