Seam 2.x Web Development
Build Robust Web Applications with Seam, Facelets, and RichFaces, using the JBoss Application Server
David Salter
BIRMINGHAM - MUMBAI
Seam 2.x Web Development Copyright © 2009 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews. Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing, and its dealers and distributors will be held liable for any damages caused or alleged to be caused directly or indirectly by this book. Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.
First published: April 2009
Production Reference: 1200409
Published by Packt Publishing Ltd. 32 Lincoln Road Olton Birmingham, B27 6PA, UK. ISBN 978-1-847195-92-0 www.packtpub.com
Cover Image by Leo Cornall (
[email protected])
Credits Author David Salter Reviewers Cameron Ingram
Production Editorial Manager Abhijeet Deobhakta Editorial Team Leader Akshara Aware
Daniel Roth Wouter van Reeven
Project Team Leader Lata Basantani
Acquisition Editor Sarah Cullington Development Editor Nikhil Bangera Siddharth Mangarole Technical Editor Rakesh Shejwal Copy Editor Sumathi Sridhar Indexer Hemangini Bari
Project Coordinator Neelkanth Mehta Proofreader Dirk Manuel Production Coordinator Dolly Dasilva Cover Work Dolly Dasilva
About the author David Salter is an enterprise software architect who has been developing software
professionally since 1991. His relationship with Java goes right back to the beginning, using Java 1.0 for writing desktop applications and applets for interactive web sites. David has been developing Enterprise Java applications using both the J2EE standards and open source solutions since 2001. David runs Develop In Java, a Java community web site, for all levels of Java developers.
First and foremost, I would like to thank my wife and family for putting up with my many hours at the computer whilst writing this book. Special thanks and love to my wife for all her encouragement and support. I'd also like to say thank you to all the people at Packt Publishing for helping me with this book. Thank you Sarah for encouraging and believing in me from the beginning. Thanks also to Neelkanth and Rakesh for your hard work helping me to complete the book. Finally, thanks to Gavin King and JBoss, without whom there would be no book.
About the reviewers Cameron Ingram started in the software industry in the 90's. In the last few years, he has been concentrating on RIA with a special emphasis on Seam and Flex, and has been a contributor to the GraniteDS project. Cameron has presented at various conferences, ranging from monitoring application environments to implementing an ESB. He currently lives in St. Petersburg, FL with his wife and two daughters, and cooks a mean set of ribs. Cameron Ingram currently works at PSCU Financial Services as a Senior Software Architect.
Tracy, Ally, Camille, thank you for your love and support—it always helps me through the long hours that sometimes come with this profession. And to Joy, without your guidance and leadership I wouldn't be where I am today.
Daniel Roth is a software developer from Sweden, who has been working in web development for almost ten years. He has a Master's degree in Computer Science and Engineering from Chalmers University of Technology in Gothenburg. Since 2007, when he first was introduced to JBoss Seam, he has worked with it both professionally and personally. Today, Daniel works as a Seam committer in his spare time, trying to make Seam even better.
I'd like to thank Fredrik Wendt for converting my preferences from PHP to Java for web development, Carl-Magnus Nordin, Bengt Fredin and David Rosell for introducing me to Seam, the awesome Seam community for all of the support, and of course my lovely girlfriend Sofia.
Table of Contents Preface Chapter 1: Introduction to Seam
What is Seam? Why use Seam? Seam requirements Installing JBoss Application Server Starting the JBoss Application Server Installing Seam Testing our Seam installation Start the JBoss Application Server Deploy the sample application Run the sample application Summary
Chapter 2: Developing Seam Applications
Seam application architecture Seam components Object Injection and Outjection Example application WAR file deployment descriptors EAR file deployment descriptors Application layout Testing the application Building and deploying the application Seam data validation Data validation The JSF messages collection Building and testing the validating Seam calculator Summary
1 7
7 13 13 14 15 16 17 18 18 18 20
23
23 24 25 26 31 33 34 35 37 40 40 47 48 50
Table of Contents
Chapter 3: Seam Page Flow
51
Simple navigation Seam style navigation Defining a page flow in pages.xml
51 52 54
element element element
Error handling Executing code before rendering pages A working example and Rendering the options in the sample application Running the sample application Seam jPDL navigation Summary
54 55 55
56 57 57 61 63 66 67 67
Chapter 4: Facelets
69
Template parameters
73
What is Facelets? Why Facelets? Why not JSP? Templating
69 70 70
Performance EL functions XHTML No scriptlets Obtaining and installing Facelets Configuring a web application for Facelets Changes in the web.xml file Changes in the faces-config.xml file Example Facelets files from the previous chapter SeamGen Creating the Vacation Planner application using SeamGen Seam debug page Summary
Chapter 5: Testing Seam Applications Overview of Seam application testing TestNG Component testing Testing the user interface Seam component testing Mocking Seam components Summary [ ii ]
74 74 75 76 76 77 77 78 78 82 84 88 91
93
93 93 96 98 101 102 104
Table of Contents
Chapter 6: RichFaces
Obtaining RichFaces Configuring an application for RichFaces Add JAR files to the web application Configuring the application's XML resources RichFaces controls JavaScript methods
105
105 105 106 106 107 108
109
110
JavaScript methods
110
111
JavaScript methods
112
113
115
119
122
JavaScript methods
114
JavaScript methods
118
JavaScript methods
121
JavaScript methods
126
Summary
Chapter 7: Database Persistence
Overview of database persistence How SeamGen helps with persistence Persisting Java entities to the database Retrieving Java entities from the database Integrating Java entities with Facelets Relationships between entities One-to-one relationships One-to-many and many-to-one relationships Many-to-many relationships Performing CRUD operations on entities The Seam application framework Home objects Accessing home objects from Facelets XML definition of home objects
Query objects
127
129
130 132 140 143 145 148 148 149 157 158 159 159
160 161
163
Accessing query objects from Facelets XML definition of query objects
164 165
Summary
166
[ iii ]
Table of Contents
Chapter 8: Seam Conversations
Component scope Local variables HTTP session data Global variables Seam component scope Defining the scope of a Seam component What are conversations? Starting a conversation
Automatically starting a conversation when a page is viewed
Ending a conversation
Automatically ending a conversation when a page is viewed
Propagating conversations between Windows Propagating conversations Viewing conversations Conversation configuration Conversation parameter Conversation timeout Concurrent request timeout Natural conversations Setting up a natural conversation Summary
Chapter 9: Seam and AJAX
What is AJAX? AJAX and the Seam Framework Configuring Seam applications for Seam Remoting Configuring Seam Remoting server side Configuring Seam Remoting client side Invoking Seam components via Remoting Debugging Seam Remoting The org.jboss.seam.remoting.remoting component The component
167
167 168 168 170 172 172 174 175
176
176
177
178 179 179 182 182 182 183 183 184 184
187
187 188 188 189 190 192 196
196 196
Logging Changing the Please wait message AJAX4JSF Configuring an application for AJAX4JSF AJAX4JSF tags
198 199 199 200 200
Summary
205
[ iv ]
201 203 203
Table of Contents
Chapter 10: Security
207
User authentication Security authenticator
Persisting user information Creating an authenticator Securing web pages Creating a logon form Redirecting to the requested page after login
User roles Implement a role class Add required Seam annotations Assign a set of roles to a user Restricting user interface access via roles Auditing security events CAPTCHAs The Seam identity manager API Creating a user with the identity manager Specifying security rules Defining security roles with rules OpenID Configuring a Seam web application to support OpenID Configure an OpenID phase listener Ensure that the correct JAR files are on the application's classpath Write a Logon form Configure page redirection after an OpenID logon The OpenID logon process Summary
Chapter 11: Enterprise Features
Internationalization Defining the application languages Write application-specific strings Display language-specific strings Dynamically changing the localization Persisting the localization settings SeamGen support for localization Displaying Hibernate Validator messages URL rewriting Enabling URL rewriting Seam events Raising events Raising events with @RaiseEvent Raising events with org.jboss.seam.core.Events []
208 208
209 212 214 215 216
217 218 219 219 221 222 225 228 229 229 230 232 233 233 233 234 235 236 237
239
239 240 241 242 243 244 245 245 247 248 249 249
249 250
Table of Contents
Observing events PDF document generation PDF required configuration Generating PDFs
250 251 251 252
Email Configuring the SMTP settings Writing an email template
257 257 259
Sending an email The future of Seam Summary
261 262 263
and
252 254 254 255 255 256
259 259 260 260 260
Appendix: JBoss Tools
265
Index
273
Overview of JBoss Tools Installing JBoss Tools Creating Seam projects Managing Seam projects JSP / Facelet editor Pages.xml editor Components.xml editor Running Seam projects Summary
265 266 267 269 270 270 271 271 272
[ vi ]
Preface This book provides a complete walk-through of developing Web applications using Seam, Facelets, and RichFaces, and explains how to deploy them to the JBoss Application Server. This book introduces you to the fundamentals of Seam applications, describing topics such as Injection, Outjection, and Bijection. You will understand the Facelets framework, AJAX, database persistence, and advanced Seam concepts, through the many examples explained in the book. A practical approach is taken throughout the book to describe the technologies and tools involved. You will add functionality to Seam applications after you learn how to use the Seam Generator RAD tools and how to customize and fully test application functionality. Hints and tips on how to use Seam and the JBoss Application Server are provided along the way.
What this book covers
Chapter 1 provides an overview of the Seam Framework, and describes the benefits that we, as Java developers, will gain by using the framework. We learn that Seam is much more than a framework, and that it comes packed with support for numerous other features. Chapter 2 teaches you how to develop applications using Seam, and explains some of the features discussed in previous chapter. In this chapter, we will learn the basic structure of a Seam application and learn more about Seam components. We will also see exactly how Seam bridges the gap between the Web tier and the Server tier. Chapter 3 takes you through page navigation using Seam page flows. We also see how we can define page flow within the pages.xml file of our applications by defining both static rules and rules encompassing JSF expression language.
Preface
Chapter 4 shows you why Facelets is recommended, and what it offers over JSP. We'll also look at the SeamGen tool and see why this is a much better way of creating Seam applications and building scripts. Finally, we bring both SeamGen and Facelets together and re-create the Vacation Planner project that we wrote in Chapter 3, (realizing in the course of doing so how much easier and more advanced it is), using these technologies In Chapter 5, we take a look at TestNG and see how it can be used to build sets of test suites. We have a look at how Seam provides excellent facilities by allowing us to perform testing of our applications, at the class, Seam component, and user interface levels. In Chapter 6, we take a look at RichFaces and learn that it's a JSF component library that allows us to easily build rich user interfaces within web applications. This chapter explains how Seam is fully integrated with RichFaces, and that applications generated by SeamGen require no special configuration to allow RichFaces to be used. This chapter also lists all of the components available within RichFaces. Chapter 7 introduces database persistence with Seam. We take a closer look at how SeamGen helps us to create and configure our applications, allowing test, development, and production profiles to be configured. Chapter 8 takes a closer look at what exactly a conversational application is and why they make web application development easier and more functional. We also take a look at different component scopes and see why they are important to Seam. In Chapter 9, we take a look at AJAX and how it can be applied to Seam applications. We also take a closer look at the two different AJAX technologies within the Seam framework: Seam Remoting and AJAX4JSF. Chapter 10 discusses how security is implemented within a Seam application highlighting the most common features, such as logon pages, user access rights, CAPTCHAs, and so on, which are used to secure Seam applications. We also highlight some of the more advanced security features that can be used. Chapter 11 walks through some of the more advanced features of Seam, such as internationalization, URL rewriting, PDF document generation, and so on, and also discusses how these can be used within our web applications.
[]
Preface
Who this book is for
This book is for Java EE application developers who are new to Seam and who are interested in developing with Seam 2.x. You need a basic understanding of Java EE and must also be aware of EJB3, although you do not need to know it in detail. Experience with JBoss AS would be an advantage, but all you really need is to be comfortable using any application server. Knowledge of AJAX and JavaScript would also be beneficial, although not necessary.
Conventions
In this book, you will find a number of styles of text that distinguish between different kinds of information. Here are some examples of these styles, and an explanation of their meaning. Code words in text are shown as follows: "The element defines the pages to which the enclosed navigation rules are applicable." A block of code will be set as follows: Whoops. Better make sure we write some better tests !
When we wish to draw your attention to a particular part of a code block, the relevant lines or items will be shown in bold: @Entity @Name("customer") @Table(name="CUSTOMER_TABLE") public class Customer { // .. Omitted for brevity @Id @GeneratedValue @Column(name="CUSTOMER_ID") public Long getId() { return id; }
[]
Preface
Any command-line input or output is written as follows: [input] Enter the JDBC URL for your database [jdbc: hsqldb:.] [jdbc:hsqldb:.]
New terms and important words are shown in bold. Words that you see on the screen, in menus or dialog boxes for example, appear in our text like this: "The following action that is within this class is executed when the user clicks on the Select button on our web page." Warnings or important notes appear in a box like this.
Tips and tricks appear like this.
Reader feedback
Feedback from our readers is always welcome. Let us know what you think about this book—what you liked or may have disliked. Reader feedback is important for us to develop titles that you really get the most out of. To send us general feedback, simply drop an email to
[email protected], and mention the book title in the subject of your message. If there is a book that you need and would like to see us publish, please send us a note via the SUGGEST A TITLE form on www.packtpub.com or by sending an email to
[email protected]. If there is a topic that you have expertise in and that you are interested in either writing or contributing to a book on, see our author guide on www.packtpub.com/authors.
Customer support
Now that you are the proud owner of a Packt book, we have a number of things to help you to get the most from your purchase.
[]
Preface
Downloading the example code for the book
Visit http://www.packtpub.com/files/code/5920_Code.zip to directly download the example code.
Errata
Although we have taken every care to ensure the accuracy of our contents, mistakes do happen. If you find a mistake in one of our books—maybe a mistake in text or code—we would be grateful if you would report this to us. By doing so, you can save other readers from frustration, and help us to improve subsequent versions of this book. If you find any errata, please report them by visiting http://www.packtpub. com/support, selecting your book, clicking on the let us know link, and entering the details of your errata. Once your errata are verified, your submission will be accepted and the errata added to any list of existing errata. Any existing errata can be viewed by selecting your title from http://www.packtpub.com/support.
Piracy
Piracy of copyright material on the Internet is an ongoing problem across all media. At Packt, we take the protection of our copyright and licenses very seriously. If you come across any illegal copies of our works in any form on the Internet, please provide us with the location address or website name immediately so that we can pursue a remedy. Please contact us at
[email protected] with a link to the suspected pirated material. We appreciate your help in protecting our authors, and our ability to bring you valuable content.
Questions
You can contact us at
[email protected] if you are having a problem with any aspect of the book, and we will do our best to address it.
[]
Introduction to Seam Enterprise Java development has changed dramatically in recent years, particularly with the introduction of the Java Enterprise Edition version 5 (Java EE 5) specifications in early 2006. One of the main goals of Java EE 5 was to make the development of enterprise Java applications easier through the use of annotations. Annotations were first introduced to the Java platform with Java SE 5, and allow information about Java classes to be embedded within the Java source files themselves, rather than being stored in external XML files. Java EE 5 promotes a tiered development pattern by using Session Beans to develop server-side code, and Java Server Faces (JSF) to develop web based user interfaces. The Java EE specification, however, doesn't specify how these two technologies are to interoperate. This is where the Seam Framework fits in, helping to bridge the gap between server-side programming and web development. In this chapter, we will learn what Seam is and why we, as Java enterprise developers, should use Seam. We'll take a brief look at why Seam is different when compared to the previous frameworks used by Java enterprise developers, and then we'll look at how we can install Seam and the JBoss Application Server. Finally, we'll run some sample applications to confirm whether we've got the infrastructure installed correctly, so that we can learn all about the Seam framework.
What is Seam?
As most Java developers will know, there are many web frameworks available to the Java developer. But why Seam? Before we discuss what exactly is Seam and what makes Seam a different framework, let's take a very brief look at the history of the earlier web frameworks.
Introduction to Seam
With the increased popularity of the Model-View-Controller (MVC) pattern and frameworks such as Struts, Java web development suddenly got a whole lot easier. With de facto standard frameworks, developers were able to concentrate on their application logic rather than think about the plumbing of how to develop scalable database-driven web applications. However, with these frameworks, developers still had to think about HTTP requests and responses, and developing effective web-based user interfaces implied writing HTML code. The next evolutionary stage in web development came in 2004 when the Java Server Faces (JSF) technology was created. JSF uses a component-based approach to web development, unlike previous frameworks such as Struts. This component-based approach opened up the market for drag–and–drop style development environments. If a developer wanted to have a table of results shown on a web page backed by database queries, this could be achieved using drag-and-drop and minimal coding effort. If, for example, the developer wanted to change the user interface to allow the table of results to have clickable column headings, this could typically be achieved by setting a few properties within the IDE rather than having to write lots of Java code and HTML. Oracle's JDeveloper and Sun's Java Studio Creator, amongst others, offered GUI builders for web applications—the type of technology previously found only in GUI designers for desktop applications. All of the modern IDEs (Eclipse, NetBeans, and IntelliJ IDEA) now support this type of drag-and-drop support for JSF development. Examples of such a type of support in Eclipse (using JBoss Tools) and NetBeans 6.1 are shown in the following screenshots.
[]
Chapter 1
We've now seen how JSF came to be, and how web development used to be tricky and cumbersome, but how does Seam fit into this web development landscape? What is Seam, and why is it different? In simple terms, Seam is a web framework that makes developing web applications simpler. Because they have become standard with Java EE 5, Seam makes extensive use of annotations to help us achieve this goal. All of the annotations used within Seam applications take common-sense defaults with the aim of cutting down on the amount of configuration required by annotations. This use of default values is called configuration by exception. In most cases, the default values for annotations are correct and applicable to the task at hand, meaning that less configuration data has to be entered into the application code. Of course, it is still possible (and necessary) to change configuration values and add parameters into annotations. But in most cases, configuration by exception helps cut down on the amount of configuration required. The use of annotations in Seam does not completely remove the need for XML configuration. However, it is greatly reduced when compared to other frameworks such as Spring, Struts, or JSF. With the exception of defining page flows in XML files, Seam rarely uses XML configuration. The XML configuration in these cases is generally very similar among applications, and is not considered a large part of application development.
[]
Introduction to Seam
With the Java EE 5 framework, Dependency Injection has become a standard feature of enterprise application development. Within Java EE 5, Dependency Injection allows components to be automatically set within classes without the need for the developer to call any setter methods. For example, Session Beans can automatically be looked up using the @EJB annotation, or the Entity Manager Factory can be set using the @PersistenceUnit annotation. Seam extends the Dependency Injection capabilities provided by Java EE 5 and allows Seam components to be both injected into and outjected from other components. This facility to perform both Dependency Injection and Dependency Outjection is more commonly called Dependency Bijection, and will be discussed fully in Chapter 2. The Seam Framework is a stateful framework. Seam components can be declared to have different contextual scopes. These scopes range from stateless (having no state) through to long running scope or application lifetime scope. Frameworks such as Spring and Struts are stateless frameworks in the sense that they do not offer facilities to define scope for components. All of these frameworks allow components to be cached using various caching strategies, but Seam component scope further allows different components to be declared with different levels of scope. This is discussed in depth later in this book. Seam components are used to define entities and classes that manage entities within an application. Seam does not impose any design patterns on components. We do not need to extend Seam classes or implement Seam interfaces to define Seam components. A Seam component can be any POJO (Plain Old Java Object). In this book, however, we will use Session Beans (both stateless and stateful) as Seam components. This will be useful because of the benefits (security, transactions, pooling, and so on) that Session Beans provide us. With Seam we don't have to use Session Beans as Seam components. A Seam component can be just as easily declared a POJO as can it be declared a Session Bean. Seam is supplied with, and fully integrates with, the RichFaces component library. RichFaces provides many user interface components, and is described fully in Chapter 6. JSF didn't really take off as a development model until it became the standard web framework used within Java EE 5 applications. The first versions of Seam used JSF with JSP as the view technology, but this is no longer the view technology recommended by JBoss, and has been superseded by Facelets. Seam also allows other view technologies, such as Flex, GWT, and Wicket, to be used. However, these technologies are not covered in this book. JSF or JSP can still be successfully used as the view technology for Seam applications, and we will be using them in the next few chapters of this book, to learn the basics of Seam. We will discuss Facelets in Chapter 4, when we have a good understanding of Seam, and will see what it offers that JSF does not. [ 10 ]
Chapter 1
Over the past few years, agile techniques have turned favorable, including Test Driven Development and Unit Testing. Traditionally, testing web applications has been a fairly complex task. However, Seam provides a simple yet effective mechanism for unit testing that includes not only testing our application logic, but also testing the user interface logic. In Chapter 5, we will detail how we can successfully test our Seam applications and provide good test coverage by using the TestNG test tool. So far, we have discussed Seam as though it were only a web framework. Seam promises to be much more than that. At the beginning of this chapter, we learned that Seam helps bridge the gap between server-side programming and web programming. Well, what exactly does this mean? In traditional J2EE, Java EE 5 web applications, database tables are typically modeled as either Entity Beans or Entities with Persistence. The typical flow of events in a database-driven web application would be: 1. A web page requests information that is held in the database. 2. The server makes a request to the database and retrieves the entities. 3. The entities are detached (or worse, converted into DTOs) and returned to the web page. 4. The web page displays "dumb" data. This technique of detaching objects and displaying "dumb" data works quite well for small domain models that don't have many relationships between objects. However, when there are complex relationships, the web client cannot always access the entire domain model, as only certain aspects of the model have been serialized to the client. This can lead to lazy initialization errors, where clients try to access parts of our data model that have yet to be initialized from the database. Seam alleviates this problem by allowing Java EE 5 Persistent Entities to be used within the web tier. In Java EE 5, Persistent Entities are simple POJOs. Hence, full access to an applications domain model is available on the web tier within Seam applications. Seam provides the Hibernate Validator classes as a part of the framework, and these provide an additional level of entity validation over what the Java EE framework provides as standard. We can therefore validate our entity properties easily in the user interface, to ensure that invalid values are not entered into our domain model. We will discuss database persistence in depth in Chapter 7.
[ 11 ]
Introduction to Seam
More often than not, web applications require users to traverse through several pages before completing a given action. Consider, for example, applying for motor insurance. As users of motor insurance web sites, we have to enter our name, address, the use of our motor vehicle, the type of insurance required, and so on, before we can finally get a quote for insurance. Typically, this type of procedure is mapped out as a series of web pages with the Previous and the Next buttons. How many times have you accidentally closed the browser in the middle of applying for or buying something on the Internet? How many times have you pressed the Back button and lost all of the submitted data that you had just entered? Seam provides the solution to these problems in terms of conversations, which we'll discuss in Chapter 8. These conversations can be anything from a simple one-page request/response conversation through to a multipage conversation that lasts several hours or longer! A conversation is an example of how Seam works as a stateful framework, with a conversation being one of the different levels of component scope. With the advent of modern web applications and AJAX, users expect a higher level of interactivity within their web applications. The distinction between desktop applications and web applications is becoming increasingly blurry. Manually crafting JavaScript code can be time-consuming and prone to error, and isn't always the best way to add AJAX functionality to an application. The Seam Framework provides AJAX support via tight integration, with the AJAX4JSF library helping to alleviate the issues with writing the JavaScript code manually. We will discuss AJAX4JSF support in Chapter 9. Most applications need some sort of security access to restrict application functionality between users. Sometimes, a simple level of security access is required, whereby, only logged-on users can access certain pages—for example, so that only registered and logged-on users can post comments on a blog. Sometimes, this level of security isn't sufficient and a role-based security model is required. For example, again in a blog application, only users with the editor role would be able to edit and delete comments. You'll be pleased to know that Seam provides both user-based and role-based security mechanisms. We'll discuss these in detail in Chapter 10. On top of all of these features that the framework provides, Seam is also supplied with a tool for rapidly generating application skeletons. The SeamGen tool allows application skeletons to be built via a series of interactive questions that are asked to the developer. Throughout the course of this book, we'll first learn how to write Seam applications without using the SeamGen tool, so that we can get a good understanding of the different parts of a Seam application and how they all fit together. Only when we have a solid understanding of what constitutes a Seam application, we'll introduce the SeamGen tool and show the benefits and performance gains that it provides. [ 12 ]
Chapter 1
Why use Seam?
So, how is Seam different from other web frameworks, and why should we learn a new technology? Seam is a stateful, conversational framework, as opposed to being a stateless framework such as Spring or Struts, which provide rapid application development tools that wouldn't be unfamiliar in a RAD environment such as Ruby on Rails. Seam integrates different aspects of Java EE, providing us with a unified development model where we don't need to worry about redundant patterns such as DTOs. We can access our domain model and data model and all of their classes from within our web tier, without worrying about detached classes and lazy initialization errors. Seam uses the Hibernate Validator classes to provide additional validation on classes over what Java EE provides as standard. We could use JPA as our persistence provider if we wished, but by no means is this mandatory. We can use as many or as few aspects of the Java EE specification as we wish—a simple Seam application could use Servlet and JSF, and be deployed on Tomcat. Alternatively, we could use more advanced Java EE features such as Session Beans and JPA, and deploy our applications to full Java EE Application Servers such as JBoss, GlassFish, or Weblogic. Seam components don't need to extend any Seam classes or implement any Seam interfaces—they can be simple POJOs or Session Beans. Finally, Seam requires a minimal XML configuration, with the majority of configuration being required as Java annotations, where the default values are usually the correct ones.
Seam requirements
To compile and build Seam applications, we need JDK 5.0 or above installed. We also need Apache Ant 1.6 or above (http://ant.apache.org) installed, as we use this as the build tool for the projects that we build throughout this book. Here, we do not describe how to install Apache Ant as there is full documentation for this on the Apache Ant web site. To run our Seam applications, we need to deploy them to a Java Application Server. Throughout this book, we will be using the JBoss Application Server version 5.0. Many other application servers can be used for deploying and running Seam applications, such as WebSphere, WebLogic, GlassFish, or even Tomcat. However, we will concentrate solely on the JBoss Application Server.
[ 13 ]
Introduction to Seam
The recommended version of Java for running Seam applications is 5.0. As at the time of writing this book, Seam and the JBoss Application Server have not been fully tested with JDK 6.0.
Installing JBoss Application������� ������������������ Server
The JBoss Application Server can be downloaded from the JBoss web site at http://www.jboss.org/jbossas/downloads/.
Throughout this book, we will be using JBoss Application Server version 5.0.0.GA. This version is licensed under the LGPL license. Please check and read the license agreement before downloading the software. When downloading JBoss, make sure you download the application server (with a name similar to JBoss-5.0.0.GA.zip) rather than the source code (which has a name similar to JBoss-5.0.0-src.tar.gz). As the application server is written in pure Java, there is only one download for all operating systems. Hence, regardless of whether you are developing on Windows, MaxOS X, or Linux, the download is the same.
[ 14 ]
Chapter 1
After downloading the JBoss Application Server, use your favorite unzipping tool to extract the JBoss Application Server. Once unzipped, you should find the directory structure of the application server to be as shown in the following screenshot.
Starting the JBoss Application Server
The JBoss Application Server is started on Windows by executing the / bin/run.bat command, where is the directory into which you have installed JBoss (for example, C:\jboss-5.0.0.GA). The run.bat application can be started with different command switches to start JBoss in different configurations. The default configuration is correct for our Seam applications throughout the course of this book. So, the easiest way to start JBoss is by double-clicking run.bat within an explorer window.
[ 15 ]
Introduction to Seam
Once started, the JBoss Application Server window will contain a log of all of the startup events that have occurred, and will appear as shown in the following screenshot. If this window indicates any errors upon startup, check whether you have downloaded and installed your JDK correctly before proceeding. Depending upon your hardware configuration, starting the application server can take anywhere between 20 seconds and a few minutes.
To shut down the JBoss Application Server, we can simply press Ctrl+C within the application server console window. To start the application server in either Linux or MacOS X, open up a command window and execute the /bin/run.sh command. Refer to the following example: /Applications/jboss-5.0.0.GA/bin $> ./run.sh
Similar to Windows, press Ctrl+C within the JBoss Application Server window to shut down the application server.
Installing Seam ����
The Seam framework can be downloaded from http://seamframework.org/ Download.
[ 16 ]
Chapter 1
The latest stable release of Seam is 2.1.1.GA, which is the version we will be using throughout this book. Download this package from the Seam web site and unzip it into a directory of your choice. The name of the download will be similar to jboss-seam-2.1.1.GA.zip. As with the JBoss Application Server, Seam is platform-independent. So, there is only one package to download and install, irrespective of your operating system. Again, please read the license agreement for Seam, and make sure that you are happy with it before downloading the product. After extracting the Seam archive, you should see the following directory structure:
Testing our Seam installation ������������
Now that we have downloaded and installed both the JBoss Application Server and the Seam Framework, we can deploy one of the sample applications provided with Seam to ensure that we have got everything configured correctly. To test our environment, we need to: 1. Start the JBoss Application Server 2. Deploy the sample application 3. Run the sample application [ 17 ]
Introduction to Seam
Start the JBoss Application Server
As we mentioned in the earlier section, starting the JBoss Application Server is a simple matter of either double-clicking on the run.bat file or executing the run.sh script. Startup the Application Server using the appropriate technique and ensure that there are no errors displayed within the console window.
Deploy the sample application
Each of the Seam sample applications can be built using pre-supplied ant scripts. These scripts allow applications to be built and deployed from the command line. Before building and deploying these sample applications, we need to edit the /build.properties file to specify the location of the JBoss Application Server. We need to do this so that the compiled application can automatically be deployed to the application server. Edit this file and add a line that sets the jboss.home property to the location of the application server, as shown (/Applications/jboss-5.0.0.GA) in the following example screenshot:
We need to be careful on Windows systems when using the "\" character because of the way that Java escapes characters. On a Windows system, we would declare the jboss.home property as either c:/jboss-5.0.0.GA or c:\\jboss-5.0.0.GA.
Run the sample application
Now that we have started the application server and specified its location within the Seam example configuration files, we can build one of the sample applications to ensure that it runs correctly.
[ 18 ]
Chapter 1
Open up a command prompt (or command shell) and navigate to the / examples/numberguess directory. Within this directory, execute the ant deploy task, which will build the Number Guess example application and deploy it to our running instance of the application server. In the case of the JBoss Application Server, deploying the sample application is simply a matter of the application's .ear file being copied into the /server/default/deploy directory.
Assuming that no errors were displayed whilst deploying the application, we can open up a browser and point it to http://localhost:8080/seam-numberguess to test the application, as shown in the following screenshot:
[ 19 ]
Introduction to Seam
In addition to running the sample application, we can run the unit tests that are supplied with it by executing the ant test command. The Seam Framework provides excellent support for unit testing applications, which we will discuss in detail in Chapter 5. For the moment though, execute the test target command and note that several tests run successfully.
When performing unit tests using the embedded JBoss Application Server, such as the ones performed in the Number Guess example application, we must use JDK 5 and not JDK 6. This is because the embedded JBoss Application Server does not yet work correctly with JDK 6.
Summary
In this chapter, we've provided an overview of what the Seam Framework is, and the benefits that we as Java developers, will gain by using the framework. We've learned that Seam is much more than a web framework, and that it provides: •
Java 5 annotation support, allowing Seam components to be configured easily
•
Dependency Bijection facilities and component lookup, without needing to write large amounts of XML
•
Integration with both JSF and Facelets web view technologies
[ 20 ]
Chapter 1
•
First-class support for testing Seam components both as POJOs and within the JBoss embeddable Application Server
•
Tight integration with RichFaces (including AJAX4JSF) and ICEFaces
•
An application framework for handling database persistence
•
AJAX support via integration with the AJAX4JSF library
•
User-based and role-based security access
•
A tool for generating skeleton applications
After the introduction to Seam, we saw that Seam can be deployed to many different Application Servers such as JBoss Application Server, WebLogic, and Tomcat. We saw how we are going to use the JBoss Application Server throughout this book, and also how to install, start, and stop this server. Finally, we looked at how to download the Seam Framework, and how to configure it to allow us to compile and deploy the supplied sample applications. In the next chapter, we are going to look in detail at the Seam Framework, and will begin our journey of learning how to develop with Seam.
[ 21 ]
Developing Seam Applications In the previous chapter, we learned what the Seam Framework is, and what it offers to Java developers. In this chapter, we are going to start learning how to develop applications using Seam, and we will see some of the features we had discussed previously. In this chapter, we will learn the basic structure of a Seam application. We will see in practice how Seam Injection and Outjection work, and we will learn more about Seam components. We will also see exactly how Seam bridges the gap between the Web tier (using Java Server Faces) and the Server tier (using Enterprise Java Beans).
Seam application architecture As most enterprise Java developers are probably familiar with JSF and JSP, we will be using this as the view technology for our sample applications, until we have a solid understanding of Seam. In Chapter 4, we will introduce Facelets as the recommended view technology for Seam-based applications.
Developing Seam Applications
In a standard Java EE application, Enterprise Application Resource (EAR) files contain one or more Web Application Resource (WAR) files and one or more sets of Java Archive (JAR) files containing Enterprise JavaBeans (EJB) functionality. Seam applications are generally deployed in exactly the same manner as depicted in the following diagram. It is possible to deploy Seam onto a servlet-only container (for example, Tomcat) and use POJOs as the server-side Seam components. However, in this situation, we don't get any of the benefits that EJBs provide, such as security, transaction handling, management, or pooling. Application.ear
Application.jar
Application.war
jboss-seam.jar
Seam components
Within Seam, components are simple POJOs. There is no need to implement any interfaces or derive classes from a Seam-specific base class to make Seam components classes. For example, a Seam component could be: • • • •
A simple POJO a stateless Session Bean a stateful Session Bean a JPA entity and so on
Seam components are defined by adding the @Name annotation to a class definition. The @Name annotation takes a single parameter to define the name of the Seam component. The following example shows how a stateless Session Bean is defined as a Seam component called calcAction. package com.davidsalter.seamcalculator; @Stateless @Name("calcAction") public class CalcAction implements Calc { ... } [ 24 ]
Chapter 2
When a Seam application is deployed to JBoss, the log output at startup lists what Seam components are deployed, and what type they are. This can be useful for diagnostic purposes, to ensure that your components are deployed correctly. Output similar to the following will be shown in the JBoss console log when the CalcAction class is deployed: 21:24:24,097 INFO [Initialization] Installing components... 21:24:24,121 INFO [Component] Component: calcAction, scope: STATELESS, type: STATELESS_SESSION_BEAN, class: com.davidsalter. seamcalculator.CalcAction, JNDI: SeamCalculator/CalcAction/local
Object Injection and Outjection
One of the benefits of using Seam is that it acts as the "glue" between the web technology and the server-side technology. By this we mean that the Seam Framework allows us to use enterprise beans (for example, Session Beans) directly within the Web tier without having to use Data Transfer Object (DTO) patterns and without worrying about exposing server-side functionality on the client. Additionally, if we are using Session Beans for our server-side functionality, we don't really have to develop an additional layer of JSF backing beans, which are essentially acting as another layer between our web page and our application logic. In order to fully understand the benefits of Seam, we need to first describe what we mean by Injection and Outjection. Injection is the process of the framework setting component values before an object is created. With injection, the framework is responsible for setting components (or injecting them) within other components. Typically, Injection can be used to allow component values to be passed from the web page into Seam components. Outjection works in the opposite direction to Injection. With Outjection, components are responsible for setting component values back into the framework. Typically, Outjection is used for setting component values back into the Seam Framework, and these values can then be referenced via JSF Expression Language (EL) within JSF pages. This means that Outjection is typically used to allow data values to be passed from Seam components into web pages. Seam allows components to be injected into different Seam components by using the @In annotation and allows us to outject them by using the @Out annotation. For example, if we have some JSF code that allows us to enter details on a web form, we may use an tag such as this:
[ 25 ]
Developing Seam Applications
The Seam component calculator could then be injected into a Seam component using the @In annotation as follows: @In private Calculator calculator;
With Seam, all of the default values on annotations are the most likely ones to be used. In the preceding example therefore, Seam will look up a component called calculator and inject that into the calculator variable. If we wanted Seam to inject a variable with a different name to the variable that it is being injected into, we can adorn the @In annotation with the value parameter. @In (value="myCalculator") private Calculator calculator;
In this example, Seam will look up a component called myCalculator and inject it into the variable calculator. Similarly, if we want to outject a variable from a Seam component into a JSF page, we would use the @Out annotation. @Out private Calculator calculator;
The outjected calculator object could then be used in a JSF page in the following manner:
Example application
To see these concepts in action, and to gain an understanding of how Seam components are used instead of JSF backing beans, let us look at a simple calculator web application. This simple application allows us to enter two numbers on a web page. Clicking the Add button on the web page will cause the sum of the numbers to be displayed. This basic application will give us an understanding of the layout of a Seam application and how we can inject and outject components between the business layer and the view. The application functionality is shown in the following screenshot.
[ 26 ]
Chapter 2
The sample code for this application can be downloaded from the Packt web site, at http://www.packtpub.com/support.
For this sample application, we have a single JSF page that is responsible for: • • •
Reading two numeric values from the user Invoking business logic to add the numbers together Displaying the results of adding the numbers together
Seam Calculator Value 1: Value 2: Add them together gives:
[ 27 ]
Developing Seam Applications
We can see that there is nothing Seam-specific in this JSF page. However, we are binding two inputText areas, one outputText area, and a button action to Seam components by using standard JSF Expression Language. JSF EL calculator.value1
Seam Binding
calculator.value2
This is bound to the member variable value2 on the Seam component called calculator. This value will be injected into the Seam component.
calculator.answer
This is bound to the member variable answer on the Seam component called calculator. This value will be outjected from the Seam component.
calcAction.calculate
This will invoke the method calculate() on the Seam component called calcAction.
This is bound to the member variable value1 on the Seam component called calculator. This value will be injected into the Seam component.
Our business logic for this sample application is performed in a simple POJO class called Calculator.java. package com.davidsalter.seamcalculator; import java.io.Serializable; import org.jboss.seam.annotations.Name; @Name("calculator") public class Calculator { private double value1; private double value2; private double answer; public double getValue1() { return value1; } public void setValue1(double value1) { this.value1 = value1; } public double getValue2() { return value2; } public void setValue2(double value2) { this.value2 = value2; } public double getAnswer() { [ 28 ]
Chapter 2 return answer; } public void add() { this.answer = value1 + value2; } }
This class is decorated with the @Name("calculator") annotation, which causes it to be registered to Seam with the name, "calculator". The @Name annotation causes this object to be registered as a Seam component that can subsequently be used within other Seam components via Injection or Outjection by using the @In and @Out annotations. Finally, we need to have a class that is acting as a backing bean for the JSF page that allows us to invoke our business logic. In this example, we are using a Stateless Session Bean. The Session Bean and its local interface are as follows. In the Java EE 5 specification, a Stateless Session Bean is used to represent a single application client's communication with an application server. A Stateless Session Bean, as its name suggests, contains no state information; so they are typically used as transaction façades. A Façade is a popular design pattern, which defines how simplified access to a system can be provided. For more information about the Façade pattern, check out the following link: http://java.sun.com/blueprints/corej2eepatterns/Patterns/ SessionFacade.html
Defining a Stateless Session Bean using Java EE 5 technologies requires an interface and an implementation class to be defined. The interface defines all of the methods that are available to clients of the Session Bean, whereas the implementation class contains a concrete implementation of the interface. In Java EE 5, a Session Bean interface is annotated with either the @Local or @Remote or both annotations. An @Local interface is used when a Session Bean is to be accessed locally within the same JVM as its client (for example, a web page running within an application server). An @Remote interface is used when a Session Bean's clients are remote to the application server that is running within a different JVM as the application server. There are many books that cover Stateless Session Beans and EJB 3 in depth, such as EJB 3 Developer's Guide by Michael Sikora, published by Packt Publishing. For more information on this book, check out the following link: http://www.packtpub.com/developer-guide-for-ejb3
[ 29 ]
Developing Seam Applications
In the following code, we are registering our CalcAction class with Seam under the name calcAction. We are also Injecting and Outjecting the calculator variable so that we can use it both to retrieve values from our JSF form and pass them back to the form. package com.davidsalter.seamcalculator; import javax.ejb.Stateless; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Out; import org.jboss.seam.annotations.Name; @Stateless @Name("calcAction") public class CalcAction implements Calc { @In @Out private Calculator calculator; public String calculate() { calculator.add(); return ""; } } package com.davidsalter.seamcalculator; import javax.ejb.Local; @Local public interface Calc { public String calculate(); }
That's all the code we need to write for our sample application. If we review this code, we can see several key points where Seam has made our application development easier: •
All of the code that we have developed has been written as POJOs, which will make unit testing a lot easier.
•
We haven't extended or implemented any special Seam interfaces.
•
We've not had to define any JSF backing beans explicitly in XML. We're using Java EE Session Beans to manage all of the business logic and web-tier/business-tier integration.
•
We've not used any DTO objects to transfer data between the web and the business tiers. We're using a Seam component that contains both state and behavior. [ 30 ]
Chapter 2
If you are familiar with JSF, you can probably see that adding Seam into a fairly standard JSF application has already made our development simpler. Finally, to enable Seam to correctly find our Seam components and deploy them correctly to the application server, we need to create an empty file called seam.properties and place it within the root of the classpath of the EJB JAR file. Because this file is empty, we will not discuss it further here. We will, however, see the location of this file in the section Application layout, later in this chapter. To deploy the application as a WAR file embedded inside an EAR file, we need to write some deployment descriptors.
WAR file deployment descriptors
To deploy a WAR file with Seam, we need to define several files within the \WEB-INF directory of the archive: •
web.xml
•
faces-config.xml
•
components.xml
First let's take a look at the web.xml file. This is a fairly standard file that specifies the SeamListener class, which aids internal Seam initialization and cleanup. This file also specifies that all pages with the extension .seam will be run as JSF pages. org.jboss.seam.servlet. SeamListener javax.faces.DEFAULT_SUFFIX .jsp Faces Servlet javax.faces.webapp.FacesServlet 1
[ 31 ]
Developing Seam Applications Faces Servlet *.seam
The faces-config.xml file allows Seam to be integrated into the JSF page life cycle, by adding the org.jboss.seam.jsf.SeamPhaseListener into the life cycle events chain. org.jboss.seam.jsf. SeamPhaseListener
Finally, the components.xml file allows any Seam components that we may be using to be configured. In our sample application, the only configuration that we are performing is specifying the JNDI pattern to allow EJB components to be looked up by Seam.
In the later chapters, we will look in detail at the components.xml file, as it is in this file that we will specify Seam-specific information such as security and web page viewing options.
[ 32 ]
Chapter 2
EAR file deployment descriptors
To deploy an EAR file with Seam on JBoss, we need to define several deployment descriptors within the \META-INF directory of the archive: •
application.xml
•
ejb-jar.xml
•
jboss-app.xml
The application.xml file allows us to define the modules that are to be deployed to JBoss. In this file, we need to specify the WAR file for our application and the JAR file that contains our Session Beans. We must also include a reference to the jboss-seam.jar file. SeamCalculator SeamCalculator.war /SeamCalculator SeamCalculator.jar jboss-seam.jar
In the ejb-jar.xml file, we must attach the org.jboss.seam.ejb.SeamInterceptor class onto all our EJBs. [ 33 ]
Developing Seam Applications org.jboss.seam.ejb. SeamInterceptor * org.jboss.seam.ejb. SeamInterceptor
Finally, we can specify a class loader for our Seam application in jboss-app.xml so that there are no conflicts between the classes deployed to the JBoss Application Server and the ones deployed to our application. seam.jboss.org:loader=SeamCalculator
Application layout
Before we deploy and run the test application, let's take a look at the layout of our sample application. As you can see in the following screenshot, the layout of the application follows the standard layout of a Java EE application. The application is deployed as a single EAR file. Within the EAR file, we have the Seam JAR file, our EJB JAR file, and our web application EAR file. This is the standard layout that all of our Seam applications are going to follow.
[ 34 ]
Chapter 2
Testing the application
Now that we have looked at the code for both the application and the deployment descriptors, let's build the application and test it before deploying it to the JBoss Application Server. It is traditionally quite difficult to perform unit tests on enterprise applications because of their nature. Without using Seam, the testing of web pages and Java EE resources (for example, Session Beans) requires that the application be deployed to an application server. This makes testing more difficult and can vastly increase the development time of applications. However, Seam offers great support for testing applications outside the application server, including support for testing web pages. The standard testing framework used with Seam is TestNG (http://www.testng.org). However, there are some Seam-specific extensions that make testing easier. We'll cover the full testing of Seam applications in a later chapter, but for now, let's see how to test the business logic of our application, and indeed any POJOs that we can write, by using a standard TestNG unit test.
[ 35 ]
Developing Seam Applications
TestNG uses Java 5 annotations to allow methods to be declared as tests. To declare a method as a unit test, we simply need to adorn the method with the @Test annotation. Within a test, we can then assert different conditions to test whether our code is working as expected. We could write a simple test for our calculator application as follows: package com.davidsalter.seamcalculator.test; import com.davidsalter.seamcalculator.Calculator; import org.testng.annotations.Test; public class CalculatorTest { private static double tolerance = 0.001; @Test public void testAdd() throws Exception { Calculator testCalculator = new Calculator(); testCalculator.setValue1(10.0); testCalculator.setValue2(20.0); testCalculator.add(); assert (testCalculator.getAnswer() - 30.0 < tolerance); } }
This test simply instantiates an instance of the Calculator class, sets up some test data, and invokes the add() method before asserting that the answer is as expected. Before we can run this unit test, we must also define an XML file that details which tests to run. For Seam tests, this file is typically called AllTests.xml. The contents of this file, for our sample application, are as follows:
Finally, before we can build and test the application, we need to copy a few JAR files from the Seam installation into our build path. To build and test a basic Seam application, we need to build against some of the Seam JAR files:
[ 36 ]
Chapter 2
• • •
jboss-seam.jar – This contains the main Seam Framework such as the @In and @Out annotations ejb-api.jar – This contains the references we need to build EJB 3
Session Beans
testng.jar – This contains the TestNG framework that allows us to test
our application
All of these JAR files can be found in the /lib directory, where is the directory that you have installed Seam into. If you have taken the sample application from the download bundle for this chapter, copy these files into the /lib directory of the project.
To test the sample application, we need to execute the Test target in the ant build file, as shown in the following screenshot.
Executing the tests should show that one test has been run, and that there were no failures and no skipped tests. Assuming that the test was completed successfully, we can be fairly confident that our application will run correctly when built and deployed to the application server.
Building and deploying the application
In the previous section, we saw how to perform a basic unit test to verify that the application logic is functioning correctly. Once all of our application's unit tests succeed (one of them in this case), we can build the application and deploy it to JBoss. [ 37 ]
Developing Seam Applications
To build and deploy the application, we use the ant script again. The ant script provides several different targets, allowing the project to be compiled, deployed, and archived into an EAR file. Executing the Info target provides the following help screen, which shows which targets are available.
Ant target archive
Description The archive target compiles the project and builds the packaged project output files: • • •
EJB archive JAR Web application WAR Enterprise application EAR
clean
The clean target tidies up the project by deleting all of the files that have been generated by building or testing the project.
deploy
The deploy target builds the project, packages up the archives into a deployable format (EAR file), and then deploys the application to the JBoss Application Server.
info
The info target displays a help page listing in which targets are available.
test
The test target runs the application unit tests.
undeploy
The undeploy target undeploys the application from the application server. Within the JBoss Application Server, applications can be hot deployed by copying them into the /server/default/deploy directory. Similarly, to undeploy an application, just delete it from the /server/default/deploy directory.
[ 38 ]
Chapter 2
To build and deploy the application to JBoss, execute the deploy task.
If the JBoss Application Server is running, we should see some log output appear on the JBoss console, ending with a line saying that the application has been deployed successfully, as shown below: [EARDeployer] Started J2EE application: file:/Applications/ jboss-5.0.0.GA/server/default/deploy/SeamCalculator.ear
If we don't see the application deployed to the JBoss Application Server and there were no errors during compilation, check the value of the jboss.home property within the build.properties file.
Once we see the preceding line on the JBoss console, it means that JBoss has successfully deployed the application and we can test it out. To run the application, visit the web site http://localhost:8080/SeamCalculator/ calc.seam and try adding some numbers together to prove that the application is functioning as expected.
[ 39 ]
Developing Seam Applications
Seam data validation
So far we've looked at Seam and learned how Seam can act as JSF backing beans and how it can act as the glue between our server-tier Session Beans and our web-tier JSF pages. Unfortunately, though, users could easily break our sample application by entering invalid data (for example, entering blank values or non-numeric values into the edit boxes). Seam provides validation tools to help us to make our application more robust and provide feedback to our users. Let's look at these tools now.
Data validation
In order to perform consistent data validation, we would ideally want to perform all data validation within our data model. We want to perform data validation in our data model so that we can then keep all of the validation code in one place, which should then make it easier to keep it up-to-date if we ever change our minds about allowable data values. Seam makes extensive use of the Hibernate validation tools to perform validation of our domain model. The Hibernate validation tools grew from the Hibernate project (http://www.hibernate.org) to allow the validation of entities before they are persisted to the database. To use the Hibernate validation tools in an application, we need to add hibernate-validator.jar into the application's class path, after which we can use annotations to define the validation that we want to use for our data model. We'll discuss data validation and the Hibernate validator further in Chapter 7, when we talk about persisting objects to the database. But for now, let's look at a few validations that we can add to our sample Seam Calculator application. In order to implement data validation with Seam, we need to apply annotations either to the member variables in a class or to the getter of the member variables. It's good practice to always apply these annotations to the same place in a class. Hence, throughout this book, we will always apply our annotation to the getter methods within classes. In our sample application, we are allowing numeric values to be entered via edit boxes on a JSF form. To perform data validation against these inputs, there are a few annotations that can help us.
[ 40 ]
Chapter 2
Annotation @Min
Description The @Min annotation allows a minimum value for a numeric variable to be specified. An error message to be displayed if the variable's value is less than the specified minimum can also be specified. The message parameter is optional. If it is not specified, then a sensible error message will be generated (similar to must be greater than or equal to …). @Min(value=0, message="…")
@Max
The @Max annotation allows a maximum value for a numeric variable to be specified. An error message to be displayed if the variable's value is greater than the specified maximum can also be specified. The message parameter is optional. If it is not specified, then a sensible error message will be generated (similar to must be less than or equal to …). @Max(Value=100, message="…")
@Range
The @Range annotation allows a numeric range—that is, both minimum and maximum values—to be specified for a variable. An error message to be displayed if the variable's value is outside the specified range can also be specified. The message parameter is optional. If it is not specified, then a sensible error message will be generated (similar to must be between … and …). @Range(min=0, max=10, message="…")
At this point, you may be wondering why we need to have an @Range validator, when by combining the @Min and @Max validators, we can get a similar effect. If you want a different error message to be displayed when a variable is set above its maximum value as compared to the error message that is displayed when it is set below its minimum value, then the @Min and @Max annotations should be used. If you are happy with the same error message being displayed when a variable is set outside its minimum or maximum values, then the @Range validator should be used. Effectively, the @Min and @Max validators are providing a finer level of error message provision than the @Range validator. The following code sample shows how these annotations can be applied to the sample application that we have written so far in this chapter, to add basic data validation to our user inputs. package com.davidsalter.seamcalculator; import java.io.Serializable; import org.jboss.seam.annotations.Name; import org.jboss.seam.faces.FacesMessages; import org.hibernate.validator.Max;
[ 41 ]
Developing Seam Applications import org.hibernate.validator.Min; import org.hibernate.validator.Range; @Name("calculator") public class Calculator implements Serializable { private double value1; private double value2; private double answer; @Min(value=0) @Max(value=100) public double getValue1() { return value1; } public void setValue1(double value1) { this.value1 = value1; } @Range(min=0, max=100) public double getValue2() { return value2; } public void setValue2(double value2) { this.value2 = value2; } public double getAnswer() { return answer; } … }
Displaying errors to the user
In the previous section, we saw how to add data validation to our source code to stop invalid data from being entered into our domain model. Now that we have reached a level of data validation, we need to provide feedback to the user to inform them of any invalid data that they have entered. JSF applications have the concept of messages that can be displayed associated with different components. For example, if we have a form asking for a date of birth to be entered, we could display a message next to the entry edit box if an invalid date were entered. JSF maintains a collection of these error messages, and the simplest way of providing feedback to the user is to display a list of all of the error messages that were generated as a part of the previous operation. [ 42 ]
Chapter 2
In order to obtain error messages within the JSF page, we need to tell JSF which components we want to be validated against the domain model. This is achieved by using the or tags. These are Seam-specific tags and are not a part of the standard JSF runtime. In order to use these tags, we need to add the following taglib reference to the top of the JSF page.
In order to use this tag library, we need to add a few additional JAR files into the WEB-INF/lib directory of our web application, namely: •
jboss-el.jar
•
jboss-seam-ui.jar
•
jsf-api.jar
•
jsf-impl.jar
This tag library allows us to validate all of the components () within a block of JSF code, or individual components () within a JSF page. To validate all components within a particular scope, wrap them all with the
tag as shown here:
To validate individual components, embed the tag within the component, as shown in the following code fragment.
[ 43 ]
Developing Seam Applications
After specifying that we wish validation to occur against a specified set of controls, we can display error messages to the user. JSF maintains a collection of errors on a page, which can be displayed in its entirety to a user via the tag.
It can sometimes be useful to show a list of all of the errors on a page, but it isn't very useful to the user as it is impossible for them to say which error relates to which control on the form. Seam provides some additional support at this point to allow us to specify the formatting of a control to indicate error or warning messages to users. Seam provides three different JSF facets () to allow HTML to be specified both before and after the offending input, along with a CSS style for the HTML. Within these facets, the tag can be used to output the message itself. This tag could be applied either before or after the input box, as per requirements. Facet beforeInvalidField
Description This facet allows HTML to be displayed before the input that is in error. This HTML could contain either text or images to notify the user that an error has occurred. …
afterInvalidField
This facet allows HTML to be displayed after the input that is in error. This HTML could contain either text or images to notify the user that an error has occurred. …
[ 44 ]
Chapter 2
Facet aroundInvalidField
Description This facet allows the CSS style of the text surrounding the input that is in error to be specified. …
In order to specify these facets for a particular field, the tag must be specified outside the facet scope. **
In the preceding code snippet, we can see that a CSS style called invalidInput is being applied to any error or warning information that is to be displayed regarding the field. An erroneous input field is being adorned with a double asterisk (**) preceding the edit box, and the error message specific to the inputText field after is displayed in the edit box. Applying these features to our sample application, the calc.jsp file will appear as follows: Seam Calculator
[ 45 ]
Developing Seam Applications Value 1: ** Value 2: ** Adding them together gives: [ 46 ]
Chapter 2
To allow validation information to be displayed, we made several changes to this file and to our web application. These changes are listed here: •
Adding the Seam UI tag library to the header of the JSF page.
•
Specifying which objects need to be validated, by using the or tags.
•
Defining the three different facets that specify the formatting and output of the error message for each field. These facets must be surrounded by the tag.
•
Adding the Seam and third-party JARs into the WEB-INF/lib directory of the web application.
The JSF messages collection
So far, we've only added messages to the JSF messages collection via Hibernate validators that are applied to entities within our domain model. Sometimes, it can be useful to add a message that can be displayed irrespective of any errors or input controls on a form to the messages collection. The JSF messages collection is one of Seam's in-built components and is named org.jboss.seam.faces.facesMessages. @Name("org.jboss.seam.faces.facesMessages")
This in-built component can easily be accessed within Seam applications by using the FacesMessages().instance() method. For example: FacesMessages.instance().add("Phew, that was a lot of maths !!");
If we add all of our Hibernate validators to our Calculator.java class, the code will look as follows: package com.davidsalter.seamcalculator; import java.io.Serializable; import org.jboss.seam.annotations.Name; import org.jboss.seam.faces.FacesMessages; import org.hibernate.validator.Max; import org.hibernate.validator.Min; import org.hibernate.validator.Range; @Name("calculator") public class Calculator implements Serializable { private double value1; private double value2; private double answer; @Min(value=0) @Max(value=100) public double getValue1() { [ 47 ]
Developing Seam Applications return value1; } public void setValue1(double value1) { this.value1 = value1; } @Range(min=0, max=100) public double getValue2() { return value2; } public void setValue2(double value2) { this.value2 = value2; } public double getAnswer() { return answer; } public void add() { this.answer = value1 + value2; //Access the "org.jboss.seam.faces.facesMessages" component and add a message into it. FacesMessages.instance().add("Phew, that was a lot of maths !!"); } }
In this code, we have added validators to the input data and then added a custom message into the JSF messages collection, to be displayed when a successful calculation was performed.
Building and testing the validating Seam calculator
In order to build and test the validating Seam calculator, we need to copy the additional JAR files (namely, hibernate-validator.jar, jboss-el.jar, jboss-seam-ui.jar, jsf-api.jar, and jsf-impl.jar) into the /lib directory of the sample project. A second sample project—Seam Validating Calculator—is available in the code bundle for this chapter.
To build and deploy the project onto the JBoss Application Server, we need to execute the deploy ant target.
[ 48 ]
Chapter 2
To view the application in the browser, navigate to http://localhost:8080/
SeamCalculator/calc.seam.
If any invalid data is entered into the web application, this is picked up by the Hibernate validators and displayed on the form, both next to the field containing the error and in a list at the bottom of the page, which shows all of the errors on the page.
[ 49 ]
Developing Seam Applications
In the previous screenshot, you can see that the Hibernate validator has done its job correctly and added an error message informing the user that Value 2 is invalid as abc is not a valid number. While this is technically correct, the error message is very user-unfriendly and would probably result with many unhappy customers! Later on in this book, we will look at how this error message can easily be changed in a localizable fashion so that an appropriate error message can be displayed in different languages for different users. For the moment, the important thing to note is that we have performed validation on user input and provided an error message when the validation fails. When valid information is entered into the fields and the Add button is clicked, the two inputs are added together and displayed along with a custom error message that was added to the JSF messages collection.
Summary
In this chapter, we've taken our first real steps into developing Seam applications. We've looked into Seam components and learned that they are declared with the @Name annotation. We've seen how we can inject and outject Seam components by using the @In and @Out annotations respectively. After learning about Seam components, we wrote a simple Seam application using these concepts, which shows the layout for Seam applications. In the second half of this chapter, we looked at how to add validation to Seam components and how to display validation errors and special messages on web pages, both around input components and in a list on the page. In the sample application that we built in this chapter, everything took place within a single JSF page. In the next chapter, we'll look at page flow within Seam applications, and show how we can easily build up complex routing mechanisms to navigate between pages.
[ 50 ]
Seam Page Flow In the previous chapter, we looked at the components necessary to build a simple Seam application. The simple application that we developed supported only one JSP page. However, in the real world, this is far from sufficient! Within Seam applications, there are several different ways to navigate through web pages. We will discuss these ways in this chapter, including: •
Simple navigation
•
JSF style navigation
•
Seam jPDL navigation
Simple navigation
In the previous chapter, we saw that performing business logic and redirecting a user to a different web page can be performed by adding a commandButton to a web page. The action property of a commandButton specifies the business method to be executed, and this method is then responsible for routing the user to a web page. If we want to route the user back to the same page they were viewing before clicking on the actionButton, the business method simply returns an empty string, as shown in the following code snippet. @Name("manager") … public String run() { return ""; }
Seam Page Flow
This is the simplest possible case—returning the user to the same point at which they started. Although this type of page routing can be very useful, it is highly limited. It is much more useful to redirect a user to a different page after performing some business action. In Seam, it is not necessary to return a value from a method to get the initial page re-rendered. Declaring an action as void will have the same effect as returning an empty string. @name("manager") … public void run() { }
With simple navigation, the user can be redirected to a different page by returning the path to the page from the business method, as shown in the following example. @Name("manager") … public String run() { return "/results.jsp"; }
In this example, the user clicks on the Run button, which causes the run() method to be executed with the browser page open, then the user is redirected to /results.jsp (which will in turn be routed to /results.seam as per our JSF mapping). In smaller applications, it may be completely appropriate to use this style of page routing throughout the entire application. However, this can get cumbersome as the number of pages and business methods gets larger. There is high coupling between the business logic and the application page flow because the page flow is effectively hard-coded into the application. In this scenario, if we wanted to show a summary results page before the full results, we would need to change the business method rather than simply change the page flow.
Seam ���������������� style����������� navigation
Seam style page navigation eliminates the need for hard-coding page flows into the application's business logic. Typically in JSF applications, page flows are defined in the faces-config.xml file. This JSF page flow is defined by a set of navigation rules, which can either be specified on a per-page basis (if the element is defined), or on a global basis.
[ 52 ]
Chapter 3 /myPage.jsp success /success.jsp fail /failure.jsp logout /logout.jsp
Seam applications use a similar mechanism for performing page flow. However, they are defined within a pages.xml file, which is stored in the /WEB-INF folder of a web application. It is possible to combine the two styles (JSF and Seam) by defining page flows in both, the faces-config.xml file as well as the pages.xml file. However, in larger applications, this will soon get unwieldy and difficult to support. It is recommended that page flow logic be stored in the pages.xml file to ensure that the page flow logic is defined in one central place. Defining page flows in pages.xml rather than faces-config.xml has several advantages: •
pages.xml allows parameters to be passed within page flows
•
We can use JSP expression language within pages.xml file
If you have many page flows within an application, the pages.xml file can get very large. Seam allows page flows to be defined on a per-page basis, or on the basis of all pages in a central file. In the per-page basis case, page flow files must be stored in the same location as the JSP file and must follow the standard naming syntax, which is .page.xml. The following are all examples of page flow files: •
\WEB-INF\faces-config.xml
•
\WEB-INF\pages.xml
•
\vacations.page.xml (assuming we have a \vacations.jsp file)
[ 53 ]
Seam Page Flow
Defining a page flow in pages.xml
A page flow in Seam is typically defined by specifying one or more navigation rules within a page tag. Navigation rules can be specified either for a single page, or for all pages by using a wildcard as the page name. A basic navigation rule would be defined as in the following code sample:
In this sample, we can see that the pages.xml file is defined with a root element called using the appropriate name spaces. For each page, a element is defined that holds multiple definitions. Each of these navigation elements defines navigation rules for a particular page flow action. Each navigation rule is defined by a element. Let's take a look at each of these as they are defined in this example.
element
The element defines the pages to which the enclosed navigation rules are applicable. In the previous example, the enclosed navigation rules are applicable to the /vacations.jsp JSF page. If we wanted to create a set of navigation rules applicable to a set of pages, we could specify a wildcard as the view-id, for example:
[ 54 ]
Chapter 3
element
The element is used in JSF expression language to define which business method on a Seam component is responsible for initiating our page flow. In the previous example, the public String selectVacationType() method is on a Seam component named vacationManagerAction.
element
Individual navigation rules are defined within elements. In its simplest form, a navigation rule can redirect to a different JSF page based on the outcome of the business method defined in the element.
In this example, if the business method on the Seam component returns the string city, the /city.jsp page will be displayed. You'll remember that in the previous section, we stated that we could use a JSP expression language in Seam page flows. Instead of using the if-outcome attribute of rules, we can use the if attribute to decide the page flow based upon logical decisions made in the pages.xml file.
In this example, we have created a rule that specifies that if the minimumBudget property on the destination Seam component is less than 100.0, redirect the page to the /insufficientfunds.jsp web page. This technique of allowing us to use expression language in page flows is very powerful, and one of the reasons why Seam page flow is more powerful than the typically provided page flow in a JSF application. Navigation rules are executed in the order that they are defined within the pages.xml file. This allows certain navigation rules to take precedence over others, depending upon their ordering within the file.
[ 55 ]
Seam Page Flow
Error �������� handling
It is inevitable that exceptions are thrown within the applications that we write. Seam provides functionality to allow exceptions thrown within Seam components to be caught and handled gracefully within the web user interface. To catch unhandled exceptions thrown from Seam components, we need to add an tag into the pages.xml file. This tag allows us to identify which exceptions are to be handled via the class attribute, and which web page is to be displayed when the error is caught. Within the tag, we can also add an error message by using the tag. These messages can be displayed on the resulting error page, to provide additional information to the user, showing what has gone wrong. Whoops. Better make sure we write some better tests !
In this code example, if an exception of the type com.davidsalter. HolidayException is thrown, the user will be redirected to the /error.jsp page with an appropriate message being added to the JSF messages collection. In order to configure Seam applications to use this type of exception handling, we first need to configure the org.jboss.seam.servlet.SeamFilter within the application's web.xml file. This is a one-off process that needs to be done for each web application on which we intend to use this type of exception handling. To configure the web.xml file, the following code needs to be added to the file: Seam Filter org.jboss.seam.servlet.SeamFilter Seam Filter *.seam
[ 56 ]
Chapter 3
Executing code before���������������� ���������������������� rendering pages
By using the pages.xml file, it is possible to define methods within Seam components that can be executed before a page is rendered. This is achieved by specifying the action attribute on a element.
In this example, the beforeRender() method on the vacationManagerAction Seam component will be invoked before the /vacations.jsp page is rendered. If we want to execute multiple actions before a page is rendered, we can specify the actions by using the element rather than the action attribute of the element.
The element gives us an additional advantage: it lets us define conditional page actions. Page actions can be made conditional by specifying a piece of EL within the if attribute of the element. The action is then performed only if the if attribute evaluates to true.
A working example
In the previous few sections, we've looked at some of the theory behind page flow navigation within Seam applications. Let's now pull all of this together in a working example. The complete code base for this sample application can be found within the Chapter 3/Vacation Planner folder of the code download bundle for the book.
[ 57 ]
Seam Page Flow
To gain practice with the Seam page flow, our sample application displays a set of vacation types and allows the users to select their favorite type of vacation. In addition, the user can select their minimum budget. The sample application appears as shown in the following screenshots:
Once you have gone through all of the pages and made your choices, you will be redirected to the following page:
In this sample application, we have different types of page flows being used. •
If the user selects an Adventure vacation as his or her favorite, he or she is reminded to get travel insurance.
•
If the user selects a Beach vacation as his or her favorite, he or she is reminded to take plenty of sun block.
•
If the user selects a City break as his or her favorite vacation, he or she is reminded not to tire themselves out by shopping.
•
Finally, if the user has a budget of less than $100, he or she is told they have insufficient funds and cannot afford to go on vacation. This navigation rule overrides all other rules.
[ 58 ]
Chapter 3
First of all, we need to define a Seam component that we will be using to manage all the page flows for our sample application. We will use a stateful Session Bean for this functionality. We have chosen a stateful Session Bean for the Seam manager component because, in the real world, we would be performing database queries and maintaining state across different web pages. As we want to maintain state throughout our application, a stateful Session Bean makes sense. The code for this Session Bean is as follows: package com.davidsalter.vacationplanner.action; import javax.ejb.Remove; import javax.ejb.Stateful; import javax.persistence.PersistenceException; import com.davidsalter.vacationplanner.model.Destination; import import import import import
org.jboss.seam.annotations.Destroy; org.jboss.seam.annotations.In; org.jboss.seam.annotations.Logger; org.jboss.seam.annotations.Name; org.jboss.seam.log.Log;
@Name("vacationManagerAction") @Stateful public class VacationManagerAction implements VacationManager { @In Destination destination; @Logger private Log log; public String selectVacationType() throws Exception { log.debug("DestinationType: ", destination.getDestinationType()); if (destination.getDestinationType() == Destination. DestinationType.ACTION) return "danger"; else if (destination.getDestinationType() == Destination. DestinationType.BEACH) return "beach"; else if (destination.getDestinationType() == Destination. DestinationType.CITY) return "/city.jsp"; else throw new Exception("Oops"); }
[ 59 ]
Seam Page Flow public void beforeRender() { log.info("Just about to render the page."); } @Remove @Destroy public void remove() { } }
We can see that this class is a simple POJO, similar to the Seam components that we looked at in the previous chapter. This class does not implement any Seam-specific interfaces or extend any Seam-specific classes. The @Name annotation defines the name of this Seam component as vacationManagerAction. The following action that is within this class is executed when the user clicks on the Select> button on our web page: public String selectVacationType() throws Exception {}
Within this method is a rather large if .. else block that is responsible for initiating different page flows. The following outcomes are possible from this block: User Selection
Outcome
User selects Action
The navigation rule, danger, will be executed.
User selects Beach
The navigation rule, beach, will be executed.
User selects City
The user will be forwarded to the /city.jsp page.
The user makes no selection
An exception is thrown, which will be picked up and handled by the Seam exception handling page specified in the pages.xml file.
The vacations.jsp JSF page invokes this action; it is as follows: Vacation
[ 60 ]
Chapter 3
What type of holiday do you like?
What is your minimum budget?
and
This code is very similar to what we have seen previously, except that we have introduced the and tags within the standard JSF tag. Within a JSF page, we typically use the tag to display an HTML drop-down menu. This tag renders an HTML tag, as shown: Action Beach City
Within static web applications, this list (vacation types, in our example) can be hard-coded into the HTML page. However, with a dynamic web application, we want to get this list from a database or from the members of an enumeration. The Seam tag allows us render a dynamic List, Set, DataModel, or Array as HTML code.
[ 61 ]
Seam Page Flow
The tag supports several attributes, as shown here:
Attributes disabled
Explanation
hideNoSelectionLabel
Boolean flag indicating whether the noSelectionLabel is hidden.
label
The value that is displayed to the user as an item to be selected.
noSelectionLabel
Optional item to be placed at the top of the list to indicate that no selection has been made. Typically, this would contain a text such as Please select.
value
JSF EL that specifies the Seam component data to be displayed in the drop-down list box. This can either reference a List, a Set, a DataModel, or an Array.
var
Local variables that hold the current item being rendered. This can be used, for example, to change the label depending on the item being rendered.
Boolean flag indicating whether the items are rendered or not.
Within a JSF component model, the JSF runtime converts data types that are held as backing beans or Seam components into the relevant format to allow them to be displayed on an HTML page. Seam provides standard converters to allow different component types to be rendered within drop-down lists on a web page. The converter allows us to render enumerations as data lists on HTML forms—in our example, we are rendering an enumeration into a drop-down selection list. To tell the JSF runtime that we wish to use a converter, we must declare the converter alongside the appropriate tag, and within the tag, as shown below. The tag contains no attributes.
[ 62 ]
Chapter 3
Check out Sun's JSF technology page for more details about JSF converters http://java.sun.com/javaee/javaserverfaces/.
Rendering the options in the sample application
As we have only a finite number of options to display to the user in our sample application (Action, Beach, and City), we're using an enumeration to list all of the options. In a larger application, these values would probably be pulled from a database (we'll look at how that is done in a future chapter). Each destination type is defined as a Java enumeration. This enumeration is held within a Java class, namely Destination, which also holds the minimum amount that a user of our application can spend on his or her vacation. package com.davidsalter.vacationplanner.model; import import import import import import import import
java.io.Serializable; javax.persistence.Entity; javax.persistence.EnumType; javax.persistence.Enumerated; javax.persistence.Id; javax.persistence.GeneratedValue; javax.persistence.Version; org.jboss.seam.annotations.Name;
@Name("destination") public class Destination implements Serializable { public enum DestinationType { ACTION("Action"), BEACH("Beach"), CITY("City"); private String label; DestinationType(String label) { this.label = label; } public String getLabel() { return label; } }
[ 63 ]
Seam Page Flow private double minimumBudget; @Enumerated(EnumType.STRING) private DestinationType destinationType; public DestinationType getDestinationType() { return destinationType; } public void setDestinationType(DestinationType type) { this.destinationType = type; } public double getMinimumBudget() { return minimumBudget; } public void setMinimumBudget(double minimumBudget) { this.minimumBudget = minimumBudget; } }
The enumeration has a property called label. This is used for displaying the name of the destination type within the web page. The enumeration property that is displayed within the web page is configured via the label attribute of the tag. Note that this enumeration isn't explicitly defined as a Seam component, as it isn't directly referenced within the JSF page. However, the value of the selected destination type is stored within the destination.destinationType Seam component. In this instance, the Destination class is defined as a Seam component called destination. Within our manager class, namely VacationManagerAction, we are injecting the Seam component destination. This allows data entered on the web form to be passed to our business logic Seam component. So far though, we have not seen how the drop-down list gets populated. From the code fragment, we can see that the drop-down list is bound to a Seam component called destinationTypes, but we have not defined that yet. The destinationTypes Seam component is a special component annotated with the @Factory annotation. package com.davidsalter.vacationplanner.model; import com.davidsalter.vacationplanner.model.Destination. DestinationType; import org.jboss.seam.annotations.Factory; import org.jboss.seam.annotations.Name;
[ 64 ]
Chapter 3 @Name("factories") public class Factories { @Factory("destinationTypes") public DestinationType[] getDestinationTypes() { return DestinationType.values(); } }
When the vacations.jsp web page is first loaded, Seam will try to resolve all of the required components to allow the page to be rendered correctly. If a component is not found, Seam will try to instantiate the component via its factory method, as defined by the @Factory annotation. In this example, the factory method for the destinationTypes component simply returns an array of the entries within our DestinationType enumeration. We've now seen all of the Java and JSP code that make up our sample application. The final part to look at is the pages.xml file that defines the application's page flows.
[ 65 ]
Seam Page Flow Whoops. Better make sure we write some better tests !
In the navigation rules defined in the pages.xml file, we do not have a navigation rule for the case where the destination type is set as CITY. This is because we have declared this navigation rule explicitly within the VacationManagerAction class. This has been added to show a comparison of the different navigation techniques that are available within a Seam application. Please remember that this tight coupling between business logic and presentation logic is not always a good idea.
This file defines the navigation rules for the /vacations.jsp web page when the vacationManagerAction.selectVacationType Seam component method is executed. We can see that there are three navigation rules and one exception rule defined within the file. You will remember that we previously mentioned that the navigation rules are executed in sequence from top to bottom. The first rule to be executed uses JSF expression language to check the user's minimum budget and display an appropriate page if the user has less than $100 to spend. If this rule is executed, then processing stops and the subsequent rules are not processed. In our application, therefore, if the user has insufficient funds, it doesn't matter what choices they make for the favorite vacation types, as this rule will always be executed first. Following this rule are the rules to display the appropriate pages depending on the user's selection on the web page.
Running the sample application
To run the application, we need to start up JBoss, copy the appropriate JAR files into the lib directory of the project (see the previous chapter for details), and then execute the ant deploy target. If all goes well, the application should be deployed to the JBoss Application Server and should be accessible at: http://localhost:8080/VacationPlanner/vacations.seam
[ 66 ]
Chapter 3
Seam jPDL navigation
In the previous section, we saw how it is possible to use Seam navigation rules to define page flows within an application. Seam also allows us to use the JBoss jBPM Process Definition Language (jPDL) to define page flows within an application. A full description of jBPM and jPDL is outside the scope of this book and other texts are available to describe it (see for example—Business Process Management with JBoss jBPM by Matt Cumberlidge). Developing page flows with jBPM is probably more complex than with Seam style navigation rules. However, JBoss provides tools to help developers easily visualize jPDL page flows making them much easier to understand. At the moment, it's sufficient to know that we can use jPDL to define our page flows.
Summary
In this chapter we've looked further at Seam, in particular at, page navigation using Seam page flows. We've seen how we can define page flow within the pages.xml file of our applications by defining both static rules and rules encompassing JSF expression language. We've taken our first steps into developing a vacation planning web site that we'll expand on in the future chapters. We've also looked at Seam factories and how to display drop-down combo boxes within the web pages based upon Java enumerations. So far, while learning Seam, we've concentrated entirely on using JSP as our view technology. Now that we have a good understanding of the key concepts behind Seam, we are going to investigate the Facelets view technology in the next chapter, where we'll see why JBoss prefers this over JSP.
[ 67 ]
Facelets So far, we've looked at the basics of Seam and learned how to build web applications using the Seam Framework. In all of the examples that we've seen so far, we've used JSP as our view technology, and custom build ant scripts for building and deploying the applications. However, the JBoss recommended view technology is Facelets rather than JSP. In this chapter, we will look at why Facelets is recommended, and what it offers over JSP. We'll also look at the SeamGen tool and see why this is a much better way of creating Seam applications and build scripts. SeamGen generated build scripts provide many advantages over the custom build scripts that we have seen so far, including support for advanced testing of web applications. Finally, we'll bring both SeamGen and Facelets together and re-create the Vacation Planner project that we wrote in Chapter 3 (realizing, in the course of doing so, how much easier and more advanced it is), using these technologies. In the subsequent chapters of this book, we'll build on this Vacation Planner application and see how Seam can be used to build modern web applications.
What is Facelets?
Facelets (https://facelets.dev.java.net) is a framework that provides an alternative view technology to JSP, and is one of the supported view technologies of Seam. Seam 2.1 introduced support for Apache Wicket (http://wicket.apache.org) as a Seam view technology, but that is not covered in this book. With JSP, suppose that a client requests a page from the server. The first time this page is accessed, it is converted into a servlet. This servlet is then responsible for generating the HTML that is returned to the client.
Facelets
Java Server Faces works in a different manner, by building up a tree of components that are rendered to the view as necessary (note that JSF does not necessarily have to render HTML). Each JSF page has a life cycle consisting of several stages, as shown in the following diagram. Facelets is responsible for managing the Restore View and Render Response phases within the JSF life cycle. You can read all about the JSF life cycle in Sun's Java EE 5 tutorial at: http://java.sun.com/javaee/5/docs/tutorial/ doc/bnaqq.html. Restore View Apply Requests Process Validations Update Model Values
JSF Lifecycle
Invoke Application Render Response
Why Facelets? Why not JSP?
Facelets offers several advantages over using JSP as our view technology for web applications. The following are some of these advantages: •
Templating
•
Performance
•
EL functions
•
XHTML
•
No scriptlets
Let's take a look at each of these.
Templating
Probably the main benefit in using Facelets over JSP is that Facelets offers a template approach to building web pages. This makes a component-based design much easier to implement.
[ 70 ]
Chapter 4
Facelets provides several tags that allow templates to be defined and populated with different markup: •
•
•
These tags are defined within the xmlns:ui="http://java.sun.com/jsf/facelet" namespace. Facelets has the concept of a template and a template client. Typically, the
tag is used within a template and the and tags are used within a template client.
The tag specifies which Facelets template will be used for a page. This tag is therefore very powerful, as it allows the look and feel of a web site to be changed. By changing the tag, we could easily change a web site, for example, from having a single header and a body to having a single header and two columns in the body. Many different layouts of a web application can be achieved by changing this value. Using NetBeans, for example, we can easily create eight different layouts for a web application, as shown in the following screenshot:
The tag defines place holders within a template file that can be overwritten by different Facelets pages. [ 71 ]
Facelets
The tag defines JSF markup that will be placed within a tag. If a tag is not specified for a particular tag, then the default value of the tag will be used. To get a better understanding of these tags, let's take a quick look at a sample Facelets page and see how all of these tags are used. As we mentioned earlier, the tag defines insertion points within a template where Facelets components can be placed. This can be seen in the example template.xhtml file shown here: BugTime
Within this file, we can see that the namespace for Facelets has been defined (xmlns:ui="http://java.sun.com/jsf/facelets"), and that we have defined two template insertion points, called body and footer. The following templateclient.xhtml file shows how the and tags are used to take advantage of the templating features of Facelets to replace the body and the footer sections defined within the template.xhtml file.
[ 72 ]
Chapter 4 … HTML Content Defined Here … HTML Content Defined Here
This file is defined as a standard XML file with the tag as the root node. As with the template itself (template.xhtml), this file also specifies that we are using the Facelets UI library (xmlns:ui="http://java.sun.com/jsf/ facelets"). However, because this file is referencing a template, we must specify which template to reference, by using the template attribute within the tag.
The sections within the template file that are to be replaced are then specified by using the tag.
Template parameters
The , , and tags allow us to build complex page layouts by defining a template and replacing page fragments within the template. The tag adds to this templating functionality of Facelets by allowing objects to be passed into templates that are included via the tag. The and tags are defined within the standard Facelets namespace as described earlier. The tag specifies an attribute, src, that specifies another Facelets document to be included into the page. In the following example, the Facelets document footer.xhtml is included. [ 73 ]
Facelets
The tag can be used in conjunction with the tag to specify objects within the included document. This parameter takes two attributes, name and value. The name attribute specifies the literal or name of the variable to pass to the template, whereas the value parameter specifies the EL expression value of the variable to pass.
Performance
When a JSP page is accessed for the first time, it is compiled into a servlet, which consumes resources, both in terms of time and memory. For large applications, it is often useful to precompile JSP pages so that there is no performance hit the first time a user accesses a page. With Facelets, however, this precompilation is not necessary as a component tree of all of the JSF components on a page is built. The JSF component tree is comprised of Java objects (for example, edit boxes or buttons) that contain attributes that describe the object being displayed.
EL functions
Typically, within a JSF page, we would make use of JSF Expression Language (EL). However, within Facelets, we can use JSF Expression Language functions in addition to Expression Language. So, for example, we could define some JSF code as shown in the following code snippet:
In this example, a text box is defined with a value equal to the result of invoking the trim() function on the contents of the text box. The Expression Language supported by Facelets is called Unified Expression Language. Complete details of Unified Expression Language can be found at the following URL: http://java.sun.com/products/jsp/reference/techart/unifiedEL.html
[ 74 ]
Chapter 4
XHTML
Facelets pages are defined as Extensible HTML (XHTML), with the .xhtml file extension. As XHTML documents are fully XML compliant, they can be parsed by software, usually your Java IDE, which helps to ensure that Facelet files are always valid, and will therefore be better supported by browsers. XHTML allows namespaces to be used within the web page, which allows easy identification of different libraries for different user controls. The Facelets library is defined within the xmlns:ui="http://java.sun.com/jsf/facelets" namespace. The standard JSF HTML and Core libraries are represented by the xmlns: h="http://java.sun.com/jsf/html" and xmlns:f="http://java.sun.con/jsf/ core" namespaces respectively. A typical Facelets file would have a header similar to the following header: …
By default, with Facelets, comments within XHTML are processed by the framework and will therefore show up within the rendered pages. Putting comments around a section of code is not sufficient to comment out code. If you wish to use comments within Facelets XHTML files, you need to configure Facelets to skip comments. This can be achieved by setting the facelets.SKIP_COMMENTS property to true within the web.xml file for the application. facelets.SKIP_COMMENTS true
[ 75 ]
Facelets
No scriptlets
It is very easy to write scriptlets within the body of a JSP page, for example: Valid Invalid
Facelets does not allow such scriptlets to be written inside a page. This has the effect of forcing the separation of view and application logic, making it much easier to have coherent business logic with different pluggable user interfaces.
Obtaining and installing Facelets
Facelets version 1.1.15 is provided with Seam 2.1.1.GA. At the time of writing (early 2009), this is the latest available version of both Seam and Facelets. Facelets can be independently downloaded from the Facelets web site (https://facelets.dev.java.net). To use Facelets within a Seam application, we need to make sure that jsf-facelets.jar is in the classpath for the web application. For the example applications we have looked at so far, we need to place the jsf-facelets.jar file within the lib directory of our project structure. When we look at SeamGen we will see that we do not need to download or explicitly configure Facelets for use within SeamGen generated applications. This entire configuration is taken care of for us automatically by the SeamGen tool. If you wish to use Facelets outside of a Seam application, several different JAR files, which are not included with the Facelets download, are required to allow Facelets to operate correctly. • • •
JSF Reference Implementation, or My Faces JSF Implementation JSF 1.2 API
[ 76 ]
Chapter 4
Configuring a web application for Facelets
In order to use Facelets within a JSF application, we need to make several configuration changes to the web.xml and faces-config.xml files.
Changes in the web.xml file
Within the web.xml file, we need to configure the default suffix for our web pages to be .xhtml. This is achieved by setting the javax.faces.DEFAULT_SUFFIX initialization parameter. A minimal web.xml file would look similar to the following: org.jboss.seam.servlet.SeamListener javax.faces.DEFAULT_SUFFIX .xhtml Faces Servlet javax.faces.webapp.FacesServlet 1 Faces Servlet *.seam
In this file, you will also notice that a listener, org.jboss.seam.servlet. SeamListener, has been declared. In Java EE applications, listeners are used to hook into the servlet life cycle so that initialization and destruction events can be managed. The SeamListener class is responsible for internally managing Seam components and must therefore be properly declared within the web.xml to allow Seam to manage component life cycles correctly. [ 77 ]
Facelets
Changes in the faces-config.xml file
Within the faces-config.xml file, we need to configure JSF so that Facelets is used within the Restore View and Render Response phases of the JSF life cycle. This is achieved by adding com.sun.facelets.FaceletViewHandler as a view handler. org.jboss.seam.jsf.SeamPhaseListener com.sun.facelets.FaceletViewHandler
In this sample faces-config.xml file, we can see that we have declared a class to manage Seam component life cycles. In Java EE applications, a Phase Listener hooks into the JSF life cycle events and is invoked before and after each of the phases in the JSF life cycle (Restore View, Apply Requests, Process Validations, Update Model Values, Invoke Application, and Render Response). The SeamPhaseListener is responsible for internally managing Seam components, and must therefore be declared within the faces-config.xml file.
Example Facelets files from the previous chapter
Now that we've taken a look at Facelets and the benefits it offers over standard JSP, let's convert the Seam Calculator application we wrote in Chapter 2 into a Facelets application. The source code for this application can be found in the folder named Chapter 4 within the source code download bundle for this book. The first stage of converting this application to use Facelets is to configure the web.xml and faces-config.xml files within the resources/WEB-INF folder. These files are practically the same as shown in the previous section, and haven't been repeated. [ 78 ]
Chapter 4
There are two XHTML Facelets pages for the application. The template file (template.xhtml) and the template client file (calc.xhtml). Seam Calculator Seam Calculator
In this template file, we can see that we have the following three template insertion points that can be populated from within a template client: •
Seam Calculator
•
•
The first of these tags contains the data, Seam Calculator. This is a default value for the template section header. If a template client does not specify an insertion point to be inserted, then the default value will be used. In our simple application, we do not define the header within the client file (calc.xhtml) so the default value is used.
[ 79 ]
Facelets
We've also defined a minimal amount of CSS styling to template.xhtml so that we can distinguish the different sections within the file. The CSS is stored in styles.css. body { margin: 0px; font-family: Arial,sans-serif; color: #616161; } header { font-family: Arial,sans-serif; text-align: left; font-size: 28px; margin: 20px; } footer { font-family: Arial,sans-serif; text-align: center; font-size: 10px; }
Finally, we need to define a file that references the template and contains the HTML for our calc.xhtml page.
This is our first Seam application using Facelets.
Value 1: Value 2: Add them together gives: Seam Facelets Application [ 80 ]
Chapter 4
This page looks very similar to the previous simple JSP page, except that we have now broken it down into body and footer tags. We can also see that this file has no or tags, as these are taken care of in the template. The root tag of this file is the tag, which specifies, via the template attribute, which template file to use. To verify that our application still works, we can build and deploy it to a running instance of JBoss, by using ant. ant deploy
To run the application, we need to point the web browser to: http://localhost:8080/SeamCalculator/calc.seam
If all goes well, we should see the familiar calculator application displayed in the browser, as shown in the following screenshot.
We can see that the CSS stylesheet and the template file now define the layout of the application. From this, we can see the template benefits of Facelets, and can easily see how we can change these files to give the application a completely different look and feel. You may have noticed that in the template client file, calc.xhtml, we have added the following line, which should output the HTML This is our first Seam application using Facelets.
This is our first Seam application using Facelets.
[ 81 ]
Facelets
If you have a look at the preceding screenshot of the working application, this piece of text isn't displayed anywhere on the page. This is because we entered the text within the template client file, outside of one of the tags. Facelets is clever enough to work out that this text isn't a part of the template client and therefore doesn't display it. If we wanted this text to be rendered within the browser, we would need to add it to the template file itself, template.xhtml.
SeamGen
In the previous section, we introduced Facelets, the recommended view technology for Seam applications. So far, all of our applications have been written manually, without any kind of computer assistance. We've even had to write the ant scripts manually. This can be time consuming, particularly in large projects. Fortunately, the Seam Framework is supplied with a command line tool called SeamGen that can automatically generate projects, even ones including Eclipse and NetBeans project files, for us. SeamGen is an ant script that asks the developers questions about the project they wish to develop, and then, based upon the responses to those questions, builds project templates that can be managed using either Ant, NetBeans, or Eclipse. The SeamGen application is invoked by executing the /seam.bat command in Windows, or the /seam command on Linux or Mac.
If you are running SeamGen on Linux/Mac, you may have to make the Seam application executable before you can run it. This is achieved by using the following chmod command. chmod u+x seam
The SeamGen application takes different command line parameters and allows all aspects of the project life cycle to be managed. The following table lists the different options available to the application.
[ 82 ]
Chapter 4
Option
Description
setup
Runs the setup wizard, which asks the user about the project that they are working on, such as which database drivers to use, the package name for classes, and so on. The answers are stored within the /seam-gen/build.properties file. The details specified within this file define the current project.
create-project
Creates a Seam project using the definitions stored within the /seam-gen/build.properties file.
update-project
Updates the current project to use the latest Seam JAR files.
delete-project
Deletes the current project.
archive
Builds the current project as either a WAR or a EAR file, depending upon the project configuration options.
deploy
Deploys the current project to JBoss.
undeploy
Undeploys the current project from JBoss.
explode
Deploys the current project to JBoss as an exploded archive.
restart
Restarts the currently-exploded project.
unexploded
Undeploys the currently-exploded project.
new-action
Creates a new Seam action class.
new-form
Creates a new Seam action form and action class.
new-conversation
Creates a new Seam conversation.
new-entity
Creates a new Seam entity class.
generate-model
Reverse engineers the current project's database, and generates JPA entity classes from it.
generate-ui
Creates entity management pages (create, update, delete) for existing Seam entity classes.
generate
Creates entity management pages and JPA entity classes by reverse engineering the current project's database.
SeamGen can be used to manage all aspects of a project's life cycle, from originally creating a new project through to adding new actions, reverse engineering database schemas into JPA entities, and deploying applications to the application server. Let's take a look at using SeamGen.
[ 83 ]
Facelets
Creating the Vacation Planner application using SeamGen To get a better picture of the SeamGen application, let's generate the Vacation Planner application that we created in the previous chapter, but instead of manually creating the project and building scripts, this time we'll use SeamGen.
The first stage in using SeamGen is to run the setup option. When you run the setup option, the application asks questions about the different facets of the application, such as—do we want to deploy as an EAR or a WAR, what package names do we want to use, and so on. The following screenshots show the output of the SeamGen application for creating the Vacation Planner application.
[ 84 ]
Chapter 4
Running the SeamGen application project wizard gathers information from the user by asking relevant questions. You can see that these questions have default values that can be accepted by pressing the Enter key. During the running of the SeamGen application, you will notice that the user is asked whether they wish to use ICEFaces or RichFaces. ICEFaces and RichFaces are two JSF frameworks that provide different widgets that can be placed on HTML pages (such as list selectors, tree controls, or calendars). RichFaces is a JBoss project (just as Seam is), and is the framework that we will use throughout this book. When using RichFaces, we are asked which skin we wish to use (blueSky, ruby, and so on). These skins allow the look and feel of RichFaces applications to be changed easily. We will discuss this later in the book. For now, it is sufficient to know that we can use either RichFaces or ICEFaces—both of which are competing JSF component toolkits. [ 85 ]
Facelets
When all of the answers have been entered, they are stored within the /seam-gen/seam.properties file. If you open up this file in a text editor and take a look at the contents, you can see all of these answers. The contents of this file are not shown here as you probably won't need to manually edit the file. You may want to open the file and take a quick look at its contents, just for information. After running the setup option, we can tell SeamGen to generate a new Seam project using all of the information we have entered. This is achieved by using the seam create-project command.
[ 86 ]
Chapter 4
The seam create-project command creates the project structure for our application, complete with: • • • • • •
Ant scripts for project build/deploy Embedded JBoss for effective unit testing of web pages and Seam actions Support for database deployment descriptors for both development and production Configuration to use Facelets Internationalization support for the web application Security support for the web application
To build and deploy the application to JBoss, we can execute the ant deploy target.
To run the application, open your browser and type in the following URL: http://localhost:8080/vacationplanner/home.seam
[ 87 ]
Facelets
To complete converting the Vacation Planner application into a Facelets application, we need to do several tasks: •
Add the JSP files and convert them into .xhtml files
•
Add the Seam components (VacationManager.java and others)
•
Configure the pages.xml file
All of these files can be found in the Chapter 4/vacationplanner folder of the download bundle for this chapter. When these tasks have been completed, we can run the application and check out the new functionality. We can test the functionality similar to how we did in the previous chapter.
Seam debug page
One of the benefits of using the entire Seam Framework in this manner is that we have full access to all of the facilities provided by Seam, including the Seam debug page. If an unknown exception is thrown within our application (that is, we have not catered for it within the pages.xml file), then the Seam debug page will be displayed. This page can also be accessed at any time by browsing to http://localhost:8080/vacationplanner/debug.seam . [ 88 ]
Chapter 4
The seam-debug.jar file provides the Seam debug page functionality. Therefore, this type of debug functionality is available only if the JAR file is on the application's classpath. SeamGen automatically adds this JAR file into projects, however, if you are not using SeamGen, you will need to manually add the JAR file into your project's classpath to gain this functionality. The JBoss Seam Debug Page is shown in the following screenshot:
[ 89 ]
Facelets
The Seam debug page shows all of the Seam components that are available within the application, including the components that you have developed, as well as the built-in components developed by the Seam team. The debug page can be very useful when you are trying to track down problems with components that you have developed, or stack traces that are thrown at you. It can be useful during development to turn off nice error handling within pages.xml to try and help track down any error conditions. If we turn off error handling within the Vacation Planner application and throw an exception (for example, if we don't select a vacation type), then the following debug window is displayed, showing full details of the Java stack trace, enabling us to locate and identify the error.
[ 90 ]
Chapter 4
Summary
In this chapter, we looked at the Facelets framework and saw the different advantages that it provides over using JSP, as the view technology in our web applications. We've looked in detail at the template support in Facelets and seen how to change the calculator application that we developed in the previous chapter into a Facelets application. After examining Facelets, we just saw the SeamGen application and saw how this allows us to easily create a skeleton application that supports a range of Seam features, including Facelets support. We also saw the Seam debug page, which can be used to provide details of all of the Seam components in an application, together with error stack traces.
[ 91 ]
Testing Seam Applications One of the most important aspects of application development is testing. In order to ensure that applications work correctly when deployed to your user base, you need to ensure that you have performed full testing. The Seam Framework provides excellent support for testing Seam applications at both component and integration levels.
Overview of Seam application testing
The Seam Framework includes the TestNG (http://www.testng.org) library for performing the testing of Seam components and applications. When used in conjunction with the SeamGen application generator, Seam removes many of the complexities of application testing. This results in tests that are easier to write, to maintain, and are hopefully more prevalent.
TestNG
TestNG is a unit-testing framework for Java that uses Java annotations, allowing complex test libraries to be built up. TestNG test suites can be developed entirely separately from your application's source code, and therefore do not have to be deployed with applications. TestNG test suites are built up from classes that are adorned with different annotations. Let's take a look at a basic test class: package mytests; import org.testng.annotations.* public class ATest { @Test (groups= {"examples"}) public void runATest() { assert getString().equals("some value"); } }
Testing Seam Applications
This simple test class (ATest) defines a test class that has one test method (runATest). The method we wish to run as a test is adorned with the @Test annotation. @Test (groups= {"examples"})
This annotation takes an array of the different groups that the test is defined as belonging to. In this example, the test belongs to the examples group. The groups parameter is optional, but can be used to differentiate different groups of tests so that groups of tests can be run separately, or can be identified for reporting purposes. So, how does TestNG know if a test has succeeded or not? If a test completes without throwing an exception, it is considered to have succeeded. Typically, the assert statement is used, as this throws an AssertionError exception if the expression being asserted evaluates to false. If you are unfamiliar with the assert statement, check out the Sun Microsystems guide Programming with Assertions at: http://java.sun.com/j2se/1.4.2/docs/guide/lang/assert.html
TestNG allows pre and post conditions to be defined within a test class. These can be used for setting an object's state or initializing data before a test is executed. Methods can be configured to run before and/or after a suite, a test, a test group, a class, or a method is executed. To define a method that is run in these conditions, the following annotations are used: •
@AfterSuite / @BeforeSuite
•
@AfterGroups / @BeforeGroups
•
@AfterTest / @BeforeTest
•
@AfterClass / @BeforeClass
•
@AfterMethod / @BeforeMethod
For example, the following setup() method would be executed before the first test method in the class is executed. @BeforeClass public void setUp() { // Setup initialization data. this.intVal=17; }
[ 94 ]
Chapter 5
To run TestNG tests, a control XML file is required, which details different test suite parameters such as the name of tests to be run, whether they should be run in parallel, and so on. A test suite is a collection of related tests. If you are using TestNG then this control file is typically called testng.xml. However, SeamGen-generated projects expect the control files to be called *Test.xml (for example, AllTests.xml or UITests.xml). Typically, this file is placed within the default package for your application, or inside the package that contains the tests. A typical control file to run a set of tests would look like this:
In this control file we can see that a test suite called MySuite has been defined. This test suite does not run tests in parallel—all of the tests are run in sequence. Within this test, there is one test class defined (myPackage.MyTest). If you have a large number of tests, all within the same package, listing out all of the test classes in this way can become cumbersome. TestNG allows a element to be defined instead of the element, which details the packages that are to be executed as tests.
[ 95 ]
Testing Seam Applications
Executing a set of test suites is performed using the ant test target. This is achieved by opening a command prompt within the project directory and running ant test, as shown in the following screenshot. You can see from the output following the command line that the project is built if necessary (the compiletest and buildtest ant targets are executed). The tests are then executed by TestNG and the number of tests run, failed, and skipped are output once all of the tests have been executed.
One of the benefits of using SeamGen to generate application skeletons is that the infrastructure for testing applications is preconfigured for you. You don't need to worry which JAR files go into which directory, and you don't need to build the ant script to execute tests.
TestNG provides many more features than we have discussed so far, and it's outside the scope of this book to describe them all. We suggest that you check out the TestNG web site (http://www.testng.org) for further details.
Component testing
Now that we've had an overview of what TestNG can do, we can add a test to our sample Vacation Planner application. At present, there aren't many components within the application to be tested. However, as we progress throughout the book, we'll keep coming back to the tests, and show how new components that we develop can be tested. [ 96 ]
Chapter 5
To start testing our application we need to have a TestNG control file. We'll create a file called AllTests.xml and place it in the src/test/com/davidsalter/ vacationplanner/actions/test folder of our sample application.
Because all of our tests are in the same package, we can specify the tests by using the element within the AllTests.xml file. For our sample application, the AllTests.xml file is as follows.
We can then write a test for our Destination.java class as follows: package com.davidsalter.vacationplanner.actions.test; import import import import
org.testng.annotations.BeforeClass; org.testng.annotations.Configuration; org.testng.annotations.Test; com.davidsalter.vacationplanner.model.Destination;
public class DestinationTest { Destination destination = null; private static double TOLERANCE = 0.001; @BeforeClass public void setUp() { destination = new Destination(); destination.setMinimumBudget(200.0); [ 97 ]
Testing Seam Applications } @Test (groups= {"component"}) public void testDestination() { assert destination.getMinimumBudget() - 200.0 < TOLERANCE; } }
This class declares one test and adds it to the component group. We can see that the @BeforeClass annotation is used on the setUp() method to instantiate an instance of the Destination class and perform some initialization. The testDestination() method is annotated with the @Test annotation, indicating that this is the method that runs to perform the test.
Testing the user interface
Unit testing allows us to perform discrete tests on classes and groups of classes to see how they all work in isolation from the application as a whole. Unit testing provides an excellent way to test our classes in isolation, but doesn't allow us to test any interaction between classes, components, or user interfaces. It is useful, therefore, to be able to run integration tests on components and on the user interface, showing the interaction of the classes when events such as buttons on forms are clicked. Traditionally, this type of testing is performed within the container. To perform this testing, the application needs to be built, deployed, and then tested. However, Seam makes this process easy by allowing us to perform integration tests of this type, outside of the container. Seam is supplied with an embedded version of JBoss, which can be found within the bootstrap directory of SeamGen generated projects. When you run integration tests with Seam, the tests are deployed to the embedded version of JBoss and are run outside of the container, although, the tests still use the TestNG framework. This provides a much quicker turnaround, and can easily be integrated into your IDE as all of the major IDEs support TestNG testing. By now, you should be able to see the benefits of a SeamGen generated project. I would recommend that you always use SeamGen to create Seam projects as doing so can save you an awful lot of time and effort.
To create an integration test to validate user interface interactions, we need to derive our test class from the org.jboss.seam.mock.SeamTest class. Within this class, we create an anonymous class that extends org.jboss.seam.mock.FacesRequest, to allow us to emulate different phases of the JSF life cycle (we discussed the JSF lifecycle in the previous chapter). The FacesRequest class provides several methods that we can override in order to test different stages within the JSF life cycle of our user interface. [ 98 ]
Chapter 5
Method protected void applyRequestValues()
Description
protected void invokeApplication()
This method is called during the invoke application phase of the JSF life cycle.
protected void processValidations()
This method is called during the process validations phase of the JSF life cycle.
protected void renderResponse()
This method is called during the render response phase of the JSF life cycle.
This method is called during the apply request value phase of the JSF life cycle.
Therefore, the basic structure of a class that can test user interface interactions is as follows: import org.jboss.seam.mock.SeamTest; import org.testng.annotations.Test; public class MyTest extends SeamTest { @Test(groups={"ui"}) public void testMe() throws Exception { new FacesRequest() { @Override protected void processValidations() throws Exception { // Perform Tests } @Override protected void updateModelValues() throws Exception { // Perform Tests } @Override protected void invokeApplication() throws Exception { // Perform Tests } @Override protected void renderResponse() throws Exception { // Perform Tests } }.run(); } [ 99 ]
Testing Seam Applications
Within these methods of the FacesRequest anonymous class, we can get, set, and validate Seam component properties and invoke Seam component methods. Method protected Object getValue(…)
Description
protected void setValue(…) protected Boolean validateValue(…)
Sets the value of a JSF expression.
protected Object invokeMethod(…)
Invokes a JSF method binding such as {vacationManagerAction. selectVacationType}.
Gets a value from a JSF expression such as #{destination.minimumBudget}.
Validates an expression based upon the JSF validations present.
If we put all of this together, we can create user interface tests that allow us to emulate user input, follow the JSF life cycle, and then verify the correctness of any output parameters of our page flow. We can write a test for our sample Vacation Planner application to test the interaction that is performed when we select a vacation type. package com.davidsalter.vacationplanner.actions.test; import org.jboss.seam.mock.SeamTest; import org.testng.annotations.Test; import com.davidsalter.vacationplanner.model.Destination; public class VacationTest extends SeamTest { @Test(groups={"ui"}) public void testCityVacation() throws Exception { new FacesRequest() { @Override protected void processValidations() throws Exception { validateValue("#{destination.destinationType}", Destination.DestinationType.CITY); validateValue("#{destination.minimumBudget}", 200.0); assert !isValidationFailure(); } @Override protected void updateModelValues() throws Exception { setValue("#{destination.destinationType}", Destination.DestinationType.CITY); setValue("#{destination.minimumBudget}", 200.0); } @Override protected void invokeApplication() { assert invokeMethod("#{vacationManagerAction. [ 100 ]
Chapter 5 selectVacationType}").equals("/city.xhtml"); } @Override protected void renderResponse() { assert getValue("#{destination.minimumBudget}"). toString().equals("200.0"); } }.run(); }
In this code, we can see that we have declared a user interface integration test and added it to our user interface group of tests (denoted by the group name ui). We've then overridden each of the four different phases of the JSF life cycle (process validations, update model, invoke application, and render response) and performed validation at each stage.
Seam component testing
Sometimes, we may want to test the interaction of Seam components, but don't want to emulate the JSF life cycle. Seam allows us to do that, again by extending our test class from org.jboss.seam.mock.SeamTest, but this time creating an instance of an anonymous org.jboss.seam.mock.ComponentTest class. Within this class, we can still use the getValue(), setValue(), and invokeMethod() methods that we used in the FacesRequest class. However, we must also declare the testComponents()method into which we can put code to test the interaction of components. protected abstract void testComponents()
A typical Seam component test would look similar to the following: import org.jboss.seam.mock.SeamTest; import org.testng.annotations.Test; public class MyTest extends SeamTest { @Test(groups={"component"}) public void testMe() throws Exception { new ComponentTest() { @Override protected void testComponents() throws Exception { // Perform tests. } }.run(); }
[ 101 ]
Testing Seam Applications
To test our VacationPlanner application—specifically how the VacationManagerAction class works at a component level—we could write a test that follows the preceding structure by defining a new ComponentTest. package com.davidsalter.vacationplanner.actions.test; import org.jboss.seam.mock.SeamTest; import org.testng.annotations.Test; import com.davidsalter.vacationplanner.model.Destination; public class VacationManagerActionTest extends SeamTest { @Test public void testSelectVacationType() throws Exception { new ComponentTest() { @Override protected void testComponents() throws Exception { setValue("#{destination.destinationType}", Destination.DestinationType.ACTION); assert invokeMethod("#{vacationManagerAction. selectVacationType}").equals("danger"); } }.run(); } }
In this code, we have written a test, testSelectVacationType, which sets up the destination type and then invokes the selectVacation method. The test finally asserts that the correct result is obtained, by executing the selectVacationType method.
Mocking Seam components
Sometimes, when we are performing components testing, we may not want to test the full interaction between components and may want to use a mock implementation for specific components. A mock object is the one that provides the same API as the object that it is mocking, but does not necessarily include the full functionality of the object. A mock object will have a well-defined set of input and output data, which makes it ideal to test against. Mock objects are useful when it is difficult to simulate a real object in a unit test. This may be, for example, a database operation or invoking external functionality. For a database operation, we may not have access to a full production database to do the testing against. So we may want to make a mock datasource that we can perform queries against. Again, when we are invoking external functionality, this may not be fully available within our test environment. So we may want, to provide mock implementations of our external services. [ 102 ]
Chapter 5
Seam provides an easy way to allow mock components to be written. In Seam, we can define a component as a mock component by setting its install precedence to be higher than that of the standard component. When Seam looks up a component to inject or outject into another Seam component, it will find the component with the highest precedence and use that. When we define a component in Seam, it is typically created with the APPLICATION precedence (this is the third-lowest component precedence—just above that of the Seam Framework components). If we create a component with the same name as an already-existing component within the Seam Framework, the default operation will use the one provided by us, the developer, rather than the one provided by the framework. A precedence can be defined for each Seam component, and when Seam instantiates a component, it always instantiates the one with the highest precedence. Therefore, we can see that if we wish to mock a component, we need to provide it with the highest precedence, and Seam will ensure that the mock implementation is used. Within Seam, the following component precedences are defined as follows (ranging from the lowest precedence to the highest precedence) • • • • •
Built in (Install.BUILT_IN) Framework (Install.FRAMEWORK) Application (Install.APPLICATION) Deployment (Install.DEPLOYMENT) Mock (Install.MOCK)
These precedences are defined within the Seam org.jboss.seam.annotations. Install interface. To set the precedence of a component to Install.MOCK, and
therefore define a Seam component as a mock component, we need to annotate the component with the @Install(precedence=Install.MOCK) annotation. A simple definition of a mock for our vacationManagerAction could therefore be developed, as shown in the following code. package com.davidsalter.vacationplanner.actions; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Name; @Name("vacationManagerAction") @Install(precedence=Install.MOCK) public class MockVacationManagerAction { public String selectVacationType() throws Exception { return "danger"; } } [ 103 ]
Testing Seam Applications
From this simple class, we can note several things. •
The class has been defined within the same package as the "full" vacationManagerAction
•
The class has been defined with the same @Name
•
The precedence of the class has been defined as the highest that is allowed by Seam, by using the @Install(precedence=Install.MOCK) annotation
•
The code has a well-defined (albeit rather simplified) set of outputs
Summary
In this chapter, we've looked at TestNG, and seen how it can be used to build sets of test suites. We saw that TestNG provides annotations that can be applied to Java classes in order to turn them into test classes. After introducing TestNG, we looked at how it can be applied to our sample application to test any POJO classes that we may have developed, effectively using TestNG as a component-testing framework. Next, we looked at how the Seam testing framework leverages TestNG to provide testing facilities for JSF pages. We saw how it is possible to test JSF pages outside of the application server, enabling integration tests to be run in a simple straightforward manner that is not immediately available for other web frameworks. Finally, we saw how it is possible to test Seam components directly, by using a similar mechanism to how we performed user interface testing, creating mock components where necessary. We've seen in this chapter how Seam provides excellent facilities for allowing us to perform testing of our applications, at the class, Seam component, and user interface levels.
[ 104 ]
RichFaces RichFaces is a JSF-based framework of GUI components that can easily be integrated into web applications, allowing rich user interfaces to be built up easily. RichFaces provides AJAX support for these components without the need for the developer to write JavaScript. RichFaces also provides skinning support, allowing the look and feel of RichFaces components to be quickly changed via a single configuration setting. All of this functionality is, of course, provided in a cross platform manner and works with Internet Explorer, Firefox, Safari, and Opera.
Obtaining RichFaces
RichFaces comes bundled with each Seam release, so if you are developing Seam applications and wish to use RichFaces, there are no additional downloads needed. Sometimes, however, RichFaces is updated outside of the release schedule for Seam. So newer versions of RichFaces may be available, other than those supplied with Seam. In such a case, RichFaces can be downloaded from http://www.jboss.org/ jbossrichfaces/downloads/. It's best to take care and ensure that full testing is performed when downloading and using a different version of RichFaces from that supplied with the version of Seam.
Configuring an application for RichFaces Configuring an application to use RichFaces consists of two steps: 1. Adding JAR files to the web application. 2. Configuring the application's XML resources.
RichFaces
Add JAR files to the web application
RichFaces is implemented as three JAR files, namely richfaces-api.jar, richfaces-impl.jar, and richfaces-ui.jar. Different versions of RichFaces may have the version number embedded in the file name (for example, richfaces-api-3.2.1.jar). These JAR files are to be added to an application's WEB-INF\lib folder, in order to make use of RichFaces. In an application generated by SeamGen, this is done for you automatically when you create a new project.
Configuring the application's XML resources
In addition to ensuring that the RichFaces JAR files are on your web application's classpath, the application's web.xml file needs to be configured. Within a Seam application, the only change that needs to be done to the web.xml file, is to specify which application skin is required. When generating a Seam application with SeamGen, the generator asks which skin is to be used for the application. The default, Blue Sky, uses the following settings within web.xml: org.richfaces.SKIN blueSky
Application skins allow the look and feel of the application to be quickly changed at development time by modifying the org.richfaces.SKIN parameter within the web.xml file. Changes to this parameter cause RichFaces to use different CSS values, to change colors, fonts, and so on. RichFaces is supplied with the following eight different skins. To use any of these skins, simply set the org.richfaces.SKIN parameter value to the name of the skin required. •
DEFAULT
•
Plain
•
emeraldTown
•
blueSky
•
wine
•
japanCherry
•
ruby
•
classic
•
deepMarine [ 106 ]
Chapter 6
Within RichFaces, all of these skins are defined within the files of the /skin. properties within the META-INF/skin folder of the richfaces-impl.jar file. It is
therefore important that the correct capitalization of skin names is used—that is, the skin names are case-sensitive. It's possible to create custom skins easily using RichFaces if you want to tailor your application differently from the nine available skins. Developing skins involves creating .properties files that define the CSS properties for RichFaces components. Further details on how to create different skins can be found on the RichFaces web site: http://www.jboss.org/file-access/ default/members/jbossrichfaces/freezone/docs/devguide/ en/html_single/index.html#Skinnability.
To use RichFaces components within a Facelets page, we need to define the namespace for the RichFaces components. This is achieved by specifying the namespace xmlns:rich=http://richfaces.org/rich within the or tags of the Facelets file, as shown in the following code snippet.
RichFaces controls
RichFaces contains over 70 different components. These components allow many different types of rich user interfaces to be built, using calendars, data tables, selectors, drag–and–drop, and so on. A full list of RichFaces components can be found at http://www.jboss.org/file-
access/default/members/jbossrichfaces/freezone/docs/tlddoc/index.html. Full details of all of these RichFaces components can be found on the JBoss web site: http://www.jboss.org/file-access/default/ members/jbossrichfaces/freezone/docs/devguide/ en/html_single/index.html
We'll now cover some of the more common controls in depth. [ 107 ]
RichFaces
The control allows pop-up calendars to be displayed within web pages, allowing users to easily select dates. The date selected in the calendar can be passed back to a member of a Seam component, allowing full integration between the web site view technology and the server-side components. To add a control to a web page, the following type of code should be added to the Facelets page.
This code will add an edit box with a calendar button to its right, as shown in the following image:
In this instance, the value of the calendar will be bound to the variable, theDate, within a Seam component called demo. In its simplest form, this Seam component would be written as: package com.davidsalter.richfaces.entity; import java.io.Serializable; import java.util.Date; import org.jboss.seam.annotations.Name; @Name("demo") public class Demo implements Serializable { private Date theDate; // Getters and setters omitted for brevity. }
The download bundle for Chapter 6 of this book contains the sample RichFaces demonstration application. The screenshots and code samples are taken from this application.
[ 108 ]
Chapter 6
When the calendar button to the right of the calendar edit box is clicked, a pop-up calendar is displayed, as shown in the following screenshot. The calendar shows the current date and allows the user to select dates, change the month and year, and so on—all of the standard functionality you would expect a calendar to support.
JavaScript methods
The component also supports different JavaScript hooks allowing JavaScript to be executed when events occur within the panel. The most common of these hooks are listed in the following table. Hook
Description
onChanged
Invoked when the calendar date is changed
onCollapse
Invoked when the pop-up calendar collapses
onCurrentDateSelected
Invoked when the current date is selected
onDateMouseOut
Invoked when the mouse leaves the date selector
onDateMouseOver
Invoked when the mouse is over the date selected
onDateSelect
Invoked when a date is selected
onExpand
Invoked when the calendar is expanded
onInputKeyDown
Invoked when a key is pressed down
onInputKeyPress
Invoked when a key is pressed down and released
onInputKeyUp
Invoked when a key is released
[ 109 ]
RichFaces
Within applications, it can be useful to create individual panels, each of which can hold different pieces of information. For example, a web application could hold pictures within one panel, descriptions within another panel, and then navigation buttons within a third panel. RichFaces allows panels to be created by using the tag.
The tag can be created in its simplest form by creating a element. The header attribute allows the header text for the panel to be defined. Some text inside the outer panel. Some text inside the inner panel.
The component supports the styleClass, headerClass, and bodyClass attributes, allowing the look and feel of the component to be changed via CSS.
JavaScript methods
The component also supports different JavaScript hooks, allowing JavaScript to be executed when events occur within the panel. Hook
Description
onClick
Invoked when the panel is clicked with the mouse
onDblClick
Invoked when the panel is double-clicked with the mouse
onKeyDown
Invoked when a key is pressed down
onKeyPress
Invoked when a key is pressed and released
onKeyUp
Invoked when a key is released
onMouseDown
Invoked when the mouse button is pressed
onMouseMove
Invoked when the mouse is moved
onMouseUp
Invoked when the mouse button is released [ 110 ]
Chapter 6
The tag allows modal panels to be displayed within a web page. A modal panel takes the focus of the web application, causing all other regions of the web page to be disabled until the modal panel is closed. In the following screenshot, a modal panel is displayed showing the copyright information for an application. Until the Close link is clicked, the rest of the web page is disabled.
A modal panel can contain any content that is allowed on a standard web page (text, images, links, and so on). With the , the user can grab the title bar of the panel and drag the modal panel around the screen. Again, while this is being done, the panel retains its modal status, and the rest of the web page is not accessible. The has a JSF facet named header. A JSF facet is a section of a control that can be defined separately from the control itself. In this case, the header facet allows the header of the modal panel to be defined.
[ 111 ]
RichFaces
To invoke and close the components, RichFaces provides the following JavaScript show() and hide() functions: •
#{rich:component('panelId')}.show()
•
#{rich:component('panelId')}.hide()
Both of these methods take the Id of the modal panel to show or hide as a parameter. These methods can be used in the following manner: Click To Open Click to Close
The following code shows how the modal panel, shown in the previous screenshot, was created. In this code fragment, we first create a RichFaces panel () to provide a visual panel on the page. Inside this, we add a link, About this application, which, when clicked, will cause the modal panel called aboutPanel to be displayed, Next, we define the modal panel () by adding some simple code to display a message. We can add whatever markup we like to the modal panel—text, images, or a user input form. Finally, within the modal panel, we add a link, Close, which, when clicked, will close the form. About this application
Blah, blah, I wrote this !!
Copyright (c) Me 2008
etc..
Close
JavaScript methods
The component also supports different JavaScript hooks, allowing JavaScript to be executed when events occur, both within and outside the panel. The most common of these hooks are listed in the following table: [ 112 ]
Chapter 6
Hook
Description
onBeforeHide
Invoked before the panel is hidden
onBeforeShow
Invoked before the panel is shown
onHide
Invoked after the panel is closed
onMaskClick
Invoked when the mouse is clicked outside the panel
onMaskContextClick
Invoked when the mouse is right-clicked outside the pane
onMaskDblClick
Invoked when the mouse is double-clicked outside the panel
onMaskMouseDown
Invoked when the mouse button is pressed outside the pane
onMaskMouseMove
Invoked when the mouse is moved outside the panel
onMaskMouseOut
Invoked when the mouse is moved outside of the panel
onMaskMouseOver
Invoked when the mouse is moved inside the panel
onMaskMouseUp
Invoked when the mouse button is released outside the panel
onMove
Invoked before the panel is moved
onResize
Invoked when the panel is resizing
onShow
Invoked after the panel is opened
The component allows a panel whose body can be toggled on and off by clicking on the >> or