OSGi Community Event 2015
Selecting the right toolchain that “just works” for a new OSGi based effort is still a difficult task. Many developers struggle and perceive OSGi to be overly complex and too painful. We would like to share our experience using a holistic approach to OSGi server side development based on plain Equinox that covers IDE, dependency management, build and integration testing.</p>
This approach differs from others in that it focuses on getting the average developer on board quickly and involves writing some small (core) parts of the toolchain yourself to put you in the driver’s seat. The only preconditions for this approach are Maven and an m2e plugin for Eclipse to execute Bnd on each incremental build. Our approach is particularly valuable in product-line or platform development.
Using essential code snippets and brief live demos we will demonstrate:
• How to develop your own OSGi launcher
• How to use the launcher during development (IDE + Maven) and deployment
• How to use the launcher for JUnit based integration tests
• How to single source dependency management from the pom.xml: no target platforms, no config.inis, and no manual editing of MANIFEST.MF files (let Bnd do its job)
In the second part, we will show you how to tackle bad OSGi metadata at runtime using a simple Java DSL. We will specifically address a live patching mechanism of MANIFEST.MF files based on Equinox hooks that allows third-party .jar files to remain unchanged. This has many advantages both from a licensing and from a maintainability perspective.
OSGi from the Trenches- Painless Server Side Development - Magnus Jungsbluth & Domagoj Cosic
1. public2015-11-04
•
1
•
OSGi from the Trenches
Painless Server Side Development
Date: 2015-11-04
Location: OSGi Community Event / EclipseCon 2015, Ludwigsburg, Germany
Speakers: Magnus Jungsbluth, Domagoj Cosic
2. public2015-11-04 2
What we do
▪ Platform development for products and in-house
projects, server side
▪ Plain OSGi – no Eclipse RCP
4. public2015-11-04 4
Pain?
▪ Bad OSGi metadata
▪ Missing Import-Package,
▪ version range does not match
documented compatibility,
▪ missing resolution:=optional
▪ Implicit assumptions on Start Levels
▪ Differences between Test, Deployment and
Development
6. public2015-11-04 6
Create your own Launcher!
FrameworkFactory frameworkFactory =
ServiceLoader.load(FrameworkFactory.class).iterator().next();
Framework framework = frameworkFactory.newFramework(Collections.emtpyMap());
framework.start();
[1] http://njbartlett.name/2011/07/03/embedding-osgi.html
7. public2015-11-04 7
OsgiContainerConfiguration config = new DefaultOsgiContainerConfiguration();
…
framework.init();
for (BundleMetaData bundleMeta : config.getBundlesToInstall()) {
Bundle bundle = framework.getBundleContext()
.installBundle(“reference:” + bundleMeta.getLocation());
applyStartLevel(bundle, config.getStartLevelToApply(bundleMeta));
bundle.start();
}
framework.start();
In-code configuration
… aka "beware of another XML format"
8. public2015-11-04 8
Startlevelspublic class DefaultOsgiContainerConfiguration implements OsgiContainerConfiguration {
public DefaultOsgiContainerConfiguration() {
withDefaultStartLevel("org.apache.cxf.*", 9);
withDefaultStartLevel("javax.persistence", 4);
}
@Override
public void withDefaultStartLevel(String pattern, int level) {
defaultStartLevels.put(
Pattern.compile(pattern.replace(".", ".").replace("*", ".*")), level);
}
@Override
public Integer getStartLevelToApply(BundleMetaData bundleMeta) {
Integer startLevel = null;
for (Entry<Pattern, Integer> entry : defaultStartLevels.entrySet()) {
if (entry.getKey().matcher(bundleMeta.getBundleSymbolicName()).matches()) {
startLevel = entry.getValue();
}
}
return startLevel;
}
}
! uniform behavior throughout different applications on the same
platform
9. public2015-11-04 9
Which Bundles to install?
Maven
▪ create one pom.xml for each deployment to define application‘s bundles
▪ Custom Maven Plugin starts the framework with runtime+compile
(transitive!) dependencies from that pom.xml
JUnit
▪ Start all OSGi Bundles that are on the Classpath
(TCCL.getResources("/META-INF/MANIFEST.MF"))
▪ ! Either IDE or Maven will provide the dependencies on the class path of the
JUnit launcher
Deployment (launcher.jar)
▪ Scan bin/bundles and install all .jars from that directory
10. public2015-11-04 10
How about the IDE?
Launch / Debug
Just run mvn rt:launch
! no plugin necessary
Test
Just run the JUnit test as you always do
! no plugin necessary
12. public2015-11-04 12
JUnit integration
▪ Analogous to the SuiteRunner, create an OsgiTestSuite that is responsible
for setting up the container and SuiteClasses to define the actual test classes
! still have the @RunWith annotation at your free disposal
▪ Use Boot Delegation to delegate all test libraries (org.junit.*,
org.mockito.*, ...)
▪ No special bundle configuration necessary (tradeoff flexibility), aimed at integration
tests with fixed set of bundles
15. public2015-11-04 15
Bundle creation (Maven)
mvn process-classes:
create target/classes/META-INF/MANIFEST.MF using maven-bundle-
plugin:manifest
mvn package:
create .jar using maven-jar-plugin (Ordinary “jar“ packaging>)
!Simple, standard Maven setup that does not deviate much from non-OSGi builds.
16. public2015-11-04 16
Bundle creation (Eclipse)
m2e “Tycho Project Configurators“ configures affected
projects to rebuild MANIFEST.MF on each incremental
build (equivalent to process-classes in Maven)
à target/classes is always a valid bundle
! perfectly fits Eclipse IDE's incremental build philosophy
17. public2015-11-04 17
How to tackle bad OSGi Metadata?
"traditional" approach
▪ Open the .jar with the tool of your choice and fiddle with
the MANIFEST.MF until your test is green.
▪ Create a patch version and deploy it to your local repository
(Nexus / Artifactory).
▪ Maintain this patch for a while (a.k.a ,"Rip your hair out")
18. public2015-11-04 18
Fix Metadata at Runtime
▪ Use a Java DSL to describe the difference:
bundles("org.apache.cxf.*")
.having(Constants.IMPORT_PACKAGE)
.matching("org.springframework.beans.*")
.widenUpperVersionRange("5");
bundles("org.apache.cxf.*")
.having(Constants.IMPORT_PACKAGE)
.matching("org.springframework.beans.*")
.ensureDirective("resolution", "optional");
▪ add it to the default configuration of you launcher
▪ continue with what you were originally doing ("relax")
19. public2015-11-04 19
Last but not Least
Open an issue with the library‘s owner, document
which headers must be changed:
! Exactly the same information you just defined using
the DSL!