13、File, rich rendering,and email support
文件、富生成和email支持
This chapter covers
■ Handling file uploads
■ Creating PDF documents and charts
■ Sending emails with attachments
■ Customizing the UI with resource bundles
本章包括:
处理文件上传
建立PDF及图表
发送带有附件的emails
用资源包定制UI
Many people playing their first round of golf question why anyone would want to
torture themselves with such a maddening game, concluding that those who play it
are simply masochistic. But anyone who has experienced the triumph of sinking
the ball in the hole from the tee box, clearing a large water hazard, or just taking a
great swing understands that there’s something extremely gratifying about golf
once you get the hang of it. The same can be said about an application framework.
There’s a lot to learn at first and it can seem overwhelming. Then things click. Your
newfound ability makes the experience enjoyable and you get to do things you’ve
never experienced before.
很多人在玩第一回合高尔夫时会问为什么每个人会用这个疯狂的游戏来折磨自己,认为玩家都是受虐狂。但只有经历种种考验而得以入洞的人才会理解这种感受。对于应用框架也是一样。首先要有很多要学,看上去是排山倒海。随后事件有所变化,你新增的能力使经验有趣了,你要做些以前从未经历的事情。
You saw in the last chapter how Seam and JSF component libraries take the pain
out of using Ajax, making Ajax more accessible than ever. That’s just one example
of how Seam provides features that are both rewarding to develop and rewarding for
your customers and clients to use. In this chapter, you’ll learn how to do more fun and
enjoyable tasks in Seam, including handling file uploads, creating PDF documents,
rendering dynamic image data and charts, sending emails that include attachments,
and adding themes to your application. This sampling represents the set of features
that are quite often tossed out in the name of budget and time constraints. With
Seam, you discover that performing these tasks is a breeze. They’re all just variations
on what you have done so many times throughout this book.
上一章你看到了Seam 和JSF部件库解决了Ajax之痛,使其更易访问。这只是SEAM所提供的特性中的一个例子。既对开发有益也对用户有益。本章,你将学习如何从SEAM中找更多的乐子,包括处理文件上传,建立PDF文档,生成动态图像及图表,发送带有附件的邮件,向你的应用中加入主题。这个例子代表一套特性经常增加项目的预算及时间紧迫性。使用SEAM你发现这些都成了小菜一碟。所有这些只是你在本书所学的招术稍做变化。
Because several of the examples covered in this chapter work with raw file and
image data, you’ll begin by learning how to accept file uploads and how to serve them
back to the browser.
因为本章的一些例子要使用原文件和图像数据,你要开始学习如何接受文件上传并如何将其传回浏览器。
13.1 Uploading files and rendering dynamic images
How many times have you cringed at the requirement of processing an image upload
and having it rendered on a page in the application? The problem isn’t that the task is
impossible, but that it isn’t as straightforward as dealing with plain form data. In fact,
accepting file uploads in Java has a well-founded reputation for being notoriously difficult.
With Seam, it’s almost too easy. In this section, you’ll learn how to bind an
upload form element to a Seam component to accept an image and persist it to the
database. Then you’ll use Seam’s enhanced graphic component to turn that raw data
back into a dynamically rendered image.
上传文件和生成动态图像
有多少次你对上传图像并在应用的页面上重画感到为难?问题不在于不可能,而在于不像处理表单数据那样直接。事实上,在JAVA中接受上传恶名流传。对于SEAM来说,就太简单了。这一节,你会学习如何绑定一个上传表单元素到SEAM部件以接受一个图像并将其保存到数据库。随后你使用SEAM增强的图形部件来将其转化为动态重画的图像。
13.1.1 Accepting file uploads
Seam practically sacrifices itself to protect you from the nastiness of file uploads in Java,
reducing the task to a simple EL value binding expression—it’s that dramatic. There are
no buffers, stream reading, or multipart boundaries to worry about. All of that is handled
for you transparently by the MultipartFilter and the MultipartRequest it wraps
around the incoming servlet request. If you already have the SeamFilter configured,
you don’t have to do anything else to enable Seam’s file upload support.
接受文件上传
SEAM在文件上传上给了你坚强庇护,将任务简化成一个简单地EL值绑定表达式,太神了。不再有缓冲、读流,以及多边界需要担心。只是透明地在进入的servlet请求外包装上MultipartFilter 和MultipartRequest。如果你已有SeamFilter配置,除了启动SEAM文件上传支持,你不用做任何事。
SEAM’S FILE UPLOAD UI COMPONENT
Seam provides a UI input component,
from a JSF form. The file data is passed through an EL value binding that references a
byte[] or InputStream property on a Seam component. The upload component can
also capture the content type of the file, the filename, and the file size and apply that
information to a Seam component along with the file data.
SEAM文件上传UI部件
SEAM提供了UI录入部件,
To demonstrate a file upload, we augment the registration form to allow members
to upload a profile image, or avatar. Two properties have to be added to the Golfer
entity, image and imageContentType, to capture the image data and content type,
respectively. The relevant parts of the Golfer entity class are shown here:
为演示一个文件上传,我们给注册表单加上参数,让会员上传一个图像。要加上两个属性到Golfer实体:image 和imageContentType,用来获得图像数据和内容类型。Golfer实体类型的相关部分显示如下:


I’ve decided to accept the file data as a byte[]. The lazy-fetch strategy prevents the
data from being loaded until the image data is requested, slightly reducing the memory
footprint.
我已决定接受文件数据为byte[]。延迟加载策略不会加载数据,直到图像数据被请求。给会员省了些周折。
The only remaining step is to add the upload field to the registration form and wire
it to the image and imageContentType properties on the Golfer entity. You also need
to set the enctype attribute on the
This setting tells the browser to send the form data using multipart data streams. Failure
to make this adjustment will prevent the browser from sending the file data. An excerpt
of the registration form with these changes applied is shown here:
唯一剩下的步骤是增加上传字段到注册表单并将其连到Golfer实体的image 和imageContentType属性。你还要设置

You don’t have to make any changes to the RegisterAction class to accept the
uploaded image and have it stored in the database. The image data is bound to the
entity instance named newGolfer and automatically persisted to the database along
with the other fields on this entity. If you’re content with the image as it’s uploaded,
your work is done. However, it’s likely that you’ll want to put some limits on what the
user can upload.
你不用对RegisterAction类做任何变化来接受图像并存入数据库。图像数据被绑定到名为newGolfer的实体实例并跟随其它字段一起自动地保存到数据库,如果你满意于上传的图像,你的工作就完成了。不管怎样,你肯定还是希望对用户的上传加一些限制。
CONTROLLING WHAT GETS UPLOADED
The accept attribute on
standard Multipurpose Internet Mail Extensions (MIME) types that can be uploaded.
The upload field in the registration form limits the acceptable file types to graphic
formats that Seam is capable of rendering dynamically. The use of wildcards is also
permissible. You could accept all image MIME types, for instance, using the pattern
image/*. Even with this restriction in place, though, you should still validate the file
type in the action method.
控制上传的内容
Seam exposes two global settings on the built-in component named multipart-
Filter to control file uploads. The maxUploadSize property allows you to cap the size
(in bytes) of the file being uploaded. There’s no limit in the default configuration. You
use the createTempFiles property to control whether Seam uses a temporary file to
store the uploaded file data or whether the file data is held in memory, which is the
default. These two properties can be adjusted using component configuration as follows:
在名为multipartFilter的内建部件中,SEAM暴露两个全局设定,来控制文件上传。maxUploadSize属性让你设置被上传的文件的最大尺寸(bytes)。在默认配置中无限制。你使用createTempFiles属性控制是否SEAM使用临时文件来保存上传的文件数据,或是否文件被保存在内存中(默认)。两个属性可用部件描述调整如下:
While the maxUploadSize property puts a limit on file size of the uploaded profile
image, it doesn’t put restrictions on its dimensions. Once the profile image has
uploaded, it’s a good idea to scale it so that it doesn’t steal too much space on the
page when rendered.
当maxUploadSize属性设置文件尺寸,不是在设置分辨率。一旦文件已上传,最好调整其分辨率以免重画时占用太大的页面空间。
PROCESSING AN UPLOADED IMAGE
The uploaded image can be resized in the action method before the newGolfer
instance is persisted. The class org.jboss.seam.ui.graphicImage.Image, which is
bundled with the jboss-seam-ui.jar file, makes resizing and scaling images a cinch. Listing
13.1 shows the code added to the register() action method that manipulates the
uploaded image. This code is just a starting point. If you use it in your application,
you’ll likely want to make it more configurable by eliminating the hardcoded values
you see used here.
处理一个上传的图像
在newGolfer实例被保存前,上传的图像可以用action方法改变大小。位于jboss-seam-ui.jar中的类org.jboss.seam.ui.graphicImage.Image使得改变大小或分辨率变得很简单。表13.1显示了加下register() action方法中的代码。这个代码只是个起点,如果你在应用中使用,你最好不用这里看到的硬编码而多增加些配置。


Once you’ve accepted the raw file data into the database, you need to render it. After
all, what good would it be? Seam can render raw file data in addition to static files
read from the classpath and input streams, in any of the following ways:
■ As an image in a web page
■ As an image in a PDF document
■ Pushed to the browser to be downloaded
■ As an inline image in an email or as an email attachment
一旦你将收到的原文件加入数据库,你就需要重画它。SEAM可用下列方式重画原文件数据。
作为页面上的图像
作为PDF文档中的图像
发到将被下载的浏览器
作为email的内嵌图像或作为附件
Let’s begin by exploring how to render the raw image data in a web page using Seam’s
enhanced graphic UI component. As the chapter progresses, you’ll learn about the
other ways to use the raw file data listed here. These additional options build on this
initial lesson in that they’re merely variations on the graphic UI component.
让我们开始浏览如何使用SAAM增强的图形UI部件在WEB页面重画原图像。随着本章深入,你会学到使用原文件的其它方式。开篇中的这些附加选择只是图形UI部件上的少许变化。
13.1.2 Rendering images from raw data
The Seam UI component set includes an enhanced graphic component capable of operating
on a dynamically generated image. Seam’s graphic component, Image>, is an extension of the standard JSF graphic component, addition to the features supported by the standard component, Seam’s version has support for rendering raw image data and performing image transformations. 从原数据生成图像 SEAM UI部件设置包括一个增强的图形部件可以动态生成图像。SEAM的图形部件 RENDERING DYNAMIC IMAGES WITH SEAM’S ENHANCED GRAPHIC UI COMPONENT The standard that resolves to a string value. This value is used to serve a static graphic resource from the web application context (e.g., /img/golfer.png). The supports a much broader range of Java types resolved from an EL value expression. Table 13.1 lists the supported dynamic Java types from which the can read image data and the image MIME types that the component can handle. 用SEAM增强的图形UI部件生成动态图像 标准的 Like the HTML for the image, which is used in the src attribute of the SeamResourceServlet. If you don’t want the filename to be random, you can specify a fixed filename in the fileName attribute. You don’t need to put the extension of the image in the filename; Seam appends the extension automatically according to image type. 类似于 You should recognize that one of the supported Java types listed in table 13.1 is byte[], which is the property type that holds the golfer’s profile image. Let’s use the registration process on the golfer’s profile page. The image data is specified in the value attribute. A fallback image is used if the golfer doesn’t have a profile image. The golfer’s username is used as the filename of the image to produce a URL that remains stable and thus allows the browser to cache the image. Finally, alternate text is provided for browsers that can’t render images: 你应该认识到表13.1中一个支持的JAVA类型是byte[],这是保存球员简介图象的属性类型。让我们使用 In this example, we display image data retrieved from the database. However, you can also use a Seam component to create an image using Java 2D. The image can be prepared in a Seam component, converted to one of the accepted Java types listed in table 13.1, and then bound to the may be able to leverage one of the basic image transformations that Seam provides. 本例,我们显示从数据库中检索出的图像数据。你也可以使用SEAM部件的Java 2D来建立图像。图像可在SEAM部件中准备,转化成表13.1接受的JAVA类型,然后绑定到 IMAGE TRANSFORMATIONS Given that the to reason that the image can be transformed prior to being rendered. One of three transformation component tags can be nested within transformations to the image using Java 2D, which are listed in table 13.2. Each component tag accepts one or more parameters that control how the transformation is applied. These transformations are the same as those provided by the Image class from the Seam API introduced in section 13.1. 图像变形 假定 The golfer’s profile image is reduced to a reasonable size when it’s initially uploaded. But it may be necessary to scale it even further to create a thumbnail for personalizing a review or comment that the golfer posts somewhere on the site, as shown here: 在最初上传时,球员简介图像被缩小到合理的大小。但可能需要进一步缩小以产生一个缩略图用于个人的检查或球员可能在站点的某处发布评论用到。 You can easily create your own transformation component by implementing the interface org.jboss.seam.ui.graphicImage.ImageTransform. This interface has one method, applyTransform(), which accepts the Image type from the Seam API that you worked with in section 13.1. To make your component available to JSF, you have to go through the song and dance of setting up a JSF component. If you’re going to do so, check out the source code in the Seam UI module and have a good JSF reference close by such as JavaServer Faces in Action (Manning, 2004) or Pro JSF and Ajax (Apress, 2006). To save yourself time, take advantage of the Ajax4jsf Component Development Kit (CDK). 通过实现接口org.jboss.seam.ui.graphicImage.ImageTransform,你可以简单地建立你自己的变形部件。这个接口有一个方法applyTransform(),从第13.1节要用的SEAM API接受Image类型。为了让JSF能用你的部件,你必须设置JSF部件。如果你想这样做,参考Seam UI模块的源码,或JSF参考,如JavaServer Faces in Action (Manning, 2004) or Pro JSF and Ajax (Apress, 2006)。为了节省你的时间,可利用Ajax4jsf 部件开发工具包(CDK)。 You’ll have the opportunity to visit the how to send emails with Seam in section 13.4. There’s an equivalent graphic component for embedding dynamic images in a PDF document. Speaking of PDF, it’s time to move away from HTML and explore how to create PDF documents dynamically. 第13.4节,当你学习用SEAM发送emails时,你还会有机会见到 13.2 PDF generation with iText You may be wondering, “What can an application framework do to help me create PDF documents?” After all, most other frameworks provide a halfhearted integration attempt by only helping you serve the PDF to the browser, leaving the work of creating the PDF up to you. Well, with Seam, the answer to this question is plenty. 用iText生成PDF 你也许想知道,“应用框架可做什么来帮我建立PDF?”毕竟,大多其它框架提供了不认真的集成,只将PDF发送到浏览器,让将建立PDF的工作留给了你。使用SEAM,它会帮你很多。 You see, Seam goes well beyond just playing matchmaker between the browser and the PDF renderer. In Seam, creating and serving a PDF is handled just like any other JSF view. Seam provides a set of UI component tags that generate PDF content. When the template is processed, the view handler serves a PDF document to the browser, generated by the open source (LGPL/MPL) iText Java-PDF library, rather than an HTML document. SEAM在浏览器和PDF生成器方面不只是充当媒人。在SEAM中,建立和使用PDF和其它JSF view一样。Seam提供一套UI部件标签生成PDF文档。当模板被处理时,view处理器将PDF文档发送到浏览器,其由开源的iText Java-PDF库生成,不再是HTML文档。 The PDF component tags extend from the tag handler in the Facelets API, so you must use Facelets to generate PDF documents in this way. To enable PDF support in your application, you need to add two files to the application classpath: itext.jar and jbossseam- pdf.jar (both of which you’ll find in the lib folder of projects generated with seamgen). You can then begin using the PDF component tags in your Facelets templates. PDF部件标签从Facelets API的标签处理器扩展而来,这样,你必须使用Facelets来生成PDF文档。要在应用中启用对PDF的支持,你需要增加两个文件到应用的类路径。itext.jar 和jbossseampdf.jar(这两个你都能在seam-gen生成的项目中发现)。随后你就可以在Facelets 模板中使用PDF部件标签。 13.2.1 Laying out a PDF with UI components A Facelets template that renders a PDF document uses Aside from the new root tag and the accompanying palette of PDF tags, there’s no difference in how you develop it compared to any other Facelets template. You can use Facelets and Seam composition tags (e.g., JSF component tags (e.g., tags that produce HTML to build the JSF UI component tree. Since the PDF template is rendered by JSF just like any other JSF view, you can front-load the request with Seam’s page-oriented controls (page actions, page parameters, and page restrictions). That’s a pretty powerful combination. Notice that nowhere in that description did I mention Java. In this scenario, we want to avoid the use of Java to reuse our Facelets knowledge to create dynamic views. There’s no need to step into a Java API to perform this work. 用UI部件规则PDF 生成PDF文档的Facelets模板使用 For the most part, the PDF component tags map one to one with the functionality provided by iText. An iText PDF document rendered through Seam consists of paragraphs, images, headers, footers, chapters, sections, tables, lists, bar codes, and even Swing components. You can customize the font size, font color, and background color on most elements. Some limitations exist, but the PDF component tags should be sufficient for all but the most complex requirements. 对于大多情况,PDF部件标签与iText提供的功能形成一对一的映射。一个iText PDF文档通过SEAM生成来组成段落、图像、标题、注脚、章节、小节、表、列表、条形码、甚至Swing部件。你可以对大多元素定制字体大小、颜色、背景色。有一些限制存在,但PDF部件可以满足大多的复杂需求。 Rather than itemize each and every tag in the PDF component palette, I provide a comprehensive example that puts many of the tags to use. This approach will give you real-world experience with the PDF tags, which you can supplement by consulting the reference documentation for the specifics of each tag. In this example, we generate a scorecard in PDF format for a golf course. The scorecard is the grid of holes and tee sets that you use to record the number of strokes you took on each hole. It’s fairly complex to render, but also aesthetically pleasing. Thus, I guarantee that this will be a rewarding experience. 不是在PDF部件模板中分列每一个标签,我提供了一个全面的例子用到了所有标签。这种方式给了你PDF标签的真实的体验,你可以另外对每个去参考文档。在这个例子中,我们有球场用PDF格式生成一个分数卡。分数卡用洞和tee sets的格子来记录你在每个洞上的杆数。生成起来很复杂,但审美快乐。所以我保证这是个很有价值的经验。 SETTING UP FOR THE SCORECARD To display the full scorecard for a course, it’s necessary to use all the entities in the golf course model: Facility, Course, Hole, TeeSet, and Tee. The associations between these entities are configured to be lazy loaded. However, as you’ve learned, it’s best to avoid lazy loading in cases when using it wouldn’t be efficient. For instance, rendering the scorecard would cause a large number of lazy associations to be crossed, in turn causing a lot of queries. To optimize, we want to use a page action to eagerly fetch all the necessary data in a single query and then make that data available to the Facelets template. The Scorecard component, shown in listing 13.2, handles this preload logic in the load() method. The abundance of join fetch clauses in the JPQL that’s executed in this method represents the eager fetching of the associations. 设计计分卡 为了显示一个回合的全部记分卡,你需要使用球场模型的所有实体:Facility, Course, Hole, TeeSet, 和Tee。这些实体间的关联被配置成延迟加载。如你所学,这里最好避开延迟加载,以防使用它时无效。例如生成计分卡将会遇到大量的延迟加载,从而会导致大量的查询。为了优化,我们要使用一个页面action来立即在一个请求中抓取所有需要的数据随后在整个Facelets模板中可用。Scorecard部件,显示于列表13.2,在load()中处理这个提前加载的逻辑。在这个方法执行产生的JPQL语句中大量的join fetch分句表明了相关的立即抓取。 The Scorecard component also provides a handful of utility methods needed to render portions of the scorecard. The implementation details aren’t important to this discussion, so the method bodies are hidden (you can see them in the book’s source code). The use of the terms out and in represent the two halves of the golf course. Out is the first nine holes, leading away from the clubhouse. In is the back nine holes, returning to the clubhouse. The methods getTeesOut() and getTeesIn() are invoked from the Facelets template using a parameterized value expression. Scorecard部件也提供很多实用的方法来生成计分卡的一些部分。实现细节不是本节的重点,所以方法体被隐藏了(你可以到本书的源码中看)。词汇out 和in球场的两半,Out是前9个洞,是远离方向的,In则是回来的9个。方法getTeesOut() and getTeesIn()使用带参的表达式从Facelets模板调用。 A scorecard is complex and so is the Facelets template needed to generate it. We get there in two phases. In the first phase, we get our feet wet with a simple PDF report. 记分卡很复杂,所以Facelets模板需要生成它。我们用两个阶段。第一阶段,我们先看看简单的PDF支持。 A BASIC PDF REPORT The first step is to create the Facelets template exportCourseInfo.xhtml, shown in listing 13.3. This template renders basic information about a course and the facility logo. Notice that the root of the template is the following namespace, which imports the PDF UI component tags: 基本PDF报告 第一步是建立Facelets模板,exportCourseInfo.xhtml,如列表13.3。这个模板生成关于球场及设施logo的基本信息。注意模板的根是 p:xmlns="http://jboss.com/products/seam/pdf" Next, we connect a page action to this view ID to preload the scorecard data, defined in the exportCourseInfo.page.xml descriptor: 下面我连接一个页action到这个view ID来预加载计分卡数据,被定义在exportCourseInfo.page.xml描述文件: The page action isn’t a prerequisite for rendering a PDF, but it’s relevant in this scenario. 页面action不是生成PDF的前提条件,但与这一场景相关。 如你在列表13.3中所见,建立PDF文档用费什么事。 title, subject, author,keywords, 和creator。你也可以改变页面的方向和尺寸。默认为A4,这里改为了LETTER。 生成PDF时,iText文档被优化,也可以使用RTF or HTML格式。你可以通过type属性来设置输出格式,其接受三上值:pdf, rtf, 和html。这时的输出格式通过请求参数type控制(如果存在的话)。 NOTE The RTF and HTML output formats support the same features as PDF with the exception of tables, images, and inline HTML. If any of these features are present in the template, they’re ignored when the document is rendered. RTF 和HTML输出格式支持PDF同样的特性,一点例外是表、图像、行内HTML。如果任何这些特性出现在模板,当页面重画时,它们会被忽略。 来看文档的内容,模板包括一个图像,三个段落、一个强调列表。 NOTE There are some cases when the font settings aren’t inherited by a nested then use the font settings from the outer tag—such as font size—aren’t inherited, forcing you to have to apply them again on the nested tag. Expect this to be fixed in the future. 有些时候字体设置没有被嵌套的 element. The difference is that Seam generates a random filename
tag and served by the
元素。不同是
标签的src属性,由SeamResourceServlet来支持。如何你不想用随机的文件名,可以在fileName属性指定一个固定的文件名。你不需要在文件名中使用图像的扩展名,SEAM会依图像的类型而自动加上。









