[봄싹] 모임 추가 시나리오 – web 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”>

    <secured attributes=”ROLE_MEMBER” />
   
    <input name=”studyId” required=”true” type=”java.lang.Integer” />
   
    <on-start>
        <evaluate expression=”meetingService.createMeeting(studyId)” result=”flowScope.meeting” />
    </on-start>
   
    <view-state id=”addMeetingForm” model=”meeting” view=”add.jsp”>
        <binder>
            <binding property=”openDate” converter=”shortDate” required=”true” />
            <binding property=”closeDate” converter=”shortDate” required=”true” />
            <binding property=”openTime” converter=”shortTime” required=”true” />
            <binding property=”closeTime” converter=”shortTime” required=”true” />
            <binding property=”title” required=”true” />
            <binding property=”maximum” required=”true” />
            <binding property=”location” required=”true” />
            <binding property=”contents” required=”true” />
        </binder>
        <transition on=”proceed” to=”addPresentationForm” />
        <transition on=”submit” to=”confrimMeetingDetail” />
        <transition on=”cancel” to=”cancel” bind=”false” validate=”false” />
    </view-state>
   
    <view-state id=”addPresentationForm” model=”presentation” view=”presentation/add.jsp”>
        <binder>
            <binding property=”key” required=”true” />
            <binding property=”title” required=”true”/>
            <binding property=”topic” required=”true”/>
            <binding property=”summary” required=”true”/>
            <binding property=”presenter” converter=”memberConverter”/>
        </binder>
        <on-render>
            <evaluate expression=”meetingService.createPresentation(meeting)” result=”viewScope.presentation”/>
        </on-render>
        <transition on=”proceed” to=”presentationList” history=”discard”>
            <evaluate expression=”meetingService.addPresentation(meeting, presentation)”/>
        </transition>
        <transition on=”cancel” to=”cancel” bind=”false” validate=”false” />
    </view-state>

    <view-state id=”presentationList” view=”presentation/list.jsp”>
        <transition on=”delete” to=”presentationList”>
            <set name=”requestScope.presentationKey” value=”requestParameters.presentationKey” />
            <evaluate expression=”meetingService.deletePresentation(meeting, presentationKey)” />
        </transition>
        <transition on=”new” to=”addPresentationForm” />
        <transition on=”submit” to=”confrimMeetingDetail” />
        <transition on=”cancel” to=”cancel” bind=”false” validate=”false” />
    </view-state>

    <view-state id=”confrimMeetingDetail” view=”confirmMeeting.jsp”>
        <transition on=”submit” to=”submit” />
        <transition on=”cancel” to=”cancel” bind=”false” validate=”false” />
    </view-state>
   
    <end-state id=”meetingEnd” view=”externalRedirect:contextRelative:/study/view/${studyId}.do” />
   
    <end-state id=”submit” commit=”true” parent=”#meetingEnd”>
        <on-entry>
            <evaluate expression=”meetingService.addMeeting(studyId, meeting)”/>       
        </on-entry>
    </end-state>

    <end-state id=”cancel” parent=”#meetingEnd” />

</flow>

일단, 이 플로우로 진입하면, addMeetingForm 뷰로 이동, 여기서 나온 transition에 따라 contirmMeetingDetail로 바로 가거나, addPresentationForm으로 이동.

addPresentationForm에서 presentationList로 이동하고, 여기서는 back할 수 없도록 history를 discard로 설정함.

대략 80 줄 정도의 XML 설정으로 아침에 구상한 플로우를 구현했습니다. 이 시나리오를 구현하는데 필요한 자바 코드는 서비스 메서드 몇 개 정도. 컨트롤러 코드는 하나도 없습니다. 만약 웹 플로우 없이, 스프링 MVC만을 이용해서 비슷한 플로우를 구현했다면 훨씬 복잡하고 코드도 길었을 텐데 다행입니다. 웹 플로우 사용법도 생각보다 간편하고 쉬웠던 것 같네요.

이제는 웹 플로우 테스트와 <persistent-context />에 대해 좀 알아봐야겠습니다.

[Spring Wev Flow(2.0.8)] SecurityFlowExecutionListener 패치 for Spring Security 3.X

스프링 시큐리티 3.0 RC1이 나온지가 언젠데 스프링 웹 플로우는 아직도 시큐리티 2점대 기준이더군요. 스프링 웹 플로우 때문에 시큐리티 버전을 낮출수도 없는 노릇이고, 안 돌아가는 클래스 소스를 가져다 스프링 시큐리티 3.X에서 돌아가도록 수정했습니다.

웹 플로우 2.X는 아직 자바 5 기능을 도입하지 않았더군요. 스프링 플젝만 자바 5 기준으로 변경한건지.. 흠.. 그래서 고치는 김에 자바5 Generic을 도입해서 타입 세이프티를 보장하게 코드를 아주 약간만 손 봤습니다.

필요하신 분은 쓰세요~

/*
 * Copyright 2004-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the “License”);
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an “AS IS” BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package springsprout.common.webflow;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.vote.AbstractAccessDecisionManager;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.access.vote.UnanimousBased;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.definition.StateDefinition;
import org.springframework.webflow.definition.TransitionDefinition;
import org.springframework.webflow.execution.EnterStateVetoException;
import org.springframework.webflow.execution.FlowExecutionListenerAdapter;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.security.SecurityRule;

/**
 * Flow security integration with Spring Security
 *
 * @author Scott Andrews
 * @author Keesun Baik(Whiteship)
 */
public class SecurityFlowExecutionListener extends FlowExecutionListenerAdapter {

    private AccessDecisionManager accessDecisionManager;

    /**
     * Get the access decision manager that makes flow authorization decisions.
     * @return the decision manager
     */
    public AccessDecisionManager getAccessDecisionManager() {
        return accessDecisionManager;
    }

    /**
     * Set the access decision manager that makes flow authorization decisions.
     * @param accessDecisionManager the decision manager to user
     */
    public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager) {
        this.accessDecisionManager = accessDecisionManager;
    }

    public void sessionCreating(RequestContext context, FlowDefinition definition) {
        SecurityRule rule = (SecurityRule) definition.getAttributes().get(SecurityRule.SECURITY_ATTRIBUTE_NAME);
        if (rule != null) {
            decide(rule, definition);
        }
    }

    public void stateEntering(RequestContext context, StateDefinition state) throws EnterStateVetoException {
        SecurityRule rule = (SecurityRule) state.getAttributes().get(SecurityRule.SECURITY_ATTRIBUTE_NAME);
        if (rule != null) {
            decide(rule, state);
        }
    }

    public void transitionExecuting(RequestContext context, TransitionDefinition transition) {
        SecurityRule rule = (SecurityRule) transition.getAttributes().get(SecurityRule.SECURITY_ATTRIBUTE_NAME);
        if (rule != null) {
            decide(rule, transition);
        }
    }

    /**
     * Performs a Spring Security authorization decision. Decision will use the provided AccessDecisionManager. If no
     * AccessDecisionManager is provided a role based manager will be selected according to the comparison type of the
     * rule.
     * @param rule the rule to base the decision
     * @param object the execution listener phase
     */
    protected void decide(SecurityRule rule, Object object) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        List<ConfigAttribute> configAttrs = getConfigAttributes(rule);
        if (accessDecisionManager != null) {
            accessDecisionManager.decide(authentication, object, configAttrs);
        } else {
            AbstractAccessDecisionManager abstractAccessDecisionManager;
            List<AccessDecisionVoter> voters = new ArrayList<AccessDecisionVoter>();
            voters.add(new RoleVoter());
            if (rule.getComparisonType() == SecurityRule.COMPARISON_ANY) {
                abstractAccessDecisionManager = new AffirmativeBased();
            } else if (rule.getComparisonType() == SecurityRule.COMPARISON_ALL) {
                abstractAccessDecisionManager = new UnanimousBased();
            } else {
                throw new IllegalStateException(“Unknown SecurityRule match type: ” + rule.getComparisonType());
            }
            abstractAccessDecisionManager.setDecisionVoters(voters);
            abstractAccessDecisionManager.decide(authentication, object, configAttrs);
        }
    }

    /**
     * Convert SecurityRule into a form understood by Spring Security
     * @param rule the rule to convert
     * @return list of ConfigAttributes for Spring Security
     */
    protected List<ConfigAttribute> getConfigAttributes(SecurityRule rule) {
        List<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>();
        Iterator<String> attributeIt = rule.getAttributes().iterator();
        while (attributeIt.hasNext()) {
            configAttributes.add(new SecurityConfig(attributeIt.next()));
        }
        return configAttributes;
    }
}

ps: 스프링 소스 직원님들.. 웹 플로우도 빨랑 3.0으로 올려줘요. 한 달에 이슈 한 개 처리하는건 너무 심한거 아니삼..??

[Spring Web Flow] booking 예제 분석 2 – Resources Servlet

<?xml version=”1.0″ encoding=”ISO-8859-1″?>
<web-app xmlns=”http://java.sun.com/xml/ns/j2ee”
    xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
    xsi:schemaLocation=”http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd”
    version=”2.4″>

    <!– The master configuration file for this Spring web application –>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/config/web-application-config.xml
        </param-value>
    </context-param>
   
    <!– Enables Spring Security –>
     <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

    <filter-mapping>
      <filter-name>springSecurityFilterChain</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>
   
    <!– Loads the Spring web application context –>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

      <!– Serves static resource content from .jar files such as spring-faces.jar –>
    <servlet>
        <servlet-name>Resources Servlet</servlet-name>
        <servlet-class>org.springframework.js.resource.ResourceServlet</servlet-class>
        <load-on-startup>0</load-on-startup>
    </servlet>
       
    <!– Map all /resources requests to the Resource Servlet for handling –>

    <servlet-mapping>

        <servlet-name>Resources Servlet</servlet-name>

        <url-pattern>/resources/*</url-pattern>

    </servlet-mapping>
   
    <!– The front controller of this Spring Web application, responsible for handling all application requests –>

    <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></param-value>

        </init-param>

        <load-on-startup>1</load-on-startup>

    </servlet>
       
    <!– Map all *.spring requests to the DispatcherServlet for handling –>
    <servlet-mapping>
        <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
        <url-pattern>/spring/*</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>

</web-app>

web.xml에서 눈여볼만한 부분은 먼저, 스프링 JS가 제공하는 ResourceServlet 입니다.
JAR 파일에 들어있는 정적인 자원들(이미지, javascript, css)을 효율적으로 가져오기 위한 서블릿입니다. (JAR 파일에 없으면 로컬에 있는 파일을 참조합니다.

standard.jsp 파일에 있는 헤더를 보죠.

<head>
    <meta http-equiv=”Content-Type” content=”text/html; charset=UTF-8″ />
    <title>Spring Travel: Spring MVC and Web Flow Reference Application</title>
    <link type=”text/css” rel=”stylesheet” href=”<c:url value=”/resources/dijit/themes/tundra/tundra.css” />” />
    <style type=”text/css” media=”screen”>
        @import url(“<c:url value=”/resources/css-framework/css/tools.css” />”);
        @import url(“<c:url value=”/resources/css-framework/css/typo.css” />”);
        @import url(“<c:url value=”/resources/css-framework/css/forms.css” />”);
        @import url(“<c:url value=”/resources/css-framework/css/layout-navtop-localleft.css” />”);
        @import url(“<c:url value=”/resources/css-framework/css/layout.css” />”);
        @import url(“<c:url value=”/resources/styles/booking.css” />”);
    </style>
    <script type=”text/javascript” src=”<c:url value=”/resources/dojo/dojo.js” />”></script>
    <script type=”text/javascript” src=”<c:url value=”/resources/spring/Spring.js” />”></script>
    <script type=”text/javascript” src=”<c:url value=”/resources/spring/Spring-Dojo.js” />”></script>
</head>

저기서 보면 /resources로 시작하는 url들이 보입니다. css와 js를 찹조하고 있군요. spring js JAR 파일을 볼까요.


위에서 참조하는 리소스들이 이 JAR 파일에 들어있네요. 그럼 JAR 파일에 들어있지 않은 자원들은 어찌할까요.


        <div id=”branding” class=”spring”>
            <a href=”<c:url value=”/” />”><img
src=”<c:url value=”/resources/images/header.jpg”/>” alt=”Spring
Travel” /></a>
        </div>
    </div>
    <div id=”content” class=”clearfix spring”>
        <div id=”local” class=”spring”>
            <a href=”http://www.thespringexperience.com”>
                <img src=”<c:url value=”/resources/images/diplomat.jpg”/>” alt=”generic hotel” />
            </a>
            <a href=”http://www.thespringexperience.com”>
                <img src=”<c:url value=”/resources/images/tse.gif”/>” alt=”The Spring Experience” />
            </a>

이것도 standard.jsp 파일의 일부입니다. 위에 보시면 /resources 로 시작하기 때문에 ResourceServlet이 요청을 처리할겁니다. 그러나 JAR 파일에는 저런 이미지들이 들어있지 않습니다. 그때는 프로젝트의 웹 폴더를 기준으로 /resources를 잘라낸 나머지 부분을 가지고 경로 탐색을 해서 찾아줍니다.


이렇게 보시다시피, /resoruces/images/header.jsp 라는 리소스 URL을 ResourceServelt이 받아서 프로젝트에 위치한 web/images/header.jsp를 찾아서 돌려줍니다.

마지막으로 ResourceServlet의 속성을 살펴보죠.

public void setGzipEnabled(boolean gzipEnabled)
public void setAllowedResourcePaths(java.lang.String allowedResourcePaths)
public void setCompressedMimeTypes(java.lang.String compressedMimeTypes)
public void setJarPathPrefix(java.lang.String jarPathPrefix)
public void setCacheTimeout(int cacheTimeout)

흠.. 뭐 이런 것들이 있군요.

다음..DispatcherServlet 설정이 뭔가 좀 낯설게 느껴집니다. 봄싹같은 경우는.

    <servlet>
        <servlet-name>springsprout</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>springsprout</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

이렇게 설정했습니다. 저렇게 해두면, springsprout-servlet.xml 파일을 찾아서 WebApplicationContext의 설정파일로 사용합니다.

그런데 지금 저 예제에서는

    <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></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
        <url-pattern>/spring/*</url-pattern>
    </servlet-mapping>

이렇게 설정했지요. 아무래도 설정을 간단하게 하고자.. 보통 ApplicationContext의 설정파일로 쓰는 것에 웹과 관련 된 설정까지 다 해놓고, 저기서는 별다른 설정 파일을 읽지 않도록, 기본 설정 파일을 찾지 않게 그냥 빈 문자열로 설정한 듯 합니다.

봄싹의 경우 기본 네임스페이스가 springsprout기 때문에 init-param으로 contextConfigLocation 속성을 설정하지 않아도 springsprout-servlet.xml 파일을 찾도록 되어 있습니다. 몾찾으면 에러가 납니다.

그런데, <servlet-name>Spring MVC Dispatcher Servlet</servlet-name> 이녀석은 지금 기본 네임스페이스가 Spring MVC Dispatcher Servlet 이게 되고 따라서 그냥 두면 Spring MVC Dispatcher Servlet-servlet.xml 이 파일을 찾다가 몾찾아서 에러가 날 겁니다. 그래서 contextConfigLocation을 null로 설정해서 설정 파일이 필요없도록 해둔거네요.

굳이 Web 설정이랑 App 설정을 구분할 필요가 없다면, 저렇게 해도 괜찮긴 하겠네요. 흠..

web.xml 분석은 이만~

[Spring Web Flow] booking 예제 분석 1 – 예제 실행하기

cfile6.uf.17714D194AD6B88564FBC1.zip
위 코드를 받아서 메이븐 플젝 import를 해서 Run on server로 돌리거나, Spring 사이트에서 Spring Web Fow를 다운 받은 다음 projects 폴더에 가셔서 booking-mvc 폴젝을 메이븐을 플젝 import 하셔도 됩니다.

막상 실행해 보고나니 너무 간단한 예제라서 조금 아쉽네요. 적어도 멀티 폼 정도의 예제는 만들어줘어야 하는가 아닌가 싶기도 합니다. 그렇치만, 스프링 MVC 연동, 스프링 시큐리티 연동, 스프링 EL, ConversionService 등록, 스프링 JS 등을 눈여겨 볼 수 있는 좋은 예제입니다.

실행은 별거 없으니 여기서 마무리?

혹시 Run On Server로 실행할 때 ClassNotFound 예외가 발생한다면 mvn war:inplace를 한 번 해주고 다시 하면 될 겁니다.

SWF 9장 시스템 설정

9.1. 도입

이번 장에서는 웹 플로우 시스템을 웹 환경에서 사용하기 적당하도록 설정하는 방법을 살펴본다.

9.2. webflow-config.xsd

웹 플로우는 스프링 스키마를 제공하여 여러분이 시스템을 설정할 수 있도록 한다. 이 스키마를 사용하려면 그것을 기반시설 계층의 빈 파일 중 하나에 추가하라.

<beans xmlns=”http://www.springframework.org/schema/beans”
       xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
       xmlns:webflow=”http://www.springframework.org/schema/webflow-config”
       xsi:schemaLocation=”
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/webflow-config
           http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd”>       

    <!– Setup Web Flow here –>
   
</beans>

9.3. 기본 시스템 설정

다음 절에서 웹 플로우 시스템을 여러분 애플리케이션에 설정할 때 필요한 최소한의 설정을 살펴보겠다

9.3.1. FlowRegistry

플로우를 FlowRegistry에 등록하라.

<webflow:flow-registry id=”flowRegistry”>
    <webflow:flow-location path=”/WEB-INF/flows/booking/booking.xml” />
</webflow:flow-registry>

9.3.2. FlowExecutor

플로우를 실행하는 핵심 서비스 FlowExecutor를 배포하라.

<webflow:flow-executor id=”flowExecutor” />

스프링 MVC와 스프링 Faces 부분에서 웹 플로우 시스템을 어떻게 MVC와 JSF에 통합하는지 살펴보라.

9.4. flow-registry 옵션

이번 절은 flow-registry 설정 옵션을 살펴본다

9.4.1. 플로우 위치 명시하기

location 엘리먼트를 사용하여 등록할 플로우 정의 경로를 명시한다. 기본으로 base-path가 정의되어 있지 않다면 플로우는 자신의 파일 이름에서 확장자를 뺀 식별자를 부여받는다.

<webflow:flow-location path=”/WEB-INF/flows/booking/booking.xml” />
        
9.4.2. 커스텀 플로우 식별자 부여하기

id를 명시하여 커스텀 등록 식별자를 플로우에 부여한다.

<webflow:flow-location path=”/WEB-INF/flows/booking/booking.xml” id=”bookHotel” />
           
9.4.3. 플로우 meta-attributes 부여하기

flow-definition-attributes 엘리먼틀르 사용하여 등록한 플로우에 커스텀 meta-attributes를 부여한다.

<webflow:flow-location path=”/WEB-INF/flows/booking/booking.xml”>
    <flow-definition-attributes>
        <attribute name=”caption” value=”Books a hotel” />
    </flow-definition-attributes>
</webflow:flow-location>
           

9.4.4. 위치 패턴으로 플로우 등록하기

flow-location-pattern 엘리먼트를 사용하여 명시한 리소스 위치 패턴에 대응하는 플로우를 등록한다.

<webflow:flow-location-pattern value=”/WEB-INF/flows/**/*-flow.xml” />
           
9.4.5. 플로우 위치 기반 경로

base-path 엘리먼트를 사용하여 애플리케이션의 모든 플로우에 대한 기본 위치를 정의한다. 모든 플로우 위치는 기본 경로를 기준으로 상대 경로가 된다. 기본 경로는 ‘/WEB-INF’같은 리소스 경로 또는 ‘classpath:org/springframework/webflow/samples’ 같은 클래스패스 위치가 될 수 있다.

<webflow:flow-registry id=”flowRegistry” base-path=”/WEB-INF”>
    <webflow:flow-location path=”/hotels/booking/booking.xml” />
</webflow:flow-registry>
           
With a base path defined, the algorithm that assigns flow identifiers changes slightly. Flows will now be assigned registry identifiers equal to the the path segment between their base path and file name. For example, if a flow definition is located at ‘/WEB-INF/hotels/booking/booking-flow.xml’ and the base path is ‘/WEB-INF’ the remaining path to this flow is ‘hotels/booking’ which becomes the flow id.

기본 경로를 정의함으로서 플로우 식별자를 부여하는 알고리즘이 약간 바뀐다. 플로우는 이제 기본 경로와 파일이름 사아의 값을 등록 식별자로 부여받는다. 예를 들어, 플로우가 정의되어 있는 위치가 ‘/WEB-INF/hotels/booking/booking-flow.xml’ 이렇고 기본 경로가
‘/WEB-INF’ 라면 남는 부분인  ‘hotels/booking’가 플로우의 id가 된다.

[팁]    디렉토리 당 플로우 정의
각각의 플로우 정의를 별도의 디렉토리로 묶는 것이 좋은 습관이라는 것을 기억해두자. 이렇게 하면 모듈화를 증진시키고 독립적인 리소스를 프로우 정의와 함께 묶을 수 있다. 또한 규약을 이용할 때 두 개의 플로우가 같은 식별자를 갖는 것을 방지할 수 있다.

만약 기본 경로가 명시되어 있지 않거나 플로우 정의가 기본 경로에 있다면 파일이름(에서 확장자를 뺸)것을 사용하여 id를 대입한다. 예를 들어, 만약 플로우 정의 파일이 ‘booking.xml’라면 플로우 식별자는 간단하게 ‘booking’이 된다.

위치 패턴은 등록 기본 경로와 함께 사용하면 매우 막강하다. 플로우 식별자가 ‘*-flow’가 되지 않고 디렉토리 경로 기반이 된다. 예를 들어..

<webflow:flow-registry id=”flowRegistry” base-path=”/WEB-INF”>
    <webflow:flow-location-pattern value=”/**/*-flow.xml” />
</webflow:flow-registry>
           
위 예제에서 WEB-INF 밑에 /user/login, /user/registration, /hotels/booking, /flights/booking에 위치한 플로우들이 있다고 했을 때 플로우 id는 각각 user/login, user/registration, hotels/booking,  flights/booking가 된다.

9.4.6. FlowRegistry 계층구조 설정하기

Use the parent attribute to link two flow registries together in a hierarchy. When the child registry is queried, if it cannot find the requested flow it will delegate to its parent.

parent 속성을 사용하여 두 플로우 등록을 하나의 계층구조로 묶을 수 있다. 요청받은 플로우를 하위 레지스트리에서 찾지 못하면 parent로 위임한다.

<!– my-system-config.xml –>
<webflow:flow-registry id=”flowRegistry” parent=”sharedFlowRegistry”>
    <webflow:flow-location path=”/WEB-INF/flows/booking/booking.xml” />
</webflow:flow-registry>

<!– shared-config.xml –>
<webflow:flow-registry id=”sharedFlowRegistry”>
    <!– Global flows shared by several applications –>
</webflow:flow-registry>
           
9.4.7. 커스텀 FlowBuilder 서비스 설정하기

flow-builder-services 속성을 사용하여 flow-registry에서 플로우를 만들 때 사용할 서비스와 설정을 커스터마이징할 수 있다. 만약 flow-builder-services 태그가 명시되어 있지 않다면 기본 서비스 구현체를 사용한다. 태그가 정의되어 있다면 커스터마이징 하고 싶은 서비스를 참조하면 된다.

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

<webflow:flow-builder-services id=”flowBuilderServices” />
           

설정 가능한 서비스는 conversion-service, expression-parser, view-factory-creator다. 이 서비스들은 여러분이 정의한 커스텀 빈을 첨조하도록 설정되었다. 예를 들어..

<webflow:flow-builder-services id=”flowBuilderServices”
    conversion-service=”conversionService”
    expression-parser=”expressionParser”
    view-factory-creator=”viewFactoryCreator” />

<bean id=”conversionService” class=”…” />
<bean id=”expressionParser” class=”…” />
<bean id=”viewFactoryCreator” class=”…” />
           

9.4.7.1. conversion-service

conversion-service 속성을 사용하여 웹 플로우 시스템이 사용할 ConversionService를 커스터마이징할 수 있다. Converter는 플로우 실행도중 어떤 타입을 다른 타입으로 변경할 필요가 있을 때 사용한다. 기본 ConversionService는 숫자, 클래스, Enum같은 기본 객체 타입에 대한 컨버터를 등록한다.

9.4.7.2. expression-parser

expression-parser 속성을 사용하여 웹 플로우 시스템이 사용할 ExpressionParser를 커스터마이징할 수 있다. 기본 ExpressionParser는 클래스패스에서 사용할 수 있다면 Unified EL을 사용하고 그렇지 않다면 OGNL을 사용한다.

9.4.7.3. view-factory-creator

view-factory-creator 속성을 사용하여 웹 플로우 시스템이 사용할 ViewFactoryCreator를 커스터미이징 할 수 있다. 기본 ViewFactoryCreator는 JSP, Velocity, Freemarker를 랜더링 할 수 있는 스프링 MVC ViewFactory를 생성한다.

설정 가능한 설정은 development다. 이 설정은 플로우 생성 과정 중에 적용할 전역 설정 속성이다.

9.4.7.4. development

플로우를 개발 모드로 변경하려면 이것을 true로 설정하라. 개발 모드는 메시지 번들 같은 독립적인 플로우 리소스 변경을 포함하여 플로우 정의 변경 핫-릴로딩을 변경한다.

9.5. flow-executor 옵션

이번 장에서는 flow-executor 설정 옵션을 살펴본다.

9.5.1. 플로우 실행 리스너 부착하기

flow-execution-listeners 엘리먼틀르 사용하여 플로우 실행 생명주기 리스너를 등록한다.

<flow-execution-listeners>
    <listener ref=”securityListener”/>
    <listener ref=”persistenceListener”/>
</flow-execution-listeners>
           
특정 플로우만 관찰할 리스너를 설정할 수도 있다.

<listener ref=”securityListener” criteria=”securedFlow1,securedFlow2″/>
           

9.5.2. FlowExecution 영속성 튜닝하기

flow-execution-repository 엘리먼트를 사용하여 플로우 실행 영속성 설정을 튜닝한다.

<flow-execution-repository max-executions=”5″ max-execution-snapshots=”30″ />
           
9.5.2.1. max-executions

max-executions 속성을 설정하여 사용자 세션당 생성할 수 있는 플로우 실행 수 상한선을 설정한다.

9.5.2.2. max-execution-snapshots

max-execution-snapshots 속성을 설정하여 플로우 실행당 가질 수 있는 히스토리 스냅샷 수 상한선을 정할 수 있다. 스냅샷을 못찍게 하려면 이 값을 0으로 설정하라. 무한대로 스냅샷을 찍으려면 이 값을 -1로 설정하라.