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.