添加校验和菜单 
本章将向你展示怎样为personForm添加校验逻辑,使得firstName和lastName在页面上变成必填field,并且添加一个屏幕列表来显示数据库中所有人的记录。 
这部分内容依赖于Part III: 创建Action和JSP 
本章将向你展示怎样使用Struts的Validator 为你的PersonForm对象添加校验逻辑(客户端和服务器端)。我们也会使用Display Tag Library 创建一个屏幕列表来显示数据库中所有的people。  
·   [1] 为Person.java 添加XDoclet Validator标签 
·   [2] 检查和测试添加了校验的JSP 
·   [3] 为 DAO和Manager Test添加testGetPeople方法 
·   [4] 为DAO和Manager添加getPeople()方法 
·   [5] 为Action Test 添加testSearch() 方法 
·   [6] 为Action添加search方法 
·   [7] 创建personList.jsp和Canoo test 
·   [8] 为菜单(Menu)添加链接(Link) 
为Person.java 添加XDoclet Validator标签  
正常情况下,如果你想使用Struts的Validator,你必须用手写validation.xml文件。如果你不使用AppFuse, 你也必须在ApplicationResources_en.properties文件中配置Validator Plugin和error key。如果你想获得更多的信息,请参见Validation Made Easy Tutorial 。 
我们要感谢Xdoclet,它使得这一切都变得很简单 – 你只需要为Person类添加几个@struts.validator标签。打开它 (src/dao/**/model/Person.java),然后修改getFirstName()和getLastName() 方法的JavaDoc,使它们包含 @struts.validator type="required" 标签。  
| 
     /**      * @struts.validator type="required"      * @hibernate.property column="first_name" length="50"      */     public String getFirstName() {         return this.firstName;     }
      /**      * @struts.validator type="required"       * @hibernate.property column="last_name" length="50"      */     public String getLastName() {         return this.lastName;     }   |   
你还可以在这个标签里添加一个msgkey属性( attribute),它用来覆盖错误的默认消息的key。  
| 
 @struts.validator type="required" msgkey="errors.required"   |   
type="required"的默认的Key已经是errors.required了,所以我经常保留它做为默认值。这个key被定义在web/WEB-INF/classes/ApplicationResources_*.properties中。 你也许注意到:虽然XDoclet的文档 说应该把这些标签放在Setter部分,但我们放到了getter部分,这是因为模板文件(metadata/template/struts_form.xdt) 会将这些标签放在产生的PersonForm.java的Setter部分。 
现在如果你保存Person.java,然后运行ant clean webdoclet,将会在目录build/你的项目名称/WEB-INF/下产生一个validation.xml文件. 它应该有一个为"personForm"使用的入口: 
| 
       <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中可以使用客户端校验,我们在PersonForm.jsp文件的底部添加一个<html:javascript>标签和一些脚本。下面的内容是已经存在的(感谢viewgen) – 你只需要将它们从注释中放开,它们被注释的原因是:如果指定了一个Form的名字并且校验规则不存在,Validator将会抛出一个异常。  
 
我个人认为这是一个 a bug ,但Struts的提交者们并不这样认为.  
<html:javascript formName="personForm" cdata="false" 
    dynamicJavascript="true" staticJavascript="false"/> 
<script type="text/javascript"  
    src="<html:rewrite page="/scripts/validator.jsp"/>"></script>  
现在你已经为你的Form配置了Validation,当这个Form在action-mapping中被配置后,并且设置了validate="true",这些校验规则将会生效。在上一章中, 我们为PersonAction添加"savePerson" action-mapping。这个action-mapping 的Xdoclet标签应该是这样的:  
| 
  * @struts.action name="personForm" path="/savePerson" scope="request"  *  validate="true" parameter="method" input="edit"   |   
这样,只要web/pages/personForm.jsp有<html:form action="savePerson">,当我们保存窗体时,校验将会生效。运行ant db-load deploy,启动Tomcat,然后浏览http://localhost:8080/appfuse/editPerson.html?id=1 。如果你删除了firstName和lastName field的值,然后单击save按钮,你应该会看到下面的JavaScript提示的警告: 
 
如果你确定一切都真正的如同期望的那样工作后,你就可以关掉JavaScript以保证可以使用服务器端的校验可以正常使用。在 Mozilla Firebird (我喜欢的浏览器)中,这些很容易,只是到Tools → Options → Web Features,然后去掉"Enable JavaScript"。现在如果你清除field的值,然后保存,你会看到如下的界面:  
 
如果你看不到上面的错误,那么可能你出现了下面的问题:  
·   窗体显示保存成功,但firstName和lastName field是空的。  
这是因为web/pages/personForm.jsp 中的<html:form>有 action="editPerson" – 你要改为action="savePerson"。 
·   你单击了save,但出现了一个空白页面(blank page)。 
你的"savePerson" forward的"input" attribute配置不正确,一定要保证它指向了一个local或者global action-forward。在这个例子中,它应该是 input="edit"(指向.personDetail tile的定义)。根据我的经验,input的值必须是一个forward,不能是一个指向action的path。  
如果你只想使用服务器端校验(server-side validation(没有JavaScript)) ,你可以删除<html:form>的onsubmit属性(在 web/pages/personForm.jsp中) ,同时也要删除页面底部的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 Test添加testGetPeople方法  
为了创建一个List screen (也叫做master screen), 我们需要创建一个方法,这个方法将返回person 表中的所有记录,我们先为类PersonDAOTest和PersonManagerTest添加测试方法。我通常将这个方法命名为getEntities (如: getUsers),但你也可以使用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() {         List results = mgr.getPeople(new Person());         assertTrue(results.size() > 0);     }   |   
为了使这些测试类能够被编译通过, 你需要为类PersonDAO和接口PersonManager添加getPeople()方法,然后实现它们。 
为DAO和Manager添加getPeople()方法  
打开src/dao/**/dao/PersonDAO.java,然后添加getPeople()方法声明(method signature):  
| 
     public List getPeople(Person person);   |   
现在为src/service/**/service/PersonManager.java添加同样的方法声明。保存你的文件,然后为你的测试文件添加必要的import。下一步我们需要在我们的实现类中实现getPeople() 方法。 打开 src/dao/**/dao/hibernate/PersonDAOHibernate.java,然后添加下面的方法:  
| 
     public List getPeople(Person person) {         return getHibernateTemplate().find("from Person");     }   |   
你可能注意到这里没有用person参数做任何事情. 现在它只是一个占位符 – 将来你可能需要使用Hibernate's Query Language (HQL) or using Criteria Queries 来过滤它的属性值。  
下面是一个使用 Criteria Query的例子:  
| 
     Example example = Example.create(person)                              .excludeZeroes()    // exclude zero valued properties                              .ignoreCase();      // perform case insensitive string comparisons     try {         return getSession().createCriteria(Person.class)                            .add(example)                            .list();     } catch (Exception e) {         throw new DataAccessException(e.getMessage());     }     return new ArrayList();  |   
现在我们在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层添加这个retrieve all 功能。 
为Action Test 添加testSearch() 方法  
打开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变量后,这个类才能编译通过。 
我一般会复制一个已经存在的类似的变量 – 如: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。 
打开src/web/**/action/PersonAction.java,然后在文件顶部添加Xdoclet标签- 为list screen添加forward。  
| 
  * @struts.action-forward name="list" path="/WEB-INF/pages/PersonFormList.jsp"   |   
现在在PersonAction类的主题部分添加search方法。 
我使用UserAction.search()作为这个方法的模板。  
| 
     public ActionForward search(ActionMapping mapping, ActionForm form,                                 HttpServletRequest request,                                 HttpServletResponse response)             throws Exception {         if (log.isDebugEnabled()) {             log.debug("Entering 'search' method");         }
          PersonManager mgr = (PersonManager) getBean("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.  
多漂亮啊!  
BUILD SUCCESSFUL Total time: 1 minute 26 seconds  
创建personFormList.jsp(PersonList。jsp)和Canoo test  
在目录web/pages 下应该已经有一个PersonFormList.jsp(PersonList.Jsp)文件了,如果没有你可以使用viewgen创建它。使用命令行,进入到目录extras/viewgen下,运行ant -Dform.name=PersonForm。这将会在目录extras/viewgen/build下产生一个PersonFormList.jsp(PersonList.Jsp)文件。 
在目录web/pages下找到PersonFormList.jsp(PersonList.Jsp),打开。  
这个文件的头部是一个 <bean:struts>标签,它将edit screen的 forward作为一个page范围的变量。这个"editPerson"应该已经有一个值了。(This should already have a value of "editPerson")  
| 
 <%-- For linking to edit screen --%> <bean:struts id="editURL" forward="editPerson"/>   |   
将下面内容添加到metadata/web/global-forwards.xml,也是一个列表的视图。这样,它们就可以被包含到struts-config.xml文件中了。 
(Add this to the metadata/web/global-forwards.xml, as well as one for viewing the list. This way, they will get included in our struts-config.xml file.)  
| 
         <forward name="editPerson" path="/editPerson.html"/>         <forward name="viewPeople" path="/editPerson.html?method=Search"/>   |   
你用来创建这个JSP的模板应该有一个对应硬编码的id属性的列。所以Xdoclet会把它添加2次。从PersonFormList.jsp(PersonList.Jsp)文件中删除下面的内容。 
| 
     <display:column property="id" sort="true" headerClass="sortable"         titleKey="personForm.id"/>   |   
如果你知道更改 extras/viewgen/src/List_jsp.xdt文件能够不包含这个列标签的办法,请告诉我。  
你可能想去改变的另一件事情是:这个例子中产生的名字是"persons" ,它可能是people,在第31行附近,你应该可以看到下面的内容:  
<display:setProperty name="paging.banner.items_name" value="persons"/>  
更改为:  
<display:setProperty name="paging.banner.items_name" value="people"/>  
最后,在web/WEB-INF/classes/ApplicationResources_en.properties 文件中添加title 和heading的key (personList.title和personList.heading)。打开这个文件,然后添加下面的内容:  
# -- person list page -- 
personList.title=Person List 
personList.heading=All People  
此时,你应该可以执行ant clean deploy,启动Tomcat,查看这个页面: http://localhost:8080/appfuse/editPerson.html?method=Search 。 
现在我们已经有一个List Screen, 我们来修改这个添加或者删除了一个新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") 。最后,Canoo test的 "AddPerson"和"DeletePerson"需要被修改。打开test/web/web-tests.xml,然后在"AddPerson" target 部分更改如下内容:  
<verifytitle stepid="Main Menu appears if save successful"  
    text="${webapp.prefix}${mainMenu.title}"/>  
改为:  
<verifytitle stepid="Person List appears if save successful"  
    text="${webapp.prefix}${personList.title}"/>  
然后,在"DeletePerson" target部分,将下面的内容:  
<verifytitle stepid="display Main Menu"  
    text="${webapp.prefix}$(mainMenu.title)"/>  
更改为:  
<verifytitle stepid="display Person List" text="${webapp.prefix}${personList.title}"/>  
我们使用"viewPeople"代替"list",这样search方法将会被执行。这比使用指向PersonForm.jsp的简单的list (它是一个forward)要好。  
我们要测试这个显示页是否工作,在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">         <canoo name="searchPeople">             &config;             <steps>                 &login;                 <invoke stepid="click View People link" url="/editPerson.html?method=Search"/>                 <verifytitle stepid="we should see the personList title"                      text="${webapp.prefix}${personList.title}"/>             </steps>         </canoo>     </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) 。  
对于这个list的最后一步: 使用户可以看到add, edit和delete功能。最简单的办法是在web/pages/mainMenu.jsp文件中添加一个链接的列表:  
NOTE: 不要使用mainMenu.jsp 中的其他的链接,这样可以保证JSP能够被其他的Web框架使用(如:Spring MVC、WebWork)。 [The other links in mainMenu.jsp don't use so this JSP can be shared among the various web framework implementations in AppFuse (如:Spring MVC and WebWork).] 
| 
     <li>         <html:link forward="viewPeople">             <fmt:message key="menu.viewPeople"/>         </html:link>     </li>   |   
在web/WEB-INF/classes/ApplicationResources_en.properties 中有一个条目: menu.viewPeople  
menu.viewPeople=View People  
另一种办法是:添加下面的内容到web/WEB-INF/menu-config.xml中: 
[The other (more likely) alternative is that you'll want to add it to the menu. To do this, add the following to web/WEB-INF/menu-config.xml:]  
| 
 <Menu name="PeopleMenu" title="menu.viewPeople" forward="viewPeople"/>   |   
要保证<Menus> tag中有上面的XML代码,而不是在另一个<Menu>中。 然后添加这个心菜单到web/pages/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 ,你应该可以看到下面的内容: 
 
需要注意的是左边多了一个新的链接,它从mainMenu.jsp中获取,在右边的菜单中也多了一个新的链接,它从menu.jsp中获取。  
恭喜你,至此,你已经完成了全部的使用AppFuse进行开发的流程!如果你成功的运行了上面的所有的测试用例,那我们现在开始真正的测试。停止Tomcat,运行ant clean test-all. 这将运行所有的工程中的单元测试. 提示:使用ant setup-db、 setup-tomcat、 test-all 可以很容易的对我们的系统雏形进行测试。另外,如果你正在寻找稳定性和健壮性的例子 - checkout Struts Resume.  
多好的一天!  
BUILD SUCCESSFUL Total time: 2 minutes 31 seconds  
如果你喜欢,你可以在这里下载我们这个指南中创建的文件。  
 
  |