Uploaded image for project: 'Hippo CMS'
  1. Hippo CMS
  2. CMS-12907

Spring request and session-scoped beans are not supported in the out-of-the-box default configuration of BloomReach

    XMLWordPrintable

Details

    • Improvement
    • Status: Open
    • Normal
    • Resolution: Unresolved
    • 13.0.0
    • None
    • site-toolkit
    • None
    • Not environment specific.

    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:

      https://jar-download.com/artifacts/org.springframework.security.oauth/spring-security-oauth2/2.0.6.RELEASE/source-code/org/springframework/security/oauth2/config/annotation/web/configuration/OAuth2ClientConfiguration.java

      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:

      https://docs.spring.io/spring/docs/3.2.4.RELEASE/spring-framework-reference/htmlsingle/#beans-factory-scopes-other

      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&amp;gt;org.springframework.web.context.request.RequestContextListener</listener-class&amp;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.

      Attachments

        Activity

          People

            Unassigned Unassigned
            nkatsifarakis Nick Katsifarakis
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

            Dates

              Created:
              Updated: