From OSGi to Jigsaw
A tutorial on porting a sample OSGi application to the new Java Platform Module System and Jigsaw.
Join the DZone community and get the full member experience.
Join For Freein my last post i introduced the new java platform module system with a small example. as of september, there is an implementation to play with, codename jigsaw. after some introductory toy examples, i did what any curious coder does: port a familiar (and not entirely trivial) codebase to the new shiny technology. in this case, i took a small dynamic dashboard based on osgi and implemented it using the proposed java platform module system.
if you want an introduction on what the new java platform module system (referred to as jpms hereafter) entails and how to get started, read 'the java module system: a first look' . this post assumes you're familiar with the basics of the proposed module system. and if you're the kind of person who just wants to see the code: here you go .
the original osgi application
before diving into the jigsaw port, let's have a look what the original application is all about:
as you can see, it mimics a car entertainment system dashboard. however, the requirement is that this car entertainment system can be dynamically extended and updated. each of the 'apps' come from a separate osgi bundle. the dashboard only collects applications that are currently provisioned to the osgi runtime and shows them. you can click on the icons to access the underlying 'app' (music player, navigaton, phone), which come from the same bundle that contributes the dashboard icon. this example has served as demo for a conference talk by myself and my colleague paul bakker called 'provisioning the iot'. in this talk, we use apache ace to dynamically update and provision osgi bundles to running instances of the car entertainment system on multiple devices. it's actually really cool to see your system update in real-time without restarting. if you want to see it in action i recommend watching the talk . the demo starts around the 11 minute mark.
technically, the dynamic dashboard looks up all instances of the
app
interface in the osgi service registry. this interface is almost the only piece of code that is publicly shared between bundles. in turn, bundles containing an
app
implementation register themselves upon bundle start, and unregister when the bundle is stopped. this makes full use of the dynamic life-cycle afforded by osgi. the dashboard gets
app
instances from the service registry without having to know about the implementation classes. inversion of control in action! each app implementation bundle also provides its own resources such as images. you can check out the original application
on github
.
finding the right modules
the question is, how hard is it to port this modularized osgi application to the jpms using the jigsaw prototype? being as dynamic as osgi isn't a goal of the jpms. so to keep expectations in check, i'm already happy if we can port the module and service structure at startup. adding and removing new modules dynamically will have to wait for now.
our challenge is to translate the osgi bundles into equivalent jigsaw modules. the first step for re-creating this example in the jpms is to find out what should go into the
module-info.java
descriptors. these module descriptors contain the dependency information for java modules. it is similar to the osgi meta-data in the manifest file of osgi jars.
the most straightforward module definition is the one for the api bundle:
module carprov.dashboard.api {
exports carprov.dashboard.api;
requires public javafx.graphics;
}
you can find the full code for the jigsaw version of the dashboard on github if you want to follow along. it compiles and runs on build b86 of the jigsaw-enabled jdk.
we declare a module with the name
carprov.dashboard.api
, exporting a package of the same name. meaning the interface and helper class inside this package are visible to all modules that import this module. next, we need to declare what this module needs in terms of dependencies. since the
app
interface uses javafx types, these need to be required somehow. an important goal of the jpms is to modularize the itself jdk as well. therefore we cannot just import types from the jdk without specifying which module they come from. note that unlike the exports-clause, the requires-clause takes a module name rather than a package name.
so how do you find the right module to require amongst the ~80 modules that currently comprise the jdk in the jigsaw prototype? fortunately, we can do better than trial and error. the jdk provides a tool called jdeps, which analyzes the dependencies of an existing java class. you provide the class name and an appropriate classpath that contains the class:
$ jdeps -module -cp carprov.dashboard.api.jar carprov.dashboard.api.app
carprov.dashboard.api -> java.base
carprov.dashboard.api -> javafx.graphics
carprov.dashboard.api (carprov.dashboard.api)
-> java.lang java.base
-> javafx.scene javafx.graphics
the last two lines indicate that the app interface imports from the java.lang and javafx.scene packages. by providing the
-module
option, jdeps also outputs the source modules (on the far right). this way, you can identify the modules providing the packages that the analyzed class depends on. in this case, the dashboard module should require the java.base module and the javafx.graphics module from the jdk. that's exactly what we did in the module-info.java descriptor earlier. except, the java.base module is always implicitly required for all modules. you can't live without it.
another option for finding the right modules is to peruse the module overview page of the early access jigsaw build. it gives a comprehensive overview of all jdk modules and their dependencies. to get a feeling for the new modularized java platform, it's indispensable.
there's on last twist: what does the
public
in
requires public
mean in the module descriptor? let's have a look at the app interface:
import javafx.scene.node;
public interface app {
string getappname();
int getpreferredposition();
node getdashboardicon();
node getmainapp();
}
if only the carprov.dashboard.api package would be exported by the dashboard api module, what happens if another module imports it and tries to use it? that consuming module is then forced to also require the module containing javafx.scene.node (in this case javafx.graphics). since node is used as return type in app, the interface cannot be used without access to this class as well. you could document this as part of the dashboard api module, but that's error-prone and generally unsatisfactory. the
public
keyword in the requires-clauses solves this. effectively, it re-exports the public packages from the required module as part of the dashboard api module. now, the app implementation modules can require the dashboard api module without having to worry about requiring javafx.graphics. without the public keyword, compilation fails unless the consuming module itself imports the javafx.graphics module.
this re-exporting mechanism solves the same problem that osgi 'uses-constraints' solve. it goes a bit further though. with the re-exporting mechanism in the jpms, you can create an 'empty' module that acts as a façade. the public exports in the module descriptor of this empty module can aggregate several other modules. as an example, you can use this mechanism to split a module into multiple modules without breaking consumers. they still require the same module, only now it 'delegates' to other modules.
however, we're getting off track. back to porting the dashboard example. how do the apps actually end up on the dashboard using the jpms?
services with serviceloader
so far, we've talked about a single module and its dependencies: the dashboard api. however, the diagram above shows 5 modules in the sample application. what about the dashboard implementation module, and the app implementation modules? we explicitly do not want the dashboard to know about the concrete app implementation classes. it just needs to gather instances of those implementation classes, without doing the instantiation itself. loose coupling, remember?
this means we don't require any app implementation modules in the dashboard's module-info:
module carprov.dashboard.jfx {
requires carprov.dashboard.api;
requires javafx.base;
requires javafx.controls;
requires javafx.swing;
uses carprov.dashboard.api.app;
}
the interesting part is the last line of the module descriptor:
uses carprov.dashboard.api.app;
. with this uses-clause, we tell the jpms that we are interested in instances of app interface. subsequently, the dashboard can use the
serviceloader api
to retrieve these instances:
iterable<app> apps = serviceloader.load(app.class);
for(app app: apps) {
renderdashboardicon(app);
}
instances are created by the module system. of course, the big question is: how does the module system locate service providers?
let's look at an example of a module providing an app service. the phone module exposes its app implementation as follows:
module carprov.phone {
requires carprov.dashboard.api;
requires javafx.controls;
provides carprov.dashboard.api.app with carprov.phone.phoneapp;
}
the magic happens in the last line. it indicates that we want to expose an app instance, using the concrete phoneapp implementation class. note that phoneapp's package is not exported. nobody can instantiate it but the jpms, or another class inside the same module. there is one requirement for a service class: it must have a default no-arg constructor. you can even provide services and consume them in the same module. see the actual source of the dashboard implementation for an example of both a uses and provides-clause in the same module descriptor.
now the jpms knows that the dashboard implementation module wants to see app instances, and the phone module (and others) provide these instances. if at any time an additional service implementing the app interface is put on the modulepath, the dashboard will pick it up without modifications to the module descriptor. it's not as dynamic as the original osgi application, though. only after a restart of the whole application (jvm) are these new modules loaded.
for those who know the osgi service model, statically describing service dependencies is quite a difference. osgi services come and go at run-time. on the one hand, this is more powerful and dynamic. on the other hand, the jpms approach could provide errors in case wiring is not possible at startup time. by the way, the current prototype does not appear to do so. declaring a uses-clause on an interface without any implementations on the modulepath at runtime does not trigger any warnings or errors. it's still on my list to experiment with the layer construct of the jpms. let's see how close it can bring us to loading additional modules on-the-fly.
in short, the serviceloader mechanism allows us to hide implementations in a modular world. it's not quite dependency injection but it is a form of inversion of control. i'm sure dependency injection models will be built upon this foundation.
resources
modules can encapsulate more than just code. in this application, we need images as well. loading resources using class.getresourceasstream still works, with some caveats. the class calling this method must be in the same module that contains the resource. otherwise, null is returned.
the original osgi implementation delegated loading resources to a helper class in the dashboard api bundle. it did this by passing the bundlecontext of the requesting bundle to this helper class. the bundlecontext provides access to the bundle and its meta-data.
public static imageview getimagebyfullname(bundlecontext bundlecontext, string name) {
url entry = bundlecontext.getbundle().getentry(name);
try {
image image = new image(entry.openstream());
imageview view = new imageview(image);
view.setpreserveratio(true);
return view;
} catch (ioexception e) {
throw new runtimeexception(e);
}
}
i tried to emulate this by passing a class object from the requesting module to a similar helper class in the jpms version:
public static imageview getimaget(class<?> loadingcontext, string name) {
image image = new image(loadingcontext.getresourceasstream(name));
imageview view = new imageview(image);
view.setpreserveratio(true);
return view;
}
however, the access checks do not seem to care about the class object which
getresourceasstream
is invoked on, but rather on which class is on top of the call-stack. that's of course the module that contains the helper class, which cannot read resources from the module that called the helper method. in that case, getresourceasstream just returns null. that lead to some interesting nullpointerexceptions and confused looks on my face. in the end, i just had my requesting modules call
getresourceasstream
and pass the resulting inputstream to the helper instead:
public static imageview getimage(inputstream stream) {
image image = new image(stream);
imageview view = new imageview(image);
view.setpreserveratio(true);
return view;
}
after talking to mark reinhold at javaone, i learned this behavior is by design. there is an alternative that looks more like the bundlecontext solution: you can also pass a
java.lang.reflect.module
to a helper method like the one above. this reified module instance effectively allows the recipient to do anything they would like with the calling module. including getresourceasstream on that module.
list loaded modules
the original dashboard had an app that lists the loaded osgi bundles comprising the whole application. naturally, that needs to be ported as well. there is a new api for introspecting modules of the jpms. using it is fairly straightforward:
layer layer = layer.boot();
for (module m: layer.modules()) {
if(m.getname().startswith("carprov")) {
string name = m.getname();
optional<version> version = m.getdescriptor().getversion();
// show it in the ui
}
}
modules are organized into layers. since we do not specifically create a module layer ourselves, the loaded modules are part of the boot-layer. we retrieve this layer, and ask it for all the loaded modules. then, we only process modules that start with "carprov", in order to not show jdk modules in the overview.
conclusion
it's going to be interesting to see how the current jpms prototype will morph into a production-ready module system for java 9. one thing is sure: it's a big step forward for the java platform.
all in all i was pleasantly surprised how far i could come with the jigsaw prototype. yes, it is less dynamic than the osgi original. on the other hand, it is also vastly less complex. osgi service dynamics are cool, but it makes you handle lots of (concurrency) edge-cases. do you really need these dynamics all the time? nevertheless, my next challenge will be to bring some of the original dynamics back using the jpms. stay tuned!
Published at DZone with permission of Sander Mak, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments