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

UnsupportedOperationException for request scoped bean with parameter #362

Open
TobiasBerndt opened this issue Dec 8, 2022 · 29 comments
Open

Comments

@TobiasBerndt
Copy link

Versions:

  • Spring boot 2.7.6
  • Joinfaces 4.7.4
  • Rewrite 3.5.1.Final
  • JSF 2.3

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
Copy link
Member

@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
Copy link
Author

TobiasBerndt commented Mar 2, 2023

@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
Copy link
Member

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
Copy link
Author

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
Copy link
Member

lincolnthree commented Mar 16, 2023

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
Copy link
Member

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
Copy link
Author

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.

  • org.ocpsoft.rewrite.el.DefaultBeanNameResolver --> returns null for singleton and request scoped bean
  • org.ocpsoft.rewrite.faces.FacesBeanNameResolver --> returns null for singleton and request scoped bean
  • org.ocpsoft.rewrite.spring.SpringBeanNameResolver --> for singleton and request scoped bean is results in java.lang.IllegalStateException: Unable to get current WebApplicationContext
  • org.joinfaces.autoconfigure.rewrite.SpringBootBeanNameResolver
    • for singleton scoped bean --> bean gets resolved
    • for request scoped bean --> java.lang.UnsupportedOperationException

@lincolnthree
Copy link
Member

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
Copy link
Author

TobiasBerndt commented Mar 21, 2023

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
Copy link
Member

lincolnthree commented Mar 21, 2023

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
Copy link
Member

lincolnthree commented Mar 21, 2023

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
Copy link
Contributor

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
Copy link
Member

lincolnthree commented Mar 21, 2023

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:[email protected]">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:[email protected]">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
Copy link
Member

Proof it works :)

image

@larsgrefer
Copy link
Contributor

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
Copy link
Member

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
Copy link
Contributor

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

@lincolnthree
Copy link
Member

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
Copy link
Member

lincolnthree commented Mar 21, 2023

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
Copy link
Member

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
Copy link
Member

lincolnthree commented Mar 21, 2023

@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 - #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
Copy link
Author

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
Copy link
Member

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
Copy link
Author

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

@lincolnthree
Copy link
Member

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
Copy link
Author

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
Copy link
Contributor

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

@lincolnthree
Copy link
Member

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
Copy link
Member

lincolnthree commented Oct 4, 2023

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

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