I am getting a ClassNotFoundException from Jetty (Equninox embedded) when trying to use JDBCSessionManager and JDBCSessionIdManager.
Exception:
2017-01-06 10:37:02.620:WARN:oejss.JDBCSessionManager:qtp1215746443-29: Unable to load session 192168178229yf02ln7ut25phh97b49003w
java.lang.ClassNotFoundException: org.eclipse.equinox.http.servlet.internal.servlet.HttpSessionAdaptor$ParentSessionListener cannot be found by org.eclipse.jetty.util_9.3.9.v20160517
at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:439)
at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:352)
at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:344)
at org.eclipse.osgi.internal.loader.ModuleClassLoader.loadClass(ModuleClassLoader.java:160)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:628)
at org.eclipse.jetty.util.ClassLoadingObjectInputStream.resolveClass(ClassLoadingObjectInputStream.java:59)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1620)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
at java.util.HashMap.readObject(HashMap.java:1404)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1909)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1808)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
at org.eclipse.jetty.server.session.JDBCSessionManager$1.run(JDBCSessionManager.java:970)
at org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:1262)
at org.eclipse.jetty.server.session.JDBCSessionManager.loadSession(JDBCSessionManager.java:992)
at org.eclipse.jetty.server.session.JDBCSessionManager.getSession(JDBCSessionManager.java:502)
at org.eclipse.jetty.server.session.JDBCSessionManager.getSession(JDBCSessionManager.java:75)
at org.eclipse.jetty.server.session.AbstractSessionManager.getHttpSession(AbstractSessionManager.java:331)
at org.eclipse.jetty.server.session.SessionHandler.checkRequestedSessionId(SessionHandler.java:275)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:151)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1106)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:134)
at org.eclipse.jetty.server.Server.handle(Server.java:524)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:319)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:253)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:273)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:95)
at org.eclipse.jetty.io.SelectChannelEndPoint$2.run(SelectChannelEndPoint.java:93)
at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.executeProduceConsume(ExecuteProduceConsume.java:303)
at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.produceConsume(ExecuteProduceConsume.java:148)
at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.run(ExecuteProduceConsume.java:136)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:671)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:589)
at java.lang.Thread.run(Thread.java:745)
I am using a JettyCustomizer to hook into the Jetty startup to change the default HashSessionManager with the JDBCSessionManager. The JettyCustomizer is located in a Fragment Bundle which belongs to
Fragment-Host: org.eclipse.equinox.http.jetty
I got this idea from https://wiki.eclipse.org/RAP/FAQ#How_can_I_use_Jetty_basic_authentication_in_my_application.3F
This setup works ok, and JDBCSessionManager places a session in the Database. The session is Serialized to a Byte-BLOB and stored in the DB. I can see it there.
But it seems the serialization is done by org.equinox.http and it places class reference like org.eclipse.equinox.http.servlet.internal.servlet.HttpSessionAdaptor$ParentSessionListener
into the BLOB.
Note, that internal.servlet.HttpSessionAdaptor
is an internal class which is not exported to other bundles.
Now when the session information is read again from the database (e.g. when I access the webpage again later with the same sessionCookie) I run into this problem when org.eclipse.jetty.util.ClassLoadingObjectInputStream.resolveClass(ClassLoadingObjectInputStream.java:59)
tries to load the classHttpSessionAdaptor$ParentSessionListener
but cannot see it (because it is a) internal and / or b) in another bundle.
org.eclipse.jetty.util.ClassLoadingObjectInputStream
lives in bundle org.eclipse.jetty.util
but org.eclipse.equinox.http.servlet.internal.servlet.HttpSessionAdaptor$ParentSessionListener
lives in bundle org.eclipse.equinox.http.servlet
.
org.eclipse.jetty.util.ClassLoadingObjectInputStream seems to do the following:
@Override
public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
{
try
{
return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
}
catch (ClassNotFoundException e)
{
return super.resolveClass(cl);
}
}
Is there anybody from the OSGI experts with an ideas?
I would describe the problem as that Session byte-BLOB contains Class References to internal classes which cannot be seen by org.eclipse.jetty.util.ClassLoadingObjectInputStream.resolveClass
Does that seem like a bug? Or is the approach with the FragmentBundle with the the wrong approach? (IMO it is the only way I found to exchange the SessionManager)
The issue is probably because
ClassLoadingObjectInputStream
is using the TCCL for class resolution, which - in Equinox - by default is theorg.eclipse.osgi.internal.framework.ContextFinder
. It is finding the first bundle on the call stack. This is likely the Jetty bundle, which does not see any of the Equinox classes.As far as the Equinox HTTP Service is concerned, the fragment approach is the right one for hooking into Jetty. If I'm reading the code path right, you could try the following things.
(1) Set class loader on
ContextHandler
In your
JettyCustomizer.customizeContext
you should inspect the context. It should be aServletContextHandler
. Use itssetClassLoader
method to give it a class loader that knows about the Equinox classes (which any fragment oforg.eclipse.equinox.http.jetty
should know anyway) and any other classes of your own custom code.(2) Fork/patch
JDBCSessionManager
If approach 1 does not work then you likely need to create your own fork of
JDBCSessionManager
. Extending might not work because of visibility issues (some methods are private). You need to override/patch/reimplement theJDBCSessionManager.loadSession
method to use the correct class loader for loading. In the original implementation you can see why approach 1 should work (in theory). The code of your implementation can be much simpler, though.If your fragment also imports the packages of your code, then simple use your fragment class loader. Otherwise you can create a custom one that delegates to the correct bundles for resolution.