Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ClassNotFoundException when using jetty 9.4.0.M0 within OSGi container #705

Closed
jerboaa opened this issue Jul 8, 2016 · 20 comments
Closed
Assignees

Comments

@jerboaa
Copy link

jerboaa commented Jul 8, 2016

Please see this downstream bug we've noticed in Thermostat:
https://bugzilla.redhat.com/show_bug.cgi?id=1353999

The bug lists a reproducer.

Anyway, we are seeing a CNFE for org.eclipse.jetty.webapp.WebInfConfiguration et.al in 9.4.0.M0. Stack trace looks like this

2016-07-08 15:01:52.092:WARN:oejw.WebAppContext:Thread-3: Failed startup of context o.e.j.w.WebAppContext@6cdce5ed{/thermostat/storage,null,null}{/usr/share/thermostat/webapp}
java.lang.ClassNotFoundException: org.eclipse.jetty.webapp.WebInfConfiguration
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        at org.eclipse.jetty.util.Loader.loadClass(Loader.java:65)
        at org.eclipse.jetty.webapp.WebAppContext.loadConfigurations(WebAppContext.java:931)
        at org.eclipse.jetty.webapp.WebAppContext.preConfigure(WebAppContext.java:451)
        at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:520)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:132)
        at org.eclipse.jetty.server.Server.start(Server.java:437)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:106)
        at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:93)
        at org.eclipse.jetty.server.Server.doStart(Server.java:404)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
        at com.redhat.thermostat.web.endpoint.internal.JettyContainerLauncher.doStartContainerAndDeployWar(JettyContainerLauncher.java:214)
        at com.redhat.thermostat.web.endpoint.internal.JettyContainerLauncher.startContainerAndDeployWar(JettyContainerLauncher.java:125)
        at com.redhat.thermostat.web.endpoint.internal.JettyContainerLauncher.access$000(JettyContainerLauncher.java:77)
        at com.redhat.thermostat.web.endpoint.internal.JettyContainerLauncher$1.run(JettyContainerLauncher.java:99)
        at java.lang.Thread.run(Thread.java:745)

Note the same code works with the 9.3.x train of jetty. The bug might only be happening if jetty is booted via an OSGi framework.

The relevant refactoring which seems to have broken this is here:
a311c8b

The proposed fix would be to revert to the old behavior in WebAppContext.loadConfigurations() to load the config classes with an additional loadClass parameter. To me it looks like the current implementation is incorrect when run in an OSGi container. Using TCCL for loading the config classes is the wrong call.

Thoughts?

@jerboaa jerboaa changed the title ClassNotFoundException when using jetty within OSGi container ClassNotFoundException when using jetty 9.4.0.M0 within OSGi container Jul 8, 2016
@joakime
Copy link
Contributor

joakime commented Jul 8, 2016

9.4.0 is not a stable branch (yet).

That Loader change was very important to us, and its highly unlikely that it will be reverted.
That being said, we'll dig into this for a cleaner solution using the newer techniques.

Do you have a relevant testcase on the thermostat project we can use to replicate?

@jerboaa
Copy link
Author

jerboaa commented Jul 11, 2016

@joakime There is not exactly an easy test case, unfortunately. To my knowledge it requires a jetty server to be started from within an OSGi framework. The best we have is something like this:

$ hg clone http://icedtea.classpath.org/hg/thermostat/
$ cd thermostat
$ wget -O thermostat_jetty_9.4.0.patch https://gist.githubusercontent.com/jerboaa/d8f6b00e21f175b7518a8400cad57567/raw/aaef7b2309ecc777a9649dd8980d0a6bee176bc1/thermostat_jetty_9.4.0_reproducer.patch
$ patch -p1 < thermostat_jetty_9.4.0.patch
$ mvn -Dmaven.test.skip=true clean package
$ ./distribution/target/image/bin/thermostat web-storage-service

@janbartel janbartel self-assigned this Jul 12, 2016
@gregw
Copy link
Contributor

gregw commented Jul 13, 2016

@jerboaa That loader code was rather evil as it went on a rather full search of every possible classloader looking for a class to load. This is rather against the spirit of OSGi and all other classloader architectures. We want to run properly in OSGi by design, not by bypassing classloaders. I see that @janbartel has self assigned this issue, so she will be looking into what we need to do to make the OSGI run correctly.

@janbartel
Copy link
Contributor

@jerboaa actually at the point in time that the configuration classes are loaded, there is NO thread context classloader. Thus, the Loader will use Class.forName() call, which will use the classloader of the Loader class, which will be the jetty-util bundle, unfortunately.

So, the easy fix for this is to set up a thread context classloader in thermostat's JettyContainerLauncher, and ensure that it points to the classloader of the JettyContainerLauncher class, which presumably has the right bundle import statements to resolve all of the org.eclipse.jetty.* classes. The right place to do that would be around line 142, just before you call new Server(). This is what we do in the jetty osgi support classes: in our bundle listener that detects and deploys new webapp bundles, we set a thread context classloader that is the classloader of the main jetty bundle, which is able to resolve all jetty classes: https://github.com/eclipse/jetty.project/blob/jetty-9.4.x/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleWebAppProvider.java#L110. Actually the main jetty bundle also uses a Dynamic-Import statement to resolve all org.eclipse.jetty.* classes, but from the looks of the relevant thermostat manifest, I think you should have the right import statements to be able to resolve everything necessary.

@jerboaa
Copy link
Author

jerboaa commented Jul 13, 2016

@janbartel I can do this and it'll likely work, but I don't understand why it should be a third-party's (Thermostat in our case) responsibility to set a TCCL so as to facilitate proper class loading across jetty bundles. In this particular instance the caller, WebAppContext, in bundle jetty-webapp uses another bundle, jetty-util, to load a class (amongst others) from jetty-webapp. Specifically, configuration classes from the jetty-webapp bundle for the default config case. What's more, we didn't have to do this before with jetty 9.3.x, why do you expect users to set up a TCCL now?

jerboaa added a commit to jerboaa/jetty.project that referenced this issue Jul 13, 2016
Use regular class loading for default configuration instance
creation.
@jerboaa
Copy link
Author

jerboaa commented Jul 13, 2016

@janbartel --^ is a quick hack for circumventing Loader class loading which works for us. I believe it shouldn't break anything else either since it special cases the default config case. But that's hard for me to judge/test. Anyway, something like that would be more like the OSGi way ;-)

@jerboaa
Copy link
Author

jerboaa commented Jul 13, 2016

http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=3086#c2 Has a patch which works around this issue. Not nice :-(

@janbartel
Copy link
Contributor

@jerboaa the problem with the proposed fix is that it only takes care of the bare minimum set of Configurations that happen to be defined in the jetty-webapp bundle - it won't work if any Configurations from other bundles are added in (eg AnnotationConfiguration, EnvConfiguration, PlusConfiguration etc etc). So I think any solution needs to work for the general case, not just this specific one.

While I understand your sentiment that you would like this to work out-of-the-box in an OSGi environment, it's very difficult to make that work ;) I will have a further ponder on what more we could do to make this easier, but I don't think any solution could be entirely transparent to the embedding application: fundamentally only the application knows about a classloader that has been wired up (via manifest import statements etc) with visibility across jetty classes, and it is that classloader that should be the parent of the webapp's classloader, and it is also that classloader that needs to be used to load the Configurations, before the webapp's classloader has been established.

I'll take a look and see if there's any refactoring that I can do to our existing osgi support (jetty-osgi-boot bundle) to extract some utilities that would make it easier to use jetty in osgi without using our entire solution to deploy jetty as an osgi service.

@jerboaa
Copy link
Author

jerboaa commented Jul 15, 2016

Which makes me wonder why the Loader call which takes a class as additional param is the wrong call though. Failing that, if jetty-util would have proper bundle wiring via Import-Package it should be able to resolve classes via Class.forName(). If jetty-util is expected to load classes for other bundles this should be expressed via the manifest. At least via resolution:="optional".

@janbartel
Copy link
Contributor

@jerboaa Loader.loadClass(Class,String) wouldn't work because you don't know what class to pass in that has a classloader that can load the desired class. If you passed in WebAppContext.class, then sure you can load classes that are in the jetty-webapp bundle, but you couldn't load classes that are in eg jetty-annotation bundle.

resolution:="optional" is not really a solution either for a couple of reasons:

  • you can't fully enumerate all packages that might ever need to be loaded: besides the org.eclipse.jetty.* packages there are other third party classes.
  • if you use Import-Package "*" (because you can't enumerate all packages), then you are prevented from using any version specifier, so you could potentially get multiple simultaneous versions of jetty deployed and you wouldn't be able to determine from which a particular class is resolved
  • even if you did manage to do a full enumeration of packages, resolution:=optional is fragile. Resolution happens at bundle deploy time, so whether or not a package is recorded as being available depends entirely on the order in which bundles are deployed.
  • Dynamic-Import might be better, but again, it would be difficult to enumerate all packages that ever need to be loaded, and dynamic class loading is known to be slow. Same problem as for resolution:=optional with using "*" as the Import-Package.

Fundamentally, it just doesn't work in osgi-land to have a bundle try and load classes on behalf of all other bundles: it contradicts the very essence of osgi :)

@jerboaa
Copy link
Author

jerboaa commented Jul 25, 2016

Right, makes sense. Any recommendations on how to correctly use embedded jetty within an OSGi context? My fear is that we might run into an issue like this again on the next version update. I suppose setting the TCCL before any of the jetty classes are being used is the only choice? Note that in Thermostat-land we needed to add a delegating class loader so as to be able to continue to resolve system classes coming from the JDK. It's pretty ugly:

http://pkgs.fedoraproject.org/cgit/rpms/thermostat.git/commit/?id=75fc74c341a12bd6f247b5c7f7391c91958f4eff

@janbartel
Copy link
Contributor

@jerboaa not sure why the WorkAroundJettyClassLoader is necessary? If you set the TCCL to be the classloader for JettyContainerLauncher that should be enough I would think: the osgi environment never looks at the TCCL, only jetty uses it when doing explict classloading and I would have thought that system classes would be loaded implicitly (and therefore from the osgi environment loader).

Can you post an example of what fails unless you have this fix in place?

For the jetty osgi library, for every jetty Server instance we create a new URLClassloader whose parent is the classloader of the bundle with all the correct import statements. Let's call that the "container classloader". When a webapp is deployed, the TCCL is set to the container classloader, and it becomes the parent of the webapp's classloader that is created during deployment. At no time do we do any type of explict delegation looking for the system classloader.

@jerboaa
Copy link
Author

jerboaa commented Aug 1, 2016

@janbartel

Example of the exception we see when not using WorkAroundJettyClassLoader:

java.lang.SecurityException: Configuration error: java.lang.ClassNotFoundException: sun.security.provider.ConfigFile not found by com.redhat.thermostat.web.endpoint [9]|
    at javax.security.auth.login.Configuration.getConfiguration(Configuration.java:278)
    at javax.security.auth.login.LoginContext$1.run(LoginContext.java:245)
    at javax.security.auth.login.LoginContext$1.run(LoginContext.java:243)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.login.LoginContext.init(LoginContext.java:243)
    at javax.security.auth.login.LoginContext.<init>(LoginContext.java:381)
    at javax.security.auth.login.LoginContext.<init>(LoginContext.java:458)
    at org.eclipse.jetty.jaas.JAASLoginService.login(JAASLoginService.java:231)
    at org.eclipse.jetty.security.authentication.LoginAuthenticator.login(LoginAuthenticator.java:61)
    at org.eclipse.jetty.security.authentication.BasicAuthenticator.validateRequest(BasicAuthenticator.java:92)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:483)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1589)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1213)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:487)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1552)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1126)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
    at org.eclipse.jetty.server.Server.handle(Server.java:550)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:321)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:254)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:269)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:97)
    at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:124)
    at org.eclipse.jetty.util.thread.Invocable.invokePreferred(Invocable.java:102)
    at org.eclipse.jetty.util.thread.strategy.ExecutingExecutionStrategy.invoke(ExecutingExecutionStrategy.java:58)
    at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.produceConsume(ExecuteProduceConsume.java:201)
    at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.run(ExecuteProduceConsume.java:133)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:672)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:590)
    at java.lang.Thread.run(Thread.java:745)
Caused by: 
java.lang.ClassNotFoundException: sun.security.provider.ConfigFile not found by com.redhat.thermostat.web.endpoint [9]
    at org.apache.felix.framework.BundleWiringImpl.findClassOrResourceByDelegation(BundleWiringImpl.java:1500)
    at org.apache.felix.framework.BundleWiringImpl.access$400(BundleWiringImpl.java:75)
    at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.loadClass(BundleWiringImpl.java:1923)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at com.redhat.thermostat.web.endpoint.internal.DelegatingWebappClassLoader.loadClass(DelegatingWebappClassLoader.java:80)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at javax.security.auth.login.Configuration$2.run(Configuration.java:251)
    at javax.security.auth.login.Configuration$2.run(Configuration.java:247)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.login.Configuration.getConfiguration(Configuration.java:246)
    at javax.security.auth.login.LoginContext$1.run(LoginContext.java:245)
    at javax.security.auth.login.LoginContext$1.run(LoginContext.java:243)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.login.LoginContext.init(LoginContext.java:243)
    at javax.security.auth.login.LoginContext.<init>(LoginContext.java:381)
    at javax.security.auth.login.LoginContext.<init>(LoginContext.java:458)
    at org.eclipse.jetty.jaas.JAASLoginService.login(JAASLoginService.java:231)
    at org.eclipse.jetty.security.authentication.LoginAuthenticator.login(LoginAuthenticator.java:61)
    at org.eclipse.jetty.security.authentication.BasicAuthenticator.validateRequest(BasicAuthenticator.java:92)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:483)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1589)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1213)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:487)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1552)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1126)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
    at org.eclipse.jetty.server.Server.handle(Server.java:550)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:321)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:254)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:269)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:97)
    at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:124)
    at org.eclipse.jetty.util.thread.Invocable.invokePreferred(Invocable.java:102)
    at org.eclipse.jetty.util.thread.strategy.ExecutingExecutionStrategy.invoke(ExecutingExecutionStrategy.java:58)
    at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.produceConsume(ExecuteProduceConsume.java:201)
    at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.run(ExecuteProduceConsume.java:133)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:672)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:590)
    at java.lang.Thread.run(Thread.java:745)

sun.security.provider.ConfigFile comes from the JDK. I suppose an alternative would be to work out the proper imports for those, then ensure the Framework properly exports them. Then DelegatingWebappClassLoader, the rough equivalent to your "container classloader" should be able to figure out where classes come from.

Thinking some more about this I've got some ideas as to how to simplify our class loading maze a bit. TCCL becoming the parent of WebAppClassLoader was the hint :-) Hopefully this will work with jetty 8+. I agree, WorkAroundJettyClassLoader, won't be needed.

@jerboaa
Copy link
Author

jerboaa commented Aug 3, 2016

So here is how we ended up solving this webapp container vs. OSGi class loading issue:
http://icedtea.classpath.org/hg/thermostat/rev/6c9955f6bc91

We still delegate to the system classloader since by setting the TCCL all classes of the webapp will get loaded by WeppAppClassLoader delegating to the set TCCL loader as parent. Our webapp libs include mongo-java-driver which uses java system classes and we don't know which they're using unless we try those empirically and solve it via explicit Import-Package instructions. That would be a fairly brittle approach. The delgating-to-system-loader-if-not-found seems the better alternative.

Either way, I think you can close this issue.

@janbartel
Copy link
Contributor

Ok, thanks for that info. Closing this issue.

@hieplq
Copy link

hieplq commented Feb 19, 2017

hi @janbartel and @joakime
after learn this issue for while, i guess it's difference #262.
This issue relate resolve a class, but #262 relate to load a service, i'm not sure it's same or not.

I still reproduce #262 with latest of jetty 9.4.1.v20170120 (setup a http2 on osgi container equinox)

from comment of janbartel

The right place to do that would be around line 142, just before you call new Server(). This is what we do in the jetty osgi support classes: in our bundle listener that detects and deploys new webapp bundles, we set a thread context classloader that is the classloader of the main jetty bundle, which is able to resolve all jetty classes

for Osgi container, BundleWebAppProvider already set TCCL so i don't know what i have to do to resolve #262.

please let me know latest of jetty support out of box to setup http2 on Osgi container or point me how to fix #262

@col-panic
Copy link

col-panic commented Mar 30, 2018

After finding this issue, and setting the Thread ContextClassLoader I'm left with

java.lang.NullPointerException: null
	at org.eclipse.jetty.webapp.MetaInfConfiguration.scanJars(MetaInfConfiguration.java:143) ~[org.eclipse.jetty.webapp_9.4.8.v20171121.jar:9.4.8.v20171121]
	at org.eclipse.jetty.webapp.MetaInfConfiguration.preConfigure(MetaInfConfiguration.java:102) ~[org.eclipse.jetty.webapp_9.4.8.v20171121.jar:9.4.8.v20171121]
	at org.eclipse.jetty.webapp.WebAppContext.preConfigure(WebAppContext.java:506) ~[org.eclipse.jetty.webapp_9.4.8.v20171121.jar:9.4.8.v20171121]
	at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:544) ~[org.eclipse.jetty.webapp_9.4.8.v20171121.jar:9.4.8.v20171121]
	at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68) [org.eclipse.jetty.util_9.4.8.v20171121.jar:9.4.8.v20171121]
	at info.elexis.server.openid.internal.WebappStarter.activate(WebappStarter.java:74) [es.openid/:na]

Just changed my target from Neon to Oxygen in Eclipse, hence upgrading from jetty 9.3 to 9.4 and my osgi embedded war file stopped working .... really not liking this too ....

@trush19
Copy link

trush19 commented May 9, 2024

Hi @janbartel , Is this issue fixed in Jetty Version 10.x (OSGi context) as this issue seems to be reproducible in Jetty 10.x (10.0.6) version.

@joakime
Copy link
Contributor

joakime commented May 9, 2024

@trush19 Jetty versions up to Jetty 11 are now at End of Community Support.

You should be using Jetty 12 at this point in time.

BTW, Eclipse RT and Eclipse IDE use our artifacts on OSGi without issue. (even back in the Jetty 10.0.6 time period)

@trush19
Copy link

trush19 commented May 17, 2024

Thank you for the suggestion @joakime .
One question on Jetty version 10.0.6 following is the comment in WebAppContext.class for Server classes.
// Server classes are classes that are hidden from being
// loaded by the web application using system classloader,
// so if web application needs to load any of such classes,
// it has to include them in its distribution.
public static final ClassMatcher __dftServerClasses = new ClassMatcher(
"org.eclipse.jetty." // hide jetty classes
);

Even after explicitly added these classes using below method still getting classNotFound Exception (java.lang.RuntimeException: java.lang.ClassNotFoundException: org.eclipse.jetty.webapp.WebInfConfiguration)

getServerClassMatcher().exclude("org.eclipse.jetty.webapp.");

Is there any other way we can include and load these classes?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants