Sonntag, 24. März 2013

JavaFX FXMLLoader with OSGi

Using FXMLLoader in OSGi is not completely trivial. This article explains, how to do it. The osgisnippets project contains a project bundle_set5fxml, which is used for that.

Loading an fxml file is done like that:

URL url = this.getClass().getResource("clickme.fxml"); Pane pane = FXMLLoader.load(url);

So let's just try that and see what happens:

18:05:57.927 [JavaFX Application Thread] ERROR - Error loading FXML file javafx.fxml.LoadException: java.lang.ClassNotFoundException: example.set5fxml.impl.ClickmeController at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:726) ~[jfxrt.jar:na] ... at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2685) ~[jfxrt.jar:na] at example.set5fxml.impl.Activator$1.run(Activator.java:63) ~[na:na] ... Caused by: java.lang.ClassNotFoundException: example.set5fxml.impl.ClickmeController at java.net.URLClassLoader$1.run(URLClassLoader.java:366) ~[na:1.7.0_17] ... at java.lang.ClassLoader.loadClass(ClassLoader.java:356) ~[na:1.7.0_17] at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:724) ~[jfxrt.jar:na] ... 17 common frames omitted

The fxml file references the class ClickmeController, which is inside the set5fxml bundle. Obviously FXMLLoader doesn't use a bundle class loader and thus cannot load the controller class. There's a method FXMLLoader.setDefaultClassLoader():

FXMLLoader.setDefaultClassLoader(Activator.class.getClassLoader()); URL url = this.getClass().getResource("clickme.fxml"); Pane pane = FXMLLoader.load(url);

The class loader passed into setDefaultClassLoader must be a bundle class loader, so it should be one from a class which is loaded from this bundle. (See below for more information.) So that worked well enough:

There are a few other things, which must done to make it work. Apache Felix does not export the JavaFX packages by default, so the relevant packages must be added to the list of extra system packages. This is done in the class OSGiFwLoader:

private void prepareConfigMap(Map<Object, Object> configMap) { configMap.put( Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, "example.set2appapi.api, " + "example.set2cmdapi.api, " + "javafx.application, " + "javafx.fxml, " + "javafx.event, " + "javafx.scene, " + "javafx.scene.control, " + "javafx.scene.layout, " + "javafx.scene.paint, " + "javafx.scene.text, " + "javafx.stage, " + "org.slf4j"); ... }

Also the bundle must import the required packages. In the osgisnippets project this is done in the file build.default.properties:

prj.bundle.name=Set5_FXML prj.bundle.import-package=org.osgi.framework, org.slf4j, \ javafx.application, \ javafx.fxml, \ javafx.event, \ javafx.stage, \ javafx.scene, \ javafx.scene.control, \ javafx.scene.layout, \ javafx.scene.paint, \ javafx.scene.text #prj.bundle.export-package=

Coming back to the class loader given to FXMLLoader.setDefaultClassLoader: In the sample bundle, the ClassLoader of the Activator class is used. This works, because the Activator class is part of the bundle and thus loaded by the bundle class loader. What happens, if the class loader of the BundleActivator class is used? An error is logged and the underlying exception with stack trace is this one:

Caused by: java.lang.ClassNotFoundException: example.set5fxml.impl.ClickmeController at java.net.URLClassLoader$1.run(URLClassLoader.java:366) ~[na:1.7.0_17] at java.net.URLClassLoader$1.run(URLClassLoader.java:355) ~[na:1.7.0_17] at java.security.AccessController.doPrivileged(Native Method) [na:1.7.0_17] at java.net.URLClassLoader.findClass(URLClassLoader.java:354) ~[na:1.7.0_17] at java.lang.ClassLoader.loadClass(ClassLoader.java:423) ~[na:1.7.0_17] at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308) ~[na:1.7.0_17] at java.lang.ClassLoader.loadClass(ClassLoader.java:356) ~[na:1.7.0_17] at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:724) ~[jfxrt.jar:na]

In osgisnippets and its guitool application, the classes of the OSGi framework (Apache Felix) are loaded by the system class loader. The felix jar is in the classpath of the guitool application. The BundleActivator interface is part of the felix jar and its class loader is the system class loader. However, the system class loader doesn't know about the bundles and it can't find the ClickmeController class. To summarize: Make sure, the class loader passed into FXMLLoader.setDefaultClassLoader() is actually a bundle class loader.

Resources

Samstag, 23. März 2013

JDK8: Unable to Resolve javax.swing ?

The other day I was running an OSGi project with JDK8 and noticed, that the javax.swing package could not be found. This worked in JDK7. Switching to JDK8 in osgisnippets was leading to the following error, when Swing is about to be loaded:

org.osgi.framework.BundleException: Unresolved constraint in bundle example.swing1 [1]: Unable to resolve 1.0: missing requirement [1.0] osgi.wiring.package; (osgi.wiring.package=javax.swing) at org.apache.felix.framework.Felix.resolveBundleRevision(Felix.java:3826) ~[org.apache.felix.main-4.0.2.jar:na] at org.apache.felix.framework.Felix.startBundle(Felix.java:1868) ~[org.apache.felix.main-4.0.2.jar:na] at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:944) ~[org.apache.felix.main-4.0.2.jar:na] at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:931) ~[org.apache.felix.main-4.0.2.jar:na] ...

The reason is simple: A Felix version was used, which does not know about JDK8. Upgrading to a newer version like org.apache.felix.main-4.2.1.jar solves the issue.

The OSGi framework makes java.* packages available automatically. The JDK contains several packages outside the java.* namespace such as javax.swing or org.xml.sax, which are made available too, but specific to the Java version. The felix main jar contains a file default.properties which also defines the exported packages for each Java version. So felix detected jre-1.8, but in the default.properties file of v4.0.2 there is no entry for that. Hence all the extra JRE packages (other than java.*) were not exported.

Resources

Samstag, 16. März 2013

Using JavaFX in a Bundle

If a bundle shall provide a user interface, it could create a Stage, add some components and present it on screen. (For instance assume you built some home automation and a fridge monitoring bundle shall present some feedback when the temperature is too high.)

Create and Dispose the Stage

One approach is to create the Stage when the bundle's start method is called. When the bundle is stopped, it should clean up and thus dispose the Stage. Bundles may have an "Activator" which is an implementation of the BundleActivator interface. The interface defines a start method - which is called by the framework when the bundle is started - and a stop method - which is called by the framework when the bundle is stopped.

The OSGi snippets project contains a "bundle_set4jfx" project which contains exactly one class:

public class Activator implements BundleActivator { Logger logger = LoggerFactory.getLogger(Activator.class); Stage stage; @Override public void start(BundleContext context) throws Exception { logger.info("Set4Jfx Bundle: start()"); Platform.runLater(new Runnable() { @Override public void run() { stage = new Stage(); BorderPane pane = new BorderPane(); Scene scene = new Scene(pane, 400, 200); pane.setCenter(new Label( "This is a JavaFX Scene in a Stage")); stage.setScene(scene); stage.show(); } }); } @Override public void stop(BundleContext context) throws Exception { logger.info("Set4Jfx Bundle: stop()"); Platform.runLater(new Runnable() { @Override public void run() { stage.close(); } }); } }

In the guitool (*), start the framework, install the bundle file named "bundle_set4jfx" from the files list and start it in the bundles list. (Bundle files are on the right hand side and bundles are on the left hand side in the guitool screen. Bundles appear when bundle files are installed.)

(*) The JavaFX ready guitool must be used. It is located in the example.guitool.fx package.

The bundle starts and a Stage is shown:

Pressing the "Stop" button in the guitool stops the bundle and closes the Stage.

To use JavaFX classes, they must be available to the bundle, otherwise a ClassNotFoundException is thrown. The bundle has a MANIFEST file which among other things contains these three lines:

Bundle-Activator: example.set4jfx.impl.Activator Import-Package: org.osgi.framework, org.slf4j, javafx.application, jav afx.scene, javafx.scene.control, javafx.scene.layout, javafx.stage

The entry Bundle-Activator defines the class which is called when the bundle shall be started or stopped. The entry Import-Package defines the java packages which shall be available to the bundle which in this case must include javafx.application, javafx.scene, etc.

Playing with Import-Package

As a little exercise, you may want to change the Import-Package entry and see what happens. The build scripts create the Import-Package entry from information stored in the build.default.properties file in the project. This file is in the root of project bundle_set4jfx. In the line prj.bundle.import-package=org.osgi.framework, org.slf4j, javafx.application, ... remove the javafx.application package and save the file. Then build the bundle by either running build-bundles.xml or the project's build.xml script. Install the bundle and start it. The bundle information will contain "Activator start error in bundle" and the Eclipse console will show a " java.lang.ClassNotFoundException".

Resources

Freitag, 15. März 2013

guitool with JavaFX

Similar to a Swing based guitool, the osgisnippets project contains a JavaFX 2 based guitool. It is located in the project guitool in the package example.guitool.fx . The package is essentially a copy of example.guitool with adjustments to make it work with JavaFX.

Embedding OSGi into JavaFX

The class OSGiFwLoader takes care of the main work of starting and stopping the OSGi framework and managing bundles. In Embedding OSGi the main features of the OSGiFwLoader class are described. There is one major difference between the version there and the JavaFX related version.

The goal is to load bundles which use JavaFX components. To do that, the bundle imports javafx.* packages. These packages must be made available and this is done in OSGiFwLoader.prepareConfigMap():

private void prepareConfigMap(Map<Object, Object> configMap) { // List the packages which shall be exported by the System Bundle. ... configMap.put( Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, "example.set2appapi.api, " + "example.set2cmdapi.api, " + "javafx.application, " + "javafx.scene, " + "javafx.scene.control, " + "javafx.scene.layout, " + "javafx.stage, " + "org.slf4j"); // Each time the OSGi Framework starts, it shall clean up its // storage. I decided to do that, so one gets a clean environment // each time. configMap.put( Constants.FRAMEWORK_STORAGE_CLEAN, Constants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT); // This is for application provided BundleActivators: List<Object> list = new ArrayList<>(); // fillSystemBundleActivators(list); configMap.put(FelixConstants.SYSTEMBUNDLE_ACTIVATORS_PROP, list); }

The bold lines let these JavaFX packages be exported by the system bundle and are thus available to the bundles which use JavaFX components. (If further packages are required, they need to be added to the list of packages.)

The JavaFX GUI Tool

The JavaFX gui tool shall be able to start bundles which display Swing components. As described in JavaFX and Swing (on a Mac) some extra steps are necessary to allow Swing components to be used in JavaFX. Instead of the typical Application.launch() call, Swing is started first and JavaFX is embedded there:

public static void main(final String[] args) { startJFXPanel(args); } private static void startJFXPanel(final String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { try { final JFrame mainFrame = new JFrame("gui tool (s/fx)"); final JFXPanel jfxPanel = new JFXPanel(); mainFrame.getContentPane().add(jfxPanel); mainFrame.setTitle("OSGi Snippets GUI Tool (s/fx)"); mainFrame.setSize(800, 600); mainFrame.setLocationRelativeTo(null); mainFrame.setVisible(true); Platform.runLater(new Runnable() { @Override public void run() { MainFXUI fxui = new MainFXUI(); try { fxui.start(jfxPanel, mainFrame); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); } catch (Exception e) { e.printStackTrace(); } } }); }

Doing these things on a Mac was leading to more issues than under Windows. If it is good enough under the circumstances, the following start method may be used too:

private static void startAWT(String[] args) { System.setProperty("javafx.macosx.embedded", "true"); java.awt.Toolkit.getDefaultToolkit(); Application.launch(MainFXUI.class, args); }

Things are changing a lot currently so this jumping through hoops may not be necessary in the future (JavaFX 1.x reaches EOL, JavaFX 2.x in JDK7 is out for a while, JavaFX 8 is available as early access and parts of JavaFX already went Open Source).

Resources

JavaFX and Swing (on a Mac)

The other day I was taking a look at JavaFX and how it might fit one of my OSGi related projects. As usual things are not as easy as they appear at first sight, so it is time to add JavaFX to osgisnippets.

To use JavaFX with my other project and with osgisnippets, the following goals exist:

  1. Ability to show Swing components
  2. Provide a guitool based on JavaFX like the one based on Swing
  3. Get it to work with OSGi

Goal 1 is covered in this article. The others will be covered in their own articles soon. For the tests I created the class SnipFXSwing. A link to the sources is at the end of the article. In the source archive, it is in the guitool project in package example.guitool.snip.

JFXPanel - embed JavaFX in Swing

This is straight forward, the API doc explains how. (See the Resources section at the end of the article.) Note that JFXPanel is about having a Swing application and starting JavaFX components in it. I need it the other way around.

Preparation

The class SnipFXSwing has a main method in which different start methods are used depending on the command line argument:

public static void main(final String[] args) { if (args.length<1) { System.err.println( "Usage:\n" + "SnipFXSwing fx starts FX GUI\n" + "SnipFXSwing awtfx starts fx after AWT init\n" + "SnipFXSwing swfx starts fx within Swing"); return; } switch (args[0]) { case "fx": startFX(args); break; case "awtfx": startAWTFX(args); break; case "swfx": startSwFX(args); break; default: System.err.println("Error: argument " + args[0] + " not supported"); } }

Approach 1 - Just Try It

Lets just fire up a JavaFX UI and try to create a Swing JFrame. To do that, SnipFXSwing is started with the argument fx. The relevant code is this:

public class SnipFXSwing extends Application { ... private static void startFX(String[] args) { Application.launch(SnipFXSwing.class, args); } ... Stage mainStage; ... ToolBar mainToolBar; Button exitButton; Button showSwingButton; @Override public void start(Stage stage) throws Exception { mainStage = stage; mainStage.setTitle("Snippet JavaFX with Swing"); Scene mainScene = createMainScene(); mainStage.setScene(mainScene); mainStage.setOnCloseRequest(new EventHandler<WindowEvent>() { @Override public void handle(WindowEvent event) { if (mainStage!=null) { doExit(); } } }); mainStage.show(); } }

This is typical source code for small JavaFX applications. The method createScene creates the JavaFX components. This separate method exists, because this part will be reused later. The Stage is the main window and a handler is registered to shut down the application when the window is closed.

The method doShowSwing is called, when the button in the center of the window is pressed:

protected void doShowSwing() { if (!SwingUtilities.isEventDispatchThread()) { // re-run in EDT: SwingUtilities.invokeLater(new Runnable() { @Override public void run() { doShowSwing(); } }); return; } JFrame f = new JFrame("Swing Window"); f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); f.getContentPane().add(new JLabel("This is a Swing JFrame")); f.setSize(400, 200); f.setLocationRelativeTo(null); f.setVisible(true); }

The first if statement checks, whether the method has been called from the Event Dispatch Thread (EDT) and calls it from the EDT if not. The remaining code is the usual way to create a Swing JFrame. When running on Mac OS X and pressing the button "Show Swing Window", this error is logged:

java[10613:707] [JRSAppKitAWT markAppIsDaemon]: Process manager already initialized: can't fully enable headless mode. Exception in thread "AWT-EventQueue-0" java.awt.HeadlessException at java.awt.GraphicsEnvironment.checkHeadless(GraphicsEnvironment.java:207) at java.awt.Window.<init>(Window.java:535) at java.awt.Frame.<init>(Frame.java:420) at javax.swing.JFrame.<init>(JFrame.java:224) at example.guitool.snip.SnipFXSwing.doShowSwing(SnipFXSwing.java:231) ...

So that doesn't work. There are a few issues which require, that AWT is initialized before JavaFX. (Link to bug reports in the Resources section.)

BTW: If the if block is commented out and the code is not run in the EDT, the following error is logged:

java[10698:707] [JRSAppKitAWT markAppIsDaemon]: Process manager already initialized: can't fully enable headless mode. Glass detected outstanding Java exception at -[GlassViewDelegate sendJavaMouseEvent:]:src/com/sun/mat/ui/GlassViewDelegate.m:541 Exception in thread "AWT-AppKit" java.awt.HeadlessException at java.awt.GraphicsEnvironment.checkHeadless(GraphicsEnvironment.java:207) at java.awt.Window.<init>(Window.java:535) at java.awt.Frame.<init>(Frame.java:420) at javax.swing.JFrame.<init>(JFrame.java:224) at example.guitool.snip.SnipFXSwing.doShowSwing(SnipFXSwing.java:231) ...

On to the next approach.

Approach 2 - Initialize AWT before JavaFX

Some internals require, that AWT is initialized before JavaFX. One approach is this:

private static void startAWTFX(String[] args) { java.awt.Toolkit.getDefaultToolkit(); Application.launch(SnipFXSwing.class, args); }

To try that, the SnipFXSwing class is started with the argument awtfx. Well, as can be seen, nothing is seen. The application hangs and it must be terminated.

A system property is needed:

private static void startAWTFX(String[] args) { System.setProperty("javafx.macosx.embedded", "true"); java.awt.Toolkit.getDefaultToolkit(); Application.launch(SnipFXSwing.class, args); }

Ah! It works :-)

Can we see a Swing window please?

Finally some cannons for the sparrows:

Approach 3 - Embed JavaFX in Swing

If for some reason, the javafx.macosx.embedded property cannot be set, another solution is required. Lets embed JavaFX in Swing so we can embed Swing in JavaFX.

Starting the application:

private static void startSwFX(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { try { final JFrame frame = new JFrame("Snippet JavaFX with Swing"); final JFXPanel jfxPanel = new JFXPanel(); frame.getContentPane().add(jfxPanel); frame.setSize(600, 400); frame.setLocationRelativeTo(null); frame.setVisible(true); Platform.runLater(new Runnable() { @Override public void run() { SnipFXSwing ui = new SnipFXSwing(); try { ui.start(jfxPanel, frame); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); } catch (Exception e) { e.printStackTrace(); } } }); }

First Swing is started by creating a JFrame. Such things must be done in the EDT, so it is wrapped in SwingUtilities.invokeLater. Basically a JFrame is created and a JFXPanel is added to the frame's content pane.

Now the JavaFX components are created. Similar to Swing with EDT that must be done in the "JavaFX Application Thread". Hence the Platform.runLater call. A different start method is used now, as there is no Stage instance:

public void start(JFXPanel jfxPanel, JFrame frame) throws Exception { this.mainFrame = frame; Scene mainScene = createMainScene(); frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(java.awt.event.WindowEvent e) { if (mainFrame!=null) { Platform.runLater(new Runnable() { @Override public void run() { doExit(); } }); } else { super.windowClosing(e); } } }); jfxPanel.setScene(mainScene); }

Similar to the Stage related start method, the Scene is created, however, it is set in the JFXPanel instance. Also the code to handle a closed main window must be slightly different. Instead of a Stage, a JFrame must be observed, which is done with a WindowListener. A little tricky is the call to doExit. As this shall be JavaFX code, it must be run in the JavaFX Application Thread, so again Platform.runLater is used.

The "JavaFX in JFXPanel" main window:

Pressing the "Swing" button:

(*Wiping sweat away...*)

Resources

Donnerstag, 28. Februar 2013

ServiceFactory

There is an interesting interface called ServiceFactory. Citing the documentation (link at end), it

Allows services to provide customized service objects in the OSGi environment.

When registering a service, a ServiceFactory object can be used instead of a service object, so that the bundle developer can gain control of the specific service object granted to a bundle that is using the service.

So what does that mean?

  1. The service object can be created when the consuming bundle asks for it.
    In the normal scenario a service object is created and then registered when the bundle is started. With the ServiceFactory, the consuming bundle asks for the service and then the provider bundle creates the service object. (You don't need to register the newly created service object; the registration was already done with the ServiceFactory instance.)
  2. The ServiceFactory can return service objects which are different for each requesting bundle.
    In the normal scenario a service object is created and used by whichever bundle wants it. Now for each bundle which requests the service, the method ServiceFactory.getService is called and allows to return a new instance.
  3. The ServiceFactory can customize service objects before making them available.
    For instane the ServiceFactory instance can inspect the Bundle which requests the service object (like looking at certain entries in the MANIFEST). Depending on that inspection it can return different types of service objects or service objects with different configurations.

There are some interesting runtime details too:

  1. Service objects are cached by the framework.
    When a bundle requests a service, it gets delivered a service object. The next request call will deliver the same service object (unless it has been released in between). When the consuming bundle calls ungetService to tell that it doesn't need the service object anymore, the method ServiceFactory.ungetService is called by the framework. (Actually ungetService must be called as many times as getService, the framework maintains a use count.)

However, for a second bundle, the service objects of the first bundle are not reused by the framework. If two bundles retrieve a service and thus use the ServiceFactory, its getService method gets called for each of the bundles.

Samples in osgisnippets Project

The project osgisnippets contains a set of bundles with the name starting with set3. set3fac provides services using the ServiceFactory. set3cons1 and set3cons2 are two almost identical bundles, which consume the service. They create a window with a get and an unget button. The ServiceFactory provides a unique ID for each service object. The ID of the service object is shown by the consuming bundles in their window.

Resources

Error: type ServiceReference does not take parameters

When developing for an OSGi environment and especially when using Apache Felix derived projects, one may run into a strange error. It is strange, because inside Eclipse no errors are shown, but when using the Java compiler (javac), it complains like so:

[javac] .../Activator.java:53: error: type ServiceRegistration does not take parameters [javac] ServiceRegistration svcReg;

A couple of circumstances need to exist to trigger this error:

  • JDK7 is used
  • Apache Felix 4.0.2 or 4.0.3 is used
  • javac is used to compile the project

OSGi code is compiled with the -jsr14 option and that leads to problems with the JDK7 compiler. The Eclipse JDT compiler compiles it well, and therefore there is no error when building only in Eclipse.

There are updated OSGi jars available now which fix this problem (and one doesn't need to recompile). These are org.osgi.core-4.3.1.jar and org.osgi.compendium-4.3.1.jar. They are on OSGi's site or on the maven sites. (See below)

My fix for an earlier project was to build the OSGi sources (org.osgi.* packages) with JDK7 and put them before the Felix jars in the classpath. This makes the new OSGi class files being used instead of the ones which are packaged with the Felix jars.

Resources