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

Build GWT against Jetty transitive dependencies baseline #9720

Closed
eliasbalasis opened this issue Jan 17, 2021 · 29 comments
Closed

Build GWT against Jetty transitive dependencies baseline #9720

eliasbalasis opened this issue Jan 17, 2021 · 29 comments
Milestone

Comments

@eliasbalasis
Copy link

eliasbalasis commented Jan 17, 2021

GWT version: 2.9.0
Browser (with version): any
Operating System: any


Description

Running DevMode under GWT 2.9.x is practically impossible due to the build classpath inconsistencies.
see #9606 #9693

To permanently make the DevMode runtime classpath pains go away,
GWT needs to start getting built against the transitive dependencies baseline of the associated Jetty version.

Perhaps GWT built could start using "Ivy" for dependencies resolution, similar to Maven in order to achieve alignment with Jetty transitive dependencies baseline and produce a stable classpath at last.

Steps to reproduce

see my comments on #9606 and #9693
see also (gwtproject/tools#21)

Known workarounds

Maven classpath manipulation, as described in #9606 but doesn't work for GWT 2.9.0 due to its inconsistent build classpath.
More precisely,
gwt-dev is built against org.ow2.asm:7.1 while the asociated Jetty is built against org.ow2.asm:5.0.1
This causes "Super DevMode" with either "GWT Eclipse Plugin" or Maven commands and org.codehaus.mojo:gwt-maven-plugin to crash with runtime classpath errors.
A more recent version of Jetty is required as suggested under (gwtproject/tools#21) but even that won't permanently protect the build classpath from further abuse.
Besides, org.ow2.asm may not be the only misaligned transitive dependency.

Links to further discussions

#9606
#9693
(gwtproject/tools#21)

@eliasbalasis
Copy link
Author

eliasbalasis commented Apr 9, 2021

Having studied the structure of GWT source code,

I realized that "gwt-dev" and its siblings are Maven builds and use fixed versions for the common artifacts.
(e.g. org.ow2.asm:asm)

I wonder, if we could change that and inherit the common artifacts from "Jetty" transitive dependencies set instead, expecting that GWT would still build and run.

In fact, at maven\poms\gwt\pom-template.xml of master branch the declarations <jetty.version>9.2.14.v20151106</jetty.version> and <asm.version>7.1</asm.version> seem to be responsible for the classpath conflict.

I changed to <asm.version>5.0.1</asm.version> and the main build still is successful but I don't have privileges to push a change and make a pull request.

Can anyone help?

@jnehlmeier
Copy link
Member

You can not downgrade ASM to 5.0.1 because GWT compiler in gwt-dev.jar needs ASM 7.1 to support Java 11.

The only option is upgrading Jetty to a compatible version or not using DevMode with embedded Jetty. Use CodeServer with your own server instead, which is the recommend way to use GWT these days.

@eliasbalasis
Copy link
Author

I understand but I cannot afford not using DevMode.

Can't we build GWT against a version of Jetty based on same version of asm?

This would keep everyone happy 😊

see also gwtproject/tools#21

@jnehlmeier
Copy link
Member

I understand but I cannot afford not using DevMode.

Could very well be that you can not use future GWT versions then. At least future GWT based on J2CL will not have a DevMode with embedded Jetty for server code.

Can't we build GWT against a version of Jetty based on same version of asm?

Sure, if a decision is made to drop Java 7 support because the Jetty version in question requires Java 8. Currently we still compile to Java 7 byte code for those enterprise users so that server side jars, e.g. gwt-servlet.jar, can run with legacy Java 7.

@tbroyer
Copy link
Member

tbroyer commented Apr 11, 2021

Fwiw: https://groups.google.com/g/google-web-toolkit-contributors/c/tMR3Dv1YBBE (almost two years old)

I think nobody objects to dropping Java 7 support.

@eliasbalasis
Copy link
Author

Thank you @jnehlmeier and @tbroyer.

Could very well be that you can not use future GWT versions then

Indeed, I am stuck at GWT 2.8.2

future GWT based on J2CL will not have a DevMode with embedded Jetty for server code.

I am looking forward to that.

I think nobody objects to dropping Java 7 support.

I agree, Java 7 could be dropped and this should make room for classpath alignment between GWT and Jetty so that DevMode can be used with GWT 2.9.0+, until J2CL replaces DevMode.

@jnehlmeier
Copy link
Member

Fwiw: https://groups.google.com/g/google-web-toolkit-contributors/c/tMR3Dv1YBBE (almost two years old)

I think nobody objects to dropping Java 7 support.

Yeah I know. Personally I am fine with dropping Java 7.

However the question is if we want to invest work into upgrading Jetty again or rather remove jetty from DevMode.

We also have to find a solution to support javax.servlet and jakarta.servlet for GWT's server side classes, mainly GWT-RPC servlet and RequestFactory servlet. There is already a first question here on GitHub if we plan to support the new jakarta namespace (#9727).
With DevMode + embedded Jetty this becomes even more annoying to solve, given that we can only bundle one jetty into gwt-dev. We already have to think about duplicating some server classes to support GWT-RPC / RequestFactory going forward.

So I am thinking a bit more long term, given the limited maintenance effort for GWT these days. I could even imaging @eliasbalasis asking for Jetty 11 in a couple of months because he wants to use newer libraries that require jakarta now ;-) And then we might be in ASM trouble again, if jetty becomes too new.

That is why I strongly think we should:

  • remove embedded Jetty from DevMode so developers are forced to use their own jetty even if some of them will be unhappy. However the benefit is that they can switch to Jakarta whenever their app and their app dedepencies are ready without again waiting long time for GWT to update its embedded Jetty.
  • invest some time to provide servlet endpoints for GWT-RPC / RequestFactory for both javax.servlet and jakarta.servlet

@tbroyer
Copy link
Member

tbroyer commented Apr 11, 2021

That is why I strongly think we should:

  • remove embedded Jetty from DevMode so developers are forced to use their own jetty even if some of them will be unhappy. However the benefit is that they can switch to Jakarta whenever their app and their app dedepencies are ready without again waiting long time for GWT to update its embedded Jetty.

I've been advocating this for years, so you're preaching to the choir here as far as I'm concerned.
But just as legacy DevMode hasn't been removed yet despite being "almost dead" for many years, I doubt this will happen; particularly as saying "DevMode embedded Jetty has been removed, you have to use a separate servlet container" is not much different from what we've been saying here: "if DevMode's embedded Jetty doesn't work for you, just don't use it, use a separate servlet container".

  • invest some time to provide servlet endpoints for GWT-RPC / RequestFactory for both javax.servlet and jakarta.servlet

This should be relatively easy, possibly with a bit of refactoring around RPCServletUtils and quite a bit of copy/pasting. It'll be harder to test those though as JUnitShell could only support one or the other, but not both.

I'd say this is low priority, and could be done as a third-party (possibly with a bit more copy/pasting than if it was "built in").
Fwiw, Spring hasn't made the switch to Jakarta EE 9 either.

@eliasbalasis
Copy link
Author

eliasbalasis commented Apr 11, 2021

Well said @jnehlmeier and @tbroyer,

I agree that until DevMode gets permanently removed there could still be a possibility of having to make further adjustments to support newer versions of Jetty.

I can promise you I will not request this.
However, others may ask for it and you could safely say it is not supported yet.

despite being "almost dead" for many years
I agree, DevMode cannot be retired yet. not until J2CL becomes available in my view.

Until then, can we at least drop Java 7 and use a compatible version of Jetty one depending on same version of ASM? with or without "Jakarta namespace" (your call).

@jnehlmeier
Copy link
Member

@tbroyer I know you have. I have created https://groups.google.com/g/google-web-toolkit-contributors/c/iU9hckIab2o and hope we can get to a decision. I hate it that it feels like nobody is really responsible for such decisions.

@eliasbalasis

I also believe that DevMode cannot be retired yet, not until J2CL becomes available.

Of course it can be retired. A lot of people are not using it anymore, or at least are not using the embedded Jetty anymore without loosing productivity. These people are using Maven / Gradle and use Maven / Gradle plugins to launch GWT CodeServer and a servlet container of choice. Or they use a ready made Docker image that contains a preconfigured servlet container to deploy the app, usable by all employees in a company. Or they simply use their IDE to launch Jetty / Tomcat (works well with IntelliJ for example). So there are quite a few options that don't really hurt productivity.

With J2CL you have to launch your server side code yourself anyways.

@eliasbalasis
Copy link
Author

eliasbalasis commented Apr 11, 2021

I am not perfectly familiar with J2CL but since it still needs a code server it won't change the situation much.

I have to admit that this last bit has started making me lean towards retirement of DevMode even though it doesn't suit my team's needs.
Also, this is not just about myself or my team.
As long as DevMode is still present people may inevitably still want to make use of it.
I am sorry to say, but you may never be able to convince everyone to change ways, unless you forcibly retire DevMode even though that could raise a few eyebrows.

I equally consider support for Jakarta namespaces low-priority.

@jnehlmeier
Thanks for creating https://groups.google.com/g/google-web-toolkit-contributors/c/iU9hckIab2o , I will follow.

Finally, it shouldn't be too difficult to drop Java 7 and use a compatible version of Jetty, one depending on same version of ASM. This should keep everyone happy 😊

@eliasbalasis
Copy link
Author

eliasbalasis commented Apr 18, 2021

A workaround has been found.

(see #9693 )

study https://github.com/eliasbalasis/eclipse-gwt-recipe

Import this Maven project into Eclipse
and
use "GWT Eclipse Plugin" with "Super Development Mode" or any other DevMode combination.

This should reproduce the problem.

Enable exclusions section around <artifactId>jetty-annotations</artifactId> at modules\gwt-web-eclipse\pom.xml to bypass the problem (read explanation in comments section).

I still strongly believe that the GWT classpath must be aligned with that of the associated Jetty though

@ArveMediaconnect
Copy link

@jnehlmeier

Or they simply use their IDE to launch Jetty / Tomcat (works well with IntelliJ for example).

You wouldn't by any chance have an example of how to set this up? The ability to do this seems to always be slightly outside of my grasp, being mostly a coder and not having much knowledge about servlet containers or similar.

I've successfully managed to run tomcat and having deployed a war:exploded of a web-module, so I know tomcat itself is working, but naturally this does not provide a CodeServer and hence debugging abilities.

I would think that it should be as simple as making my GWT run config start with "-nostartServer -war <some.smart.dir>" and then pointing a tomcat run config to that somehow, but I have simply not been able to connect these two sides in a functional setup.

Sorry for bringing this up on this bug report.

@jnehlmeier
Copy link
Member

@ArveMediaconnect

I use IntelliJ Ultimate, which allows you to configure applications servers within the IDE. You would download/install Jetty locally, configure that installation within IntelliJ as application server and then create a run configuration for Jetty. Within the run configuration you can tell IntelliJ which artifacts to deploy. These artifacts can either be produced by IntelliJ itself or you use external artifacts (e.g. produced by Maven/Gradle). As I use Gradle I configured the *.war file produced by Gradle as external artifact. In addition I configured the Jetty run configuration to launch the Gradle build before starting Jetty itself. So whenever I restart Jetty within IntelliJ, Gradle will be launched beforehand and will rebuild the war.

GWT CodeServer / DevMode is started separately in a terminal window (either within IntelliJ or external) using a custom Gradle task which basically just forks a JVM and launches CodeServer main class with the appropriate classpath and parameters. Given that CodeServer or DevMode are just Java classes you can launch them however you want. And yes you would need to make sure that the output of either DevMode -war /some/path or CodeServer -launcherDir /some/path ends up in your deployed war. Personally I have configured a custom Gradle war task and added the contents of that '/some/path' to the war. I am pretty sure you can do that with Maven as well.

If you use IntelliJ's own artifact building feature then you can add any additional directory to an artifact (project structure -> artifacts -> select your artifact -> the small + button allows you do add various content). So you could add the directory you have used with DevMode - war / CodeServer -launcherDir to the artifact.

@ArveMediaconnect
Copy link

@jnehlmeier
Thank you for your reply!

I use IntelliJ Ultimate, which allows you to configure applications servers within the IDE. You would download/install Jetty locally, configure that installation within IntelliJ as application server and then create a run configuration for Jetty. Within the run configuration you can tell IntelliJ which artifacts to deploy. These artifacts can either be produced by IntelliJ itself or you use external artifacts (e.g. produced by Maven/Gradle). As I use Gradle I configured the *.war file produced by Gradle as external artifact. In addition I configured the Jetty run configuration to launch the Gradle build before starting Jetty itself. So whenever I restart Jetty within IntelliJ, Gradle will be launched beforehand and will rebuild the war.

This was more or less what I had done before, only with tomcat. I've since tried it with jetty as well, and they seem to work more or less the same for me.

If you use IntelliJ's own artifact building feature then you can add any additional directory to an artifact (project structure -> artifacts -> select your artifact -> the small + button allows you do add various content). So you could add the directory you have used with DevMode - war / CodeServer -launcherDir to the artifact.

This seems to be the missing piece of the puzzle. I was still not seeing anything different, but now the good old dev mode bookmarklet did its thing, and sourcemaps and debugging with this now seems to work!

This whole thing is quite cumbersome though, compared to the more or less one step to do all this with the built-in jetty. I'm sure there's something I could set up to trigger the recompile automatically without the bookmarklet, but there's still a large amount of extra compiling necessary. The building of the artifact for the tomcat run configuration builds 3 permutations, and then the one that will actually be used is compiled again when hitting the bookmarklet, if I understand how this works. This is simply because of 3 locales, which have been constrained to 1 by the superdevmode propertym which is not active when building the artifact. Have been looking into how to bypass this, with the inheriting module that overwrites the locale property, but so far I have not found a way to dynamically choose which module is the one to run, without having a whole separate web.xml or similar. Maybe I'll fix that in time.

@jnehlmeier
Copy link
Member

@ArveMediaconnect

This seems to be the missing piece of the puzzle. I was still not seeing anything different, but now the good old dev mode bookmarklet did its thing, and sourcemaps and debugging with this now seems to work!

If you don't use an ancient version of GWT then you usually don't need these bookmarklets. If setup correctly, then GWT recompiles the app automatically when you reload the browser and changes have been detected.

The building of the artifact for the tomcat run configuration builds 3 permutations

Because your IntelliJ artifact has "GWT compile output" configured in the war. This tells IntelliJ to run a GWT build as configured in the GWT facet of your project. For development you don't need that so just remove it from the artifact (or create a second "dev" artifact and deploy that one). For development you only need the CodeServer/DevMode output in your war.

and then the one that will actually be used is compiled again when hitting the bookmarklet, if I understand how this works.

Thats the single permutation produced by CodeServer / DevMode matching your browser. As said above, with more recent GWT versions you usually don't need that bookmarklet as long as you make sure that the output of CodeServer/DevMode -launcherDir/war is inside your war.

Maybe because you have "GWT compile output" in your artifact, it overrides the special code produced by CodeServer/DevMode -launcherDir/war which defeats the "auto recompile on reload" feature. Or you first build your artifact and then launch CodeServer/DevMode. In that case the special files produced by CodeServer/DevMode might not end up in your deployed war file.

@ArveMediaconnect
Copy link

@jnehlmeier
That sounds great, I'll check that today. We're running GWT 2.9.0 so that should work.

Thank you so much for all the help!

@niloc132
Copy link
Member

I have a patch in progress that updates latest HtmlUnit, and as part of this, jetty must be updated. I switched to latest jetty, 9.4.43.v20210629, and only found there to be a few things that had to be updated inside of GWT:

  • jaspi changes in jetty-all mean that we either need to ship a separate jar for licensing reasons, or disable the authentication module. I've elected for now to disable that authentication, and let consumers re-enable it if necessary
  • WebAppContext changed in a few ways, I had to change a few details to have our JettyLauncher continue to work, but nothing too major. Biggest concern is that in order to take over some classloading, we are calling some now-deprecated methods in WebAppContext. A few new warnings popped up, I cleaned things up to suppress or prevent them.

To build this, use https://github.com/niloc132/gwt/tree/htmlunit-upgrade for your gwt/ code, and https://github.com/Vertispan/tools/tree/update-htmlunit for your tools/ directory, so that the new dependencies are available. There are two bugs in htmlunit that have been reported that we will need to have fixed before this can ship, as several tests break in gwt-user tests, but so far, it seems to function properly.

To try using it from maven, please point to https://repo.vertispan.com/gwt-snapshot/ and use version 2.10.0-htmlunit-upgrade-SNAPSHOT.

@sstamm
Copy link
Contributor

sstamm commented Oct 1, 2021

Thanks @niloc132.
I'm getting an error with your changes, do I miss someting? 😫

2021-10-01 10:10:11.738:INFO:oejs.Server:main: jetty-9.4.43.v20210629; built: 2021-06-30T11:07:22.254Z; git: 526006ecfa3af7f1a27ef3a288e2bef7ea9dd7e8; jvm 1.8.0_302-b08
Starting Jetty on port 8888
   [WARN] Failed startup of context c.g.g.d.s.j.WebAppContextWithReload@14d9a855{....}
java.lang.ClassNotFoundException: org.eclipse.jetty.webapp.WebAppContext
	at java.lang.ClassLoader.findClass(ClassLoader.java:523)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
	at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:487)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
	at org.eclipse.jetty.util.Loader.loadClass(Loader.java:64)
	at org.eclipse.jetty.xml.XmlConfiguration$JettyXmlConfiguration.nodeClass(XmlConfiguration.java:477)
	at org.eclipse.jetty.xml.XmlConfiguration$JettyXmlConfiguration.configure(XmlConfiguration.java:417)
	at org.eclipse.jetty.xml.XmlConfiguration.configure(XmlConfiguration.java:364)
	at org.eclipse.jetty.plus.webapp.EnvConfiguration.lambda$configure$0(EnvConfiguration.java:120)
	at org.eclipse.jetty.webapp.WebAppClassLoader.runWithServerClassAccess(WebAppClassLoader.java:138)
	at org.eclipse.jetty.plus.webapp.EnvConfiguration.configure(EnvConfiguration.java:118)
	at org.eclipse.jetty.webapp.WebAppContext.configure(WebAppContext.java:498)
	at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1409)
	at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:910)
	at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:288)
	at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:524)
	at com.google.gwt.dev.shell.jetty.JettyLauncher$WebAppContextWithReload.doStart(JettyLauncher.java:555)
	at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:73)
	at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:169)
	at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:110)
	at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:97)
	at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:73)
	at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:169)
	at org.eclipse.jetty.server.Server.start(Server.java:423)
	at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:110)
	at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:97)
	at org.eclipse.jetty.server.Server.doStart(Server.java:387)
	at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:73)
	at com.google.gwt.dev.shell.jetty.JettyLauncher.start(JettyLauncher.java:763)
	at com.google.gwt.dev.DevMode.doStartUpServer(DevMode.java:636)
	at com.google.gwt.dev.DevModeBase.startUp(DevModeBase.java:898)
	at com.google.gwt.dev.DevModeBase.run(DevModeBase.java:705)
	at com.google.gwt.dev.DevMode.main(DevMode.java:432)
	Suppressed: java.lang.ClassNotFoundException: org.eclipse.jetty.webapp.WebAppContext
		at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
		at org.eclipse.jetty.webapp.WebAppClassLoader.findClass(WebAppClassLoader.java:629)
		at com.google.gwt.dev.shell.jetty.JettyLauncher$WebAppContextWithReload$WebAppClassLoaderExtension.findClass(JettyLauncher.java:450)
		at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:511)
		... 30 more

@niloc132
Copy link
Member

niloc132 commented Oct 1, 2021

@sstamm can you give some detail on how you got that error? How you're running dev mode, what else is running in your server or on your classpath, etc.

The class is definitely on the classpath somewhere, since WebAppContextWithReload was able to load (WebAppClassLoaderExtension is an inner class to it, so an instance has been created), so we might be loading it wrong. On the other hand, from the tail end of your log...:

java.lang.ClassNotFoundException: org.eclipse.jetty.webapp.WebAppContext
		at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
		at org.eclipse.jetty.webapp.WebAppClassLoader.findClass(WebAppClassLoader.java:629)
		at com.google.gwt.dev.shell.jetty.JettyLauncher$WebAppContextWithReload$WebAppClassLoaderExtension.findClass(JettyLauncher.java:450)
		at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:511)

Assuming you are using the very latest on my branch (which I think is slightly ahead of the maven artifact I deployed), line 450 is the super call here:

       try {
          return super.findClass(name);
        } catch (ClassNotFoundException e) {
          // Don't allow server classes to be loaded from the outside.
          if (WebAppContextWithReload.this.isServerClass(name)) {
            throw e;
          }
        }

which suggests that super wasnt able to find it either, even though that is what would have been called without our subclass.

I'm making a new build shortly to tweak a few other small things and update it to latest upstream, can you confirm that the bug still happens and see if you can add any extra detail? I'll add an edit here when the snapshot is updated.

EDIT: updated at https://repo.vertispan.com/gwt-snapshot/com/google/gwt/gwt-dev/2.10.0-htmlunit-upgrade-SNAPSHOT/, see version 20211001.151235-5 etc.

@sstamm
Copy link
Contributor

sstamm commented Oct 4, 2021

@niloc132 I'm runnig devmode out of Eclipse (GWT Eclipse Plugin 3.0.0).

I debugged into it:

      protected Class<?> findClass(String name) throws ClassNotFoundException {
        // For system path, always prefer the outside world.
        // Note: bootstrap has already been searched, so javax. classes should be
        // tried from the webapp first (except for javax.servlet).
        if (WebAppContextWithReload.this.isSystemClass(name) && !systemClassesFromWebappFirst.match(name)) {
          try {
            return systemClassLoader.loadClass(name);
          } catch (ClassNotFoundException e) {
          }
        }

        try {
          return super.findClass(name);
        } catch (ClassNotFoundException e) {
          // Don't allow server classes to be loaded from the outside.
          if (WebAppContextWithReload.this.isServerClass(name)) {
            throw e;
          }
        }

systemClassLoader does contain the class, but WebAppContextWithReload.this.isSystemClass(name) retuns false
At super.findClass(name) the WebAppClassLoader does not have any _transformers so it looks up all JARs from my Web-App (WEB-INF/lib) which does not contain jetty.

So finally WebAppContextWithReload.this.isServerClass(name) causes the Exception.

@niloc132
Copy link
Member

niloc132 commented Oct 4, 2021

How are you reproducing this, so I can see it also and work on it? For example, is this a normal startup of a plain application with no servlets (or one simple servlet etc), or does this require some specifics from your own application to cause this?

The class you seem to be trying to look up is part of jetty itself, and it looks like some jetty xml file is being read - do you have some extra jetty xml files in your project that aren't part of GWT itself?

There is a special check to allow some startup code to be able to get outside the server classloader, but it isn't being triggered here. I see two paths that this could take to fix it (either "we need to preload the class so that these xml files work, before we replace the classloader", or "Jetty's WebAppClassLoader can no longer be extended in the way we do it, we need to delegate to specific superclass methods, since __loadServerClasses isn't exposed so we can read it").

@sstamm
Copy link
Contributor

sstamm commented Oct 4, 2021

@niloc132 dang it, this was the right hint, thanks!
I've a jetty-env.xml to fake a JNDI-Resource (Datasource) because otherwise Jetty is crashing on startup due to an java.lang.IllegalStateException: (Nothing to bind for name jdbc/fcc_ds) .

Do you know what I've to use instead of WebAppContext?

<?xml version="1.0" encoding="UTF-8"?>

<Configure class="org.eclipse.jetty.webapp.WebAppContext">
	<New id="DSTest" class="org.eclipse.jetty.plus.jndi.Resource">
		<Arg></Arg>
		<Arg>jdbc/fcc_ds</Arg>
		<Arg>
			<!-- Dummy Object that jetty has something to lookup -->
			<New class="java.lang.Object"/>
		</Arg>
	</New>
</Configure>

@tbroyer
Copy link
Member

tbroyer commented Oct 4, 2021

This is the correct way to do it: https://www.eclipse.org/jetty/documentation/jetty-9/index.html#jetty-env-xml

But it looks like the filtering in GWT's WebAppContextWithReload.WebAppClassLoaderExtension needs to be adapted.
(last time I did it, I spent days on it and ended up exhausted; I swore to myself that I would never ever touch that code again, which is also why I advocated since then for trimming down DevMode's features to either no longer do that classloading massaging –loading everything from WEB-INF/classes and WEB-INF/lib like in a normal Jetty, ignoring the classpath–, possibly removing support for jetty-env.xml and the like, or going all the way to removing servlet support altogether and only serve static files; see discussions above and elsewhere)

The right answer in your case anyway is to stop using DevMode's embedded server, and instead use a real Jetty (or Tomcat or whatever) server, along with CodeServer (or DevMode's -noserver mode).

@niloc132
Copy link
Member

niloc132 commented Oct 4, 2021

The filtering seems to be correct - that class is not legal to load from inside the application, but jetty's WebAppClassLoader is intended to be able to load it within the context of the WebAppClassLoader.runWithServerClassAccess(...) wrapper around a lambda:

                    WebAppClassLoader.runWithServerClassAccess(() -> {
                        configuration.configure(context);
                        return null;
                    });

Within that scope, the WebAppClassLoader.__loadServerClasses threadlocal is set to true, and loadClass is supposed to succeed... except GWT's subclass passed a "bootstrapOnlyClassLoader", so WebAppClassLoader can't find its own classes properly, and the threadlocal isn't visible, else we could use it to read from the systemClassLoader instead. Had we not "passed" the isServerClass check (since org.eclipse.jetty.* is a server class), the later allowedFromSystemClassLoader check would have matched it, and we would have loaded the class correctly.

My guess is that Jetty is "expecting" that GWT overrides loadClass instead of findClass, as the contract for ClassLoader.loadClass is that loadClass is called first, and only if that fails (going up the classloaders until there is no parent) does findClass get called. WebAppClassLoader.findClass has transformer logic as noted, but loadClass is where WebAppClassLoader does the check if it should permit the class to be loaded in this special case due to being in the lambda above.
https://github.com/eclipse/jetty.project/blob/9f1f1fea1bd84590739cdf26b04e92052a1fa465/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppClassLoader.java#L492-L495

@oliviercailloux
Copy link

oliviercailloux commented Oct 17, 2021

I'd say this is low priority [about supporting the Jakarta namespace]

As a developer coming from a Jakarta background (to which I naturally evolved, coming previously from Java EE and having never been much convinced by the Spring alternative), and currently considering GWT for building GUIs, I’d view it as an important point that GWT would adopt the Jakarta namespace. I wouldn’t want to come back to JPA 2, and so on, now that I have switched my dependencies to JPA 3 (and similarly for many other Jakarta specs) and have published code that refers to the new namespace. Not that it would be extremely hard work, admittedly, but it would be going in the wrong direction.

Fwiw, Spring hasn't made the switch to Jakarta EE 9 either

Well, Spring is essentially an alternative, or even a competitor, to Jakarta EE (and was an alternative / a competitor to Java EE previously), so I wouldn’t consider this point as a strong indication. You might want to consider the behavior of other actors of the Java ecosystem. Jetty has switched though maintains compatibility (v11 is Jakarta, v10 uses Java EE namespace); Eclipse (obviously) has switched; IntelliJ seems to support it (I just did a quick check); Hibernate seems to have switched to Jakarta being supported first; and so on. Basically is seems like the major actors are deciding to switch to Jakarta while maintaining compatibility with Java EE but gently pushing their users to upgrade to Jakarta. It would be strange that GWT would stick with the old namespace for still quite some time; it might feel like signalling non up-to-date technology.

(I know that I am just one user among many, but I thought I’d let you know my feelings; it seems to me that I might well be a typical or growing case among your users. Sorry for the noise if this opinion is inappropriate here.)

@tbroyer
Copy link
Member

tbroyer commented Oct 18, 2021

@oliviercailloux Please note that we are only talking about:

  • Support for Jakarta Servlets in the embedded servlet container of DevMode, something useful for demo-size projects but that we're pushing against for anything bigger (use your own server alongside DevMode)
  • Support for Jakarta Servlets in GWT-RPC and Request Factory, things I don't think we're advocating nowadays (use RESTish endpoints with JSON, with JsInterop for the JSON↔️Java mapping). @niloc132 has been working on a new version of GWT-RPC, maybe he'll want to migrate it to Jakarta though.
  • Support for Jakarta Bean Validation, knowing that Java Bean Validation has been deprecated for a couple releases

@niloc132
Copy link
Member

niloc132 commented Nov 1, 2021

@sstamm I have just pushed a new build of the 2.10.0-htmlunit-upgrade-SNAPSHOT version to the same repo, can you try out your jetty use cases? From a quick local sample, I think I may have resolved that issue.

niloc132 added a commit to niloc132/gwt-playground that referenced this issue Apr 19, 2022
Several old tests previously not able to run in HtmlUnit are now
enabled, and two tests are disabled, linked to the appropriate bug
report.

The jaspi security/authentication module has been disabled in the dev
mode server. It is no longer included in the base jetty install due to
licensing reasons, and is disabled by default here for the same reason.

Many small changes were necessary across various parts of testing and
dev mode infrastructure:
 * HtmlUnit has introduced a proxy for window and document, legacy dev
   mode needs to treat those objects as if they were the real instance.
 * HtmlUnit has additional protection to avoid letting a page interact
   with a real Java object. This had to be relaxed in order to let
   legacy dev mode tests continue to work.
 * The custom classloader used to run a webapp within dev mode now
   overrides loadClass instead of findClass, to correctly delegate to
   Jetty's own implementation to handle startup issues. Additionally,
   isSystemClass and isServerClass are relocated and deprecated.

Bug: #9720
Bug-Link: gwtproject/gwt#9720
Co-Authored-By: Mihai Stanciu <[email protected]>
Change-Id: Ie14cb33cb0edea57c121de943f2964a8cf0540ee
@niloc132 niloc132 added this to the 2.10 milestone Apr 19, 2022
@niloc132
Copy link
Member

niloc132 commented Jun 9, 2022

GWT 2.10 will be released in the next few days, marking this as closed.

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