如果你从事Web应用程序开发已经很久了,那么你可能已经意识到你正花费大量的时间在重新发明轮子。典型的任务包括数据库连接池、构建控件Servlets、书写导航菜单和设计众多的页面。重复使用在哪里?组件?还是框架?幸运的是,许多人已经认真思考过这个问题,并创建了一些Web开发者需要的工具。在这一章里,我们将介绍Turbine,它是一个试图把Web开发带入和传统软件开发相同的舞台的一个框架。为什么这个框架对我们如此重要?原因就在于这个框架依赖Velocity作为MVC模式的view组件。
Turbine是什么?
Turbine,Apache软件开发组织旗下的Jakarta项目之一,是其众多使用Velocity进行Web开发的可视化(visible显著的)中的一个。它包含以下特性:
■它是一个基于Servlets的控制器组件
■它强调在应用程序中(比如购物车)固有的安全
■你可以在不依赖Web的环境下使用它
Turbine不仅仅是另一个应用程序服务器,它还是一个应用程序框架(application framework),它为开发者提供了让他们在构建企业级应用程序时所需要的工具,而不需要书写大量的代码。这并不是说,你可以把你的应用程序直接丢给服务器。这个框架毫无疑问是一台主机,同时通过适当环境执行,比如Tomcat或Resin。Turbine为你构建特定Web应用程序提供了一些服务。另外,Turbine专用于在MVC模式中使用EJB、控制器Servlets和用Velocity写屏幕的环境中。
随着我们开始寻求理解什么是Turbine和它是怎么和Velocity关联的等问题答案的时候,Turbine变得越来越清晰,Turbine其实就是专门为Web开发者设计的。如果你是一个Web设计者,那么你可能对Java代码不是太熟悉,可能就需要一段艰苦的时间用于了解Java基础知识,在这里,哪怕你只具备一点点Java基础知识,你也可以通过我们提供的详细步骤做完全部示例。Velocity的角色就是构建在Turbine里需要使用的屏幕输出。注意,Velocity不是你在Turbine里唯一能够用的模板语言,Turbine也支持WebMacro、JSP、Cocoon和Freemarker。
有了这些概念之后,请系好你的安全带,让我们开始学习使用框架进行设计的激情之旅吧!
Turbine体系
首先,你需要一个围绕Turbine体系进行设计的清晰图表。Figure 14.1图解了在这个框架里的不同模块。
Figure 14.1 The Turbine architecture.
正如你看到的一样,这个框架包含了5个主要模块,所有的模块都在assemblers(汇编程序)的统一指挥下进行工作:
Action—完成特定任务的代码
Navigation—显示导航链接和控件的Velocity模板
Screen—结合Velocity模板和Java类,以显示Layout模块内的关键信息
Layout—描述页面外观的Velocity模板
Page—是一个概念上的对象,它包含了所有上面的模块
下面我们将按顺序解释每一个模块
Action模板
正如你所期待的一样,这个Action模块是用于执行一个特定任务的一小块Java代码。其中最重要的任务就是执行将处理信息以HTML<form>方式传递给用户。在Action模块里的代码将处理这些信息,并指定给相应窗体,但一般而言,你知道,这些信息还需要进行验证、处理,可能的话,还需要进行持久化处理。Figure 14.2显示了在Action和其他模板之间的透视图。
在Figure 14.2里可以看到,Page模块执行一个Action模块以响应一个POST或GET请求。Action模块和Page模块进行“沟通(communicates)”,以确定适当的屏幕(screen)返回给用户。
如果你思考一下在此创建的范例,你可以很容易创建一个Action模块的库,一个可以在整个Web应用程序中重复使用的库,而不需要为每一个页面创建新的代码。让我们来考虑一下购物站点的设计,不管货品出现在应用程序的哪里,用户都可以随时把货品加入到购物车。在Action模块里为窗体书写这个处理过程的代码,你要确保相同的模块不经过任何代码更改就能够在整个应用程序中使用。
Figure 14.2 The Action module flow.
在传统的Web开发模型中,处理窗体的代码一般在Servlets里,并通过窗体进行调用。然而,在Action模块里,你可以在这个模块里保持商业逻辑和数据库接口,从而把它们从控制器里分离出来。如果你愿意的话,你甚至可以使用EJB。
Navigation模块
当你访问一个Web站点的时候,首先呈现给你的是一个页面导航结构。这个结构可能在页面的顶部,也可能在页面底部,或在页面的左边。Turbine通过Navigation模块来支持导航结构。一个Web应用程序可以有许多不同的导航结构,而且不限位置:可以是顶部、底部、页面侧面。Navigation模块被用于处理这个导航结构,并这个结构是基于呈现给用户的页面细节进行变化的。通过某些机制来获取信息,比如EJB,这个模块就拥有和数据库通信的能力。稍后我们会讨论如何通过Layout模块执行Navigation模块。
Screen模块
一个Web页面包含了导航信息和页面有关的信息。这个“重要”的页面内容一般放在页面的body里。在Turbine里通过使用Screen模块来处理body。这个模块主要负责渲染HTML标签以回传给用户。对Screen模块来说,让其为用户的特定信息进行数据库访问并不罕见。
Layout模块
所有的Web页面在向用户呈现信息的时候都有一个物理的布局或模板。在某些情况下,相同的模板可以用于整个应用程序或站点。在其他情况下,比如书店,例如,模板基于呈现给用户的页面进行改变。在Turbine里的布局是在Layout模块里定义的。这个模块为Navigation模块、body或在Screen模块定义的页面屏幕提供了一个占位符。Layout模块主要负责执行Screen和Navigation模块对页面布局的依赖。
Page模块
在极大程度上,Page模块是其他模块的容器,也是第一个从用户浏览器接受请求的模块。如果请求包含了一个在Page模块里定义的action,Page模块将会被执行。在完成action的执行后,Page模块和Screen模块进行通话,以确定将要执行的布局。
模块对象封装(Encapsulation)
Figure 14.3里显示了所有的模块如何封装在一起的。如果有用户浏览器请求,将调用Page模块,这时,Page模块执行一个装入的Action模块。接着,Page模块继续调用Layout模块来决定要显示哪一个屏幕。Layout模块调用Navigation模块来显示这个页面的导航信息。最后,Screen模块被执行,用于放置HTML格式的请求信息到页面上。
正如你所看见的一样,Turbine框架把一个Web页面作为一个对象集来进行显示在页面里的。每一个对象只负责其特定用于页面的某一部分。
Figure 14.3 The Turbine modules.
Loaders
相对于我们之前讨论的5个模块来讲,Turbine定义了5个类,名叫loaders,每一个加载器负责加载其对应的特定模块。Figure 14.4显示了在Turbine里的loaders层级图。正如你所看到的一样,加载器分别用于对应5个模块。
Figure 14.4 Loader modules.
每一个类的成就都融入到Turbine的设计里,这些加载器赋予他们一定程度的智能。在TurbineResources.properties里,CLASSPATH变量被作为一个属性进行定义,你可以定义一个特定路径或设置路径,用于让loader尝试定位资源。一旦loader从硬盘提取资源到内存的时候,loader有一个选项用于缓存这些模块以方便下次再用。正如你所期待的一样,多个loader可能尝试加载同一个模块,因此,所有模块必须以线程安全方式进行书写。
Turbine是如何工作的?
在我们深入讨论Turbine和构建一些应用程序之前。让我们花一点时间来看一下使用Turbine系统进行页面请求的处理过程。首先,我们大概描述一下test系统的布局。
用户通过Web浏览器发出一个请求,这个请求被分派给一个符合Turbine 控制器Servlets的URL。由于请求是使用的HTTP方式,因此这就需要在计算机上安装一个传统的Web服务器。这个Web服务器可以是Apache或Resin的内建HTTP服务器。这个Servlets请求被转移到一个在配置文件时指定的应用程序服务器上。
当Turbine Servlets从用户那里接收到这个请求时,它将对其进行检测看是否是有一个当前用户的HttpSession对象存在。一旦某个Turbine系统的功能是秘密的,那么所有用户都必须首先登录到应用程序中。这个HttpSession对象将返回登录的结果。Turbine在应用程序中也使用cookie或带有session信息的URL。如果没有HttpSession对象存在,系统将自动切换到登录页面,这个切换过程是通过TurbineResources.properties进行配置来完成的。
Turbine Servlets的重要工作之一就是创建运行数据(Run-Data)对象。这个对象是非线程安全(non-thread-safe)的对象,用于Action、Screen和Document对象携带信息,所有传递给Servlets的信息都是通过Request对象进行传递的。
Turbine servlet尝试通过检测当前为用户定义的action来决定用户是否已经登录到应用程序中。如果这个action的值为LoginUser,则相应的action被执行。最后,一个名叫validateUser()的方法调用被执行,在这个方法里,开发者书写代码依靠某些存储方式(比如数据库)验证用户的用户名和密码。在确认了用户登录信息后,用户将直接返回到登录屏幕或DefaultPage。所有这些工作都是通过SessionValidator action来完成的,而且它可以进行重载以支持更多功能。
当DefaultPage被执行时,它将对一个action进行检测并执行它。完成这个action后,DefaultPage将询问Screen模块以找出DefaultPage的布局,找到后就执行它。Layout模块执行Navigation和Screen模块,最后,控件返回给Turbine Servlets和一个新的HTML页面被转交给用户。
获取和安装TDK
使用Velocity和Turbine的第一步就是获取Turbine系统并安装它。Turbine系统被分成几个下载。可能在单机模式下使用Turbine,最强大的方式是联合使用Turbine、Velocity和一个应用程序服务器(比如Tomcat)。你可以下载以Turbine Development Kit(TDK)方式的需要重配置环境的Turbine,下载地址是:ttp://jakarta.apache.org/ builds/jakarta-turbine/release/
在这个地址,你可以找到所有TDK的released版本。写本书的时候,最新版为2.1。进行最新版的Turbine目录,你将看到TAR.GZ(Unix/Linux)和zip文件(Windows)的TDK、Turbine和Torque。由于我们只想下载TDK,因此,我们只需下载对象工作环境的TDK即可。
下载完成后,解压文件到一个指定目录。解压完成后,将生成一个带有众多子目录的/tdk目录。
在测试安装之前,你必须在你的系统上安装两个附加系统:
Ant—下载URL为jakarta.apache.org/ant
JDK—下载URL为sun.java.com
测试TDK安装
测试TDK的安装包括两个步骤。
首先你必须配合TDK编译示例应用程序,步骤为:进入/tdk目录,执行Ant命令(简单键入ant),Ant应用程序将基于/tdk目录的build.xml文件执行,过几秒钟后,构建完成。在很多情况下,构建是成功的。如果不成功,请检查Turbine官方站点的邮件列表。
构建完成后,就可以进行系统测试了。进入/TDK根目录下的/bin目录。TDK的部分部件安装已经完成,只需要重新装配应用程序服务器Tomcat即可。虽然Turbine和Velocity都可以在Resin上使用(稍后讨论),但Tomcat是我们推荐的最佳应用服务器。
在windows下用下面的命令启动Tomcat
catalina.bat run
在Unix/Linux下用下面的命令启动Tomcat
catalina.sh start
如果启动成功,你将看到如下的信息
Using CLASSPATH: .."bin"bootstrap.jar;c:"j2sdk1.4.1_01"lib"tools.jar
Starting service Tomcat-Standalone
Apache Tomcat/4.0-b6-dev
Starting service Tomcat-Apache
Apache Tomcat/4.0-b6-dev
Tomcat启动后,在浏览器输入下面的URL来查看TDK示例:
http://localhost:8080/newapp/servlet/newapp
如果运行成功的话,你可以看到Figure 14.5所示的输出。
Figure 14.5 TDK sample application.
在这里,你可以看到你安装的TDK已经成功。如果你对TDK的应用程序示例感兴趣,你可以,你可以使用下面的的URL来查阅相关文档。
http://localhost:8080
你的第一个Turbine应用程序
安装完TDK后,你就可以开始使用Turbine作为MVC框架、Velocity作为view组件来构建你自己的应用程序了。第一步是为Ant build文件增加一个任务,并在build.properties里增加一个适当的目录结构。打开位于/tdk根目录下的build.properties文件,修改这个文件里的三个变量:
turbine.app—指定应用程序(在我们的示例里为testApplication)
target.package—指定应用程序包(在我们的示例里为:com.company. testApplication)
target.directory—指定将要构建的包的存放目录(通常情况下为target.package,在我们的示例里为:com/company/testApplication)
下一步,Ant将构建你的新应用程序。记住,Turbine只是一个框架,因此你将得到许多自由的服务。Ant脚本移动不同的文件到你指定的应用程序目录。在下一节里,我们将对这些放置到这个指定目录的文件进行研究。
Turbine(和你的应用程序)将使用一个数据库。当我们构建一个新Turbine应用程序的时候,需要进入/tdk/webapps/<your application name>/WEB-INF/build目录,并打开build.properties文件,修改以下内容:
■定位database(数据库)输入,将其默认为mysql,并设置好相应的值。这个值会在Turbine创建SQL语句的时候使用。
■定位databaseUrll输入,并增加适当的JDBC连接字符串,同时把JDBC驱动放到/WEB-INF/lib目录。
■修改databaseDriver输入为JDBC驱动类。
■修改databaseUser为数据库登录用户
■修改databasePassword为数据登录用户密码
■修改databaseHost为服务器的IP地址
在这里,系统必须为Turbine创建一个数据库表。因此,进入/tdk/webapps/<your application name>/WEB-INF/build目录,并执行ant init。这个Ant构建脚本将用你在build.properties属性里指定的内容连接到数据库,并创建所需的表。当任意一个初始化命名被执行的时候,可能会发生许多错误。比如,build.xml文件只寻找Windows 98、Windows NT和Windows 2000,但如果你用的是Windows XP的话,只需把所有的“98”修改成“XP”或为XP增加一个<target>元素。另外一个是在构建脚本尝试访问数据库命令行管理工具时可能会出现错误。请确保你的数据库服务器下的/bin目录被包含进操作系统路径(path)。
一旦Ant初始化命令成功执行,就可以尝试编辑你的新应用程序了(记住,我们将使用Velocity来进行页面表现)。为了查看你的应用程序,请启动Tomcat,之后浏览下面的URL
http://localhost:8080/testApplication/servlet/testApplication
你将看到一个Turbine登录页面,见Figure 14.6
为了访问你的新应用程序,请键入turbine作为你的用户名和密码。登录后的页面见Figure 14.7
现在让我们回头看看你刚才构建的应用程序,看如何对其进行扩展来完成一些你想做的事情。
Figure 14.6 The Turbine login.
Figure 14.7 The Turbine main page.
解剖(Dissecting)这个应用程序
Okay,你已经安装完成Turbine,并看到了示例的输出。但它到底做了些什么?好,让我们来看一下这个应用程序,并看一下如何把我们所学的Velocity应用到进而去。让我们从头到尾看一下这个应用程序,并讨论几个基础类、方法和Turbine应用程序的功能调用。
当你在之前浏览这个应用程序示例的时候,控件被传递给Turbine Servlets调用的普通Servlets。不管在这个Servlets里到底发生了什么,你需要几个类型的HTML输出来提供给客户端。下面你将会看到,这里仅有两条路可以接触服务器,既通过链接或窗体。在第一情况下,你通过主应用程序URL来调用服务器。
这个应用程序的URL首先被Web服务器处理,这个Web服务器可是内嵌型服务器,也可以是独立的Web服务器。当URL最后传递到应用程序服务器的时候,它将检查它的主机列表,之后决定这个URL是否和他们匹配。在这种情况下,Tomcat找到这个应用程序testApplication就是主机并存在对应的目录结构。其中第一件Tomcat或Resin需要做的事就是打开web.xml文件,并定位应用程序的/WEB-INF目录。这是一个配置文件,用于告诉服务器当一个详细的URL被发现的时候需要执行哪一个类。你的应用程序的web.xml文件包含这些元素:
■ <servlet-name>testApplication</servlet-name>
■ <servlet-class>org.apache.turbine.Turbine</servlet-class>
这些元素告诉应用程序服务器去执行在Turbine类里找到的代码。当Turbine Servlets在Turbine类里被发现的时候正是如此。当Turbine Servlets执行的时候,它立即尝试执行一个默认页面。这个应用程序的默认页面在/WEBINF/conf/TurbineResource.properties文件里进行定义。大约25%的情况下会设成
services.VelocityService/default.layout.template = /Default.vm
因此,因为在你的应用程序里没有布局或action(后面会介绍如何增加),因此Turbine Servlets使用了default.vm布局。这个default.vm布局(Listing 14.1)位于/templates/app/layouts目录。
<table width="100%">
<tr>
<td colspan="2">
$navigation.setTemplate("/DefaultTop.vm")
</td>
</tr>
<tr>
<td width="20" align="left" valign="top">
$navigation.setTemplate("/Menu.vm")
</td>
<td align="left" valign="top">
$screen_placeholder
</td>
</tr>
<tr>
<td colspan="2">
$navigation.setTemplate("/DefaultBottom.vm")
</td>
</tr>
</table>
Listing 14.1 The default.vm Velocity template.
在你的应用程序晨,default.vm文件是一个Layout模块页面。在这个文件里的代码用于呈现将要返回给用户浏览器的页面内容。在这个页面上没有太多内容,你可以很容易的增加不同的可视化(look-and-feel)组件,比如一个颜色背景和插图(和12章的CD应用程序一样)。这个default.vm文件包含了4个Velocity引用:
■第一个是位于页面顶部的导航组件
■第二个也是导航组件,呈现为一个在页面左侧的导航条
■第三个是一个屏幕组件,用作页面的身体body
■第四个是位于屏幕底部的另一个导航组件
任何在这个模板里的改变将反映到输出到用户的页面上。
在这里,你或许会问,$navigation.setTemplate(String)方法到底做了些什么?还记得Velocity允许得到依靠上下文里的对象的方法调用吗?$navigation.setTemplate(String)得到一个方法调用,setTemplate(String)依靠的是在上下文里的$navigation对象。方法的调用结果将被放入模板default.vm中,并返回给用户。现在,在default.vm模板中的改动将被放入内存中,因此,它不是持久性的。在这种$screen_placeholder引用情况下,Velocity解析将尝试用$screen_placeholder引用来进行替换这个引用。我们知道,在Turbine的处理流程中,Layout模块调用适当的Screen模块,并自动从Screen模块返回的代码替换进$screen_placeholder里。所有这些处理过程不需要任何人为干预。其中的一些过程的理解或许有困难,因此一定坚持住,继续探索究竟发生了什么。
你或许会问$navigation和$screen_placeholder是在哪里被替换的?这两个引用使用的对象放置在Turbine Servlets支持的上下文中。实际上,有些替换过程是自动完成的,我们让Turbine处理这些引用。因此,Turbine得到Layout文件并尝试去决定每一个引用。让我们看一下导航组件中一个模板文件,是menu.vm模板文件的代码,见Listing 14.2。
<font face="$ui.sansSerifFonts">
<a href="$link.setPage("Insert.vm")">Insert Entry</a>
<p>
<b>Flux</b>
<br>
<a href="$link.setPage("user,FluxUserList.vm")">Users</a>
<br>
<a href="$link.setPage("group,FluxGroupList.vm")">Groups</a>
<br>
<a href="$link.setPage("role,FluxRoleList.vm")">Roles</a>
<br>
<a href="$link.setPage("permission,FluxPermissionList.
vm")">Permissions</a>
<p>
<b>Services</b>
##<br>
##<a href="">Intake Service</a>
##<br>
##<a href="">Localization Service</a>
##<br>
##<a href="">Pull Service</a>
##<br>
##<a href="">Scheduler Service</a>
<br>
<a href="$link.setPage("Upload.vm")">Upload Service</a>
<br>
<a href="$link.setPage("ServletInfo.vm")">Servlet Service</a>
<br>
##<a href="">Unique Id Service</a>
##<br>
##<a href="">XML-RPC Service</a>
##<br>
##<a href="">XSLT Service</a>
<p>
<b>Common Tasks</b>
<br>
<a href="">User Downloads</a>
<p>
<a href="$link.setPage("Index.vm")">Home</a>
<p>
<a href="$link.setAction("LogoutUser")">Logout</a>
Listing 14.2 The menu.vm Velocity template. (continued)
这个menu.vm模板包含相当多的Velocity代码。两个不同的调用了上下文。第一个是$link.setPage(String)方法调用。这个代码访问了上下文中的“link”对象,并向其传递Velocity模板的名称,用于当用户单击链接的时候。当用户不允许cookie时,系统将为这个用户增加适当的路径信息并且有可能修改这个URL以满足这种情况下的访问。$link.setPage(“Upload.vm”)调用产生一个如下所示的链接:
http://localhost:8080/TestApplication/servlet/TestApplication/template/Upload.vm
接下来是setAction(String),另一个用于上下文中链接对象的方法调用。这个方法也用于页面的链接,但它不仅仅是放入一个链接,你将获得一个联合了action的链接。这个$link.setAction(“LogoutUser”)将产生下面的链接
http://localhost:8080/TestApplication/servlet/TestApplication/action/LogoutUser
正如你所期待的一样,在一些窗体里,用户退出登录action是一个Java类,它是一个扩展自Action基类。正如你看到的一样,通过这个示例应用程序的代码,你可以看到下面的代码
$link.setPage(“UploadComplete.vm”).setAction(“Upload”)
这种类型的调用会为一个模板创建了一个链接,并且指示一个必须执行的action。正如我们之前讨论的一样,这个action将在页面被构建之前执行。
现在你或许会惊讶填充$screen_placeholder的代码来自哪里?这里有一小点窍门,$screen_placeholder的信息是通过Screen模块产生的。Screen模块是使用两个组件来创建的:一个Velocity模板和一个Java类。这个模板提供了一个可视化界面(look and feel),Java类为这个模板增加引用到上下文给用户。在本章的后面,我们将解释为什么这两个组件都是必需的。为了让系统能够正常工作,模板得到一个和Java类一模一样的名称。
那么,当default.vm布局被使用的时候,既然Screen模块并没有在URL中指定,那么它是从哪里获得的呢?答案是:Turbine的Resources.properties文件在后台支持的。在这个文件里有两个实体:
template.homepage=/Index.vm
template.login=/Login.vm
因为没有在URL中指定屏幕,Turbine将自动使用index.vm屏幕。这个屏幕的代码在webapps/TestApplication/templates/app/screens目录下,具体内容见Listing 14.3。你的系统依靠你想显示的屏幕,使用Velocity模板来替换占位符。
$page.setTitle("Index")
$page.setBgColor("#ffffff")
#set ( $headings = ["Title", "Dept", "Author", "Url","Body"," "] )
#if ($entries)
<table>
<tr>
<td>
<table cellspacing="1" cellpadding="1">
<tr>
#foreach ($heading in $headings)
#headerCell ($heading)
#end
</tr>
#foreach ($entry in $entries)
<tr>
#entryCell ($entry.Title)
#entryCell ($entry.Dept)
#entryCell ($entry.Author)
#entryCell ($entry.Url)
#entryCell ($entry.Body)
<td><a href="$link.setPage("Form.vm").addPathInfo("rdfid",
$entry.RdfId)">Edit</a></td>
</tr>
#end
</table>
</td>
</tr>
</table>
#end
Listing 14.3 The index.vm code. (continud)
在Listing 14.3里的代码是许多屏幕中的一个屏幕的代码,让你可以用作在default.vm页面里的$screen_placeholder的代替者。我们想指出在这个列表里的几件事。第一,注意这个页面是用于显示产生于数据库的动态数据的,通过$entries reference。$entries reference被填充并且被加入到index.java页面的上下文中,见Listing 14.4。因为这个Java类的名称和模板的名称相同,因此,当index.java类被用户浏览器调用时,Turbine系统会自动使用index.vm。这就是index.java类的责任,既把适当的值放入$entries reference。第二,注意index.vm这个Velocity模板,它使用了Velocimacros。#entryCell()是一个宏,它提供显示表的格式。
package org.mycompany.newapp.modules.screens;
import java.util.Vector;
import org.apache.turbine.modules.screens.VelocityScreen;
import org.apache.turbine.util.RunData;
import org.apache.turbine.util.db.Criteria;
import org.mycompany.newapp.om.RdfPeer;
import org.apache.velocity.context.Context;
public class Index extends SecureScreen {
public void doBuildTemplate(RunData data, Context context) {
context.put("entries", getEntries());
}
private Vector getEntries() {
try {
Criteria criteria = new Criteria();
return RdfPeer.doSelect(criteria);
} catch (Exception e) {
return null;
}
}
}
Listing 14.4 Java code for the index.vm template.
正如你所看见的,index.java继承自SecureScreen类,意思是这个Index类是一个屏幕,并且它需要用户先登录系统后才能显示。OK,让我们在这儿停一下。这个SecureScreen类源自一个名叫VelocityScreen的Turbine基类。所有Screen模块都继承自VelocityScreen(如果需要验证的话)。然而,所有的这些都依赖于我们之前列出来的template.login属性。如果这儿有一个适当的模板,并且为登录属性联合了Java类,那么用户必须先登录后才能使用这个模板。如果你从template.login属性中移去/login.vm,系统将不会提示你需要登录,因为它不知道把哪一个默认屏幕发给用户,让他登录到系统来。
让我们回到index.java文件。Index类的目的是创建一个$entries引用,以便让index.vm模板可以获得数据并返回给用户。在VelocityScreen基类里,SecureScreen类起源于一个名叫doBuildTemplate()的方法。当一个Screen模块需要返回它的模板的时候,这个方法会被Turbine系统自动调用。因此,Turbine调用doBuildTemplate()方法,Screen模块将把所有必须的Velocity引用放入到上下文中,并且所有关联的模板将获得Turbine放置在$screen_placeholder引用里的引用,用于最后返回HTML,所有这些都很非常好、非常整洁。
在你运行代码之前,先来看一看所有的平面输出。让我们花一点时间来解释doBuildTemplate()方法和一些在Velocity模板index.vm上的引用。虽然你不能看见他们,index.vm和index.java文件查询数据库并显示任何一个结果。你需要理解这个,以便你可以在你自己的代码里使用相同的技术。
从高层次(high level)来说,在index.java文件里的代码把一个名叫$entries引用放入到上下文中,并且通过getEntries()方法返回这个引用和它的值。getEntries()方法使用了一个Peer对象,这是一个助手对象,用于履行一个在数据库上的选择。数据库结果行被放入上下文中。
OK,所有的用意是什么?当Layout模块调用index Screen模块时,doBuildTemplate()方法被执行,它把一个名叫$entries的引用加入所提供的上下文对象中。通过Index类里的getEntries()方法,$entries和它的值一起被获取。虽然这个方法只包含了两行代码,但有许多工作是在里面完成的。
第一行代码包含了一个Criteria类型的对象,Criteria类是一个助手类,用于从Turbine和一个名叫Torque的关联包管理的数据库获取信息。使用Criteria类,你可以放置过滤器(filter)或限制一个SELECT SQL命令执行。当没有参数的实例或额外的方法在使用它的时候,对象将通知,一个SELECT将把所有的行和列从数据库里取出。Criteria对象的方法完整列表见现在这个URL:http://jakarta.apache.org/turbine/torque-3.0.0/apidocs/org/apache/torque/util/Criteria.html
在getEntries()里的最后一行代码返回
RdfPeer.doSelect(criteria);
许多事情都发生在这个语句上,并且我们需要得到一个数据库以避开对其进行说明。
在Turbine里操作数据库
所有在Turbine应用程序里的数据库都是在配置文件里定义的,这个配置文件为/webapps/testApplication/WEB-INF/conf/testapplication-schema.xml,Listing 14.5显示这个文件里的代码
<database>
<table name="RDF">
<column name="RDF_ID" required="true" autoIncrement="true"
primaryKey="true" type="INTEGER"/>
<column name="TITLE" size="255" type="VARCHAR"/>
<column name="BODY" size="255" type="VARCHAR"/>
<column name="URL" size="255" type="VARCHAR"/>
<column name="AUTHOR" size="255" type="VARCHAR"/>
<column name="DEPT" size="255" type="VARCHAR"/>
</table>
</database>
Listing 14.5 The database schema XML.
从这个列表里,你可以看到在Turbine里定义一个数据库非常容易。只需要为一个特定的列(particular column)提供列、大小、类型和其他属性。你可以通过以下命令增加一个表到你的数据库
ant init
从/webapps/testApplication/WEB-INF/build目录,当你做的时候,Ant执行了数据库的所有在XML文件里定义好的构建任务。如果你关注一下Ant构建XML文件的执行情况,你可以看到第一个操作是使用在XML文件里定义的表名称DROP任意表。因此,所有已经存在的表的数据将全部丢失。你可以修改Ant任务或在执行任务之前先确认你的所有表定义。
Figure 14.8 shows a listing of the tables produced for the testApplication.
Figure 14.8 Tables for testApplication in MySQL.
当一个新表被创建的时候,会发生两个主要的操作。第一个是,在数据库配置里定义的结构被写入数据库(Turbine配置的一部分)。第二个是执行处理SELECT、INSERT、DELETE和UPDATE的代码,以及其他的数据库操作任务。所有这些代码被放在/webapps/testApplication/WEB-INF/src/java/com/company/testApplication/om目录下。这个路径com/company/testApplication依赖于你的应用程序名称。
在这个目录里,你可以找到四个文件,他们包含你的表的名称。在RDF表的创建是通过Turbine使用Torque系统完成的,这些文件是:
■ BaseRdf.java
■ BaseRdfPeer.java
■ Rdf.java
■ PdfPeer.java
BaseRdf.java文件为数据库行包含了所有的setter/getter方法。请不要修改这个文件。Rdf.java文件是一个从BaseRdf.java衍生而来的类,如果需要的话,可用于定义应用程序逻辑。BaseRdf-Peer.java文件是一个助手类,它使用BaseRdf.java文件来进行低级数据库操作,其对象包含的方法可以完成所有普通的数据库操作。RdfPeer.java文件是一个从BaseRdfPeer.java衍生而来的类,在这里可以放置应用程序逻辑,其用于代码的对象将用于获取或放置适当的表。
执行Select
基于我们已经呈现的信息,getEntries()方法创建了一个Criteria对象,没有任何表限制或结果返回。接着,Criteria对象被提供给RdfPeer类的doSelect()方法。这个方法在RdfPeer类里没有重载,因此,一个调用创建了BaseRdfPeer类的doSelect()方法。这个方法基于传递进来的Criteria对象从数据库里重新得到行。
显示结果
一旦getEntries()返回,从RDF表返回的行被作为$entries引用的值放到上下文对象中。在Index对象完成它的工作之后,Turbine Servlets将合并上下文和index.vm模板,以为$screen_placeholder引用提供代码。最后一步是返回一个HTML页面给用户。
使用testApplicationAdding增加一个用户
在从default.vm布局返回给用户的页面和index.vm/java Screen模块的左边导航条上都有许多的链接。第一个链接叫“Insert Entry”,其href属性指定的是一个Velocity代码。
<a href=”$link.setPage(“Insert.vm”)”>Insert Entry</a>
其对应的URL是
http://localhost:8080/testApplication/servlet/testApplication/template/Insert.vm
正如你看到的一样,这个和我们最原始的URL比较想似,除了我们现在有一个新路径信息/template/Insert.vm外。当你单击这个链接时,default.vm布局被使用,而不是默认的index.vm,你已经告诉Turbine Servlets,让他使用由insert.vm和insert.java联合定义的Screen模块。现在让我们来看一下Listing 14.6里的insert.vm。
$page.setTitle("Insert")
<meta http-equiv="Content-Type" content="text/html; charset=iso-
8859-1">
</head>
<body bgcolor="#ffffff" leftmargin="0" topmargin="0"
marginwidth="0" marginheight="0">
<form
method="post"
action="$link.setPage("Index.vm").setAction("SQL")">
<div align="left">
<table bgcolor="#ffffff" cellpadding="5">
<tr>
#formCell ("Title" "title" "")
</tr>
<tr>
#formCell ("Author" "author" "")
</tr>
<tr>
#formCell ("Department" "dept" "")
</tr>
<tr>
#formCell ("Url" "url" "")
</tr>
<tr>
#formCell ("Body" "body" "")
</tr>
</table>
<input type="submit" name="eventSubmit_doInsert" value="Insert"/>
</div>
</form>
Listing 14.6 The insert.vm Velocity template.
我们已经介绍了Velocity模板insert.vm里面几乎所有的东西。然而,这个时候,包含在<form>标签里的代码。这个标签用于从用户和支持它的服务器中提取数据库的数据。这个窗体action包含了一个Turbine action模块引用:
<form action=”$link.setPage(“Index.vm”).setAction(“SQL”)”>
Turbine action被找到并作为一个SQL action调用。用于告诉SQL action如何去做,这个输入按钮有一个名称叫eventSubmit_doInsert,让我们思考一下代码是如何做的。首先,用户单击Insert Entry链接,Turbine servlet提出默认的布局default.vm用于渲染适当的Navigation模块,除了调用insert.java类的doBuildTemplate()方法外。然而,如果你看一下/src/com/mycompany/testApplication/modules/screens/目录,你就会发现insert.java的源文件。这就提出了一个好的点子,Screen模块不再需要由VM和Java类文件组成。如果一个Screen模块只有一个VM模板,Turbine服务器只需简单渲染Velocity模板而不需要任何上下文的改变。如果仅包含了一个Screen类,这个类将负责提供所有的输出支持,因为这个没有Velocity模板来渲染HTML输出。
在这里,Velocity模板insert.vm基于当前上下文对象被渲染为$screen_placeholder引用。在这点上,用户在<form>输出字段输入数据并单击提交按钮。这个单击将导致Turbine servlet执行一个Action模块来调用SQL。记住在Layout对象之前,所有的action都将被执行。
SQL action在modules/actions目录下。这个action的源文件在/src/com/mycompany/testApplication/modules/actions目录下的SQL.java文件(Listing 14.7)。
import org.apache.velocity.context.Context;
import org.apache.turbine.util.RunData;
import org.apache.turbine.util.db.Criteria;
import org.apache.turbine.modules.actions.VelocityAction;
import com.company.testApplication.om.Rdf;
import com.company.testApplication.om.RdfPeer;
public class SQL extends SecureAction {
public void doInsert(RunData data, Context context)
throws Exception {
Rdf entry = new Rdf();
data.getParameters().setProperties(entry);
entry.save();
}
public void doUpdate(RunData data, Context context)
throws Exception {
Rdf entry = new Rdf();
data.getParameters().setProperties(entry);
entry.setModified(true);
entry.setNew(false);
entry.save();
}
public void doDelete(RunData data, Context context)
throws Exception {
Criteria criteria = new Criteria();
criteria.add(RdfPeer.RDF_ID, data.getParameters().getInt("rdfid"));
RdfPeer.doDelete(criteria);
}
public void doPerform(RunData data, Context context)
throws Exception {
data.setMessage("Can't find the button!");
}
}
Listing 14.7 The SQL Action class.
SQL Action用于SecureAction。通过使用SecureAction基类,只有当用户登录进系统后,这个action才会被执行。提交按钮使用了一个doInsert的值,在doInsert()方法里,系统利用全局runData对象,在这里,来自<form>的参数被定位。在这种情况下,你不能为RDF表使用Peer对象,但可以使用RDF类自身。这个来自<form>的值被替换成RDF对象并存入数据库。
一旦SQL Action把新信息存储到适当的数据库,使用它的Navigation模块来执行默认的布局,同时index.vm屏幕在<form action>里指定。Figure 14.9显示了我们向数据库增加一个新实体后的情况,Figure 14.10显示了testApplication页面。
Figure 14.9 A database entry in MySQL.
Figure 14.10 The testApplication page after the insert.
注意在Figure 14.10屏幕右上边的“edit”链接。如果你把鼠标移至链接的上方,你可以看到现在的链接
http://localhost:8080/testApplication/servet/testApplication/tempalte/Form.vm/rdfid/1
在这里,你应该知道当你单击这个链接的时候,这些代码打算做些什么。form.vm和form.java Screen模块文件被调用。这个时候,你需要有一个form.java文件。正如你料想的一样,在URL末尾的rdfid/1信息将用于从数据库提取行并显示它,见Figure 14.11。Form类的代码见Listing 14.8。
Figure 14.11 An edit on the database row.
public class Form extends SecureScreen {
public void doBuildTemplate( RunData data, Context context {
try {
int entry_id = data.getParameters().getInt("rdfid");
Criteria criteria = new Criteria();
criteria.add(RdfPeer.RDF_ID, entry_id);
Rdf rdf = (Rdf) RdfPeer.doSelect(criteria).elementAt(0);
context.put("entry", rdf);
} catch (Exception e){
}
}
}
Listing 14.8 The form.java code.
正如你所知道的一样,Screen模块代码在Velocity模板之前执行。因此在Listing 14.8里的doBuildTemplate()代码通过链接获得主要关键字,并用于提取数据库行、替换上下文中的Velocity模板form.vm来显示给用户。在链接末尾的rdfid被转换成一个参数,并且被放到RunData对象中。之后,它使用Criteria对象来定位数据库,并从数据库提取数据行。elementAt(0)方法被用于把一个数据行中放入上下文的“entry”引用中。
注意那些action是不返回结果的。当你需要从数据库中得到信息的时候,只需为Screen模块使用一个类就行。如果这儿有你需要的action来传达,你可以通过使用data.setMessage(String)命令在RunData对象中设置一个消息变量。
当书写你的action的时候,你需要从VelocityAction和Secure Action类(依赖于是否需要进行登录验证)开始。在任意一种情况下,这个action都包含了一个或多个ActionEvent。正如你在SQL Action里看到的,那里的事件都用于提交按钮的值和一名名叫doPerform(RunData, Context)的方法。doPerform()方法是一个action的默认方法。如果没有其他的方法可用或在单击窗体上的按钮没有匹配的ActionEvents(在类里)时,doPerform()方法将会被执行。
重建和部署
一旦你修改了你的应用程序,你就必须重建它。在当前的TDK环境下非常容易,只需进入/webapps/testApplication/WEB-INF/build目录,并执行下面的命令
ant compile
最后,当Turbine在Tomcat上运行时,就不再需要它了。如果你想转移到Resin平台或BOSS或其他应用程序服务器,只需要打包你的testApplication或其他应用程序名称目录结构并在别处进行部署就行。你或许会需要删除或调整在web.xml文件中DTD。
在Turbine里使用Velocity的高级功能
最后一节比较简略,我们将介绍在Turbine里使用Velocity的许多主要特性。然而,我们想介绍一点附加的高级特性。当然,你可以查阅Turbine文档以获得更多信息。
RunData对象
在整个示例应用程序中,我们谈到了RunData对象,并且通过使用数据关键字获取了一个该对象的引用。RunData对象有许多方法和许多有益于开发的字段。既然RunData对象在模板和许多模块Java类中可以使用,那么开发者就应该充分利用这个对象。该对象的一些方法为:
void setMessage(String msg)—在RunData对象里设置一条消息,这个常用于错误处理和其他模块间的信息传递,比如Action模块
string getMessage()—返回一条RunData信息
parameter Parser getParameters()—返回从一个HTML<form>或在URL链接上的参数
void setTitle(String)—指定页面标题,常常在Velocity Screen模块模板里用作第一个语句
user getUser()—得到当前在session里的用户
boolean userExists()—检查在session里是否存在当前用户
boolean removeUserFromSession()—使当前在session里的用户无效
void setRedirectUri(String ruri)—为重定向(redirection)设置URL
如何你正在Screen模块Java类里处理信息,并且你想更改Screen模块的模板,你可以使用getTemplateInfo()方法来获取一个TemplateInfo对象,并且使用setScreenTemplate(String)方法来设置一个新的动态模板。
TemplateLink对象
在所有的Velocity模板中,你可以使用TemplateLink对象来创建一个新链接到另外一个模板,这个对象源自DynamicURI。新链接通过下面的代码来加入:
$link.setPage(String);
$link引用是一个TemplateLink对象,它和DynamicURI一起拥有一个巨大的方法列表和用于构建动态连接的字段。所有在我们示例中的这些链接都是指向一个动态页面。我们也可以使用下面的命令链接到一个静态页面
$link.setPage(“/docs/static/privacy.html”)
这个新的静态页面是一个用户Web浏览器的没有任何导航或布局控件的主页面。最后的操作是在Velocity模板里使用静态页面,并且维持Web应用程序的界面。
TemplatePageAttributes对象
当$page引用被用于一个Velocity模板时,TemplatePageAttributes对象被获取。这个对象允许当前页面的元素被更改,比如标题和背景颜色。比如:
$page.setBgColor(“#FF0000”)
这个命令将产生一个红色背景,你允许通过addAttribute(string, int)命令访问<body>标签。其他可用的命令包括:
addAttribute(String name, String value)—在body标签里增加属性 “name”和“value”
setBackground(String url)—设置背景 URL.
setDescription(String description)—设置声明(description)标签
setKeywords(String description)—设置一个关键字(keyword)标签
setLinkColor(String color)—指定链接颜色
setStyleSheet(String url)—设置一个CSS(stylesheet)
setTextColor(String color)—指定文本颜色
setVLinkColor(String color)—指定vlink颜色
本章小结和下章介绍
在本章,你了解到,在Turbine驱动的Web应用程序的开发里,Velocity是一个主要的组件。在下一章里,我们将介绍另一个框架,名叫Maverick。