vaadin / flow

Vaadin Flow is a Java framework binding Vaadin web components to Java. This is part of Vaadin 10+.
Apache License 2.0
610 stars 166 forks source link

Not possible to use skinnywar in Vaadin14 #7805

Closed guttormvik closed 4 years ago

guttormvik commented 4 years ago

TLDR

With a large ear project with multiple war/jars, it is desirable to use skinnywar to keep the size down. This does not work with Vaadin14

Our existing Vaadin8 app

In our Vaadin8 based application we have 10 wars, 5 jars + one "widgetset" war. Those wars that use vaadin depend on the "widgetset" war, and everything works fine.

We have also turned on "skinnywar" in the ear build, to place all shared jars in ear\lib and keep the overall size down. With skinnywar on, or ear is 100M. Without skinnywar, our ear is 180M. So skinnywar gives a significant saving that we'd like to keep when moving to Vaadin14.

Trying the same in Vaadin14

So far in my Vaadin14 experiments it seems it is not possible to use skinnywar. With Skinnywar on, and 2 war/jars depending on Vaadin, all vaadin jars end up in ear\lib, and they are unable to use the classloader to 1) access the frontend stuff in the war 2) Scan for @Route

As soon as I remove skinnywar, my tester ear project works.

Note: This issue is perhaps not present in all application servers. It depends how their classloader is implemented. We use wildfly 18, and there, if jar A depends on jar B, A's classloader can see B's classes, but B's classloader can't see A's.

In addition, it also doesn't seem possible in Vaadin14 to create a shared "widgetset". So far it seems all Vaadin-based wars must build their own copy of the client-side .

This means that for each Vaadin-based war I'll get an overhead of about:

( Based on my trivial starter project and Vaadin 14.2.0.alpha7 )

If I convert all 10 wars in my ear to vaadin, that means an overhead of 170M! Adding in the 80MB of shared classes from the rest of our app, it means our 100MB Vaadi8 app becomes a 350MB Vaadin14 app!

Possible solution?

With my simplistic understanding of Vaadin's internal workings, this could pehaps be solved if each war had an "entrypoint" class (MyServlet extends VaadinServlet perhaps?) and Vaadin used its classloader. This would then see everything in the war, even if the rest of vaadin's classes were in ear\lib.

With something like this, we would get rid of 14MB of the per-war overhead. We would still have the 3MB of frontend stuff per war, but size-wise this would be a huge improvement.

juhopiirainen commented 4 years ago

Thanks for the issue @guttormvik . I move this to the Flow team for investigations.

pleku commented 4 years ago

In addition, it also doesn't seem possible in Vaadin14 to create a shared "widgetset". So far it seems all Vaadin-based wars must build their own copy of the client-side .

This is possible since 14.2 with this approach https://github.com/vaadin/flow-and-components-documentation/blob/master/documentation/portlet-support/portlet-05-creating-multi-module-portlet-project.asciidoc#configuring-a-vaadin-portlet-multi-module-project But this makes the "widgetset" depend on all other Vaadin app jars. which is not always optimal.

Another alternative is shown here https://vaadin.com/labs/micro-frontend where the "default widgetset" is excluded from the other apps frontend build.

guttormvik commented 4 years ago

This is possible since 14.2 with this approach https://github.com/vaadin/flow-and-components-documentation/blob/master/documentation/portlet-support/portlet-05-creating-multi-module-portlet-project.asciidoc#configuring-a-vaadin-portlet-multi-module-project But this makes the "widgetset" depend on all other Vaadin app jars. which is not always optimal.

That looks promising. This specifically talks about portlet all the way through. Will it also work for multiple regular vaadin apps in separate wars?

And, if I understand correctly, this only takes care of the "frontend" bit? Each "portlet" war would still depend on vaadin, and have a copy of the vaadin jars, and if you package into an ear with skinnywar it stops working?

Just to be clear: For our use-case, skinnywar support is the most important thing, as it affects everything, not just vaadin. Sharing "widgetset" would just be the icing on the cake.

guttormvik commented 4 years ago

It seems the problem isn't limited to "skinnywar".

I have now turned off skinnywar, so that my main ptsmc-web.war contains all its requred vaadin jars, and then I tried to add a ptsmc-core.jar with the intention of having some vaadin-related utility functions. When this depends on vaadin, or even just flow-data, my app fails on startup:

    16:32:29,513 ERROR [org.jboss.msc.service.fail] (ServerService Thread Pool -- 76) MSC000001: Failed to start service jboss.deployment.subunit."ptsmc.ear"."ptsmc-web.war".undertow-deployment: org.jboss.msc.service.StartException in service jboss.deployment.subunit."ptsmc.ear"."ptsmc-web.war".undertow-deployment: java.lang.RuntimeException: java.lang.NullPointerException
        at org.wildfly.extension.undertow.deployment.UndertowDeploymentService$1.run(UndertowDeploymentService.java:81)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
        at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1982)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
        at java.lang.Thread.run(Thread.java:748)
        at org.jboss.threads.JBossThread.run(JBossThread.java:485)
    Caused by: java.lang.RuntimeException: java.lang.NullPointerException
        at io.undertow.servlet.core.DeploymentManagerImpl.deploy(DeploymentManagerImpl.java:254)
        at org.wildfly.extension.undertow.deployment.UndertowDeploymentService.startContext(UndertowDeploymentService.java:96)
        at org.wildfly.extension.undertow.deployment.UndertowDeploymentService$1.run(UndertowDeploymentService.java:78)
        ... 8 more
    Caused by: java.lang.NullPointerException
        at com.vaadin.flow.router.internal.AbstractRouteRegistry.addErrorTarget(AbstractRouteRegistry.java:392)
        at com.vaadin.flow.server.startup.ApplicationRouteRegistry.lambda$setErrorNavigationTargets$3(ApplicationRouteRegistry.java:263)
        at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
        at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
        at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
        at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
        at java.util.HashMap$KeySpliterator.forEachRemaining(HashMap.java:1556)
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
        at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
        at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
        at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:485)
        at com.vaadin.flow.server.startup.ApplicationRouteRegistry.setErrorNavigationTargets(ApplicationRouteRegistry.java:263)
        at com.vaadin.flow.server.startup.ErrorNavigationTargetInitializer.onStartup(ErrorNavigationTargetInitializer.java:54)
        at io.undertow.servlet.core.DeploymentManagerImpl$1.call(DeploymentManagerImpl.java:204)
        at io.undertow.servlet.core.DeploymentManagerImpl$1.call(DeploymentManagerImpl.java:186)
        at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:42)
        at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
        at org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105)
        at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1504)
        at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1504)
        at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1504)
        at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1504)
        at io.undertow.servlet.core.DeploymentManagerImpl.deploy(DeploymentManagerImpl.java:252)
        ... 10 more

    16:32:29,586 ERROR [org.jboss.as.controller.management-operation] (Controller Boot Thread) WFLYCTL0013: Operation ("deploy") failed - address: ([("deployment" => "ptsmc.ear")]) - failure description: {"WFLYCTL0080: Failed services" => {"jboss.deployment.subunit.\"ptsmc.ear\".\"ptsmc-web.war\".undertow-deployment" => "java.lang.RuntimeException: java.lang.NullPointerException
        Caused by: java.lang.RuntimeException: java.lang.NullPointerException
        Caused by: java.lang.NullPointerException"}}
pleku commented 4 years ago

This specifically talks about portlet all the way through. Will it also work for multiple regular vaadin apps in separate wars?

It does work. In portlets case those are separate wars deployed independently.

And, if I understand correctly, this only takes care of the "frontend" bit?

Yes, only frontend part, what used to be called "widgetset" in earlier versions.

Each "portlet" war would still depend on vaadin, and have a copy of the vaadin jars, and if you package into an ear with skinnywar it stops working?

I don't know. I think skinny wars don't have anything to do with the wars used in portals. Those contain all.

For using skinny wars with Vaadin even without portlets - I think it should be possible but it might be that there is some classloader issue or something (first guess).

But, I think the error from the previous message might though just be a bug of some sort in the routeregistry code

 Caused by: java.lang.NullPointerException
        at com.vaadin.flow.router.internal.AbstractRouteRegistry.addErrorTarget(AbstractRouteRegistry.java:392)
        at com.vaadin.flow.server.startup.ApplicationRouteRegistry.lambda$setErrorNavigationTargets$3(ApplicationRouteRegistry.java:263)
        at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)

and being able to reproduce this with a simple case would be great. Are there routes defined in multiple jars ?

guttormvik commented 4 years ago

and being able to reproduce this with a simple case would be great.

Here is a simple application ptsmc-ear-war-jar.zip:

I build it with mvn clean package -Pproduction And copy ptsmc-ear\target\ptsmc.ear to wildfly-18.0.1.Final deploy dir

Running wildfly I get:

16:07:40,964 ERROR [org.jboss.msc.service.fail] (ServerService Thread Pool -- 76) MSC000001: Failed to start service jboss.deployment.subunit."ptsmc.ear"."ptsmc-web.war".undertow-deployment: org.jboss.msc.service.StartException in service jboss.deployment.subunit."ptsmc.ear"."ptsmc-web.war".undertow-deployment: java.lang.RuntimeException: java.lang.NullPointerException
        at org.wildfly.extension.undertow.deployment.UndertowDeploymentService$1.run(UndertowDeploymentService.java:81)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
        at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1982)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
        at java.lang.Thread.run(Thread.java:748)
        at org.jboss.threads.JBossThread.run(JBossThread.java:485)
Caused by: java.lang.RuntimeException: java.lang.NullPointerException
        at io.undertow.servlet.core.DeploymentManagerImpl.deploy(DeploymentManagerImpl.java:254)
        at org.wildfly.extension.undertow.deployment.UndertowDeploymentService.startContext(UndertowDeploymentService.java:96)
        at org.wildfly.extension.undertow.deployment.UndertowDeploymentService$1.run(UndertowDeploymentService.java:78)
        ... 8 more
Caused by: java.lang.NullPointerException
        at com.vaadin.flow.router.internal.AbstractRouteRegistry.addErrorTarget(AbstractRouteRegistry.java:392)
        at com.vaadin.flow.server.startup.ApplicationRouteRegistry.lambda$setErrorNavigationTargets$3(ApplicationRouteRegistry.java:263)
        at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
        at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
        at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
        at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
        at java.util.HashMap$KeySpliterator.forEachRemaining(HashMap.java:1556)
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
        at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
        at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
        at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:485)
        at com.vaadin.flow.server.startup.ApplicationRouteRegistry.setErrorNavigationTargets(ApplicationRouteRegistry.java:263)
        at com.vaadin.flow.server.startup.ErrorNavigationTargetInitializer.onStartup(ErrorNavigationTargetInitializer.java:54)
        at io.undertow.servlet.core.DeploymentManagerImpl$1.call(DeploymentManagerImpl.java:204)
        at io.undertow.servlet.core.DeploymentManagerImpl$1.call(DeploymentManagerImpl.java:186)
        at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:42)
        at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
        at org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105)
        at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1504)
        at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1504)
        at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1504)
        at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1504)
        at io.undertow.servlet.core.DeploymentManagerImpl.deploy(DeploymentManagerImpl.java:252)
        ... 10 more

16:07:40,971 ERROR [org.jboss.as.controller.management-operation] (Controller Boot Thread) WFLYCTL0013: Operation ("deploy") failed - address: ([("deployment" => "ptsmc.ear")]) - failure description: {"WFLYCTL0080: Failed services" => {"jboss.deployment.subunit.\"ptsmc.ear\".\"ptsmc-web.war\".undertow-deployment" => "java.lang.RuntimeException: java.lang.NullPointerException
    Caused by: java.lang.RuntimeException: java.lang.NullPointerException
    Caused by: java.lang.NullPointerException"}}
16:07:41,043 INFO  [org.jboss.as.server] (ServerService Thread Pool -- 44) WFLYSRV0010: Deployed "ptsmc.ear" (runtime-name : "ptsmc.ear")
16:07:41,045 INFO  [org.jboss.as.controller] (Controller Boot Thread) WFLYCTL0183: Service status report
WFLYCTL0186:   Services which failed to start:      service jboss.deployment.subunit."ptsmc.ear"."ptsmc-web.war".undertow-deployment: java.lang.RuntimeException: java.lang.NullPointerException
WFLYCTL0448: 2 additional services are down due to their dependencies being missing or failed
16:07:41,093 INFO  [org.jboss.as.server] (Controller Boot Thread) WFLYSRV0212: Resuming server
16:07:41,096 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management
16:07:41,096 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990
16:07:41,101 ERROR [org.jboss.as] (Controller Boot Thread) WFLYSRV0026: WildFly Full 18.0.1.Final (WildFly Core 10.0.3.Final) started (with errors) in 8078ms - Started 477 of 708 services (5 services failed or missing dependencies, 377 services are lazy, passive or on-demand)
pleku commented 4 years ago

The NPE originates from https://github.com/vaadin/flow/blob/master/flow-server/src/main/java/com/vaadin/flow/internal/ReflectTools.java#L582 where it tries to find the generic type for https://github.com/vaadin/flow/blob/master/flow-server/src/main/java/com/vaadin/flow/router/RouteNotFoundError.java#L42 which returns null.

This because the call to https://github.com/objectify/objectify/blob/master/src/main/java/com/googlecode/objectify/repackaged/gentyref/GenericTypeReflector.java#L129 with parameters RouteNotFoundError and HasErrorParameter will not be able to get the type parameter, which should be NotFoundException.

Don't yet know why the Gentyref code is not able to get the type parameter in this case. Don't know if it is something to do with the library (gentyref) or if you need to configure the EAR in a different way, like adding some explicit classpath entries. Might be I didn't understand some part of the type resolving code, I would probably have to see how it works when this feature actually works.

A way to workaround this would be to hardcode the type parameter as these are internal error views from Flow, but not sure if that would help if you have your own error view there instead - can the type parameter be fetched then.

pleku commented 4 years ago

FYI - dropping this issue for now to resume other work as this has not been prioritized with warranty.

guttormvik commented 4 years ago

FYI - dropping this issue for now to resume other work as this has not been prioritized with warranty.

Yea. Sorry about that. I've prioritized it now.

guttormvik commented 4 years ago

It seems the problem isn't limited to "skinnywar".

An update on this: It seems this is a variant of skinnywar after all.

The issue with my example was that both ptsmc-ear and ptsmc-web depended on ptsmc-core, and that ptsmc-web depended with "provided". This meant ptsmc-core + the vaadin libs it depended on ended up in ear, and that gave the startup issues.

If I remove "provided" from ptsmc-web, and remove the dependency in ptsmc-ear, ptsmc-core ends up only inside the ptsmc-web war. The ear doesn't get any vaadin libraries and the app works.

Of course, as soon as I add one of my other war modules that also depend on ptsmc-core, that war will also get a copy of ptsmc-core inside; Same as for the duplicated vaadin libraries; but then we're back to the "skinnywar" issue.

denis-anisimov commented 4 years ago

The reason if the exception is the fact that the classes have different classloaders in the place where it happens : HasErrorParameter.class.getClassLoader() : ModuleClassLoader for Module "deployment.ptsmc.ear" from Service Module Loader target.getClassLoader() : ModuleClassLoader for Module "deployment.ptsmc.ear.ptsmc-web.war"

That's why ReflectTools.getGenericInterfaceType(target, ReflectTools .getGenericInterfaceType(target, HasErrorParameter.class).class) returns null. target class here is incompatible with HasErrorParameter.class : it extends HasErrorParameter but a different one.

This is only one place where error happens.

In fact everything is broken because of this. We rely on ApplicationRouteRegistry::getInstance method: it's crucial part of the whole framework. But it stores the registry instance inside ServletContext : https://github.com/vaadin/flow/blob/2.1/flow-server/src/main/java/com/vaadin/flow/server/startup/ApplicationRouteRegistry.java#L216 and then it reads it from the context: https://github.com/vaadin/flow/blob/2.1/flow-server/src/main/java/com/vaadin/flow/server/startup/ApplicationRouteRegistry.java#L221

This fails because of the same reason: the instance stored inside the context is loaded by a different classloader. So it's not an instance of ApplicationRouteRegistry even though it's actually is.

As a result everything is broken: it's not even possible to register a Vaadin servlet because the decision is made using ApplicationRouteRegistry instance.

Unfortunately I'm not familiar with "skinnywar". So I cannot say whether this is related to incorrect "skinnywar" packaging or this is a bug on WildFly.

But I don't see what we can do on our side here :

So as I said: I don't know what we can do here on our side. We are using Servlet 3.0 specification and write code without using any custom Classloaders. So it should just work. But it doesn't which means that either "skinnywar" is not properly assembled or WildFly has bug . And I don't see any workaround here.

If you may suggest any solution here on our side please let me know.

guttormvik commented 4 years ago

I can't really provide a solution, but I think it is better to start with what is acceptable.

To me the response is clear; Of course not.

Structurally, I understand the solution is to place all shared jars in ear\lib. The maven-ear-plugin helps you achieve this: https://maven.apache.org/plugins/maven-ear-plugin/examples/skinny-wars.html

That is as far as I had hoped I needed to specify it. The rest is Vaadin's problem :)

I do have some thoughts, but those are not based on any particular knowledge of Vaadin, Servlet 3.0 or such.

One thought is that isn't SomeClass.class.getClassLoader() bad? You can then get a classloader further down the chain, that can only see part of what you need it to see. Should you perhaps be using Thread.currentThread().getContextClassLoader() ?

Another thought is that when Vaadin's "Let's do everything automatically" fails, it isn't worth much. It is great when I can add a @Route in dev, and access it in the browser without doing anything more, but it isn't worth the issues I've had with my non-trivial application. When the magic fails, you need to understand how it works to perhaps get around it, but everything is hidden and opaque. I don't know how much vaadin sets up for me, which is part of the problem, but for instance, I would be happy to manually register my routes and call some config routines, or define servlet in web.xml

denis-anisimov commented 4 years ago

OK, you don't want to go into the details why it doesn't work and that's OK.

So I will just give an overview why it doesn't work.

Just a short explanation:

You may face with the same issue if you have some web application with a custom ServletContainerInitializer packaged in similar way. Your custom ServletContainerInitializer won't work. So it's not Vaadin issue.

One thought is that isn't SomeClass.class.getClassLoader() bad? You can then get a classloader further down the chain, that can only see part of what you need it to see. Should you perhaps be using Thread.currentThread().getContextClassLoader() ?

We don't use any classloader code : that's the point. It's just a normal Java code without any tricks with classloader. And it should just work. And it doesn't work because of classloaders : the situation is absolutely unexpected.

To be able to write the code which will work we will need in fact to deal with the classloaders magic and reflection . But this is simply wrong: it's either a bug in Wildfly or wrong packaging.

Another thought is that when Vaadin's "Let's do everything automatically" fails, it isn't worth much.

I totally agree with these points. I know that pain with magic : it works out of the box but when some specific situation arise you don't know how it works and you may not fix it anyhow . This is very annoying , I know this.

The problem is a bit different here:

And this is exactly broken: the resulting application is deployed somehow that it's not Servlet specification 3.0 compliant. As a result all our reliance on Servlet spec 3.0 is broken. And it's broken in many places: it's not only about routes. So the application is totally broken because of this . As I mentioned : the magic is avoidable but Servlet spec 3.0 is not avoidable. And that's the problem: Vaadin works only assuming Servlet spec 3.0. This is out of our hands.

On the other hands I feel your pain and we have to deal with this broken things. I can provide a hack on our side which will allow to deal with this situation and workaround that. I'm not happy with this hack but may be it's worth to have it since there is a real usacase.

guttormvik commented 4 years ago

OK, you don't want to go into the details why it doesn't work and that's OK.

Thanks :) Ideally I want to do what vaadin says on the front page:

Focus on creating apps that your users will love

Just some more observations from me:

The solution to the duplicate classes, is probably to ensure that there is only one version of each. Ie all vaadin libs are only in ear\lib, and then have "something" in each war to trigger vaadin?

I have tried to experiment a bit, and suddenly realised that Eclipse doesn't obey the 'skinnywar' setting. I get different results when I run inside eclipse with exploded ear, and when I deploy the one maven builds with mvn clean package -Pproduction

Inside eclipse, when I have skinnywar on, I end up with vaadin libs in both ear\lib and war\web-inf\lib and at startup it fails because different parts of the code reads in different copies of the class.

Building the ear with maven however, all vaadin libs end up in ear\lib and I get nothing on startup. Probably because the ServletContainerInitializer stuff has to be in the war to be triggered.

To try to get Eclipse's deploy to also do nothing (which is better than error,) I added scope 'provided' to vaadin in the war. Now Eclipse also has all vaadin libs in ear\lib, and nothing in war\web-inf\lib

But when I run inside eclipse, the frontend stuff fails.. First I see the npm install downloads stuff to wildfly\bin\node_modules, which was surprising. Then it fails to find my specific CssImports:

Failed to find the following css files in the node_modules or C:\Projects\vaadin\ptsmc-multimodule\wildfly-18.0.1.Final\bin\.\frontend directory tree:

  • ./styles/vaadin-app-layout.css
  • ./styles/vaadin-text-area.css
  • ./styles/vaadin-text-field.css
  • ./styles/ptsmc.css
  • ./styles/vaadin-grid.css
  • ./styles/vaadin-grid-tree-toggle.css
  • ./styles/vaadin-combo-box-overlay.css Unable to locate frontend resources and missing token file. Please run the prepare-frontend Vaadin plugin goal before deploying the application

( Those vaadin-xxxx.css are our overrides ) If I uncomment all my CssImports from my MainView I get this error instead:

Vaadin requires node.js & npm to be installed. Please install the latest LTS version of node.js (with npm) either by: ...

So I assume that when I launch in dev mode, it does something relative to where the jars are, in ear\lib ?

guttormvik commented 4 years ago

We don't use any classloader code : that's the point

But, everything; classes and resources, are loaded by a classloader. If you don't specify classloader, you get whatever the container decides, and if there is more than one you have no control.

One example:

Just took in 14.2.0.alpha9 and now DevModeInitializer fails on startup because of call to Class.forName from this pull: #7709

DevModeInitializer is in ear\lib, so I assume Class.forName uses the ear classloader. It then tries to look up the class of servlets in the war, and fails. DevModeInitializer.onStartup gets a ServletContext as parameter, which has getClassLoader(), which is a servlet 3.0 addition!, so I assume the right way to look up a war class would be via that.