Raible's Wiki

Raible Designs
Wiki Home
News
Recent Changes

AppFuse

Homepage
  - Korean
  - Chinese
  - Italian
  - Japanese

QuickStart Guide
  - Chinese
  - French
  - German
  - Italian
  - Korean
  - Portuguese
  - Spanish
  - Japanese

User Guide
  - Korean
  - Chinese

Tutorials
  - Chinese
  - German
  - Italian
  - Korean
  - Portuguese
  - Spanish

FAQ
  - Korean

Latest Downloads

Other Applications

Struts Resume
Security Example
Struts Menu

Set your name in
UserPreferences

Edit this page


Referenced by
Articles
Articles_ko
CreateActions_ko




JSPWiki v2.2.33

[RSS]


Hide Menu

ValidationAndList_ko


Part V: Validation 과 List 페이지 추가하기 - personForm 에 firstName 과 lastName 필드를 필수 요소로 하는 validation logic(검증 로직)을 추가하고 database에 있는 모든 person 레코드를 보여주는 list 페이지를 만듭니다.

이 tutorial 은 Part III: Actions 클래스 들과 JSP 작성하기에 의존 되어 있습니다.

About this Tutorial

이 튜토리얼에서는 PersonForm object 에 Struts Validator 를 사용해서 Valication logic(client and server-side) 을 작성하는 방법을 알려줍니다. 그리고 Display Tab Library 를 이용해서 database에 있는 모든 사람들 정보를 보여주는 list screen 을 만들것입니다.

Table of Contents

  • [1] Person.java 에 XDoclet 태그(@struts.validator)를 추가합니다.
  • [2] validation 이 추가된 JSP 페이지를 확인하고 테스트 합니다.
  • [3] DAO 와 Manager 테스트에 testGetPeople 메소드를 추가합니다.
  • [4] PersonDao 와 Manager 에 getPeople 메소드를 추가합니다.
  • [5] Action 테스트에 testSearch 메소드를 추가합니다.
  • [6] Action 클래스에 search 메소드를 추가합니다.
  • [7] personList.jsp 을 만들고 Canoo 테스트를 합니다.
  • [8] 메뉴에 링크를 추가 합니다.

Person.java 에 XDoclet 태그(@struts.validator)를 추가합니다 [#1]

일반적으로 Struts Validator 를 사용 하려면 validation.xml 을 직접 작성해야 합니다. 만약 AppFuse 를 사용하지 않는다면, Validator Plugin 설정과 ApplicationResources.properties 에 error key 들을 작성해야 합니다. 이에 대한 더 자세한 정보를 원하면 Validation Made Easy Tutorial를 참조해 보세요.

XDoclet 을 사용하면 훨씬 쉽습니다 - 우리는 우리가 작성한 POJO (Person.java) 에 @struts.validator 태그만을 작성하면 됩니다.
(src/dao/**/model/Person.java) 파일을 열고 getFirstName 와 getLastName 에 @struts.validator type="required" 를 추가 합니다.


    /**
     @return Returns the firstName.
     * @struts.validator type="required"
     * @hibernate.property column="first_name"
     */
    public String getFirstName() {
        return this.firstName;
    }

    /**
     @return Returns the lastName.
     * @struts.validator type="required" 
     * @hibernate.property column="last_name"
     */
    public String getLastName() {
        return this.lastName;
    }

이 태그에 msgkey 속성을 추가 하면 이 에러에 대한 기본 message key 를 override 해서 사용 할 수 있습니다.


@struts.validator type="required" msgkey="errors.required"

type="required" 에 대한 기본 메세지 키는 errors.required 이므로 나는 대부분 이것을 default 로 놔둡니다. 이 키 정의는 web/WEB-INF/classes/ApplicationResources_*.properties 에 정의되어있습니다. XDoclet documentation 에서는 setters 에다 이 태그를 작성하라고 하는데 우리는 이 태그를 getters 에 작성한 것을 당신은 아마 알아차렸을겁니다. 그 이유는 PersonForm.java 파일을 만들때 template file (metadata/template/struts_form.xdt) 에서 이 태그를 setters 에 옮겨서 작성하게 되어있기 때문입니다.

이제 Person.java 를 저장하고 ant clean webdoclet 을 실행하면 build/appfuse/WEB-INF/ 에 validation.xml 파일이 만들어 질겁니다. 이 파일에는 "personForm" 을 위한 entry 가 포함되어 있을겁니다.


      <form name="personForm">
              <field property="firstName"
                     depends="required">

                  <arg0 key="personForm.firstName"/>
              </field>
              <field property="lastName"
                     depends="required">

                  <arg0 key="personForm.lastName"/>
              </field>
      </form>

personForm.jsp 에 client-side validation(자바스크립트를 이용한 폼 유효성 체크) 를 사용하려면 javascript JSP tag(html:javascript) 와 script 태그를 personForm.jsp 의 하단에 작성 해야 합니다. 이것은 이미 작성되어 있고(AppGen에게 감사) 단지 여러분은 코멘트 처리를 없애기만 하면 됩니다.


<html:javascript formName="personForm" cdata="false"
    dynamicJavascript="true" staticJavascript="false"/>
<script type="text/javascript" 
    src="<html:rewrite page="/scripts/validator.jsp"/>"></script>

NOTE: 만약 validation rule 을 가지고 있는 nested object 가 있다면, 그것들은 nested object 로 부터 꺼내져서 validation.xml 에 포함되게 될 것입니다.
(User 클래스와 Address 라는 클래스가 있다면 User.address 에 대해 address의 내부 벨리데이션 까지도 User validation 에 포함되게 된다는 뜻 같음)
이것은 metadata/templates/struts_form.xdt에 의해서 form 이 만들어 질때 nested object 의 setter 메소드에 @struts.validator 가 추가되기 때문입니다.
만약 nested-object 와 many-to-many bi-directional 관계라면 문제가 생깁니다.(무한루프에 의한 메모리 오버?)
여기에 두가지 해결책이 있습니다. 하나는 struts_form.xdt 에 @struts.validator 를 없애고 필요에 따라서 수동적으로 POJO 에 setter 에 넣는 방법이고 또하나는 여기를 참조하세요.

validation 이 추가된 JSP 페이지를 확인하고 테스트 합니다 [#2]

이 form 에 Validation 을 구성하였고 이 폼이 validate="true" 속성을 가진 action-mapping 에 의해 사용 된다면 이 validation rule 는 적용 될겁니다. 지난 튜토리얼에서, 우리는 PersonAction 에 대한 "savePerson" action-mapping 을 추가했었습니다. 이 action-mapping 에 대한 XDoclet tag 는 다음과 같습니다.


 * @struts.action name="personForm" path="/savePerson" scope="request"
 *  validate="true" parameter="method" input="edit"

web/pages/personForm.jsp 가 태그를 가지고 있는 한, 우리가 이 form 을 저장하려고 하면 validation 이 작동 할 것입니다. ant db-load deploy 을 실행하고, Tomcat 을 구동시켜서 http://localhost:8080/appfuse/editPerson.html?id=1 으로 가보세요.

firstName 과 lastName 필드의 내용을 없애고 save 버튼을 클릭하면 다음과 같은 JavaScript alert 창을 보게될 것입니다.

validation-required.png

모든것이 정말로 잘 작동되는지 확인하세요. JavaScript 를 끄고 server-side validation 이 잘 작동하는지 확인해보세요. (내가 가장 즐겨쓰는)Mozilla Firebird 브라우저에서는 쉽게 할 수 있습니다. Tools → Options → Web 으로 가서 "Enable JavaScript" 의 체크를 없애면 됩니다. 자 이제 다시 폼의 내용들을 지우고 save 버튼을 눌러보세요. 다음과 같은 화면을 볼 수 있을겁니다.

validation-required-nojs.png

다음과 같은 validation 에러를 볼 수 없다면. 다음과 같은 이유일 수 있습니다.

  • firstName 과 lastName 필드가 비어있지만 정상적으로 저장됬다는 메세지가 나온다면.
web/pages/personForm.jsp 의 <html:form> 태그가 action="editPerson" 으로 되있기 때문일 겁니다. action="savePerson"으로 되있는지 확인해 보세요.
  • save 를 클릭했는데 빈페이지가 나온다.
빈 페이지는 "savePerson" 의 "input" 속성이 잘못 구성되어있다는 뜻입니다. "input" 속성이 local 혹은 global 영역의 action-forward 를 가리키고 있는지 확인해 보세요. 이 예제에서는 tile's definitaion 의 .personDetail 를 가리키는 input="edit" 으로 되어있어야 합니다. 내 경험에 의하면 input 의 값은 반드시 ㅁaction 이 아닌 forward 이어야 합니다.
만약 오직 servier-side validation(JavaScript가 아닌) 만을 원한다면, <html:form> (web/pages/personForm.jsp) 의 속성과 onsubmit 속성과 이 페이지 하단에 있는 Validator JavaScript 태그를 삭제하면 됩니다.


<html:javascript formName="personForm" cdata="false"
      dynamicJavascript="true" staticJavascript="false"/>
<script type="text/javascript" 
      src="<html:rewrite page="/scripts/validator.jsp"/>"></script>

DAO 와 Manager 테스트에 testGetPersons 메소드를 추가합니다 [#3]

List screen(master screen 이라고도 부름) 을 만들려면, person 테이블의 모든 row 들을 반환하는 메소드를 만들어야 합니다.
이러한 메소드에 대한 테스트를 PersonDaoTest 와 PersonManagerTest 클래스에 추가해 봅시다.
나는 주로 이런 메소드를 getEntities (즉, getPersons) 으로 이름 짓습니다. 그러나 getAll 또는 search 등의 이름을 사용할 수도 있습니다. 이것은 전적으로 여러분의 취향에 달려 있는겁니다.

test/dao/**/dao/PersonDaoTest.java 를 열고 testGetPeople 메소드를 추가 합니다:


    public void testGetPeople() {
        person = new Person();
        List results = dao.getPeople(person);
        assertTrue(results.size() 0);
    }

내가 person 오브젝트를 getPeople 메소드에 전달한 이유는 앞으로 필터링(person 내의 값들에 의한) 기능을 사용하기 위해서 입니다. 이 파라미터를 getPeople() 메소드에 사용하는 것은 선택적이지만 이 튜토리얼의 나머지 부분에서는 이 파라미터를 전달하는것으로 간주하고 진행됩니다.

이제 test/service/**/service/PersonManagerTest.java 을 열고 testGetPeople 메소드를 추가합니다:


    public void testGetPeople() throws Exception {
        List results = new ArrayList();
        person = new Person();
        results.add(person);

        // set expected behavior on dao
        personDao.expects(once()).method("getPeople")
            .will(returnValue(results));

        List people = personManager.getPeople(null);
        assertTrue(people.size() == 1);
        personDao.verify();
    }

이 테스트들을 컴파일 하려면 PersonDao 와 PersonManager 인터페이스 그리고 그 구현클래스들에 getPeople() 메소드를 추가해야 합니다.

PersonDao 와 Manager 에 getPeople 메소드를 추가합니다 [#4]

src/dao/**/dao/PersonDao.java 을 열고 getPeople()를 추가합니다:


    public List getPeople(Person person)

이제 src/service/**/service/PersonManager.java 에 같은 모양의 메소드를 작성합니다. 모든 파일을 저장하고 이 파일들을 기존의 작성한 test 들에 import 합니다.
이제 이 인터페이스의 구현클래스들에서 getPeople()를 구현해야 합니다.
src/dao/**/dao/hibernate/PersonDaoHibernate.java 을 열고 다음 메소드를 추가합니다.


    public List getPeople(Person person) {
        return getHibernateTemplate().find("from Person");
    }

보다시피 여기서는 person 파라미터 가지고 하는 일이 아무것도 없습니다. 현재 위 예의 person 파라미터는 단지 placeholder 의 역할을 하고 있는겁니다. 차후에 위 파라미터(person)의 속성값들을 통해 필터 기능을 원할 수도 있습니다(Hibernate's Query Language (HQL) 나 Criteria Query를 이용해서).

Criteria Query 를 사용한 예입니다:


    // filter on properties set in the person object
    HibernateCallback callback = new HibernateCallback() {
        public Object doInHibernate(Session sessionthrows HibernateException {
            Example ex = Example.create(person).ignoreCase().enableLike(MatchMode.ANYWHERE);
            return session.createCriteria(Person.class).add(ex).list();
        }
    };
    return (ListgetHibernateTemplate().execute(callback);

이제 src/service/**/impl/PersonManagerImpl.java 에 getPeople() 메소드를 구현합니다:


    public List getPeople(Person person) {
        return dao.getPeople(person);
    

수정한 모든 것들을 저장하고, 다음의 명령으로 두 테스트를 실행합니다.

  • ant test-dao -Dtestcase=PersonDao
  • ant test-service -Dtestcase=PersonManager

모든 것이 작동한다면 잘한겁니다! 이제 이 모든 기능들을 web tier(웹 계층) 에 추가해야 합니다.

Action 테스트에 testSearch 메소드를 추가합니다 [#5]

test/web/**/action/PersonActionTest.java 를 열고 다음 메소드를 추가합니다:


    public void testSearch() {
        setRequestPathInfo("/editPerson");
        addRequestParameter("method""Search");
        actionPerform();

        verifyForward("list");

        assertNotNull(getRequest().getAttribute(Constants.PERSON_LIST));
        verifyNoActionErrors();
    

src/dao/**/Constants.java 파일에 PERSON_LIST 를 추가할때까지 이 클래스는 컴파일이 안될겁니다.

나는 주로 이 파일에 들어있는 비슷한 변수를 복사해서 사용합니다 - i.e. USER_LIST.


    /**
     * The request scope attribute that holds the person list
     */
    public static final String PERSON_LIST = "personList";

작업한 모든 것을 저장합니다. 아직 PersonAction.search() 메소드를 작성하지 않았기 때문에 ant test-web -Dtestcase=PersonAction 는 작동하지 않을겁니다.

Action 클래스에 search 메소드를 추가합니다 [#6]

src/web/**/action/PersonAction.java 를 열고 리스트 스크린으로 forward 하기 위한 아래의 XDoclet 태그를 파일 상단에 추가 합니다


 * @struts.action-forward name="list" path="/WEB-INF/pages/personList.jsp" 

이제 PersonAction 클래스에 search 메소드를 추가합니다.

나는 UserAction.search() 를 이 메소드를 위한 template 으로 사용합니다.


    public ActionForward search(ActionMapping mapping, ActionForm form,
                                HttpServletRequest request,
                                HttpServletResponse response)
            throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("Entering 'search' method");
        }

        PersonManager mgr = (PersonManagergetBean("personManager");
        List people = mgr.getPeople(null);
        request.setAttribute(Constants.PERSON_LIST, people);

        // return a forward to the person list definition
        return mapping.findForward("list");
    }

ant test-web -Dtestcase=PersonAction 을 실행합니다.

Nice! BUILD SUCCESSFUL
Total time: 1 minute 26 seconds

personList.jsp 을 만들고 Canoo 테스트를 합니다 [#7]

web/pages 에 personList.jsp 가 있을겁니다. 만약 없다면 viewgen 을 사용해서 만들면 됩니다. extras/viewgen 으로 이동한후 ant -Dform.name=PersonForm 을 실행합니다. 이것을 extras/viewgen/build 에 PersonFormList.jsp 를 만들겁니다.

web/pages 에 personList.jsp 을 복사한뒤 이를 열어 수정합니다.

파일 상단에 보면 수정 페이지의 forward 를 page-scope 변수에 담게 하는 태그가 있습니다. 이것의 값은 이미 "editPerson" 으로 되어 있습니다.


<%-- For linking to edit screen --%>
<bean:struts id="editURL" forward="editPerson"/>

위 forward 와 리스트를 보여주는 forward 를 metadata/web/global-forwards.xml 에 추가합니다. 이렇게 하면 이 내용이 struts-config.xml 에 포함되게 됩니다.


        <forward name="editPerson" path="/editPerson.html"/>
        <forward name="viewPeople" path="/editPerson.html?method=Search"/>

아마도 당신이 변경하길 원하는 다른 것은 당신이 목록화한 항목의 복수형태일것이다. 이 예제에서 생성된 이름은 "person"이고 이것은 아마도 people가 될것이다. 31번 라인이나 그 근처에서, 당신은 다음의 라인을 볼수 있을것이다.

<display:setProperty name="paging.banner.items_name" value="persons"/>

이것을 다음과 같이 변경한다.

<display:setProperty name="paging.banner.items_name" value="people"/>

마지막으로, 타이틀과 해더키(personList.title and personList.heading)를 web/WEB-INF/classes/ApplicationResources.properties에 추가한다. 이 파일을 열고 다음을 추가한다.

# -- person list page --
personList.title=Person List
personList.heading=All People

personList.title은 브라우저의 타이틀(<title> 태그)바에서 보여지는 것이고 personList.heading는 페이지 내용 이전에 <h1>태그에 넣어질것이다.

이 시점에서, 당신은 ant clean deploy를 실행할수 있을것이다. Tomcat을 실행하고 당신의 브라우저에서 http://localhost:8080/appfuse/editPerson.html?method=Search 페이지를 보라.

지금 우리는 목록 화면을 보고있다. 새로운 Person을 추가하거나 삭제한후에 페이지가 변경되는 것을 보자. src/web/**/action/PersonAction.java에서, save, delete 그리고 cancel 메소드내 mapping.findForward("mainMenu")를 변경하자.


    return mapping.findForward("viewPeople");

당신은 test/web/**/action/PersonActionTest.java의 testRemove메소드내 verifyForward("mainMenu")verifyForward("viewPeople")로 변경할 필요가 있을것이다. 마지막으로, 수정될 필요가 있는 "AddPerson" 과 "DeletePerson"를 테스트하자. test/web/web-tests.xml를 열고 "AddPerson" target내 다음 라인을 변경하자.

<verifytitle description="Main Menu appears if save successful" 
    text=".*${mainMenu.title}.*" regex="true"/>

위 라인을 아래와 같이 변경한다.

<verifytitle description="Person List appears if save successful" 
    text=".*${personList.title}.*" regex="true"/>

그 다음 "DeletePerson" target에서, 다음 라인을 변경하자.

<verifytitle description="display Main Menu" 
    text=".*$(mainMenu.title}.*" regex="true"/>

위 라인을 아래와 같이 변경한다.

<verifytitle description="display Person List" text=".*${personList.title}.*" regex="true"/>

우리는 "list"대신에 "viewPeople"를 사용한다. 그래서 personForm.jsp로 간단하게 포워딩하는 것보다 search메소드가 수행될것이다.

이 페이지가 작동하는것을 보기 위해, test/web/web-tests.xml내 새로운 JSP테스트를 생성하자.


    <!-- Verify the people list screen displays without errors -->
    <target name="SearchPeople" 
        description="Tests search for and displaying all people">
        <webtest name="searchPeople">
            &config;
            <steps>
                &login;

                <invoke description="click View People link" url="/editPerson.html?method=Search"/>
                <verifytitle description="we should see the personList title" 
                    text=".*${personList.title}.*" regex="true"/>
            </steps>
        </webtest>
    </target>

당신은 "PersonTests" target에 "SearchPeople" target를 추가하길 원할것이다. 그래서 이것은 다른 person관련 테스트와 함께 수행될것이다.


    <!-- runs person-related tests -->
    <target name="PersonTests" 
        depends="SearchPeople,EditPerson,SavePerson,AddPerson,DeletePerson"
        description="Call and executes all person test cases (targets)">
        <echo>Successfully ran all Person JSP tests!</echo>
    </target>

지금 당신은 ant test-canoo -Dtestcase=SearchPeople (Tomcat이 구동중이지 않다면 ant test-jsp)를 실행할수 있고 "BUILD SUCCESSFUL"이라는 결과가 나오길 바랄것이다. 만약 그렇다면, 제대로 작업이 수행된것이다.

메뉴에 링크추가하기 [#8]

가장 마지막 단계는 사용자가 볼수 잇는 list, add, edit, delete함수를 만드는 것이다. 가장 간단한 방법은 web/pages/mainMenu.jsp에 새로운 링크를 추가하는 것이다.

NOTE: mainMenu.jsp내 다른 링크는 를 사용하지 않는다. 그래서 이 JSP는 AppFuse내 다른 다양한 웹 프레임워크(이를테면, Spring MVC와 WebWork) 구현물간에 공유될수 있다.


    <li>
        <html:link forward="viewPeople">
            <fmt:message key="menu.viewPeople"/>
        </html:link>
    </li>

menu.viewPeople는 web/WEB-INF/classes/ApplicationResources.properties파일내 항목이고 값은 아래와 같다..

menu.viewPeople=View People

다른 대안은 메뉴에 이것을 추가하는 것이다. 이것을 하기 위해, web/WEB-INF/menu-config.xml에 다음을 추가한다.


<Menu name="PeopleMenu" title="menu.viewPeople" forward="viewPeople"/>

위 XML이 <Menu>가 아닌 <Menus> 태그내 위치하는지 확인하라. 그리고 나서 다음과 같은 web/common/menu.jsp에 새로운 메뉴를 추가한다.


<%@ include file="/common/taglibs.jsp"%>

<div id="menu">
<menu:useMenuDisplayer name="ListMenu" permissions="rolesAdapter">
    <menu:displayMenu name="AdminMenu"/>
    <menu:displayMenu name="UserMenu"/>
    <menu:displayMenu name="PeopleMenu"/>
    <menu:displayMenu name="FileUpload"/>
    <menu:displayMenu name="FlushCache"/>
    <menu:displayMenu name="Clickstream"/>
</menu:useMenuDisplayer>
</div>

지금 당신이 ant clean deploy를 실행한다면, Tomcat을 실행하고 http://localhost:8080/appfuse/mainMenu.html를 보라. 당신은 아래와 같은 화면을 보게 될것이다.

new-menu-item.png

왼쪽(mainMenu.jsp에서)에 새로운 링크가 있고 우리의 메뉴는 오른쪽(menu.jsp에서)에 있다.

다 되었다.!

당신은 AppFuse와 Struts를 가지고 독립적인 상세한 페이지들을 개발하는 완전한 생명주기를 완성했다. 축하한다! 지금 당신은 실패없이 당신의 애플리케이션내 모든 테스트를 수행할수 있을것이다. 테스트하기 위해, tomcat를 중지하고 ant clean test-all를 실행한다. 이것은 당신의 프로젝트내 모든 단위테스트를 수행할것이다. 이것은 ant setup-db setup-tomcat test-all을 사용하여 AppFuse를 쉽게 셋업하고 테스트할수 있다. 또한 당신은 좀더 견고한 예제를 찾는다면, Struts Resume를 보라.

즐거운 시간이 되길.!

BUILD SUCCESSFUL
Total time: 2 minutes 31 seconds

Attachments:


Go to top   Edit this page   More info...   Attach file...
This page last changed on 06-Nov-2006 13:53:00 MST by MattRaible.