Chapter 10. Float Your Application with Web Flow

When it comes to web application development, reusability and maintenance are two important factors that need to be considered. Spring Web Flow (SWF) is an independent framework that facilitates the development of highly configurable and maintainable flow-based web applications.

In this chapter, we are going to see how to incorporate the Spring Web Flow framework within a Spring MVC application. Spring Web Flow facilitates the development of stateful web applications with controlled navigation flow. After finishing this chapter, you will have an idea about developing flow-based applications using Spring Web Flow.

Working with Spring Web Flow

Spring Web Flow allows us to develop flow-based web applications easily. A flow in a web application encapsulates a series of steps that guides the user through the execution of a business task, such as checking in to a hotel, applying for a job, checking out a shopping cart, and so on. Usually, a flow will have clear start and end points, include multiple HTTP requests/responses, and the user must go through a set of screens in a specific order to complete the flow.

In all our previous chapters, the responsibility for defining a page flow specifically lies with controllers, and we weaved the page flows into individual Controllers and Views; for instance, we usually mapped a web request to a controller, and the controller is the one who decides which logical View to return as a response.

This is simple to understand and sufficient for straightforward page flows, but when web applications get more and more complex in terms of user interaction flows, maintaining a large and complex page flow becomes a nightmare.

If you are going to develop such complex flow-based applications, then SWF is your trusty companion. SWF allows you to define and execute user interface (UI) flows within your web application. Without further ado, let's dive straight into Spring Web Flow by defining some page flows in our project.

It is nice that we have implemented a shopping cart in our previous chapter, but it is of no use if we do not provide a checkout facility to finish shopping and perform order processing. Let's do that in two phases. Firstly, we need to create the required backend services, domain objects, and repository implementation, in order to perform order processing (here strictly no web flow related stuff is involved, it's just a supportive backend service that can be used later by web flow definitions in order to complete the checkout process). Secondly, we need to define the actual Spring Web Flow definition, which can use our backend services in order to execute the flow definition. There, we will do the actual web flow configuration and definition.

Time for action - implementing the order processing service

We will start by implementing our order processing backend service first. We proceed as follows:

  1. Add the following schema definitions for the CART and CART_ITEM tables in create-table.sql; you can find create-table.sql under the folder src/main/resources/db/sql/:
          CREATE TABLE ADDRESS ( 
             ID INTEGER IDENTITY PRIMARY KEY, 
             DOOR_NO VARCHAR(25), 
             STREET_NAME VARCHAR(25), 
             AREA_NAME VARCHAR(25), 
             STATE VARCHAR(25), 
             COUNTRY VARCHAR(25), 
             ZIP VARCHAR(25), 
          ); 
     
          CREATE TABLE CUSTOMER ( 
             ID INTEGER IDENTITY PRIMARY KEY, 
             NAME VARCHAR(25), 
             PHONE_NUMBER VARCHAR(25), 
             BILLING_ADDRESS_ID INTEGER FOREIGN KEY REFERENCES ADDRESS(ID), 
          ); 
     
          CREATE TABLE SHIPPING_DETAIL ( 
             ID INTEGER IDENTITY PRIMARY KEY, 
             NAME VARCHAR(25), 
             SHIPPING_DATE VARCHAR(25), 
             SHIPPING_ADDRESS_ID INTEGER FOREIGN KEY REFERENCES ADDRESS(ID), 
          ); 
     
          CREATE TABLE ORDERS ( 
             ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 
             1000, INCREMENT BY 1) PRIMARY KEY, 
             CART_ID VARCHAR(50) FOREIGN KEY REFERENCES CART(ID), 
             CUSTOMER_ID INTEGER FOREIGN KEY REFERENCES CUSTOMER(ID), 
             SHIPPING_DETAIL_ID INTEGER FOREIGN KEY REFERENCES 
             SHIPPING_DETAIL(ID), 
          ); 
    
  2. Also add the following drop table command as the first line in create-table.sql:
          DROP TABLE ORDERS IF EXISTS; 
          DROP TABLE CUSTOMER IF EXISTS; 
          DROP TABLE SHIPPING_DETAIL IF EXISTS; 
          DROP TABLE ADDRESS IF EXISTS; 
    
  3. Create a class named Address under the com.packt.webstore.domain package in the src/main/java source folder, and add the following code to it:
          package com.packt.webstore.domain; 
     
          import java.io.Serializable; 
     
          public class Address implements Serializable{ 
     
             private static final long serialVersionUID = -530086768384258062L; 
        
             private Long id; 
             private String doorNo; 
             private String streetName; 
             private String areaName; 
             private String state; 
             private String country; 
             private String zipCode; 
        
             public Long getId() { 
                return id; 
             } 
        
             public void setId(Long id) { 
                this.id = id; 
             } 
        
             public String getDoorNo() { 
                return doorNo; 
             } 
             public void setDoorNo(String doorNo) { 
                this.doorNo = doorNo; 
             } 
             public String getStreetName() { 
                return streetName; 
             } 
             public void setStreetName(String streetName) { 
                this.streetName = streetName; 
             } 
             public String getAreaName() { 
                return areaName; 
             } 
             public void setAreaName(String areaName) { 
                this.areaName = areaName; 
             } 
             public String getState() { 
                return state; 
             } 
             public void setState(String state) { 
                this.state = state; 
             } 
             public String getCountry() { 
                return country; 
             } 
             public void setCountry(String country) { 
                this.country = country; 
             } 
             public String getZipCode() { 
                return zipCode; 
             } 
             public void setZipCode(String zipCode) { 
                this.zipCode = zipCode; 
             } 
     
             @Override 
             public int hashCode() { 
                final int prime = 31; 
                int result = 1; 
                result = prime * result + ((id == null) ? 0 : id.hashCode()); 
                return result; 
             } 
     
             @Override 
             public boolean equals(Object obj) { 
                if (this == obj) 
                   return true; 
                if (obj == null) 
                   return false; 
                if (getClass() != obj.getClass()) 
                   return false; 
                Address other = (Address) obj; 
                if (id == null) { 
                   if (other.id != null) 
                      return false; 
                } else if (!id.equals(other.id)) 
                   return false; 
                return true; 
             } 
          } 
    
  4. Create another class named Customer under the same package, and add the following code to it:
          package com.packt.webstore.domain; 
     
          import java.io.Serializable; 
     
          public class Customer implements Serializable{ 
     
             private static final long serialVersionUID = 2284040482222162898L; 
        
             private Long customerId; 
             private String name; 
             private Address billingAddress; 
             private String phoneNumber; 
        
             public Customer() { 
                super(); 
                this.billingAddress = new Address(); 
             } 
        
             public Customer(Long customerId, String name) { 
                this(); 
                this.customerId = customerId; 
                this.name = name; 
             } 
     
             public Long getCustomerId() { 
                return customerId; 
             } 
     
             public void setCustomerId(long customerId) { 
                this.customerId = customerId; 
             } 
     
             public String getName() { 
                return name; 
             } 
     
             public void setName(String name) { 
                this.name = name; 
             } 
     
             public Address getBillingAddress() { 
                return billingAddress; 
             } 
     
             public void setBillingAddress(Address billingAddress) { 
                this.billingAddress = billingAddress; 
             } 
     
             public String getPhoneNumber() { 
                return phoneNumber; 
             } 
     
             public void setPhoneNumber(String phoneNumber) { 
                this.phoneNumber = phoneNumber; 
             } 
     
             public static long getSerialversionuid() { 
                return serialVersionUID; 
             } 
     
             @Override 
             public int hashCode() { 
                final int prime = 31; 
                int result = 1; 
                result = prime * result + ((customerId == null) ? 0 : 
                customerId.hashCode()); 
                return result; 
             } 
     
             @Override 
             public boolean equals(Object obj) { 
                if (this == obj) 
                   return true; 
                if (obj == null) 
                   return false; 
                if (getClass() != obj.getClass()) 
                   return false; 
                Customer other = (Customer) obj; 
                if (customerId == null) { 
                   if (other.customerId != null) 
                      return false; 
                } else if (!customerId.equals(other.customerId)) 
                   return false; 
                return true; 
             } 
          } 
    
  5. Create one more domain class named ShippingDetail under the same package, and add the following code to it:
          package com.packt.webstore.domain; 
     
          import java.io.Serializable; 
          import java.util.Date; 
          import org.springframework.format.annotation.DateTimeFormat; 
     
          public class ShippingDetail implements Serializable{ 
     
             private static final long serialVersionUID = 6350930334140807514L; 
        
             private Long id; 
             private String name; 
             @DateTimeFormat(pattern = "dd/MM/yyyy") 
             private Date shippingDate; 
             private Address shippingAddress; 
        
             public Long getId() { 
                return id; 
             } 
        
             public void setId(long id) { 
                this.id = id; 
             } 
        
             public ShippingDetail() { 
                this.shippingAddress = new Address(); 
             } 
     
             public String getName() { 
                return name; 
             } 
     
             public void setName(String name) { 
                this.name = name; 
             } 
     
             public Date getShippingDate() { 
                return shippingDate; 
             } 
     
             public void setShippingDate(Date shippingDate) { 
                this.shippingDate = shippingDate; 
             } 
     
             public Address getShippingAddress() { 
                return shippingAddress; 
             } 
     
             public void setShippingAddress(Address shippingAddress) { 
                this.shippingAddress = shippingAddress; 
             } 
     
             @Override 
             public int hashCode() { 
                final int prime = 31; 
                int result = 1; 
                result = prime * result + ((id == null) ? 0 : id.hashCode()); 
                return result; 
             } 
     
             @Override 
             public boolean equals(Object obj) { 
                if (this == obj) 
                   return true; 
                if (obj == null) 
                   return false; 
                if (getClass() != obj.getClass()) 
                   return false; 
                ShippingDetail other = (ShippingDetail) obj; 
                if (id == null) { 
                   if (other.id != null) 
                      return false; 
                } else if (!id.equals(other.id)) 
                   return false; 
                return true; 
             } 
          } 
    
  6. Similarly, create our final domain class, named Order, under the same package, and add the following code to it:
          package com.packt.webstore.domain; 
     
          import java.io.Serializable; 
     
          public class Order implements Serializable{ 
     
             private static final long serialVersionUID = 
             -3560539622417210365L; 
        
             private Long orderId; 
             private Cart cart; 
             private Customer customer; 
             private ShippingDetail shippingDetail; 
        
             public Order() { 
                this.customer = new Customer(); 
                this.shippingDetail = new ShippingDetail(); 
             } 
     
             public Long getOrderId() { 
                return orderId; 
             } 
     
             public void setOrderId(Long orderId) { 
                this.orderId = orderId; 
             } 
     
             public Cart getCart() { 
                return cart; 
             } 
     
             public void setCart(Cart cart) { 
                this.cart = cart; 
             } 
     
             public Customer getCustomer() { 
                return customer; 
             } 
     
             public void setCustomer(Customer customer) { 
                this.customer = customer; 
             } 
     
             public ShippingDetail getShippingDetail() { 
                return shippingDetail; 
             } 
     
             public void setShippingDetail(ShippingDetail shippingDetail) { 
                this.shippingDetail = shippingDetail; 
             } 
     
             @Override 
             public int hashCode() { 
                final int prime = 31; 
                int result = 1; 
                result = prime * result + ((orderId == null) ? 0 : 
                orderId.hashCode()); 
                return result; 
             } 
     
             @Override 
             public boolean equals(Object obj) { 
                if (this == obj) 
                   return true; 
                if (obj == null) 
                   return false; 
                if (getClass() != obj.getClass()) 
                   return false; 
                Order other = (Order) obj; 
                if (orderId == null) { 
                   if (other.orderId != null) 
                      return false; 
                } else if (!orderId.equals(other.orderId)) 
                   return false; 
                return true; 
             } 
          } 
    
  7. Next, create an exception class called InvalidCartException under the com.packt.webstore.exception package in the src/main/java source folder, and add the following code to it:
          package com.packt.webstore.exception; 
     
          public class InvalidCartException extends RuntimeException { 
     
             private static final long serialVersionUID = 
             -5192041563033358491L; 
             private String cartId; 
     
             public InvalidCartException(String cartId) { 
                this.cartId = cartId; 
             } 
     
             public String getCartId() { 
                return cartId; 
             } 
          } 
    
  8. Open the CartRepository interface from the com.packt.webstore.domain.repository package, and add one more method declaration to it as follows:
             void clearCart(String cartId); 
    
  9. Now open the InMemoryCartRepository implementation class from the com.packt.webstore.domain.repository.impl package and add the following method definition to it:
          @Override 
          public void clearCart(String cartId) { 
            String SQL_DELETE_CART_ITEM = "DELETE FROM CART_ITEM WHERE CART_ID = :id"; 
         
            Map<String, Object> params = new HashMap<>();    
            params.put("id", cartId); 
           
            jdbcTempleate.update(SQL_DELETE_CART_ITEM, params);   
           } 
    
  10. Now open the CartService interface from the com.packt.webstore.service package in the src/main/java source folder and add two more method declarations to it as follows:
             Cart validate(String cartId); 
             void clearCart(String cartId); 
    
  11. Next, open the CartServiceImpl implementation class from the com.packt.webstore.service.impl package in the src/main/java source folder, and add the following method implementations to it:
             @Override 
             public Cart validate(String cartId) { 
                Cart cart = cartRepository.read(cartId); 
                if(cart==null || cart.getCartItems().size()==0) { 
                   throw new InvalidCartException(cartId); 
                }  
           
                return cart; 
             } 
     
             @Override 
             public void clearCart(String cartId) { 
                cartRepository.clearCart(cartId); 
             } 
    
  12. Next, create an interface named OrderRepository under the com.packt.webstore.domain.repository package in the src/main/java source folder, and add a single method declaration to it as follows:
          package com.packt.webstore.domain.repository; 
     
          import com.packt.webstore.domain.Order; 
     
          public interface OrderRepository { 
             long saveOrder(Order order); 
          } 
    
  13. Create an implementation class called InMemoryOrderRepository under the com.packt.webstore.domain.repository.impl package in the src/main/java source folder, and add the following code to it:
          package com.packt.webstore.domain.repository.impl; 
     
          import java.util.HashMap; 
          import java.util.Map; 
     
          import org.springframework.beans.factory
          .annotation.Autowired; 
          import org.springframework.jdbc.core.
          namedparam.MapSqlParameterSource; 
          import org.springframework.jdbc.core
          .namedparam.NamedParameterJdbcTemplate; 
          import org.springframework.jdbc.core
          .namedparam.SqlParameterSource; 
          import org.springframework.jdbc.support.GeneratedKeyHolder; 
          import org.springframework.jdbc.support.KeyHolder; 
          import org.springframework.stereotype.Repository; 
     
          import com.packt.webstore.domain.Address; 
          import com.packt.webstore.domain.Customer; 
          import com.packt.webstore.domain.Order; 
          import com.packt.webstore.domain.ShippingDetail; 
          import com.packt.webstore.domain.repository.OrderRepository; 
          import com.packt.webstore.service.CartService; 
     
          @Repository 
          public class InMemoryOrderRepository implements OrderRepository { 
     
             @Autowired 
             private NamedParameterJdbcTemplate jdbcTempleate; 
        
             @Autowired 
             private CartService CartService; 
     
             @Override 
             public long saveOrder(Order order) { 
           
                 Long customerId = saveCustomer(order.getCustomer()); 
                 Long shippingDetailId = 
                 saveShippingDetail(order.getShippingDetail()); 
            
                 order.getCustomer().setCustomerId(customerId); 
                 order.getShippingDetail().setId(shippingDetailId); 
            
                 long createdOrderId = createOrder(order); 
                 CartService.clearCart(order.getCart().getId()); 
            
                 return createdOrderId; 
             } 
        
             private long saveShippingDetail(ShippingDetail shippingDetail) { 
                long addressId = 
                saveAddress(shippingDetail.getShippingAddress()); 
           
              String SQL = "INSERT INTO 
              SHIPPING_DETAIL(NAME,SHIPPING_DATE,SHIPPING_ADDRESS_ID) " 
                      + "VALUES (:name, :shippingDate, :addressId)"; 
     
                Map<String, Object> params = new HashMap<String, Object>(); 
                params.put("name", shippingDetail.getName()); 
                params.put("shippingDate", shippingDetail.getShippingDate()); 
                params.put("addressId", addressId); 
     
                SqlParameterSource paramSource = new 
                MapSqlParameterSource(params); 
           
                KeyHolder keyHolder = new GeneratedKeyHolder(); 
                jdbcTempleate.update(SQL, paramSource,keyHolder, new 
                String[]{"ID"}); 
           
                return keyHolder.getKey().longValue(); 
             } 
     
             private long saveCustomer(Customer customer) { 
           
                long addressId = saveAddress(customer.getBillingAddress()); 
           
                String SQL = "INSERT INTO 
                CUSTOMER(NAME,PHONE_NUMBER,BILLING_ADDRESS_ID) " 
                      + "VALUES (:name, :phoneNumber, :addressId)"; 
     
                Map<String, Object> params = new HashMap<String, Object>(); 
                params.put("name", customer.getName()); 
                params.put("phoneNumber", customer.getPhoneNumber()); 
                params.put("addressId", addressId); 
     
                SqlParameterSource paramSource = new 
                MapSqlParameterSource(params); 
           
                KeyHolder keyHolder = new GeneratedKeyHolder(); 
                jdbcTempleate.update(SQL, paramSource,keyHolder, new 
                String[]{"ID"}); 
           
                return keyHolder.getKey().longValue(); 
             } 
     
             private long saveAddress(Address address) { 
                String SQL = "INSERT INTO 
             ADDRESS(DOOR_NO,STREET_NAME,AREA_NAME,STATE,COUNTRY,ZIP) " 
                    + "VALUES (:doorNo, :streetName, :areaName, :state, 
                    :country, :zip)"; 
     
                Map<String, Object> params = new HashMap<String, Object>(); 
                params.put("doorNo", address.getDoorNo()); 
                params.put("streetName", address.getStreetName()); 
                params.put("areaName", address.getAreaName()); 
                params.put("state", address.getState()); 
                params.put("country", address.getCountry()); 
                params.put("zip", address.getZipCode()); 
     
                SqlParameterSource paramSource = new 
                MapSqlParameterSource(params); 
           
                KeyHolder keyHolder = new GeneratedKeyHolder(); 
                jdbcTempleate.update(SQL, paramSource,keyHolder, new 
                String[]{"ID"}); 
           
                return keyHolder.getKey().longValue(); 
             } 
     
             private long createOrder(Order order) { 
     
                String SQL = "INSERT INTO 
                ORDERS(CART_ID,CUSTOMER_ID,SHIPPING_DETAIL_ID) " 
                      + "VALUES (:cartId, :customerId, :shippingDetailId)"; 
     
                Map<String, Object> params = new HashMap<String, Object>(); 
                params.put("id", order.getOrderId()); 
                params.put("cartId", order.getCart().getId()); 
                params.put("customerId", order.getCustomer().getCustomerId()); 
                params.put("shippingDetailId", 
                order.getShippingDetail().getId()); 
     
                SqlParameterSource paramSource = new 
                MapSqlParameterSource(params); 
           
                KeyHolder keyHolder = new GeneratedKeyHolder(); 
                jdbcTempleate.update(SQL, paramSource,keyHolder, new 
                String[]{"ID"}); 
           
                return keyHolder.getKey().longValue(); 
             } 
          } 
    
  14. Create an interface named OrderService under the com.packt.webstore.service package in the src/main/java source folder and add the following method declarations to it as follows:
          package com.packt.webstore.service; 
     
          import com.packt.webstore.domain.Order; 
     
          public interface OrderService { 
     
             Long saveOrder(Order order); 
          } 
    
  15. Create an implementation class named OrderServiceImpl for the previous interface under the com.packt.webstore.service.impl package in the src/main/java source folder and add the following code to it:
          package com.packt.webstore.service.impl; 
     
          import org.springframework.beans.factory.annotation.Autowired; 
          import org.springframework.stereotype.Service; 
     
          import com.packt.webstore.domain.Order; 
          import com.packt.webstore.domain.repository.OrderRepository; 
          import com.packt.webstore.service.OrderService; 
     
          @Service 
          public class OrderServiceImpl implements OrderService{ 
        
             @Autowired 
             private OrderRepository orderRepository; 
        
             @Override 
             public Long saveOrder(Order order) { 
                return orderRepository.saveOrder(order); 
             } 
          } 
    

What just happened?

I guess what we have done so far must already be familiar to you: we have created some domain classes (Address, Customer, ShippingDetail, and Order), an OrderRepository interface, and its implementation class, InMemoryOrderRepositoryImpl, to store processed Order domain objects. And finally, we also created the corresponding OrderService interface and its implementation class OrderServiceImpl.

On the surface, it looks the same as usual, but there are some minute details that need to be explained. If you notice, all the domain classes that we created from steps 1 to 4 have just implemented the Serializable interface; not only that, we have even implemented the Serializable interface for other existing domain classes as well, such as Product, CartItem, and Cart. This is because later we are going to use these domain objects in Spring Web Flow, and Spring Web Flow is going to store these domain objects in a session for state management between page flows.

Session data can be saved onto a disk or transferred to other web servers during clustering. So when the session object is re-imported from a disk, Spring Web Flow de-serializes the domain object (that is, the form backing bean) to maintain the state of the page. That's why it is a must to serialize the domain object/form backing bean. Spring Web Flow uses a term called Snapshot to mention these states within a session.

The remaining steps, steps 6 to 13, are self-explanatory. We have created the OrderRepository and OrderService interfaces and their corresponding implementations, InMemoryOrderRepositoryImpl and OrderServiceImpl. The purpose of these classes is to save the Order domain object. The saveOrder method from OrderServiceImpl just deletes the corresponding CartItem objects from CartRepository, after successfully saving the order domain object. Now we have successfully created all the required backend services and domain objects, in order to kick off our Spring Web Flow configuration and definition.

Time for action - implementing the checkout flow

We will now add Spring Web Flow support to our project and define the checkout flow for our shopping cart:

  1. Open pom.xml; you can find pom.xml under the root directory of the project.
  2. You will be able to see some tabs at the bottom of the pom.xml file. Select the Dependencies tab and click on the Add button of the Dependencies section.
  3. A Select Dependency window will appear; enter Group Id as org.springframework.webflow, Artifact Id as spring-webflow, Version as 2.4.2.RELEASE, select Scope as compile, click on the OK button, and save pom.xml.
  4. Create a directory structure flows/checkout/ under the src/main/webapp/WEB-INF/ directory, create an XML file called checkout-flow.xml in flows/checkout/, add the following content into it, and save it:
          <?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.xsd"> 
     
             <var name="order" class="com.packt.webstore.domain.Order" 
             /> 
     
             <action-state id="addCartToOrder"> 
                <evaluate expression="cartServiceImpl.validate
                (requestParameters.cartId)" 
                   result="order.cart" /> 
                <transition to="invalidCartWarning" 
                   on-
               exception="com.packt.webstore.exception
               .InvalidCartException" /> 
                <transition to="collectCustomerInfo" /> 
             </action-state> 
     
             <view-state id="collectCustomerInfo" 
              view="collectCustomerInfo.jsp" model="order"> 
                <transition on="customerInfoCollected" 
              to="collectShippingDetail" /> 
             </view-state> 
     
             <view-state id="collectShippingDetail" model="order"> 
                <transition on="shippingDetailCollected" 
                 to="orderConfirmation" /> 
                <transition on="backToCollectCustomerInfo" 
                 to="collectCustomerInfo" /> 
             </view-state> 
     
             <view-state id="orderConfirmation"> 
                <transition on="orderConfirmed" to="processOrder" /> 
                <transition on="backToCollectShippingDetail" 
                 to="collectShippingDetail" /> 
             </view-state> 
        
             <action-state id="processOrder"> 
                <evaluate expression="orderServiceImpl.saveOrder(order)" 
                result="order.orderId"/> 
                <transition to="thankCustomer" /> 
             </action-state> 
        
             <view-state id="invalidCartWarning"> 
                <transition to="endState"/> 
             </view-state> 
        
             <view-state id="thankCustomer" model="order"> 
                <transition to="endState"/> 
             </view-state> 
     
             <end-state id="endState"/>    
     
             <end-state id="cancelCheckout" view = "checkOutCancelled.jsp"/>       
        
             <global-transitions> 
                <transition on = "cancel" to="cancelCheckout" /> 
             </global-transitions> 
          </flow> 
    
  5. Now, create a web flow configuration class called WebFlowConfig under the com.packt.webstore.config package in the src/main/java source folder, and add the following code to it:
          package com.packt.webstore.config; 
     
          import org.springframework.context.annotation.Bean; 
          import org.springframework.context.annotation.Configuration; 
          import org.springframework.webflow.config 
          .AbstractFlowConfiguration; 
          import org.springframework.webflow.definition
          .registry.FlowDefinitionRegistry; 
          import org.springframework.webflow.executor.FlowExecutor; 
          import org.springframework.webflow.mvc.servlet.FlowHandlerAdapter; 
          import org.springframework.webflow.mvc.servlet.FlowHandlerMapping; 
     
          @Configuration 
          public class WebFlowConfig extends AbstractFlowConfiguration { 
     
             @Bean 
             public FlowDefinitionRegistry flowRegistry() { 
                 return getFlowDefinitionRegistryBuilder() 
                         .setBasePath("/WEB-INF/flows") 
                         .addFlowLocationPattern("/**/*-flow.xml") 
                         .build(); 
              
             } 
        
             @Bean 
             public FlowExecutor flowExecutor() { 
                 return getFlowExecutorBuilder(flowRegistry()).build(); 
             } 
        
        
             @Bean 
             public FlowHandlerMapping flowHandlerMapping() { 
                 FlowHandlerMapping handlerMapping = new FlowHandlerMapping(); 
                 handlerMapping.setOrder(-1); 
                 handlerMapping.setFlowRegistry(flowRegistry()); 
                 return handlerMapping; 
             } 
     
             @Bean 
             public FlowHandlerAdapter flowHandlerAdapter() { 
                 FlowHandlerAdapter handlerAdapter = new FlowHandlerAdapter(); 
                 handlerAdapter.setFlowExecutor(flowExecutor()); 
                 handlerAdapter.setSaveOutputToFlashScopeOnRedirect(true); 
                 return handlerAdapter; 
                 } 
          } 
    

What just happened?

From steps 1 to 3, we just added the Spring Web Flow dependency to our project through Maven configuration. It will download and configure all the required web flow-related JARs for our project. In step 4, we created our first flow definition file, called checkout-flow.xml, under the /src/main/webapp/WEB-INF/flows/checkout/ directory.

Spring Web Flow uses the flow definition file as a basis for executing the flow. In order to understand what has been written in this file, we need to get a clear idea of some of the basic concepts of Spring Web Flow. We will learn about those concepts in a little bit, and then we will come back to checkout-flow.xml to understand it better.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.168.83