ocpsoft / rewrite

OCPsoft URL-Rewriting Framework
http://ocpsoft.org/rewrite/
Apache License 2.0
189 stars 86 forks source link

UnsupportedOperationException for request scoped bean with parameter #362

Open TobiasBerndt opened 1 year ago

TobiasBerndt commented 1 year ago

Versions:

On initializing RewriteFilter for request scoped bean 'MyController' (see below) during spring boot application start-up the following exception occurs.

Exception starting filter [OCPsoft Rewrite Filter]

java.lang.UnsupportedOperationException: null
    at java.base/java.util.AbstractMap.put(AbstractMap.java:209)
    at org.springframework.web.context.request.FacesRequestAttributes.setAttribute(FacesRequestAttributes.java:112)
    at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:46)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:371)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:673)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:661)
    at org.springframework.context.support.AbstractApplicationContext.getBeansOfType(AbstractApplicationContext.java:1300)
    at org.joinfaces.autoconfigure.rewrite.SpringBootBeanNameResolver.resolveBeanNames(SpringBootBeanNameResolver.java:80)
    at org.joinfaces.autoconfigure.rewrite.SpringBootBeanNameResolver.getBeanName(SpringBootBeanNameResolver.java:49)
    at org.ocpsoft.rewrite.el.TypeBasedExpression.lookupBeanName(TypeBasedExpression.java:72)
    at org.ocpsoft.rewrite.el.TypeBasedExpression.getExpression(TypeBasedExpression.java:55)
    at org.ocpsoft.rewrite.el.TypeBasedExpression.toString(TypeBasedExpression.java:108)
    at java.base/java.lang.String.valueOf(String.java:2951)
    at java.base/java.lang.StringBuilder.append(StringBuilder.java:168)
    at org.ocpsoft.rewrite.el.El$ElProperty.toString(El.java:335)
    at org.ocpsoft.rewrite.param.ParameterBuilder.addOrReplaceBinding(ParameterBuilder.java:83)
    at org.ocpsoft.rewrite.param.ParameterBuilder.bindsTo(ParameterBuilder.java:69)
    at org.ocpsoft.rewrite.param.ParameterBuilder.bindsTo(ParameterBuilder.java:29)
    at org.ocpsoft.rewrite.config.ConfigurationLoader$1.call(ConfigurationLoader.java:196)
    at org.ocpsoft.rewrite.config.ParameterizedConditionVisitor.visit(ParameterizedConditionVisitor.java:40)
    at org.ocpsoft.rewrite.config.ParameterizedConditionVisitor.visit(ParameterizedConditionVisitor.java:27)
    at org.ocpsoft.rewrite.config.ConditionVisit.visit(ConditionVisit.java:53)
    at org.ocpsoft.rewrite.config.ConditionVisit.accept(ConditionVisit.java:44)
    at org.ocpsoft.rewrite.config.ConfigurationLoader.build(ConfigurationLoader.java:204)
    at org.ocpsoft.rewrite.config.ConfigurationLoader.buildCached(ConfigurationLoader.java:118)
    at org.ocpsoft.rewrite.config.ConfigurationLoader.loadConfiguration(ConfigurationLoader.java:81)
    at org.ocpsoft.rewrite.servlet.impl.DefaultHttpRewriteProvider.init(DefaultHttpRewriteProvider.java:81)
    at org.ocpsoft.rewrite.servlet.impl.DefaultHttpRewriteProvider.init(DefaultHttpRewriteProvider.java:55)
    at org.ocpsoft.rewrite.servlet.RewriteFilter.init(RewriteFilter.java:142)
    at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:272)
    at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:106)
    at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4609)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5248)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1393)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1383)
    at java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java)
    at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
    at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:140)
    at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:916)
    at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:835)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1393)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1383)
    at java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java)
    at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
    at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:140)
    at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:916)
    at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:265)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.core.StandardService.startInternal(StandardService.java:430)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:930)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.startup.Tomcat.start(Tomcat.java:486)
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:123)
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:104)
    at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:479)
    at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:211)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:184)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:162)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:577)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:307)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292)
@Join(path = "/mypath", to = "/pages/myPage.jsf")
public class MyController {
   @Parameter("someParam")
   @Setter
   @Getter
   private String someParam;
}
@Bean
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public MyController myController() {
   return new MyController();
}
lincolnthree commented 1 year ago

@TobiasBerndt Apologies for the delay. The holidays were difficult.

If you are still struggling with this, could you please provide a sample project with minimal dependencies, so I can try to reproduce the issue?

TobiasBerndt commented 1 year ago

@lincolnthree I have created this sample project https://github.com/TobiasBerndt/rewrite-example First I have to mention that I had another issue in the past in rewrite version 3.4.3.Final. So I forked rewrite and modified the SpringBeanNameResolver (rewrite-integration-spring) class to not throw below exception. This exception occurred also on startup and is related @Parameter. With the forked version I now get the exception of my first post. Did I use @Parameter in a wrong way?

java.lang.IllegalStateException: Unable to get current WebApplicationContext
    at org.ocpsoft.rewrite.spring.SpringBeanNameResolver.getBeanName(SpringBeanNameResolver.java:45) ~[rewrite-integration-spring-3.5.1.Final.jar:3.5.1.Final]
    at org.ocpsoft.rewrite.el.TypeBasedExpression.lookupBeanName(TypeBasedExpression.java:72) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at org.ocpsoft.rewrite.el.TypeBasedExpression.getExpression(TypeBasedExpression.java:55) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at org.ocpsoft.rewrite.el.TypeBasedExpression.toString(TypeBasedExpression.java:108) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at java.base/java.lang.String.valueOf(String.java:2951) ~[na:na]
    at java.base/java.lang.StringBuilder.append(StringBuilder.java:168) ~[na:na]
    at org.ocpsoft.rewrite.el.El$ElProperty.toString(El.java:335) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at org.ocpsoft.rewrite.param.ParameterBuilder.addOrReplaceBinding(ParameterBuilder.java:83) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at org.ocpsoft.rewrite.param.ParameterBuilder.bindsTo(ParameterBuilder.java:69) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at org.ocpsoft.rewrite.param.ParameterBuilder.bindsTo(ParameterBuilder.java:29) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at org.ocpsoft.rewrite.config.ConfigurationLoader$1.call(ConfigurationLoader.java:196) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at org.ocpsoft.rewrite.config.ParameterizedConditionVisitor.visit(ParameterizedConditionVisitor.java:40) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at org.ocpsoft.rewrite.config.ParameterizedConditionVisitor.visit(ParameterizedConditionVisitor.java:27) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at org.ocpsoft.rewrite.config.ConditionVisit.visit(ConditionVisit.java:53) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at org.ocpsoft.rewrite.config.ConditionVisit.accept(ConditionVisit.java:44) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at org.ocpsoft.rewrite.config.ConfigurationLoader.build(ConfigurationLoader.java:204) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at org.ocpsoft.rewrite.config.ConfigurationLoader.buildCached(ConfigurationLoader.java:118) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at org.ocpsoft.rewrite.config.ConfigurationLoader.loadConfiguration(ConfigurationLoader.java:81) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at org.ocpsoft.rewrite.servlet.impl.DefaultHttpRewriteProvider.init(DefaultHttpRewriteProvider.java:81) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at org.ocpsoft.rewrite.servlet.impl.DefaultHttpRewriteProvider.init(DefaultHttpRewriteProvider.java:55) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at org.ocpsoft.rewrite.servlet.RewriteFilter.init(RewriteFilter.java:142) ~[rewrite-servlet-3.5.1.Final.jar:3.5.1.Final]
    at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:272) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:106) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4609) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5248) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1393) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1383) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264) ~[na:na]
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java) ~[na:na]
    at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:140) ~[na:na]
    at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:916) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:835) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1393) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1383) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264) ~[na:na]
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java) ~[na:na]
    at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:140) ~[na:na]
    at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:916) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:265) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.apache.catalina.core.StandardService.startInternal(StandardService.java:430) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:930) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.apache.catalina.startup.Tomcat.start(Tomcat.java:486) ~[tomcat-embed-core-9.0.69.jar:9.0.69]
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:123) ~[spring-boot-2.7.6.jar:2.7.6]
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:104) ~[spring-boot-2.7.6.jar:2.7.6]
    at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:479) ~[spring-boot-2.7.6.jar:2.7.6]
    at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:211) ~[spring-boot-2.7.6.jar:2.7.6]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:184) ~[spring-boot-2.7.6.jar:2.7.6]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:162) ~[spring-boot-2.7.6.jar:2.7.6]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:577) ~[spring-context-5.3.24.jar:5.3.24]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.6.jar:2.7.6]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731) ~[spring-boot-2.7.6.jar:2.7.6]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[spring-boot-2.7.6.jar:2.7.6]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) ~[spring-boot-2.7.6.jar:2.7.6]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) ~[spring-boot-2.7.6.jar:2.7.6]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) ~[spring-boot-2.7.6.jar:2.7.6]
    at com.example.rewrite.RewriteApplication.main(RewriteApplication.java:10) ~[classes/:na]
lincolnthree commented 1 year ago

Ok, that makes some sense. If the WebApplicationContext is yet not available in a request (for instance, if Spring hasn't started yet), it's possible that the ServletFilter ordering needs to be adjusted so that Spring initializes first, then Rewrite does.

You could try re-ordering the servlet filters in your application - let me know if that resolves the exception?

I've got this issue on my tasks list for next day or so, today if I can finish up the Monday tasks. I'll take a closer look soon. Sorry for the delay.

TobiasBerndt commented 1 year ago

I saw in org.joinfaces.autoconfigure.rewrite.RewriteFilterProperties and org.joinfaces.autoconfigure.rewrite.RewriteAutoConfiguration that RewriteFilter has lowest precedence by default. Is there anything I should configure differently? The issue only occurs with @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS). For e.g. singleton bean there is no issue.

lincolnthree commented 1 year ago

Hi Tobias, apologies for the delay. I've got some crazy stuff happening here right now. I still need to dig more, but...

I'm looking at your first (original) exception where @Parameter is throwing java.lang.UnsupportedOperationException: null

On first glance I do see one issue, but I'm not sure if it would cause this problem. First, you have @Parameter("someParam"), but your @Join path does not accept this parameter. See the docs here: https://github.com/ocpsoft/rewrite/blob/6.0.0.Alpha1/config-annotations/src/main/java/org/ocpsoft/rewrite/annotation/Parameter.java

You should have something like this instead:

@Join(path = "/mypath/{someParam}", to = "/index.jsf")

Otherwise you are specifying a parameter which is unbound. I'm honestly not sure we even have a test for this scenario so it's definitely something I'm going to take a closer look at.

lincolnthree commented 1 year ago

As for the startup error. I'm worried that this might be some circular initialization loop where SpringBoot is trying to start the WebApplicationContext, then Rewrite tries to start up and request bean names to create the configuration, then SpringBoot says, "Hey, I'm not ready yet and don't have a WebApplicationContext", and returns null back to Rewrite.

Digging deeper.

TobiasBerndt commented 1 year ago

First of all thank you for taking care about this issue.

Hi Tobias, apologies for the delay. I've got some crazy stuff happening here right now. I still need to dig more, but...

I'm looking at your first (original) exception where @Parameter is throwing java.lang.UnsupportedOperationException: null

On first glance I do see one issue, but I'm not sure if it would cause this problem. First, you have @Parameter("someParam"), but your @Join path does not accept this parameter. See the docs here: https://github.com/ocpsoft/rewrite/blob/6.0.0.Alpha1/config-annotations/src/main/java/org/ocpsoft/rewrite/annotation/Parameter.java

You should have something like this instead:

@Join(path = "/mypath/{someParam}", to = "/index.jsf")

Otherwise you are specifying a parameter which is unbound. I'm honestly not sure we even have a test for this scenario so it's definitely something I'm going to take a closer look at.

I use the parameter as a GET parameter e.g. http://localhost:8080/mypath?someParam=something and I thought the only difference to having it not in the path is that it's not mandatory to set. At least in the example here the parameter is also not part of join path.

As for the startup error. I'm worried that this might be some circular initialization loop where SpringBoot is trying to start the WebApplicationContext, then Rewrite tries to start up and request bean names to create the configuration, then SpringBoot says, "Hey, I'm not ready yet and don't have a WebApplicationContext", and returns null back to Rewrite.

Digging deeper.

To understand wants going on there I have set a breakpoint in org.ocpsoft.rewrite.el.TypeBasedExpression.lookupBeanName() and the iterator for BeanNameResolver has 4 entries. It looks like they behave differently (returning null or throwing exception). If a BeanNameResolver returns null the next BeanNameResolver is taken but the order of BeanNameResolver calls seems to be random.

lincolnthree commented 1 year ago

Okay, so I think I know why this is happening. First, the startup error is caused because you need to extend SpringBootServletInitializer in your RewriteConfiguration to tell Spring Boot that this Configuration uses the WebApplicationContext (which changes initialization order.)

This resolves the startup error:

@Configuration
public class RewriteConfiguration extends SpringBootServletInitializer
{
    @Bean
    @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
    public MyController myController()
    {
        return new MyController();
    }
}

Next, regarding the PrettyFaces error when loading the Parameter config, I am still working on that. But it's being caused because the request-scoped bean is being looked up during application startup for some reason. It shouldn't be. The EL container should be able to return metadata about the bean without instantiating the bean. I'm still researching.

TobiasBerndt commented 1 year ago

I thought SpringBootServletInitializer is only needed if you want to run the application as WAR. My sample application is running as JAR with an embedded tomcat. Javadoc of SpringBootServletInitializer:

An opinionated WebApplicationInitializer to run a SpringApplication from a traditional WAR deployment. [...] Note that a WebApplicationInitializer is only needed if you are building a war file and deploying it. If you prefer to run an embedded web server then you won't need this at all.

Nevertheless I tried it with SpringBootServletInitializer but exceptions during startup are still thrown.

lincolnthree commented 1 year ago

Nevertheless I tried it with SpringBootServletInitializer but exceptions during startup are still thrown.

[Edit. You are correct -- This doesn't fix the issue. I think it just changed the order of classloading and a different issue came up first.] Please ignore.

lincolnthree commented 1 year ago

Ok, so from what I can tell, it appears that Spring Boot is misconfigured or incompatible somehow. The Spring WebApplicationContext is not available during Servlet Filter startup, and it needs to be. I am not an expert in Spring Boot and its startup lifecycle, so I'm not sure how much I will be able to help, here. Know anyone with Spring Boot expertise?

Generally, when configuring Spring Manually, you'd ensure this happens by ordering Spring's WebContextListener to run before Rewrite's.

2023-03-21 11:46:58.515  WARN 47756 --- [           main] o.s.w.c.s.SpringBeanAutowiringSupport    : 
Current WebApplicationContext is not available for processing of FacesRewriteLifecycleListener:
Make sure this class gets constructed in a Spring web application after the Spring
WebApplicationContext has been initialized. Proceeding without injection.

I've tried all the solutions proposed here: https://stackoverflow.com/questions/33486219/spring-boot-no-webapplicationcontext-found, but I suspect something is interfering with or overriding the boot order. I might suggest checking with the JoinFaces folks and see what they are doing in this regard, and if there is any way to override their defined start order (if they've set one, which I presume they have.)

larsgrefer commented 1 year ago

As for the startup error. I'm worried that this might be some circular initialization loop where SpringBoot is trying to start the WebApplicationContext, then Rewrite tries to start up and request bean names to create the configuration, then SpringBoot says, "Hey, I'm not ready yet and don't have a WebApplicationContext", and returns null back to Rewrite.

Digging deeper.

The "problem" is that org.ocpsoft.rewrite.spring.SpringServiceEnricher does not have access to the current ServletContext, so it can't access the ServletContext attribute org.springframework.web.context.WebApplicationContext.ROOT in which the WebApplicationContext could be found.

See also org.springframework.web.context.support.WebApplicationContextUtils#getRequiredWebApplicationContext

Instead it relies on org.springframework.web.context.ContextLoader#getCurrentWebApplicationContext which is null for Spring Boot Applications, because they aren't loaded by the ContextLoaders. (The only exception might be WAR-based Spring-Boot Application, deployed to an existing servlet container)

lincolnthree commented 1 year ago

Hey @larsgrefer! Thanks for the info and for pointing me toward getRequiredWebApplicationContextg The issue here is that the JoinFaces BeanNameResolver is still using ApplicationContext.getBeansOfType. So it's going to fail when Rewrite attempts to configure Bean names and parameters (which is what's happening to @TobiasBerndt) and the Context isn't found.

I think the better solution here would be to make the Rewrite SpringBeanNameResolver aware of the ServletContext so that it can properly attempt to access the WebApplicationContext in Spring Boot applications using the method you recommended. Then you'd be able to remove your JoinFaces SPI implementation. Or you can make yours implement ContextListener and fix it that way too.

Here's what I propose, regardless of where the fix goes:

/*
 * Copyright 2011 <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.spring;

import java.util.HashSet;
import java.util.Set;

import org.ocpsoft.logging.Logger;
import org.ocpsoft.rewrite.el.spi.BeanNameResolver;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.core.PriorityOrdered;
import org.springframework.web.context.WebApplicationContext;

/**
 * {@link BeanNameResolver} implementation for Spring.
 * 
 * @author Christian Kaltepoth
 * @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
 */
public class SpringBeanNameResolver implements BeanNameResolver
{
   private final Logger log = Logger.getLogger(SpringBeanNameResolver.class);

   @Override
   public String getBeanName(Class<?> clazz)
   {
      WebApplicationContext context = SpringWebApplicationContextProvider.getWebApplicationContext();
      if (context == null) {
         throw new IllegalStateException("Unable to get current WebApplicationContext");
      }

      // obtain a map of bean names
      Set<String> beanNames = resolveBeanNames(context, clazz);

      // no beans of that type, nothing we can do
      if (beanNames == null || beanNames.size() == 0) {
         return null;
      }

      // more than one result -> warn the user
      else if (beanNames.size() > 1) {
         log.warn("Spring knows more than one bean of type [{}]", clazz.getName());
         return null;
      }

      // exactly one result -> we got a name
      else {
         return beanNames.iterator().next();
      }

   }

   /**
    * Will ignore scoped proxy target bean names.
    * 
    * @see https://github.com/ocpsoft/rewrite/issues/170
    */
   private Set<String> resolveBeanNames(ListableBeanFactory beanFactory, Class<?> clazz)
   {

      final Set<String> result = new HashSet<String>();

      String[] names = beanFactory.getBeanNamesForType(clazz); <--- NOTE: This is the important change right here.
      if (names != null) {
         for (String name : names) {
            if (name != null && !name.startsWith("scopedTarget.")) {
               result.add(name);
            }
         }
      }

      return result;

   }

}

I am working on a new release/snapshot that should resolve this, regardless :) Let's see if we can get it taken care of!

lincolnthree commented 1 year ago

Proof it works :)

image

larsgrefer commented 1 year ago

The interesting SPI is ServiceEnricher once this works correctly, the other Spring SPI Implementations can get their ApplicationContext reference from the enriched by just implementing ApplicationContextAware

lincolnthree commented 1 year ago

The interesting SPI is ServiceEnricher once this works correctly, the other Spring SPI Implementations can get their ApplicationContext reference from the enriched by just implementing ApplicationContextAware

That might work if we switch to using processInjectionBasedOnServletContext. Let me see if I can make that happen.

larsgrefer commented 1 year ago

processInjectionBasedOnServletContext also uses org.springframework.web.context.support.WebApplicationContextUtils#getRequiredWebApplicationContext internally.

lincolnthree commented 1 year ago

processInjectionBasedOnServletContext also uses org.springframework.web.context.support.WebApplicationContextUtils#getRequiredWebApplicationContext internally.

Right, that's why I think it should be used if possible. Once we have access to the ServletContext that would prevent the call to getCurrentWebApplicationContext. Or am I confused?

lincolnthree commented 1 year ago

BAM. It works. This is MUCH cleaner. Thanks @larsgrefer.


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.6)

2023-03-21 16:20:14.422  INFO 63251 --- [           main] com.example.rewrite.RewriteApplication   : Starting RewriteApplication using Java 11.0.18 on mshark.local with PID 63251 (/Users/lincoln/projects/ocpsoft/rewrite-issue-362/target/classes started by lincoln in /Users/lincoln/projects/ocpsoft/rewrite-issue-362)
2023-03-21 16:20:14.424  INFO 63251 --- [           main] com.example.rewrite.RewriteApplication   : No active profile set, falling back to 1 default profile: "default"
2023-03-21 16:20:15.025  INFO 63251 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-03-21 16:20:15.033  INFO 63251 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-03-21 16:20:15.033  INFO 63251 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.69]
2023-03-21 16:20:15.193  INFO 63251 --- [           main] org.apache.jasper.servlet.TldScanner     : At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
2023-03-21 16:20:15.204  INFO 63251 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-03-21 16:20:15.204  INFO 63251 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 736 ms
2023-03-21 16:20:15.220  WARN 63251 --- [           main] o.s.w.c.s.SpringBeanAutowiringSupport    : Current WebApplicationContext is not available for processing of SLF4JLogAdapterFactory: Make sure this class gets constructed in a Spring web application after the Spring WebApplicationContext has been initialized. Proceeding without injection.
2023-03-21 16:20:15.733  INFO 63251 --- [           main] vletContainerInitializerRegistrationBean : Resolving classes for com.sun.faces.config.FacesInitializer took 0.485617667s
2023-03-21 16:20:15.747  WARN 63251 --- [           main] o.s.w.c.s.SpringBeanAutowiringSupport    : Current WebApplicationContext is not available for processing of SpringServiceEnricher: Make sure this class gets constructed in a Spring web application after the Spring WebApplicationContext has been initialized. Proceeding without injection.
2023-03-21 16:20:15.775  INFO 63251 --- [           main] j.e.resource.webcontainer.jsf.config     : Initializing Mojarra 2.3.5 ( 20180516-1910 bf35b0f6c540c69e80e6da962a2b62756838ac41) for context ''
2023-03-21 16:20:15.843  INFO 63251 --- [           main] j.e.r.webcontainer.jsf.application       : JSF1048: PostConstruct/PreDestroy annotations present.  ManagedBeans methods marked with these annotations will have said annotations processed.
2023-03-21 16:20:15.906  INFO 63251 --- [           main] o.o.rewrite.faces.RewritePhaseListener   : RewritePhaseListener starting up.
2023-03-21 16:20:16.011  INFO 63251 --- [           main] o.ocpsoft.rewrite.servlet.RewriteFilter  : RewriteFilter starting up...
2023-03-21 16:20:16.023  INFO 63251 --- [           main] o.ocpsoft.rewrite.servlet.RewriteFilter  : Loaded [3] org.ocpsoft.rewrite.servlet.spi.RewriteLifecycleListener [org.ocpsoft.rewrite.faces.FacesRewriteLifecycleListener<0>, org.ocpsoft.rewrite.servlet.impl.DefaultRewriteLifecycleListener<2147483647>, org.ocpsoft.rewrite.servlet.config.lifecycle.JoinRewriteLifecycleListener<2147483647>]
2023-03-21 16:20:16.023  INFO 63251 --- [           main] o.ocpsoft.rewrite.servlet.RewriteFilter  : Loaded [1] org.ocpsoft.rewrite.servlet.spi.RequestCycleWrapper [org.ocpsoft.rewrite.servlet.impl.HttpRewriteRequestCycleWrapper<0>]
2023-03-21 16:20:16.023  INFO 63251 --- [           main] o.ocpsoft.rewrite.servlet.RewriteFilter  : Loaded [1] org.ocpsoft.rewrite.spi.RewriteProvider [org.ocpsoft.rewrite.servlet.impl.DefaultHttpRewriteProvider<0>]
2023-03-21 16:20:16.023  INFO 63251 --- [           main] o.ocpsoft.rewrite.servlet.RewriteFilter  : Loaded [1] org.ocpsoft.rewrite.servlet.spi.RewriteResultHandler [org.ocpsoft.rewrite.servlet.impl.HttpRewriteResultHandler<0>]
2023-03-21 16:20:16.023  INFO 63251 --- [           main] o.ocpsoft.rewrite.servlet.RewriteFilter  : Loaded [1] org.ocpsoft.rewrite.servlet.spi.InboundRewriteProducer [org.ocpsoft.rewrite.servlet.impl.HttpInboundRewriteProducer<0>]
2023-03-21 16:20:16.023  INFO 63251 --- [           main] o.ocpsoft.rewrite.servlet.RewriteFilter  : Loaded [1] org.ocpsoft.rewrite.servlet.spi.OutboundRewriteProducer [org.ocpsoft.rewrite.servlet.impl.HttpOutboundRewriteProducer<0>]
2023-03-21 16:20:16.024  INFO 63251 --- [           main] o.ocpsoft.rewrite.servlet.RewriteFilter  : Loaded [1] org.ocpsoft.rewrite.servlet.spi.ContextListener [org.ocpsoft.rewrite.spring.SpringServiceEnricher<0>]
2023-03-21 16:20:16.025  INFO 63251 --- [           main] o.ocpsoft.rewrite.servlet.RewriteFilter  : Loaded [0] org.ocpsoft.rewrite.servlet.spi.RequestListener []
2023-03-21 16:20:16.026  INFO 63251 --- [           main] o.ocpsoft.rewrite.servlet.RewriteFilter  : Loaded [0] org.ocpsoft.rewrite.servlet.spi.RequestParameterProvider []
2023-03-21 16:20:16.030  INFO 63251 --- [           main] o.ocpsoft.rewrite.servlet.RewriteFilter  : Loaded [2] org.ocpsoft.rewrite.el.spi.ExpressionLanguageProvider [org.ocpsoft.rewrite.spring.SpringExpressionLanguageProvider<20>, org.ocpsoft.rewrite.faces.FacesExpressionLanguageProvider<30>]
2023-03-21 16:20:16.032  INFO 63251 --- [           main] o.ocpsoft.rewrite.servlet.RewriteFilter  : Loaded [1] org.ocpsoft.rewrite.spi.InvocationResultHandler [org.ocpsoft.rewrite.faces.NavigatingInvocationResultHandler<100>]
2023-03-21 16:20:16.034  INFO 63251 --- [           main] o.ocpsoft.rewrite.servlet.RewriteFilter  : Loaded [2] org.ocpsoft.common.spi.ServiceEnricher [org.ocpsoft.rewrite.spring.SpringServiceEnricher<0>, org.joinfaces.autoconfigure.rewrite.SpringBootServiceEnricher]
2023-03-21 16:20:16.036  INFO 63251 --- [           main] o.ocpsoft.rewrite.servlet.RewriteFilter  : Loaded [1] org.ocpsoft.rewrite.spi.ConfigurationCacheProvider [org.ocpsoft.rewrite.servlet.impl.ServletContextConfigurationCacheProvider<0>]
2023-03-21 16:20:16.039  INFO 63251 --- [           main] o.ocpsoft.rewrite.servlet.RewriteFilter  : Loaded [2] org.ocpsoft.rewrite.config.ConfigurationProvider [org.joinfaces.autoconfigure.rewrite.SpringBootAnnotationConfigProvider<100>, org.ocpsoft.rewrite.annotation.config.AnnotationConfigProvider<100>]
2023-03-21 16:20:16.042  INFO 63251 --- [           main] o.o.r.s.impl.DefaultHttpRewriteProvider  : Loaded [0] org.ocpsoft.rewrite.spi.RuleCacheProvider []
2023-03-21 16:20:16.452  INFO 63251 --- [           main] o.o.rewrite.param.DefaultParameterStore  : Loaded [1] org.ocpsoft.rewrite.spi.GlobalParameterProvider [org.ocpsoft.rewrite.instance.WildcardParameterProvider<0>]
2023-03-21 16:20:16.480  INFO 63251 --- [           main] o.ocpsoft.rewrite.servlet.RewriteFilter  : Rewrite 4.0.0-SNAPSHOT initialized.
2023-03-21 16:20:16.714  INFO 63251 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-03-21 16:20:16.721  INFO 63251 --- [           main] com.example.rewrite.RewriteApplication   : Started RewriteApplication in 2.557 seconds (JVM running for 2.778)
lincolnthree commented 1 year ago

Additionally. I have improved the expression language bean name resolution so that it tries providers until either it finds one that works, or they all fail.

lincolnthree commented 1 year ago

@larsgrefer I believe I have resolved this. Please review the pull request if you have some time :) I'll release a new version soon if you approve - https://github.com/ocpsoft/rewrite/pull/368

@TobiasBerndt You should be able to try the changes if you clone my fork and run mvn clean install or mvn clean install -DskipTests=true if you want to skip the tests. Then just update your project to use the 4.0.0-SNAPSHOT version of Rewrite. Let me know if it works?

TobiasBerndt commented 1 year ago

I tested the changes successfully with 4.0.0-SNAPSHOT as JAR and WAR. Thank you @lincolnthree and @larsgrefer. I'm looking forward to the new release. :)

lincolnthree commented 1 year ago

Thanks for confirming, @TobiasBerndt - Apologies for the delay in moving forward with this. The past few weeks went off the rails. I am working on getting this PR merged with @larsgrefer's changes, and pulled into the other release tracks/branches as well.

TobiasBerndt commented 1 year ago

Hi @lincolnthree, now I lost also track on this :blush: But to resume the topic do you think you have time to get the changes released?

lincolnthree commented 1 year ago

Hey @TobiasBerndt - Yeah, there are a number of pending updates that I need to get merged into the three primary branches. This is one of them. Sorry for the delay. Trying to make some time in the next week. Are you blocked by this currently, or can you use the local build temporarily?

TobiasBerndt commented 1 year ago

I'm not blocked right now as I have a workaround by not using 3.5.1.Final and staying on 3.4.4.Final But I want to upgrade to Spring Boot 3.x.x in near future and as far as I can see here (https://github.com/joinfaces/joinfaces#system-requirements) I need to upgrade also to new Joinfaces version and I don't know if currently used Rewrite version will then be incompatible.

larsgrefer commented 1 year ago

@TobiasBerndt Joinfaces 5.x currently includes Rewrite 6.0.0.Alpha1: https://docs.joinfaces.org/5.1.x/reference/#versions

lincolnthree commented 1 year ago

Please try the new Final versions that have been released today:

Rewrite versions 8.0.0, 9.0.0, and 10.0.0 correspond with Jakarta EE 8, 9, and 10, respectively.

image

lincolnthree commented 1 year ago

Note. This issue may still require more work, and I am still planning on merging the Spring Boot improvements in PR #375