openHAB 3.0 / Java 11 - JAXB

I am struggling to get JAXB working under openHAB 3.0 / Java 11 (working for 2.5.x / Java 8).

I use an external library, XMPP/Websocket, having dependency for jakarta.xml.bind-api/2.33, org.glassfish.jaxb/jaxb-runtime/2.33 and jakarta.xml.soap-api/1.4.2, built successfully using jdk 11.0.8.

My project is set up with dependency for the external library mentioned above, several org.glassfish libraries, and feature dependency for openhab.tp-jaxb is included in the feature.xml file. My project builds successfully using jdk 11.0.8.

However, when running the binding under OH 3.0M2, the websocket handshake is carried out successfully, but the stream negotiation never seem to start, and the websocket connection get closed with code 1006, “Closed abnormally”. As mentioned, the stream negotiation works under 2.5.x environment (libraries/project built using jdk 1.8.0) resulting in successful websocket connection/XMPP updates.

Would assume the problem relates to no stream negotiation / JAXB.

Any hints/help would be appreciated.

EDIT
And if I also try to implement jakarta.xml.bind-api/2.33 and org.glassfish.jaxb/jaxb-runtime/2.33 as dependencies in my project/binding, I get build errors related to Classes found in the wrong directory:

META-INF/versions/9/javax/xml/bind/ModuleUtil.class=javax.xml.bind.ModuleUtil   
META-INF/versions/9/com/sun/xml/bind/v2/runtime/reflect/opt/Injector.class=com.sun.xml.bind.v2.runtime.reflect.opt.Injector 
META-INF/versions/9/com/sun/xml/bind/v2/runtime/reflect/opt/AccessorInjector.class=com.sun.xml.bind.v2.runtime.reflect.opt.AccessorInjector 
META-INF/versions/9/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizedTransducedAccessorFactory.class=com.sun.xml.bind.v2.runtime.reflect.opt.OptimizedTransducedAccessorFactory 
META-INF/versions/9/com/sun/xml/bind/v2/runtime/reflect/opt/OptimizedAccessorFactory.class=com.sun.xml.bind.v2.runtime.reflect.opt.OptimizedAccessorFactory

Uncertain why, as openhab.tp-jaxb also implements jakarta.xml.bind-api/2.33.

It might be related to class loading since openHAB 2.5 and 3.0 use slightly different ways to host REST resources and some of things which were shipped by Jersey (in OH 2.x) might be different in Apache CXF (used in 3.x), namely - handling of class loaders.

Can you tell a bit more where stream negotiation is made? Who controls websocket handshake in openHAB (is it your binding or depending library?)? Lack of action might indicate swallowed exception which doesn’t get logged.

Best,
Łukasz

The XMPP / Websocket / Stream part is handled by the external library.

My binding defines the required variables (external library classes) to achieve a connect (and the initial websocket handshake succeeds as expected) and a login (which should initiate an XMPP stream, but the open frame does not seem to be sent, nor any other stream frames - and the websocket connection gets terminated with no other exception/log than CloseReason(1006, "Closed abnormally").

Listeners (Message, Presence, IQ, Status etc) are also added in my binding.

EDIT
And you are probably right wrt class loading. I initially had the external library (jar files) within the /lib folder of the binding (added directly to build path), and the binding was built successfully, and was running successfully under initial OH 3.0.

The problems started for OH 3.0M2, having /lib folders outruled, needing to reference dependency to library otherwise.

@kjoglums in such case my tip is to look at content negotiation phase executed by your library - if its ever get called or not. The lib/ folder probably caused library to be available through boot or system class loader. With recent changes your binding must take care of class loading.

Depending on library you use there are two cases - one when library rely on passed class loader and use it to resolve classes and second when library utilize thread context class loader (TCCL) set for currently executing thread. Second model brings a risk that some things will break if library attempts to manage threads without taking care about available classpath.
Given that you have websockets I suppose you have hit second (most often) scenario. In order to check that you can do following:

ClassLaoder existing = Thread.currentThread().getContextClassLoader();
try {
    Thread.currentThread().setContextClassLoader(XMPPBindingConstants.class.getClassLoader());
    websocket.init();
} finally {
    Thread.currentThread().setContextClassLoader(existing);
}

I don’t know how you exactelly spin websocket server/client, but “TCCL” pattern is there. With above code you probably will see other parts collapsing a bit earlier than negotiation. Solution for these is declaration of Import-Package statements through bnd files.

Good luck,
Łukasz

1 Like

@splatch, really appreciate your help and guidance. Will have a more thorough look into your proposal.

The strange thing about this, is that from my binding Bridge method, the websocket initialisation happens in two steps via the external library, one of which succeeds (which should indicate class loading being ok):

  1. Connect() A connect method is called within the XmppClient class in the external library, and the initial web socket handshake succeeds (confirmed from logs).
  2. Login A login method is called from the same XmppClient class in the external library which further calls additional methods within the same library for stream initiation / marshalling / unmarshalling.

So, for me the problem seems to be related to the JAXB features within the binding / openHAB.

Yet another strange thing is that I am unable to include jakarta.xml.bind-api as a dependency in my binding, causing build error Classes found in the wrong directory (probably due to being multi release which the bnd does not support), whereas the same library, jakarta.xml.bind-api, is implemented in the core feature.xml.

I believe you are able to debug code and trap both connect and login calls. Once you do you can verify what Thread.currentThread().getContextClassLoader() returns and if its the same thing, you can also compare Thread.getName to see if library spins new thread.

If you have troubles with JAXB itself try adding explicit import statements for it. Usually it is sufficient to include in imports just package with generated ObjectFactory.class. The whole thing is that websocket library works probably in its own class context (if TCCL is not handled) and knows nothing about XMPP stream. Even if you handle TCCL properly you might face a situation that context class loader is unaware of dependencies needed later.

From debug technic I can suggest - try adding java exception breakpoint for ClassNotFoundException before you spin your binding. By this way you will be able to quickly determine if something is really missing. This technic will let you grab exceptions which are swallowed and not logged by upper processing layers.

Once again, good luck!

From debugging, class loading seems fine, i.e. classes and methods are called and executed sequentially as expected from within the external library. The last method called (still within the external library), which seem to be causing the stop, is an Encode method which is relying on javax.websocket, javax.xml.bind.Marshaller, javax.xml.stream.XMLOutputFactory, javax.xml.stream.XMLStreamWriter and java.util.function.BiConsumer (among others). The websocket Open frame from client (OH) reaches the Encode method for “marshalling” / XML write to the server (Smarthome gateway).

Although, the websocket Open frame never seems to be sent, and the connection breaks/times-out.

So, it might seem like some dependencies not working/initiating as expected (everything compiles successfully). javax.websocket and org.glassfish.jaxb/jaxb-runtime I have successfully implemented in my binding pom.xml. javax.xml.bind seem to be an issue with Java 11, and when I try to implement recommended package jakarta.xml.bind-api/2.33, as the package also included in core feature.xml for openhab.tp-jaxb, the bnd ends up in error stating Classes found in the wrong directory. An issue read to be related to the library being “multiversion”.

And again, the binding works fine under OH 2.5.x, so I can not figure out what is causing the problems.