SWF 5장 액션 실행하기

5.1. 소개

이번 장에서는 action-state 엘리먼트를 사용하여 플로우 내에서 특정 시저에 액션 실행을 제어하는 방법을 살펴본다. 또한 decision-state 엘리먼트를 사용하여 플로우 방향을 결정하는 방법도 살펴본다. 마지막으로 플로우 내에서 가능한 다양한 지점에서 액션을 호출하는 예제를 다룰 것이다.

5.2. 액션 스테이트 정의하기

액션을 호출하고 싶을 때 action-state 엘리먼트를 사용하면 액션의 결과에 기반하여 다른 상태로 전이한다.

<action-state id=”moreAnswersNeeded”>
    <evaluate expression=”interview.moreAnswersNeeded()” />
    <transition on=”yes” to=”answerQuestions” />
    <transition on=”no” to=”finish” />
</action-state>

위의 action-state를 사용하여 인터뷰를 완성하는데 더 많은 질문이 필요한지 확인하는 인터뷰 플로우는 다음과 같다.

<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>
        <evaluate expression=”interviewFactory.createInterview()” result=”flowScope.interview” />
    </on-start>

    <view-state id=”answerQuestions” model=”questionSet”>
        <on-entry>
            <evaluate expression=”interview.getNextQuestionSet()” result=”viewScope.questionSet” />
        </on-entry>
        <transition on=”submitAnswers” to=”moreAnswersNeeded”>
            <evaluate expression=”interview.recordAnswers(questionSet)” />
        </transition>
    </view-state>
   
    <action-state id=”moreAnswersNeeded”>
        <evaluate expression=”interview.moreAnswersNeeded()” />
        <transition on=”yes” to=”answerQuestions” />
        <transition on=”no” to=”finish” />
    </action-state>

    <end-state id=”finish” />
   
</flow>

5.3. 의사결정 상태 정의하기

decision-state 엘리먼트를 action-state 대신 사용하여 편리한 if/else 문법을 사용하여 경로 결정을 할 수 있다. 아래 예제는 위에서 봤던 moreAnswersNeeded 상태를 action-state 대신 의사결정 상태로 구현한 것이다.

<decision-state id=”moreAnswersNeeded”>
    <if test=”interview.moreAnswersNeeded()” then=”answerQuestions” else=”finish” />
</decision-state>
           
5.4. 액션 결과 이벤트 맵핑

액션은 보통 일반 자바 객체의 매서드를 호출한다. action-state와 decision-state에서 호출한 매서드는 뷰 상태를 전이할 때 사용할 값을 반환한다. 전이는 이벤트에 의해 발생하기 때문에 매서드의 반환값은 반드시 먼저 Event 객체로 맵핑되어야 한다. 다음 표는 흔한 반환값을 어떻게 Event 객체로 맵핑하는지 보여준다.

표 5.1. 액션 매서드 반환 값을 이벤트 id로 맵핑하기

|| 매서드 반환 값 || 맵핑한 이벤트 식별자 표현식 ||
| java.lang.String | 문자열 값 |
| java.lang.Boolean | (true 면) yes, (false 면) no |
| java.lang.Enum | Enum 이름 |
| 그밖에 다른 타입 | success |

아래 예제의 액션 스테이트에서 매서드가 boolean 값을 반환한다는 것을 알 수 있다.

<action-state id=”moreAnswersNeeded”>
    <evaluate expression=”interview.moreAnswersNeeded()” />
    <transition on=”yes” to=”answerQuestions” />
    <transition on=”no” to=”finish” />
</action-state>
       
5.5. 액션 구현

액션 코드를 POJO 로직으로 작성하는 것이 가장 일반적이지만, 다른 액션 구현 방법도 있다. 가끔은 액션 코드에서 플로우 컨텍스트에 접속할 필요가 있을 것이다. 이 때 POJO를 호출하고 거기에 flowRequestContext를 EL 변수로 넘겨줄 수 있다. 또는, Action 인터페이스를 구현하거나 MultiAction 기본 클래스를 상속받을 수도 있다. 이러한 대안들이 보다 강한 타입 안전성을 제공하여 여러분의 액션 코드와 스프링 웹 플로우 API 사이를 자연스럽게 연결해준다. 아래에서 이들 각각에 대한 예제를 살펴보자.

5.5.1. POJO 액션 호출하기

<evaluate expression=”pojoAction.method(flowRequestContext)” />   
           

public class PojoAction {
    public String method(RequestContext context) {
        …
    }
}
           
5.5.2. 커스텀 Action 구현체 호출하기

<evaluate expression=”customAction” />   
           

public class CustomAction implements Action {
    public Event execute(RequestContext context) {
        …
    }
}
           
5.5.3. MultiAction 구현체 호출하기

<evaluate expression=”multiAction.actionMethod1″ />
   
           

public class CustomMultiAction extends MultiAction {
    public Event actionMethod1(RequestContext context) {
        …
    }

    public Event actionMethod2(RequestContext context) {
        …
    }

    …
}
           
5.6. 액션 예외

액션은 보통 복잡한 비즈니스 로직을 캡슐화한 서비스를 호출한다. 이러한 서비스는 액션 코드가 처리해야 할 비즈니스 에외를 던질 수도 있다.

5.6.1. 비즈니스 예외를 POJO 액션에서 처리하기

다름 예제는 비즈니스 예외를 잡는 액션을 호출하고, 에러 메시지를 컨텍스트에 추가하고, 결과 이벤트 식별자를 반환한다. 결과는 호출한 플로우가 반응할 수 있는 플로우 이벤트로 간주한다.

<evaluate expression=”bookingAction.makeBooking(booking, flowRequestContext)” />   
           

public class BookingAction {
   public String makeBooking(Booking booking, RequestContext context) {
       try {
           BookingConfirmation confirmation = bookingService.make(booking);
           context.getFlowScope().put(“confirmation”, confirmation);
           return “success”;
       } catch (RoomNotAvailableException e) {
           context.addMessage(new MessageBuilder().error().
               .defaultText(“No room is available at this hotel”).build());
           return “error”;
       }
   }
}
           
5.6.2. 비즈니스 예외를 MultiAction으로 처리하기

다음 예제는 기능적으로 위에 것과 같지만 POJO 액션이 아니라 MultiAction으로 구현했다. MultiAction은 ${methodName}(RequestContext) 형태의 액션 매서드를 필요로 하며 강한 타입 안전성을 제공한다. 반면 POJO 액샨은 더 많은 자유를 제공한다.

<evaluate expression=”bookingAction.makeBooking” />   
           

public class BookingAction extends MultiAction {
   public Event makeBooking(RequestContext context) {
       try {
           Booking booking = (Booking) context.getFlowScope().get(“booking”);
           BookingConfirmation confirmation = bookingService.make(booking);
           context.getFlowScope().put(“confirmation”, confirmation);
           return success();
       } catch (RoomNotAvailableException e) {
           context.getMessageContext().addMessage(new MessageBuilder().error().
               .defaultText(“No room is available at this hotel”).build());
           return error();
       }
   }
}
           
5.7. 기타 액션 호출 예제

5.7.1. on-start

다음 예제는 서비스의 매서드를 호출하여 새로운 Booking 객체를 만드는 액션을 보여준다.

<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=”hotelId” />

    <on-start>
        <evaluate expression=”bookingService.createBooking(hotelId, currentUser.name)”
                  result=”flowScope.booking” />
    </on-start>

</flow>
   

5.7.2. on-entry

다음 예제는 상태 진입 액션으로 특별한 fragments 변수를 정의하여 view-state가 뷰의 일부만 랜더링 하도록 하고 있다.

<view-state id=”changeSearchCriteria” view=”enterSearchCriteria.xhtml” popup=”true”>
    <on-entry>
        <render fragments=”hotelSearchForm” />
    </on-entry>
</view-state>
           

5.7.3. on-exit

다음 예제는 상태 종료(exit) 액션으로 편집중이던 레코드에 대한 롹을 해제하고 있다.

<view-state id=”editOrder”>
    <on-entry>
        <evaluate expression=”orderService.selectForUpdate(orderId, currentUser)”
                  result=”viewScope.order” />
    </on-entry>
    <transition on=”save” to=”finish”>
        <evaluate expression=”orderService.update(order, currentUser)” />
    </transition>
    <on-exit>
        <evaluate expression=”orderService.releaseLock(order, currentUser)” />
    </on-exit>
</view-state>

5.7.4. on-end

다음 예제는 위와 같은 객체 롹킹을 플로우 시작과 종료(end) 액션으로 하는 것을 보여준다.

<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=”orderId” />

    <on-start>
        <evaluate expression=”orderService.selectForUpdate(orderId, currentUser)”
                  result=”flowScope.order” />
    </on-start>

    <view-state id=”editOrder”>
        <transition on=”save” to=”finish”>
            <evaluate expression=”orderService.update(order, currentUser)” />
        </transition>
    </view-state>

    <on-end>
        <evaluate expression=”orderService.releaseLock(order, currentUser)” />
    </on-end>
   
</flow>

5.7.5. on-render

다음 예제는 랜더 액션으로 뷰를 랜더링 하기 전에 보여줄 호텔 목록을 로딩한다.

<view-state id=”reviewHotels”>
    <on-render>
        <evaluate expression=”bookingService.findHotels(searchCriteria)”
                  result=”viewScope.hotels” result-type=”dataModel” />
    </on-render>
    <transition on=”select” to=”reviewHotel”>
        <set name=”flowScope.hotel” value=”hotels.selectedRow” />
    </transition>
</view-state>
           
5.7.6. on-transition

다음 예제는 하위플로우 결과 이벤트 속성을 컬렉션에 추가하는 트랜지션 액션을 보여준다.

<subflow-state id=”addGuest” subflow=”createGuest”>
    <transition on=”guestCreated” to=”reviewBooking”>
        <evaluate expression=”booking.guestList.add(currentEvent.attributes.newGuest)” /> 
    </transition>
</subfow-state>
           
5.7.7. Names actions

다음 예제는 하나의 action-state 내에서 액션 체인을 호출하는 방법을 보여준다. 각각의 액션 이름은 액션 결과 이벤트에 대한 식별자가 된다.

<action-state id=”doTwoThings”>
    <evaluate expression=”service.thingOne()”>
        <attribute name=”name” value=”thingOne” />
    </evaluate>
    <evaluate expression=”service.thingTwo()”>
        <attribute name=”name” value=”thingTwo” />
    </evaluate>
    <transition on=”thingTwo.success” to=”showResults” />
</action-state>
       
이 예제에서, 플로우는 thingTwo가 무사히 완료되면 showResults로 전이할 것이다.

SWF 4장 뷰 랜더링

4. 뷰 랜더링

4.1. 소개

이번 장에서는 view-state 엘리먼트를 사용하여 플로우에서 뷰를 랜더링하는 방법을 살펴본다.

4.2. 뷰 상태 정의하기

view-state 엘리먼트를 사용하여 뷰를 랜더링하고 사용자 이벤트를 대기하는 플로우 스탭을 정의한다.

<view-state id=”enterBookingDetails”>
    <transition on=”submit” to=”reviewBooking” />
</view-state>
       
규약에 따라, view-state는 자신의 id를 flow가 위치한 디렉터리에서 뷰 템플릿으로 맵핑한다. 예를 들어, 만약에 flow 자체가 /WEB-INF/hotels/booking에 있다면 위의 state는 /WEB-INF/hotels/booking/enterBookingDetails.xhtml을 랜더링할 것이다.

아래는 예제 디렉터리 구조로 뷰와 메시지 번들 같은 기타 리소스가 그들의 플로우 정의와 같이 있는 것을 보여준다.

사용자 삽입 이미지
4.3. 뷰 식별자 작성하기

view 속성을 사용하여 랜더링 할 뷰를 명시적으로 기입한다.

4.3.1. 플로우 상대 뷰 id

뷰 id는 플로우가 있는 작업 디렉터리에 위치한 뷰 리소스에 대한 상대 경로가 될 수 있다.

<view-state id=”enterBookingDetails” view=”bookingDetails.xhtml”>
           
4.3.2. 절대 뷰 id

뷰 id는 웹 애플리케이션 루트 디렉터리에 있는 뷰의 절대 경로가 될 수도 있다.

<view-state id=”enterBookingDetails” view=”/WEB-INF/hotels/booking/bookingDetails.xhtml”>
           
4.3.3. 논리 뷰 id

스프링 MVC의 뷰 프레임워크 같은 몇몇 뷰 프레임워크에서 뷰 id는 프레임워크가 판단할 떄 사용할 논리적인 식별자가 될 수도 있다.

<view-state id=”enterBookingDetails” view=”bookingDetails”>
           
스프링 MVC 연동 뿐에서 어떻게 MVC ViewResolver 기반 시설과 연동하는지 더 많은 정보를 살펴보도록 하자.

4.4. 뷰 스코프

view-state는 들어올 때마다 새로운 viewScope을 할당한다. 이 스코프는 Ajax 요청처럼 같은 뷰에서 여러 요청에 걸쳐 객체를 조작할 때 유용하다. view-state는 해당 state를 나갈 때 viewScope을 소멸한다.

4.4.1. 뷰 변수 할당하기

var 태그를 사용하여 뷰 변수를 선언한다. 플로우 변수처럼, 뷰 상태가 다시 시작하면  모든 @Autowired 레퍼런스를 자동으로 복원한다.

<var name=”searchCriteria” class=”com.mycompany.myapp.hotels.SearchCriteria” />
           
4.4.2. viewScope 변수 할당하기

on-render 태그를 사용하여 뷰 랜더링하기 전에 액션 결과에 변수를 할당한다.

<on-render>
    <evaluate expression=”bookingService.findHotels(searchCriteria)” result=”viewScope.hotels” />
</on-render>
           

4.4.3. 뷰 스코프에 있는 객체 조작하기

뷰 스코프에 있는 객체는 보통 동일한 뷰에서 여러 요청에 의해 조작된다. 다음 예제 페이지는 검색 결과 목록을 보여준다. 목록은 매번 랜더링 하기 전에 뷰 스코프에서 수정된다. 비동기 이벤트 핸들러가 현재 데이터 페이지를 수정하고 검색 결과 부분에 대한 요청을 다시 랜더링 한다.

<view-state id=”searchResults”>
    <on-render>
        <evaluate expression=”bookingService.findHotels(searchCriteria)”
                  result=”viewScope.hotels” />
    </on-render>
    <transition on=”next”>
        <evaluate expression=”searchCriteria.nextPage()” />
        <render fragments=”searchResultsFragment” />           
    </transition>
    <transition on=”previous”>
        <evaluate expression=”searchCriteria.previousPage()” />
        <render fragments=”searchResultsFragment” />         
    </transition>
</view-state>
           
4.5. render 액션 실행하기

on-render 엘리먼트를 사용하여 뷰 랜더링을 하기전에 한 개 이상의 액션을 실행한다. 뷰의 일부만 다시 랜더링하는 것을 포함하여 랜더 액션은 이후의 리프래시와 마찬가지로 초기 랜더링 단계에 실행된다.

<on-render>
    <evaluate expression=”bookingService.findHotels(searchCriteria)” result=”viewScope.hotels” />
</on-render>
       
4.6. 모델로 바인딩하기

model 속성을 사용하여 뷰를 바인딩할 모델 객체를 선언한다. 이 속성은 보통 폼 같은 데이터 컨트롤을 랜더링하는 뷰랑 같이 사용한다. 폼 데이터 바인딩과 검증 작업을 모델 객체에 대한 메타데이터로 주도할 수 있다.

다음 예는 booking 모델을 다루는 enterBookingDetails State다.

<view-state id=”enterBookingDetails” model=”booking”>

모델은 flowScope 또는 viewScope 같이 접근 가능한 모든 스코프에 있는 객체다. model을 정의하면 뷰 이벤트가 발생할 때 다음 작업이 이뤄진다.

  1. 뷰에서 모델로 바인딩. 뷰 포스트백(postback)을 할 때 사용자 입력 값을 모델 객체 속성으로 바인딩한다.
  2. 모델 검증. 바인딩을 한 다음, 만약 모델 객체가 검증을 필요로 하면, 검증 로직을 호출한다.

뷰 상태 전이를 할 수 있는 플로우 이벤트가 발생할 때, 모델 바인딩은 반드시 성공적으로 완료되어야 한다. 만약 바인딩이 실패하면, 뷰는 다시 랜더링하여 사용자가 다시 편집할 수 있게 한다.

4.7. 타입 변환 수행하기

뷰 포스트백을 할 때 모델 바인딩이 발생하면, 바인딩 시스템은 필요하다면 입력 값을 타겟 모델 속성 타입으로 변환을 시도할 것이다. 숫자들과 기본타입, 이넘, 그리고 Date에 대한 기본 컨버터(Converter)는 자동으로 적용된다. 사용자는 또한 자신들이 직접 정의한 타입에 대한 컨버터를 등록할 수 있고 기본 컨버터를 재정의 할 수도 있다.

4.7.1. 컨버터 구현하기

컨버터를 구현하려면, org.springframework.binding.convert.converters.TwoWayConverter 인터페이스를 구현하라. 입력값으로 문자열을 받아서 사용자가 정의한 객체로 변경하는 컨버터를 만들 때 이 인터페이스 구현을 편리하게 해주는 기본 클래스 StringToObject를 제공한다. 간단하게 이 클래슬르 상속하고 두 개의 매서드를 재정의하면 된다.

protected abstract Object toObject(String string, Class targetClass) throws Exception;

protected abstract String toString(Object object) throws Exception;           
          
toObject(String, Class)는 입력 문자열을 객체 타입으로 바꿔야 하고 toString(Object)는 그 반대 일을 해야 한다.

다음 예제는 문자열을 MonetaryAmount로 변환하는 컨버터다.

public class StringToMonetaryAmount extends StringToObject {

  public StringToMonetaryAmount() {
      super(MonetaryAmount.class);
  }

  @Override
  protected Object toObject(String string, Class targetClass) {
      return MonetaryAmount.valueOf(string);
  }

  @Override
  protected String toString(Object object) {
      MonetaryAmount amount = (MonetaryAmount) object;
      return amount.toString();
  }
}           
  
더 많은 컨버터 구현 예제를 보고 싶다면 org.springframework.binding.convert.converters 패키지에 미리 만들어둔 컨버터들을 참고하라.

4.7.2. 컨버터 등록하기

여러분이 작성한 컨버터를 등록하거나 기본 컨버터를 변경하고 싶다면, org.springframework.binding.convert.service.DefaultConversionService를 상속하고 addDefaultConverters() 매서드를 재정의 하라. addConverter(Converter) 매서드를 사용하여 String과 MonetaryAmount 같은 두 타입을 변환할 때 사용할 주요 컨버터(Primary Converter)를 등록하라. 부가적으로 addConverter(String, Converter)를 사용하여 같은 타입에 대한 여러 대체 컨버터(Alternate Converter)를 등록할 수 있다. 예를 들어, java.util.Date를 여러 형태의 문자열로 변환할 수 있겠다.

대체 컨버터는 유일한 converterId로 인덱싱을 하는데 모델 바인딩을 설정할 때 참조할 수 있다. converterId를 지정하지 않으면 주요 컨버터를 사용한다.

ConversionService는 웹 플로우가 런타임에 어떤 타입을 다른 타입으로 변환 해주는 변환 실행기를 룩업할 때 사용하는 객체다. 애플리케이션 당 하나의 ConversionService가 있다. System Setup  절을 참조하여 애플리케이션 전반에 걸쳐 커스텀 컨버터를 등록하는 ConversionService 구현체를 설정하는 방법을 살펴보라. 또한 자세한 내용은 Convert API를 참고하라.

4.8. 바인딩 방지하기

bind 속성을 사용하여 특정 뷰 이벤트에 대한 검증과 모델 바인딩을 방지할 수 있다. 다음 예는 cancel 이벤트가 발생했을 때 바인딩을 방지한다.

<view-state id=”enterBookingDetails” model=”booking”>
    <transition on=”proceed” to=”reviewBooking”>
    <transition on=”cancel” to=”bookingCancelled” bind=”false” />
</view-state>

4.9. 명시적으로 바인딩 설정하기

binder 엘리먼트를 사용하여 뷰에서 사용할 수 있는 모델 바인딩 집합을 설정할 수 있다. 이것은 특히 스프링 MVC 환경에서 뷰 마다 “허용하는 입력값” 집합을 제한할 때 유용하다.

<view-state id=”enterBookingDetails” model=”booking”>
    <binder>
        <binding property=”creditCard” />
        <binding property=”creditCardName” />
        <binding property=”creditCardExpiryMonth” />
        <binding property=”creditCardExpiryYear” />
    </binder>
    <transition on=”proceed” to=”reviewBooking” />
    <transition on=”cancel” to=”cancel” bind=”false” />
</view-state>

 만약에 binder 엘리먼트를 설정하지 않으면 모델의 모든 public 속성이 뷰에서 바인딩하는 대상이 된다. binder 엘리먼트를 사용하여 오직 명시적으로 표현한 속성만 바인딩 한다.

각각의 바인딩은 컨버터를 적용하여 모델 속성 값을 사용자에게 익숙한 형태로 변경할 수 있을 것이다. 만약 컨버터를 명시하지 않으면 모델 속성 타입에 따라 기본 컨버터를 사용하게 된다.

<view-state id=”enterBookingDetails” model=”booking”>
    <binder>
        <binding property=”checkinDate” converter=”shortDate” />
        <binding property=”checkoutDate” converter=”shortDate” />   
        <binding property=”creditCard” />
        <binding property=”creditCardName” />
        <binding property=”creditCardExpiryMonth” />
        <binding property=”creditCardExpiryYear” />
    </binder>
    <transition on=”proceed” to=”reviewBooking” />
    <transition on=”cancel” to=”cancel” bind=”false” />
</view-state>

위의 예제에서 shortDate 컨버터를 chekingDate와 checkoutDate 속성에 적용하고 있다. 커스텀 컨버터를 애플리케이션의 ConventionService로 등록할 수도 있다.

각각의 바인딩은 또한 필수 여부를 확인하여 사용자가 입력한 값이 비어있다면 검증 에러를 발생시키도록 할 수 있다.

<view-state id=”enterBookingDetails” model=”booking”>
    <binder>
        <binding property=”checkinDate” converter=”shortDate” required=”true” />
        <binding property=”checkoutDate” converter=”shortDate” required=”true” />
        <binding property=”creditCard” required=”true” />
        <binding property=”creditCardName” required=”true” />
        <binding property=”creditCardExpiryMonth” required=”true” />
        <binding property=”creditCardExpiryYear” required=”true” />
    </binder>
    <transition on=”proceed” to=”reviewBooking”>
    <transition on=”cancel” to=”bookingCancelled” bind=”false” />
</view-state>

위의 예제에서 모든 바인딩을 필수로 설정했다. 만약 바인딩 할 입력값이 하나라도 비었다면 에러를 만들고 뷰를 에러와 함께 다시 보여줄 것이다.

4.10. 모델 검증하기

모델 검증은 도델 객체에 기반하여 설정한 제약사항으로 주도한다. 웹 플로우는 그러한 제약 사항을 프로그래밍으로 제공한다.

4.10.1. 프로그래밍적인 검증

모델 검증을 프로그래밍적으로 수행하는 두 가지 방법이 있다. 하나는 검증 로직을 모델 객체 내부에 구현하는 것이다. 두 번째는 별도의 Validator를 구현하는 것이다. 두 가지 방법 모두 ValidationContext를 사용하여 에러 메시지를 기록하고 현재 사용자 정보에 접근할 수 있다.

4.10.1.1. 모델 검증 매서드 구현하기

모델 객체 내부에 검증 로직을 정의하는 것은 그 상태를 검증하는 가장 간단한 방법이다. 한 번 이런 로직을 웹 플로우 규약에 맞게 만들어 두면 웹 플로우가 자동으로 그 로직을 view-state 포스트백 라이프사이클 동안에 호출할 것이다. 웹 플로구 규약은 뷰에서 수정하는 모델 속성 서브셋을 쉽게 검증할 수 있게 해준다. 이렇게 하려면 간단하게 validate${state} 형태의 이름을 가진 public 매서드를 만들면 된다. ${state}는 검증을 수행할 view-state의 id다. 예를 보자.

public class Booking {
    private Date checkinDate;
    private Date checkoutDate;
    …
       
    public void validateEnterBookingDetails(ValidationContext context) {
        MessageContext messages = context.getMessages();
        if (checkinDate.before(today())) {
            messages.addMessage(new MessageBuilder().error().source(“checkinDate”).
                defaultText(“Check in date must be a future date”).build());
        } else if (!checkinDate.before(checkoutDate)) {
            messages.addMessage(new MessageBuilder().error().source(“checkoutDate”).
                defaultText(“Check out date must be later than check in date”).build());
        }
    }
}

위 예제에서, Booking 모델을 편집하는 enterBookingDetails view-state로 전이가 발생하면 웹 플로우는 해당 전이에 검증을 방지하지 않았다면 validateEnterBookingDetails(ValidationContext) 매서드를 자동으로 호출한다. 그런 view-state 예제는 아래와 같다.

<view-state id=”enterBookingDetails” model=”booking”>
    <transition on=”proceed” to=”reviewBooking”>
</view-state>
       
모든 검증 메소드를 정의했다. 일반적으로 플로우는 모델을 여러 뷰에서 편집한다. 이 경우에 검증을 수행할 필요가 있는 각각의 view-state에 대한 검증 매서드를 정의했을 것이다.

4.10.1.2. Validator 구현하기

두 번째 방법은 모델 객체를 검증할 Validator라고 하는 별도의 객체를 정의하는 것이다. 그렇게 하려면 우선 ${model}Validator 패턴의 이름을 가진 클래스를 만든다. ${model}은 booking 같은 모델 표현식 형태를 띈다. 그런 다음 validate${state} 형태의 이름을 가진 매서드를 정의한다. ${state}는 enteringBookingDetails같은 view-state id에 해당한다. 클래스는 스프링 빈으로 배포될 수 있어야 한다. 여러 검증 매서드를 정의할 수 있다, 예제를 보자.

@Component
public class BookingValidator {
    public void validateEnterBookingDetails(Booking booking, ValidationContext context) {
        MessageContext messages = context.getMessages();
        if (booking.getCheckinDate().before(today())) {
            messages.addMessage(new MessageBuilder().error().source(“checkinDate”).
                defaultText(“Check in date must be a future date”).build());
        } else if (!booking.getCheckinDate().before(booking.getCheckoutDate())) {
            messages.addMessage(new MessageBuilder().error().source(“checkoutDate”).
                defaultText(“Check out date must be later than check in date”).build());
        }
    }
}
               
위 예제에서 Booking 모델을 편집하는 enterBookingDetilas view-state로 전이가 발생하면 웹 플로우는 validateEnterBookingDetails(Booking, ValidationContext) 매서드를 자동으로 호출할 것이다. 단 이때 해당 전이에 대한 검증을 방지한 상태가 아니어야 한다.

또한 Validator는 스프링 MVC의 Errors 객체로 받을 수 있다. 이 객체는 기존의 스프링 검증기를 호출할 때 필수다.

검증기는 스프링 빈으로 등록되어야 하고 ${model}Validator 라는 이름 규약을 지켜야 한다 그럼 자동으로 감지되고 호출된다. 위 예제에서 스프링 2.5 클래스패스-스캔을 하게 되면 @Component를 감지하고 자동으로 bookingValidator라는 이름의 빈으로 등록이 된다. 그러면 bokking 모델 검증이 필요할 때 이 bookingValidator 인스턴스가 호출될 것이다.

4.10.2. ValidationContext

ValidationContext는 MessageContext를 가져와서 검증을 하는 동안 메시지를 기록한다. 또한 userEvent와 현재 사용자의 Principal 같이 현재 사용자에 대한 정보를 공개한다. 이러한 정보는 검증 로직을 접속한 사용자 또는 UI의 어떤 버튼 또는 링크에 기반하여 커스터마이징 할 수 있도록 해준다. 자세한 정보는 ValidationContext API를 참고하라.

4.11. 검증 방지하기

validate 속성을 사용하여 특정 뷰 이벤트에 대한 모델 검증을 방지할 수 있다.

<view-state id=”chooseAmenities” model=”booking”>
    <transition on=”proceed” to=”reviewBooking”>
    <transition on=”back” to=”enterBookingDetails” validate=”false” />
</view-state>
       
위 예제는 back 이벤트가 발생할 경우 바인딩은 하지만 검증은 하지 않는다.

4.12. 뷰 전이 실행하기

하나 또는 그 이상의 transition 엘리먼트를 정의하여 뷰에서 발생하는 사용자 이벤트를 다룰 수 있다. 트랜지션은 사용자한테 다른 뷰를 보여주거나 단순하게 액션을 실행하거나 현재 뷰를 다시 보여주는 일을 한다. 또한 트랜지션은 애이작스(Ajax)를 다룰 때 프레그먼츠(“fragments”)라고 하는 뷰의 일부를 다시 보여주길 요청할 수도 있다. 마지막으로 글로벌(“global”) 트랜지션이라고 하는 여러 뷰에 걸쳐서 공유하는 것을 정의할 수도 있다.

뷰 트랜지션을 정의하는 방법을 다음 절에서 살펴보도록 하자.

4.12.1. 트랜지션 액션

view-state 트랜지션을 수행하기 전에 하나 또는 그 이상의 액션을 실행할 수 있다. 이러한 액션은 아마 현재 view-state를 종료하지 못하게 에러 결과를 반환할 수도 있다. 만약 에러 결과가 있다면, 뷰는 다시 보여질 것이며 사용자에게 적절한 메시지를 보여줄 것이다.

만약 트랜지션 액션이 일반 자바 매서드를 호출하면, 호출된 매서드는 false를 반환하여 트랜지션 실행을 방지할 수 있다. 이런 기술은 서비스-계층 매서드에서 발생한 예외를 다룰 때 사용할 수 있겠다. 아래 예제는 서비스를 호출하는 액션을 호출하고 예외 상황을 다룬다.

<transition on=”submit” to=”bookingConfirmed”>
    <evaluate expression=”bookingAction.makeBooking(booking, messageContext)” />
</transition>
           

public class BookingAction {
   public boolean makeBooking(Booking booking, MessageContext context) {
       try {
           bookingService.make(booking);
           return true;
       } catch (RoomNotAvailableException e) {
           context.addMessage(builder.error().
               .defaultText(“No room is available at this hotel”).build());
           return false;
       }
   }
}
           
4.12.2. Global 트랜지션

플로우의 global-transitions 엘리먼트를 사용하여 여러 뷰에 걸쳐 적용할 트랜지션을 생성한다, Global-transition은 종종 레이아웃을 구성하는 메뉴 링크 같은 것을 다룰 때 사용한다.

<global-transitions>
    <transition on=”login” to=”login”>
    <transition on=”logout” to=”logout”>
</global-transitions>
           
4.12.3. 이벤트 핸들러

view-state에서 타겟이 없는 트랜지션을 정의할 수도 있다. 이러한 트랜지션을 “이벤트 핸들러”라고 한다.

<transition on=”event”>
    <!– Handle event –>
</transition>
       
이런 이벤트 핸들러는 플로우의 상태를 변경하지 않는다. 단지 그들의 액션을 실행하고 현재 뷰 또는 하나 이상의 프레그먼트를 다시 보여준다.

4.12.4. 프레그먼트 다시 보여주기

트랜지션에서 render 엘리먼트를 사용하여 이벤트를 처리한 다음 현재 뷰에서 일부분만 다시 보여주도록 요청할 수 있다.

<transition on=”next”>
    <evaluate expression=”searchCriteria.nextPage()” />
    <render fragments=”searchResultsFragment” />           
</transition>
           
fragments 속성은 다시 보여주길 원하는 뷰 엘리먼트(들)의 id(여러개)가 될 것이다. 여러 엘리먼트를 다시 보여주도록 설정하려면 콤마로 구분하여 적으면 된다.

이렇게 일부를 다시 보여주는 기법은화면의 특정 부위를 업데이트하는 Ajax에 의한 이벤로 사용된다.

4.13. 메시지 다루기

스프링 웹 플로우의 MessageContext는 플로우 실행 도중에 메시지를 기록하기 위한 API다. 일반 텍스트 메시지는 물론이고 스프링 MessageSource를 사용하는 국제화 메시지도 추가할 수 있다. 메시지는 뷰에서 보여지며 자동으로 플로우 실행 리다이렉트에도 살아남을 것이다. 세 종류의 메시지를 제공한다. info, warning, error다. 또한, 편리한 MessageBuilder를 제공하여 메시지 작성을 돕고 있다.

4.13.1. 일반 텍스트 메시지 추가하기

MessageContext context = …
MessageBuilder builder = new MessageBuilder();
context.addMessage(builder.error().source(“checkinDate”)
    .defaultText(“Check in date must be a future date”).build());
context.addMessage(builder.warn().source(“smoking”)
    .defaultText(“Smoking is bad for your health”).build());
context.addMessage(builder.info()
    .defaultText(“We have processed your reservation – thank you and enjoy your stay”).build());

4.13.2. 국체화 메시지 추가하기

MessageContext context = …
MessageBuilder builder = new MessageBuilder();
context.addMessage(builder.error().source(“checkinDate”).code(“checkinDate.notFuture”).build());
context.addMessage(builder.warn().source(“smoking”).code(“notHealthy”)
    .resolvableArg(“smoking”).build());           
context.addMessage(builder.info().code(“reservationConfirmation”).build());
           
4.13.3. 메시지 번들 사용하기

국제화 메시지는 스프링 MessageSource로 접근하는 메시지 번들에 정의한다. 플로우-관련 메시지 번들을 만들려면, 간단하게 message.properties 파일을 플로우 디렉터리에 만든다. messages.properties 파일을 만들고 지원하고자 하하는 지역 Locale에 해당하는 .properties 파일을 각각 추가한다.

#messages.properties
checkinDate=Check in date must be a future date
notHealthy={0} is bad for your health
reservationConfirmation=We have processed your reservation – thank you and enjoy your stay
           
뷰 또는 플로우에서 여러분은 resourceBundle EL 변수를 사용하여 메시지 번들에 접근할 수도 있다.

<h:outputText value=”#{resourceBundle.reservationConfirmation}” />
           
4.13.4. 시스템이 생성한 메시지 이해하기

웹 플로우 자체가 사용자에게 보여줄 메시지를 생성하는 몇몇 부분이 있다. 이런 일이 발생하는 중요한 지점 중 하나는 뷰에서 모델로 데이터 바인딩을 할 때다. 변환 에러 같은 바인딩 에러가 발생하면 웹 플로우는 자동으로 에러를 여러분 리소스번들에서 가져온 메시지로 맵핑한다. 보여줄 메시지를 가져오려고 웹 플로우는 바인딩 에러 코드와 타겟 속성 이름을 담고 있는 리소스 키를 시도한다.

아래 예제는 Booking 객체의 checkinDate 속성에 바인딩을 생각해보고 있다. 사용자가 알파벳 문자열을 입력했다고 하자. 이런 경우 타입 변환 에러가 발생할 것이다. 웹 플로우는 “typeMismatch’ 에러 코드를 여러분 리소스 번들 중에 있는 메시지를 찾을 때 다음 키를 사용할 것이다.

booking.checkinDate.typeMismatch

키의 앞 부분은 모델 클래스의 짧은 이름이다. 두 번째 부분은 속성의 이름이다, 세 번째 부분은 에러 코드다. 모델 속성에 바인딩이 실패하면 사용자에게 보여줄 유일한 메시지를 이런식으로 찾는다. 아마 다음과 같은 메시지일 수 있다.

booking.checkinDate.typeMismatch=The check in date must be in the format yyyy-mm-dd.

만약 저 형식에 해당하는 키를 찾지 못하면, 보다 일반적인 키로 시도해본다. 이 키는 간단한 에러 코드 형태다. 속성의 플드 이름은 메시지 인자로 제공된다.

typeMismatch=The {0} field is of the wrong type.
           
4.14. 팝업 보여주기

popup 속성을 사용하여 모델 팝업 창에서 뷰를 랜더링 한다.

<view-state id=”changeSearchCriteria” view=”enterSearchCriteria.xhtml” popup=”true”>
       
스프링 자바스크립트랑 같이 웹 플로우를 사용하면, 팝업을 보여줄 때 클라이언트 쪽 코드를 사용할 필요가 없다. 웹 플로우는 팝업에서 뷰로 리다이렉트를 요청하는 사용자에게 응답을 보낼 것이고 그 결과 클라이언트는 요청은 잘 처리될 것이다.

4.15. 뷰 백트랙킹(backtracking)

기본적으로, 뷰 스테이트를 종료하고 새로운 뷰 스테이트로 전이하면, 여러분은 브라우저의 뒤로 가기 버튼을 사용하여 이전 상태로 이동할 수 있다. 이러한 뷰 스테이트 히스토리 정책은 history 속성을 사용하여 트랜지션 당 설정할 수 있다.

4.15.1. 히스토리 방지하기

history 속성을 discard로 설정하여 뷰 백트랙킹을 방지할 수 있다.

<transition on=”cancel” to=”bookingCancelled” history=”discard”>

4.15.2. 히스토리 금지하기

history 속성을 invalidate로 설정하여 뷰 백트랙킹을 방지하고 이전의 뷰로 전이하는 것도 방지한다.

<transition on=”confirm” to=”bookingConfirmed” history=”invalidate”>
           

SWF 3장 EL(Expression Language)

참조 및 번역: http://static.springframework.org/spring-webflow/docs/2.0.x/reference/html/index.html

3.1. 소개

웹 플로우는 EL을 사용하여 데이터 모델에 접근하며 액션을 호출한다. 이번 장에서 여러분은 EL 문법과 플로우 정의에서 참조할 수 있는 특별한 EL 변수들에 익숙해질 것이다.

3.2. 지원하는 EL 구현체

3.2.1. Unified EL

웹 플로우는 기본으로 Unified EL 사용을 시도한다. jboss-el이 현재 기본 EL 구현체이다. 여러분의 클래스패스에서 el-api을 발견하면 자동적으로 그것을 사용한다. JBoss EL jar 파일은 스프링소스 번들 저장소에서 찾을 수 있다.

노트

el-api 의존성은 보통 웹 컨테이너에서 제공하는 provided다. 예를 들어 톰캣 6는 그것을 가지고 있다.

3.2.2. OGNL

OGNL은 웹 플로우 2가 지원하는 또다른 EL이다. OGNL은 웹 플로우 버전 1.0 사용자에게 가장 익숙한 EL이다. ognl을 사용하려면, jboss-el 대신에 ognl을 클래스패스에 추가하면 된다. 문법을 익히려면 OGNL 레퍼런스 가이드를 참조하라.

3.3. EL 이식성

보통, UEL(Unified EL)과 OGNL은 문법이 비슷하다. 기본 변수 인식, 프로퍼티 접근, 그리고 메소드 호출 문법이 동일하다. 우리는 가능하면 UEL을 사용할 것을 권장하며 필요한 경우에만 별도의 EL 기능을 사용하라.

3.4. EL 사용법

EL은 플로우에서 여러 경우에 사용할 수 있다.

  1. 플로우 input 속성이나 request 매개변수 같은 클라이언트가 제공하는 데이터에 접근하기.
  2. flowScope 같은 내부 데이터 구조에 접근하기
  3. 스프링 빈의 메소드 호출하기
  4. 상태 전이 criteria, 하위 플로우 id 그리고 뷰 이름 같은 구조 인식하기

플로우에 의해 보여지는 뷰는 일반적으로 EL을 사용하여 플로우 데이터 구조에 접근한다.

3.4.1. 표현식 타입

웹 플로우에는 기본적으로 두 종류의 표현식이 있다.

3.4.1.1. 표준 평가 표현식(standard eval expressions)

처음으로 살펴볼 것은 가장 흔히 사용하는 표현식 타입으로 eval 표현식이다. 이 표현식은 EL에 의해 동적으로 평가되고 ${} 나 #{} 같은 구분자로 감싸지 않는다. 예제를 보자.

<evaluate expression=”searchCriteria.nextPage()” />
       
위의 표현식은 값을 구해올 때 searchCriteria 변수의 nextPage라는 메소드를 호출하는 표준 표현식이다. 이 표현식을 ${} 나 #{} 같은 구분자로 감싸면 IllegalArgumentException 예외가 발생한다.

노트

이 상황에서 별도의 평가 구분자를 사용하는 것은 필요없다고 본다. 따라서 오직 expression 속성에 사용할 수 있는 값은 평가 표현식 문자열이다.

3.4.1.2. 템플릿 표현식

두 번째 표현식 타입은 “템플릿” 표현식이다. 이런 표현식은 문자열을 한 개 또는 여러 평가 블럭으로 혼합하는 것을 허용한다. 각각의 평가 블럭은 ${} 구분자를 사용하여 명시적으로 구분한다. 예제를 보자.

<view-state id=”error” view=”error-${externalContext.locale}.xhtml” />
               
위의 표현식은 템플릿 표현식이다. view 속성 값의 결과는 error- 뒤에 externalContext.locale 값을 합친 문자열이 될 것이다. 보시다시피 여기서는 템플릿 내에 평가 블럭을 구분하기 위해 명시적인 구분자가 필요하다.

웹 플로우 XML 스키마를 참조하여 표준 표현식과 템플릿 표현식에서 사용할 수 있는 XML 속성들을 전부 확인할 수 있다.

3.5. 특별한 EL 변수

플로우 안에서 참조할 수 있는 암묵적인 변수들 몇 개가 있다. 이번 절에서 이런 변수들을 논의할 것이다.

3.5.1. flowScope

flowScope를 사용하여 플로우 변수를 할당한다. 플로우 스코프는 플로우가 시작할 때 할당되고 플로우가 끝날 때 소멸된다. 기본 구현에 따라, 플로우 스코프에 저장된 모든 객체는 직렬화 할 수 있어야 한다.

<evaluate expression=”searchService.findHotel(hotelId)” result=”flowScope.hotel” />

3.5.2. viewScope

viewScope를 사용하여 뷰 변수를 할당한다. 뷰 스코프는 view-state에 들어갈 때 할당되고 상태가 종료되면 소멸된다. 뷰 스코프는 오직 view-state에서만 참조할 수 있다. 기본 구현체 따라, 뷰 스코프에 저장된 모든 객체는 질렬화 할 수 있어야 한다.

<on-render>
    <evaluate expression=”searchService.findHotels(searchCriteria)” result=”viewScope.hotels”
              result-type=”dataModel” />
</on-render>

3.5.3. requestScope

requestScope를 사용하여 요청 변수를 할당한다. 요청 스코프는 플로우를 호출한 순간에 할당되고 플로우가 반환을 하면 소멸된다.

<set name=”requestScope.hotelId” value=”requestParameters.id” type=”long” />

3.5.4. flashScope

flashScope를 사용하여 플래시 변수를 할당한다. 플래시 스코프는 플로우가 시작될 때 할당되고, 모든 뷰 랜더링을 마친뒤에 클리어하고 플로우가 끝날 때 소멸한다. 기본 구현에 따라, 플래시 스코프에 저장된 모든 객체는 직렬화 할 수 있어야 한다.

<set name=”flashScope.statusMessage” value=”‘Booking confirmed'” />   

3.5.5. conversationScope

conversationScope를 사용하여 컨버세이션 변수를 할당한다. 컨버세이션 스코프는 최상위 플로우가 시작할 때 할당되고 최상위 플로우가 종료되면 소멸한다. 컨버세이션 스코프는 최상위 플로우와 모든 하위플로우에서 공유한다. 기본 구현에 따라, 컨버세이션 스코프에 저장하는 객체는 HTTP session에 저장하고 일반 세션 복사를 하기 위해 보통 직렬화 할 수 있어야 한다.

<evaluate expression=”searchService.findHotel(hotelId)” result=”conversationScope.hotel” />
           
3.5.6. requestParameters

requestParameters를 사용하여 클라이언트 요청 매개변수에 접근한다.

<set name=”requestScope.hotelId” value=”requestParameters.id” type=”long” />
           
3.5.7. currentEvent

currentEvent를 사용하여 현재 이벤트 속성에 접근한다.

<evaluate expression=”booking.guests.add(currentEvent.guest)” />
           
3.5.8. currentUser

currentUser를 사용하여 로그인한 사용자 정보(authenticated Principal)에 접근한다.

<evaluate expression=”bookingService.createBooking(hotelId, currentUser.name)”
          result=”flowScope.booking” />
           
3.5.9. messageContext

messageContext를 사용하여 플로우 에러나 성공 메시지를 포함한 플로우 실행 메시지를 생성하고 가져오는데 사용할 컨텍스트에 접근한다.MessageContext Javadoc에서 자세한 내용을 참조하라.

<evaluate expression=”bookingValidator.validate(booking, messageContext)” />
           
3.5.10. resourceBundle

resourceBundle을 사용하여 메시지 리소스에 접근하라.

<set name=”flashScope.successMessage” value=”resourceBundle.successMessage” />
           

3.5.11. flowRequestContext

flowRequestContext를 사용하여 RequestContext API에 접근하라. 이것은 현재 플로우 요청을 나타낸다.자세한 내용은 API Javadoc을 참조하라.

3.5.12. flowExecutionContext

flowExecutionContext를 사용하여 FlowExecutionContext API에 접근하라. 이것은 현재 프로우 상태를 나타낸다. 자세한 내용은 API Javadoc을 참조하라.

3.5.13. flowExecutionUrl

flowExecutionUrl을 사용하여 현재 플로우 실행 view-state에 대한 컨텍스트 관련 URI에 접근하라.

3.5.14. externalContext

externalContext를 사용하여 사용자 세션 속성같은 클라이언트 환경에 접근하라. 자세한 내용은 ExternalContext API JavaDoc을 참조하라.

<evaluate expression=”searchService.suggestHotels(externalContext.sessionMap.userProfile)”
          result=”viewScope.hotels” />
           
3.6. Scope 검색 알고리즘

변수를 어떤 플로우 스코프에 할당할 때, 해당 스코프를 참조하는 것이 필요하다. 예제를 보자.

<set name=”requestScope.hotelId” value=”requestParameters.id” type=”long” />
       
어떤 스코프에 들어있는 변수에 단순하게 접근할 때는, 스코프를 반드시 참조하지 않아도 된다. 예제를 보자.

<evaluate expression=”entityManager.persist(booking)” />
       
위에서 booking을 사용한것처럼 만약 스코프를 적지 않으면 스코프 검색 알고리즘이 적용된다. 알고리즘은 request, flash, view, flow, conversation 스코프에서 변수를 찾아본다. 만약 해당 변수를 찾지 못하면 EvaluationException 예외를 던진다.

SWF 2장 플로우 정의하기

참조: http://static.springframework.org/spring-webflow/docs/2.0.x/reference/html/index.html

2.1. 개요

이번 장은 User Section으로 시작한다. 플로우 정의 언어(flow definition language)를 사용하여 플로우를 구현하는 방법을 살펴볼 것이다. 이번 장이 끝날 때 여러분은 언어 구조를 이해하고 플로우를 정의할 수 있을 것이다.

2.2 플로우란 무엇인가?

플로우는 여러 문맥에서 실행될 수 있는 재사용 가능한 스탭의 연속체를 캡슐화 한 것이다. 아래 그림은 Garrett Inforamtion Architecture 다이어그램으로 호텔 예약 프로세스의 단계를 캡슐화한 플로우를 보여주고 있다.

사용자 삽입 이미지
플로우를 나타내는 사이트 맵

2.3. 일반적인 플로우의 구성요소는 무엇인가?

스프링 웹 플로우에서, 플로우는 스테이트(“states”)라고 부르는 스탭의 일련으로 구성한다. 하나의 스테이트로 들어가면 보통 사용자에게 어떤 뷰 하나를 보여주게 된다. 해당 뷰에서, 스테이트가 처리할 사용자 이벤트가 발생한다. 이런 이벤트는 다른 스테이트로 이동(transition)을 시켜서 뷰 네이게이션을 하게 한다.

아래 예제는 앞선 다이어그램에서 살펴본 호텔 예약 플로우 구조를 보여주고 있다.

사용자 삽입 이미지플로우 다이어그램

2.4. 어떻게 플로우를 작성하는가?

플로우는 웹 애플리케이션 개발자들이 간단한 XML 기반 플로우 정의 언어를 사용하여 작성한다. 이번 가이드의 다음 단계에서 이 언어의 구성 요소를 살펴보겠다.

2.5. 기본적인 언어 구성 요소

2.5.1 플로우(flow)

모든 플로우는 다음의 루트 엘리먼트로 시작한다.

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

</flow>

플로우의 모든 스테이트(state)를 이 엘리먼트 안에 정의한다. 처음에 정의한 스테이트가 플로우의 시작점이 된다.

2.5.2. 뷰-스테이트(view-state)

view-state 엘리먼트를 사용하여 플로우에서 뷰를 보여주는 단계를 정의한다.

<view-state id=”enterBookingDetails” />

기본적으로(by convention), 뷰-스테이트는 자신의 id를 플로우가 위치한 디렉터리의 뷰 템플릿으로 맵핑한다. 예를 들어, 위의 스테이트는 만약 플로우가 /WEB-INF/hotels/booking 디렉터리에 있었다면, /WEB-INF/hotels/booking/enterBookingDetails.xhtml을 사용자에게 보여줄 것이다.(rendering)

2.5.3. 트랜지션(transition)

transition 엘리먼트를 사용하여 스테이트에서 발생한 이벤트를 처리한다.

<view-state id=”enterBookingDetails”>
    <transition on=”submit” to=”reviewBooking” />
</view-state>
       
이런 트랜지션이 뷰 네이게이션을 끌어낸다.

2.5.4. end-state

end-state 엘리먼트를 사용하여 플로우 종료를 정의한다.

<end-state id=”bookingCancelled” />

플로우 트랜지션이 종료-상태에 다다르면 플로우를 끝내고 결과를 반환한다.

2.5.5. 체크포인트: 기본 언어 구성 요소

세 개의 구성 요소 view-state, transition, end-state를 가지고 여러분은 빠르게 여러분의 뷰 네비게이션 로직을 표현할 수 있다. 팀에서 플로우 행동을 추가하기 전에 이 작업을 해봄으로써 먼저 사용자와 함께 애플리케이션 UI를 개발에 집중할 수 있다. 아래 예제는 이들 엘리먼트를 사용하여 뷰 네비게이션 로직을 구현한 것이다.

<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”>

    <view-state id=”enterBookingDetails”>
        <transition on=”submit” to=”reviewBooking” />
    </view-state>
   
    <view-state id=”reviewBooking”>
        <transition on=”confirm” to=”bookingConfirmed” />
        <transition on=”revise” to=”enterBookingDetails” />
        <transition on=”cancel” to=”bookingCancelled” />
    </view-state>
   
    <end-state id=”bookingConfirmed” />

    <end-state id=”bookingCancelled” />
       
</flow>   

2.6. 액션(Actions)

대부분의 플로우는 단순히 뷰 네비게이션 로직 보다 더 많은 것을 표현할 필요가 있다. 일반적으로 애플리케이션의 비즈니스 서비스 또는 다른 액션을 호출할 필요가 있다.

플로우에서 여러분이 액션을 실행할 수 있는 몇몇 포인트(point)가 있다.

  • On flow start
  • On state entry
  • On view render
  • On transition execution
  • On state exit
  • On flow end

액션은 간결한 EL(expression language)로 정의한다. 스프링 웹 플로우는 기본적으로 UEL(Unified EL)을 사용한다. 다음의 몇몇 절에서 액션을 정의할 때 사용하는 기본 언어 구성 요소를 살펴보겠다.

2.6.1. evaluate

여러분이 사용할 대부분의 액션 엘리먼트는 evaluate 엘리먼트일 것이다. evaluate 엘리먼트를 사용하여 플로우의 어떤 순간(point)에 표현식의 값을 구할 수 있다. 이 태그 하나로 스프링 빈 또는 다른 플로우 변수의 메소드를 호출할 수 있다. 예를 들어 다음과 같이 사용할 수 있다.

<evaluate expression=”entityManager.persist(booking)” />

2.6.1.1. evaluate 결과값 대입하기

만약 표현식이 값을 반환한다면, 그 값을 플로우의 데이터 모델 flowScope에 저장할 수 있다.

<evaluate expression=”bookingService.findHotels(searchCriteria)” result=”flowScope.hotels” />

2.6.1.2. evaluate 결과값 변경하기

만약 표현식이 반환하는 값이 컨버팅이 필요하다면, 원하는 타입을 result-type 속성에 명시할 수 있다.

<evaluate expression=”bookingService.findHotels(searchCriteria)” result=”flowScope.hotels”
          result-type=”dataModel”/>
               
2.6.2. 체크포인트: 플로우 액션

이제 예제 예약 플로우에 액션을 추가해보자.

<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=”hotelId” />

    <on-start>
        <evaluate expression=”bookingService.createBooking(hotelId, currentUser.name)”
                  result=”flowScope.booking” />
    </on-start>

    <view-state id=”enterBookingDetails”>
        <transition on=”submit” to=”reviewBooking” />
    </view-state>
   
    <view-state id=”reviewBooking”>
        <transition on=”confirm” to=”bookingConfirmed” />
        <transition on=”revise” to=”enterBookingDetails” />
        <transition on=”cancel” to=”bookingCancelled” />
    </view-state>
   
    <end-state id=”bookingConfirmed” />

    <end-state id=”bookingCancelled” />
       
</flow>   

이 플로우는 이제 플로우가 시작되면 Booking 객체를 플로우에 생성한다. 예약하려는 호텔의 id는 플로우 input 속성에서 얻어온다.

2.7. Input/Output 맵핑

각각의 플로우는 잘 정의되어 있는 input/output 제약을 가지고 있다. 플로우를 시작할 때 input 속성에 flow를 넘겨줄 수 있고, 플로우가 끝날 때 flow를 output 속성에 넘겨줄 수 있다. 플로우를 호출하는 것은 다음 시그너처를 가진 메소드를 호출하는 것과 비슷하다.

FlowOutcome flowId(Map<String, Object> inputAttributes);

FlowOutcome은 다음과 같이 생겼다.

public interface FlowOutcome {
   public String getName();               
   public Map<String, Object> getOutputAttributes();
}

2.7.1. input

input 엘리먼트를 사용하여 플로우의 input 속성을 선언한다.

<input name=”hotelId” />

입력한 값은 속성의 name으로 flow 스코프에 저장된다. 예를 들어, 위의 input은 hotelId라는 이름으로 저장될 것이다.

2.7.1.1. input 타입 선언하기

type 속성을 사용하여 input 속성의 타입을 선언할 수 있다.

<input name=”hotelId” type=”long” />

만약 input 값이 선언한 타입과 일치하지 않으면, 타입 전환을 시도한다.

2.7.1.2. input 값 대입하기

value 속성을 사용하여 input 값에 대입할 표현식을 기술할 수 있다.

<input name=”hotelId” value=”flowScope.myParameterObject.hotelId” />

type 속성을 기술하지 않아을 때, 표현식 값의 타입을 판별할 수 있다면 해당 메타데이터를 타입 제약으로 사용할 것이다.

2.7.1.3. input을 필수로 만들기

required 속성을 사용하여 input이 null이거나 빈값이 아니도록 강제할 수 있다.

<input name=”hotelId” type=”long” value=”flowScope.hotelId” required=”true” />

2.7.2. output

output 엘리먼트를 사용하여 플로우 output 속성을 선언할 수 있다. output 속성은 특정 플로우 종료를 나타내는 end-states 내부에 선언한다.

<end-state id=”bookingConfirmed”>
    <output name=”bookingId” /> 
</end-state>
       
output 값은 name 속성 값과 일치하는 것을 flow 스코프에서 가져온다. 예를 들어, 위의 output은 bookingId 변수의 값을 대입할 것이다.

2.7.2.1. output 값 기술하기

value 속성을 사용하여 output 값 표현식을 기술할 수 있다.

<output name=”confirmationNumber” value=”booking.confirmationNumber” /> 

2.7.3. 체크포인트: input/output 맵핑

이제 예제 예약 플로우에서 input/output 맵핑을 다시 살펴보자.

<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=”hotelId” />

    <on-start>
        <evaluate expression=”bookingService.createBooking(hotelId, currentUser.name)”
                  result=”flowScope.booking” />
    </on-start>

    <view-state id=”enterBookingDetails”>
        <transition on=”submit” to=”reviewBooking” />
    </view-state>
   
    <view-state id=”reviewBooking”>
        <transition on=”confirm” to=”bookingConfirmed” />
        <transition on=”revise” to=”enterBookingDetails” />
        <transition on=”cancel” to=”bookingCancelled” />
    </view-state>
   
    <end-state id=”bookingConfirmed” >
        <output name=”bookingId” value=”booking.id”/>
    </end-state>

    <end-state id=”bookingCancelled” />
       
</flow>   

플로우는 hotelId라는 input 속성을 받고 새로운 예약이 확정되면 bookingId라는 output 속성을 반환한다.

2.8. 변수(Variables)

플로우는 한 개 이상의 변수를 선언할 수 있다. 이들 변수는 플로우가 시작할 때 자리를 잡는다. 변수들이 가지고 있는 모든 @Autowired 임시 레퍼런스들 또한 플로우가 재시작 할 때 다시 와이어링한다.

2.8.1. var

var 엘리먼트를 사용하여 플로우 변수를 선언한다.

<var name=”searchCriteria” class=”com.mycompany.myapp.hotels.search.SearchCriteria”/>

여러 플로우 요청(request)에 걸쳐 인스턴스 상태를 저장할 때 해당 클래스가 java.io.Serializable을 구현했는지 확인하라.

2.9. subflow 호출하기

플로우에서 다른 플로우를 하위 플로우(subflow)로 호출할 수 있다. 플로우는 서브플로우가 끝날 때까지 기다렸다가 반환값이 오면 그때 서브 플로우 결과에 응답(response)을 한다.

2.9.1. subflow-state

subflow-state 엘리먼트를 사용하여 다른 플로우를 서브플로우로 호출하기

<subflow-state id=”addGuest” subflow=”createGuest”>
    <transition on=”guestCreated” to=”reviewBooking”>
        <evaluate expression=”booking.guests.add(currentEvent.attributes.guest)” /> 
    </transition>
    <transition on=”creationCancelled” to=”reviewBooking” />
</subfow-state>

위의 예제는 createGuest를 호출하고 있다. 그런 다음 반환되기를 기다린다. 플로우가 guestCreated라는 결과를 받으면, 새로운 guest를 booking guest 리스트에 추가한다.

2.9.1.1. subflow input 전달하기

input 엘리먼트를 사용하여 input을 서브플로우에 전달한다.

<subflow-state id=”addGuest” subflow=”createGuest”>
    <input name=”booking” />
    <transition to=”reviewBooking” />
</subfow-state>

2.9.1.2. 서브플로우 output 맵핑하기

간단하게 서브플로우 output 속성을 결과 트랜지션에서 그 이름으로 참조할 수 있다.

<transition on=”guestCreated” to=”reviewBooking”>
    <evaluate expression=”booking.guests.add(currentEvent.attributes.guest)” /> 
</transition>
       
위에서 guest는 guestCreated 결과에 의해 반환되는 output 속성 이름이다.

2.9.2. 체크포인트: 서브플로우 호출하기

<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=”hotelId” />

    <on-start>
        <evaluate expression=”bookingService.createBooking(hotelId, currentUser.name)”
                  result=”flowScope.booking” />
    </on-start>

    <view-state id=”enterBookingDetails”>
        <transition on=”submit” to=”reviewBooking” />
    </view-state>

    <view-state id=”reviewBooking”>
        <transition on=”addGuest” to=”addGuest” />
        <transition on=”confirm” to=”bookingConfirmed” />
        <transition on=”revise” to=”enterBookingDetails” />
        <transition on=”cancel” to=”bookingCancelled” />
    </view-state>

    <subflow-state id=”addGuest” subflow=”createGuest”>
        <transition on=”guestCreated” to=”reviewBooking”>
            <evaluate expression=”booking.guests.add(currentEvent.attributes.guest)” /> 
        </transition>
        <transition on=”creationCancelled” to=”reviewBooking” />
    </subfow-state>
       
    <end-state id=”bookingConfirmed” >
        <output name=”bookingId” value=”booking.id” />
    </end-state>

    <end-state id=”bookingCancelled” />
       
</flow>       

플로우는 이제 createGuest 서브플로우를 호출하여 새로운 게스트를 게스트 목록에 추가한다.

SWF Chapter 1

참조: 스프링 웹 플로우 레퍼런스 1 장

1. 소개

1.1 이 가이드가 다루고 있는 것

이 가이드는 스프링 웹 플로우와 관련된 모든 것을 다룬다. 최종-사용자 애플리케이션의 플로우를 구현하는 방법과 기능 들을 사용하는 방법을 다룬다. 또한 프레임워크를 확장하는 방법과 전체적인 아키텍처 모델도 다룬다.

1.2. 웹 플로우를 실행할 때 필요로 하는 것

자바 1.4 이상

스프링 2.5.4 이상

1.3 지원을 받을 수 있는 곳

생략

1.4. 개발을 주도 할 수 있는 곳

생략

1.5. 웹 플로우 구성 요소를 스프링소스 번들 저장소에서 얻는 방법

각각의 웹 플로우 배포판의 jar 파일들은 스프링소스 엔터프라이즈 번들 저장소에서 얻을 수 있다. 이 jar 파일들에 메이븐이나 Ivy 의존성 관리자를 사용하여 접근할 수 있다.

1.5.1. 메이븐으로 웹 플로우에 접근하기

메이븐을 사용하여 jar에 접근 하려면 다음 저장소를 메이븐 pom에 추가하라.

<repository>
    <id>com.springsource.repository.bundles.release</id>
    <name>SpringSource Enterprise Bundle Repository – SpringSource Releases</name>
    <url>http://repository.springsource.com/maven/bundles/release</url>
</repository>

<repository>
    <id>com.springsource.repository.bundles.external</id>
    <name>SpringSource Enterprise Bundle Repository – External Releases</name>
    <url>http://repository.springsource.com/maven/bundles/external</url>
</repository>
           

그런 다음 다음의 의존성을 추가하라.

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.binding</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.js</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.webflow</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

       
그리고 만약 JSF(JavaServerFaces)를 사용하고 있다면 다음을 추가하라.

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.faces</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

          
1.5.2. Ivy로 웹 플로우에 접근하기

Ivy를 사용하여 jar 파일에 접근하려면, 다음 저장소를 Ivy 설정에 추가하라.

<url name=”com.springsource.repository.bundles.release”>
    <ivy pattern=”http://repository.springsource.com/ivy/bundles/release/
                  [organisation]/[module]/[revision]/[artifact]-[revision].[ext]” />
    <artifact pattern=”http://repository.springsource.com/ivy/bundles/release/
                       [organisation]/[module]/[revision]/[artifact]-[revision].[ext]” />
</url>

<url name=”com.springsource.repository.bundles.external”>
    <ivy pattern=”http://repository.springsource.com/ivy/bundles/external/
                  [organisation]/[module]/[revision]/[artifact]-[revision].[ext]” />
    <artifact pattern=”http://repository.springsource.com/ivy/bundles/external/
                       [organisation]/[module]/[revision]/[artifact]-[revision].[ext]” />
</url>
           

그런 다음 다음 의존성을 선언하라.

<dependency org=”org.springframework.webflow” name=”org.springframework.binding”
            rev=”2.0.5.RELEASE” conf=”compile->runtime” />
<dependency org=”org.springframework.webflow” name=”org.springframework.js”
            rev=”2.0.5.RELEASE” conf=”compile->runtime” />
<dependency org=”org.springframework.webflow” name=”org.springframework.webflow”
            rev=”2.0.5.RELEASE” conf=”compile->runtime” />

           

그리고 만약 JSF를 사용한다면 다음을 추가하라.

<dependency org=”org.springframework.webflow” name=”org.springframework.faces”
            rev=”2.0.5.RELEASE” conf=”compile->runtime” />

           
1.6. 메이븐 중앙 저장소에서 웹 플로우 구성 요소 얻는 방법

웹 플로우 배포판의 모든 jar 파일은 메이븐 중앙 저장소에서도 접근할 수 있다.

메이븐 중앙 저장소에 있는 웹 플로우 jar에 접근하려면 다음 의존성을 pom에 추가하라.

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.binding</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.js</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.webflow</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

       
만약 JSF를 사용한다면 다음을 추가하라.

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.faces</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

       
1.7. 나이틀리 빌드 얻는 방법

나이틀리 스냅샷 버전의 웹 플로우 트렁크는 스프링 소스 번들 저장소에서 이용할 수 있다. 스냅샷에 접근하려면 다음 저장소를 pom에 추가하라.

<repository>
    <id>com.springsource.repository.bundles.snapshot</id>
    <name>SpringSource Enterprise Bundle Repository – Nightly Snapshots</name>
    <url>http://repository.springsource.com/maven/bundles/snapshot</url>
</repository>
       
그리고 다음 의존성을 선언하라.

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.binding</artifactId>
    <version>3.0.0.CI-###</version>
</dependency>

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.js</artifactId>
    <version>3.0.0.CI-###</version>
</dependency>

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.webflow</artifactId>
    <version>3.0.0.CI-###</version>
</dependency>

           
그리고 만약 JSF를 사용한다면 다음을 추가하라.

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.faces</artifactId>
    <version>3.0.0.CI-###</version>
</dependency>

       
위에서 ‘# # #’은 3.0.0.CI-500 같은 빌드 넘버를 나타낸 것이다. 최근 빌드 넘버를 알고 싶다면 나이틀리 빌드 영역을 참조하라.