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

4.x: @PreDestroy failing on MyFaces 4.x #729

Closed
melloware opened this issue Jan 4, 2023 · 29 comments
Closed

4.x: @PreDestroy failing on MyFaces 4.x #729

melloware opened this issue Jan 4, 2023 · 29 comments

Comments

@melloware
Copy link
Contributor

Using @PreDestroy failing on MyFaces 4.x with OmniFaces 4.0.1.

Code:

@PreDestroy
    public void preDestroy() {
        if (Faces.getContext() != null) { // It can be null during session invalidate!
            log.info("OmniFaces preDestroy");
            if (ViewScopeManager.isUnloadRequest(Faces.getContext())) {
                Faces.setSessionAttribute("unloadMessage",
                        Messages.createInfo("PreDestroy invoked during unload: {0}", this));
            } else {
                Messages.addInfo("cdiViewScopedForm", "PreDestroy invoked during postback: {0}", this);
            }
        }
    }

Error:

java.lang.IllegalStateException: Cannot invoke method 'destroyViewScopeMap' of class 'null' with arguments [org.apache.myfaces.context.servlet.FacesContextImpl@30236551, 596332002].
        at org.omnifaces.util.Reflection.invokeMethod(Reflection.java:861)
        at org.omnifaces.util.Hacks.removeViewState(Hacks.java:318)
        at org.omnifaces.viewhandler.OmniViewHandler.unloadView(OmniViewHandler.java:168)
        at org.omnifaces.viewhandler.OmniViewHandler.restoreView(OmniViewHandler.java:110)
        at org.apache.myfaces.lifecycle.RestoreViewExecutor.execute(RestoreViewExecutor.java:170)
        at org.apache.myfaces.lifecycle.LifecycleImpl.executePhase(LifecycleImpl.java:172)
        at org.apache.myfaces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:125)
        at jakarta.faces.webapp.FacesServlet.service(FacesServlet.java:223)
        at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
        at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
        at org.omnifaces.filter.CacheControlFilter.doFilter(CacheControlFilter.java:239)
        at org.omnifaces.filter.HttpFilter.doFilter(HttpFilter.java:108)
        at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
        at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
        at org.omnifaces.filter.GzipResponseFilter.doFilter(GzipResponseFilter.java:183)
        at org.omnifaces.filter.HttpFilter.doFilter(HttpFilter.java:108)

It looks like MyFaces 4.X has changed they way it handles ViewScope so this "hack" might not be needed anymore.

cc @tandraschko

@melloware
Copy link
Contributor Author

melloware commented Jan 5, 2023

Here is a runnable reproducer:
O729.zip

Just run mvn clean jetty:run and go to http://localhost:8080/primefaces-test/test.xhtml

Press the "Rebuild View" button.

Mojarra 4.0 using `mvn clean jetty:run -Pmojarra40"

    Rebuild view invoked: OmniCdiViewScopedBean(message=Hello from OmniFaces ViewScope!)
    PreDestroy invoked during postback: OmniCdiViewScopedBean(message=Hello from OmniFaces ViewScope!)
    PostConstruct invoked: OmniCdiViewScopedBean(message=Hello from OmniFaces ViewScope!) 

MyFaces4.0 using `mvn clean jetty:run -Pmyfaces40"

jakarta.servlet.ServletException: jakarta.el.ELException: java.lang.NullPointerException
    at jakarta.faces.webapp.FacesServlet.service (FacesServlet.java:255)
    at org.eclipse.jetty.servlet.ServletHolder$NotAsync.service (ServletHolder.java:1410)
    at org.eclipse.jetty.servlet.ServletHolder.handle (ServletHolder.java:764)
    at org.eclipse.jetty.servlet.ServletHandler$ChainEnd.doFilter (ServletHandler.java:1665)
    at org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter.doFilter (WebSocketUpgradeFilter.java:170)
    at org.eclipse.jetty.servlet.FilterHolder.doFilter (FilterHolder.java:202)
    at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter (ServletHandler.java:1635)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle (ServletHandler.java:527)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle (ScopedHandler.java:131)
    at org.eclipse.jetty.security.SecurityHandler.handle (SecurityHandler.java:578)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle (HandlerWrapper.java:122)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle (ScopedHandler.java:223)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle (SessionHandler.java:1570)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle (ScopedHandler.java:221)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle (ContextHandler.java:1380)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope (ScopedHandler.java:176)
    at org.eclipse.jetty.servlet.ServletHandler.doScope (ServletHandler.java:484)
    at org.eclipse.jetty.server.session.SessionHandler.doScope (SessionHandler.java:1543)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope (ScopedHandler.java:174)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope (ContextHandler.java:1302)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle (ScopedHandler.java:129)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle (ContextHandlerCollection.java:149)
    at org.eclipse.jetty.server.handler.HandlerList.handle (HandlerList.java:51)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle (HandlerWrapper.java:122)
    at org.eclipse.jetty.server.Server.handle (Server.java:563)
    at org.eclipse.jetty.server.HttpChannel.lambda$handle$0 (HttpChannel.java:505)
    at org.eclipse.jetty.server.HttpChannel.dispatch (HttpChannel.java:762)
    at org.eclipse.jetty.server.HttpChannel.handle (HttpChannel.java:497)
    at org.eclipse.jetty.server.HttpConnection.onFillable (HttpConnection.java:282)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded (AbstractConnection.java:314)
    at org.eclipse.jetty.io.FillInterest.fillable (FillInterest.java:100)
    at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run (SelectableChannelEndPoint.java:53)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask (AdaptiveExecutionStrategy.java:416)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask (AdaptiveExecutionStrategy.java:385)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce (AdaptiveExecutionStrategy.java:272)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.lambda$new$0 (AdaptiveExecutionStrategy.java:140)
    at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run (ReservedThreadExecutor.java:411)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob (QueuedThreadPool.java:934)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run (QueuedThreadPool.java:1078)
    at java.lang.Thread.run (Thread.java:829)

@melloware
Copy link
Contributor Author

Weird with just Jetty I can't reproduce the exact issue I am getting in Quarkus

@melloware
Copy link
Contributor Author

OK I reported the MyFaces issue and fixed it and now the behavior matches in Mojarra and MyFaces: apache/myfaces#471

Now I need to figure out the Quarkus Error

@melloware
Copy link
Contributor Author

Ahh my reproducer above once I have fixed the NPE in MyFaces now produces a similar exception to Quakrus.

jakarta.servlet.ServletException: Cannot invoke method 'destroyViewScopeMap' of class 'null' with arguments [org.primefaces.context.PrimeFacesContext@57e939f4, 525809575].
    at jakarta.faces.webapp.FacesServlet.service (FacesServlet.java:255)
    at org.eclipse.jetty.servlet.ServletHolder$NotAsync.service (ServletHolder.java:1410)
    at org.eclipse.jetty.servlet.ServletHolder.handle (ServletHolder.java:764)
    at org.eclipse.jetty.servlet.ServletHandler$ChainEnd.doFilter (ServletHandler.java:1665)
    at org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter.doFilter (WebSocketUpgradeFilter.java:170)
    at org.eclipse.jetty.servlet.FilterHolder.doFilter (FilterHolder.java:202)
    at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter (ServletHandler.java:1635)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle (ServletHandler.java:527)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle (ScopedHandler.java:131)
    at org.eclipse.jetty.security.SecurityHandler.handle (SecurityHandler.java:578)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle (HandlerWrapper.java:122)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle (ScopedHandler.java:223)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle (SessionHandler.java:1570)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle (ScopedHandler.java:221)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle (ContextHandler.java:1380)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope (ScopedHandler.java:176)
    at org.eclipse.jetty.servlet.ServletHandler.doScope (ServletHandler.java:484)
    at org.eclipse.jetty.server.session.SessionHandler.doScope (SessionHandler.java:1543)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope (ScopedHandler.java:174)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope (ContextHandler.java:1302)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle (ScopedHandler.java:129)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle (ContextHandlerCollection.java:149)
    at org.eclipse.jetty.server.handler.HandlerList.handle (HandlerList.java:51)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle (HandlerWrapper.java:122)
    at org.eclipse.jetty.server.Server.handle (Server.java:563)
    at org.eclipse.jetty.server.HttpChannel.lambda$handle$0 (HttpChannel.java:505)
    at org.eclipse.jetty.server.HttpChannel.dispatch (HttpChannel.java:762)
    at org.eclipse.jetty.server.HttpChannel.handle (HttpChannel.java:497)
    at org.eclipse.jetty.server.HttpConnection.onFillable (HttpConnection.java:282)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded (AbstractConnection.java:314)
    at org.eclipse.jetty.io.FillInterest.fillable (FillInterest.java:100)
    at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run (SelectableChannelEndPoint.java:53)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask (AdaptiveExecutionStrategy.java:416)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask (AdaptiveExecutionStrategy.java:385)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce (AdaptiveExecutionStrategy.java:272)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.produce (AdaptiveExecutionStrategy.java:194)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob (QueuedThreadPool.java:934)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run (QueuedThreadPool.java:1078)
    at java.lang.Thread.run (Thread.java:829)
Caused by: java.lang.IllegalStateException: Cannot invoke method 'destroyViewScopeMap' of class 'null' with arguments [org.primefaces.context.PrimeFacesContext@57e939f4, 525809575].
    at org.omnifaces.util.Reflection.invokeMethod (Reflection.java:861)
    at org.omnifaces.util.Hacks.removeViewState (Hacks.java:318)
    at org.omnifaces.viewhandler.OmniViewHandler.unloadView (OmniViewHandler.java:168)
    at org.omnifaces.viewhandler.OmniViewHandler.restoreView (OmniViewHandler.java:110)
    at jakarta.faces.application.ViewHandlerWrapper.restoreView (ViewHandlerWrapper.java:98)
    at org.apache.myfaces.lifecycle.RestoreViewExecutor.execute (RestoreViewExecutor.java:170)
    at org.apache.myfaces.lifecycle.LifecycleImpl.executePhase (LifecycleImpl.java:172)
    at org.apache.myfaces.lifecycle.LifecycleImpl.execute (LifecycleImpl.java:125)
    at jakarta.faces.webapp.FacesServlet.service (FacesServlet.java:223)
    at org.eclipse.jetty.servlet.ServletHolder$NotAsync.service (ServletHolder.java:1410)
    at org.eclipse.jetty.servlet.ServletHolder.handle (ServletHolder.java:764)
    at org.eclipse.jetty.servlet.ServletHandler$ChainEnd.doFilter (ServletHandler.java:1665)
    at org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter.doFilter (WebSocketUpgradeFilter.java:170)
    at org.eclipse.jetty.servlet.FilterHolder.doFilter (FilterHolder.java:202)
    at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter (ServletHandler.java:1635)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle (ServletHandler.java:527)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle (ScopedHandler.java:131)
    at org.eclipse.jetty.security.SecurityHandler.handle (SecurityHandler.java:578)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle (HandlerWrapper.java:122)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle (ScopedHandler.java:223)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle (SessionHandler.java:1570)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle (ScopedHandler.java:221)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle (ContextHandler.java:1380)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope (ScopedHandler.java:176)
    at org.eclipse.jetty.servlet.ServletHandler.doScope (ServletHandler.java:484)
    at org.eclipse.jetty.server.session.SessionHandler.doScope (SessionHandler.java:1543)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope (ScopedHandler.java:174)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope (ContextHandler.java:1302)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle (ScopedHandler.java:129)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle (ContextHandlerCollection.java:149)
    at org.eclipse.jetty.server.handler.HandlerList.handle (HandlerList.java:51)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle (HandlerWrapper.java:122)
    at org.eclipse.jetty.server.Server.handle (Server.java:563)
    at org.eclipse.jetty.server.HttpChannel.lambda$handle$0 (HttpChannel.java:505)
    at org.eclipse.jetty.server.HttpChannel.dispatch (HttpChannel.java:762)
    at org.eclipse.jetty.server.HttpChannel.handle (HttpChannel.java:497)
    at org.eclipse.jetty.server.HttpConnection.onFillable (HttpConnection.java:282)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded (AbstractConnection.java:314)
    at org.eclipse.jetty.io.FillInterest.fillable (FillInterest.java:100)
    at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run (SelectableChannelEndPoint.java:53)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask (AdaptiveExecutionStrategy.java:416)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask (AdaptiveExecutionStrategy.java:385)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce (AdaptiveExecutionStrategy.java:272)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.produce (AdaptiveExecutionStrategy.java:194)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob (QueuedThreadPool.java:934)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run (QueuedThreadPool.java:1078)
    at java.lang.Thread.run (Thread.java:829)
Caused by: java.lang.NullPointerException
    at org.omnifaces.util.Reflection.findMethod (Reflection.java:581)
    at org.omnifaces.util.Reflection.invokeMethod (Reflection.java:852)
    at org.omnifaces.util.Hacks.removeViewState (Hacks.java:318)
    at org.omnifaces.viewhandler.OmniViewHandler.unloadView (OmniViewHandler.java:168)
    at org.omnifaces.viewhandler.OmniViewHandler.restoreView (OmniViewHandler.java:110)
    at jakarta.faces.application.ViewHandlerWrapper.restoreView (ViewHandlerWrapper.java:98)
    at org.apache.myfaces.lifecycle.RestoreViewExecutor.execute (RestoreViewExecutor.java:170)
    at org.apache.myfaces.lifecycle.LifecycleImpl.executePhase (LifecycleImpl.java:172)
    at org.apache.myfaces.lifecycle.LifecycleImpl.execute (LifecycleImpl.java:125)
    at jakarta.faces.webapp.FacesServlet.service (FacesServlet.java:223)
    at org.eclipse.jetty.servlet.ServletHolder$NotAsync.service (ServletHolder.java:1410)
    at org.eclipse.jetty.servlet.ServletHolder.handle (ServletHolder.java:764)
    at org.eclipse.jetty.servlet.ServletHandler$ChainEnd.doFilter (ServletHandler.java:1665)
    at org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter.doFilter (WebSocketUpgradeFilter.java:170)
    at org.eclipse.jetty.servlet.FilterHolder.doFilter (FilterHolder.java:202)
    at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter (ServletHandler.java:1635)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle (ServletHandler.java:527)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle (ScopedHandler.java:131)
    at org.eclipse.jetty.security.SecurityHandler.handle (SecurityHandler.java:578)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle (HandlerWrapper.java:122)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle (ScopedHandler.java:223)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle (SessionHandler.java:1570)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle (ScopedHandler.java:221)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle (ContextHandler.java:1380)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope (ScopedHandler.java:176)
    at org.eclipse.jetty.servlet.ServletHandler.doScope (ServletHandler.java:484)
    at org.eclipse.jetty.server.session.SessionHandler.doScope (SessionHandler.java:1543)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope (ScopedHandler.java:174)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope (ContextHandler.java:1302)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle (ScopedHandler.java:129)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle (ContextHandlerCollection.java:149)
    at org.eclipse.jetty.server.handler.HandlerList.handle (HandlerList.java:51)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle (HandlerWrapper.java:122)
    at org.eclipse.jetty.server.Server.handle (Server.java:563)
    at org.eclipse.jetty.server.HttpChannel.lambda$handle$0 (HttpChannel.java:505)
    at org.eclipse.jetty.server.HttpChannel.dispatch (HttpChannel.java:762)
    at org.eclipse.jetty.server.HttpChannel.handle (HttpChannel.java:497)
    at org.eclipse.jetty.server.HttpConnection.onFillable (HttpConnection.java:282)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded (AbstractConnection.java:314)
    at org.eclipse.jetty.io.FillInterest.fillable (FillInterest.java:100)
    at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run (SelectableChannelEndPoint.java:53)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask (AdaptiveExecutionStrategy.java:416)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask (AdaptiveExecutionStrategy.java:385)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce (AdaptiveExecutionStrategy.java:272)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.produce (AdaptiveExecutionStrategy.java:194)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob (QueuedThreadPool.java:934)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run (QueuedThreadPool.java:1078)
    at java.lang.Thread.run (Thread.java:829)

@melloware
Copy link
Contributor Author

I have confirmed the old SPI for ViewScope is not there in MYfaces 4.0

org.apache.myfaces.FACES_SERVLET_FOUND
 org.apache.myfaces.FACES_SERVLET_SERVLETREGISTRATION
 org.apache.myfaces.INITIALIZED
 org.apache.myfaces.MAC_SECRET.CACHE
 org.apache.myfaces.SERIAL_FACTORY
 org.apache.myfaces.application.ApplicationImpl
 org.apache.myfaces.application.ViewIdSupport
 org.apache.myfaces.config.RuntimeConfig
 org.apache.myfaces.config.webparameters.MyfacesConfig
 org.apache.myfaces.core.api.shared.lang.PropertyDescriptorUtils.CACHE
 org.apache.myfaces.lifecycle.first.request.processed
 org.apache.myfaces.spi.AnnotationProvider.INSTANCE
 org.apache.myfaces.spi.AnnotationProvider.LIST
 org.apache.myfaces.spi.AnnotationProviderFactory
 org.apache.myfaces.spi.BEAN_ENTRY_STORAGE
 org.apache.myfaces.spi.FaceletConfigResourceProvider.LIST
 org.apache.myfaces.spi.FaceletConfigResourceProviderFactory
 org.apache.myfaces.spi.FacesConfigResourceProvider.LIST
 org.apache.myfaces.spi.FacesConfigResourceProviderFactory
 org.apache.myfaces.spi.FacesConfigurationMerger.INSTANCE
 org.apache.myfaces.spi.FacesConfigurationMergerFactory
 org.apache.myfaces.spi.FacesConfigurationProvider.INSTANCE
 org.apache.myfaces.spi.FacesConfigurationProvider.LIST
 org.apache.myfaces.spi.FacesConfigurationProviderFactory
 org.apache.myfaces.spi.FacesFlowProvider.INSTANCE
 org.apache.myfaces.spi.FacesFlowProviderFactory
 org.apache.myfaces.spi.InjectionProvider.INJECTION_PROVIDER_INSTANCE
 org.apache.myfaces.spi.InjectionProviderFactory
 org.apache.myfaces.spi.ResourceLibraryContractsProvider.LIST
 org.apache.myfaces.spi.ResourceLibraryContractsProviderFactory
 org.apache.myfaces.spi.ServiceProviderFinder
 org.apache.myfaces.spi.StateCacheProvider.INSTANCE
 org.apache.myfaces.spi.StateCacheProvider.LIST
 org.apache.myfaces.spi.StateCacheProviderFactory
 org.apache.myfaces.spi.WebConfigProvider.LIST
 org.apache.myfaces.spi.WebConfigProviderFactory
 org.apache.myfaces.util.WebXmlParser.errorpages

@melloware
Copy link
Contributor Author

Found the original issue: https://stackoverflow.com/a/40552152/502366

And Discussion I think this Hacks for MyFaces views can be removed safely in 4.0 but I am still unclear. It says this SPI was removed in 4.0 to "let CDI deal with views". But I am still not sure it solves the use case described in the ticket?

@BalusC
Copy link
Member

BalusC commented Jan 6, 2023

Hmm ok, I wanted to reactivate MyFaces 4.x in the IT of OmniFaces but it still returns a lot of errors for 4.0.0-RC3.

[ERROR] Failures: 
[ERROR]   ViewScopedIT.ajax:128 expected: <unload init> but was: <destroy init>
[ERROR]   ViewResourceHandlerIT.test:83 Page source ==> expected: <<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><url><loc>http://localhost:8080/ViewResourceHandlerIT/entity.xhtml?id=1</loc><lastmod>2020-12-22T19:20:10Z</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url><url><loc>http://localhost:8080/ViewResourceHandlerIT/entity.xhtml?id=2</loc><lastmod>2020-12-22</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url><url><loc>http://localhost:8080/ViewResourceHandlerIT/entity.xhtml?id=3</loc><lastmod>2020-12-22T15:20:10-04:00</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url><url><loc>http://localhost:8080/ViewResourceHandlerIT/entity.xhtml?id=4</loc><lastmod>2020-12-22T15:20:10-04:00</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url></urlset>> but was: <<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><url><loc>http://localhost:8080/ViewResourceHandlerIT/entity.xml?id=1</loc><lastmod>2020-12-22T19:20:10Z</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url><url><loc>http://localhost:8080/ViewResourceHandlerIT/entity.xml?id=2</loc><lastmod>2020-12-22</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url><url><loc>http://localhost:8080/ViewResourceHandlerIT/entity.xml?id=3</loc><lastmod>2020-12-22T15:20:10-04:00</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url><url><loc>http://localhost:8080/ViewResourceHandlerIT/entity.xml?id=4</loc><lastmod>2020-12-22T15:20:10-04:00</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url></urlset>>
[ERROR] Errors: 
[ERROR]   ViewScopedIT.nonAjax:88 » NoSuchElement Unable to locate element with ID: bean...
[ERROR]   ViewScopedViewStateIT.ajax:162 » RequestGuard Request type 'XHR' was expected,...
[ERROR]   HashParamIT.testHashParam:58 » RequestGuard Request type 'XHR' was expected, b...
[ERROR]   InputFileIT.uploadMultipleMaxsizeClient:236->OmniFacesIT.triggerOnchange:93->OmniFacesIT.waitUntilTextContent:106 » Timeout
[ERROR]   InputFileIT.uploadSingleMaxsizeClient:182->OmniFacesIT.triggerOnchange:93->OmniFacesIT.waitUntilTextContent:106 » Timeout
[ERROR]   InputHiddenIT.testInputHidden:44 » RequestGuard Request type 'XHR' was expecte...
[ERROR]   FullAjaxExceptionHandlerIT.throwDuringInvokeApplication:95 » NoSuchElement Una...
[ERROR]   FullAjaxExceptionHandlerIT.throwDuringRenderResponse:111 » NoSuchElement Unabl...
[ERROR]   FullAjaxExceptionHandlerIT.throwDuringSecondUpdateOfRenderResponse:120 » NoSuchElement
[ERROR]   FullAjaxExceptionHandlerIT.throwDuringTreeVisitingOnRenderResponse:128 » NoSuchElement
[ERROR]   FullAjaxExceptionHandlerIT.throwDuringUpdateModelValues:103 » NoSuchElement Un...
[ERROR]   ViewExpiredExceptionHandlerIT.test:54 » RequestGuard Request type 'XHR' was ex...
[ERROR]   SocketIT.test:76->pushSessionScopedUserTargeted:115 » RequestGuard Request typ...
[ERROR]   CombinedResourceHandlerIT.ajax:104 » RequestGuard Request type 'XHR' was expec...
[ERROR]   CombinedResourceHandlerIT.mixed:116->verifyElements:130 » NoSuchElement Unable...
[ERROR]   CombinedResourceHandlerIT.nonAjax:96->verifyElements:130 » NoSuchElement Unabl...
[ERROR]   PWAResourceHandlerIT.verifyViewScopedBeanAfterAjaxSubmit:91 » RequestGuard Req...
[ERROR]   SkipValidatorsIT.test:54 » RequestGuard Request type 'XHR' was expected, but t...
[ERROR]   ValidateBeanIT.validateByCommand:392 » RequestGuard Request type 'XHR' was exp...
[ERROR]   ValidateBeanIT.validateByInput:435->OmniFacesIT.triggerOnchange:93->OmniFacesIT.waitUntilTextContent:106 » Timeout
[ERROR]   ValidateBeanIT.validateClassLevelActual:500 » RequestGuard Request type 'XHR' ...
[ERROR]   ValidateBeanIT.validateClassLevelActualWithMessageForViolating:595 » RequestGuard
[ERROR]   ValidateBeanIT.validateClassLevelByCopier:512 » RequestGuard Request type 'XHR...
[ERROR]   ValidateBeanIT.validateClassLevelDefault:488 » RequestGuard Request type 'XHR'...
[ERROR]   ValidateBeanIT.validateClassLevelWithFormEntityComposite:867 » RequestGuard Re...
[ERROR]   ValidateBeanIT.validateClassLevelWithInputEntityComposite:849 » RequestGuard R...
[ERROR]   ValidateBeanIT.validateClassLevelWithMessageForAll:549 » RequestGuard Request ...
[ERROR]   ValidateBeanIT.validateClassLevelWithMessageForForm:536 » RequestGuard Request...
[ERROR]   ValidateBeanIT.validateClassLevelWithMessageForGlobal:562 » RequestGuard Reque...
[ERROR]   ValidateBeanIT.validateClassLevelWithMessageForViolating:577 » RequestGuard Re...
[ERROR]   ValidateBeanIT.validateClassLevelWithMessageFormat:524 » RequestGuard Request ...
[ERROR]   ValidateBeanIT.validateDefaultWithMessageForViolating:476 » RequestGuard Reque...
[ERROR]   ValidateBeanIT.validateDoubleNestedListClassLevelActualWithMessageForViolating:804 » RequestGuard
[ERROR]   ValidateBeanIT.validateDoubleNestedListClassLevelActualWithMessagesForViolating:829 » RequestGuard
[ERROR]   ValidateBeanIT.validateDoubleNestedListClassLevelWithMessageForViolating:754 » RequestGuard
[ERROR]   ValidateBeanIT.validateDoubleNestedListClassLevelWithMessagesForViolating:779 » RequestGuard
[ERROR]   ValidateBeanIT.validateNestedClassLevelActualWithMessageForViolating:631 » RequestGuard
[ERROR]   ValidateBeanIT.validateNestedClassLevelWithMessageForViolating:613 » RequestGuard
[ERROR]   ValidateBeanIT.validateNestedListClassLevelActualWithMessageForViolating:704 » RequestGuard
[ERROR]   ValidateBeanIT.validateNestedListClassLevelActualWithMessagesForViolating:729 » RequestGuard
[ERROR]   ValidateBeanIT.validateNestedListClassLevelWithMessageForViolating:654 » RequestGuard
[ERROR]   ValidateBeanIT.validateNestedListClassLevelWithMessagesForViolating:679 » RequestGuard
[ERROR]   ValidateAllIT.testForm1:100 » RequestGuard Request type 'XHR' was expected, bu...
[ERROR]   ValidateAllIT.testForm2:138 » RequestGuard Request type 'XHR' was expected, bu...
[ERROR] Tests run: 136, Failures: 3, Errors: 44, Skipped: 0

This already happened with RC1 and because there were so many errors and it was a beta version I didn't bother to look closer. Only thing which caught my eye that it uses .xml instead of .xhtml as default suffix: a64c049#diff-9c5fb3d1b7e3b0f54bc5c4182965c4fe1f9023d449017cece3005d3f90e8e4d8R84 This seems to be still the case according to ViewResourceHandlerIT.

@melloware
Copy link
Contributor Author

Let me look that would be a bad bug!

@melloware
Copy link
Contributor Author

Everywhere in the MyFaces 4.0 codebase I see it using .xhtml

    public static final String DEFAULT_FACELETS_SUFFIX = ".xhtml";
    @Deprecated(since = "4.0")
    public static final String DEFAULT_SUFFIX = ".xhtml";

@melloware
Copy link
Contributor Author

melloware commented Jan 6, 2023

Is it possible that somehow EXTENSIONLESS_MAPPING is enabled because locally my PrimeFaces test project with Myfaces 4.0 is respecting .xhtml by default?

@melloware
Copy link
Contributor Author

And in ProjectStage == DEVELOPMENT I see it print out this..

Jan 06, 2023 2:48:27 PM org.apache.myfaces.webapp.WebConfigParamsLogger logWebContextParams
INFO: No context init parameter 'jakarta.faces.DEFAULT_SUFFIX' found, using default value '.xhtml'.
Jan 06, 2023 2:48:27 PM org.apache.myfaces.webapp.WebConfigParamsLogger logWebContextParams
INFO: No context init parameter 'jakarta.faces.FACELETS_SUFFIX' found, using default value '.xhtml'.

@BalusC BalusC closed this as completed in ece072b Jan 14, 2023
@BalusC
Copy link
Member

BalusC commented Jan 14, 2023

Locally tested and confirmed fixed with ece072b

As to the wrong extension it appears to be specific to the ViewResourceHandlerIT itself. It looked ok in others. But still that strange RequestGuard Request type 'XHR' was expected, but 'NONE' was found error when a 2nd ajax submit is performed against same view.

@melloware
Copy link
Contributor Author

Interesting. Thanks for fixing the original issue! It might be a bug with the MyFaces AJAX handling?

@BalusC
Copy link
Member

BalusC commented Jan 21, 2023

Coming back to failing tests, majority of the ones failing with NoSuchElement (except for those in FullAjaxExceptionHandlerIT) appear to be caused by the same underlying exception as fixed in apache/myfaces#471. So I'll for these have to wait until MyFaces 4.0.0(.RC4) is available in Maven.

@melloware
Copy link
Contributor Author

Thank you! I will push for an RC4 release this week because I would also like to continue testing Quarkus EE10 issues before final release.

@BalusC
Copy link
Member

BalusC commented Jan 22, 2023

Still no clue about these RequestGuard errors every time when any form is ajax-submitted for the second time during the same view. Upgrading Selenium to 1.7.0 didn't help. Upgrading Graphene to 3.0.0 didn't help. Changing browser from htmlunit to phantomjs/firefox/chrome didn't help.

Curious how MyFaces own unit tests deal with this use case.

@melloware
Copy link
Contributor Author

melloware commented Jan 22, 2023

The Myfaces Integration tests just upgraded to Arquillian drone to 3.0.0-alpha.7 apache/myfaces#485

I know @bohmber did a lot to get them back working again. Our PrimeFaces Jakarta Integration Test suite is also passing with MyFaces 4.0 https://github.com/primefaces/primefaces/tree/master/primefaces-integration-tests-jakarta but it just uses straight Selenium 4.X

@melloware
Copy link
Contributor Author

@BalusC Myfaces 4.0.0-RC4 is in Maven Central if you want to update and try your integration tests again?

@BalusC
Copy link
Member

BalusC commented Jan 27, 2023

Yup that fixed all tests failing with NoSuchElement except for those in FullAjaxExceptionHandlerIT. All other tests still fail the same way.

@melloware
Copy link
Contributor Author

OK is there anything i can look into for that FullAjaxExceptionHandler I would like to get it fixed for RC5 ?

@BalusC
Copy link
Member

BalusC commented Jan 27, 2023

Not sure. It's probably HtmlUnit related. This is the underlying error during that test and then the test fails because it couldn't find element by id exception.

Jan 27, 2023 9:14:13 AM com.gargoylesoftware.htmlunit.javascript.background.JavaScriptJobManagerImpl runSingleJob
SEVERE: Job run failed with unexpected RuntimeException: Exception invoking setInnerHTML
======= EXCEPTION START ========
Exception class=[java.lang.RuntimeException]
com.gargoylesoftware.htmlunit.ScriptException: Exception invoking setInnerHTML
	at com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine$HtmlUnitContextAction.run(JavaScriptEngine.java:883)
	at net.sourceforge.htmlunit.corejs.javascript.Context.call(Context.java:628)
	at net.sourceforge.htmlunit.corejs.javascript.ContextFactory.call(ContextFactory.java:513)
	at com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine.callFunction(JavaScriptEngine.java:815)
	at com.gargoylesoftware.htmlunit.javascript.host.xml.XMLHttpRequest.setState(XMLHttpRequest.java:233)
	at com.gargoylesoftware.htmlunit.javascript.host.xml.XMLHttpRequest.doSend(XMLHttpRequest.java:786)
	at com.gargoylesoftware.htmlunit.javascript.host.xml.XMLHttpRequest.access$000(XMLHttpRequest.java:97)
	at com.gargoylesoftware.htmlunit.javascript.host.xml.XMLHttpRequest$1.run(XMLHttpRequest.java:641)
	at net.sourceforge.htmlunit.corejs.javascript.Context.call(Context.java:628)
	at net.sourceforge.htmlunit.corejs.javascript.ContextFactory.call(ContextFactory.java:513)
	at com.gargoylesoftware.htmlunit.javascript.background.JavascriptXMLHttpRequestJob.run(JavascriptXMLHttpRequestJob.java:36)
	at com.gargoylesoftware.htmlunit.javascript.background.JavaScriptJobManagerImpl.runSingleJob(JavaScriptJobManagerImpl.java:427)
	at com.gargoylesoftware.htmlunit.javascript.background.DefaultJavaScriptExecutor.run(DefaultJavaScriptExecutor.java:156)
	at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.lang.RuntimeException: Exception invoking setInnerHTML
	at net.sourceforge.htmlunit.corejs.javascript.MemberBox.invoke(MemberBox.java:181)
	at net.sourceforge.htmlunit.corejs.javascript.ScriptableObject$GetterSlot.setValue(ScriptableObject.java:311)
	at net.sourceforge.htmlunit.corejs.javascript.ScriptableObject$RelinkedSlot.setValue(ScriptableObject.java:384)
	at net.sourceforge.htmlunit.corejs.javascript.ScriptableObject.putImpl(ScriptableObject.java:2887)
	at net.sourceforge.htmlunit.corejs.javascript.ScriptableObject.put(ScriptableObject.java:547)
	at net.sourceforge.htmlunit.corejs.javascript.ScriptableObject.putProperty(ScriptableObject.java:2570)
	at net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime.setObjectProp(ScriptRuntime.java:1674)
	at net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime.setObjectProp(ScriptRuntime.java:1669)
	at net.sourceforge.htmlunit.corejs.javascript.Interpreter.interpretLoop(Interpreter.java:1255)
	at net.sourceforge.htmlunit.corejs.javascript.Interpreter.interpret(Interpreter.java:798)
	at net.sourceforge.htmlunit.corejs.javascript.InterpretedFunction.call(InterpretedFunction.java:105)
	at net.sourceforge.htmlunit.corejs.javascript.ContextFactory.doTopCall(ContextFactory.java:411)
	at com.gargoylesoftware.htmlunit.javascript.HtmlUnitContextFactory.doTopCall(HtmlUnitContextFactory.java:252)
	at net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime.doTopCall(ScriptRuntime.java:3286)
	at com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine$4.doRun(JavaScriptEngine.java:808)
	at com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine$HtmlUnitContextAction.run(JavaScriptEngine.java:868)
	... 13 more
Caused by: java.lang.NullPointerException
	at com.gargoylesoftware.htmlunit.html.HtmlScript$2.execute(HtmlScript.java:232)
	at com.gargoylesoftware.htmlunit.html.HtmlScript.onAllChildrenAddedToPage(HtmlScript.java:257)
	at com.gargoylesoftware.htmlunit.html.HTMLParser$HtmlUnitDOMBuilder.endElement(HTMLParser.java:747)
	at org.apache.xerces.parsers.AbstractSAXParser.endElement(Unknown Source)
	at com.gargoylesoftware.htmlunit.html.HTMLParser$HtmlUnitDOMBuilder.endElement(HTMLParser.java:704)
	at org.cyberneko.html.HTMLTagBalancer.callEndElement(HTMLTagBalancer.java:1170)
	at org.cyberneko.html.HTMLTagBalancer.endElement(HTMLTagBalancer.java:1072)
	at org.cyberneko.html.filters.DefaultFilter.endElement(DefaultFilter.java:206)
	at org.cyberneko.html.filters.NamespaceBinder.endElement(NamespaceBinder.java:330)
	at org.cyberneko.html.HTMLScanner$ContentScanner.scanEndElement(HTMLScanner.java:3126)
	at org.cyberneko.html.HTMLScanner$ContentScanner.scan(HTMLScanner.java:2093)
	at org.cyberneko.html.HTMLScanner.scanDocument(HTMLScanner.java:920)
	at org.cyberneko.html.HTMLConfiguration.parse(HTMLConfiguration.java:499)
	at org.cyberneko.html.HTMLConfiguration.parse(HTMLConfiguration.java:452)
	at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)
	at com.gargoylesoftware.htmlunit.html.HTMLParser$HtmlUnitDOMBuilder.parse(HTMLParser.java:924)
	at com.gargoylesoftware.htmlunit.html.HTMLParser.parseFragment(HTMLParser.java:166)
	at com.gargoylesoftware.htmlunit.html.HTMLParser.parseFragment(HTMLParser.java:125)
	at com.gargoylesoftware.htmlunit.javascript.host.html.HTMLElement.parseHtmlSnippet(HTMLElement.java:1016)
	at com.gargoylesoftware.htmlunit.javascript.host.html.HTMLElement.setInnerHTML(HTMLElement.java:927)
	at jdk.internal.reflect.GeneratedMethodAccessor11.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at net.sourceforge.htmlunit.corejs.javascript.MemberBox.invoke(MemberBox.java:153)
	... 28 more

This IT passes when PhantomJS is used but that in turn fails in a bunch of other tests.

@melloware
Copy link
Contributor Author

got it is there a way I can run this test myself?

@BalusC
Copy link
Member

BalusC commented Jan 27, 2023

mvn clean verify -Ptomcat-myfaces4 -Dit.test=FullAjaxExceptionHandlerIT#throwDuringInvokeApplication

@melloware
Copy link
Contributor Author

I tried but it fails with a fatal exception?

[INFO] Running org.omnifaces.test.exceptionhandler.fullajaxexceptionhandler.FullAjaxExceptionHandlerIT
Jan 27, 2023 11:24:11 AM org.jboss.arquillian.drone.webdriver.factory.remote.reusable.ReusedSessionPermanentFileStorage readStore
INFO: Reused session store is not available at C:\Users\elefkof\.drone-webdriver-session-store, a new one will be created.
Jan 27, 2023 11:24:11 AM org.jboss.arquillian.container.tomcat.managed.TomcatManagedContainer start
INFO: Starting Tomcat with: [C:\Tools\jdk-17\bin\java, -Djava.util.logging.config.file=C:\dev\melloware-git\omnifaces\target\apache-tomcat-10.1.5\conf\logging.properties, -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager, -Dcom.sun.management.jmxremote.port=8089, -Dcom.sun.management.jmxremote.ssl=false, -Dcom.sun.management.jmxremote.authenticate=false, -Xmx512m, -XX:MaxPermSize=128m, -classpath, C:\dev\melloware-git\omnifaces\target\apache-tomcat-10.1.5\bin\bootstrap.jar;C:\dev\melloware-git\omnifaces\target\apache-tomcat-10.1.5\bin\tomcat-juli.jar, -Dcatalina.base=C:\dev\melloware-git\omnifaces\target\apache-tomcat-10.1.5, -Dcatalina.home=C:\dev\melloware-git\omnifaces\target\apache-tomcat-10.1.5, -Djava.io.tmpdir=C:\dev\melloware-git\omnifaces\target\apache-tomcat-10.1.5\temp, org.apache.catalina.startup.Bootstrap, -config, C:\dev\melloware-git\omnifaces\target\apache-tomcat-10.1.5\conf\server.xml, start]
Unrecognized VM option 'MaxPermSize=128m'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

Maven and JDK info:

Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)
Maven home: C:\Tools\apache-maven-3.8.6
Java version: 17.0.5, vendor: Eclipse Adoptium, runtime: C:\Tools\jdk-17
Default locale: en_US, platform encoding: Cp1252
OS name: "windows 11", version: "10.0", arch: "amd64", family: "windows"

@BalusC
Copy link
Member

BalusC commented Jan 27, 2023

Arquillian Tomcat Plugin does in its current version not support Java 17.

I'm using Java 11.

@melloware
Copy link
Contributor Author

Ahhh got it thanks.

@melloware
Copy link
Contributor Author

@werpu this sounds familiar like something you ran into with MyFaces TCK tests as well which is why you switched them to Selenium tests?

@werpu
Copy link

werpu commented Jan 30, 2023

Hi yes very familiar, we switched some of the TCK tests (the ajax ones) to a Selenium/chromium based implementation, because of HTMLUnit.
HTMLUnit uses internally the Mozilla Rhino engine, which atm is stuck at Ecmascript 5 level with a handful of higher level language and dom elements thrown in.
So if you threw a newer script against it which uses newer Ecmascript or Dom constructs, you will get this error.
We simply could not verify our new version of the scripts (which will replace the old ones) anymore with HTMLUnit!
Unfortunately the only two fixes are
a) Keep everything at ES5 and Dom3 Level until Rhino is updated
b) Move over to another framework and ditch HTMLUnit alltogether

@werpu
Copy link

werpu commented Jan 30, 2023

Btw forgot: You can use https://github.com/werpu/tckworkbench as base... the utils folder is basically my framework on top of selenium and chrome... the utils folder is ASL2 but not the rest, so feel free to rip that part out and uses it wherever you like! (bugfixes are always welcome)

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

3 participants