ContentVersionStrategy doesn't work with thymeleaf when a web flow starts

472 views Asked by At

I have the following settings in application.properties

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=true
spring.resources.cache-period=10000
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**

So cache period and strategy content works fine in all pages, but when I tried to use webflow all static doesn't get associated with the content vesrion as the other pages.

Here is my flow definition (resources/webflow/requests/new)

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
      http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

  <var name="uri" class="medicallab.misc.Uri"/>

  <view-state id="selectPatient" view="add-request/select-patient">
    <transition on="next" to="selectDoctor"></transition>
  </view-state>

  <view-state id="selectDoctor">
    <transition on="next" to="selectTextType"></transition>
  </view-state>

  <view-state id="selectTestType">
    <transition on="next" to="Confirm"></transition>
  </view-state>

  <view-state id="Confirm">
    <transition on="next" to="Done" history="invalidate"></transition>
  </view-state>


  <end-state id="Done" view="externalRedirect:#{uri.get('requests')}">
    <output name="success" value="'Request has been added successfully'"/>
  </end-state>

  <end-state id="cancelFlow" view="externalRedirect:#{uri.get('requests')}">
  </end-state>

  <global-transitions>
    <transition on="cancel" to="cancelFlow" history="discard"></transition>
  </global-transitions>

</flow>

And this is my WebMvcConfig class:

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private WebFlowConfig webFlowConfig;

    @Autowired
    private SpringTemplateEngine springTemplateEngine;

    @Bean
    public BCryptPasswordEncoder bcryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    @Bean
    public FlowHandlerMapping flowHandlerMapping() {
        FlowHandlerMapping handlerMapping = new FlowHandlerMapping();
        handlerMapping.setOrder(-1);
        handlerMapping.setFlowRegistry(this.webFlowConfig.flowRegistry());
        handlerMapping.setUrlDecode(true);

        return handlerMapping;
    }

    @Bean
    public FlowHandlerAdapter flowHandlerAdapter() {
        FlowHandlerAdapter handlerAdapter = new FlowHandlerAdapter();
        handlerAdapter.setFlowExecutor(this.webFlowConfig.flowExecutor());
        handlerAdapter.setSaveOutputToFlashScopeOnRedirect(true);
        return handlerAdapter;
    }

    @Bean
    public AjaxThymeleafViewResolver ajaxThymeleafViewResolver() {
        AjaxThymeleafViewResolver viewResolver = new AjaxThymeleafViewResolver();
        viewResolver.setViewClass(FlowAjaxThymeleafView.class);
        viewResolver.setTemplateEngine(springTemplateEngine);
        return viewResolver;
    }

}

Here is my WebFlowConfig

@Configuration
public class WebFlowConfig extends AbstractFlowConfiguration {

    @Autowired
    private WebMvcConfig webMvcConfig;

    @Autowired
    private List<ViewResolver> viewResolvers;

    @Bean
    public FlowExecutor flowExecutor() {
        return getFlowExecutorBuilder(flowRegistry())
                .addFlowExecutionListener(securityFlowExecutionListener(), "*")
                .build();
    }

    @Bean
    public FlowDefinitionRegistry flowRegistry() {
        return getFlowDefinitionRegistryBuilder()
                .setBasePath("classpath*:/templates")
                .addFlowLocationPattern("/**/*-flow.xml")
                .setFlowBuilderServices(flowBuilderServices())
                .build();
    }

    @Bean
    public FlowBuilderServices flowBuilderServices() {
        return getFlowBuilderServicesBuilder()
                .setViewFactoryCreator(mvcViewFactoryCreator())
                .setDevelopmentMode(true)
                .build();
    }

    @Bean
    public MvcViewFactoryCreator mvcViewFactoryCreator() {
        viewResolvers.add(this.webMvcConfig.ajaxThymeleafViewResolver());

        MvcViewFactoryCreator factoryCreator = new MvcViewFactoryCreator();
        factoryCreator.setViewResolvers(viewResolvers);
        factoryCreator.setUseSpringBeanBinding(true);

        return factoryCreator;
    }

    @Bean
    public SecurityFlowExecutionListener securityFlowExecutionListener() {
        return new SecurityFlowExecutionListener();
    }
}

Here is my thymeleaf layout (page.html):

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">

  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <meta http-equiv="x-ua-compatible" content="ie=edge" />

    <link rel="stylesheet" th:href="@{/css/app.css}">

    <link rel="shortcut icon" type="image/x-icon" th:href="${ uri.get('favicon') }">

    <!--/*  Each token will be replaced by their respective titles in the resulting page. */-->
    <title layout:title-pattern="$CONTENT_TITLE - $LAYOUT_TITLE">Title</title>
  </head>

  <body>
    <header id="page-header" layout:fragment="page-header">
      <span th:replace="layouts/page-header :: page-header"></span>
    </header>

    <br />
    <section id="page-alert" layout:fragment="page-alert">
      <span th:replace="layouts/page-alert :: page-alert"></span>
    </section>

    <section id="page-content" layout:fragment="page-content">
      page.html Page Content
    </section>

    <br /><br /><br /><br />

    <footer id="page-footer" layout:fragment="page-footer">
      <span th:replace="layouts/page-footer :: page-footer"></span>
    </footer>

    <script type="text/javascript" th:src="@{/js/app.js}"></script>

  </body>
</html>

And here is the first flow page (add-patient.html):

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/page}">
<head>

  <title th:text="#{app.page-title.requests}">RequestsPageTitle</title>
</head>
<body>
  <section class="container" layout:fragment="page-content">
    ...
  </section>


</body>
</html>

And this is the versions used (pom.xml):

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.6.RELEASE</version>
</parent>

<properties>
    <java.version>1.8</java.version>
    <thymeleaf.version>3.0.7.RELEASE</thymeleaf.version>
    <thymeleaf-springsecurity4.version>3.0.2.RELEASE</thymeleaf-springsecurity4.version>
    <thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
</properties>

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>spring-webflow</artifactId>
    <version>2.4.5.RELEASE</version>
</dependency>

Here is what I found, when I use mvcResourceUrlProvider.getForLookupPath('/css/app.css') the css file is associated with the content hash, I think the ResourceUrlProvider doesn't wrap the response that is related to webflow.

Any way how to hack it or fix something?

1

There are 1 answers

0
cgw cgw On

You can add the following code to your WebMvcConfig

private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
    "classpath:/META-INF/resources/", "classpath:/resources/",
    "classpath:/static/", "classpath:/public/" };

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!registry.hasMappingForPattern("/webjars/**")) {
        registry.addResourceHandler("/webjars/**").addResourceLocations(
                "classpath:/META-INF/resources/webjars/");
    }
    if (!registry.hasMappingForPattern("/**")) {
        registry.addResourceHandler("/**").addResourceLocations(
                CLASSPATH_RESOURCE_LOCATIONS);
    }
}