Tapestry in Action



字数:0 关键词: Tapestry Web框架

Tapestry in Action Licensed to Rodrigo Urubatan Ferreira Jardim rurubatan@terra.com.br Tapestry in Action HOWARD M. LEWIS SHIP MANNING Greenwich (74° w. long.) Licensed to Rodrigo Urubatan Ferreira Jardim For online information and ordering of this and other Manning books, go to www.manning.com. The publisher offers discounts on this book when ordered in quantity. For more information, please contact: Special Sales Department Manning Publications Co. 209 Bruce Park Avenue Fax: (203) 661-9018 Greenwich, CT 06830 email: orders@manning.com ©2004 by Manning Publications Co. All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by means electronic, mechanical, photocopying, or otherwise, without prior written permission of the publisher. Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in the book, and Manning Publications was aware of a trademark claim, the designations have been printed in initial caps or all caps. Recognizing the importance of preserving what has been written, it is Manning’s policy to have the books they publish printed on acid-free paper, and we exert our best efforts to that end. Manning Publications Co. Copyeditor: Liz Welch 209 Bruce Park Avenue Typesetter: Denis Dalinnik Greenwich, CT 06830 Cover designer: Leslie Haimes ISBN 1-932394-11-7 Printed in the United States of America 12345678910–VHG–08 07 06 05 04 Licensed to Rodrigo Urubatan Ferreira Jardim To my parents, who bought me my first computer when I was 13 and had no idea what they had started Licensed to Rodrigo Urubatan Ferreira Jardim Licensed to Rodrigo Urubatan Ferreira Jardim vii PART 1USING BASIC TAPESTRY COMPONENTS ...........................1 1 ■ Introducing Tapestry 3 2 ■ Getting started with Tapestry 38 3 ■ Tapestry and HTML forms 92 4 ■ Advanced form components 133 5 ■ Form input validation 169 PART 2CREATING TAPESTRY COMPONENTS............................213 6 ■ Creating reusable components 215 7 ■ Tapestry under the hood 269 8 ■ Advanced techniques 322 PART 3BUILDING COMPLETE TAPESTRY APPLICATIONS........381 9 ■ Putting it all together 383 10 ■ Implementing a Tapestry application 403 brief contents Licensed to Rodrigo Urubatan Ferreira Jardim viii BRIEF CONTENTS APPENDIXES A ■ Getting involved with Tapestry 479 B ■ Building the examples with Ant 485 C ■ Tapestry component reference 493 D ■ Tapestry specifications 516 Licensed to Rodrigo Urubatan Ferreira Jardim ix foreword xvii preface xix acknowledgments xxi about this book xxiii about the title xxvii about the cover illustration xxviii PART 1 USING BASIC TAPESTRY COMPONENTS ......................1 1 Introducing Tapestry 3 1.1 What are web applications? 5 1.2 What are Java servlets? 7 Understanding servlet multithreading 9 ■ Managing server-side state 10 ■ Using Struts with servlets 12 ■ Understanding the limitations of servlets 12 1.3 Why do we need Tapestry? 16 What is a framework? 16 ■ What is a component? 17 What is Tapestry? 18 ■ Comparing Tapestry to Swing 21 1.4 Understanding Tapestry’s goals 22 Simplicity 22 ■ Consistency 22 ■ Efficiency 23 Feedback 23 contents Licensed to Rodrigo Urubatan Ferreira Jardim x CONTENTS 1.5 How does Tapestry work? 25 What’s in a Tapestry application? 27 ■ Tapestry’s Model- View-Controller (MVC) pattern 29 ■ Tapestry classes 33 1.6 Using Spindle 35 1.7 Summary 37 2 Getting started with Tapestry 38 2.1 Introducing the Hangman application 39 Determining the application flow 41 ■ Creating page mockups 42 ■ Defining the domain objects 44 Defining the pages 49 2.2 Developing the Home page 51 Understanding the Home page specification 52 ■ Rendering the Home page 54 ■ Defining the Home page class 57 Examining the Visit object 60 2.3 Implementing the Home page using standard servlets 62 2.4 Developing the Guess page 65 Displaying the remaining guesses 73 ■ Generating the guessed word display 79 ■ Selecting guesses 83 2.5 Developing the Win and Lose pages 89 2.6 Configuring the web.xml deployment descriptor 89 2.7 Summary 91 3 Tapestry and HTML forms 92 3.1 Understanding HTML forms 93 3.2 Creating a simple login form 96 Implementing the Login page class 99 ■ Using specified properties 101 3.3 Understanding the Form component 103 Developing forms without Tapestry 103 ■ Developing forms with Tapestry 105 ■ Handling form submissions 108 3.4 Using basic form control components 109 Understanding the essentials 110 ■ The Checkbox component 111 ■ Radio and RadioGroup components 111 Licensed to Rodrigo Urubatan Ferreira Jardim CONTENTS xi Select and Option components 113 ■ Submit and ImageSubmit components 118 3.5 Creating a to-do list 120 Defining the data object 121 ■ Creating the ToDo HTML template 122 ■ Specifying properties in the page specification 125 ■ Initializing the toDoList property 126 Handling reordering 127 ■ Deleting completed items 128 Triggering stale links 129 3.6 Summary 132 4 Advanced form components 133 4.1 Introducing the advanced form components 134 4.2 Creating drop-down lists with PropertySelection 134 Adding priority levels to the ToDo application 136 ■ Updating the HTML template 138 ■ Implementing the page class 139 Implementing the model 140 ■ Using enums instead of integers 144 4.3 Recording data in the form with Hidden 148 4.4 Looping within a form using ListEdit 151 Using the ListEditMap 153 ■ Using ListEdit in the ToDo application 154 4.5 Handling file uploads 160 4.6 Creating pop-up date selections using DatePicker 165 4.7 Summary 168 5 Form input validation 169 5.1 Validating user input 170 Using FieldLabels in conjunction with ValidFields 173 Using validators 173 ■ Using validation delegates 174 Using helper beans 178 5.2 Building the Register page 179 Creating the Register HTML template 181 ■ Creating the Register page specification 189 5.3 Validating input based on regular expressions 195 5.4 Customizing label and field decorations 199 5.5 Enabling client-side validation 202 Licensed to Rodrigo Urubatan Ferreira Jardim xii CONTENTS 5.6 Handling form-level validations 205 5.7 Using validation without ValidField 208 5.8 Summary 211 PART 2 CREATING TAPESTRY COMPONENTS ..................... 213 6 Creating reusable components 215 6.1 Creating simple template components 216 6.2 Creating the component specification 219 Specifying the component’s Java class 219 ■ Discarding the component’s body 220 ■ Forbidding informal parameters 221 Declaring parameters 222 ■ Reserving names 230 6.3 Creating the Digit and Scaffold components 230 Specifying the digit parameter 232 ■ Using the digit parameter 232 ■ Creating the template 233 ■ Using the Digit component 233 ■ Using the Scaffold component 234 6.4 Creating the Letter component 234 Specifying the Letter component 235 ■ Implementing the Letter component 236 ■ Using the Letter component 237 6.5 Building the Spell component 238 Implementing the Spell component 239 ■ Using the Spell component 240 6.6 Building the Border component 242 Creating the Border template 243 ■ Creating the Border specification 244 ■ Using the Border component 244 6.7 Creating interactive, reusable components 246 Introducing the Pet Store image map 246 ■ Specifying the DirectArea component 247 ■ Implementing the DirectArea component 248 ■ Using the DirectArea component 252 6.8 Using component libraries 254 Declaring libraries 254 ■ Referencing library components 256 6.9 Packaging components into libraries 261 Creating the library specification 262 ■ Creating the library specification 264 ■ Creating the FormError component 264 Using the FormError component 267 6.10 Summary 268 Licensed to Rodrigo Urubatan Ferreira Jardim CONTENTS xiii 7 Tapestry under the hood 269 7.1 Processing requests 270 7.2 Understanding the application servlet 271 Servlet request processing 271 ■ Understanding server-side state 272 ■ Managing server-side state in a cluster 274 7.3 Understanding the Tapestry engine 277 7.4 Understanding engine services 279 What’s the problem with application URLs? 279 ■ How does Tapestry handle application operations? 280 ■ Using the home service 281 ■ Rendering pages with the page service 283 Linking to listener methods with the direct service 284 Creating bookmarkable links using the external service 290 7.5 Page rendering in detail 291 Using markup writers 292 ■ Going beyond HTML 295 Understanding the page-rendering sequence 297 Using page-rendering events 297 7.6 Loading and pooling pages 299 Retrieving pages from the pool 300 ■ Creating new page instances 302 ■ Returning pages to the pool 304 7.7 Using persistent page properties 306 7.8 Using specified properties 308 7.9 Localizing Tapestry applications 309 Using Java localization 310 ■ Using Tapestry’s localization features 311 7.10 Summary 321 8 Advanced techniques 322 8.1 Creating new engine services 323 Defining a banner ad system 324 ■ Defining the data model 326 ■ Accessing the data model as an application extension 328 ■ Implementing the BannerAd component 329 Implementing the banner service 332 ■ Creating the library specification 336 ■ Building a banner ad application 337 8.2 Client-side scripting 339 Defining the CreditCardField component 341 ■ Working with the Body component 344 ■ Creating the Tapestry script Licensed to Rodrigo Urubatan Ferreira Jardim xiv CONTENTS specification 345 ■ Creating the CreditCardField specification 355 ■ Creating the CreditCardField component 356 ■ Using the component 368 8.3 Integrating with JavaServer Pages 369 Redirecting to a JSP 369 ■ Linking JSPs to Tapestry pages 371 ■ Submitting JSP forms into Tapestry 375 8.4 Summary 379 PART 3 BUILDING COMPLETE TAPESTRY APPLICATIONS...... 381 9 Putting it all together 383 9.1 Introducing the Virtual Library 384 9.2 Performing searches 385 Changing the table sort order 387 ■ Paging through the results 387 9.3 Logging in and registering 388 9.4 Borrowing books 389 9.5 Getting details about books and persons 390 Viewing book details 390 ■ Viewing a person 392 9.6 Managing your books 393 Editing a book 394 ■ Deleting a book 395 ■ Returning books 395 ■ Adding a new book 396 ■ Editing your profile 396 ■ Giving away books 396 9.7 Administering the Virtual Library 399 Editing users 399 ■ Editing publishers 400 ■ Transferring books 400 9.8 Summary 402 10 Implementing a Tapestry application 403 10.1 Looking at the application layers 404 10.2 Organizing EJB access 406 Handling authentication 407 ■ Accessing Enterprise JavaBeans 407 ■ Tracking user identity with the Visit object 407 ■ Understanding page inheritance 410 Licensed to Rodrigo Urubatan Ferreira Jardim CONTENTS xv 10.3 Implementing the Search page 413 Identifying application-specific components 415 ■ Referencing the engine 416 ■ Specifying the page class and properties 417 Performing searches 418 10.4 Implementing the BookMatches page 419 Handling paging and column sorting 420 ■ Using the Browser component 425 ■ Executing queries and re-queries 426 10.5 Implementing the Browser component 430 Specifying Browser’s parameters 430 ■ Getting results from the BookQuery bean 432 ■ Rendering the Browser component 433 10.6 Implementing the ColumnSorter component 434 Creating the ColumnSorter HTML template 434 ■ Specifying ColumnSorter parameters 435 ■ Responding to the user 437 10.7 Implementing the Border component 438 Handling user login 442 ■ Linking to MyLibrary 443 10.8 Authenticating the user 445 Remembering the user 446 ■ Clearing the password field 447 Invoking the login operation 449 10.9 Creating bookmarkable links 449 Creating the BookLink component 450 ■ Displaying the Book on the ViewBook page 451 ■ Creating the PersonLink component 453 ■ Displaying the Person 455 10.10 Editing a Book 457 Tracking the Book ID 459 ■ Generating dynamic JavaScript 460 10.11 Giving books away 463 10.12 Editing the publishers 464 Constructing the EditPublishers template 464 ■ Declaring properties for the EditPublishers page 465 ■ Creating the ListEditMap 467 ■ Updating the publishers 469 10.13 Editing the list of users 470 Creating the ListEditMap subclass 471 ■ Handling the form submission 472 Licensed to Rodrigo Urubatan Ferreira Jardim xvi CONTENTS 10.14 Creating the web deployment descriptor 474 Deploying web applications as root 476 ■ Deploying an enterprise application as root 476 10.15 Wrapping it all up 477 appendix A: Getting involved with Tapestry 479 appendix B: Building the examples with Ant 485 appendix C: Tapestry component reference 493 appendix D: Tapestry specifications 516 index 537 Licensed to Rodrigo Urubatan Ferreira Jardim xvii foreword My involvement with Tapestry began in the autumn of 2001. I read about the framework in an article in ONJava magazine. At the same time, our company was poised to begin several new web projects, and we were looking for a way to avoid the problems inherent in building complex web pages and forms with the standard tools. We analyzed a large number of frameworks, but Tapestry immediately attracted our attention, with its unique development method and its helpful community. The first glance was not misleading—Tapestry proved to be a powerful and helpful instrument in practice as well. The component structure was not sim- ply an add-on but was entrenched in the philosophy of the framework. We also discovered that Tapestry offered a number of other powerful features that proved to be critical in our work. For example, it allowed a clean separation between Java and HTML, and made it possible for the design work on the application to continue well after the code had been completed—and it could be performed by designers who never had to know anything about Tapestry. It provided internationalization capabilities well beyond simply replacing text with its translation. The framework was designed with EJBs and clustering in mind, and integrated with them effortlessly. Today our company has libraries containing hundreds of Tapestry compo- nents. Some of these are simple, such as a Login component that manages authentication using HTTP cookies. Others are far more complex, constructed Licensed to Rodrigo Urubatan Ferreira Jardim xviii FOREWORD from smaller components and full of intricate JavaScript, such as a tool for dynamically defining web forms. All of these components can simply be taken off the shelf and plugged into our latest application with ease. We do not have to worry how the various components will work together—we know that they will do so by design. During the two years we have worked with Tapestry, its development has not stood still. While the earlier versions of the framework concentrated on deliver- ing power, version 3.0 (described in this book) concentrates on both delivering maximum ease of use for programmers and enhancing their productivity. It low- ers the entry requirements, decreases the amount of developer effort needed, and makes the framework as easy to use as a scripting language for simple appli- cations. The result is that you can achieve the same results as before but with much less coding. The majority of these improvements have occurred and moved forward due to feedback and ideas from Tapestry users and contributors. The active commu- nity surrounding the framework helps it remain focused on resolving the prob- lems developers encounter in the real world. A major commendation for encouraging and integrating the contributions must also go to Howard Lewis Ship, the author of this book. As the creator and original designer of Tapestry, he has made a remarkable effort to listen to users, understand their needs, and address their requirements. The other Tapestry contributors have followed his lead and have extended the framework to provide support for a variety of new functionality areas. The community has also been instrumental in the process that made Tapestry a part of the Apache Jakarta family. With this step, it became a companion project of other popular projects, including Struts, Tomcat, and log4J. All of these factors ensure that Tapestry will persist in its evolution and will continue to improve the ways in which it makes the life of web developers easier and more productive. This book will allow you to delve into a different, better world of web develop- ment than the one you have known. It will show you an innovative approach for creating and organizing your applications, enable you to develop more robust and scalable code, and make previously difficult tasks much simpler. After you get to know Tapestry, you will start looking at web development in a very differ- ent way. Enjoy! THEODORE TONCHEV “MINDBRIDGE” TECHNICAL DIRECTOR, RUSHMORE DIGITAL Licensed to Rodrigo Urubatan Ferreira Jardim xix preface From the fall of 1998 through late 1999, I worked on a mix of interesting projects for my then-employer, Primix, a Boston Internet consulting firm. The first of these was a standard CRUD (Create Read Update Delete) application, and it used servlets and JavaServer Pages (JSPs). Despite the fact that we were a small team (just three or four of us at various times) working on a very mod- est application, it seemed like we were constantly having to reinvent the wheel. I’d had some exposure to Apple’s WebObjects framework, and the difference between the two approaches was like night and day: Not only did WebObjects have better tool integration, including a WYSIWYG ( W h a t Yo u S e e I s W h a t Yo u Get) HTML editor, it was easier to use even without the extra tools, just by edit- ing the underlying template and configuration files. We were a small, smart, experienced team—but working with servlets and JSPs was always an uphill battle compared to using WebObjects. A major part of this problem was impedance; each developer worked in his or her own corner of the application and had a particular way of working. Dif- ferent developers used different naming conventions and solved similar prob- lems in different ways—which caused extra grief when new developers cycled through the team, or when we had to venture outside our little fiefdoms to fix bugs. Remember, this was a time before J2EE servers really existed, before cus- tom JSP tag libraries, before Sun had even started promoting the Model 2 design pattern. It was also obvious that WebObjects, with its notion of components and Licensed to Rodrigo Urubatan Ferreira Jardim xx PREFACE all the magic it did to hide URLs from the developer, was a far superior option. At the same time, steep licensing fees and lukewarm support and marketing by Apple made pitching a WebObjects solution to clients a nonstarter. We soldiered on with servlets and JSPs. Near the end of 1999 I was left with engagements in the pipeline but no short-term responsibilities. What was to eventually become Tapestry started as a kind of mental doodle: How does WebObjects do it? I started drawing diagrams and sketches, puzzling out a way to gracefully connect stateless HTTP requests to stateful Java objects via Java servlets, and eventually decided to code up a little prototype to see if my ideas would work. Somewhere along the line, I came up with the name: Tapestry. Tapestry quickly took on a life of its own; as primitive as that early code base looks compared to the Tapestry of today, it was still a solid foundation, something that would start to address the issues Primix had with servlet and JSP develop- ment. Primix was in the business of creating high-quality web sites on a regular basis, and Tapestry was a platform that would support the solid software engi- neering principles necessary to achieve its goals. Tapestry set up shop at Source- Forge (http://sf.net) and was released under the Lesser GNU Public License. Along the way, I realized something profound: I could make a difference. Inter- esting software didn’t have to come forth directly from the Suns, IBMs, and Microsofts, or even from the Apaches; it could be homegrown, like Tapestry. With Tapestry, I really thought I could change the world…or at least, the little niche of the world that writes web applications in Java. As fun as working on the code has been, the most fascinating part of Tapestry has been the creation of a real, online community around Tapestry. These are people for the most part I have yet to meet, who have recognized Tapestry as a Good Thing and who work as hard as I do to improve and promote it. It was on the strength of this community that Tapestry relocated from SourceForge to Apache’s Jakarta project, alongside established projects such as Struts, Turbine, and Velocity. Crafting a book about Tapestry has been more difficult and more intense an experience than anything else related to the project, but ultimately the result is worth the effort. What you are holding in your hands may open up a whole new approach to web application development for you and (I hope) will change your world for the better. Licensed to Rodrigo Urubatan Ferreira Jardim xxi acknowledgments Working on this book has been the highlight of my career to date. I’ve never before had the opportunity to be so focused and so self-directed for such a long time. I would never have had this opportunity without the support and friend- ship of Timothy Dion, the CTO of Primix, who gave me the green light to work on Tapestry in the first place, as well as the support to release it as open source. Just as instrumental was my good friend Gregory Burd, who championed Tap- estry within Primix and put it into play for its first big engagement. The Tapestry crew—Theodore Tonchev, Richard Lewis-Shell, David Solis, Neil Swinton, Harish Krishnaswamy, and Geoff Longman—have been tireless supporters of Tapestry, and good friends, many of whom I’ve yet to meet in person! Thanks as well to Andrew C. Oliver for sponsoring Tapestry in the Apache Jakarta project. Writing a book is a group effort—without the tireless help from the crew at Manning, this book would never have reached your hands. Both Marilyn Smith and Liz Welch provided endless assistance with grammar and style, as well as much valuable advice. Tiffany Taylor’s eagle eyes spotted an embar- rassing number of typos and inconsistencies. Denis Dalinnik did a standout job of typesetting. Special thanks to Jackie Carter, for keeping me honest and keeping me focused on the reader. Thanks also to my technical reviewers, espe- cially Bill Lear, Ryan Cox, Adam Greene, and Joel Trunick—they pushed me to create a better book than I would have thought possible. Thanks as well to Licensed to Rodrigo Urubatan Ferreira Jardim xxii ACKNOWLEDGMENTS Mary Piergies for keeping it all organized, to Clay Andres for getting the whole thing started, and to publisher Marjan Bace for the vote of confidence. Special thanks go to my wife, Suzanne, who has encouraged me every step of the way on this long process. Suzanne’s support has never wavered, even as this project consumed my time, attention, energy, and the dining room table (my workspace) for months on end. Suzanne has indulged my need to talk about Tapestry and this book long past the point where anyone else would have run screaming from the room, and I love her for that, and for her boundless enthusi- asm. We are stylin’, baby! Licensed to Rodrigo Urubatan Ferreira Jardim xxiii about this book Tapestry is a comprehensive web application framework for the Java pro- gramming language. Tapestry is based on components, highly reusable build- ing blocks that can be quickly and easily combined to form pages within your application. By using and reusing components, and creating your own com- ponents, you can create richly interactive, robust applications with only a modest effort. Tapestry’s basic style is to break problems into smaller and smaller units; this complements a team development environment where different develop- ers work on different parts of the application. The framework makes it easy for both Java and HTML-only developers to work together without acciden- tally undermining each other’s work. When building a web application with any technology, you will be faced with a constant stream of questions: How do I figure out what the user has requested? Where can I store this bit of information? How can I safely add this new functionality? How can I make my application scale? In too many envi- ronments, it’s easy to make the wrong decision when confronted with any of these, and many other, development-time questions. It’s too easy to take a quick-and-dirty detour down the wrong path, which ultimately comes back to bite you when you are least prepared to deal with it. The central goal of Tapestry is to make the easiest choice the correct choice. Over the course of this book, we’ll show you how to build applications using Licensed to Rodrigo Urubatan Ferreira Jardim xxiv ABOUT THIS BOOK Tapestry, but we will also show you the hidden traps and tangles that Tapestry helps you to avoid. Roadmap ■ Chapter 1 introduces web applications in general. We begin to describe where Tapestry fits into the overall scheme of things and expand on the basic goals achieved by the framework. ■ Chapter 2 sets the stage, describing the implementation of a simple web application that plays the word game Hangman. Here we introduce many of the major concepts of Tapestry. ■ Chapters 3 through 5 describe how Tapestry handles HTML form input, including the framework’s sophisticated form-validation subsystem. ■ Chapter 6 describes how to build basic reusable components and how to package them into component libraries. ■ Chapter 7 delves into the inner implementation of Tapestry, shedding light on how the framework addresses scalability issues, localization, and the lifecycle of pages. ■ Chapter 8 describes advanced components, including components that create client-side JavaScript. We also discuss integrating Tapestry with a traditional servlets-and-JSPs application. ■ Chapters 9 and 10 describe the Virtual Library, a complete J2EE example application using Tapestry for the presentation layer and a mix of session and entity EJBs for the application layer. We include several real-world examples that illustrate how to build and polish a Tapestry application. Who should read this book? This is a book about getting things done using the Tapestry framework; as such, it will appeal to Java web developers looking for a better, easier way to build web applications. Because Tapestry is explicitly designed to support team develop- ment of web applications, this book will also be of interest to managers looking for a better way to leverage their team’s efforts. This book is targeted at people who have at least gotten their feet wet in terms of Java web application development (or perhaps have already taken the full plunge). Therefore, we assume that you are at least somewhat acquainted with a number of concepts and technologies. Obviously, an understanding of the Java programming language is a prerequisite, as well as familiarity with such key Java APIs as the Licensed to Rodrigo Urubatan Ferreira Jardim ABOUT THIS BOOK xxv collections framework. You should also be clear on the distinction between Java interfaces and Java classes. Much of Tapestry concerns the moving of information from one object to another; this is facilitated using JavaBeans properties. The core concept of the JavaBeans framework is that an object can be treated as if it was a Map, as a collec- tion of named properties that can be read or updated without knowing the actual class of the object. More information about JavaBeans is available at http:// java.sun.com/products/javabeans/docs/. You should be familiar with the basic set of HTML tags, including , , ,
, and . You must also be familiar with URLs and query parameters and the difference between HTTP GET and HTTP POST. Many of the artifacts of a Tapestry application are XML documents. You should be familiar with basic XML usage and syntax. Some of the later examples show how to implement client-side logic. This requires an understanding of JavaScript (the scripting language that executes within a client web browser) as well as the Document Object Model, the data structure representing a web page inside a web browser. Code conventions and downloads This book includes copious examples, which include all the Tapestry application artifacts: Java code, HTML templates, and XML specification files. Source code in listings or in text is in a fixed width font to separate it from ordinary text. Addi- tionally, Java method names, component parameters, object properties, and HTML and XML elements and attributes in text are also presented using fixed width font. Java method names will generally not include the signature (the list of parameter types). Java, HTML, and XML can all be quite verbose. In many cases, the original source code (available online) has been reformatted, adding line breaks and reworking indentation, to accommodate the available page space in the book. In rare cases, even this was not enough, and listings will include line continuation markers. Additionally, comments in the source code have been removed from the listings. Code annotations accompany many of the source code listings, highlighting important concepts. In some cases, numbered bullets link to explanations that follow the listing. Tapestry is an open-source project, released under the very liberal Apache Software License. Directions for downloading Tapestry, in source or binary form, Licensed to Rodrigo Urubatan Ferreira Jardim xxvi ABOUT THIS BOOK are available from the Tapestry home page: http://jakarta.apache.org/tapestry/. Documentation available from the home page also identifies how to download the source code via CVS so that you can build the framework locally, if you are so inclined. The Tapestry distribution includes the Virtual Library application described in chapters 9 and 10. The source code for all examples in this book is available from Manning’s web site: www.manning.com/lewisship/. To run the examples, you need to download Tapestry and the Tomcat servlet container. Appendix B contains the details. Author Online The purchase of Tapestry in Action includes free access to a private web forum run by Manning Publications, where you can make comments about the book, ask technical questions, and receive help from the author and from other users. To access the forum and subscribe to it, point your web browser to www.man- ning.com/lewisship. This page provides information on how to get on the forum once you are registered, what kind of help is available, and the rules of conduct on the forum. Manning’s commitment to our readers is to provide a venue where a mean- ingful dialogue between individual readers and between readers and the author can take place. It is not a commitment to any specific amount of participation on the part of the author, whose contribution to the forum remains voluntary (and unpaid). We suggest you try asking the author some challenging questions, lest his interest stray! The Author Online forum and the archives of previous discussions will be accessible from the publisher’s web site as long as the book is in print. About the author Howard Lewis Ship is the lead developer for the Tapestry project. He cut his teeth writing customer support software for Stratus Computer, but eventually traded PL/1 for Objective-C and NextStep before settling into Java. Howard is currently an independent open-source and J2EE consultant, specializing in cus- tomized Tapestry training. You can find Howard on the web at http://howard- lewisship.com. In the real world, he lives in Quincy, Massachusetts, with his wife Suzanne, a novelist. Licensed to Rodrigo Urubatan Ferreira Jardim xxvii about the title By combining introductions, overviews, and how-to examples, the In Action books are designed to help learning and remembering. According to research in cognitive science, the things people remember are things they discover dur- ing self-motivated exploration. Although no-one at Manning is a cognitive scientist, we are convinced that for learning to become permanent it must pass through stages of exploration, play, and, interestingly, re-telling of what is being learned. People understand and remember new things, which is to say they master them, only after actively exploring them. Humans learn in action. An essential part of an In Action guide is that it is example-driven. It encourages the reader to try things out, to play with new code, and explore new ideas. There is another, more mundane, reason for the title of this book: our readers are busy. They use books to do a job or solve a problem. They need books that allow them to jump in and jump out easily and learn just what they want just when they want it. They need books that aid them in action. The books in this series are designed for such readers. Licensed to Rodrigo Urubatan Ferreira Jardim xxviii about the cover illustration The figure on the cover of Tapestry in Action is a “Gonaquesa Baylando,” a dancing woman of the Gonaqua tribe in Africa. The Gonaquas were herders and farmers living on the southern coast of the continent near the Cape of Good Hope in what is today South Africa. The illustration is taken from a Spanish compendium of regional dress customs first published in Madrid in 1799. The book’s title page states: Coleccion general de los Trages que usan actualmente todas las Nacionas del Mundo desubierto, dibujados y grabados con la mayor exactitud por R.M.V.A.R. Obra muy util y en special para los que tienen la del viajero universal which we translate, as literally as possible, thus: General collection of costumes currently used in the nations of the known world, designed and printed with great exactitude by R.M.V.A.R. This work is very useful especially for those who hold themselves to be universal travelers Although nothing is known of the designers, engravers, and workers who colored this illustration by hand, the “exactitude” of their execution is evident in this drawing. The “Gonaquesa Baylando” is just one of many figures in this colorful collection. Their diversity speaks vividly of the uniqueness and Licensed to Rodrigo Urubatan Ferreira Jardim ABOUT THE COVER ILLUSTRATION xxix individuality of the world’s towns and regions just 200 years ago. This was a time when the dress codes of two regions separated by a few dozen miles identified people uniquely as belonging to one or the other. The collection brings to life a sense of isolation and distance of that period—and of every other historic period except our own hyperkinetic present. Dress codes have changed since then and the diversity by region, so rich at the time, has faded away. It is now often hard to tell the inhabitant of one conti- nent from another. Perhaps, trying to view it optimistically, we have traded a cul- tural and visual diversity for a more varied personal life. Or a more varied and interesting intellectual and technical life. We at Manning celebrate the inventiveness, the initiative, and, yes, the fun of the computer business with book covers based on the rich diversity of regional life of two centuries ago‚ brought back to life by the pictures from this collection. Licensed to Rodrigo Urubatan Ferreira Jardim Licensed to Rodrigo Urubatan Ferreira Jardim Part 1 Using basic Tapestry components Chapters 1 through 5 introduce you to the basic concepts of Tapestry. You’ll learn how Tapestry defines an application, a page within an application, and a component within a page. These chapters gradually expose you to the more complicated challenges in developing web applications and explain how Tapestry meets these challenges. Licensed to Rodrigo Urubatan Ferreira Jardim Licensed to Rodrigo Urubatan Ferreira Jardim 3 Introducing Tapestry This chapter covers ■ Understanding web applications ■ The goals of Tapestry ■ Using the Model-View-Controller pattern ■ Essential Tapestry classes and interfaces Licensed to Rodrigo Urubatan Ferreira Jardim 4 CHAPTER 1 Introducing Tapestry “… and we’re going to implement it as a web interface,” your boss announces, and you start to get that sick feeling in your stomach. You had only been half-listening, daydreaming about the clever, graceful user interface you were going to create. But now you’re in unknown territory; you might as well be fresh out of school! Your men- tal roadmap to the application starts to lose focus; you can still see the islands of code and functionality, but now you imagine a dark, churning sea between them. How are you going to get this to work as a web application? If you are used to creating traditional desktop applications, switching over to devel- oping web applications can be a journey into unfamiliar territory. Web applications turn everything you know upside down—in desktop applications, your code is in the driver’s seat, ultimately controlling every pixel visible to your single user, and the application is all-knowing about every mouse movement or key press. In web applications, your code is just part of an overall picture involving a variety of com- puters and network communications… and your code must support multiple simul- taneous users. This book describes how to use the Tapestry web application framework to ease your transition into the web application development space. The Tapestry framework is more than a crutch for new web developers; even seasoned developers and project leaders will find advantages to building their applications using it. Tapestry allows true black-box code reuse within and between applications; it addresses the concerns of large teams of developers with mixed skill sets; it has support for application internationalization as a central feature, not an afterthought. This book shows you how to leverage Tapestry to produce a more sophisticated, more robust application faster and more easily than you might suspect. Tapestry is an open source, Java web application framework designed from the ground up to help you deliver the best web applications possible. It allows new developers to get up to speed easily, creating working applications with a surprisingly small amount of Java code. It includes numerous features that address the issues encountered during team development of large, complex, internationalized applications. Tapestry is open-ended—it doesn’t shoehorn you into any one category of web application, which means you can build your own Amazon, Slashdot, Yahoo!, or eBay with it. Tapestry is designed to plug into whatever form of back-end systems you use, with a minimum of fuss. This is a framework that lets you have it both ways: you create a web applica- tion, but you don’t have to throw away everything you’ve previously learned about coding just because you are creating a web application instead of a tradi- tional desktop application. The cornerstone of Tapestry is letting you write Licensed to Rodrigo Urubatan Ferreira Jardim What are web applications? 5 applications in terms of objects, methods, and properties—not in terms of URLs and query parameters and Java servlets. 1.1 What are web applications? In the earliest days of the World Wide Web, the majority of web sites were entirely static, unchanging. Many early sites were created entirely by hand, by folks using simple text editors to directly edit the HTML of individual pages. Other sites were created in a batch mode, where a source file or database would be transformed into HTML. In fact, the original purpose of the Web was to allow physicists to easily share their publications; physics papers written using LaTeX (a special-purpose typesetting language) were translated into HTML for instant sharing. Web sites didn’t stay static for long; instead, they transformed into web appli- cations. A web application is interactive; the end user will see links that may be clicked and HTML forms that may be filled out. These links and forms become requests sent to a server, which will respond with a new HTML page, often cre- ated (from a template) on the fly, as a personalized response to a specific request. Figure 1.1 illustrates the general flow of a web application, which is divided into four steps: 1 A request is received by the server. Information in the request is used to dispatch control to the correct application code. 2 Application-specific code executes. This code interprets the information available to it in the URL and query parameters, and executes some application-specific logic. This includes deciding what response to send back to the client. Figure 1.1 Web applications are built around a cycle; a request is received, application- specific code is executed, and a response is rendered, eventually appearing in the client’s web browser. Licensed to Rodrigo Urubatan Ferreira Jardim 6 CHAPTER 1 Introducing Tapestry 3 A response is rendered. The response includes links and forms that will result in new request cycles. 4 The response is visible in the client web browser. Users now have a chance to see the results of the action they initiated. In addition, any client-side logic is activated.1 The user may click links or submit forms, resulting in a new request cycle. Web applications are written in the context of the underlying network protocol connecting the client web browser (such as Microsoft Internet Explorer or Netscape Navigator) and the web server: the Hypertext Transport Protocol (HTTP). HTTP is a stateless protocol, meaning that each request is completely independent of any requests that come before or after it. There is no explicit concept of user identity in HTTP or even, at the protocol level, any way to identify that a series of requests comes from a single user. The stateless nature of HTTP is an important factor in its success; a stateless protocol is much easier to imple- ment in a web browser or a web server. For dynamic, interactive web applications, the inherent statelessness of HTTP creates new challenges. End users are not concerned with this; they simply expect to progress from a catalog page to a shopping cart page to a checkout page. Creating a stateful application from a stateless protocol is a concern for the application developer and involves issues not present in a desktop application. By comparison, imagine writing a document by launching a text editor applica- tion, loading a file, typing a single sentence, saving the file, and then shutting down the application before repeating the whole process for the next sentence. For a web application, each request is like one sentence in that document…and part of the request had better be which document is being edited and where in the document the new sentence goes. A desktop application automatically has a kind of continuity that must be specifically engineered into a web application. This same basic web application flow can be implemented in many different environments and languages. In the early heyday of web applications, scripting languages such as Perl were most often used to implement the application- specific code. Over time, as web applications evolved from clever novelties into critical enterprise infrastructure, the implementation choice for web applications shifted to more powerful, higher-level languages. The Java programming 1 This takes the form of JavaScript programs embedded within the HTML that execute within the cli- ent’s web browser. Licensed to Rodrigo Urubatan Ferreira Jardim What are Java servlets? 7 language is especially well suited for web application development because of its standard web application framework, the Java Servlet API. 1.2 What are Java servlets? The Java Servlet API is an open standard, created by Sun, for creating web applications using the Java programming language. A servlet is an object responsible for receiving a request from a client web browser and returning a response—an HTML page to be displayed in the browser. The Servlet API defines an interface and base class for servlets, as well as interfaces for several other supporting objects, such as the HttpServletRequest (which represents a request and allows the servlet to access query parameters). Vendors, both open source and proprietary, provide the actual server code and the implementations of the standard interfaces. Servlets are most often paired with JavaServer Pages (JSPs) in order to gener- ate a response. JSPs are a standard templating technology for servlets. A JSP is a mix of ordinary, static HTML with additional, specialized tags and directives that provide dynamic output, such as including the current user’s name or the con- tents of an online shopping cart. Under the hood, each JSP is converted into a Java servlet that is compiled and loaded into memory. More information about servlets and JSPs is available online and in print, including Web Development with JavaServer Pages.2 A servlet operates within a servlet container. The servlet container serves as a bridge between HTTP and the Java servlet code that you, as the developer, will write. The servlet container is responsible for instantiating and initializing your servlets as needed. Servlet containers may be standalone, such as Apache Tom- cat, or may be one part of an overall application server, such as BEA WebLogic, IBM WebSphere, or the open source JBoss application server. The servlet container is responsible for selecting the correct servlet to invoke based on the URL of the request; a single web application will contain many serv- lets. The web application’s deployment descriptor (an XML file packaged with the application) gives the name and Java class for each servlet and identifies which URLs are mapped to which servlets. For example, your application may have a reg- istration page containing an HTML form enabling new users to register (supply- ing their name and email address or other data). That form will submit to the 2 Duane K. Fields, Mark A. Kolb, and Shawn Bayern, Web Development with JavaServer Pages, 2nd Edition (Greenwich, CT: Manning Publications, 2001). Licensed to Rodrigo Urubatan Ferreira Jardim 8 CHAPTER 1 Introducing Tapestry /addCustomer URL, which is mapped to the addCustomer servlet, which is instan- tiated as the AddCustomerServlet class. The deployment descriptor will include the following elements describing the addCustomer servlet’s configuration: addCustomer com.mycompany.AddCustomerServlet addCustomer /addCustomer Figure 1.2 shows how the application request cycle applies to servlets. As can be seen from this example, each servlet represents a particular opera- tion within the overall web application. The AddCustomerServlet class will include code that reads the query parameters submitted with the form and uses those values to create some kind of Customer object that can be stored into a database. Additional logic must decide what form of response to display—normally a con- firmation page, but possibly the original page containing the form if an error occurred when creating the customer. This operation-centric approach can work well for many parts of a web appli- cation. In this example, there is only one registration page, and so only one place from which the addCustomer servlet can be invoked. If the original registra- tion page must be redisplayed (to present the user with an error message), there is no guesswork about what page to use; it will always be Registration.jsp. When an operation can be associated with many different application pages, the operation-centric approach can start to become a burden. For example, many applications include pages that display long lists of information and allow Figure 1.2 Incoming requests to the server are dispatched to a particular servlet instance. The servlet forwards to a JSP that renders the response sent back to the client web browser. Licensed to Rodrigo Urubatan Ferreira Jardim What are Java servlets? 9 users to navigate through the results one page at a time. Such pages include links to operations such as “next page of results” or “previous page of results.” Using servlets, it becomes necessary to somehow identify not just the opera- tion, but to where the operation should be applied. Are we displaying the prod- uct catalog page, or the shopping cart page, or the related-items page? Now we need a way to identify the page; a common approach is to add a query parame- ter to the URL to identify that page. Multiplexing operations onto multiple pages is just one factor a servlet devel- oper must keep in mind. Another is dealing with the multithreaded environment in which a servlet object operates. 1.2.1 Understanding servlet multithreading Within a servlet container each servlet is instantiated exactly once; all requests mapped to the servlet’s URL pattern will be passed through to the service() method of a single servlet object.3 Servlet containers are multithreaded, which is to say they contain multiple threads of execution, allowing requests from several client web browsers to be processed simultaneously—a critical factor for building scalable web applica- tions. Despite this, servlets are still single instances, shared between the threads. Servlet instances must be thread-safe; instance variables can’t be used either for temporary storage during a single request or for persistent storage between requests, because any information stored in an instance variable is likely to be overwritten by other threads. Servlets are the odd-man-out in the object-oriented world; they are techni- cally still objects but largely fall short of the accepted definition of an object: a combination of operations and state. Servlets aren’t allowed to directly store state in their own instance variables. Local variables are fine for some things, but there are specific cases where local variables are not applicable, such as forward- ing to a JSP to render a response. JSPs, like servlets, are stateless and multithreaded; therefore, there’s no way that a servlet could, for example, set a property of a JSP to the current user’s name. Instead, the Servlet API object HttpServletRequest is used to store such transient information. One aspect of the request object is that it can be used to store named attribute values. The servlet can store values into the request before forwarding to a particular JSP to render the response, as shown in figure 1.3. 3 Your servlet code will be inside the doGet() or doPost() method that is invoked by the ser- vice() method. Licensed to Rodrigo Urubatan Ferreira Jardim 10 CHAPTER 1 Introducing Tapestry This is an example of the servlet “pushing” data into the JSP via the HttpServlet- Request. The servlet and JSP are intimately tied together; they must agree on exactly which attributes will be stored into the request object, and there’s no room for error on the naming (username is not the same as userName, for exam- ple). Overall, the HttpServletRequest provides a flexible solution for managing transient information; as we’ll see, a similar solution addresses the more compli- cated problem of persistent server-side state. 1.2.2 Managing server-side state The single attribute that most widely separates a static web site from a true web application is server-side state. Amazon has a shopping cart that remembers what items you’ve placed in it from one request to the next. Google remembers enough about your query to allow you to page through the list of results. Any application will need to identify you as a particular user and remember some amount of information about the activities you’ve performed while visiting the site. For a web application to be truly interactive and personalized, it must have a way of identifying the user from one request to request, and a way to store infor- mation on the server about that particular user. This is not necessarily the same thing as authentication; the application may not be able to match the requests to Figure 1.3 A servlet can store a request attribute before forwarding to the JSP; the JSP can then read the attribute and incorporate it into the response. Licensed to Rodrigo Urubatan Ferreira Jardim What are Java servlets? 11 a particular user account or credit card number, but that shouldn’t prevent the application from tracking short-term information, such as the contents of a shopping cart in an e-commerce application—information that needs to persist from one request to the next. So, if HTTP is stateless, how can an application store any state at all? What’s needed is some form of identifier to be created on the server, sent back to the cli- ent, and returned to the server as part of subsequent requests. Fortunately, most client web browsers support HTTP cookies; a cookie is a small string stored within the browser. The server can invisibly include a cookie value in a response,4 and the client browser will send the cookie value back up to the server in each subse- quent request—exactly what is required to support server-side state. A series of such linked requests forms a session. For servlets, server-side state is stored in the HttpSession object. Like the HttpServletRequest, the session object may store named attribute values; the dif- ference is that such values are stored on the server between requests. For example, the ShoppingCart object created and stored in the session by the addToShopping- Cart servlet will later be available to the checkout servlet responsible for collect- ing payment information, even though the user may do quite a bit of browsing around the product catalog in between those two operations. Sessions are not created until specifically requested; the HttpServletRequest object includes a method, getSession(), for creating the session as needed. Some people or organizations do not allow HTTP cookies (most browsers allow support for cookies to be turned off, for privacy and security reasons). With a lit- tle extra effort, it is still possible to support stateful servlet applications even when the client does not support cookies.5 Support for servlets and JSPs is a standard feature for all popular application servers, and many developers do manage to create successful web applications using just the standard servlet technology. At the same time, there is a growing acknowledgement that servlets are just the starting point for Java web applica- tions, as evidenced by an increasing number of popular frameworks. This goes beyond just Tapestry and includes other frameworks such as Turbine (http:// jakarta.apache.org/turbine/), Maverick (http://mav.sourceforge.net/), WebWork (http:// www.opensymphony.com/webwork/) and, most prominently, Struts. 4 The HTTP cookie is part of the HTTP “envelope” around the HTML “message”; it isn’t part of the HTML and won’t be visible even if the user views the HTML source of a rendered page. 5 The servlet container can encode the session ID into the application’s URLs to provide session continuity. Licensed to Rodrigo Urubatan Ferreira Jardim 12 CHAPTER 1 Introducing Tapestry 1.2.3 Using Struts with servlets Struts, like Tapestry, is an open source framework available from the Jakarta project, at http://jakarta.apache.org/struts/. Struts is an extension to standard servlets, rounding out rough edges and adding a few much-needed abstractions that simplify servlet coding. A comprehensive guide to Struts is available in Struts in Action.6 A Struts application uses a single servlet to represent all possible operations. The servlet uses a set of Struts-specific configuration files to dispatch incoming requests to a particular Struts Action object. Like servlets, Struts Actions are mul- tithreaded and stateless. Struts also includes a set of JSP tag libraries that sim- plify and standardize the creation of dynamic web pages, especially with respect to creating URLs for links and form submissions. Struts uses its configuration files to loosely couple Actions to the JSPs that render responses (and makes it easier to use another templating system, such as Velocity). JSPs are assigned logical names in the Struts configuration that can be referenced from an Action; if the JSP is moved or renamed, the side effects are isolated to just the Struts configuration file, not the Java code for the Action. The same mechanism also makes it easier to chain together a series of Actions; for example, the one Action can be used to update some server-side state and can forward to a second Action that stores information into the HttpServlet- Request before forwarding to a JSP to render the response; this useful pattern is easier to accomplish in Struts than with ordinary servlets. Struts includes a standard mechanism for converting form submissions into objects, which works by matching query parameter names against JavaBean properties of a user-defined form object. The latest release of Struts comple- ments this with a rules-based input-validation system. Struts standardizes some common patterns and techniques that would other- wise need to be re-invented by each developer, but it is still fundamentally similar to ordinary servlet development. Whether developing using just servlets or with servlets augmented by Struts, you will still encounter some significant limitations in the basic development model that you must overcome. 1.2.4 Understanding the limitations of servlets There is a qualitative difference between the kind of servlet application seen in demos and tutorials and true enterprise applications. Demo applications are 6 Ted Husted et al., Struts in Action (Greenwich, CT: Manning Publications, 2002). Licensed to Rodrigo Urubatan Ferreira Jardim What are Java servlets? 13 small, focused, and limited in scope; they are often created in a short period of time by a single individual, who fills the roles of application architect, HTML developer, and Java developer simultaneously. By contrast, development of a full-scale application entails a number of real- world concerns: ■ A large number of developers (HTML and Java) may be working in parallel. ■ Individual developers will have varying levels of skill and understanding. ■ The HTML portions of the application may be developed by team mem- bers with little or no knowledge of Java or JSPs (or even by a completely separate, isolated team). ■ Large applications (with hundreds of distinct pages) may be so complex that individual developers will understand only a small portion of the overall application. ■ Successful applications will, almost by definition, grow and expand in com- plexity to meet new customer requirements. These concerns manifest themselves in a number of common antipatterns, which we describe next. Weak binding The most fundamental issues with building, maintaining, and extending a web application using servlets surround two related problems: weak binding and unwanted dependencies. In a servlet application, the connections between pages are weak because those connections are expressed as URLs, not as method invo- cations or object properties. At the time that a JSP renders a link (or form) con- necting to another page in an application, all it is doing is outputting a string as the href attribute of an HTML hyperlink tag (or the action attribute of an HTML tag). This is another difference between desktop applications and servlet applications: Changing a method signature in an object of a desktop application will create errors in code elsewhere in the application that uses the old method signature. The integrated development environment (IDE) will clearly show those errors, and you can find and fix all of them before running and testing your application. In a servlet application, the connection between two servlets is reduced to a string within a JSP. The JSP must include a URL string (for the target servlet) in the response sent to the client web browser. Where does that URL string come from? The developer manually inserts it into the JSP, after consulting the web Licensed to Rodrigo Urubatan Ferreira Jardim 14 CHAPTER 1 Introducing Tapestry deployment descriptor (or Struts configuration file) to find the operation map- ping to link to. Assuming the developer introduces no typos, the link between pages should be functional—but there’s no way to be absolutely sure without actu- ally running the application. This servlet-to-servlet linkage is a weak binding; the Java compiler or other tools cannot validate (at build time) that the URLs are correct and functional; validation can occur only at runtime. Weak URL bindings don’t really show their teeth until the application is altered in some way, such as a change to the signature of an application opera- tion; for example, an operation URL may change (i.e., /addCustomer to /addRetail- Customer), or the query parameters passed in the URL may change (i.e., productId to sku). Such a change in signature will require a hunt throughout the application for references to the old operation signature, to update it to the new signature. In other words, there are unwanted dependencies between the pages that link to operations and the implementations of the operations themselves. Team conflicts Weak bindings can provoke one form of team conflict; another example occurs between the HTML developers and the Java developers. It is too easy for an HTML developer to make a minor change to a JSP that “breaks” the page in some way. In most environments, the entire application would need to be built, deployed, and executed in order for the offending change to be noticed. Only recently have JSP-aware HTML editors become available; when used, these edi- tors can provide a real-time WYSIWYG view of application pages—but it is not a given that HTML developers will have access to such tools or be fully proficient using them. This can leave HTML developers in a situation where they need to build, deploy, and run the live application to see the effects of their changes—a solution that may not be practical unless the HTML developers are co-located with the Java developers. Another form of team conflict concerns choke points in the application—shared files or Java classes that many Java developers will need to frequently update. A key example of this is the web deployment descriptor, the file used to define serv- lets and URL mappings. As each developer adds a new servlet, the file must be updated; with many developers doing similar work in parallel, the opportunity for conflicts is inevitable. Struts reduces the level of contention for the web deployment descriptor by allowing multiple configuration files to express the same kind of information, but there’s still room for conflicts elsewhere. Licensed to Rodrigo Urubatan Ferreira Jardim What are Java servlets? 15 Bad coding shortcuts Creating servlet applications can be a code-intensive process; this can lead to developers taking bad coding shortcuts. A common example is merging data- base access code directly into a JSP. Initially this seems like a good idea, since it locates the code for database access directly with the JSP that will present the results of the query to the user. Unfortunately, this approach leads to horrific problems in a running applica- tion. It makes the JSP very fragile in the face of any change to the underlying data model. The query will simply throw an exception at runtime if a table or column name changes. It is unlikely that shortcut code will take advantage of database connection pools, but will instead create, use, and close a connection inline. While this works fine in testing, it falls flat in the face of a high volume of concurrent users. Finally, a shortcut approach will likely not include a try…finally block to ensure that the connection is closed when done; this can lead to database connection leaks, another way to bring a production application to its knees. Here are other common coding shortcuts: ■ Failure to properly localize output in an internationalized application (including failure to use localized date and number formatters). ■ Not using HttpServletRequest.encodeURL() to encode the session ID into application URLs, which breaks the application for users who do not have HTTP cookies enabled. ■ Not properly filtering output to convert reserved HTML characters (such as < and >) to HTML entities; this results in visual oddities in the client web browser. ■ Using short, unqualified names for HttpServletRequest and HttpSession attributes, resulting in naming conflicts and overwritten data. Many other examples abound but require considerably more background mate- rial to fully describe. The point of this section was not to launch a diatribe against servlets—servlets are a powerful tool for creating web applications. Instead, we want to focus on the areas where servlets require too much effort or expertise on the part of the developer to be used effectively, especially in large and complex applications. Licensed to Rodrigo Urubatan Ferreira Jardim 16 CHAPTER 1 Introducing Tapestry 1.3 Why do we need Tapestry? Tapestry exists to simplify Java web application development. All the features of this framework are designed to make it simpler to create robust applications that are easier to construct, debug, maintain, and extend than traditional servlet applications. Tapestry extends your reach, allowing you to create more powerful, more useful, more interactive applications faster than you would using ordinary servlets. In addition, you’ll have greater confidence that your application will be free of bugs. Also, Tapestry reduces the level of contention between team mem- bers (both Java and HTML) working on a shared project. Ultimately, Tapestry applications are still servlet applications. With enough code, and enough time to debug that code, there’s nothing (from an end user’s point of view) that a Tapestry application can do that a servlet application can’t. Tapestry applications still follow the request cycle in figure 1.1; a request is received, some kind of server-side processing occurs, and a response is sent back to the client web browser. The difference is that Tapestry allows you to write far less code, and the code you do write is simpler and more natural because Tapes- try excuses you from concerns about multithreading. Tapestry recasts the stateless, operation-centric Servlet API into a stateful, component-centric model, the familiar coding pattern used in traditional desk- top applications. The approaches you are accustomed to taking—such as com- bining operations and data together to form objects, using components and inheritance, storing information in instance variables, and so forth—all of these are hallmarks of Tapestry application development as well. This will not seem remarkable if you haven’t done any work with servlets before; if you have, you’ll be surprised by the things you don’t see in a Tapestry application: the HttpSession and HttpServletRequest objects, or any thought of URLs or query parameters; all of these things are pushed down into the bowels of the Tapestry framework, where the details are handled robustly, efficiently—and invisibly. 1.3.1 What is a framework? A framework is a set of cooperating classes that make up a reusable design for a specific category of software. A framework is different from a toolkit; a toolkit is a collection of individually reusable classes, each performing a small, isolated function that is applicable to a wide variety of uses. By contrast, a framework …dictates the architecture of your application. It will define the overall structure, its partitioning into classes and objects, the key responsibilities thereof, how the classes and objects collaborate, and the thread of control. A framework predefines Licensed to Rodrigo Urubatan Ferreira Jardim Why do we need Tapestry? 17 these design parameters so that you, the application designer/implementer, can concentrate on your application. The framework captures the design deci- sions that are common to its application domain.7 Frameworks are very useful; instead of your having to start with a clean slate, the design is partially filled in and the path to follow is clear. Many design decisions are already made for you, decisions that leverage the combined experience of the frameworks’ authors and users. A framework also provides a significant cod- ing head start in the form of reusable classes, utility classes, and base classes for you to extend. Ideally, you simply have to fill in the blanks, connect a few dots, and provide some application-specific subclasses. The end result is less code for you to write and more consistency between applications, not just from the devel- oper’s point of view but from the end users’ perspective as well. Many frameworks, including Tapestry, incorporate a component object model, which we will look at in the next section. This approach allows you to eas- ily mix and match off-the-shelf objects with objects you create yourself. 1.3.2 What is a component? A component is an object that fits into an overall framework; the responsibilities of the component are defined by the design and structure of the framework. A component is a component, and not simply an object, when it follows the rules of the framework. These rules can take the form of classes to inherit from, naming conventions (for classes or methods) to follow, or interfaces to implement. Compo- nents can be used within the context of the framework. The framework will act as a container for the component, controlling when the component is instantiated and initialized, and dictating when the methods of the component are invoked. A component framework is a useful thing; components, more so than objects, can be easily combined to perform complex operations or to create entirely new compo- nents. The rules of the framework dictate how components can be used together. A component object model is the portion of the framework that defines the rules for how individual components may be combined for such purposes. The framework’s com- ponent object model also dictates the responsibilities and lifecycles of containers (components that are constructed from other components) and contained objects. The JavaBeans framework is a good example of a component framework— it’s very general and adaptable. Following the basic naming convention for 7 Erich Gamma et al., Design Patterns: Elements of Reusable Object-Oriented Software (Reading, MA: Addison- Wesley, 1994). Licensed to Rodrigo Urubatan Ferreira Jardim 18 CHAPTER 1 Introducing Tapestry methods specified by the framework (the well-known getters and setters naming conventions) makes it possible for components created at different times and by different authors to be seamlessly combined together at runtime. The JavaBeans framework’s component object model is structured around an event-notification system. Individual components may be event producers or event consumers. Another standard naming convention allows objects and components to register for event notifications in a generic fashion. Originally, the JavaBeans framework was intended for use in developing graphical user interfaces (GUIs) for desktop applications; the components were expected to be visual objects, such as buttons and text fields, that could be manipulated in a visual GUI editor. Ultimately, the JavaBeans framework proved to be just as useful for nonvisual objects and is nowadays frequently used as the basis for other frameworks—including Tapestry. The component approach has been widely used in many environments to support traditional GUI development, including Java’s AWT and Swing. Using a component approach for web applications is less widespread but is not original to Tapestry. As far back as 1994, NeXT’s WebObjects framework integrated a component object model with graphical editor tools and an object relational database layer (to handle long-term data persistence, much like entity Enter- prises JavaBeans in a Java 2 Enterprise Edition application). In WebObjects, components were responsible for both generating portions of an HTML page and responding to user requests, and the framework was respon- sible for hiding the details of HTTP and server-side state management. This approach to componentizing web application behavior matches the high-level design goals of Tapestry, though the overall design and implementation of the two frameworks are completely different. 1.3.3 What is Tapestry? The Tapestry framework is a layer between a Java servlet container and a Tapes- try application. Tapestry is not a standalone server; it is an extension to servlets and works within existing servlet containers (such as Tomcat) or application serv- ers (like JBoss, WebSphere, or WebLogic), which include a servlet container. Fig- ure 1.4 shows how Tapestry fits into the overall picture; the application consists of pages, which are themselves constructed from reusable components. The application operates within the application server. To the application server, the application is just another servlet. Web applications are typically implemented in terms of three layers, each addressing a different concern within the overall application: Licensed to Rodrigo Urubatan Ferreira Jardim Why do we need Tapestry? 19 ■ The presentation layer is responsible for receiving incoming HTTP requests and forming HTML responses. ■ The application layer is responsible for all business logic; this is often imple- mented using Enterprise JavaBeans (EJBs). ■ The database layer is responsible for storing data persistently. Tapestry is only the presentation layer of an application. By this, we mean that Tapestry deals only with presenting information to an end user, as HTML,8 and 8 Actually, very little of Tapestry is HTML specific. Tapestry is equally at home creating Extensible Markup Language (XML), Extensible Hypertext Markup Language (XHTML), or Wireless Markup Language (WML). A suite of WML-related components is included with the framework. Figure 1.4 Tapestry applications run within a servlet container or application server; inside the server, the application consists of individual pages built from components. The Tapestry application servlet links the requests from the servlet container to the pages and components of the application. Licensed to Rodrigo Urubatan Ferreira Jardim 20 CHAPTER 1 Introducing Tapestry handling input from the user in the form of clickable links and HTML forms. Tapestry doesn’t know, or care, about the application layer, the domain logic, or the source of information. A Tapestry application can be built on top of anything from a flat-file database, to a relational database accessed via Java Database Con- nectivity (JDBC), all the way up to a globe-spanning network of EJBs. Tapestry doesn’t mandate anything about these aspects of your application; it is con- cerned solely with how the presentation layer is organized. The Tapestry framework fills a gap that is often missing in web application development: It provides a consistent structure, a skeleton for developers to work with. Too often, each developer is left to manage on his or her own, a freedom that is missing from other engineering disciplines and one that entails a high degree of risk. Imagine building a skyscraper by dividing the job into construct- ing individual floors and instructing each worker to design and build a single floor, with the intention of using a crane to stack them all up at the end. Each worker is provided with a rough sketch of the finished building and told what kind of work will be done on that floor: “We’ll be putting the accounts payable department here,” or “This is where the company cafeteria will go.” You can imagine the kind of disaster that would result; free of any constraints, each worker would use different materials and a different layout. Elevator shafts wouldn’t line up, some workers would forget to leave room for electrical wiring or plumbing…and some might even forget to put in windows! And yet many web applications are developed under a similar kind of chaos. Specifications are incomplete; different developers approach similar problems in different ways. Some developers are unaware of important details of the project or of the many minor details of web application development. Skills sets vary, so some developers excel at writing client-side JavaScript, others are good at inter- acting with back-end systems—but project management may treat the develop- ers as completely interchangeable. Too often, the success or failure of entire projects rests on the shoulders of a “SWAT team” that must glue all the bits and pieces of code together at the last minute. Continuing with the skyscraper metaphor, Tapestry changes the picture sig- nificantly. Instead of starting with individual two-by-fours, the workers start with the empty shell of each floor, with most of the plumbing and wiring already in place. It just becomes a matter of installing those fixtures and features unique to that floor, all with the knowledge that everything will stack up properly at the end of the project. Translate floor to page, fixture to component, and install to con- figure, and you’re well on your way to understanding what Tapestry offers you as a developer. Licensed to Rodrigo Urubatan Ferreira Jardim Why do we need Tapestry? 21 Tapestry is not a panacea for all development problems; however, it does pro- vide a consistent way to describe the implementation of a web application project and the interaction between elements of the project provided by different devel- opers. Web applications don’t have elevator shafts, but they do have, for example, consistent navigation bars, or embedded search forms, or login buttons. These elements can be implemented as Tapestry components, tested separately, and reused on every page, thus guaranteeing both a consistent visual look and a con- sistent interactive behavior. 1.3.4 Comparing Tapestry to Swing Tapestry represents a different way of assembling web applications, one based on combining and configuring components. Tapestry development is much closer in style to creating an application using the Swing GUI framework than tradi- tional web application development with servlets and JSPs. Developers who are familiar with creating desktop applications (using Swing or AWT or even toolkits for other languages and environments) are often left in the lurch when transitioning over to web applications. The familiar patterns of development don’t translate well. For desktop applications, you expect to com- bine and configure existing components to create your application. You expect individual components to encapsulate both output (what gets drawn to the screen) and input (what happens when the user types or clicks the mouse). The components, and the framework they build upon, shield the developer from the minute details of the desktop environment: It is rare that you need to be con- cerned with event queues or pixel-oriented screen updates when developing a Swing application because the JButton component already knows how to draw on the screen and respond to user input. Traditional servlet development doesn’t contain anything similar; servlets and JSPs are always custom written and tied directly to the overall application. Tapestry restores this style of development. Tapestry pages consist of compo- nents that are configured and connected to one another. The framework allows you to create components, pages, and applications without any awareness of serv- lets. Like Swing developers, Tapestry developers know objects such as HttpSession, HttpServletRequest, and HttpServlet are present; there just isn’t any pressing need to access those objects—that’s Tapestry’s responsibility, encapsulated within the framework and the Tapestry components. Tapestry performs all the neces- sary dispatching of incoming requests and uses an event-notification system to get application-specific code executed. Licensed to Rodrigo Urubatan Ferreira Jardim 22 CHAPTER 1 Introducing Tapestry 1.4 Understanding Tapestry’s goals At the core of Tapestry’s design is a vision: It should be easier for you to do the right thing than the wrong thing—Tapestry should allow you to avoid the pitfalls and antipatterns prevalent in traditional web application development. You should end up with a robust, scalable, maintainable application not because the framework merely allows it, but because the framework makes that the clearest, simplest option to follow. This vision is expressed in terms of four goals that influence the design and implementation of the framework: simplicity, consis- tency, efficiency, and feedback. 1.4.1 Simplicity Tapestry applications contain a surprisingly small amount of Java code. The stateless, operation-centric programming model used by normal servlet applica- tions requires far too much Java code—code to extract and interpret query parameters from the request, code to manage data stored as session attributes, and so on—code for all the uninteresting plumbing. At the center of all this plumbing is the small amount of application-specific logic: what to do with the data once it has been extracted, translated, converted, and validated. In a Tapes- try application, the Java code you write is just that application-specific logic (in the form of a listener method, as you’ll see in chapter 2). Combining a generic component with the application-specific listener method is a simple and elegant way to structure an application. All that plumbing is no longer your responsibil- ity; it’s all buried inside the Tapestry framework. You tell the framework what needs doing, and it takes care of the details. For example, rather than write code to build and format a URL with query parame- ters, you just use an existing component and configure it to execute a method you supply when it is triggered. Likewise, Tapestry’s HTML form support takes care of reading object properties when a page is rendered, but also reads and interprets query parameters and updates object properties when a form is sub- mitted (this is covered in chapters 3, 4, and 5). 1.4.2 Consistency Consistency on a large team project can be a godsend. If you’ve ever been brought into an existing project, or have even filled in temporarily for another team member on a project, you’ve likely experienced “impedance frustration.” That is, each developer names things a little differently, solves problems a little dif- ferently, puts code in different places, and so forth. Before you can be productive Licensed to Rodrigo Urubatan Ferreira Jardim Understanding Tapestry’s goals 23 in another developer’s code, you have to figure out that developer’s style, and that causes frustration. With Tapestry, you get a consistent environment and approach across pages, even when different pages are the responsibility of different developers. There’s little or no guesswork when adding new links or forms to pages; they all work pretty much the same way because they are based on the same reusable compo- nents. Tapestry makes it easy to create new components for common functionality in an application. The application behaves consistently and is coded consistently because of reuse of both components and code. 1.4.3 Efficiency It is important that Tapestry applications be scalable; there’s no point in creating a web application if it can’t handle a reasonable number of concurrent users on rea- sonable hardware. Internally, Tapestry uses object pools and caches to minimize the amount of processing that must occur during a request. For example, Tapestry will read each XML specification file and each HTML template exactly once and store the file’s contents for later use within the application. In addition, the frame- work is structured so that expensive application operations (for example, perform- ing a Java Naming and Directory Interface [JNDI] lookup to resolve an EJB’s home interface) can be performed once and cached for fast access when needed again elsewhere within the application. You’ll see examples of this in chapter 10. Tapestry applications have been compared to equivalent servlet or Struts applications and found to have similar performance curves. Such results match the traditional wisdom that the presentation layer is rarely the application per- formance bottleneck; the time it takes to process a request and render a response is usually gated by the speed with which data can be obtained from the applica- tion’s back-end database. 1.4.4 Feedback In most web application frameworks, when something goes wrong in your code or in the framework code, you will see a stack trace in the web browser. Suddenly, you are forced to play detective, working backward from the cryptic clues pro- vided in the stack trace to the problem. Does your code contain an error? Is there a typo in a deployment descriptor? An error communicating with your application server? A deployment problem with an EJB? You can waste large amounts of valuable developer time tracking down often-trivial problems. The main code path into a Tapestry application has multiple layers of excep- tion catching and reporting that ensure that a reasonable exception report is Licensed to Rodrigo Urubatan Ferreira Jardim 24 CHAPTER 1 Introducing Tapestry displayed either in the client web browser or in the server’s console. Figure 1.5 shows the initial portion of such an exception report (this exception was pro- voked by introducing a typo into the HTML template for the Hangman applica- tion, described in detail in chapter 2). Figure 1.5 The Tapestry exception report page starts by identifying the nested exceptions, providing a stack trace for the most deeply nested exception. The exception also identifies the file and line associated with the error. In this case, line 27 of file Home.html had a typo: listenersstart instead of listeners.start. Licensed to Rodrigo Urubatan Ferreira Jardim How does Tapestry work? 25 Tapestry’s approach is to provide you with as much information as possible to help you rapidly fix the problem. This is represented by five factors visible in figure 1.5: ■ Tapestry has worked through the stack of exceptions, starting with org. apache.tapestry.BindingException and digging down to ognl.NoSuch- PropertyException. ■ For each exception, it has displayed the exception message and all the properties of the exception (binding and location for the BindingException, and target for NoSuchPropertyException). ■ The report identifies the exact file and line that is in error—the location property of the BindingException indicates that the error is on line 27 of file Home.html. ■ The stack trace for the deepest exception, NoSuchPropertyException, is the most relevant, and that’s the only one displayed. ■ Tapestry has attempted to describe in the exception message exactly what went wrong. Stack traces and exceptions are not always enough. Sometimes to understand a problem you need to know more about the request and general environment. As shown in figure 1.6, the exception report continues with exhaustive output about the key Servlet API objects (HttpServletRequest, HttpSession, HttpServ- let, and HttpServletContext), followed by a listing of all Java Virtual Machine (JVM) system properties. Collecting this kind of information would normally require restarting the application and using the Java debugger. Tapestry saves you time by providing all this useful information immediately, at the time of the initial error, which means less time tracking down bugs and more time for everything else. With these four central goals in mind, it’s now time to start describing, at a high level, how Tapestry operates. 1.5 How does Tapestry work? Tapestry is an extension that builds on the standard Java Servlet API. Tapestry still uses a servlet and still interacts with all the Servlet API objects: HttpServlet- Request, HttpSession, and so forth. Tapestry applications are still servlet applica- tions and can be deployed into any standard servlet container, such as Apache Tomcat, Jetty, Resin, WebLogic, or WebSphere. The framework is code compatible Licensed to Rodrigo Urubatan Ferreira Jardim 26 CHAPTER 1 Introducing Tapestry with Java Development Kit (JDK) 1.2 and Servlet API 2.2,9 which allows Tapestry to be used in virtually any recent Java application server. 9 Tapestry includes a servlet filter that performs a redirect of the initial application request to the Tap- estry application servlet; servlet filters are a feature of Servlet API 2.3. Chapter 10 discusses the need for this filter and how to make use of it. Figure 1.6 The exception report includes a vast amount of information about the Request, Session, Servlet, ServletContext, and all JVM system properties—the kind of information you would normally need to collect using the Java debugger. Licensed to Rodrigo Urubatan Ferreira Jardim How does Tapestry work? 27 Tapestry applications consist of any number of pages, where the pages are each constructed from individual, reusable, configurable components. Table 1.1 defines the terms used to describe Tapestry applications. Your Tapestry application includes templates and specifications for these ele- ments. Those elements work together to form your GUI. 1.5.1 What’s in a Tapestry application? Like other servlet applications, Tapestry applications are most commonly distrib- uted as Web archives (WARs). A WAR is a variation of an ordinary Java archive (JAR) file. A JAR file is used to store Java class files, but a WAR file stores a mix of files: Primarily it stores HTML files, stylesheets, and images that will be directly accessible to a client web browser using a URL. Like a JAR file, a WAR file contains many files organized into many folders within the WAR. A deployed web application is also called a web application context. The default name of the context is based on the name of the WAR. Figure 1.7 dia- grams a deployed web application, the Virtual Library application described in chapters 9 and 10. The Virtual Library is deployed as vlib.war, which will define a web application context URL as /vlib. Static files (those images and stylesheets) are accessible at URLs that extend from the context URL, such as http://server/vlib/images/search.png (which accesses a file stored as images/ search.png inside vlib.war). Table 1.1 Basic terms used when describing Tapestry applications Term Description Page Applications consist of a collection of uniquely named pages; each page has a tem- plate and contains other components. Template An HTML template for a page (or a component). In Tapestry, a template contains ordi- nary HTML markup, with certain tags marked with a special attribute to indicate they are placeholders for components. Component A reusable object that may be used as part of a Tapestry page. Components generate HTML when a page is rendered and may also participate when a link or form in the rendered page is triggered. Components may also be used to construct new compo- nents. Parameter Components have parameters, which link properties of the component to properties of the page (or of domain objects accessible from the page). Components mostly read their parameters, but some components (often related to HTML forms) can update their parameters and thus update the page properties bound to the parameter. Licensed to Rodrigo Urubatan Ferreira Jardim 28 CHAPTER 1 Introducing Tapestry WARs also contain a WEB-INF folder; this includes private resources that are not made visible to clients. A servlet application will store class files and libraries inside the WEB-INF folder. Tapestry applications go further, storing additional artifacts and resources in the WEB-INF folder: ■ The compiled Java classes are stored in the WEB-INF/classes folder. ■ The WEB-INF/lib folder stores JAR files needed by the application’s classes. In a Tapestry application, the WEB-INF/lib folder will contain the Tapestry framework JAR (e.g., tapestry-3.0.jar) as well as a number of supporting libraries needed by the framework. ■ Page specifications are stored in the WEB-INF folder, with a .page extension. ■ Page templates are stored in the root context folder (that is, directly within vlib.war) with an .html extension. ■ Component specifications are stored in the WEB-INF folder with a .jwc extension. ■ Component templates are stored in the WEB-INF folder with an .html extension. ■ The WEB-INF folder also contains the web deployment descriptor, web.xml. This file defines, to the servlet container, all of the servlets and URL map- pings contained in the WAR. A Tapestry application uses only a single serv- let but still requires a deployment descriptor. Figure 1.7 The WAR file, vlib.war, defines a context, /vlib. Resources inside the WAR file can be referenced by extending the URL for the context itself. Licensed to Rodrigo Urubatan Ferreira Jardim How does Tapestry work? 29 Appendix B discusses the example web applications for this book; it includes details on how to build and deploy the examples on your own workstation. It also goes into some detail about the directory layout for the examples, and how the Ant build tool is used to compile, package, and deploy the examples. 1.5.2 Tapestry’s Model-View-Controller (MVC) pattern Perhaps the most successful design pattern in GUI development is the Model- View-Controller pattern (MVC). Most GUI frameworks, including Swing, use some variation of MVC. The MVC pattern divides up each user interaction within the application into three categories of objects: ■ Model—The Model object is used to store information that will ultimately be presented to the user (and possibly edited). These objects are often domain objects—objects that represent the specific application domain, such as a Customer, Order, or Product (in an e-commerce application). The Model should be completely independent of the GUI. In Java applica- tions, the Model is often a JavaBean. ■ View—The View object is responsible for presenting data obtained from the model in a format appropriate to the application. In a web application, a simple View may do no more than write a string into the rendered page; a very complex View may create a graphic chart based on data in the Model, and an editable View may write a text field or other form element into the response. ■ Controller—The Controller object has two functions: First, it bridges between the Model and the View, reading data from the Model and provid- ing it to the View. Second, it is responsible for interpreting user input and updating the Model in response; in a web application, the Controller will service incoming requests (including form submissions). As shown in figure 1.8, the Model and View have no direct connection; instead, the Controller mediates between the two and has the added responsibility of pro- cessing user input. The MVC pattern is useful because it emphasizes separation of concerns, a powerful concept that supports more flexible, more robust development. The Model and the View are kept entirely separate; they may be developed at dif- ferent times, by different developers, and tested independently. The Model has no knowledge that it is part of a GUI or any type of application. The Con- troller is responsible for monitoring the Model and informing the View of rel- evant changes when the Model changes. Typically, the Model includes an Licensed to Rodrigo Urubatan Ferreira Jardim 30 CHAPTER 1 Introducing Tapestry event mechanism so that any changes will be propagated, through the Con- troller, to the View. A single Model may be represented by multiple Views, either as different options or simultaneously in multiple windows. A common example is to display tabular data as both a table and a chart—that’s two dif- ferent Views of the same Model. The MVC design pattern has been battle- tested in countless applications. Using servlets as Controllers Within the realm of typical servlet applications, the MVC pattern typically takes on the form shown in figure 1.9. In a servlet application, servlets play the role of Controller. A servlet will receive a request from the client and will perform an operation on the Model, the domain-specific objects for the appli- cation. For example, the request may be a form submission, and the Control- ler servlet will update properties of the Model domain objects and store them into a database. The Controller servlet will then load up the HttpServletRequest with attributes needed by the View to render a response, in effect “pushing” informa- tion from the Model into the View. The View itself will be a JSP (or some other form of template) and can draw information from the domain objects (the Model) as it renders the response to be returned to the client. Tapestry makes use of a common variant of MVC, where the View “pulls” information out of the Model (rather than having the Controller push informa- tion into the View). The View still has no explicit knowledge of the application; it Figure 1.8 Using the Model-View- Controller pattern separates the View and the Model, which have no direct knowledge of each other. The Controller mediates between the two and handles user input. Licensed to Rodrigo Urubatan Ferreira Jardim How does Tapestry work? 31 just knows where it can read (and occasionally write) data. The Controller is still responsible for handling user input, as well as for establishing the relationships between Model and View. The component as Controller As shown in figure 1.10, a Tapestry component fills the role of Controller, mediating between pure-domain objects in the Model (the LineItem and Prod- uct objects, in this example) and components contained within its HTML tem- plate (TextField and Insert). Most often, this pattern applies to pages (pages are still Tapestry components), but in many cases, a component will have its own template, containing further components, and support its own interac- tions with the user. The page establishes relationships between the Model and the View in terms of property expressions. Property expressions are implemented using another open source framework, the Object Graph Navigation Language (OGNL), a very pow- erful Java expression language. The page exposes the Model objects to the View components by providing JavaBeans properties (such as the lineItem property in figure 1.10)—but that’s just a starting point. Properties of the Model objects are bound to the components—when the page renders, the TextField component will read the quantity property of the LineItem object, and the Insert component will read the name property of the Product object. This approach of navigating the Figure 1.9 The servlet, as Controller, receives the request. It locates and updates the domain objects, possibly reading and updating database data. The Controller servlet selects a View (a JSP) to render the response. The View draws data from the domain objects as it renders, and the final response page is sent to the client. Licensed to Rodrigo Urubatan Ferreira Jardim 32 CHAPTER 1 Introducing Tapestry object graph to reach relevant properties is possible only because the page and all the components are stateful objects—JavaBeans with properties that can be read and updated. The page, in its role as Controller, is responsible for making these connections between the domain objects (the Model) and the components (the View). Figure 1.10 Model-View-Controller in Tapestry: The page is the Controller, LineItem and Product are domain objects forming the Model, and the TextField and Insert components are the View. The TextField component is bound to the quantity property of the LineItem object (which it both reads and updates), and the Insert component is bound to the name property of the Product object. Licensed to Rodrigo Urubatan Ferreira Jardim How does Tapestry work? 33 OGNL OGNL is a separate open source project distributed with, and heavily uti- lized by, Tapestry. OGNL’s primary purpose is to read and update Java- Beans properties of objects. A basic form of OGNL expression is a chain of property names, separated with periods, as in lineItem.product.name. This OGNL expression is roughly equivalent to the Java expression get- LineItem().getProduct().getName() or getLineItem().getProd- uct().setName(), depending on whether the expression is being used to read or update a property. However, that’s just the start of what OGNL can do. It is an extremely powerful expression language, mimick- ing (and in some cases exceeding) the capabilities of Java language ex- pressions. It predates the expression language introduced with the Java Standard Tag Library (JSTL) and is both easier to use and more power- ful. OGNL is the creation of Drew Davidson and Luke Blanshard. Full documentation for OGNL, as well as source and binary downloads, is available at http://www.ognl.org. The page also fulfills its role as the Controller by providing the logic that occurs when links within the page are clicked, or when forms within the page are sub- mitted. This logic is provided in the form of listener methods, short methods implemented in the page class that are invoked by the Tapestry framework. 1.5.3 Tapestry classes The Tapestry framework consists of over 400 classes and interfaces—fortu- nately, when building your own applications with Tapestry, you’ll most often need to be concerned only with the handful of classes, interfaces, and methods shown in figure 1.11. At the root of the hierarchy is the IRender interface,10 implemented by all objects that can render HTML. Components are the main objects that can ren- der, but not the only ones: for example, static HTML from a page or component template is wrapped up as an object that implements the IRender interface. Two key interfaces: IComponent and IPage Two key interfaces are IComponent and IPage, which define Tapestry components and pages, respectively. All Tapestry code is coded against interfaces, not imple- mentations; therefore, IComponent is consistently used throughout the framework as a parameter type or return value, never AbstractComponent, an implementation 10 All Tapestry interfaces start with a leading I, as in IRender or IComponent. The only exceptions are event interfaces, such PageDetachListener. These are named as such for compatibility with Java- Beans naming conventions for event listener interfaces. Licensed to Rodrigo Urubatan Ferreira Jardim 34 CHAPTER 1 Introducing Tapestry of IComponent. The AbstractComponent class is the base class for implementing components. AbstractComponent is an abstract class; it defines but does not implement the renderComponent() method. Subclasses of AbstractComponent implement this method, producing all HTML output in Java code. Figure 1.11 Here are the key interfaces, classes, and methods provided in the Tapestry framework. Licensed to Rodrigo Urubatan Ferreira Jardim Using Spindle 35 BaseComponent extends AbstractComponent, adding initialization logic that knows how to locate and read a template. Most components you create will sub- class BaseComponent. We discuss creating components in chapters 6 and 8. In Tapestry, pages are specialized components with additional responsibili- ties; this is shown by having IPage extend IComponent, and by having BasePage subclass BaseComponent. When creating new pages, you will always create sub- classes of BasePage. Three useful interfaces: IRequestCycle, IMarkupWriter, and IEngine Most Tapestry pages and components can be coded with references to just three additional interfaces: ■ IRequestCycle—A request cycle stores information about the current request. It tracks the active page involved in the request and is used to orchestrate the rendering of a response. The request cycle may also be used to access the Servlet API objects (HttpServletRequest, HttpSession, and HttpServletResponse) in the rare event that such access is necessary. ■ IMarkupWriter—A markup writer is used to produce HTML output when a page is rendering a response. It operates much like a java.io.PrintWriter but includes additional methods useful for creating markup output (con- taining XML-style elements and attributes). ■ IEngine—The engine is a central object with several related responsibili- ties, the core around which a Tapestry application hangs. Primarily, the engine is responsible for maintaining server-side state, but it also acts as a gateway to a number of subsystems used internally by Tapestry. Most of the examples in the first few chapters are limited to just these interfaces; as we get under the hood in chapters 6 through 8, a few additional interfaces will be introduced. 1.6 Using Spindle In this book, we’ll be sticking close to the source with our many example Tapestry applications, presenting the different artifacts (the HTML templates, the XML specifications, and the Java code) in listings. If your development environment of choice is Eclipse (http://www.eclipse.org), you should check out Spindle, the Tapes- try plug-in for Eclipse. Spindle blends customized wizards and editors for Tapestry applications into the Eclipse IDE. Spindle streamlines Tapestry application devel- opment in a number of ways: Licensed to Rodrigo Urubatan Ferreira Jardim 36 CHAPTER 1 Introducing Tapestry ■ Spindle scans all types of input files for a wide variety of errors, including invalid OGNL expressions, unresolvable class names, invalid or unknown component IDs, and so forth. ■ Errors are identified in the Eclipse Tasks view. ■ Errors are indicated with markers in the editor gutter. Figure 1.12 shows an example, where some errors have been introduced into part of the example J2EE application described in chapters 9 and 10. Spindle takes Tapestry to an even higher level of productivity, because it can catch errors at build time that ordinarily aren’t caught until runtime. Like Tapes- try, Spindle is distributed as a free, open source project. Spindle is available at http://sf.net/projects/spindle and was developed by Geoff Longman. Figure 1.12 Using Spindle, errors in your templates and specifications are highlighted in the editor and listed in the Tasks view. Licensed to Rodrigo Urubatan Ferreira Jardim Summary 37 1.7 Summary Ordinary Java web applications are code heavy: Adding a new interaction (such as a link or form) to a page requires creating new classes—new servlets (or new Struts Actions) to handle the interaction. Quite a bit of cookie-cutter code must be writ- ten just to establish the context of the interaction—which server-side objects should be affected and how. You, the developer, are responsible for making a series of small decisions: what to name the new servlet, what URL pattern to map to the servlet, what to name any query parameters, and what information to store in those parameters. Additionally, you must make coordinated changes to Java code, to the JSP, and to the web deployment descriptor. This is all unavoidable, simply an offshoot of the operation-centric approach mandated by the use of servlets, and the way in which servlets within an application are weakly bound to one another. Tapestry applications flip this situation on its ear; a Tapestry application con- sists of a tiny amount of application-specific code within a web of objects, meth- ods, and properties, and uses XML specifications and OGNL expressions, not Java code, to tie it all together. Tapestry extends your reach as a developer because of all the decisions you don’t have to make and all the code you don’t have to write and test. You get to concentrate on the critical aspects of your own application without having to address all the generic concerns of structuring a servlet application. In the next chapter, we’ll put together a small Tapestry application to demon- strate concretely how the framework’s component-based approach to develop- ment really does simplify and accelerate application development. Licensed to Rodrigo Urubatan Ferreira Jardim 38 Getting started with Tapestry This chapter covers ■ Creating HTML templates, page specifications, and page classes ■ Using Tapestry components inside an HTML template ■ Creating clickable links ■ Encoding extra information into link URLs ■ Configuring Tapestry applications for deployment Licensed to Rodrigo Urubatan Ferreira Jardim Introducing the Hangman application 39 In the first chapter, we made a number of claims about what Tapestry is capable of; now it is time to start backing up those claims with hard code. Launching into a complete Java 2 Enterprise Edition (J2EE) application right here would be a bit premature; instead, we’ll start with more of a toy, an application that plays the sim- ple word game Hangman.† In effect, Hangman is a “scale model” of a real Tapestry application; it demonstrates the basic capabilities of the framework and will give you an initial sense for what developing Tapestry applications is all about. Along the way, you’ll see how to: ■ Separate business logic from presentation logic, within the Model-View- Controller (MVC) pattern (described in chapter 1) ■ Combine HTML templates, page specifications (in XML), and Java classes to form pages within the application ■ Create HTML hyperlinks that activate application logic when clicked ■ Encode custom application data into HTML hyperlinks ■ Manage server-side state information ■ Configure a Tapestry application for deployment inside a servlet container More importantly, you’ll see quite a bit about the work you don’t have to do, because the framework takes care of it for you. Appendix B covers how to obtain the source code for all the examples in the book, as well as how to build the examples on your own computer and deploy them into the Tomcat servlet container (Tomcat is an open source servlet con- tainer available from http://jakarta.apache.org/tomcat). Once Tomcat is running and you have downloaded the source code, you can launch the Hangman appli- cation by opening a web browser to http://localhost:8080/hangman1/app. 2.1 Introducing the Hangman application Hangman is a simple word game for two players, played on a piece of paper or on a chalkboard. One player selects a secret target word; the other player attempts to guess the word. To start, you draw an empty gallows. The guessing player selects a letter from the alphabet; if the letter appears in the target word, the other player writes the letter in each position of the target word that the letter appears in. Each unsuccessful guess is marked by adding a line to a stick figure † An even simpler example of a “Hello World” Tapestry application is available at http://www.manning. com/lewisship/helloworld. The helloworld.war file is pre-compiled and pre-built, containing all the necessary Tapestry libraries and deployment descriptors. It may be downloaded directly into your servlet container, Tomcat or otherwise, and accessed as http://localhost:8080/helloworld/app. Licensed to Rodrigo Urubatan Ferreira Jardim 40 CHAPTER 2 Getting started with Tapestry on the gallows: the head, torso, and then the limbs. The game is over when the word is guessed or the stick figure is completed. The Tapestry Hangman application captures all of this functionality and, at the same time, attempts to capture the classic look of playing the game by hand on a chalkboard. The user interface makes use of images to represent the letters and other artifacts of the game, to provide a “hand–scrawled” look and feel. Fig- ure 2.1 shows the middle of a game of Hangman; the player has made several wrong guesses, so parts of the stick figure are filled in, and one letter (A) has been guessed correctly so far. At this point, all we have is a general idea for the application; before we can get to the coding stage, we must formalize this general idea into something a bit more concrete—and that begins with identifying the application flow. Figure 2.1 A Tapestry Hangman game in progress. The player has successfully guessed the letter A, but has also guessed E, P, and V, which are not in the target word. An important aspect of this application is the look and feel, which should resemble a game played by hand on a chalkboard. Licensed to Rodrigo Urubatan Ferreira Jardim Introducing the Hangman application 41 2.1.1 Determining the application flow The application flow is a model of how the end user will navigate through the application. Determining the flow occurs very early in the development cycle; it is driven by the specific requirements and use cases of the application. Applica- tion flow is the most abstract model of the application; it identifies the different pages in the application and how they are connected, but rarely has to precisely identify what is on any particular page. Key aspects of the application user inter- face are discernable from the flow diagrams, such as the need for common navi- gation menus or specific links between individual pages. The flow of the Hangman application is quite simple: From the Guess page, the user makes guesses at the target word, eventually winning or losing the game. Figure 2.2 is a state diagram for this simple application; when the applica- tion is launched, the user is presented with a Start page (figure 2.4); from there, he or she can start a new game, making guesses that eventually reach either a win or a loss; from there, the player can restart the game with a new target word. From this simple description, you can see that we’ll have four distinct pages in the application: Figure 2.2 The player starts the game and makes guesses, eventually reaching the win or lose page, from which the player can start a new game (with a new word). Licensed to Rodrigo Urubatan Ferreira Jardim 42 CHAPTER 2 Getting started with Tapestry ■ Start—A welcome page to greet players before starting a new game ■ Make Guess—The main page, from which players may guess letters of the target word ■ Win—The page reached after the target word is successfully guessed ■ Lose—The page reached after players have exhausted their guesses Once the application flow has been determined, the next step is to prototype what the individual HTML pages will look like. 2.1.2 Creating page mockups Page mockups are static HTML pages that represent what the active pages from the running application will look like. These are ordinary HTML pages with placeholder values representing the content that will eventually be generated dynamically by the application. The point of creating the mockups is to give the HTML developers a chance to work out the look and feel of the application, right down to fonts, colors, and graphics, without concern for how the applica- tion will be implemented. Figure 2.3 shows the mockup for the Guess page in an HTML editor. The HTML source is shown in the upper pane, and the WYSIWYG preview appears in the lower pane. This mockup will eventually be converted for use as the Guess page’s HTML template. Page mockups should display all the features of the running application, especially such features as error messages that are included only conditionally. For example, a mockup may include a snippet for an error message: Placeholder for error message. This snippet is important for two reasons: It clearly identifies how a real error message should be displayed, and it identifies exactly where within the page the error message should be displayed. In the Guess page mockup, the Guess and Choose sections demonstrate what the page looks like in the middle of a game, with some letters of the target word filled in and several letters from the alphabet already guessed. Having clear examples of these dynamic aspects of the page will be invaluable to the Java developer when he or she is converting the mockup into a usable HTML template. It is not an absolute requirement that you create a mockup for every page in the application; often, mockups for only a handful of key pages will suffice, and Licensed to Rodrigo Urubatan Ferreira Jardim Introducing the Hangman application 43 developers can use these core mockups as templates for the remaining applica- tion pages. As you’ll see shortly, converting these HTML mockup pages into usable Tapes- try page templates requires a minimal number of unobtrusive changes. A mockup is converted into a page template by adding instrumentation: Additional tags and tag attributes are used to identify and configure Tapestry components within the template. This instrumentation is designed to be nearly invisible. Tap- estry’s approach stands in stark contrast to the use of JSPs, where the conversion Figure 2.3 The page mockup for the Guess page in an HTML editor. Licensed to Rodrigo Urubatan Ferreira Jardim 44 CHAPTER 2 Getting started with Tapestry from HTML mockup to JavaServer Page (JSP) is a one-way process. Once the HTML mockup page has been converted to a JSP, it will not preview correctly in a standard HTML editor, making all subsequent changes to the JSP that much more difficult. Within Tapestry, a page template can still be edited by an HTML developer using standard HTML editing tools; in effect, the mockups evolve into the HTML page templates yet can still be treated as mockups.1 This is an important aspect of Tapestry because late changes to application flow and look and feel are simply a reality when creating web applications— there’s always a last-minute change: a new page to add, a background color to change, or a column width to tweak. Even in an impossibly idealized project, one where no late changes ever occurred, a subsequent release of the application would inevitably update the application flow and at least some aspect of the look and feel. Tapestry accommodates these kind of late cycle changes quite well because of how unobtrusive the instrumentation (the additional tags and tag attributes used to identify components within a template) is. Much more work can be done by an HTML developer using standard HTML editing tools, without the involvement of Java developers. Meanwhile, even as the HTML developers are working on the mockups, the Java developers should be getting a head start on the design of the actual appli- cation, and that begins with identifying the domain objects. 2.1.3 Defining the domain objects The architects and developers on the Java side of the team are ultimately responsible for the running application; in most applications, this becomes a question of linking a user interface to your domain objects. Domain objects are the objects of the middle tier, the application tier, in the overall application—they are the entity objects for data stored in a database, or objects that implement your business’s specific processes. Common problems to solve involve what infor- mation is stored by these objects, how the different objects are related, and how they are read from, or stored into, a database. Even in a simple application such as Hangman, which does not make use of a database, there are still domain objects, and still advantages (in accordance with the MVC design pattern) to keeping these objects well separated from any code directly related to the user interface. 1 Tapestry isn’t magic, and there are some limitations on maintaining full WYSIWYG previews of HTML templates once more sophisticated custom components are created and used within a page template; this subject is covered in chapter 6. Licensed to Rodrigo Urubatan Ferreira Jardim Introducing the Hangman application 45 The two domain objects used in the Hangman application are WordSource and Game. The first, WordSource, is simply a wrapper around a list of words read from a text file and is used to dole out random words for the player to guess. Game is a bit more interesting; it encompasses all the logic about the game. Spe- cifically, the Game object knows: ■ The target word the player is attempting to guess ■ Which of the 26 letters of the alphabet the player has already guessed ■ Which letters of the target word have been filled in by successful guesses ■ How many incorrect guesses remain ■ If the player has won the game (by guessing all the letters in the target word) As promised, the implementation of the Game class (in listing 2.1) knows nothing about Tapestry or any other user interface. package hangman1; public class Game { private String _targetWord; private int _incorrectGuessesLeft; private char[] _letters; private boolean[] _guessed = new boolean[26]; private boolean _win; public boolean isWin() { return _win; } public char[] getLetters() { return _letters; } public int getIncorrectGuessesLeft() { return _incorrectGuessesLeft; } public boolean[] getGuessedLetters() { return _guessed; } Listing 2.1 Game.java: domain object for the Hangman application Returns true once word has been guessed Returns array of letters in the word Returns 26 flags: letters guessed by player Licensed to Rodrigo Urubatan Ferreira Jardim 46 CHAPTER 2 Getting started with Tapestry public void start(String word) { _targetWord = word; _incorrectGuessesLeft = 5; _win = false; int count = word.length(); _letters = new char[count]; for (int i = 0; i < count; i++) _letters[i] = '_'; for (int i = 0; i < 26; i++) _guessed[i] = false; } public boolean makeGuess(char letter) { char ch = Character.toLowerCase(letter); if (ch < 'a' || ch > 'z') throw new IllegalArgumentException( "Must provide an alphabetic character."); int index = ch - 'a'; if (_guessed[index]) return true; _guessed[index] = true; boolean good = false; boolean complete = true; for (int i = 0; i < _letters.length; i++) { if (_letters[i] != '_') continue; if (_targetWord.charAt(i) == ch) { good = true; _letters[i] = ch; continue; } complete = false; } if (good) Starts a new game Processes a player’s guess Licensed to Rodrigo Urubatan Ferreira Jardim Introducing the Hangman application 47 { _win = complete; return !complete; } if (_incorrectGuessesLeft == 0) { _letters = _targetWord.toCharArray(); return false; } _incorrectGuessesLeft--; return true; } } The makeGuess() method is invoked to process a player’s guess. It updates the target word and other properties and returns true if more guesses are allowed. It returns false if the player has either won or lost the game. The Game class must provide some support for the user interface, but it does so in a generic fashion without being tied to the interface; it’s the Model in the MVC pattern described in chapter 1. This support takes the form of JavaBeans properties that are exposed to the user interface, such as the number of incorrect guesses remaining or the list of letters already guessed. These properties are bound to Tapestry component parameters, allowing those components to dis- play the number of guesses remaining, the partially guessed word, or the list of remaining unguessed letters. In addition, Game provides methods that can be invoked by the user interface code to start a new game or to process a guess made by the player. A second class, WordSource, is also used. WordSource is responsible for provid- ing a random word for the player to guess. The source of the words is a small file, WordList.txt, packaged with the WordSource class. The WordSource class is pro- vided in listing 2.2. package hangman1; import java.io.IOException; import java.io.InputStream; Listing 2.2 WordSource.java: domain object for the Hangman application Processes a player’s guess Licensed to Rodrigo Urubatan Ferreira Jardim 48 CHAPTER 2 Getting started with Tapestry import java.io.InputStreamReader; import java.io.LineNumberReader; import java.io.Reader; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class WordSource { private int _nextWord; private List _words = new ArrayList(); public WordSource() { readWords(); } private void readWords() { try { InputStream in = getClass().getResourceAsStream("WordList.txt"); Reader r = new InputStreamReader(in); LineNumberReader lineReader = new LineNumberReader(r); while (true) { String line = lineReader.readLine(); if (line == null) break; if (line.startsWith("#")) continue; String word = line.trim().toLowerCase(); if (word.length() == 0) continue; _words.add(word); } lineReader.close(); } catch (IOException ex) { throw new RuntimeException( "Unable to read list of words from file WordList.txt.", Licensed to Rodrigo Urubatan Ferreira Jardim Introducing the Hangman application 49 ex); } // Randomize the word order Collections.shuffle(_words); } public String nextWord() { if (_nextWord >= _words.size()) { _nextWord = 0; Collections.shuffle(_words); } return (String) _words.get(_nextWord++); } } When WordSource is instantiated, it reads the list of words. Later, the nextWord() method is invoked to get a new word for the player to guess. The method is designed to not repeat a target word until every word in the list has been guessed. As with the Game class, this class has no direct connection to Tapestry—these objects fit firmly into the Model category within the MVC pattern. This kind of decoupling from the user interface is very important, because it means the Game and WordSource classes can be tested without having to run the Tapestry applica- tion, which in turn means the code can be fully tested inside an automated test suite. Making code testable is always a worthy goal, because no matter how sim- ple the code is, when you write tests, you find bugs. Once all the details of the domain objects are worked out, the next step is to begin work on the pages that will interact with those domain objects. 2.1.4 Defining the pages Like any other Tapestry application, the Hangman game consists of a set num- ber of pages, which are themselves composed of components. In a Tapestry application, each page is constructed by combining three related artifacts: an HTML template, a page specification, and a Java class.2 2 Refer back to section 1.5.1 to see how to properly package these artifacts for use within a servlet con- tainer. Appendix B provides examples of how to set up your development workspace and how to use Ant to build and deploy the WAR. Licensed to Rodrigo Urubatan Ferreira Jardim 50 CHAPTER 2 Getting started with Tapestry Each Tapestry page has a specific, unique name. The page name is used to locate the page specification and HTML template. Part of the page specification is the name of the Java class to instantiate; this is called the page class, and it will include properties and methods specific to your application. The Hangman application contains only four pages: Home, Guess, Lose, and Win, corresponding to the four pages identified in the application flow state dia- gram (figure 2.2). The Home page here is the same as the Start page in figure 2.2. By default, when a Tapestry application is first launched, the framework renders the page named Home. Although there are several options for changing this behavior, the simplest approach is to follow Tapestry’s naming convention—by naming the first page a user will see Home. Creating a functioning Tapestry page starts with the HTML mockup for the page. This mockup must be instrumented to act as an HTML template instead of a mockup. Instrumenting a mockup inserts additional attributes and tags in the mockup that tell Tapestry which parts of the template are dynamic compo- nents. Most of a template, however, is exactly the same as the mockup—simple, static HTML. NOTE In real projects, the mockups are not always available when needed by the Java developers creating the pages. In this situation, the Java devel- opers will create simple, minimal HTML templates—just enough to wire up the functionality of the application. When the mockup is ready, some careful cut and paste from the mockup into the minimal HTML tem- plate will convert it to use the desired application look and feel. Once the HTML template is instrumented, a page specification (a short XML docu- ment) can be created. The page specification has a number of responsibilities (many of which will be discussed in later chapters). Its most basic responsibility is to identify which Java class is to be instantiated as the page. In chapter 1, we described Tapestry as being a component object framework; this means that each component fits into an object hierarchy, either as a container of other com- ponents or as a containee of a specific component—or, in many cases, as both container and containee. Pages are still components, sitting at the root of the component object hierarchy. As you’ll see, the page class is specific to the application and contains a mix- ture of properties and methods that support both the rendering of the page and any user interaction in the page. Ultimately, the behavior of the page is defined by the page’s properties and methods, combined with the components contained Licensed to Rodrigo Urubatan Ferreira Jardim Developing the Home page 51 within the page—including the templates, properties, and methods of those components. This may seem a bit dizzying in theory, but in practice it all comes together simply and seamlessly. For our first example, let’s start with the Home page—the simplest page in the Hangman application. 2.2 Developing the Home page The Hangman application’s Home page has only one small bit of user interac- tion: a link that starts a new game. This interaction is triggered by clicking the Start image, shown in figure 2.4. Like any page, the Home page is a combination of an XML page specification, an HTML template, and a Java class. Our first steps into Tapestry will be to examine how these three artifacts are combined to form a simple page. Figure 2.4 The Home page of the Tapestry Hangman application. The player may click the word Start to begin a game. Licensed to Rodrigo Urubatan Ferreira Jardim 52 CHAPTER 2 Getting started with Tapestry The Home page is displayed when the application is first launched. The Web archive (WAR) for the application must be deployed into the servlet container, and the servlet container must itself be running. This WAR will contain the Tap- estry framework JARs, the page templates and specifications, the static image files (and other assets), and the compiled Java classes (this is discussed in chapter 1, section 1.5.1). When the user launches the application (by opening a web browser to http://localhost:8080/hangman1/app), the framework responds by rendering the Home page. The first step in rendering a page is to create an instance of the page. The framework reads the Home page’s specification and HTML template and uses this information to create the page instance. A Tapestry page is not a single object; the page object is the root of a tree of objects, including Tapestry compo- nents from the page’s template, the contents of the HTML template, and a num- ber of objects used to connect the individual pieces together. There’s no special assembly stage for Tapestry applications, nor are there any special build steps or compilation—all that is necessary is to package the specifications, templates, and Java classes inside the WAR. NOTE You might be concerned about performance, given all this talk of pars- ing specifications and templates and instantiating trees of objects—but don’t be. This parsing occurs very quickly, and, unlike with JSPs, there’s no time spent compiling generated Java source code (JSP compilation causes a noticeable delay the first time a JSP is used within a traditional servlet application). In line with Tapestry’s efficiency goal, all the speci- fications and templates are read and parsed just once, and then cached for fast access when needed again in future requests. Page instances are also stored and reused in later requests. Let’s dive a little deeper and see exactly how the Home page’s specification is used by the framework. 2.2.1 Understanding the Home page specification The framework’s first step toward instantiating the Home page is to locate and read the page’s specification. Page specifications are validated XML files (with a .page extension) that are stored in the WEB-INF folder of the web application. The page specification’s first responsibility is to identify the page class it needs to instantiate—it has other many other, optional responsibilities that we’ll cover Licensed to Rodrigo Urubatan Ferreira Jardim Developing the Home page 53 later in this chapter and in subsequent chapters. Listing 2.3 contains the com- plete specification for the Home page of the Hangman application. This is about as simple as a page specification can get; its only purpose is to identify the page class, hangman1.Home. This is a Java class written for the Hang- man application, which will be the runtime representation of the page (see sec- tion 2.2.3 for more details). By convention, the class name for a page is the same as the page’s name (though often stored inside a Java package), but of course, you are free to ignore this convention and name pages and classes differently. It’s important, however, that the declaration be exactly as shown in list- ing 2.3. WARNING Use the correct . Tapestry uses a validating XML parser to read specifications. Tapestry is purposely finicky about the public ID (the first string after PUBLIC), since it uses the known public ID to access a copy of the document type definition (DTD) inside the framework’s JAR rather than access it over the Internet using the system ID. The public ID must exactly match the value in listing 2.3, or an Application- RuntimeException is thrown. For example, changing Foundation to Floundation will result in an exception report with this error message: Doc- ument context:/WEB-INF/Home.page has an unexpected public id of ‘-//Apache Software Floundation//Tapestry Specification 3.0//EN’. Watch out for typos; this is one area where a little cut and paste will save you some grief. In addition, there is nothing that keeps a single page class from being used for multiple pages. Each page will have a distinct instance of the page class, just as each component in a page is a distinct instance of a component class. Listing 2.3 Home.page: specification for the Home page Licensed to Rodrigo Urubatan Ferreira Jardim 54 CHAPTER 2 Getting started with Tapestry 2.2.2 Rendering the Home page After parsing the page specification, Tapestry locates the HTML template for the Home page. The HTML template, which is named Home.html, is located in the root of the web application archive. This template is shown in listing 2.4. Tapestry Hangman
[Tapestry Hangman] 5 Guesses Left

Start The majority of the HTML template is standard, static HTML; only a single Tap- estry extension beyond ordinary HTML is used, showing up in the portion of the template that provides the link to start the game. Listing 2.4 Home.html: HTML template for the Home page Dynamic portion of template Licensed to Rodrigo Urubatan Ferreira Jardim Developing the Home page 55 The tag declares a Tapestry component within the template, giving us our first whiff of a dynamic web application rather than a static web page. The attribute jwcid is the indicator that Tapestry uses to identify components within the tem- plate. The name jwcid is simply Java Web Component ID. The component is type DirectLink, one of over 40 components provided with the Tapestry framework. The example here is an implicit component, where the type of component and its configuration are declared directly in the HTML template. The @ symbol indicates to Tapestry that the component is implicitly declared. Later in this chapter, we’ll show examples of declared components, which have their type and configuration stored inside the page specification. The DirectLink component is used to create a particular type of callback into the application. This component is one of the two primary ways that interaction occurs in Tapestry; the other is user-submitted forms (which are covered starting in chapter 3). The DirectLink component renders an HTML element, supplying a URL that, when clicked by the end user, causes a specific listener method of the page to be executed (we’ll discuss what a listener method is shortly, in section 2.2.3). The position of the DirectLink component within the template is delineated by the and tags. Everything else in this HTML template is static HTML—text that is sent through to the client web browser unchanged. Just the portion rendered by the DirectLink component is dynamic. Figure 2.5 shows how the dynamic and static portions of the template are integrated together to form the complete response. The Home page’s HTML template is divided into five individual “chunks.” Each chunk is either a block of static HTML, the start tag for a component (rec- ognized by Tapestry because of the presence of a jwcid attribute), or the match- ing end tag for a component. Chunk b is the portion of the HTML template that precedes the DirectLink component. Chunk c is the component itself. Chunk d is the portion of the page enclosed by the DirectLink. Chunk e is the close tag for the DirectLink component. Chunk f is the remainder of the tem- plate after the DirectLink. Chunks that are enclosed directly within a component’s start and end tags are part of that component’s body. This is a very important part of Tapestry: Com- ponents control if and when their bodies are rendered. We’ll frequently refer to the body of the component: This is the static HTML and other components that are enclosed between a component’s start and end tags. In this example, chunk d, containing the tag, is the entire body of the DirectLink component. The page itself has a body, the top-level static chunks (chunks b and f) and the components that aren’t enclosed by other components Licensed to Rodrigo Urubatan Ferreira Jardim 56 CHAPTER 2 Getting started with Tapestry (chunk c). When the page renders, it renders just the chunks in its body. Static HTML chunks render as themselves; they are passed on through to the client web browser unchanged. Components are responsible for rendering themselves and their body. Figure 2.5 references two methods related to the DirectLink component: render- Component() and renderBody(). The renderComponent() method is implemented by components that render in Java code (rather than using their own template). The method is invoked by the component’s container, in this case the Home page itself, as part of the Home page’s render. The second method, renderBody(), is inherited by the DirectLink component from the AbstractComponent base class. The component invokes this method from its own renderComponent() method to render the text and components in its own body—the static tag enclosed by the DirectLink’s and tags. In this case, the body of the DirectLink is simple, static HTML. That’s often not the case; a component may contain a mix of static HTML and other compo- nents. Tapestry figures it all out, properly slotting each chunk of the page’s tem- plate into the body of the correct component. Rendering a page is a recursive process, since components may themselves have their own templates, containing other components. Chapters 6 and 8 go into great detail about creating new components, including components that have their own template. Tapestry’s HTML template parser is very forgiving; although the examples in this book all follow Extensible HTML (XHTML) conventions, the template parser can handle the kind of HTML you’ll find in the wild: unquoted attribute values, Figure 2.5 The Home page template is broken into chunks of static HTML and component tags. Static HTML chunks render as themselves; the DirectLink renders in code, in its renderComponent() method, and causes its body (the tag) to render by invoking its renderBody() method. Licensed to Rodrigo Urubatan Ferreira Jardim Developing the Home page 57 mixed uppercase and lowercase, single or double quotes, unquoted attribute val- ues, and lots of additional whitespace. As elsewhere in Tapestry, if the parser is unable to parse a template it will throw an exception providing line-precise reporting of the problem. The last piece of the Home page puzzle is the page class; this is where we put our application-specific logic—the code that will actually start a new game. 2.2.3 Defining the Home page class So, what happens when the user clicks the link that was created when the page rendered? In Tapestry, that’s the million-dollar question,3 the point where all this talk of simplicity, consistency, and components starts to make a difference. Here’s the short answer: You tell the component about a method in your page class to execute, and it executes the method when the link is clicked. Now, let’s see what this looks like in practice. We’ll start with listing 2.5, the source code for the Home page class. package hangman1; import org.apache.tapestry.IRequestCycle; import org.apache.tapestry.html.BasePage; public class Home extends BasePage { public void start(IRequestCycle cycle) { Visit visit = (Visit)getVisit(); visit.startGame(cycle); } } A page class has many responsibilities defined by the framework, including the ability to act as a container of other components. Fortunately, the BasePage class, from which the Home class extends, contains the code needed to fulfill all these responsibilities; for the Home page, all we need to add is the little bit of application- specific logic to be executed when the Start link is clicked. That logic shows up as a method, start(), implemented by the Home page class. 3 Since Tapestry is open source, money is not the best way to gauge status. Perhaps this should be the “million download question” instead! Listing 2.5 Home.java: Java class for the Home page Licensed to Rodrigo Urubatan Ferreira Jardim 58 CHAPTER 2 Getting started with Tapestry The start() method is a listener method, a method that will be invoked in response to a user clicking a particular link. Its implementation is to defer to the Visit object to actually start a new game—we’ll discuss what the Visit object is shortly; for the moment, we’ll concentrate on how it is that the start() method is invoked when a user clicks the link. Listener methods are ordinary instance methods, implemented by the page’s class, that have a specific method signature: public void method(IRequestCycle cycle) The method must always be public, return void, and take a single parameter of type IRequestCycle. Tapestry components may have any number of parameters, both optional and required. The DirectLink component has several optional parameters and one that’s required (listener). The binding for the listener parameter was pro- vided in the Home page’s HTML template: . . . TIP Tapestry checks that there’s a binding for each required parameter. If you remove the listener attribute from the HTML template for the Home page, the page will not display. Instead, you’ll get an exception report with this message: Required parameter listener of component Home/ $DirectLink is not bound. Home/$DirectLink is the name of the page and the ID of the component. The DirectLink component’s listener parameter is used to find the listener method it should execute when the end user clicks the link visible in his or her web browser. The ognl: prefix on the attribute value informs Tapestry that the value is an Object Graph Navigation Language (OGNL) expression to be evalu- ated, rather than a literal string constant. In Tapestry terminology, the expres- sion listeners.start is bound to the DirectLink’s listener parameter. WARNING Don’t forget the ognl: prefix. If you omit the prefix, Tapestry treats the value as a string literal. Removing the prefix from the DirectLink’s lis- tener parameter will result in an error like this when you click the link: Parameter listener (listeners.start) is an instance of java.lang.String, which does not implement interface org.apache.tapestry.IActionListener. When you see Licensed to Rodrigo Urubatan Ferreira Jardim Developing the Home page 59 exceptions such as this, or perhaps ClassCastExceptions within your own code, the likely cause is a missing ognl: prefix. How does the OGNL expression listeners.start end up executing this method? All pages and components inherit a property, listeners, from the AbstractCompo- nent base class. The listeners property contains a nested property for each lis- tener method implemented by the class. Underneath the covers, there’s an interface, IActionListener, and a little bit of Java reflection used to connect the DirectLink component with the page’s listener method; this is shown in figure 2.6. A class may have any number of listener methods, each with a unique and individual name. Listener methods inherited from superclasses are also available through the listeners property. WARNING If your OGNL expression references a listener method that doesn’t exist, you’ll get an exception when you click the link. For example, changing the expression to ognl:listeners.star results in an exception with this message: Unable to resolve expression ‘listeners.star’ for hangman1.Home@ 19b808a[Home]. You’ll also see an ognl.NoSuchPropertyException for the property star. An invalid listener method will result in the same exception: This will occur if the method is not public or has the wrong method signature. Sit back and think about this for a moment: We’ve just extended the behavior of this page within the application by writing a very short method, the start() lis- tener method. The provisions we’ve made in the HTML template to get this Figure 2.6 The Tapestry servlet receives and interprets the incoming request and invokes trigger() on the DirectLink component. The DirectLink invokes the listener method provided by the page. After the method is invoked, a page is rendered, forming the HTML response sent back to the client web browser. Licensed to Rodrigo Urubatan Ferreira Jardim 60 CHAPTER 2 Getting started with Tapestry listener method to execute on cue are so minor that they’re barely worth consid- ering. The Hangman application’s Home page is unusual in that it has just the single bit of behavior—but you can imagine a more complicated page with many links (and, as you’ll see in chapter 3, forms); adding each new bit of behavior is still just…adding another listener method. This gets to the heart of the Tapestry goals described in chapter 1: ■ Simplicity—Adding new operations takes minimal code and minimal changes to the HTML template. ■ Consistency—Add as few or as many operations as you like, and the pro- cess stays the same. Look at any page in the application, and it still looks the same. ■ Feedback—By working with the framework, errors in Java code, in the template, or in the specification are detected and verbosely reported by the framework. A good practice is to keep listener methods short and focused on simply interfac- ing Tapestry components with business logic stored in domain objects. That’s demonstrated here by having the start() listener method simply find the Visit object and let it do the work of actually starting a new game. 2.2.4 Examining the Visit object The Visit object is an application-wide space for storing application logic and data. This object is accessible from all pages and components within the appli- cation and contains information specific to a single client of the web applica- tion. A single Visit object instance is shared by all pages within the application. The object fulfills much the same role as the HttpSession does in a typical serv- let application, and in fact, the Visit object is ultimately stored as an Http- Session attribute. All web applications eventually store some form of client-specific server-side state. The HttpSession acts like a map, storing named attributes. Simple as this seems, in real applications, a considerable amount of code must be written to retrieve attribute values from the HttpSession, cast them to the right type, create them on the fly as needed, and delete them when they are no longer needed. Here again, Tapestry steps in to rethink this approach in terms of objects, methods, and properties. In chapter 7, we’ll cover how Tapestry allows page properties to be stored persistently between requests, which is appropriate for values that are used only within a single page. Licensed to Rodrigo Urubatan Ferreira Jardim Developing the Home page 61 For more general data, used throughout an application, Tapestry allows for a single Visit object. Tapestry doesn’t know or care about the type of the Visit object. There is no specific Visit class defined by the framework; each applica- tion defines its own Visit class. The accessor method for the Visit object pro- vided by the page (defined by the interface IPage and implemented by the class BasePage) doesn’t specify the type of the object: public Object getVisit(); It then becomes a matter of casting to the application-specific type: Visit visit = (Visit)getVisit(); The Visit object is automatically created by the framework the first time it is ref- erenced; you must configure Tapestry, providing the name of the class to instan- tiate (this may be configured inside the web deployment descriptor; see section 2.6). Once the Visit object is created, it is stored in the HttpSession for persistent access in later requests. Developer code never has to worry about the HttpSession. The HttpSession itself is created only as needed. A stateless application is more efficient than a stateful one, and a Tapestry application will operate in a stateless mode until there is actual server-side state to store. The framework takes care of this transi- tion automatically, which would be very cumbersome to accomplish in ordinary servlet code because each and every servlet would need custom logic to check for the existence of the session and create it only as needed. For our Hangman application, the Visit object is responsible for controlling page flow. It acts as a façade around the WordSource and Game objects, handles the process of starting a new game, and processes guesses made by the player. The Visit class for the Hangman application is provided in listing 2.6. package hangman1; import org.apache.tapestry.IRequestCycle; public class Visit { private WordSource _wordSource = new WordSource(); private Game _game = new Game(); public void startGame(IRequestCycle cycle) { _game.start(_wordSource.nextWord()); Listing 2.6 Visit.java: controller object for the Hangman application Invoked by Home page listener method b Licensed to Rodrigo Urubatan Ferreira Jardim 62 CHAPTER 2 Getting started with Tapestry cycle.activate("Guess"); } public void makeGuess(IRequestCycle cycle, char ch) { if (_game.makeGuess(ch)) return; cycle.activate(_game.isWin() ? "Win" : "Lose"); } public Game getGame() { return _game; } } The startGame() method is invoked by a listener method on the Home page to start a new game. It is also invoked by listener methods on the Win and Lose pages. The makeGuess() method is invoked by a listener method on the Guess page; the listener method passes in the character to be guessed and the request cycle (so that the Visit object can activate the Win or Lose page, if necessary). The Game object is exposed as a read-only property of the Visit object. You’ll see references to the properties of the Game object in the template as ognl:visit. game.property. When the Home page invokes the startGame() method on Visit, Visit gets a random word and sets up the Game instance with it by invoking Game’s start() method. The call to activate() is used to change the active application page; the active page is responsible for rendering the response. Initially, the Home page is the active page, because it contains the DirectLink component that was triggered. Invoking the activate() method allows the correct page, the Guess page, to render the response. 2.3 Implementing the Home page using standard servlets Despite the fact that the previous discussion about the DirectLink component, listener methods, and the Visit object was unavoidably long-winded, in the end we’ve shown that creating a link and getting an application-specific method to execute when the link is clicked is extremely simple. b Invoked by Guess page listener method c Provides game property d b c d Licensed to Rodrigo Urubatan Ferreira Jardim Implementing the Home page using standard servlets 63 Let’s see what would be involved in accomplishing the same thing using stan- dard servlets and JSPs. In this simple example, the JSP is very straightforward—so much so that it could as easily be an entirely static HTML page. The DirectLink component is replaced by a standard HTML link to a servlet we’ll provide: Of course, this example is not representative. Most application operations will involve quite a bit more: more servlets to implement the operation, more query parameters to fill in the details, and more code to build and interpret the URLs—all things that the Tapestry framework provides you for free. Regardless, this example uses a very simple operation with no parameters. We still need to add a few lines to the application’s web deployment descrip- tor, web.xml: startGame StartGameServlet startGame /startGame Finally, we need the actual servlet, shown in listing 2.7. import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; public class StartGameServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session = request.getSession(true); Visit visit = new Visit(); session.setAttribute("visit", visit); Listing 2.7 StartGameServlet.java: hypothetical servlet for starting a game Gets or creates the session b Stores Visit for later c Licensed to Rodrigo Urubatan Ferreira Jardim 64 CHAPTER 2 Getting started with Tapestry visit.startGame(); RequestDispatcher d = request.getRequestDispatcher("/Guess.jsp"); d.forward(request, response); } } This accesses an existing HttpSession for the client or creates a new one if necessary. We store the Visit object as a session attribute so that it can be accessed in later requests or by a JSP. As in the Tapestry Hangman application, the hypothetical servlet Visit object is responsible for selecting a random word to guess. The RequestDispatcher object is used to bridge from the servlet to a JSP that can render the response. This servlet creates a Visit instance, similar in scope and implementation to the Visit object used in the Tapestry application. Once created, the Visit object is stored in the HttpSession, where it will be available in subsequent requests. The implementation of this Visit class may use the same Game and WordSource domain objects used by the real Tapestry application. Extending this comparison from one single interaction to the innumerable interactions in a typical web application underscores the amount of developer effort needlessly wasted on most web applications. You are forced to drop out of the world of objects and methods and deal directly with aspects of the HTTP protocol and the Servlet API. You must define a URL to trigger your operation, create a new servlet class to perform that operation, and record the mapping from the URL to the servlet in the web.xml deployment descriptor. In a team environment, you will be competing with your fellow developers to update the deployment descriptor and to lay claim to the possible URLs for the application. Certainly, as you become more experienced writing servlet-based applica- tions, you will find shortcuts to help you streamline this effort. Unfortunately, different developers are quite likely to create their own suite of shortcuts. In a large team effort, getting the bits and pieces of the application written by dif- ferent developers interoperating properly can become quite a challenge because of the impedance caused by all of the developers’ individual schemes. When using Tapestry, this is rarely an issue because Tapestry defines a standard Chooses random word to guess d Forwards to the Guess.jsp page e b c d e Licensed to Rodrigo Urubatan Ferreira Jardim Developing the Guess page 65 way for different parts of the application to interoperate—using objects, meth- ods, and properties. Now that we’ve seen how the Home page and the Hangman application’s Visit object work together to start a new game, we can continue to the Guess page, the primary page in the Hangman application. 2.4 Developing the Guess page The Guess page is the central page for the Hangman application; it allows the player to guess at letters of the target word. Figure 2.1 shows an example of the Guess page in action. The page has a number of responsibilities: ■ It displays the number of guesses remaining (as a number) as well as the number of incorrect guesses so far (as the growing stick figure). ■ It displays the partially guessed target word, with lines replacing the as-yet unguessed letters. ■ It displays a grid of remaining letters to guess; each letter is a clickable link. ■ It supports the “hand-scrawled” look and feel, using custom images to dis- play numbers and letters. To accomplish all these tasks, we’ll be introducing several new concepts for Tap- estry specifications, HTML templates, and Java classes, as well as new Tapestry components. We’ll start with the full listings for the HTML template, the page specification, and the page class, and then show how the different responsibili- ties we’ve listed are implemented—as Tapestry markup in the HTML template combined with entries in the page specification and code in the Java class. We’ll begin with listing 2.8, the HTML template for the Guess page. Tapestry Hangman Listing 2.8 Guess.html: HTML template for the Guess page Licensed to Rodrigo Urubatan Ferreira Jardim 66 CHAPTER 2 Getting started with Tapestry
Tapestry Hangman ognl:visit.game.incorrectGuessesLeft Guesses Left

Current Guess

ognl:letterLabel A______ Developing the Guess page 67 src="images/Chalkboard_5x1.png" width="36" border="0"/>


ognl:guessLabel B C D - F G H I J K L M N O P Q e f g Licensed to Rodrigo Urubatan Ferreira Jardim 68 CHAPTER 2 Getting started with Tapestry R S T U V W X - Z
This Image component selects and displays the correct image identifying the number of incorrect guesses remaining to the player. The second Image component selects and displays an image for the man on the scaffold, showing how many incorrect guesses the player has made so far. These components display the target word, with underscores marking unguessed letters within the word. This portion of the template is marked for removal (using the special $remove$ value for the jwcid attribute). The tags within the exist for WYSIWYG preview but must be removed because they conflict with the dynamic content provided by d. These components provide an array of clickable letters, allowing the player to guess the next letter in the target word. This portion of the template is also marked for removal. This page was converted directly from the HTML mockup; the bulk of the tem- plate consists of placeholder values (for the number of guesses, for the stick fig- ure, for the partially guessed word, and for the grid of guessable letters) that will actually be discarded in favor of dynamically generated HTML. We’ll go into more detail on each portion of the HTML template shortly. g b c d e f g Licensed to Rodrigo Urubatan Ferreira Jardim Developing the Guess page 69 Listing 2.9 is the page specification for the Guess page. Listing 2.9 Guess.page: specification for the Guess page b c d e Licensed to Rodrigo Urubatan Ferreira Jardim 70 CHAPTER 2 Getting started with Tapestry The element is used to declare components. The type of compo- nent and the configuration of the component’s parameters go here, in the page specification. The element defines an asset file that is stored within the web application context. This first set of assets includes the digits used to display the number of remaining incorrect guesses. These assets are given logical names that are referenced in the Java page class. The second group of elements defines the images used for the stick figure. The remaining elements define the images used for the letters of the alphabet, as well as a blank space image and the underscore image (as dash). This is a much longer specification than for the Home page, and it demonstrates a couple of new features: the ability to define the type and configuration of com- ponents in the specification rather than in the HTML template, and the ability to define assets, which are named references to static files such as images or stylesheets. Again, we’ll revisit the relevant portions of this specification shortly. Finally, listing 2.10 is the source for the Guess class: the Java class for the Guess page. package hangman1; import org.apache.tapestry.IAsset; import org.apache.tapestry.IRequestCycle; e b c d e Listing 2.10 Guess.java: Java class for the Guess page Licensed to Rodrigo Urubatan Ferreira Jardim Developing the Guess page 71 import org.apache.tapestry.html.BasePage; public class Guess extends BasePage { private char _letter; private boolean _letterGuessed; private int _guessIndex; public void initialize() { _letter = 0; _letterGuessed = false; _guessIndex = 0; } public char getLetter() { return _letter; } public void setLetter(char letter) { _letter = letter; } public String getLetterLabel() { return ("" + _letter).toUpperCase(); } public IAsset getLetterImage() { if (_letter == '_') return getAsset("dash"); return getAsset("" + _letter); } public boolean isLetterGuessed() { return _letterGuessed; } public int getGuessIndex() { return _guessIndex; } public void setLetterGuessed(boolean letterGuessed) { _letterGuessed = letterGuessed; } Resets page properties b Converts letter property to an image c Licensed to Rodrigo Urubatan Ferreira Jardim 72 CHAPTER 2 Getting started with Tapestry public void setGuessIndex(int guessIndex) { _guessIndex = guessIndex; } public IAsset getGuessImage() { if (_letterGuessed) return getAsset("space"); String name = "" + getLetterForGuessIndex(); return getAsset(name); } public char getLetterForGuessIndex() { return (char) ('a' + _guessIndex); } public String getGuessLabel() { if (_letterGuessed) return " "; char ch = Character.toUpperCase(getLetterForGuessIndex()); return new Character(ch).toString(); } public void makeGuess(IRequestCycle cycle) { Object[] parameters = cycle.getServiceParameters(); Character guess = (Character) parameters[0]; char ch = guess.charValue(); Visit visit = (Visit) getVisit(); visit.makeGuess(cycle, ch); } } This method is invoked when the page is created and at the end of each request, to reset any properties back to pristine values, ready for the next request. This method creates a read-only, synthetic property, letterImage, that provides the correct image for whatever the letter property currently is. Likewise, this guessImage property returns the correct image based on the guess- Index and letterGuessed properties. Converts guessIndex property to an image d Listener method invoked when a letter is clicked e b c d Licensed to Rodrigo Urubatan Ferreira Jardim Developing the Guess page 73 This listener method is invoked when a letter image is clicked; it exists to deter- mine the correct parameters to pass to the Visit object’s makeGuess() method. Guess is a typical Tapestry page class; it contains properties and methods that support the rendering of the page as well as listener methods activated by links on the page. 2.4.1 Displaying the remaining guesses The first dynamic bit is the part of the HTML template that displays the number of incorrect guesses remaining to the player: ognl:visit.game.incorrectGuessesLeft This snippet has an array of responsibilities: ■ It must render an HTML tag and fill in a number of attributes dynamically. ■ It must convert the incorrectGuessesLeft property of the Game object into a string, as the alt attribute. ■ It must select the correct image file to display the number of guesses left and build a URL to that file (as the src attribute). Earlier we saw how the DirectLink component on the Home page inserted an tag into the response sent to the client web browser. The Image component, another standard Tapestry component, is actually much simpler; it inserts an tag, generating the tag’s src attribute from its image parameter. Here we want it to provide the correct image (one of the hand-drawn digits) and the cor- responding alt value. NOTE To support WYSIWYG editing, the HTML template uses an tag, knowing that the component will, at runtime, render an tag. The Image component will override the src attribute in the template, which is also here just to help with the WYSIWYG preview of the template. In a Tapestry template, each component must have properly balanced start and end tags. An alternative, used here, is to include an XML-style empty tag, one e Licensed to Rodrigo Urubatan Ferreira Jardim 74 CHAPTER 2 Getting started with Tapestry that ends with />. Tapestry is flexible about attribute quoting; because the image parameter’s expression uses double quotes, the entire expression is enclosed in single quotes. WARNING Match your open and close tags. You must supply a matching close tag for each component’s start tag. Tapestry even checks that all the start tags and end tags on a page properly nest (it is forgiving for all tags that aren’t components). Changing the end of the tag from /> to just > will result in the following exception: Closing tag on line 13 is im- properly nested with tag on line 12. Tapestry matched the on line 13 with the on line 12 (before the tag) and realized that the tag hadn’t yet been closed, even though it’s a component. The first OGNL expression, visit.game.incorrectGuessesLeft, is very straight- forward; it retrieves the incorrectGuessesLeft property from the Game object (via the Visit object). The incorrectGuessesLeft property (a number) is con- verted to a string and becomes the value for the tag’s alt attribute. In the client web browser, this value becomes the tooltip for the image and is also used for accessibility (visually impaired users may have the value read to them by their computer). The other expression, for selecting the image is more complicated. It also obtains the incorrectGuessesLeft property, but then it uses that value as a parameter when invoking the getAsset() method on the page. This underscores why OGNL is so useful and powerful; without OGNL, this access and manipulation would have to occur in Java code. Using OGNL, we are able to assemble the complete string and invoke a Java method, getAsset(), on our page, all in one step. The invoked method returns the asset object representing the image to use, which is ultimately converted into a URL by the Image component and inserted in the HTML response as the src attribute of the tag. NOTE Using OGNL expressions where possible allows you to assume a rapid application development cycle, free from the normal edit/compile/de- ploy cycle that occurs with Java code. You can simply edit your tem- plates and specifications in place to see changes.4 Later, you can recode 4 It is possible to disable the normal caching that occurs inside Tapestry so that templates and specifi- cations are reread for each new request. This allows changes to templates and specifications to take effect immediately. Consult the Tapestry reference documentation, distributed with the framework, for the details. Licensed to Rodrigo Urubatan Ferreira Jardim Developing the Guess page 75 OGNL expressions as Java methods for greater application efficiency. Another good option, when not prototyping, is to move nontrivial OGNL expressions into the page specification (an example of this is shown in section 2.4.3); this results in a much improved separation of the View from the Model and application logic, which ultimately yields a more maintainable application. Tapestry allows you, as the developer, to decide how pure a separa- tion between the View and the Model you will maintain. At one extreme, the pragmatic view, you may put as much logic (in the form of OGNL ex- pressions) as you want directly into the HTML template. This pushes to- gether purely presentation-oriented aspects of the application (such as layout and fonts) with the behavioral aspects of the application (shown in this example as references to page properties, including visit and visit.game). Such an approach is perfectly acceptable for prototypes, or for small projects where a strong separation between developers isn’t realistic. Most of the examples in this book use this pragmatic approach simply because it puts related information side by side, making it easier to comprehend. At the other extreme, the purist view, your HTML template contains only placeholders for components; all details about the component con- figuration are stored outside the template, in the page specification. This is critical on larger projects, where a division can be expected be- tween the HTML developers responsible for page mockups and the Java developers responsible for converting the mockups into a working application. Minimizing how much of the application’s implementation is exposed to the HTML developers reduces the potential for conflicts between the Java developers and the HTML developers. Assets are any kind of file that may be distributed as part of the WAR; the most com- mon types of assets are images and stylesheets. The Image component’s image parameter expects an asset object (an object that implements the IAsset interface), not a string, and this pairs up with the getAsset() method, which returns just such an object. The getAsset() method is inherited from the AbstractComponent base class; it allows access to the named assets defined in the page specification. The names of the assets come from the elements in the page specification (in listing 2.9). What’s happening is a mapping from a logical name (such as x or dash) to a particular file (such as images/Chalkboard_4x6.png or images/Chalkboard_5x3.png). The assets abstraction has some other important uses related to localization and to packaging components into reusable libraries. Those uses are covered in more detail in chapters 6 and 7. Licensed to Rodrigo Urubatan Ferreira Jardim 76 CHAPTER 2 Getting started with Tapestry Defining assets in the page specification The page specification for the Guess page declares assets for the letters, digits, and underscore as well as all the images of the stick figure on the gallows. The Guess page specification includes the following lines to declare the six digits used in the user interface: WARNING Tapestry checks that a file matching the provided asset path exists.5 This check occurs when the page specification is first read and takes place regardless of whether anything ever uses the asset. Putting a typo into one of the names in the previous snippet results in the following exception: Unable to locate asset ‘digit0’ of component Guess as context:/imag- es/Challkboard_1x7.png. Here, we can see how the aliasing is useful. The letters and numbers were ini- tially drawn onto a grid, and a slicing tool was used to generate a set of individ- ual files from the cells of the grid. The filenames provided by the slicing tool are not intuitive (they are based on the position in the grid, rather the value of the image, and so are somewhat arbitrary), but the use of assets allows the code to reference them using more friendly names. Of course, we could have simply renamed the files output by the slicing tool, but by leaving the names as is, we can change the original letter grid image and then use the same slicing tool to regenerate all the images without having to go through the painful renaming process a second time. Tapestry has provided a little bit of abstraction and flexi- bility that ultimately makes the build process for this application more agile, because an annoying manual step (renaming the files) is not necessary. Assets also provide a separation of concerns, dividing the HTML developers from the Java developers. For example, an HTML developer may decide to redo the graphics for the page and use a new tool to generate the images of the dig- its—which would result in new filenames, possibly even new types (perhaps GIF or JPEG), but no change to the logical names of the assets. Either the HTML 5 This applies to the context assets defined here and the private assets we’ll discuss in chapter 6. A third asset type, the external asset, is not checked. Licensed to Rodrigo Urubatan Ferreira Jardim Developing the Guess page 77 developer or the Java developer would need to update the page specification to change the filenames, but there would be no change to the HTML template or even to the Java class (if the Java class ever accessed any assets by name). Now that we have a way of mapping from logical names to actual asset files, we still need a way to figure out which logical name, and thus, which asset, should be used when displaying the remaining guesses. Calculating the right asset Displaying the digit image is a matter of selecting the correct asset as the image parameter to the Image component. This occurs in the HTML template using an OGNL expression: image='ognl:getAsset("digit" + visit.game.incorrectGuessesLeft)' Here, OGNL has done something fairly complex: building up the name of the asset and invoking the page’s getAsset() method. There are penalties, however: This chunk of text is somewhat unwieldy and forces us to use single quotes, since the expression itself contains double quotes. Putting OGNL expressions into your template, especially expressions of this complexity, is not much better than put- ting Java scriptlets into a JSP: Such OGNL expressions strongly tie together the presentation of the page with the implementation. One option would be to move more of this logic into equivalent Java code. This can be easily accomplished by referencing a new, read-only property in the HTML template: ognl:visit.game.incorrectGuessesLeft We would then implement an accessor method for this new digitImage property in the Guess class:6 public IAsset getDigitImage() { Visit visit = (Visit)getVisit(); int guessesLeft = visit.getGame().getIncorrectGuessesLeft(); return getAsset("digit" + guessesLeft); } 6 Because this approach is only hypothetical, you won’t see this method in the Guess class in listing 2.10. Reference to the page’s digitImage property Licensed to Rodrigo Urubatan Ferreira Jardim 78 CHAPTER 2 Getting started with Tapestry Another option, which we’ll explore shortly, is to move the OGNL expression into the page specification. The decision to use OGNL expressions, Java code, or some mix of the two is left to you, according to your personal taste and the par- ticular situation. The modest runtime performance penalty for using OGNL is easily offset by increased developer productivity. Using informal component parameters If you check the description for the Image component in appendix C, you’ll see that it defines two possible parameters: a required image parameter and an optional border parameter. However, if you run the application and view the source of the page, you’ll see that the other attributes included in the tag in the template (alt, width, and height) are still present in the tag ren- dered by the Image component. How can this be? The majority of Tapestry components, including Image and DirectLink, allow informal parameters. Informal parameters are additional parameters for the com- ponent beyond those that are formally declared by the component. These addi- tional parameters are simply added to the rendered tag as additional attributes. Informal parameters can be unevaluated static values, such as for width, or expressions, such as for alt. Some informal parameters are discarded so that they don’t conflict with attributes rendered directly by the component. For exam- ple, it doesn’t matter that the template provides a value for the src attribute (in the tag for the Image component); the value in the template is discarded because the Image component will itself generate an src attribute from the asset provided in the image parameter. The src value in the template exists to support WYSIWYG previewing of the template; its value is discarded in favor of the real, dynamic URL computed on the fly in the live application. Only components that map directly to an HTML tag will accept informal parameters; each component indicates within its own component specification whether it accepts or discards informal parameters. So, when the Image component renders, it will mix and match the informal parameters with the HTML attributes it generates from formal parameters. This is a capability missing from JSP tags, where specifying an undeclared JSP tag attribute is simply an error. With JSP tags, you are limited to just the attributes explicitly declared for the tag, no more. Displaying the right stick figure image Continuing with the rest of the Guess page, the next dynamic section of the HTML template is also related to the incorrectGuessesLeft property; it is used Licensed to Rodrigo Urubatan Ferreira Jardim Developing the Guess page 79 to display one of several images for the gallows, showing increasing amounts of the stick figure as the incorrectGuessesLeft property drops toward zero. [Scaffold] Again, we use the same trick; we come up with a logical name for the image asset and map that logical name to an actual file by way of the elements in the page specification. This is a good, simple example of the MVC pattern in action; the Model in this case is the Game object and its incorrect- GuessesLeft property, but there are two Views of the data: the first as a digit, the second as the stick figure on the gallows. The remaining dynamic portions of the page are more complex and require using multiple components in concert to produce the desired output. 2.4.2 Generating the guessed word display The next section of the Guess page displays the target word the player is attempting to guess, or at least as much of the target word as the player has guessed so far. Generating this portion of the page starts with the Game object, which has a property, letters, for just this purpose. The letters property is an array of each letter of the target word as an individual character. Each unguessed letter in the target word is replaced with an underscore character. As with the previous examples, we can’t simply output the individual letters as characters. To keep the hand-scrawled look and feel, each letter must be trans- lated to the correct image. The template uses two different components to gen- erate the display: a Foreach component (which performs a kind of loop) enclosing another Image component. The two components work together to dis- play one letter after another. ognl:letterLabel Licensed to Rodrigo Urubatan Ferreira Jardim 80 CHAPTER 2 Getting started with Tapestry Looping with the Foreach component Foreach is a looping component; it iterates over the list of values provided by its source parameter7 and updates its value parameter for each value from the source before rendering its body. This is a crucial feature of Tapestry component parameters; by binding a property to a component parameter, the component is free not only to read the value of the bound property, but also to update the property as well. The Foreach component is represented in the template using a tag, which is very natural: The HTML tag is simply a container of other text and elements in a page. It doesn’t normally display anything itself, but is com- monly used in conjunction with a stylesheet to control how a portion of a page is rendered. Although the Foreach’s location in the template is specified using a tag, when it renders, it does not produce any HTML directly; it simply renders the text and components in its body repeatedly. The sequence is shown in figure 2.7. So, the Foreach component will render its body many times, but that doesn’t help the Image component display the correct letter image. Just before the Foreach renders its body (on each pass through the loop), it sets a property of the page to the next letter in the word (from the array of characters provided by the Game object). The trick is to convert this letter into the correct image. The Guess page class includes a property, letter, which is bound to the Foreach compo- nent’s value parameter so that it can be updated by the Foreach: private char _letter; public char getLetter() { return _letter; } public void setLetter(char letter) { _letter = letter; } 7 The Foreach component is flexible about how it defines “a list of values.” It may be an array of objects, or a java.util.List, or even a single object (which is treated like an array of one object). Licensed to Rodrigo Urubatan Ferreira Jardim Developing the Guess page 81 NOTE In chapter 3, we’ll see how Tapestry can automatically create properties at runtime (and the benefits of doing so beyond less typing). For now, we’ll mechanically code these properties ourselves by supplying the in- stance variable and pair of accessor methods. Figure 2.7 The Foreach component reads a list of values bound to its source parameter from a domain object (which is often the page that contains the component). For each item in the list, it updates a domain object property bound to its value parameter, and then renders its body. Components within its body can get the value from the domain object property. Licensed to Rodrigo Urubatan Ferreira Jardim 82 CHAPTER 2 Getting started with Tapestry Translating letters to images Once again, we are using assets to obtain the correct image to display within the page. The assets for the letters a through z are named, simply, a through z. How- ever, there’s a gotcha for the underscore character; its asset name is dash. The Guess page class implements another method to provide the asset to display: public IAsset getLetterImage() { if (_letter == '_') return getAsset("dash"); return getAsset("" + _letter); } This simple method captures the special rule about replacing the underscore character with the asset named dash. The Foreach component is responsible for invoking setLetter() with the correct letter well before getLetterImage() is invoked by the Image component. NOTE Because this method is public and follows the naming convention for a JavaBeans property, it can be referenced in the HTML template as ognl:letterImage. This is a common approach in Tapestry—creating synthetic properties, properties that are computed on the fly, rather than just exposing a value in an instance variable. The letters in the list (provided by the Game object) are all lowercase, but the tool- tip (generated from the tag’s alt attribute) looks better if the letter is uppercase. This is another, minor example of the Controller (the page) mediat- ing between the Model (the Game object) and the View (the Image component within the HTML template). This case conversion is accomplished by binding the value for the alt parameter to the letterLabel property of the page. The getLetterLabel() accessor method simply converts the letter to uppercase and returns it as a string: public String getLetterLabel() { char upper = Character.toUpperCase(_letter); return new Character(upper).toString(); } Removing unwanted portions of the template If you examine the complete HTML template in listing 2.4, you’ll see that just after the tag for the Foreach component is a long chunk of additional Licensed to Rodrigo Urubatan Ferreira Jardim Developing the Guess page 83 images—images for additional letters from the target word, as dashes. These images were copied over from the original HTML mockup and are left in place so that the HTML template will still preview properly. Without these additional images, the target word will appear as a single underscore, which may not be enough to validate the layout of the page. At the same time, these extra images must not be included in a live, rendered page or the target word will appear to be six letters longer than it actually is. Earlier, you saw that Tapestry will drop unwanted HTML attributes that are provided in HTML tags to support WYSIWYG preview. This is a larger case, where an entire section of HTML is dropped. The block to be removed is surrounded by a tag: . . . The special component ID, "$remove$", is the trigger for Tapestry’s template parser that this portion of the HTML template should be discarded. This is a second aspect of instrumenting an HTML mockup into an HTML template: marking portions of the mockup for removal, yet leaving them in for preview- ing purposes. So far on this page, we’ve covered just output-only behaviors: displaying the right digit image, or the right letter from the target word. The most involved part of the page comes next—the part that allows players to select letters to guess. 2.4.3 Selecting guesses This portion of the page is a grid of letters that the player may click on to make guesses. As usual, the letters are represented as images, to keep with the hand- scrawled look and feel. As the player makes guesses, the guessed letter is erased, and one or more positions in the target word are filled in or another segment is added to the stick figure. To accomplish this, we’ll use a combination of components: another Foreach to iterate over the different letters of the alphabet, a DirectLink to create a link, and an Image to display either the image for the letter or the image for a blank space for an already guessed letter. The three components appear in the HTML template: 84 CHAPTER 2 Getting started with Tapestry image="ognl:guessImage" alt="ognl:guessLabel" height="36" src="images/Chalkboard_5x3.png" width="36" border="0"/> Two of these components look a little sparse compared to previous examples; that’s because we’ve chosen to use the declared component option for them rather than configure them in-place as implicit components. For a declared com- ponent, we just put the component ID in the HTML template. Tapestry recog- nizes that the value for the first jwcid attribute is just an ID and not a component type, because it does not contain the @ character (as the previous usages of components have done). For a declared component, the element in the HTML template is simply a placeholder; the jwcid attribute provides a compo- nent ID that is used to link to a element in the page specification. The type and configuration of the component is provided in the page specifica- tion itself:8 The information that goes into the specification is the same as what would be put directly into the HTML template, but the format is slightly different. In the HTML template, we must mark OGNL expressions with the ognl: prefix; but in the XML we have a specific element, , that is always an OGNL expres- sion (other elements are used for literal strings and other variations). There is no difference to Tapestry whether a component is declared in the specification or in 8 The template may still specify additional formal and informal parameters. In keeping with the goal to provide the clearest separation of presentation and logic, the informal parameters, which are most often related purely to presentation, should go in the template, and the formal parameters, which are most often related to the behavior of the component, should go in the page specification. Licensed to Rodrigo Urubatan Ferreira Jardim Developing the Guess page 85 the HTML template; here, the sheer number of parameters for the two compo- nents indicated that specification was a better home for the component configu- ration than the HTML template. WARNING Mistakenly using the ognl: prefix inside a page or component specifica- tion will create an OGNL expression that is invalid. You’ll see an exception, such as Unable to parse expression ‘ognl:visit.game.guessedLetters’. The fact that the ognl: prefix shows up in the exception message as part of the expres- sion is the indicator that you included the prefix where it is not allowed. Once again we are combining the behaviors of different components and using the page to mediate between them. We are also making use of new features of the Foreach and DirectLink components by binding additional parameters of the components. Getting the images for the letters The source of all this data is the guessedLetters property of the Game object; this is an array of 26 flags, one for each letter in the alphabet. Initially, all the flags are false, but as the player makes guesses, the corresponding flags are set to true. The Foreach component will loop through the 26 flags and set the letter- Guessed property of the page to true or false on each pass through the loop. In addition, binding the index parameter of the Foreach component directs it to set the guessIndex property of the page. This value starts at zero and increments with each pass through the loop. The other components simply translate from this ordinal value to a letter in the range of a to z. This functionality is imple- mented by additional properties and methods in the Game class, as shown in the following snippet: private boolean _letterGuessed; private int _guessIndex; public boolean isLetterGuessed() { return _letterGuessed; } public void setLetterGuessed(boolean letterGuessed) { _letterGuessed = letterGuessed; } public int getGuessIndex() Licensed to Rodrigo Urubatan Ferreira Jardim 86 CHAPTER 2 Getting started with Tapestry { return _guessIndex; } public void setGuessIndex(int guessIndex) { _guessIndex = guessIndex; } public char getLetterForGuessIndex() { return (char) ('a' + _guessIndex); } Getting the right letter image for the current letter within the loop is very similar to the previous examples. Although the dash will never occur, we do have to sub- stitute a blank image for any letter that has already been guessed: public IAsset getGuessImage() { if (_letterGuessed) return getAsset("space"); String name = "" + getLetterForGuessIndex() return getAsset(name); } That covers how we get the image for each letter display, but what about the link that the player uses to make a guess? Handling the links for guesses Were we to display the link for guesses using ordinary servlets, we’d define a query parameter whose value is the letter selected; that is, we would encode the letter into the URL. Since we’re using Tapestry, we don’t want to think in terms of query parameters, but instead, we want to think of objects and properties—but we still want the URL to carry this piece of information. When we render the link, we know which letter the link is for, and when the link is clicked, we need that information back. In Tapestry terms, we need to invoke a specific listener method (as before on the Home page) but also propagate along some additional data: the letter selected by the player. We’ll use a DirectLink component, as we did with the link on the Home page, but with two differences. First, we only want to display the link itself (the and tags) some of the time; we want to omit the link for letters that have already been guessed (the positions that show up as blank space), because letters may Licensed to Rodrigo Urubatan Ferreira Jardim Developing the Guess page 87 only be guessed a single time. Second, we need a way to know which letter has been selected. The DirectLink component includes formal parameters to satisfy both of these needs. The disabled parameter is used to control whether the link renders the and tags. The disabled parameter is optional, and by default, the link is enabled. A DirectLink component will always render its body, regardless of the setting of the disabled parameter. The Guess page binds the disabled parame- ter to the letterGuessed property of the page—the same property that is set by the Foreach component and used in the getGuessImage() method: This ensures that once a letter has been guessed, there will not be another link for that letter. Shortly, we’ll see how we also ensure that the guessed letter is replaced by a blank space. Once again, we are working at the level of objects and properties, and not treating all of this HTML rendering as just a text processing problem. A common, ugly “JSP-ism” is to use embedded scriptlets to avoid writ- ing the open and close tags, wrapping the and tags inside conditional blocks, which can be a messy affair. The DirectLink embodies the Tapestry philosophy, solving a similar problem using JavaBeans properties and Tapestry component parameters. Every compo- nent decides, in its own code, whether to render; the DirectLink has a small con- ditional statement to control whether an element is rendered—but that’s Java code in a Java file, not cluttering up a JSP. The end result is a cleaner, sim- pler, easier-to-use solution. To identify which letter is actually clicked by the player, we will use yet another component parameter, named parameters. We can bind a single value, or an array, or a java.util.List to the parameters parameter, which, like the disabled parameter, is optional (we didn’t use it before with the Start link on the Home page). The collection of values provided by the parameters parameter is recorded into the URL constructed when the DirectLink component renders. When the link is submitted, the array of parameters is reconstructed and is avail- able to the listener method. For this case, we use a single value, provided by the property letterForGuessIndex: Each time the DirectLink component renders, within the Foreach component loop, the value for the letterForGuessIndex property will reflect the current letter Licensed to Rodrigo Urubatan Ferreira Jardim 88 CHAPTER 2 Getting started with Tapestry in the loop and the URL written into the HTML response will be different, as a portion of the URL will be an encoding of the letterForGuessIndex property. When the link is clicked, the listener method can get the parameters back: public void makeGuess(IRequestCycle cycle) { Object[] parameters = cycle.getServiceParmeters(); Character guess = (Character) parameters [0]; char ch = guess.charValue(); Visit visit = (Visit) getVisit(); visit.makeGuess(cycle, ch); } The parameters encoded into the URL by the DirectLink are available in the lis- tener method as an array of object instances, which can be obtained from the getServiceParameters() method of the IRequestCycle object. Even when, as in this case, there’s only a single parameter value, an array is returned. The lone character value is the first and only element in the array. In addition, the value has been converted from a scalar type, char, to a wrap- per object type, Character, but it is a simple chore to convert it back. The param- eter value is not simply converted to a string; it retains its original type (which is encoded into the URL along with the value). You can see a bit of this in the web browser’s Address field in figure 2.1; the URL shown contains much information used by Tapestry, but at the end is cp, an encoding of character p (the player had just clicked the letter P). Chapter 7 discusses how Tapestry encodes information into URLs. From here, it’s simply a matter of obtaining the Visit object and letting it do the rest of the processing of the player’s guess, which may result in a win or a loss or more guessing. Because we pass the request cycle to the Visit, this object is fully capable of selecting which page will render the response by invoking the activate() method on the request cycle. Adding this new interaction, the handling of guesses by the player, involved little more than creating the new listener method and pointing the DirectLink component at the method. Without Tapestry, this same functionality would entail not only writing a servlet and registering it into the web deployment descriptor, but also creating code to generate the hyperlink in the first place. This latter code could take the form of Java scriptlets in the JSP, or a new JSP tag in a JSP tag library. In either case, the HTML in the JSP file would deviate Licensed to Rodrigo Urubatan Ferreira Jardim Configuring the web.xml deployment descriptor 89 further from ordinary HTML, and the ability to preview the web page would be diminished. With Tapestry, the HTML template will continue to look and act like standard HTML. Instead, we are making use of existing components, the DirectLink, and a consistent approach to encoding data into the URL. Once again, we’re seeing the consistency goal: Anywhere in the application where we have a link that needs to pass along some data in the URL, we can use and reuse the same tool, the DirectLink component and its parameters parameter. In addition, because Tap- estry properly encodes the data type with the data (rather than just converting all the parameters to strings), we can consistently pass any type of data in the URL: strings, characters, numbers, or even custom objects. That wraps up the Guess page; we’ve discussed how to extract information from the Game domain object and present it in various ways and also figured out how to react to user input. We’ve kept the domain logic (in the Game and Word- Source objects) separate from the presentation logic (the Guess page class, HTML template, and page specification), using listener methods and the Visit object as the bridge between the two aspects. 2.5 Developing the Win and Lose pages The other two pages in the application, Win and Lose, are displayed when the player successfully guesses the word, or when the player exhausts all his or her incorrect guesses. There is nothing new on these pages; they duplicate bits and pieces of the Home and Guess pages. In fact, there’s a bit of unwanted duplica- tion in the HTML templates: the Java code and the page specifications. In chapter 6 we’ll see how easy it is to create new components that encapsulate this functionality and remove this duplication. Remember: More code means more bugs! Our Hangman application is nearly complete; all that’s left is to fulfill our contract with the servlet container and create a deployment descriptor for the Hangman application WAR. 2.6 Configuring the web.xml deployment descriptor All of these HTML templates and page specifications do not automatically become a web application. We still need a servlet to act as the bridge between the Servlet API and the Tapestry framework. Fortunately, this does not require any coding, since the framework includes the necessary servlet class. All that’s neces- Licensed to Rodrigo Urubatan Ferreira Jardim 90 CHAPTER 2 Getting started with Tapestry sary is to configure the web deployment descriptor, which is the file WEB-INF/ web.xml. The deployment descriptor is provided in listing 2.11. hangman org.apache.tapestry.ApplicationServlet org.apache.tapestry.visit-class hangman1.Visit 1 hangman /app The deployment descriptor maps an instance of the Tapestry ApplicationServlet to the path /app within the servlet context. Using /app as the servlet path is a common convention for Tapestry applications but not a requirement; Tapestry will adapt to whatever servlet mapping is actually used. The servlet context’s name is based on the name of the web application archive (the WAR file), which is hangman1.war; therefore, the complete URL of the servlet is http://localhost:8080/hangman1/app. This means that the base URL for the application is http://localhost:8080/hangman1/, which is why relative URLs (in static HTML) to assets such as images/guess.png work, both when pre- viewing the HTML template and at runtime. The org.apache.tapestry.visit-class initial parameter is used to tell Tapes- try what class to instantiate as the Visit object. Using the element in the deployment descriptor is recom- mended, especially during development. Loading on startup causes the applica- tion servlet to be instantiated and initialized. Often, problems in the application or Listing 2.11 web.xml: web deployment descriptor for the Hangman application Licensed to Rodrigo Urubatan Ferreira Jardim Summary 91 deployment descriptor will be detected immediately at startup; this is an even bet- ter idea for more advanced applications that use an application specification.9 2.7 Summary In this chapter, we’ve seen the basics of creating a web application using Tapes- try. A Tapestry application is divided into individual pages; those pages are con- structed by combining components, an overall HTML template, and a small amount of Java code. Tapestry leverages the Model-View-Controller pattern to isolate domain logic from the user interface. We’ve also begun to see the “light touch” of Tapestry, where simple properties and short Java methods are woven together to create very complex, dynamic, interactive user interfaces. This simple application demonstrates some of the key patterns that occur when developing in Tapestry. It shows how components interact with each other by reading and setting properties. It shows how the page can act as a Controller, coordinating the domain logic and mediating between its embedded compo- nents. We’ve also demonstrated how easy it is to add new interactions to a page, in the form of listener methods. We’ve begun to demonstrate how Tapestry, by excusing developers from mun- dane “plumbing” tasks, really frees up developer energies. It enables you to implement more complicated behaviors in much less time and be more confi- dent that your code is bug free. Tapestry can give projects the one thing money truly can’t buy: time—time to test and debug back-end code, time to locate and fix performance problems, even time to add new features. 9 Application specifications are an optional file described in chapter 6. They are needed only to access some advanced feature of Tapestry, such as referencing a component library. Licensed to Rodrigo Urubatan Ferreira Jardim 92 Tapestry and HTML forms This chapter covers ■ How HTML forms work ■ Creating simple forms using Tapestry components ■ Creating page properties using property specifications ■ Creating forms containing loops Licensed to Rodrigo Urubatan Ferreira Jardim Understanding HTML forms 93 Handling links and images is a good start, but the lifeblood of any real web appli- cation is the HTML form. Individual links, as in the Hangman example from chap- ter 2, provide a limited form of interactivity: Users will click a link, or they won’t. HTML forms provide a rich form of expression for the user—the ability to enter specific values in fields, whether by typing in a text field, clicking a check box, or selecting an option from a drop-down list. Tapestry’s approach to handling HTML forms follows its general philosophy of hiding the details of HTTP and the Servlet API and operating in terms of objects, methods, and properties. In Tapestry, a group of components work together when a form is submitted to update page properties before invoking a listener method. In this chapter, we’ll cover components for creating basic HTML form ele- ments, including text fields, radio buttons, and check boxes. In chapter 4, we’ll examine more advanced form-related components that include additional cli- ent- and server-side support. Chapter 5 discusses Tapestry components devoted to validating user input. The source for the examples for this chapter and the following chapters is available online (see appendix B). Once you deploy the samples into Tomcat, you can access them by opening a web browser to http://localhost:8080/examples/ app. You will find a listing of the examples, organized by chapter. 3.1 Understanding HTML forms HTML forms allow us to gather complex information from users of our web applications. The use of forms is ubiquitous on the Web. Without support for forms, the Web would never have exploded; limited to just hyperlinks, it would likely have stayed as passive, static hypertext—an interesting toy, but with no possibility of creating an Amazon or an eBay. Without forms, there would be no way to build any of the interesting e-commerce, content management, or com- munity sites. HTML forms are significantly different from forms within a desktop applica- tion. In a desktop application, each component (text field, check box, push but- ton) interacts with the application completely independently of the others. The user may update one field without changing the value for another field. The code for a desktop application is informed of every key press or mouse click, a luxury not possible with HTML forms. HTML forms are displayed to the user, who may use the mouse or the Tab key to move between the form controls and enter values. When the user clicks the submit button, the values for all of the Licensed to Rodrigo Urubatan Ferreira Jardim 94 CHAPTER 3 Tapestry and HTML forms fields and controls within the form are packaged together in a single request to the server. The server sees only the final result, not the individual mouse move- ments and key presses, or even the individual changes to fields. Within an HTML form, various HTML elements work together. The outer- most element, , groups together the many other elements it encloses between its start and end tags. Other elements within are used to create types of form controls; these are listed in table 3.1. Each of these form control elements has a name attribute. When the form is sub- mitted, the value for each control is submitted as a query parameter using the control’s name. Figure 3.1 shows how this comes together; when the page con- taining the form is rendered, a name is assigned to each element. Once the form is submitted, these values are provided in the form submission and available through the Servlet API’s HttpServletRequest object. Figure 3.1 shows a single form; beginning at the top center, the form starts as HTML: and tags. The client web browser uses these to construct form controls (radio buttons, text fields, and so forth) for the user to directly interact with. When the user fin- ishes entering information and clicks the submit button, the request includes the values for those fields as query parameter values. Within the Servlet API, those query parameters are accessible via the HttpServletRequest object. Table 3.1 Elements used to create form controls HTML element Control type Check box (toggles on or off) Radio selection (user selects one value from a list of values) Simple text input field Password field (text field where user input is obscured) Hidden field (not visible to the user) Form submit button; sends the request to the server Reset button; returns all fields to initial values Image map field; submits form and identifies where, within the image, the user clicked File upload