惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
V
Vulnerabilities – Threatpost
TaoSecurity Blog
TaoSecurity Blog
C
Cybersecurity and Infrastructure Security Agency CISA
P
Proofpoint News Feed
G
GRAHAM CLULEY
S
Securelist
P
Palo Alto Networks Blog
MongoDB | Blog
MongoDB | Blog
A
Arctic Wolf
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
WordPress大学
WordPress大学
Project Zero
Project Zero
T
Threat Research - Cisco Blogs
L
Lohrmann on Cybersecurity
C
Cyber Attacks, Cyber Crime and Cyber Security
F
Fortinet All Blogs
博客园 - 叶小钗
B
Blog RSS Feed
C
Cisco Blogs
Google DeepMind News
Google DeepMind News
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Apple Machine Learning Research
Apple Machine Learning Research
G
Google Developers Blog
K
Kaspersky official blog
D
Docker
Latest news
Latest news
Cisco Talos Blog
Cisco Talos Blog
T
Tor Project blog
Cyberwarzone
Cyberwarzone
Security Latest
Security Latest
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Spread Privacy
Spread Privacy
Microsoft Azure Blog
Microsoft Azure Blog
C
Check Point Blog
J
Java Code Geeks
Simon Willison's Weblog
Simon Willison's Weblog
T
Tenable Blog
Recent Announcements
Recent Announcements
T
Tailwind CSS Blog
H
Help Net Security
L
LINUX DO - 热门话题
T
The Exploit Database - CXSecurity.com
Jina AI
Jina AI
S
SegmentFault 最新的问题
MyScale Blog
MyScale Blog
NISL@THU
NISL@THU
美团技术团队
腾讯CDC

博客园 - 书生无用

Improve the speed to display a transparent background winform Replace the with CR in Microsoft Excel using maven and eclipse created a mutiple module project for eclipse 开源的codecoverage(测试代码覆盖率)检测工具 使用zoudry编辑blog的设置 wiki为什么会流行 实现 DWR Sample 的几个要注意的问题(2) DWR Sample的两个问题 几个有意思的在线服务网站 静悄悄的变化 APPFUSE wiki中文文档问题解决了 又是一个艳阳天 有几个Gmail、Wallop邀请 Appfuse开发实践(五)—— 增加校验和列表页面 Appfuse开发实践(四)——创建Webwork 框架的 Actions和JSP Appfuse开发实践(三) —— 创建Manager类 Appfuse开发实践(二)——创建DAO对象 Appfuse里面带的Ant任务列表 Appfuse实践(一)——配置安装
appfuse使用Taperstry框架(一)——创建Tapestry框架页面
书生无用 · 2005-01-24 · via 博客园 - 书生无用

第三部分: 创建 Tapestry page 和 HTML 模版 - 介绍如何在AppFuse 项目中创建 Tapestry页面和模版。

这个指南依赖于 第二部分: 创建新的 Managers 对象

说明

这个指南将会告诉你如何创建 Tapestry 页面和 HTML 模版。同时也将说明如何编写 JUnit 测试以测试PersonForm页面。我们创建的这个JSP页面将会使用我们在”创建Managers类“ 指南创建的PersonManager类。在大多数的web框架(web frameworks)中,控制逻辑都写在一个类似"Action" 的类中。但是在Tapestry中,这些控制逻辑通常可以以类似"Page"的方式被引用。使用这些pages的方法被称为listeners。这份指南不会讲述关于Tapestry工作机制的问题,但是会知道你快速上手使用Taperstry框架。如果你希望跟深入的学习Taperstry的有关知识,我建议你阅读Howard Lewis Ship'的 Tapestry in Action一书。当我在把Tapersty集成到Appfuse的过程中就把这部书放在身边以便随时查阅。感谢Howard的帮助!

我将以斜体字说明在 实际过程 使用的经验。

现在让我们开始在Appfuse's的整体架构下创建新的页面和HTML模版。如果你此时还没有安装Tapestry模块,请马上运行ant install-tapestry

内容提要

  • [1] 使用XDoclet创建 pageForm.html
  • [2] 创建 PersonFormTest 以测试 PersonForm
  • [3] 创建 PersonForm
  • [4] 运行 PersonFormTest
  • [5] 在你的浏览器中参看刚创建的PersonForm
  • [6] 创建 Canoo WebTests 以模拟测试 PesonForm 对浏览器中操作的响应

使用XDoclet创建 pageForm.html 模版[#1]

在这一步,你将自动生成一个HTML模版以显示Person对象的信息。这个模版将包含符合Taperstry语法规则的待填充表单元素 - 就是那些HTML文件中带有"jwcid" 的属性。用来自动生成HTML模版的AppGen工具是基于 StrutsGen工具实现的 - 这个工具最初是由Erik Hatcher开发的。基本上是由一对Java类和一组XDoclet模版组成。这些文件都可以在extras/appgen目录下找到。

下面给出产生这个HTML模版文件和一个包含了form的标签元素的properties文件的具体步骤:

  • 在命令行环境下,切换到 "extras/appgen" 目录
  • 执行 ant -Dmodel.name=Person -Dmodel.name.lowercase=person 将在extras/appgen/build/gen目录下产生这组文件。事实上,它将产生你在完成这个向导所用到的所有文件。不过,我们现在仅仅抓住那些你需要的那些文件。
    • web/WEB-INF/classes/Person.properties (表单元素的标签[label]字符串)
    • web/pages/PersonForm.html (显示一个Person对象信息的HTML 模版文件)
    • web/pages/PersonForm.page (说明前一页面的Page)
    • web/pages/PersonList.html (显示一个People列表的HTML 模版文件)
    • web/pages/PersonList.page (说明前一页面的Page)
  • 把Person.properties文件中的内容拷贝到web/WEB-INF/classes/ApplicationResources_en.properties文件中。这些都是你在JSP页面中用来显示的titles/headings 和form属性对应的具体键-值对。下面是你需要在ApplicationResources_en.properties文件中新增的内容示例:
# -- person form --
personForm.id=Id
personForm.firstName=First Name
personForm.lastName=Last Name

person.added=Person has been added successfully.
person.updated=Person has been updated successfully.
person.deleted=Person has been deleted successfully.

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

# -- person detail page --
personDetail.title=Person Detail
personDetail.heading=Person Information
  • 拷贝PersonForm.html 和 PersonForm.page 文件 到 web/pages/personForm.jsp 和 web/pages/personForm.page文件。拷贝PersonList.html和PersonList.page文件到web/pages/personList.jsp和web/pages/personList.page。请注意目标文件名的第一个字符是小写。
在 "pages" 目录下的文件将被罚不到"WEB-INF/pages"目录下。因为容器会为WEB-INF目录下的文件提供安全性保护。这意味着直接来自客户端的请求,而不是由 Tapestry's ApplicationServlet 发送的 forward请求,将无法访问对应的 JSP 页面。就是说把所有的 HTML 模版放在 WEB-INF 目录下将保证所有的 JSP 页面只能通过Taperstry的 Pages 来访问。这就允许把所有的安全性处理集中放到 Taperstry Page 中去,在那里可以得到更有效的处理,而不在需要在表示层中处理。

Appfsue的web应用程序安全性保证所有 *.html url-patterns 都是有保护的 (除了 /signup.html 和 /passwordHint.html), 这将保证客户端必须通过Page(Tperstry 框架页面条专逻辑控制器对应的 Page )来访问 template 文件。

注意: 如果你希望为特别的页面自定义一个CSS,你可以在这个文件的最上方加入 <body id="pageName"/> 标签 (紧跟在</content> 标签后面)。 SiteMesh会特别处理并把它放在最终的页面中。你也可用使用如下的代码一个页面一个页面的自定义CSS:

body#pageName element.class { background-color: blue } 

创建PersonFormTest以测试[#2]

要为PersonForm创建一个 Junit 测试,首先在 test/web/**/action 目录下创建一个 PersonFormTest.java 文件。


package org.appfuse.webapp.action;

import java.util.ResourceBundle;

import org.appfuse.model.Person;
import org.appfuse.service.Manager;

public class PersonFormTest extends BasePageTestCase {
    private PersonForm page;
    private Manager manager;

    protected void setUp() throws Exception {    
        super.setUp();
        page = (PersonFormgetPage(PersonForm.class);
        // unfortunately this is a required step if you're calling 
        // getMessage in the page class
        page.setBundle(ResourceBundle.getBundle(MESSAGES));
        page.setValidationDelegate(new Validator());

        // this manager can be mocked if you want a more "pure" unit test
        manager = (Managerctx.getBean("manager");
        page.setManager(manager);
        // default request cycle
        page.setRequestCycle(getCycle(request, response));
    }

    protected void tearDown() throws Exception {
        super.tearDown();
        page = null;
    }

    public void testAdd() throws Exception {
        Person person = new Person();
        // set required fields
        person.setFirstName("firstName");
        person.setLastName("lastName");
        page.setPerson(person);

        page.save(page.getRequestCycle());
        assertFalse(page.hasErrors());
    }

    public void testEdit() throws Exception {
        MockRequestCycle cycle = (MockRequestCyclepage.getRequestCycle();
        cycle.addServiceParameter(new Long(1));
        
        page.edit(cycle);

        assertNotNull(page.getPerson());
        assertFalse(page.hasErrors());
    }
    
    public void testSave() {
        assertNotNull(manager);
        Person person = (Personmanager.getObject(Person.class, new Long(1));

        // update fields
        person.setFirstName("firstName");
        person.setLastName("lastName");
        page.setPerson(person);

        page.save(page.getRequestCycle());
        assertFalse(page.hasErrors());
    }

    public void testRemove() throws Exception {
        Person person = new Person();
        person.setId(new Long(2));
        page.setPerson(person);

        page.delete(page.getRequestCycle());
        assertFalse(page.hasErrors());
    }
}

此时将不能通过编译因为你还没有创建被测试的 PersonForm 。

创建 PersonForm [#3]

在 src/web/**/action 目录下创建 PersonForm.java 文件,输入下面的内容:


package org.appfuse.webapp.action;

import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.event.PageEvent;
import org.apache.tapestry.event.PageRenderListener;

import org.appfuse.model.Person;
import org.appfuse.service.PersonManager;

public abstract class PersonForm extends BasePage implements PageRenderListener {
    public abstract PersonManager getPersonManager();
    public abstract void setPersonManager(PersonManager mgr);
    public abstract void setPerson(Person person);
    public abstract Person getPerson();

    public void pageBeginRender(PageEvent event) {
        if ((getPerson() == null&& !event.getRequestCycle().isRewinding()) {
            setPerson(new Person());
        else if (event.getRequestCycle().isRewinding()) { // add
            setPerson(new Person());
        }
    }

    public void cancel(IRequestCycle cycle) {
        if (log.isDebugEnabled()) {
            log.debug("Entering 'cancel' method");
        }

        cycle.activate("mainMenu");
    }

    public void delete(IRequestCycle cycle) {
        if (log.isDebugEnabled()) {
            log.debug("entered 'delete' method");
        }

        getPersonManager().removePerson(getPerson().getId().toString());

        MainMenu nextPage = (MainMenucycle.getPage("mainMenu");
        nextPage.setMessage(getMessage("person.deleted"));
        cycle.activate(nextPage);
    }

    public void edit(IRequestCycle cycle) {
        Object[] parameters = cycle.getServiceParameters();
        Long id = (Longparameters[0];
        
        if (log.isDebugEnabled()) {
            log.debug("getting person with id: " + id);
        }
        
        setPerson(getPersonManager().getPerson(id.toString()));
        cycle.activate(this);
    }
    
    public void save(IRequestCycle cycle) {
        if (getValidationDelegate().getHasErrors()) {
            return;
        }

        boolean isNew = (getPerson().getId() == null);

        getPersonManager().savePerson(getPerson());

        String key = (isNew"person.added" "person.updated";

        if (isNew) {
            MainMenu nextPage = (MainMenucycle.getPage("mainMenu");
            nextPage.setMessage(getMessage(key));
            cycle.activate(nextPage);
        else {
            PersonForm nextPage = (PersonFormcycle.getPage("personForm");
            nextPage.setMessage(getMessage(key));
            cycle.activate("personForm")// return to current page
        }
    }
}

你可能注意到在文件中使用了一组键(keys) - "person.deleted","person.added" 和 "person.updated"。所有的这些键值定义在你的 i18n 绑定文件(ApplicationResources_en.properties)中。你在这篇指南的开头应该已经添加了这部分内容。如果你希望在程序中改变这些基本信息,加入 person 的 name 或者其他内容,只需要在对应的信息内容中简单得添加一个 "{0}" 然后再程序中使用 setMessage(format(key, stringtoreplace)) 方法填充具体的内容。

你现在可能注意到了我们在这里调用 PersonManager 的代码和我们 PersonManagerTest 的相应代码是一样的。因为 PersonForm 和 PersonManagerTest 都是PersonManagerImpl 的客户 , 所以这是个优雅的结构。

现在你要告诉 Tapestry 这个 page 的存在了。你要做的是在 web/WEB-INF/tapestry.application 文件中加入 page 入口。


    <page name="personForm" specification-path="pages/personForm.page"/>

如果你把 HTML 模板文件保存在 WEB-INF 目录下,上面的步骤是不需要的。希望Taperstry未来的版本允许你设置全局路径。

这个从cancel(), delete() and save()方法 PersonForm 返回到 "MainMenu" 页面 。在下面的部分,你将把它改变成 PersonList 页面。

运行 PersonFormTest [#4]

你看 PersonFormTest 可以发现所有的测试依赖于数据库 person 表中一条 id=1 的纪录( testRemove 方法依赖于 id=2 的纪录 ),所以要在示例数据文件( metadata/sql/sample-data.xml )中加入这些纪录。我通常在文件的底部加入这些内容 - 这个顺序并不重要因为它和其他数据表没有任何关系。

  <table name='person'>
    <column>id</column>
    <column>first_name</column>
    <column>last_name</column>
    <row>
      <value>1</value>
      <value>Matt</value>
      <value>Raible</value>
    </row>
    <row>
      <value>2</value>
      <value>James</value>
      <value>Davidson</value>
    </row>
  </table>

在运行所有的测试以前 DBUnit 会加载这些数据到数据库中,所以这些纪录对你的 Form 测试是可靠的。

保证你的项目中的文件都正确保存。那样你运行ant test-web -Dtestcase=PersonForm - 所有的事情就像你最初期望的那样。

BUILD SUCCESSFUL
Total time: 12 seconds

在浏览器中查看这个表单[#5]

现在执行 ant db-load deploy,启动 Tomcat 在浏览中输入 http://localhost:8080/appfuse/personForm.html ,你将看到如下的界面(略):

在 Tapestry 中,URLs 显得有点丑陋,不过他们包含了大量的信息。与其他的框架只需要你简单调用Action中的方法不一样, - 你需要调用 Page 类中的 listeners 。为了调用PrsonForm 对象中的 "edit" listener ,需要在 web/pages/mainMenu.html 文件中加入下面的代码。

    <a jwcid="@DirectLink" listener="ognl:requestCycle.getPage('personForm').listeners.edit" 
        parameters="ognl:new java.lang.Long(1)">Edit Person</a>

最后为了提高界面的用户友好性,你也许希望在表单的上方加入信息,这可以在 personForm.html 中前面使用 <span key="..."/> 加入所需要显示的信息。

[Optional] 创建一个Canoo WebTest 以模拟测试 PesonForm 对浏览器中操作的响应[#6]

最后一步(可选步骤)是创建一个 Canoo WebTest 测试这个 HTML 模板。

我之所以说着步骤是可选的,是因为你可以通过浏览器实现同样的操作。

你可以使用下面的步骤测试adding、editing 和 saving操作。

  • Add - http://localhost:8080/appfuse/personForm.html.
  • Edit - 使用你在 Main Menu 中创建的 URL ( 确保事先运行过 ant db-load )。
  • Delete - 使用上面的 edit 链接点击 Delete 按钮。
  • Save - 点击 Main Menu 中的 edit 链接 ( 如果你已经删除了纪录需要再次运行 ant db-load ) 然后点击 Save 按钮。

Canoo 测试相当灵活,只需要通过在一个XML文件中配置实现。为了增加 add, edit, save 和 delete 操作的测试,打开 test/web/web-tests.xml 文件并且加入下面的XML。你可以看到一个命名为 PersonTests 目标的片断可以运行所有相关的测试。

我使用 CamelCase 命名 target ( 不同于传统的小写字母中线分割的命名方法 ) 因为你测试时要输入-Dtestcase=Name ,我发现我习惯使用 CamelCase 命名我的单元测试。


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

    <!-- Verify the edit person screen displays without errors -->
    <target name="EditPerson"
        description="Tests editing an existing Person's information">
        <canoo name="editPerson">
            &config;
            <steps>
                &login;
                <clicklink label="Edit Person"/>
                <verifytitle stepid="we should see the personDetail title"
                    text="${webapp.prefix}${personDetail.title}"/>
            </steps>
        </canoo>
    </target>

    <!-- Edit a person and then save -->
    <target name="SavePerson"
        description="Tests editing and saving a user">
        <canoo name="savePerson">
            &config;
            <steps>
                &login;
                <clicklink label="Edit Person"/>
                <verifytitle stepid="we should see the personDetail title"
                    text="${webapp.prefix}${personDetail.title}"/>
                <!-- update some of the required fields -->
                <setinputfield stepid="set firstName" name="firstNameField" value="Canoo"/>
                <setinputfield stepid="set lastName" name="lastNameField" value="WebTest"/>
                <clickbutton label="${button.save}" stepid="Click Save"/>
                <verifytitle stepid="Page re-appears if save successful"
                    text="${webapp.prefix}${personDetail.title}"/>
            </steps>
        </canoo>
    </target>

    <!-- Add a new Person -->
    <target name="AddPerson"
        description="Adds a new Person">
        <canoo name="addPerson">
            &config;
            <steps>
                &login;
                <invoke stepid="View Person Form" url="/personForm.html"/>
                <verifytitle stepid="we should see the personDetail title"
                    text="${webapp.prefix}${personDetail.title}"/>
                <!-- enter required fields -->
                <setinputfield stepid="set firstName" name="firstNameField" value="Jack"/>
                <setinputfield stepid="set lastName" name="lastNameField" value="Raible"/>
                <clickbutton label="${button.save}" stepid="Click button 'Save'"/>
                <verifytitle stepid="Main Menu appears if save successful" 
                    text="${webapp.prefix}${mainMenu.title}"/>
                <verifytext stepid="verify success message" text="${person.added}"/>
            </steps>
        </canoo>
    </target>

    <!-- Delete existing person -->
    <target name="DeletePerson"
        description="Deletes existing Person">
        <canoo name="deletePerson">
            &config;
            <steps>
                &login;
                <clicklink label="Edit Person"/>
                <clickbutton label="${button.delete}" stepid="Click button 'Delete'"/>
                <verifytitle stepid="display Main Menu" text="${webapp.prefix}${mainMenu.title}"/>
                <verifytext stepid="verify success message" text="${person.deleted}"/>
            </steps>
        </canoo>
    </target>

完成了前面的操作后,你可以在Tomcat运行的状态下运行 ant test-canoo -Dtestcase=PersonTests; 也可以在没有 Tomcat 运行的情况下运行 ant test-html -Dtestcase=PersonTests , Ant 会启动启动/停止 Tomcat。为了在运行所有 Canoo 测试的时候能够包括 PersonTests, 在"run-all-tests" target. 中加入对应的dependency。

你可能注意到Canoo测试没有客户端的日志记录。如果你想看看它到底做了什么,你可以在 web/WEB-INF/classes/log4j.properties 中加入 tweak the log4j settings

BUILD SUCCESSFUL
Total time: 27 seconds


下面的内容: 第四部分: 加入校验和列表页面 - 说明如何增加校验逻辑来使得 firstName 和 lastName 是必填字段。也将展示如何增加一个列表页面显示数据库中所有的person纪录。