Details
-
Improvement
-
Status: Open
-
Normal
-
Resolution: Unresolved
-
13.0.0
-
None
-
None
-
Not environment specific.
-
Flagged
-
Orion
-
BrXM Backlog
Description
Abstract
Spring request and session-scoped beans are not supported in the out-of-the-box default configuration of BloomReach without inserting a relatively obscure listener into web.xml files.
Introduction
This issue is being opened to illustrate that Spring request and session-scoped beans are not supported in the out-of-the-box default configuration of BloomReach. To clarify by example, the use of a request-scoped Spring bean such as the following generates an error in the platform:
@Configuration public class MyScopedBeanConfiguration { @Bean @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.INTERFACES) public MySpringRequestBean mySpringRequestBean() { MySpringRequestBean result = new MySpringRequestBeanImpl(); // Do stuff... return result; } }
While the use of request/session-scoped Spring beans are not common, they are quite important in certain occasions. The resolution to this issue is simple and can be implemented merely by defining a Spring listener in relevant web.xml files of the BloomReach platform. However, doing so may not be obvious to most developers, which would render some real-world Spring API code such as the following inoperable in BloomReach:
In the text that follows below, instructions are presented on how to quickly setup a default "myproject" BloomReach installation and some brief Java code is provided to illustrate the resulting error. Following that, a solution is proposed.
Demonstrating The Issue
In an empty directory of a BloomReach development-capable machine, enter the following Maven command, accepting the default values, to create a "myproject" installation:
mvn -U -e org.apache.maven.plugins:maven-archetype-plugin:2.4:generate -DarchetypeGroupId=org.onehippo.cms7 -DarchetypeArtifactId=hippo-project-archetype -DarchetypeRepository=https://maven.onehippo.com/maven2-enterprise -DarchetypeVersion=13.0.0-1
Enter the project folder, build and run the platform:
cd myproject mvn clean verify mvn -Pcargo.run -Drepo.path=storage
Run through the "Essentials Setup" page by clicking the "Get started" button on the following URL using a web browser:
http://localhost:8080/essentials
While still on the Essentials web application, install just the "Banners" feature.
Shut down the BloomReach platform by pressing Ctrl-C in the terminal.
Open a Java IDE and create a new Maven project based on the existing source files of this new folder.
Using the Java IDE, create a new Java class with the following pathname:
site/components/src/main/java/org/example/extensions/CategoriesDoBeforeRenderExtension.java
Paste the following contents into this class, even though the necessary dependencies are not yet present:
package org.example.extensions; import java.text.SimpleDateFormat; import java.util.Locale; import org.hippoecm.hst.core.component.HstRequest; import org.hippoecm.hst.core.component.HstResponse; import org.onehippo.cms7.essentials.components.CommonComponent; import org.onehippo.cms7.essentials.components.ext.DoBeforeRenderExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class CategoriesDoBeforeRenderExtension implements DoBeforeRenderExtension { private static final Logger LOG = LoggerFactory.getLogger(CategoriesDoBeforeRenderExtension.class); @Autowired private MySpringRequestBean mySpringRequestBean; @Override public void execute(CommonComponent commonComponent, HstRequest request, HstResponse response) { String result = "Missing"; Long value = mySpringRequestBean.getValue(); if (value != null) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US); result = sdf.format(value); } LOG.debug(" *** request time: {}", result); } }
Within the very same package (folder), create the following new Java class:
site/components/src/main/java/org/example/extensions/MyScopedBeanConfiguration.java
Paste the following contents into it:
package org.example.extensions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.web.context.WebApplicationContext; @Configuration public class MyScopedBeanConfiguration { @Bean @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.INTERFACES) public MySpringRequestBean mySpringRequestBean() { MySpringRequestBean result = new MySpringRequestBeanImpl(); result.setValue(System.currentTimeMillis()); return result; } }
Create the following new Java interface within the same package:
site/components/src/main/java/org/example/extensions/MySpringRequestBean.java
Paste the following contents into it:
package org.example.extensions; public interface MySpringRequestBean { Long getValue(); void setValue(Long value); }
Create the following Java class within the same package:
site/components/src/main/java/org/example/extensions/MySpringRequestBeanImpl.java
Paste the following contents into it:
package org.example.extensions; public class MySpringRequestBeanImpl implements MySpringRequestBean { public Long getValue() { return value; } public void setValue(Long value) { this.value = value; } private Long value; }
In site/components/src/main/resources/META-INF/hst-assembly/overrides/hippo-essentials-spring.xml, enter the following line (just before the closing </bean> tag) to enable component scanning for the classes that were defined above:
<context:component-scan base-package="org.example"/>
Note that the above line will require defining a new "xmlns:context" namespace attribute in the opening <beans> tag at the top of the file:
xmlns:context="http://www.springframework.org/schema/context"
Also in this same hippo-essentials-spring.xml file, replace the following snippet:
<!-- Default no-operation do before extension --> <bean id="org.onehippo.cms7.essentials.components.ext.DoBeforeRenderExtension" class="org.onehippo.cms7.essentials.components.ext.NoopDoBeforeRenderExtension"/>
By the following:
<bean id="org.onehippo.cms7.essentials.components.ext.DoBeforeRenderExtension" class="org.example.extensions.CategoriesDoBeforeRenderExtension"/>
Save all source code changes.
Returning to the terminal shell, rebuild and run the project:
mvn clean verify mvn -Pcargo.run -Drepo.path=storage
Direct a web browser to the site home page URL shown below while monitoring the BloomReach log console:
http://localhost:8080/site
Log output such as the following warning will eventually appear, indicating an inability to create the scoped bean:
[INFO] [talledLocalContainer] 10.03.2019 00:17:12 WARN http-nio-8080-exec-4 [HstComponentInvokerImpl.logComponentException:254] Component exception caught on window menu with component org.onehippo.cms7.essentials.components.EssentialsMenuComponent: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.mySpringRequestBean': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton
Enabling Request/Session-Scoped Spring Bean Support In BloomReach
The following official Spring resource provides insight into the underlying problem:
From the above URL, the following text can be found which suggests the need to define one of three possible listeners that will enable HTTP request binding to the thread that is servicing the request:
"DispatcherServlet, RequestContextListener and RequestContextFilter all do exactly the same thing, namely bind the HTTP request object to the Thread that is servicing that request. This makes beans that are request- and session-scoped available further down the call chain."
A review of the three default web.xml files provided with BloomReach reveals that none of these three listeners are ever used, hence rendering request/session-scoped Spring beans unusable.
To resolve the issue demonstrated above, in site/webapp/src/main/webapp/WEB-INF/web.xml, enter the following <listener> tag:
<listener> <listener-class&gt;org.springframework.web.context.request.RequestContextListener</listener-class&gt; </listener>
Shut down, restart and rebuild the BloomReach platform:
# Press Ctrl-C to shut down the platform, then enter: mvn clean verify mvn -Pcargo.run -Drepo.path=storage
Redirect a web browser to the home page and notice that there are no errors in the BloomReach console log:
http://localhost:8080/site
In fact, each refresh of the page will output to the console the date/time stamp of the request bean that was defined earlier. Since each timestamp is different, it's a clear indication that a different bean is being instantiated with each request - which is the desired behavior.
Recommendation
Unless there is a good reason not to do so, it is recommended that the RequestContextListener listener be inserted into each of the three web.xml files in the default BloomReach configuration. If there is some valid reason for purposely omitting this listener, an explanation as to how to proceed without Spring request/session-scoped bean support is warranted.
Thank you.