SWF 10장 스프링 MVC 통합

10.1. 도입

이번 장에서는 웹 플롱를 스프링 MVC 웹 애플리케이션으로 어떻게 통합하는지 살펴보겠다.
booking-mvc 예제 애플리케이션은 웹 플로우와 스프링 MVC에 대한 좋은 참고자료다. 이 애플리케이션은 간단하게 만든
여행 사이트로 사용자가 호텔 방을 검색하고 예약할 수 있다.

10.2 web.xml 설정하기

스프링 MVC를 사용하는 첫 번쨰 단계는 DispatcherServlet을 web.xml에 설정하는 것이다. 여러분은 일반적으로 웹 애플리케이션 마다 한 번씩 이 작업을 할 것이다.


래 예제는 /spring/ 으로 시작하는 모든 요청을 DispatcherServlet으로 맵핑한다. init-param을
사용하여 contextConfigurationLocation을 제공한다. 이 설정 파일은 웹 애플리케이션에 관한 것이다.

<servlet>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/web-application-config.xml</param-value>
    </init-param>
</servlet>
   
<servlet-mapping>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <url-pattern>/spring/*</url-pattern>
</servlet-mapping>

10.3. 플로우로 디스패칭하기

DispatcherServlet은 애플리케이션 리소스에 대한 요청을 핸들러에게 전달한다. 플로우는 핸들러 하나의 핸들러 타입니다.

10.3.1. FlowHandlerAdapter 등록하기

요청을 플로우로 디스패칭하는 첫 번째 단계는 스프링 MVC에서 플로우 처리가 가능하게 하는 것이다. 그렇게 하려면 FlowHandlerAdapter를 설치하라.

<!– Enables FlowHandler URL mapping –>
<bean class=”org.springframework.webflow.mvc.servlet.FlowHandlerAdapter”>
    <property name=”flowExecutor” ref=”flowExecutor” />
</bean>
           

10.3.2. 플로우 맵핑 정의하기

플로우 처리를 가능하게 했다면, 다음 단계는 특정 애플리케이션 리소스를 여러분 플로우로 맵핑하는 것이다. 그렇게 하는 가장 간단한 방법은 FlowHandlerMapping를 정의하는 것이다.

<!– Maps request paths to flows in the flowRegistry;
     e.g. a path of /hotels/booking looks for a flow with id “hotels/booking” –>       
<bean class=”org.springframework.webflow.mvc.servlet.FlowHandlerMapping”>
    <property name=”flowRegistry” ref=”flowRegistry”/>
    <property name=”order” value=”0″/>
</bean>
           

맵핑을 설정함으로써 Dispatcher는 애플리케이션 리소스 경로를 플로우 레지스트리에 있는 플로우로 맵핑한다. 예를 들어,
/hotels/booking 경로의 리소스에 접근한다면 레지스트리는 hotels/booking id를 가지고 있는 플로우를
찾는다. 만약 해당 id를 가진 플로우를 발경하면 그 플로우가 요청을 처리한다. 만약 플로우를 찾지 못하면 다음
Dispatcher에 있는 (ordered chain)순서에 따라 다음 핸들러 맵핑이 찾아보거나 “noHandlerFound”
응답을 반환한다.

10.3.3. 플로우 처리 워크플로우

유효한 플로우 맵핑을 발견하면,
FlowHandlerAdapter는 플로우의 새로운 실행 시작 지점이 어딘지 알아내거나 현재 HTTP 요청 정보를 기반으로
기존의 실행을 다시 시작한다. 어탭터가 적용할 플로우 실행을 새로 시작하거나 다시 시작하는데 관련된 여러 기본 행위가 존재한다.

  • HTTP 요청 매개변수들은 모든 시작 플로우 실행에서 이용할 수 있다.
  • 플로우 실행이 최종 응답 없이 종료되면 기본 핸들러가 동일한 요청을 새로운 실행 시작을 시도한다.

  • 외가 NoSuchFlowExecutionException이 아니라면 처리하지 않는 예외를 DispatcherSerlvet에
    위임한다. 기본 핸들러는 NoSuchFlowExecutionException를 만나면 새로운 실행을 시작하여 예외를 극복하려고
    한다.

보다 자세한 내용은 FlowHandlerAdapter API 문서를 참조하라. 여러분은 이런 기본 행위를 상속 또는 여러분 만의 FlowHandler를 구현하여 재정의 할 수 있다. 다음 절에서 자세히 다루겠다.

10.4. 커스텀 FlowHandler 구현하기

FlowHandler는 HTTP 서블릿 환경에서 플로우를 어떻게 실행할지 커스터마이징 할 때 사용할 수 있는 확장 지점이다. FlowHandler는 FlowHandlerAdapter가 사용하며 다음을 책임진다.

  • 실행할 플로우 정의의 id를 반환한다.
  • 시작할 때 플로우의 새로운 실행에 넘겨줄 입력값을 만든다.
  • 종료할 때 플로우의 실행에서 반환하는 결과를 처리한다.
  • 플로우 실행 중에 발생하는 예외를 처리한다.

이런 책임들은 org.springframework.mvc.servlet.FlowHandler 인터페이스에 정의에 나타나있다.

public interface FlowHandler {

    public String getFlowId();

    public MutableAttributeMap createExecutionInputMap(HttpServletRequest request);

    public String handleExecutionOutcome(FlowExecutionOutcome outcome,
        HttpServletRequest request, HttpServletResponse response);

    public String handleException(FlowException e,
        HttpServletRequest request, HttpServletResponse response);
}               
       
To
implement a FlowHandler, subclass AbstractFlowHandler. All these
operations are optional, and if not implemented the defaults will
apply. You only need to override the methods that you need.
Specifically:

FlowHandler를 구현하려면 AbstractFlowHandler를 상속하라. 모든 기능은 부가적인 것이기 떄문에 구현하지 않으면 기본 설정이 적용된다. 여러분은 필요한 매서드만 재정의하면 된다. 특히…

  • 여러분 플로우의 id를 Http 요청에서 직접 가져올 수 없을 때 getFlowId(HttpServletRequest)를 재정의하라. 기본으로 실행할 플로우의 id는 요청 URI의 pathinfo 부분에서 가져온다. 예를 들어 http://localhost/app/hotels/booking?hotelId=1는 기본으로 플로우 id로 hotels/booking를 가져온다.
  • HttpServletRequest에서 플로우 입력 매개변수 추출을 상세하게 제어하고 싶다면 createExecutionInputMap(HttpServletRequest)를 재정의하라. 기본으로 모든 요청 매개변수를 플로우 입력 매개변수로 사용한다.
  • 특정 플로우 실행 결과를 별도 방식으로 처리하고 싶다면 handleExecutionOutcome을 재정의하라 기본 행위는 종료한 플로우의 URL로 리다이렉트하여 플로우의 새로운 실행을 시작한다.
  • unhandled 플로우 예외를 상세하게 제어하고 싶다면 handleException을 재정의하라. 기본 행동은
    클라이언트가 종료됐거나 만료된 플로우 실행에 접근하면 플로우 재시작을 시도한다. 그밖의 예외는 스프링 MVC
    ExceptionResolver 기반 시설에 던져준다.

10.4.1. FlowHandler 예제

스프링 MVC와 웹 플로우 사이에 흔한 협력은 종료했을 때 @Controller로 리다이렉트 하는 것이다.
FlowHandler는 특정 컨트롤러 URL로 플로우 정의에 결합하지 않고도 할 수 있게 해준다. 스프링 MVC로 리다이렉트하는
FlowHandler 예제는 아래와 같다.

public class BookingFlowHandler extends AbstractFlowHandler {
    public String handleExecutionOutcome(FlowExecutionOutcome outcome,
                                         HttpServletRequest request, HttpServletResponse response) {
        if (outcome.getId().equals(“bookingConfirmed”)) {
            return “/booking/show?bookingId=” + outcome.getOutput().get(“bookingId”);
        } else {
            return “/hotels/index”;
        }
    }
}
           
이 핸들러는 오직 플로우 실행 결과를 특정 방법으로 다뤄야 하기 때문에 다른 것은 재정의할 필요가 없다.
bookingConfirmed 결과만 새로운 예약을 보여주도록 리다이렉트할 것이다. 그밖에 다른 결과는 호텔 인덱스 페이지로
다시 리다이렉트 한다.

10.4.2. 커스텀 FlowHandler 배포하기

To install a custom FlowHandler, simply deploy it as a bean. The
bean name must match the id of the flow the handler should apply to.

커스텀 FlowHandler를 설치하려면 간단하게 빈으로 배포하면 된다. 빈 이름은 반드시 핸들러를 적용할 플로우의 id와 일치해야 한다.

<bean name=”hotels/booking” class=”org.springframework.webflow.samples.booking.BookingFlowHandler” />
           
이 설정에서 /hotels/booking에 접근하는 것은 커스텀 BookingFlowHandler를 사용하는
/hotels/booking 플로우를 실행하는 것이다. 예약 플로우가 종료되면 FlowHandler는 플로우 실행 결과를
처리하고 적절한 컨트롤러로 리다이렉트 한다.

10.4.3. FlowHandler 리다이렉트

FlowExecutionOutcome 또는 FlowException를 처리하는 FlowHandler는 처리 한 다음 리다이렉트
할 리소스를 가리키는 문자열을 반환한다. 앞선 예제에서 BookingFlowHandler는 bookingConfirmed 결과는
booking/show 리소스 URI로 리다이렉트 하고 그밖에 모든 결과는 hotels/index 리소스로 리다이렉트 했다.

기본으로, 반환된 리소스 위치는 현재 서블릿 맵핑을 기준으로 상대적이다. 이로인해 플로우 핸들러가 상대 경로를 사용하여
애플리케이션의 다른 컨트롤러로 리다이렉트 할 수 있다. 또한 명시적인 리다이렉트 접두어는 좀 더 제어가 필요한 경우에 사용할 수
있다.

지원하는 명시적인 리다이렉트 접두어는 다음과 같다.

  • servletRelative: – 현재 서블릿에서 상대적인 위치의 리소스로 리다이렉트 하라.
  • contextRelative: – 현재 웹 애플리케이션 컨텍스트 경로에 상대적인 위치의 리소스로 리다이렉트 하라.
  • servetRelative: – 서버 루트에 상대적인 위치의 리소스로 리다이렉트 하라.
  • http:// 또는 https://: – 전체를 명시한 리소스 URI로 리다이렉트 하라.

이것과 동일한 접미어를 exteralRedirect를 사용하여 플로우 정의에서 사용할 수 있다. view-state
또는 end-state에서 지시자로 쓸 수 있다. 예를 들어
view=”externalRedirect:http://springframework.org” 이렇게 쓸 수 있다.

10.5. View Resolution

웹 플로우 2는 다른 것을 기술하지 않으면 선택한 뷰 식별자를 플로우 작업 디렉토리에 위치한 파일로 맵핑한다. 기존의 스프링
MVC + 웹 플로우 애플리케이션에서는 외부 viewResolver가 이미 이런 맵핑을 다루고 있을 것이다. 따라서 그 리졸버를
계속 하용하고 플로우 뷰를 패키징 하는 방법을 바꾸고 싶지 않다면 웹 플로우를 다음과 같이 설정하라.

<webflow:flow-registry id=”flowRegistry” flow-builder-services=”flowBuilderServices”>
   <webflow:location path=”/WEB-INF/hotels/booking/booking.xml” />
</webflow:flow-registry>

<webflow:flow-builder-services id=”flowBuilderServices” view-factory-creator=”mvcViewFactoryCreator”/>

<bean id=”mvcViewFactoryCreator” class=”org.springframework.webflow.mvc.builder.MvcViewFactoryCreator”>
   <property name=”viewResolvers” ref=”myExistingViewResolverToUseForFlows”/>
</bean>
      
10.6. 뷰에서 발생한 이벤트 신호보내기

When a flow enters a view-state it pauses, redirects the user to its
execution URL, and waits for a user event to resume. Events are
generally signaled by activating buttons, links, or other user
interface commands. How events are decoded server-side is specific to
the view technology in use. This section shows how to trigger events
from HTML-based views generated by templating engines such as JSP,
Velocity, or Freemarker.

플로우가 멈춘 view-state로 들어가면, 사용자를 실행 URL로 리다이렉트 하고 사용자가 다시 시작 이벤트를 발생할 때까지
기다린다. 이벤트는 보통 활성화 버튼, 링크 또는 다른 사용자 인터페이스 커맨드에 의해 신호가 보내진다. 이벤트가 서버쪽에
어떻게 디코드 되느냐는 사용하는 뷰 기술에 따라 다르다. 이번 절에서 (JSP, Velocity, 또는 Freemarker 같은
템플릿 엔진에 의해 만들어진 뷰를 기반으로) HTML에서 이벤트를 어떻게 발생시키는지 살펴보겠다.

10.6.1. 이름이 있는 HTML 버튼 사용하여 이벤트 신호 보내기

아래 예제는 클릭했을 때 각각 proceed와 cancel 이벤트 신호를 보내는 두 개의 버튼을 보여준다.

<input type=”submit” name=”_eventId_proceed” value=”Proceed” />
<input type=”submit” name=”_eventId_cancel” value=”Cancel” />

버튼이 눌리면 웹 플로우는  _eventId_ 로 시작하는 요청 매개 변수 찾고 나머지 문자열을 이벤트 id로 간주한다. 이
예제에서 _eventId_proceed를 보내면 proceed가 id가 된다. 같은 폼에서 발생할 수 있는 여러 이벤트가 있을
때 이런 스타일을 사용할 수 있겠다.

10.6.2. 감춰진 HTML 폼 매개변수를 사용하여 이벤트 신호 보내기

아래 예제는 서브밋 할 때 proceed 이벤트를 보내는 폼이다.

<input type=”submit” value=”Proceed” />
<input type=”hidden” name=”_eventId” value=”proceed” />   
          
여기서 웹 플로우는 간단하게 특수한 _eventId 매개변수를 찾고 그 값을 이벤트 id로 사용한다. 이런 스타일은 오직 폼에서 발생하는 이벤트가 한 개 일 때에만 사용하라.

10.6.3. HTML 링크를 사용하여 이벤트 신호 보내기

아래 예제는 활성화 했을 때 cancle 이벤트를 보내는 링크다.

<a href=”${flowExecutionUrl}&_eventId=cancel”>Cancel</a>       
          
이벤트를 발생시키면 HTTP 요청이 서버로 보내진다. 서버 쪽에서는 플로우가 현재 view-state에서 이벤트를 디코딩한다.
어떻게 이 디코딩 프로세스가 동작하는지는 뷰 구현체에 따라 다르다. 스프링 MVC 뷰 구현체가 간단하게 _eventId 이름을
가지고 있는 요청 매개변수를 찾는 것을 기억하라. 만약 _eventId 매개변수가 없다면 뷰는 _eventId_로 시작하는
매개변수를 찾고 해당 문자열의 남은 부분을 이벤트 id로 사용한다. 만약 둘 다 없는 경우에는 어떤 플로우도 실행하지 않는다.

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로 전이할 것이다.

번역 품질을 높이는 방법?

전 잘 모릅니다. 번역을 꾸준히 하고는 있지만 잘 한다고 생각하지도 않으며 잘 하려고 연구를 해보지도 않았습니다. 그럴 시간에 하나라도 더 번역을 해야하기 때문에… 그래도 한 번 생각난 김에 정리해보면 다음과 같습니다.

먼저 번역하는 사람의 문장력이 좋아야 합니다. 번역자가 문장력이 좋고 한글 표현을 잘 사용할 줄 안다면 그만큼 품질 좋은 번역 기사나 번역서가 나올 겁니다.

다음으로 번역자가 영어를 감당할 수 있어야 합니다. 영어를 잘 몰라도 주변에 영어를 잘 알고 있는 친구나 사전을 이용해 가면서 문맥에 비슷한 표현이 나오도록 할 수 있어야 합니다. 기술 서적 중에 쉬운 책들은 중학교 수준 영어 실력만 있어도 번역이 가능하죠.

세번째로는 번역하려는 내용을 알고 있어야 합니다. 저 같은 경우 짧은 글을 번역할 때는 이 규칙은 보통 무시합니다. 새로운 기술이 알고 싶어서 번역할 때도 있거든요. 대신 책이나 레퍼런스 같이 분량이 많아서 일관성이 있어야 하고 어떤 기준이 필요할 때는 해당 기술을 얼마나 잘 이해하고 있는가가 번역 품지을 좌우하기도 합니다. 실제로 제가 메이븐을 하나도 모를 때 메이븐 튜토리얼을 번역한 적이 있는데.. 요즘 들어 가끔보게 되면 민망해 죽을 지경입니다.

네번째로는 재벌이 중요합니다. 다시 한 번 전체 내용을 읽어보면서 날림 번역으로 어색한 문장이나 문맥에 맞지 않는 내용이 눈에 띄면 다시 원문과 비교해서 손보는 작업입니다. 이 작업을 하면서 자신이 읽어도 이해가 된다. 싶을 정도면 일단 OK 입니다. 저는 보통 짧은 글은 재벌도 잘 하지 않는 편입니다. 짧은 글은 애초에 처음 번역할 때 날림 번역을 하지 않고 문맥에 맞추면서 하기 때문에 자잘한 오타와 한 두 문장을 고쳐야 하는데 시간을 소비하고 싶지 않아서 입니다.

마지막으로는 번역을 꾸준히 해서 감을 잃지 말아야 하는건데 이건 잘 지키고 있습니다. 꾸준히 하다보면 비슷한 문장이 등장했을 때 번역 패턴이 생기기도 하고 더 좋은 한글 표현이 떠오르기도 합니다. 예를 들어 오늘 GMP를 듣다가 알게 된건데 All I want for cristmas is you 같은 경우. “내가 크리스마때 원하는 전부는 너야.” 라고 All 을 곧이 곧대로 번역하는 거 보다 “내가 크리스마스때 원하는 건 오직 너 뿐이야.” 라고 All의 의미를 반전시키면 훨씬 깔끔한 문장이 되는거죠. 이런 건 꾸준히 하다보면 하나 둘 씩 생기는 스킬 같은 거라고 생각합니다.

어쩃거나.. 결론은 번역 품질을 높이려면 번역자가 번역을 잘 해야 한다는 거… 그렇지 않고 베타리딩에 모든 걸 건다?? 글쎄요. 저 같은 경우 베타리딩 할 때 품질이 영 아니면 대충 해버리거나 중간에 포기해버립니다. 베타리더가 돈을 받는 것도 아니고 명예가 생기는 것도 아닌데 감수자나 번역자 역할을 대신 해줄 순 없으니까요.

one A for every B 번역하기

얼핏보면 모든 B에 대한 하나의 A. 즉 모든 B가 하나의 A를 공유하는 것 같이 보이지만, 전혀~~ 전~~~혀 아닙니다. 완전히 틀린 번역이 되버립니다.

“각각의 B에 A 하나씩”

이게 정답입니다.

“the application now maintains one instance of the aspect for every target”

자 그럼 위의 문장은 어떻게 번역 or 이해해야 할까요?

“이제 애플리케이션은 각각의 타겟 객체 마다 애스팩트 객체를 하나씩 사용하게 된다.”

이렇게 됩니다. 간혹 원서로 공부를 하시거나 번역을 하실 때 주의 하시기 바랍니다.

번역 할것들이 넘쳤다. 열심히 하자!

9월

Ajax and Java development made simpler, Part 3: Build UI features based on DOM, JavaScript, and JSP tag files
JSF for nonbelievers: JSF conversion and validation

10월

– Getting started with JavaServer Faces 1.2, Part 1: Building basic applications (스크린캐스팅)
JSF for nonbelievers: JSF component development
Automation for the people: Continual refactoring

11월

Meet the JavaScript Development Toolkit
Automation for the people: Hands-free database migration

– Java Persistence With Hibernate
– Pro Spring 2.5

기타

Spring Dynamic Modules Reference
The Ted Neward Challenge (AOP without the buzzwords)