2017年12月26日 星期二

Java Spring - 建立一個pizza flow


  • Define basic flow
    • State:
      • Start 
      • Identify Customer ->customerReady
      • buildOrder -> orderCreated
      • takePayment ->paymentTaken
      • saveOrder
      • thankCustomer
      • endState // end-state
    • 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.3.xsd">
        <var name="order"
          class="com.springinaction.pizza.domain.Order" />
        <subflow-state id="identifyCustomer" subflow="pizza/customer"> //第一個
          <output name="customer" value="order.customer" />
          <transition on="customerReady" to="buildOrder" />
        </subflow-state>
        <subflow-state id="buildOrder" subflow="pizza/order">
          <input name="order" value="order" /> // 就像Java method,需要order parameter
          <transition on="orderCreated" to="takePayment" />
        </subflow-state>
        <subflow-state id="takePayment" subflow="pizza/payment">
          <input name="order" value="order" />  // 就像Java method,需要order parameter
          <transition on="paymentTaken" to="saveOrder" />
        </subflow-state>
        <action-state id="saveOrder">
          <evaluate expression="pizzaFlowActions.saveOrder(order)" />
          <transition to="thankCustomer" />
        </action-state>
        <view-state id="thankCustomer">
          <transition to="endState" />
        </view-state>
        <end-state id="endState" />
        <global-transitions>
          <transition on="cancel" to="endState" />
        </global-transitions>
      </flow>
      
    • order class:
    • package com.springinaction.pizza.domain;
      
      import java.io.Serializable;
      import java.util.ArrayList;
      import java.util.List;
      
      public class Order implements Serializable {
          private static final long serialVersionUID = 1L;
          private Customer customer;
          private List<Pizza> pizzas;
          private Payment payment;
      
          public Order() {
              pizzas = new ArrayList<Pizza>();
              customer = new Customer();
          }
      
          public Customer getCustomer() {
              return customer;
          }
      
          public void setCustomer(Customer customer) {
              this.customer = customer;
          }
      
          public List<Pizza> getPizzas() {
              return pizzas;
          }
      
          public void setPizzas(List<Pizza> pizzas) {
              this.pizzas = pizzas;
          }
      
          public void addPizza(Pizza pizza) {
              pizzas.add(pizza);
          }
      
          public float getTotal() {
              return 0.0f;
          }
      
          public Payment getPayment() {
              return payment;
          }
      
          public void setPayment(Payment payment) {
              this.payment = payment;
          }
      }
      
    • flow definition的主要部分是flow state。default下,flow definition file中的第一個state也是flow的第一個state,本例中也就是identifyCustomer。也可以透過<flow>的start-state將任意state指定為開始state
    • <?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.3.xsd" 
      start-state="identifyCustomer">
      ... </flow>
      
    • thankCustomer是一個view state,使用了/WEB- INF/flows/pizza/thankCustomer.jsp
    • <html xmlns:jsp="http://java.sun.com/JSP/Page">
      <jsp:output omit-xml-declaration="yes" />
      <jsp:directive.page contentType="text/html;charset=UTF-8" />
      <head>
      <title>Spizza</title>
      </head>
      <body>
        <h2>Thank you for your order!</h2>
        <![CDATA[
          <a href='${flowExecutionUrl}&_eventId=finished'>Finish</a> // 觸發結束事件,以變回到web flow時觸發finished event
          ]]>
      </body>
      </html>
      
  • Collect customer information
    • 如圖,可看出有很多flow分支
    • 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.3.xsd">
        <var name="customer" class="com.springinaction.pizza.domain.Customer" />
        <view-state id="welcome">
          <transition on="phoneEntered" to="lookupCustomer" />
        </view-state>
        <action-state id="lookupCustomer">
          <evaluate result="customer"
            expression="pizzaFlowActions.lookupCustomer(requestParameters.phoneNumber)" />
          <transition to="registrationForm"
            on-exception="com.springinaction.pizza.service.CustomerNotFoundException" />
          <transition to="customerReady" />
        </action-state>
          <view-state id="registrationForm" model="customer">
            <on-entry>
              <evaluate expression="
                customer.phoneNumber=requestParameters.phoneNumber " />
            </on-entry>
            <transition on="submit" to="checkDeliveryArea" />
          </view-state>
          <decision-state id="checkDeliveryArea">
            <if test="pizzaFlowActions.checkDeliveryArea(customer.zipCode)"
              then="addCustomer" else="deliveryWarning" />
          </decision-state>
          <view-state id="deliveryWarning">
            <transition on="accept" to="addCustomer" />
          </view-state>
          <action-state id="addCustomer">
            <evaluate expression="pizzaFlowActions.addCustomer(customer)" />
            <transition to="customerReady" />
          </action-state>
          <end-state id="cancel" />
          <end-state id="customerReady">
            <output name="customer" /> // out就像java的return,如此原flow也能使用
          </end-state>
          <global-transitions>
            <transition on="cancel" to="cancel" />
          </global-transitions>
      </flow>
      
  • welcome 
    • JSP: /WEB-INF/flows/pizza/customer/welcome.jspx
    • <html xmlns:jsp="http://java.sun.com/JSP/Page"
        xmlns:form="http://www.springframework.org/tags/form">
      <jsp:output omit-xml-declaration="yes" />
      <jsp:directive.page contentType="text/html;charset=UTF-8" />
      <head>
      <title>Spizza</title>
      </head>
      <body>
        <h2>Welcome to Spizza!!!</h2>
        <form:form>
          <input type="hidden" name="_flowExecutionKey"
            value="${flowExecutionKey}" />
          <input type="text" name="phoneNumber" />
          <br />
          <input type="submit" name="_eventId_phoneEntered"
            value="Lookup Customer" />
        </form:form>
      </body>
      </html>
      
      • 當進入view state時,flow暫停並且等待user採取行動。view的flow execution key就是一種返回flow的claim ticket回程票
      • 當uset 提交form時,flow execution key會在_flowExecutionKey input field中return並且在flow暫停的位置繼續執行
      • submit name"_eventId_phoneEntered""是提供給Spring Web Flow的線索,表明接下來要發生的event。當點擊後,會觸發on="phoneEntered" 接著會轉移到to="lookupCustomer"
  • lookup customers
    • lookupCustomer的<evaluate>是lookup發生的地方
      • requestParameters.phoneNumber是phoneNumber
      • result是customer(放Customer object),並transition to customerReady;若發生exception則會導到registrationForm
  • registering a new customer
    • registrationForm  JSP
    • <html xmlns:c="http://java.sun.com/jsp/jstl/core"
           xmlns:jsp="http://java.sun.com/JSP/Page"
           xmlns:spring="http://www.springframework.org/tags"
           xmlns:form="http://www.springframework.org/tags/form">
        <jsp:output omit-xml-declaration="yes"/>
        <jsp:directive.page contentType="text/html;charset=UTF-8" />
        <head><title>Spizza</title></head>
        <body>
          <h2>Customer Registration</h2>
          <form:form commandName="customer">
            <input type="hidden" name="_flowExecutionKey"
                   value="${flowExecutionKey}"/>
            <b>Phone number: </b><form:input path="phoneNumber"/><br/>
            <b>Name: </b><form:input path="name"/><br/>
            <b>Address: </b><form:input path="address"/><br/>
            <b>City: </b><form:input path="city"/><br/>
            <b>State: </b><form:input path="state"/><br/>
            <b>Zip Code: </b><form:input path="zipCode"/><br/>
            <input type="submit" name="_eventId_submit"
                   value="Submit" />
            <input type="submit" name="_eventId_cancel"
                   value="Cancel" />
          </form:form>
          </body>
      </html>
      
  • Checking delivery area
    • 如果是pizzaFlowActions.checkDeliveryArea(customer.zipCode)在範圍內,則flow會到addCusomer,否則會到deliveryWarning。而deliveryWarning是一個view state
    • JSP: /WEB-INF/flows/pizza/customer/deliveryWarning.jspx
    • <html xmlns:jsp="http://java.sun.com/JSP/Page">
      <jsp:output omit-xml-declaration="yes" />
      <jsp:directive.page contentType="text/html;charset=UTF-8" />
      <head>
      <title>Spizza</title>
      </head>
      <body>
        <h2>Delivery Unavailable</h2>
        <p>The address is outside of our delivery area. You may still
          place the order, but you will need to pick it up yourself.</p>
        <![CDATA[
              <a href="${flowExecutionUrl}&_eventId=accept">
                                      Continue, I'll pick up the order</a> |
              <a href="${flowExecutionUrl}&_eventId=cancel">Never mind</a>
      ]]>
      </body>
      </html>
      
    • 若evnetId=accept,則會到addCustomer。否則,直接到cancel結束state
  • add Customer data
    • call pizzaFlowActions.addCustomer(customer),並將customer傳入,接著flow到customerReady
  • ending flow
    • 結束後,會轉至customerReady,並從這個subflow回到原來的flow,導致整個flow接回buildOrder
  • building an order
  • from Spring in action 4
    • 識別完customer以後,主flow會開始確定需要什麼type的pizza,order subflow就是用來提示user建立pizza並放入order
    • 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">
      
          <input name="order" required="true" />
          
          <!-- Order -->
          <view-state id="showOrder">
              <transition on="createPizza" to="createPizza" />
              <transition on="checkout" to="orderCreated" />
              <transition on="cancel" to="cancel" />
          </view-state>
      
          <view-state id="createPizza" model="flowScope.pizza">
              <on-entry> //新增一個new pizza object到flowScope
                <set name="flowScope.pizza" 
                    value="new com.springinaction.pizza.domain.Pizza()" />
                    
                <evaluate result="viewScope.toppingsList" 
                    expression="T(com.springinaction.pizza.domain.Topping).asList()" />
              </on-entry>
              <transition on="addPizza" to="showOrder"> // addPizza時會將flowScope.pizza新增到order內
                <evaluate expression="order.addPizza(flowScope.pizza)" />
              </transition>
              <transition on="cancel" to="showOrder" />
          </view-state>
      
              
          <!-- End state -->
          <end-state id="cancel" />
          <end-state id="orderCreated" />
      </flow>
      
    • CreatePizza.jsp
    • <div xmlns:form="http://www.springframework.org/tags/form"
        xmlns:jsp="http://java.sun.com/JSP/Page">
        <jsp:output omit-xml-declaration="yes" />
        <jsp:directive.page contentType="text/html;charset=UTF-8" />
        <h2>Create Pizza</h2>
        <form:form commandName="pizza">
          <input type="hidden" name="_flowExecutionKey"
            value="${flowExecutionKey}" />
          <b>Size: </b>
          <br />
          <form:radiobutton path="size" label="Small (12-inch)" value="SMALL" />
          <br />
          <form:radiobutton path="size" label="Medium (14-inch)"
            value="MEDIUM" />
          <br />
          <form:radiobutton path="size" label="Large (16-inch)" value="LARGE" />
          <br />
          <form:radiobutton path="size" label="Ginormous (20-inch)"
            value="GINORMOUS" />
          <br />
          <br />
          <b>Toppings: </b>
          <br />
          <form:checkboxes path="toppings" items="${toppingsList}"
            delimiter="&lt;br/&gt;" />
          <br />
          <br />
          <input type="submit" class="button" name="_eventId_addPizza"
            value="Continue" />
          <input type="submit" class="button" name="_eventId_cancel"
            value="Cancel" />
        </form:form>
      </div>
      
  • Taking payment
  • from Spring in action 4
    • payment-flow.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.3.xsd">
        <input name="order" required="true" />
        <view-state id="takePayment" model="flowScope.paymentDetails">
          <on-entry>
            <set name="flowScope.paymentDetails"
              value="new com.springinaction.pizza.domain.PaymentDetails()" />
            <evaluate result="viewScope.paymentTypeList"
              expression="T(com.springinaction.pizza.domain.PaymentType).asList()" />
          </on-entry>
          <transition on="paymentSubmitted" to="verifyPayment" />
          <transition on="cancel" to="cancel" />
        </view-state>
        <action-state id="verifyPayment">
          <evaluate result="order.payment"
            expression="pizzaFlowActions.verifyPayment(flowScope.paymentDetails)" />
          <transition to="paymentTaken" />
        </action-state>
        <end-state id="cancel" />
        <end-state id="paymentTaken" />
      </flow>
      
    • payment type:
    • package com.springinaction.pizza.domain;
      import static org.apache.commons.lang.WordUtils.*;
      import java.util.Arrays;
      import java.util.List;
      public enum PaymentType {
        CASH, CHECK, CREDIT_CARD;
        public static List<PaymentType> asList() {
          PaymentType[] all = PaymentType.values();
        }
        @Override
        public String toString() {
          return capitalizeFully(name().replace('_', ' '));
        }
      }
      
  • Securing web flows
    • 使用<secured>能夠implement security
    • <view-state id="restricted">
        <secured attributes="ROLE_ADMIN" match="all"/>
      </view-state>
      
    • 只有role是admin才能access view right
    • attributes的設定可以用逗號分隔
    • 若match設定為any,則user必須至少有一個attributes的right;若是any,則user必須有所有attributes

沒有留言:

張貼留言