Spring WebFlow + Thymeleaf: flowExecutionKey is not present in URL

1.4k Views Asked by At

I'm trying to integrate Spring WebFlow (SWF) into an existing Spring Boot + Thymeleaf project but am running into issues when trying to navigate away from my the first template in my flow. Specifically the URL has no flowExecutionKey parameter present, so my link which looks like:

<a th:href="@{${flowExecutionUrl}(_eventId=selectPaymentMethod,type=*{paymentMethod.name})}">
  <img th:src="@{|*{paymentMethod.logo}|}" class="img-thumbnail" src="../resources/images/paypal-logo.png" alt="" />
</a>

Ends up translating as a link to:

http://localhost:8080/payments/checkout-widget/session/1/checkout?_eventId=selectPaymentMethod&type=Google+Checkout

From what I've read the flowExecutionUrl should include a flowExecutionKey parameter, something like execution=e3s2. What I instead see with the above URL is that I'm continually redirected back to the first page in my WebFlow. It's almost as if my flow isn't quite wired up correctly although I've spent a good few hours perusing similar issues on Google but can't find anybody having this exact issue. I've checked my JSESSIONID request cookie and this does not change.

Do you know why the flowExecutionUrl is missing or null? If anymore information is needed please just ask :)

Many thanks in advance,

~Ian

WebflowConfig.java:

@Configuration
@AutoConfigureAfter(MvcConfig.class)
public class WebflowConfig extends AbstractFlowConfiguration {

    @Autowired
    private SpringTemplateEngine templateEngine;

    @Bean
    public FlowExecutor flowExecutor() {
        return getFlowExecutorBuilder(flowRegistry())
                .addFlowExecutionListener(new SecurityFlowExecutionListener())
                .build();
    }

    @Bean
    public FlowDefinitionRegistry flowRegistry() {
        return getFlowDefinitionRegistryBuilder(flowBuilderServices())
                .addFlowLocation("classpath:/templates/checkout.xml", "checkout")
                .build();
    }

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

    @Bean
    public FlowController flowController() {
        FlowController flowController = new FlowController();
        flowController.setFlowExecutor(flowExecutor());
        return flowController;
    }

    @Bean
    public FlowHandlerMapping flowHandlerMapping() {
        FlowHandlerMapping flowHandlerMapping = new FlowHandlerMapping();
        flowHandlerMapping.setFlowRegistry(flowRegistry());
        flowHandlerMapping.setOrder(-1);
        return flowHandlerMapping;
    }

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

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

    @Bean
    public MvcViewFactoryCreator mvcViewFactoryCreator() {
        List<ViewResolver> viewResolvers = new ArrayList<>();
        viewResolvers.add(thymeleafViewResolver());

        MvcViewFactoryCreator mvcViewFactoryCreator = new MvcViewFactoryCreator();
        mvcViewFactoryCreator.setViewResolvers(viewResolvers);
        mvcViewFactoryCreator.setUseSpringBeanBinding(true);
        return mvcViewFactoryCreator;
    }
}

checkout.xml:

<?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">

    <on-start>
        <set name="flowScope.paymentMethods" value="checkoutService.paymentMethods"/>
    </on-start>

    <view-state id="payment-methods" view="payment-methods">
        <transition on="selectPaymentMethod" to="new-details"/>
    </view-state>

    <view-state id="new-details" view="new-details">
        <transition on="submitDetails" to="summary"/>
    </view-state>

    <view-state id="summary" view="summary">
        <transition on="completeDetails" to="completed"/>
    </view-state>

    <end-state id="cancelled" view="externalRedirect:contextRelative:/home.do"/>
    <end-state id="completed" view="externalRedirect:contextRelative:/home.do"/>

    <global-transitions>
        <transition on="cancelCheckout" to="cancelled"/>
    </global-transitions>
</flow>

WelcomeController.java:

@Controller
@RequestMapping(value = "/payments/checkout-widget/session/{sessionId}")
@SessionAttributes({"paymentMethods", "redirectUrls", "cardDetails"})
public class WelcomeController {

    @Inject
    private CheckoutWidgetSessionService checkoutWidgetSessionService;

    @Inject
    private CheckoutService checkoutService;

    @RequestMapping(value = "/checkout", method = RequestMethod.GET)
    public String intro(
            @NotNull @PathVariable("sessionId") String sessionId,
            Model model) {
        System.out.println("Session ID:" + sessionId);

        model.addAttribute("paymentMethods", checkoutService.getPaymentMethods());
        model.addAttribute("deliveryAddress", checkoutService.getDeliveryAddress());
        model.addAttribute("date",new Date().toString());

        return "payment-methods";
    }
}

payment-methods.html:

<html>
    <head>
        <meta charset="utf-8"/>
        <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
        <meta name="viewport" content="width=device-width, initial-scale=1"/>

        <link rel="stylesheet"
              th:href="@{/css/bootstrap.min.css}"
              href="../resources/css/bootstrap.min.css"/>
    </head>
    <body>
        <div class="container">
            <h1 th:text="#{payment.methods.title}">Payment Methods</h1>
            <br/>

            <p th:text="#{payment.methods.label}">Please select a payment method:</p>

            <div class="row">
                <div class="col-sm-2 col-xs-4 col-md-1 col-lg-1" th:each="paymentMethod : ${paymentMethods}">
                    <a th:href="@{${flowExecutionUrl}(_eventId=selectPaymentMethod,type=*{paymentMethod.name})}">
                        <img th:src="@{|*{paymentMethod.logo}|}" class="img-thumbnail" src="../resources/images/paypal-logo.png" alt="" />
                    </a>
                </div>

            </div>

        </div>
        <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
        <!-- Include all compiled plugins (below), or include individual files as needed -->
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>

    </body>
</html>

EDIT: So it would seem that WebFlow is not compliant with RESTful GET methods (i.e. when you have a dynamic part of the URL which defines the object ID). I have managed to find a work-around for my particular use-case by storing and retrieving the session ID in a different manner than with POST/GET.

0

There are 0 best solutions below