Red Hat Web Application Framework 6.1 User Manual

Red Hat Web Application
Framework 6.1
WAF Developer Guide
Red Hat Web Application Framework 6.1: WAF Developer Guide
Copyright © 2004 by Red Hat, Inc.
Red Hat, Inc.
1801 Varsity Drive Raleigh NC 27606-2072 USA Phone: +1 919 754 3700 Phone: 888 733 4281 Fax: +1 919 754 3701 PO Box 13588 Research Triangle Park NC 27709 USA
rhea-dg-waf-en(EN)-6.1-Print-RHI(2004-03-29-T16:20-0800) Copyright © 2004 by Red Hat, Inc. This material may be distributedonly subject to the terms and conditions set forth in the Open Publication License, V1.0 or later (the latest version is presentlyavailableat http://www.opencontent.org/openpub/). Distribution of substantively modified versions of this document is prohibitedwithout the explicit permission of the copyright holder. Distribution of the work or derivative of the work in any standard(paper) book form for commercial purposes is prohibited unless prior permission is obtained from the copyright holder. Red Hat, Red Hat Network, the Red Hat "ShadowMan" logo, RPM, Maximum RPM, the RPM logo, Linux Library,
PowerTools,Linux Undercover, RHmember, RHmember More, Rough Cuts, Rawhide and all Red Hat-based trademarks and logos are trademarks or registered trademarks of Red Hat, Inc. in the United States and other countries. Linux is a registered trademark of Linus Torvalds. Motif and UNIX are registeredtrademarks of The Open Group. Intel and Pentium are registered trademarksof Intel Corporation. Itanium and Celeron are trademarksof Intel Corporation. AMD, Opteron, Athlon, Duron, and K6 are registered trademarks of Advanced Micro Devices, Inc. Netscape is a registeredtrademark of Netscape Communications Corporation in the United States and other countries. Java and Swing are trademarks or registered trademarks of Sun Microsystems, Inc. in the U.S. or other countries. Oracle is a registered trademark, and Oracle8i, Oracle9i, and interMedia are trademarks or registeredtrademarks of Oracle Corporation. Microsoft and Windows are either registered trademarksor trademarks of Microsoft Corporation in the United States and/or other countries. SSH and Secure Shell are trademarks of SSH CommunicationsSecurity, Inc. FireWire is a trademark of Apple Computer Corporation. IBM, AS/400, OS/400, RS/6000, S/390, and zSeries are registeredtrademarks of International Business Machines Corporation. eServer, iSeries, and pSeries are trademarks of International Business Machines Corporation. All other trademarks and copyrights referredto are the property of their respectiveowners.
The GPG fingerprint of the security@redhat.comkey is: CA 20 86 86 2B D6 9D FC 65 F6 EC C4 21 91 80 CD DB 42 A6 0E
Table of Contents
Introduction to the WAF Developer Guide........................................................................................i
1. Assumptions About WAF Developers...................................................................................i
2. Document Conventions..........................................................................................................i
3. Code Presentation Conventions ........................................................................................... iv
I. WAF Concepts ..................................................................................................................................i
1. WAF Overview .....................................................................................................................1
1.1. General Architecture ..............................................................................................2
1.2. Features.................................................................................................................. 3
1.3. Applications ........................................................................................................... 6
2. WAF Component: Persistence .............................................................................................. 9
2.1. Persistence Overview............................................................................................. 9
2.2. Object-Relational Mapping.................................................................................... 9
2.3. Persistence Definition Language (PDL) ................................................................9
2.4. Persistence and Domain APIs (DataObject, DataAssociation, DataCollection,
DomainObjects) ..............................................................................................10
2.5. Session and Transaction Management .................................................................10
3. WAF Component: Kernel....................................................................................................11
3.1. Users and Groups.................................................................................................11
3.2. ACSObject ........................................................................................................... 11
3.3. Permissions .......................................................................................................... 11
3.4. Kernel Resources .................................................................................................12
4. WAF Component: Services.................................................................................................13
4.1. Auditing Service ..................................................................................................13
4.2. Categorization Service ......................................................................................... 13
4.3. Formbuilder Service.............................................................................................14
4.4. Globalization Service ...........................................................................................15
4.5. Mail Service .........................................................................................................16
4.6. Messaging Service ...............................................................................................16
4.7. Notification Service .............................................................................................17
4.8. Portal Service ....................................................................................................... 17
4.9. Search Service...................................................................................................... 17
4.10. Workflow Service...............................................................................................17
4.11. Versioning Service .............................................................................................18
5. WAF Component: Presentation...........................................................................................19
5.1. Overview of Presentation Standards ....................................................................19
5.2. CSS and XSLT.....................................................................................................20
5.3. JavaServer Pages (JSP) ........................................................................................ 24
5.4. Bebop - Reusable Web UI Components .............................................................. 25
6. WAF Component: Web....................................................................................................... 31
6.1. Applications ......................................................................................................... 31
6.2. BaseServlet......................................................................................................31
6.3. Dispatcher ............................................................................................................32
II. Equipping Developers.................................................................................................................. 33
7. Developing with WAF ........................................................................................................ 35
7.1. Developer Education............................................................................................ 35
7.2. Third-Party Development Tools........................................................................... 35
7.3. Developer Support ...............................................................................................36
7.4. Setting Up Eclipse and WAF ...............................................................................38
7.5. Using logging for debugging ............................................................................... 42
8. WAF Application Development Tutorial ............................................................................49
8.1. Terms and Assumptions....................................................................................... 49
8.2. Good Practices ..................................................................................................... 49
8.3. Directories in the Work Area ............................................................................... 49
8.4. Build Tools...........................................................................................................50
8.5. Modeling Your Application ................................................................................. 50
8.6. Persistent Object Types........................................................................................ 51
8.7. Java Domain Objects ........................................................................................... 51
8.8. Initializing the Application Runtime Environment.............................................. 57
8.9. Creating a Web Interface......................................................................................58
8.10. Integrating Your Package With CCM Tools ...................................................... 59
8.11. Creating Upgrade Scripts ...................................................................................60
9. Persistence Tutorial.............................................................................................................63
9.1. Data Objects Tutorial ...........................................................................................63
9.2. Beginning With Data Objects .............................................................................. 63
9.3. Associations ......................................................................................................... 70
9.4. Named SQL Events.............................................................................................. 74
9.5. Filtering, Ordering, and Binding Parameters.......................................................79
9.6. Common Mistakes ............................................................................................... 88
9.7. Transaction Management.....................................................................................90
9.8. Link Attributes ..................................................................................................... 91
9.9. Dynamic Object Types.........................................................................................96
9.10. Frequently Asked Questions ..............................................................................97
10. Kernel Tutorial................................................................................................................103
10.1. Permissions Tutorial ........................................................................................ 103
10.2. Domain Objects Tutorial..................................................................................106
10.3. Security Service FAQ....................................................................................... 118
10.4. Extending the Authentication System.............................................................. 121
11. Services Tutorials............................................................................................................123
11.1. Categorization Tutorial ....................................................................................123
11.2. Categorization Scenarios .................................................................................126
11.3. Notification Tutorial......................................................................................... 132
11.4. Workflow Tutorial............................................................................................ 135
11.5. Versioning Tutorial .......................................................................................... 138
11.6. Search Tutorial.................................................................................................146
12. Presentation (Bebop) Tutorial ......................................................................................... 153
12.1. Calling XSLT from a WAF Application .......................................................... 153
12.2. Handling Pre-Formatted Text........................................................................... 153
12.3. Site-Wide Master Pages ................................................................................... 154
12.4. Varying a Shared Layout.................................................................................. 158
12.5. Special-Case Stylesheets.................................................................................. 159
12.6. UI Tutorial........................................................................................................160
12.7. Working With Formbuilder.............................................................................. 161
13. Web Applications Tutorial.............................................................................................. 165
13.1. Support for Globalization ................................................................................165
13.2. Locale Negotiation........................................................................................... 165
13.3. ResourceBundles and MessageCatalogs .......................................................... 166
13.4. Accessing Resources........................................................................................ 167
13.5. Globalization and Bebop.................................................................................. 169
13.6. Localizing Stylesheets ..................................................................................... 170
13.7. Sending Mail Messages ...................................................................................170
14. References..................................................................................................................................173
III. Appendixes................................................................................................................................ 175
A. Bebop Tag Library Reference..........................................................................................177
A.1. Bebop/JSP .........................................................................................................177
A.2. Available Page Definition Tags .........................................................................177
B. PL/SQL Standards............................................................................................................181
B.1. General .............................................................................................................. 181
B.2. Coding Standards .............................................................................................. 181
B.3. Coding Style......................................................................................................183
B.4. Constraint Naming Standards............................................................................ 183
C. Java Standards .................................................................................................................. 187
C.1. WAF Standards..................................................................................................187
C.2. Java Coding Standards — References and Related Reading ............................188
D. PDL Syntax...................................................................................................................... 189
D.1. PDL Grammar................................................................................................... 189
D.2. PDL Reserved Words........................................................................................191
D.3. PDL and SQL Used in the Tutorial...................................................................194
Persistence Glossary ............................................................................................................. 203
Index................................................................................................................................................. 209
Colophon.......................................................................................................................................... 215
Introduction to the WAF Developer Guide
The Red Hat Web Application Framework is a platform for writing database-backed web applications in Sun’s Java®. Applications leverage Web Application Framework APIs to enable the authoring of persistent structured data and to retrieve and display the data as content. The framework also integrates services such as search, versioning, and permissions into its basic objects, enabling applications to leverage framework services with little or no extra work.
The Web Application Framework domain layer models basic concepts such as users, groups, and permissions and has been proven and refined on hundreds of production deployments. A user interface (UI) framework, UI component library designed for the rapid development and reuse of web user interfaces, and a powerful object-relational persistence engine are also part of the framework.
Please enjoy this guide and report any bugs with the documentation to http://bugzilla.redhat.com/. Please report using the unique component name for this guide:
rhea-dg-waf-en(EN)-6.1-Print-RHI (2004-03-29-T16:20-0800)
1. Assumptions About WAF Developers
This manual assumes that the reader is familiar with the Java programming language, HTML, and relational databases. Familiarity with the J2EE Servlet and JSP specifications, XML, and XSLT are also helpful. An understanding of the UML and basic object-relational mapping concepts will help the reader understand the persistence system. For more information, see Section 7.1 Developer Education.
2. Document Conventions
When you read this manual, certain words are represented in different fonts, typefaces, sizes, and weights. This highlighting is systematic; different words are represented in the same style to indicate their inclusion in a specific category. The types of words that are represented this way include the following:
command
Linux commands (and other operating system commands, when used) are represented this way. This style should indicate to you that you can type the word or phrase on the command line and press [Enter] to invoke a command. Sometimes a command contains words that would be displayed in a different style on their own (such as file names). In these cases, they are considered to be part of the command, so the entire phrase is displayed as a command. For example:
Use the cat testfile command to view the contents of a file, named testfile, in the current working directory.
file name
File names, directory names, paths, and RPM package names are represented this way. This style should indicate that a particular file or directory exists by that name on your system. Examples:
The .bashrc file in your home directory contains bash shell definitions and aliases for your own use.
The /etc/fstab file contains information about different system devices and file systems.
Install the webalizer RPM if you want to use a Web server log file analysis program.
ii Introduction to the WAF Developer Guide
application
This style indicates that the program is an end-user application (as opposed to system software). For example:
Use Mozilla to browse the Web.
[key]
A key on the keyboard is shown in this style. For example:
To use [Tab] completion, type in a character and then press the [Tab] key. Your terminal displays the list of files in the directory that start with that letter.
[key]-[combination]
A combination of keystrokes is represented in this way. For example:
The [Ctrl]-[Alt]-[Backspace] key combination exits your graphical session and return you to the graphical login screen or the console.
text found on a GUI interface
A title, word, or phrase found on a GUI interface screen or window is shown in this style. Text shown in this style is being used to identify a particular GUI screen or an element on a GUI screen (such as text associated with a checkbox or field). Example:
Select the Require Password checkbox if you would like your screensaver to require a password before stopping.
top level of a menu on a GUI screen or window
A word in this style indicates that the word is the top level of a pulldown menu. If you click on the word on the GUI screen, the rest of the menu should appear. For example:
Under File on a GNOME terminal, the New Tab option allows you to open multiple shell prompts in the same window.
If you need to type in a sequence of commands from a GUI menu, they are shown like the following example:
Go to Main Menu Button (on the Panel) => Programming => Emacs to start the Emacs text editor.
button on a GUI screen or window
This style indicates that the text can be found on a clickable button on a GUI screen. For example:
Click on the Back button to return to the webpage you last viewed.
computer output
Text in this style indicates text displayed to a shell prompt such as error messages and responses to commands. For example:
The ls command displays the contents of a directory. For example:
Desktop about.html logs paulwesterberg.png Mail backupfiles mail reports
The output returned in response to the command (in this case, the contents of the directory) is shown in this style.
prompt
A prompt, which is a computer’s way of signifying that it is ready for you to input something, is shown in this style. Examples:
Introduction to the WAF Developer Guide iii
$
#
[stephen@maturin stephen]$
leopard login:
user input
Text that the user has to type, either on the command line, or into a text box on a GUI screen, is displayed in this style. In the following example, text is displayed in this style:
To boot your system into the text based installation program, you must type in the text com­mand at the boot: prompt.
replaceable
Text used for examples which is meant to be replaced with data provided by the user is displayed in this style. In the following example,
The directory for the kernel source is /usr/src/
number
is the version of the kernel installed on this system.
version-numberis displayed in this style:
version-number/, where
version-
Additionally, we use several different strategies to draw your attention to certain pieces of informa­tion. In order of how critical the information is to your system, these items are marked as note, tip, important, caution, or a warning. For example:
Note
Remember that Linux is case sensitive. In other words, a rose is not a ROSE is not a rOsE.
Tip
The directory /usr/share/doc/ contains additional documentation for packages installed on your system.
Important
If you modify the DHCP configuration file, the changes will not take effect until you restart the DHCP daemon.
Caution
Do not perform routine tasks as root — use a regular user account unless you need to use the root account for system administration tasks.
iv Introduction to the WAF Developer Guide
Warning
Be careful to remove only the necessary Red Hat Applications partitions. Removing other partitions could result in data loss or a corrupted system environment.
3. Code Presentation Conventions
In addition to the standard document conventions covered in Section 2 Document Conventions, there are some additional conventions related specifically to discussing source code:
classname
This is the name of a class in an object-oriented (OO) programming language. For example, the class com.arsdigita.categorization.CategoryTreeNode.
method name
This is the name of a method in an OO programming language, e.g. the method getBase-
DataObjectType.
function
The name of a function or subroutine, as in a programming language. For example, the function
SecurityLogger.warn().
variable name
The name of a variable. For example, the variable BASE_DATA_OBJECT_TYPE.
option
An option for a software command or Method. For example, a user has been granted read privileges on an object.
return value
The value returned by a function. For example, a method returns null.
replaceable
Content that may, must or will be replaced by the user or a program. For example, the code is commented with NOTE(n), where n is the number of the NOTE.
program listing
A literal listing of all or part of a program. The \ character is used to break a line for printing purposes. You will want to reconnect them back into a single line, preserving the spacing in the line.
import com.arsdigita.kernel.permissions.PermissionService; import com.arsdigita.kernel.permissions.PermissionDescriptor; import com.arsdigita.kernel.permissions.PrivilegeDescriptor; import com.arsdigita.persistence.OID;
OID acsObject = new OID("example.MyACSObject", new BigDecimal(50));
OID party = new OID("com.arsdigita.kernel.Group", new BigDecimal(5));
Introduction to the WAF Developer Guide v
PermissionDescriptor perm = new PermissionDescriptor(PrivilegeDescriptor.READ, acsObject, party);
PermissionService.grantPermission(perm);
first term
The first occurrence of a term, such as the first time we introduce a bulletin-board and note its abbreviated form, bboard.
vi Introduction to the WAF Developer Guide
I. WAF Concepts
This section covers the concepts of WAF. The intention is to provide both a very high-level architec­tural view and a closer review of the individual components.
Table of Contents
1. WAF Overview ................................................................................................................................1
2. WAF Component: Persistence ....................................................................................................... 9
3. WAF Component: Kernel.............................................................................................................11
4. WAF Component: Services .......................................................................................................... 13
5. WAF Component: Presentation ................................................................................................... 19
6. WAF Component: Web................................................................................................................. 31
Chapter 1.
WAF Overview
This chapter is an overview of the Web Application Framework architecture. This high-level viewpoint is especially useful for gaining a good understanding of how WAF works. It is written with both the technical developer and the technical manager/team leader in mind.
WAF is a web application development framework. Some of the web applications that have been developed using WAF include Red Hat Content Management System and Red Hat Portal Server. WAF runs in any standards-compliant servlet container. For more details about system requirements, see the Red Hat Web Application Framework Installation Guide.
Figure 1-1 describes the architecture of WAF from a high-level perspective.
Figure 1-1. Basic Configuration
2 Chapter 1. WAF Overview
1.1. General Architecture
The WAF architecture described in Figure 1-1 follows the standard n-tier design pattern, with separate presentation, domain (business logic), data, and data model layers. Web applications built on WAF also follow the same n-tier design patter, leveraging the infrastructure provided by WAF.
1.1.1. The Layers
The four layers in the WAF architecture are:
Presentation Layer (UI) — presents information to the user
Domain Layer (Business Logic) — encapsulates business logic
Data Layer — stores and retrieves data
Data Model — stores data in a structured, format in a durable fashion
These layers are shown in Figure 1-2:
Figure 1-2. Basic Configuration
Chapter 1. WAF Overview 3
1.1.1.1. Presentation Layer
The Presentation Layer is responsible for presenting information to the end user. The presentation layer accepts processed, structured data from the domain layer and is responsible for styling the data appropriately and delivering the content in a format appropriate for the end user.
1.1.1.2. Domain Layer
The Domain Layer contains Domain Objects, which are abstractions of entities that exist in the busi­ness domain, for example, Party, Person, Group, Company, Department, Team, Product, Order, Line Item.
1.1.1.3. Data Layer
The Data Layer contains two very important components:
Data Objects
Provide read and/or write access to the persistent properties of Domain Objects.
Persistence Metadata
Describes a Domain Object e.g., its name, its properties, and how each property is mapped into the Data Storage layer. This description is written using the Persistence Definition Language (PDL) format. PDL was designed to be used specifically in WAF. PDL is discussed in more detail in Section 2.3 Persistence Definition Language (PDL).
1.1.1.4. Data Storage Layer
The data storage layer Contains the mechanism(s) employed for storing data persistently. This is typically a relational database (RDBMS) such as Oracle9i™ database or PostgreSQL, combined with a data model. It may also include other mechanisms, such as an LDAP directory or filesystem.
1.2. Features
In his seminal book Analysis Patterns, Martin Fowler writes that a framework “... should be applicable across a large domain and be based on an effective conceptual model of that domain” (p. 11). Accord­ingly, the Web Application Framework defines a set of Domain Objects that are encountered in the problem domain of most WAF applications. This object model further subdivides into two categories: kernel and services.
In addition to kernel and services, WAF includes other features that facilitate building database-backed web applications: infrastructure, persistence, presentation, and web.
1.2.1. Kernel
The kernel provides all the business logic provided by WAF, namely, business logic that is essen­tial to building a web applications. Kernel provides domain objects that represent users, groups, and permissions.
4 Chapter 1. WAF Overview
Figure 1-3. Basic Configuration
1.2.2. Services
Services are building blocks that address generic requirements common to most WAF applications. Each requirement defines a set of related Domain Objects, for example, Versioning, Workflow, and Categorization:
Chapter 1. WAF Overview 5
Figure 1-4. Basic Configuration
As shown in Figure 1-4, all services follow the n-tier design pattern discussed in Section 1.1 General Architecture, providing:
A user interface for interacting with the Framework’s Domain Objects.
Domain logic.
The metadata (PDL) required for persistence of the Framework’s Domain Objects.
Data storage as appropriate.
Services are discussed in more detail in Chapter 4 WAF Component: Services.
6 Chapter 1. WAF Overview
1.2.3. Infrastructure
Infrastructure contains software to support the mechanics of application building at each layer of the architecture (for example, serving page requests, styling the user interface, logging, specifying metadata, storing data, etc.).
Note
This infrastructure exists independently from any specific problem domain and generally does not depend on other WAF systems.
1.2.4. Persistence
Persistence handles the storage and retrieval of all information in WAF applications via Data Ob­jects. Data Objects are implemented as a Java class library that supports CRUD (create, read, update, and delete) operations for any type of Data Object. This is done through a set of generic interfaces:
DataObject, DataCollection, and DataAssociation. Persistence is discussed in detail in Chap-
ter 2 WAF Component: Persistence.
1.2.5. Presentation
Presentation is responsible for presenting data in a structured format to the end user. WAF provides three basic systems for presentation: Bebop, a web user interface component framework modeled after Java Swing; Java Server Pages (JSP); and eXtensible Stylesheet Language (XSL). Presentation is discussed in detail in Chapter 5 WAF Component: Presentation.
1.2.6. Web
The Web component of WAF makes the persistent data and domain logic of your application available to others over protocols such as HTTP. It integrates the Java Servlet API and the kernel and persistence components of WAF. For more information, see Chapter 6 WAF Component: Web.
1.3. Applications
Each WAF application adds code and other assets (stylesheets and PDL files) to each layer of the architecture. The result is a complete application:
Chapter 1. WAF Overview 7
Figure 1-5. Basic Configuration
Again, each application follows the same n-tier design pattern, and builds upon the kernel and services provided by WAF.
8 Chapter 1. WAF Overview
Chapter 2.
WAF Component: Persistence
This chapter discusses the persistence layer in the overall Web Application Framework. This was originally discussed in Section 1.2.4 Persistence. You can find persistence tutorials in Chapter 9 Per- sistence Tutorial.
2.1. Persistence Overview
The storage and retrieval of persistent data is a common requirement of business applications. WAF provides a persistence layer as a generic solution to this requirement.
The WAF persistence layer allows naturally written Java classes to be persisted to and queried from a relational database. This is more than simply a method to save Java classes within the database. De­velopers can use the full range of standard object-oriented modeling techniques, such as inheritance, interfaces, polymorphism, associations, and composition when writing their Java classes.
WAF persistence is an example of an object-relational mapping layer. This chapter will discuss some of the concepts required to understand and use WAF persistence. Examples and tutorials are detailed in Chapter 9 Persistence Tutorial.
2.2. Object-Relational Mapping
An object-relational mapping layer allows developers to use both relational and object modeling in the development of their applications.
Relational Modeling — a powerful tool for modeling knowledge. Modern relational databases uti-
lized with well designed relational schemas provide guaranteed data integrity, support for multiple concurrent transactions, and support for fast and flexible querying.
Object modeling — a powerful tool for modeling behavior. Object modeling is useful when coupled
with object-oriented (OO) languages and design patterns. OO languages are a common choice for rapid application development and maintenance.
An object-relational mapping layer can automatically translate operations on an object model into operations on a relational model and vice versa. This is accomplished through the use of map­ping metadata that relates persistent attributes and associations in the object model with tables and columns in the relational model.
2.3. Persistence Definition Language (PDL)
Because of the flexibility of both relational and object models, it is possible to map a given object model to many different relational models and to map a single relational model to many different object models.
WAF persistence uses a modeling and mapping language called Persistence Definition Language (PDL) to allow developers to describe their object model, their relational model, and how the two are mapped.
The metadata described in PDL allows the relational engine to efficiently persist changes to the object model and to construct efficient SQL queries for performing object-level reads. PDL also allows developers to define their own custom queries and SQL operations to do specialized querying and updating of the relational model.
10 Chapter 2. WAF Component: Persistence
For a listing of PDL terms, see Appendix D PDL Syntax. Examples of PDL usage can be found in Chapter 9 Persistence Tutorial.
2.4. Persistence and Domain APIs (DataObject, DataAssociation, DataCollection, DomainObjects)
Once an object model is described in PDL, the persistent state associated with it may be directly accessed and manipulated by Java code through use of the DataObject, DataAssociation, and
DataCollection classes that are part of the WAF persistence API.
Developers can then build upon this API in order to add the behavior required to implement their ap­plication. This is generally done by extending DomainObject,an abstract base class that encapsulates a DataObject and uses it to provide persistence capabilities to any derived classes.
2.5. Session and Transaction Management
Domain classes, once written, can be used similarly to any other Java code, with one exception — code that involves domain objects must be executed within the context of both a session and a transaction.
The session identifies which database that changes are persisted to and queried from, and the trans­action allows atomic updates of the database. Setup of the current session and transaction is a simple process that can usually be centrally handled in an application.
Within WAF, session and transaction management is automatically handled by the BaseServlet class. The transaction model used by WAF persistence is a simple extension of common relational database transaction semantics:
Transactions cannot be nested.
Once a transaction begins it must either be committed or rolled back.
Transactions must be rolled back after an error occurs.
Chapter 3.
WAF Component: Kernel
This chapter discusses the kernel layer, which provides services used by several parts of the WAF system. This component was initially explained in Section 1.2.1 Kernel. This discussion focuses on the various parts of the kernel component which a developer will need in building for WAF.
For information on how to utilize the kernel in WAF applications, see Chapter 10 Kernel Tutorial.
3.1. Users and Groups
Applications have users. A user is a person who uses an application to accomplish some purpose. In order to serve the needs of users, applications store information about the users. This information is used to personalize content, check the permission of a user initiated operation, and to provide information about a user to other users.
Users are organized into groups. The users in a groups are said to be members of the group. Groups can also be members of groups. A group exists so that several users and groups can be collectively identified as an entity.
Groups and users are the two types of parties. This parties system is one of the pieces of the kernel. The ability to refer to an entity that may be a group or a user provides flexibility to application authors in writing data models. An example is the definition of a group itself: 1 or more parties. This definition is recursive, but that recursion reflects the flexibility of having a party type.
3.2. ACSObject
Party, Group, and User are all subtypes of ACSObject. Its purpose is to serve as a base class for use
in common object-level services, such as categorization and permissioning. As such, subclasses are often types that users directly interact with such as Workflow or Category. The ACSObject object type also has generic attributes such as a unique id and a displayName.
The ACSObject object type and class are abstract. In order to differentiate among subtypes there are attributes objectType and defaultDomainClass. These attributes are used to instantiate the appropriate Java domain class to wrap data objects that are instances of subtypes of ACSObject.
3.3. Permissions
The goal of the permissions system is to provide generic means to both programmers and site admin­istrators to check, grant, or revoke permissions via a consistent interface. For example, an application developer might decide that viewing a certain set of pages within the application is an operation to be individually granted or revoked from a user. It’s expected that the permissions system will be heavily used in production - almost every page will make at least one permissions API call, and some will make several.
The permissions systems deals with three kinds of objects: ACSObject, Party, Privilege. The system maintains a set of grants of the form Party p has Privilege priv on ACSObject obj. The privileges represent actions that are performed on objects.
There are generic privileges that are always defined: read, write, edit, admin, create, and
delete. In addition, applications can define custom privileges, for example categorize.
The permissions system is used to answer questions such as:
12 Chapter 3. WAF Component: Kernel
What parties have a particular privilege on a particular object?
What privileges does a particular party have on a particular object?
On what objects does a particular party have a particular privilege?
Much of the power of the permissions system comes from the flexibility of the model. The assertions described above are not the only input. Each privilege can imply some set of privileges. In addition, each ACSObject can have a security context from which it inherits privilege grants. This security context is just another ACSObject. Groups provide a way of aggregating users and the permissions system pays attention to group membership.
The power of the permissions system comes from the interaction of these 3 hierarchies: group mem­bership, security context, and privilege implication. Taking these 3 together, the assertion that p has privilege priv on object obj means that p is a party or a member of a party (at any depth) that was granted priv or a privilege implying privilege priv on object obj or any parent of obj in the security context hierarchy.
3.4. Kernel Resources
Resource is a base class in the kernel that represents a user-accessible resource that may serve as a
data container. Resources are organized into a parent-child hierarchy. By default, resources set their security context to their parent resource.
Currently, the two subclasses of resource are Application (which is web accessible) and Portlet (which is accessible through Red Hat Portal Server). All Resources have a ResourceType which is uniquely determined by the specific object type of the Resource. ResourceType has metadata that is used by some generic navigation and administrative interfaces.
Chapter 4.
WAF Component: Services
WAF provides a number of generic services that can be used in developing a WAF application. Ser­vices leverage the business logic provided by the kernel, and provide specialized domain APIs to solve specific, common business problems. Examples of services include notification, workflow, and versioning.
Services were originally discussed in Section 1.2.2 Services. Implementation examples are covered in Chapter 11 Services Tutorials.
4.1. Auditing Service
The auditing service can track when and by whom an object was created and last modified for an
ACSObject. This tracking is optional. The AuditedACSObject class provides methods for accessing
the auditing information.
4.2. Categorization Service
One common WAF application implementation is providing online access to large amounts of infor­mation. To make it easier for users to find relevant information, the categorization service is often employed. It allows various pieces of information such as articles, forum postings, and so on, to be categorized.
Categories are organized in a tree-like structure that is often called taxonomy or ontology. See Example 4-1.
14 Chapter 4. WAF Component: Services
Entertainment
Music
Movies
Television Shows
Education
Literacy
Testing
Home Schooling
Theories
Sports
Scores
Venues
Kinds of sports
Basketball
Chess
Soccer
Example 4-1. Sample Taxonomy
The categorization service provides:
The ability to set up multiple independent taxonomies.
A system for importing predefined taxonomies from XML files.
A user interface for managing categories.
An API for categorizing objects.
4.3. Formbuilder Service
Most applications built with WAF allow users to enter information into the system through a web browser. A user fills out a number of fields in a form and clicks the Submit button. The user’s input is validated and stored for later retrieval and display.
Building complex applications with this sort of user interaction usually requires programmer involve­ment. For example, if you are building a web browser based email client, you need to write code that knows how to send and retrieve email messages, how to organize messages in folders, and so forth.
There are another class of applications that do not require any special handling of the input data. What is required is the ability to enter the data in a structured way, and retrieve it later for display, reporting, printing and so forth.
For example, a group of users wants to input and store data about competing products — they want to record the product name, the company’s name, a short product description, and the date it was first
Chapter 4. WAF Component: Services 15
introduced to the market. Rather than requiring programming for this straightforward functionality, WAF provides the formbuilder service.
The formbuilder service enables non-technical users to build such applications via a web inter­face, without any programmer intervention. The formbuilder service provides a variety of widgets to choose from, such as a date widget which makes the part of the HTML form that allows you to specify a date. Validation code is automatically attached to the date widget to ensure that only valid dates are entered. Various widgets are provided for entering simple strings, numbers, etc.
Once the user has defined the required fields for their custom new document type, the system automat­ically allocates structured storage in the database. From this point on, other users may enter documents that require the specified fields to be filled in. Such documents are displayed in the tabular format:
Product name gizmo
Company name Acme, Inc.
Description This gizmo slices, dices, and juliennes.
Date introduced July 22, 1999
Table 4-1. Sample structured document
4.4. Globalization Service
A globalized application is one that tries to equally accommodate readers of different languages and dialects. Elements of the application’s user interface are shown in the user’s preferred language, if it is supported. For example, a link to the categories page is displayed as Categories for English speakers and Kategorien for German speakers.
A partial list of globalization and localization facilities supported by the Java language includes:
The notion of locale (http://java.sun.com/j2se/1.3/docs/api/java/util/Locale.html).
Resource bundles (http://java.sun.com/j2se/1.3/docs/api/java/util/ResourceBundle.html) allow you
to organize and maintain locale-specific objects, such as strings to be shown as button labels, e.g. Categories and Kategorien.
Locale-specific date format (http://java.sun.com/j2se/1.3/docs/api/java/text/DateFormat.html\ #get-
DateInstance(int,java.util.Locale) — "\" has been inserted to show where the line was broken for printing purposes; you will need to put both halves together again in e.g. your Web browser URL window).
Locale-specific collation (http://java.sun.com/j2se/1.3/docs/api/java/text/Collator.html) of
sequences of strings.
Miscellaneous other facilities.
The WAF globalization service provides classes and methods that can be roughly divided into the following three categories.
1. Convenient wrappers around most-frequently used Java globalization APIs, such as resource bundles.
2. Means of configuring the list of supported languages, locales, and character sets.
3. A mechanism for locale and character set negotiation.
Most modern browsers allow users to select their preferred language or a ranked sequence of preferred languages. The globalization service looks at the user’s preferred language and locale,
16 Chapter 4. WAF Component: Services
as reported by the user’s browser, and compares them to the list of languages and locales that the system is configured to support.
For example, a user can configure the preferred first language to be German ("de") and the pre­ferred second language to be British English ("en_GB"). If the system is configured to support both of the languages, then the globalization service will choose "de" as the preferred locale. If the system has been configured to support British English, but not German, then the glob- alization service will choose "en_GB" as the preferred locale. If the system does not support either of the configured languages, then the service falls back to a default locale which could be "en_US" (US English).
Once the desired locale is configured, a supported character set must be selected. For example, pages in German can be displayed using the ISO-8859-11, UTF-8, or UTF-16 character sets. The user may express a preference by configuring their browser. The user’s preference must then be reconciled with the list of character sets that the system has been configured to support.
This example of locale and character set negotiation is simplified for this discussion, but it illustrates the fact that correct negotiation is non-trivial. The globalization service relieves the developer of the burden of negotiating the preferred locale and character set correctly.
The WAF globalization service provides a set of high-level APIs based on the standard Java globaliza­tion facilities that handle issues such as language negotiation and localization of static strings. These APIs complement the basic globalization infrastructure provided by Java (i.e., ResourceBundles and property files are still used in WAF) and are specifically designed for use by web applications built with WAF.
4.5. Mail Service
The mail service acts as a Mail Transport Agent (MTA) for other WAF applications. It provides a simple mechanism to send plain or rich text messages to any entity with a valid email address. This service is not typically accessed directly, but rather is used as a building block for higher-level services such as notification. This service has no dependencies on WAF and exists primarily as a wrapper for the more cumbersome JavaMail API.
When should you use the mail service directly? If you are writing an application that sends simple plain-text alerts to users, and you do not require those alerts to be recorded in the database, then it makes sense to use the mail service. Also, if you are dealing with raw email addresses rather than WAF parties (users and groups), you probably want to use the mail service directly. Otherwise, you should consider using the higher-level notification service (see Section 4.7 Notification Service).
4.6. Messaging Service
The WAF messaging service is intended to be used as a building block for messaging applications, which are WAF applications that use messages to represent various types of communication between users.
Messages can represent an email from one user to another, or a bulletin-board post, or a comment on another object in the system. A message can store attachments such as images or other files which are relevant to the message. Each attachment can be of any arbitrary MIME type.
The implementation follows the RFC8222standard, which has been deprecated by RFC28223.
The Message class models a persistent text message with optional attachments:
1. http://www.htmlhelp.com/reference/charset/
2. http://www.rfc-editor.org/rfc/rfc822.txt
3. http://www.rfc-editor.org/rfc/rfc2822.txt
Chapter 4. WAF Component: Services 17
1. The message body should have a MIME type of text/plain or text/html.
2. Each attachment to the message can have an arbitrary MIME type and format. Messages can also refer to any ACSObject on the system.
MessageParts represent a message part (an attachment) in the sense of a multipart MIME message.
Each part has some content represented as an arbitrary block of bytes and a MIME type that identifies the format of the content.
A ThreadedMessage is an extension of Message that allows messages to be organized into discus­sion threads with a tree structure. Messages at the first level are referred to as root messages.
Warning
If you delete the root message in a ThreadedMessage tree, all of its children are deleted.
4.7. Notification Service
The notification service is built on top of the mail and messaging services. Notification provides persistent storage of messages, digest processing, automatic error recovery, and expansion of groups into individual recipients.
Tip
When looking for a ser vice to do aler ting, consider the differences between the notification service and the lower-level mail service. The mail API is capable of handling simple alerts in plain-text. If you need added features, notification provides these.
4.8. Portal Service
The portal service provides basic domain classes that represent portlets and portals (a set of zero or more portlets). The portal service is used by the Red Hat Portal Server and any WAF application that provides portlets for displaying specific content to the end user.
For more information about utilizing portal services, see the Red Hat Portal Server Developer Guide.
4.9. Search Service
The search service provides APIs for indexing and searching content. Two search engines are sup­ported by WAF: Oracle interMedia™ and Lucene. interMedia requires an Oracle database, while Lucene is an open-source, database-independent search engine (see http://jakarta.apache.org/lucene/ for more information).
Objects that should be indexed register themselves with a SearchableObserver, which indexes the object whenever the object is persisted. If you extend WAF with custom content types, making the custom content searchable is a simple matter of implementing com.arsdigita.lucene.Adapter.
18 Chapter 4. WAF Component: Services
4.10. Workflow Service
Workflows allow specialized members of a group to collaborate using a standard process. Developers can define new workflows using the workflow service.
A workflow contains a set of tasks, each of which may depend on one or more other tasks. A task is a single unit of work. It can be enabled, disabled, or finished. A task may be assigned to a user or group.
Section 11.4.1 Simple Workflow describes the components of the simple workflow and how to create workflows and assign them to users and groups.
4.11. Versioning Service
The goal of the the versioning system is to provide facilities for versioning data objects. This involves recording information about changes made to the data in order to be able to:
1. Roll back to an earlier point in a data object’s history.
2. Undelete a data object (corollary of ability to perform rollback).
3. Compute the difference between any two versions of the data object.
To illustrate the last point, suppose you want to know the difference between the Jan 12 and Apr 19 versions of an article. The versioning service can tell you that the article’s title changed from Ravioli is better than spaghetti to Macaroni is better than spaghetti, and its by-line changed from Guy Lewis Steel to Guy Lewis Steele, Jr.
The service does not provide redundant storage. If you delete the versioned article in a context where the versioning service is not available (either because it is turned off, or because you are manipulating the database directly through JDBC or command-line client), there is no way for the versioning service to restore it. It is not a substitute for regular database backups.
The service is provided transparently. There is very little that a developer has to do in order to make their data versioned. To gain conceptual understanding of the versioning service, one must be familiar with the key concepts of persistence: data objects and PDL (see Section 2.4 Persistence and Domain
APIs (DataObject, DataAssociation, DataCollection, DomainObjects), Section 2.3 Persistence Defi­nition Language (PDL)). For a more detailed explanation of versioning, please refer to Section 11.5 Versioning Tutorial.
Chapter 5.
WAF Component: Presentation
Information in the database ultimately needs to be presented to the user for viewing and manipulating. The Web Application Framework provides a variety of systems to display and style information. Each system has its strengths and weaknesses, and is designed for specific situations.
The following sections discuss the various methods available for presentation under WAF. Each ap­proach is designed to be complementary to the other approaches. Each approach is intended to be used in different situations; the following sections discuss when each system should be used.
Presentation in the context of the Web Application Framework was originally discussed in Section
1.2.5 Presentation. For information about implementing Bebop, the presentation method unique to Red Hat WAF, see Chapter 12 Presentation (Bebop) Tutorial. Further information and tutorials about implementing the open standards presentation methods in WAF can be found through Chapter 14 References and on the Web.
5.1. Overview of Presentation Standards
This section discusses the standards used in WAF for textual presentation of structured data. In addi­tion, the standard Java APIs for parsing and manipulating structured data are reviewed. This informa­tion is provided to set a common understanding of which tools are incorporated into WAF and how they are used.
HyperText Markup Language (HTML) is the predominant web standard for rendering documents.
In WAF, HTML is produced either by JSPs or by transforming the XML generated by Bebop pages. WAF applications use HTML in conjunction with the following technologies to render and style an application’s output.
XHTML is the newest version of the HTML specification, adapted to conform to the standards of XML.
Cascading Style Sheets (CSS) is a standard mechanism for adding presentation information (style
and layout) to HTML documents. See Section 5.2 CSS and XSLT for more information. WAF uses CSS to style the HTML produced in JSPs and Bebop pages.
eXtensible Markup Language (XML) is a standard format for representing arbitrary tree-structured
data in textual format. XML documents appear similar to HTML because both are derived from Standard Generalized Markup Language (SGML). WAF uses XML in its Bebop component UI framework to describe a UI before it is styled.
Document Object Model (DOM) is an API (available in both C++ and Java) for manipulating an
XML document as a tree structure in memory. The heart of the DOM is the Node object, which represents an element or attribute; a Document is the top-level object that represents an entire XML document. WAF uses DOM to generate Bebop presentation XML. Bebop uses the J2EE JAXP API to manipulate DOM documents.
eXtensible Stylesheet Language Transformations (XSLT) is a language for specifying rules to trans-
form an XML document into some other kind of output. It is most often used for rendering XML­formatted data into an XML presentation format such as XHTML. WAF uses XSLT to convert Bebop source XML into HTML for the client browser. Bebop uses the J2EE TrAX API to plug into and use one of several available transformer implementations.
See Section 5.2 CSS and XSLT for more information about XSLT.
20 Chapter 5. WAF Component: Presentation
JavaServer Pages (JSP) is a J2EE standard for scripting dynamic web pages. JSPs typically produce
HTML, though they can generate any XML markup. WAF supports the use of JSPs as a primary means of writing web UIs.
5.2. CSS and XSLT
WAF uses two technologies together, CSS and XSLT, to add style to logical (i.e. all form, no style) markup.
CSS is used to control the style properties of HTML documents. CSS has broad browser support
and fine-grained control of properties such as color, borders and padding, positioning, and typeface. Unlike XSLT, CSS cannot change the structure of the document it is styling. It cannot, for instance, turn a bulleted list into a table.
Bebop uses CSS in the HTML that it produces when it transforms source Bebop XML. JSPs may also use CSS. Consistent use of CSS under both regimes will give your site a more consistent look and feel.
XSLT is used to transform the structure of an input document into a new and different structure on
output. This is very useful for creating modeling and then rendering UI concepts that do not exist in HTML. A tabbed pane, for instance, is not part of the HTML standard, but a block of HTML that functions as a tabbed pane can be produced by transforming source XML describing tabs into a concrete HTML rendering.
Bebop, by design, produces XML that is not fit for direct consumption by a browser. Instead, it is transformed first using XSLT. JSPs, by contrast, typically produce HTML directly and so will not generally make use of XSLT. This is not, however, a rule. When it makes sense, a JSP author may wish to produce XML and transform it using XSLT.
5.2.1. Integrating XSLT with WAF
WAF integrates XSL stylesheets in the following way:
It provides a default stylesheet as an integral part of each WAF package, so that each package ships
with some way to style the content it generates.
It allows the defaults for any package to be overridden when that package is invoked from a partic-
ular URL pattern. This allows for co-branding, etc.
It allows for special-usage page scripts (for example, JSP/XSL pairs) which can import site-wide
styling rules.
Note
WAF infrastructure depends on the Xalan-J XSLT engine from the Apache XML project. In turn, this engine depends on the Xerces XML parser, also from Apache. Although it is theoretically possible to use a different XML parser with Xalan, this process hasn’t been thoroughly tested. It is also possible to use a different XSLT engine, such as Saxon. WAF contains no explicit dependency on any vendor’s XSLT engine.
This flexibility comes from the PresentationManager interface, whose one method servePage obtains a transformer for the current request, applies it to an input document, and serves the trans­formed output to the response output stream. A WAF application’s dispatcher can use the provided
Chapter 5. WAF Component: Presentation 21
BasePresentationManager or swap it out with its own to use a different algorithm for choosing a
stylesheet for the current request.
XSLT integration into WAF is accomplished by associating stylesheet documents with both WAF packages and site nodes. A default stylesheet is associated with each WAF package, and the package defaults can be overridden within a particular URL prefix’s scope. A site node represents a node in the webapp’s URL tree; each site node, combined with its ancestors, generates a URL prefix. For the purposes of this discussion, subsite is defined to mean any site node that contains child site nodes — that is, a directory that contains subdirectories.
Because XSLT templates are also XML documents, they can be manipulated and composed dynami­cally. A PresentationManager could compute a new XSLT stylesheet on the fly, taking fragments from files on disk or in the database.
For more information about using XSLT with WAF, see Section 12.1 Calling XSLT from a WAF Application.
5.2.2. WAF Templating Package
The WAF Templating package (com.arsdigita.templating) serves as a repository for all classes relating to the XSL templating.
5.2.2.1. Template Resolution
This section discusses the process of resolving the primary, top level stylesheet for transforming an application’s XML DOM into an HTML page.
5.2.2.1.1. Background
The original WAF method for resolving top level stylesheets required the application programmer to register a default com.arsdigita.kernel.StyleSheet object against its com.arsdigita.kernel.PackageType object. Project integrators could override this default application stylesheet by registering a custom stylesheet against a
com.arsdigita.kernel.SiteNode object.
These mappings were maintained in the database and the rules for querying them to discover the top level XSL template for an application were written into the standard presentation manager class (com.arsdigita.sitenode.BasePresentationManager). This resulted in an XSL templating architecture that was spread across multiple Java packages and left little scope for extending or altering the template resolution algorithms, without massive code replacement / duplication.
5.2.2.1.2. Stylesheet Resolver
A quick analysis of XSLT usage across projects and applications shows that there are a large number of variables that can come into play when deciding which stylesheet to apply to an application’s DOM. Rather than attempt to standardize on a particular algorithm for resolving stylesheets, the WAF tem­plating package introduces the com.arsdigita.templating.StylesheetResolver interface as a means to plug in an arbitrary template resolution algorithm. This interface contains a single method:
public URL resolve(HttpServletRequest sreq);
This method may use any available state information to locate a stylesheet, which transforms XML that has been generated during this supplied request object. The returned object must represent an absolute URL under any protocol supported by the java.net.URL class (typically, either file:// or http://). The com.arsdigita.templating.LegacyStylesheetResolver class provides a resolver compatible with the resolution method used in WAF releases older than 6.0. The
22 Chapter 5. WAF Component: Presentation
com.arsdigita.templating.PatternStylesheetResolver class is the new preferred
resolver and now the default setting.
5.2.2.2. Pattern Based Resolution
The primary implementation of the stylesheet resolver interface is the class
com.arsdigita.templating.PatternStylesheetResolver. The core idea behind this
approach is that there is a list of abstract paths containing placeholders, of the form ::key::. For example, a simplified version of the default list of paths looks like:
/__ccm__/apps/::application::/xsl/::url::- ::locale::.xsl /__ccm__/apps/::application::/xsl/::url::. xsl
When resolving which stylesheet to apply, the placeholders are expanded based on the request state, to generate a list of real paths. Since a placeholder potentially has multiple valid values for a request, a single abstract path could expand to multiple real paths. Placeholder expansion proceeds left-to-right, to ensure a deterministic ordering of the list of real paths.
If we consider an example request for the content section admin pages at
/content/admin/index.jsp, the placeholder expansions might be:
::application:: -
::url:: -
::locale:: -
(admin, index)
(content-section)
(en_US, en)
If we proceed left-to-right with this expansion, the first expansion is for ::application::, leading to:
/__ccm__/apps/content-section/xsl/::url::- ::locale::.xsl /__ccm__/apps/content-section/xsl/::url::. xsl
Next, the ::url:: placeholder is expanded:
/__ccm__/apps/content-section/xsl/admin-:: locale::.xsl /__ccm__/apps/content-section/xsl/admin.xs l
/__ccm__/apps/content-section/xsl/index-:: locale::.xsl /__ccm__/apps/content-section/xsl/index.xs l
Finally, the ::locale:: placeholder is expanded:
/__ccm__/apps/content-section/xsl/admin-en _US.xsl /__ccm__/apps/content-section/xsl/admin-en .xsl /__ccm__/apps/content-section/xsl/admin.xs l
/__ccm__/apps/content-section/xsl/index-en _US.xsl /__ccm__/apps/content-section/xsl/index-en .xsl /__ccm__/apps/content-section/xsl/index.xs l
Once all the placeholders have been expanded, these paths will be verified in order and the first that exists returned as the primary stylesheet for the request. In this example the first match will be /__ccm__/apps/content-section/xsl/admin.xsl.
Chapter 5. WAF Component: Presentation 23
5.2.2.3. Pattern Generators
The com.arsdigita.templating.PatternGenerator interface provides the mechanism for in­troducing new placeholders in the pattern based stylesheet resolver. This interface contains the single method:
public String[] generateValues(String name,
HttpServletRequest request);
This method may use any available state information to generate a list of values for the placeholder. The elements in the array should be ordered in decreasing specificity. If there are no possible values for a placeholder, then an empty array can be returned, causing the entire abstract path to be thrown away. As a hypothetical example, consider a pattern generator for switching based on the user’s browser:
public class BrowserPatternGenerator implements PatternGenerator {
public final static String GENERIC = "generic";
public String[] generateValues(String key,
String useragent = req.getHeader("user-agent"); if (useragent != null &&
useragent.indexOf("MSIE") return new String[] { "ie", GENERIC };
} elseif (useragent != null &&
} else {
}
}
}
useragent.indexOf("Mozilla")-1) {
return new String[] { "mozilla", GENERIC };
return new String[] { GENERIC };
HttpServletRequest req) {
-1) {
Example 5-1. Web Browser Triggered Pattern Generator
Once the new pattern generator class has been implemented, it needs to be registered with the resolver by calling the static registerPatternGenerator method in
com.arsdigita.templating.PatternStylesheetResolver, supplying the name of the
placeholder key. This registration is best done in the static initializer block of a core class, such as a servlet.
static {
PatternStylesheetResolver.registerPatternGenerator(
"browser", new BrowserPatternGenerator()
);
}
Available Patterns
application
This pattern generator expands to the current application package key. ie, the value returned by a call to com.arsdigita.web.Web.getConfig().getApplication().getKey().
host
This pattern generator expands to the hostname and port number for the current servlet container, as returned by com.arsdigita.web.Web.getConfig().getHost().
24 Chapter 5. WAF Component: Presentation
locale
This pattern generator expands to the current kernel execution context locale. ie the value re­turned by a call to com.arsdigita.kernel.Kernel.getContext().getLocale();.
outputtype
If the outputType request parameter is set, this expands to one of text-plain,
text-javascript, or text-html if the parameter value is text/plain, text/javascript or text/html respectively. In all other cases no values are generated.
prefix
If the current request has passed through an instance of the
com.arsdigita.web.InternalRedirectServlet, then this pattern generator expands to
the value of the prefix init parameter in the web.xml servlet declaration. In all other cases no values are generated.
url
This pattern generator expands to multiple values based on the URL fragment remaining after the application’s mount point. The set of values are generated by progressively stripping off trailing path components. The values are then normalized as follows:
1. The file extension is removed.
2. Trailing occurrences of /index are replaced with /.
3. If the path is the empty string, it is replaces with index.
4. Occurrences of / are replaced with -.
Some examples are:
/index.jsp -
/ -
/admin/index.jsp -
/admin/ -
/admin/item.jsp -
{ "index" }
{ "index" }
{ "admin", "index" }
{ "admin", "index" }
{ "admin-item", "admin", "index" }
5.2.2.4. Configuration
The choice of which stylesheet resolver to use is controlled by the waf.templating.stylesheet_resolver property, and defaults to com.arsdigita.templating.PatternStylesheetResolver.
The location of the paths file to configure the pattern based stylesheet resolver is controlled by the waf.templating.stylesheet_paths property, defaulting to /WEB-INF/resources/stylesheet-
paths.txt
To enable an application to operate with the default pattern stylesheet resolver configuration, its primary top level stylesheet should be named /__ccm__/apps/[application
key]/xsl/index.xsl.
Chapter 5. WAF Component: Presentation 25
5.3. JavaServer Pages (JSP)
JSP technology is a J2EE standard for presentation. JSP pages have full access to WAF APIs. For documentation and tutorials on how to write JSPs, see http://java.sun.com/products/jsp/docs.html.
The strength of JSP is the ease with which a developer can rapidly develop and modify a single page. JSPs can be recompiled on the fly, and changing layout is straightforward, since a JSP is patterned after a standard HTML file. JSPs are recommended for projects where user interface specifications rapidly change and where the need for UI reuse is limited.
JSPs that wish to use the services of the WAF framework must extend the base JSP servlet provided by the WAF framework. See Section 6.2 BaseServlet.
5.4. Bebop - Reusable Web UI Components
Bebop is a web-based UI component framework. It is named after Swing™, the Java UI toolkit from Sun.
Calling Bebop a web UI component framework has a very specific meaning:
Bebop as a framework is intended to establish a standard mechanism and pattern for defining UIs.
It is not a library of ready-to-wear UI elements, though it happens to include some.
Bebop as a component framework is used to define UIs, with the resulting components useful as UI
building blocks.
Bebop as a Web UI component framework — Bebop is specifically designed for the conventions
and constraints of the Web.
A component-based system is designed for reuse in varying contexts, bundles useful behaviors with useful state, and operates in and responds to a service-rich environment. Bebop is intended to turn a developer into a deployer by providing components which need only be told what to do, rather than how to do it.
In Figure 5-1, Bebop components provide the tabbed pane. This tabbed pane component remembers which are the currently visible components, and it knows internally how to generate the correct URLs to other tabs. This functionality is gained just in using the component.
26 Chapter 5. WAF Component: Presentation
Figure 5-1. Tabbed Pane Using Bebop Components
5.4.1. Working With Bebop
Chapter 12 Presentation (Bebop) Tutorial discusses specifics of implementing Bebop. This section is a more abstract look into the design and usage of Bebop.
Bebop components follow these guidelines:
Provide a tree of components; components are also containers.
Consolidate parameter validation logic and support for custom validators.
Lock data structures for reuse across requests.
Automate state preservation and services to allow components to control their own state.
Multiplex Swing-like events onto the HTTP request.
For a given request, one component is selected and has control.
Transforms XML for global style; no HTML in Java strings.
The code in Example 5-2 gives an idea of how Bebop is used.
public void service(HttpServletRequest sreq, HttpServletResponse sresp) {
Page page = new Page("Content Section");
page.add(new Label("Content Section"));
Chapter 5. WAF Component: Presentation 27
TabbedPane tabs = new TabbedPane(); page.add(tabs);
tabs.addTab("Browse", new BrowsePane()); tabs.addTab("Search", new SearchPane()); tabs.addTab("Roles", new RoleAdminPane());
page.lock();
Document doc = page.buildDocument(sreq, sresp);
transform(sreq, sresp, doc);
}
Example 5-2. Basic Bebop Page
5.4.1.1. Bebop Lifecycles
Bebop components used in a page design have a regular lifecycle.
UI Lifecycle
1. Register each component.
a. Each component adds any state parameters and event listeners it needs.
b. Each component is added to the page and given a unique key so that any state carried in
the query string is safe from collisions.
2. Lock the component tree.
a. Components can no longer be added or removed. The component instances are now safe
for reuse across requests.
3. Service requests.
Request Lifecycle
1. Client sends an HTTP request.
2. A new page state object is created.
Using the servlet request and the parameter models defined by the developer, the page builds an object representing the state of the current request.
In addition to component state parameters, the selected component and the visibility state are recovered from the servlet request state.
3. Fire the request event.
Any request listeners added when each component registered itself run at this time. Note that this runs before the page request state is validated; in fact, the request event runs before much of any work is done.
4. Validate page state.
This is when custom validators, e.g. a zip-code validation listener, are run. Parameters are also typed at this step.
If there are errors, they are saved so that the component whose state is invalid may choose how to present the issues.
28 Chapter 5. WAF Component: Presentation
5. Fire the control event.
When a client sends a request to a Bebop page (e.g. a mouse-click on a tab); only one component receives the request. This triggers the respond method of that component only; no other compo­nents response methods are involved.
If the request state is deemed valid, the selected component has the opportunity to respond, perhaps changing the request state and thus the outcome of further processing.
A control event handles form submission, control links, and has influence on the state and visibility before any output is produced.
6. Fire the action event.
The action event is an opportunity for any component to run code before the response is committed and after the controlling component has responded.
Any component may listen and edit state and visibility before any output is produced. This is the last opportunity to edit before XML is written out.
7. Generate XML.
The components generate the XML. They write to the document created in the first step. Each component generates its own semantic XML (such as XUL, Mozilla’s presentation-based cross­platform markup language) and also delegates to its children. A DOM is built.
8. Transform the XML.
The XML document is transformed with an XSLT stylesheet and sent back to the client in the form of HTML and CSS.
By following this method, it is easy to define and maintain a style and layout uniformity. A devel­oper or designer can drop a different XSLT into the last step in a page request cycle.
9. The client receives the transformed results of the page request.
5.4.2. JSP Integration with Bebop
JSP is integrated with Bebop components (XML sources) and XSLT by using a JSP tag library to allow the use of Bebop components in an otherwise standard JSP. The JSP tag libraries accomplish this by performing transformations on the XML document produced by a Bebop page. This JSP tag library also directs the generation of output by rendering the resulting XML document through XSLT, so that the included components displayed in a JSP are styled with the standard template rules that are used on the rest of the site. See Appendix A Bebop Tag Library Reference.
Java developers can also construct their Bebop pages in JSP. The tag library for declaring Bebop pages is separate from the one for displaying Bebop components, but they are intended to work together. This is different from using a Bebop component inside of JSP. In this instance, the specific Bebop JSP tags takeover JSP similar to writing and controlling a servlet.
There are several motivating factors for integrating JSP with Bebop. First, laying out pages using JSP integration will allow web developers who are not Java programmers to use third-party web publishing tools (Dreamweaver, etc.) to alter the layout of pages, add new components to an existing page, edit form field labels or prompts, etc. It would also be possible to make completely new pages with JSP that display components from existing Bebop pages.
Second, JSP is a convenience for Java developers creating Bebop pages, because JSP shortens the development cycle for individual pages by eliminating the need for a manual recompile and server restart when a page is changed.
The overall request pipeline for constructing a page with Bebop and displaying it with JSP follows this sequence:
Chapter 5. WAF Component: Presentation 29
1. The requested JSP obtains an XML document from a Bebop page object and the current request state.
2. The tags in the requested JSP construct a new XML document, copying pieces from the Bebop page where needed.
3. The resulting XML is passed as the XML input to an XSLT transformation step, using global stylesheets.
4. The final result is sent to the user’s browser.
5.4.3. Relationship Between Static and Dynamic Pages
The JSP tag libraries for Bebop are designed to work together with Bebop’s model of static, shared
Page and Component objects, while providing familiar semantics to JSP authors. The division in
Bebop between static Page objects that produce dynamic output on each request is reflected by the division of tags into two separate libraries:
A library of define:... tags.
Used for creating Bebop components.
The define:... tag library defines a Bebop page and its components with JSP tags. Since the page structure may be static, JSP code in the define:... tag library is not guaranteed to run on each request. Care must be taken to not put any runtime expressions or scriptlets into a
define:page
A library of show:... tags.
Used for displaying the output form Bebop components inside a JSP page.
The show:.... tag library is dynamic. Any Java code inside a
teed to run on each request, even if the components used are static. This is because the show:... tags manipulate the XML output produced by Bebop components on each request, and not the components themselves. Any valid JSP runtime expressions, tags, or scriptlets may be used in conjunction with show:... tags and they should yield the same results they would in any other JSP.
Caching in
define:page
cause the JSP define:page tag, along with the code and all the other tags inside it, to execute on each request; the entire Page object will be re-created and garbage-collected on each request.
block that are expected to run on each request.
show:page
can be suppressed with the JSP tag attribute cache="no". This will
block is guaran-
Warning
Although it seems there could be some benefit to changing the structure of a Page on each request, it is not recommended.
Variations on the structure of a page will interfere with Bebop’s state maintenance, because the URL and form variables that preserve state for individual components are linked to the component’s position in the overall page structure. Runtime errors or unpredictable results may occur if a page’s structure differs between subsequent requests.
Certain types of non-structural page variations between requests are permissible. Most importantly, the individual options in option groups (radio groups, select widgets, checkbox groups) are not com­ponents themselves, so the following code is permissible:
define:radioGroup
% loop { %
30 Chapter 5. WAF Component: Presentation
define:option label="%= rtexpr %" value="%= expr %"/
% } %
/define:radioGroup
This is especially true if the options in the group do not change frequently. Also, the contents of stateless components (Labels, Links, Images, etc.) may change between requests, though the position of the component in the page should not.
Further examples can be found at Appendix A Bebop Tag Library Reference.
Chapter 6.
WAF Component: Web
The Web component of WAF makes the persistent data and domain logic of your application available to others over protocols such as HTTP. It integrates the Java Servlet API and the kernel and persistence components of WAF.
For more information about building and deploying your own applications, see Chapter 8 WAF Appli- cation Development Tutorial.
6.1. Applications
WAF supports deploying multiple instances of an application type. A typical WAF installation may have three forum instances, each with its own data and configuration at distinct locations on the site. A WAF Application is the base on which developers build such applications in the WAF framework.
In the following, we distinguish between an application, here meaning the concrete application you wish to develop, and Application, the WAF base class used to define such new applications.
6.1.1. Application Properties
Applications have a number of specific properties.
Applications are persistent — An Application has persistent properties describing its location,
name, etc. It is also a container of persistent data, often user-entered data, that a concrete application uses to do its work. A forum, for instance, will contain database-stored message threads and posts.
Applications work with the dispatcher — Applications are designed to work together with the
WAF dispatcher to convert user-friendly URL paths into an application servlet and an application instance containing its specific data. The dispatcher uses the location and parent properties of an Application to look it up using the request URL.
Applications have a Servlet — Once the dispatcher has routed a request to an Application, the
Application responds using a Java Servlet.
Applications are associated with Stylesheets — The WAF presentation layer uses XSLT stylesheets
to implement the look-and-feel of its applications. The Application abstraction carries with it a stylesheet property that the presentation layer uses to fetch the appropriate stylesheet.
Applications extend Resources — Applications leverage a feature of the WAF kernel API, Re-
sources. Resources model containment, resource-partitioned user and configuration data, and a data-backed type system. See Section 3.4 Kernel Resources. For example, Application uses the containment behavior of Resource to implement its security.
6.2. BaseServlet
The WAF BaseServlet implements a Java Servlet and integrates it with the WAF kernel and persis­tence layers. The BaseServlet performs two functions:
Transaction management — The BaseServlet is responsible for starting and stopping the re-
quest’s database transaction. If any errors occur, the servlet will roll back the transaction. If appli­cation code requests a redirect, the BaseServlet ensures that the transaction is finished before proceeding.
32 Chapter 6. WAF Component: Web
Request context — The BaseServlet packages certain facts about the request, such as the current
user, current application, etc., and loads them into a context object whose data is available to any code running inside the servlet.
The BaseServlet is used in two principal ways in WAF:
New applications will sometimes use custom servlets that extend the BaseServlet and use the
Servlet API to implement their UI.
Applications that use JSPs can take advantage of the WAF platform by having their JSP pages
extend the BaseJSP class. Any code inside of the JSP will then be able to use the transaction and request context setup by the BaseServlet.
6.3. Dispatcher
The WAF dispatcher connects user requests to Applications. A forum instance may live at
/apps/sales/forum/ while another is at /apps/engineering/forum/. The dispatcher maps
requests to the distinct Servlet and the distinct Application instance corresponding to each.
The design of the dispatcher is such that the URL-to-application mapping is reversible. Just as it is possible to navigate from a URL to a specific data partition, it is possible to navigate from a data partition back to a URL. This is done so that, given an object and its container, the WAF platform can generate an address to it through the dispatcher.
A typical path through the dispatcher takes the following steps:
1. A request for the engineering forum at /apps/engineering/forum/index.html comes in.
2. The dispatcher is mapped to the pattern /apps/*, so the servlet container sends the request to the dispatcher servlet.
3. The dispatcher servlet searches the database of applications using the path, minus the /apps prefix and minus the filename suffix. The path /engineering/forum matches an Application instance and the dispatcher servlet loads it.
4. The dispatcher servlet then fetches the path to the servlet of the application. The forum servlet is mapped to /__ccm__/servlet/forum. The dispatcher forwards the request, now in the form
/__ccm__/servlet/forum/index.html.
5. The forum servlet receives the request. It uses application ID set on the request by the dispatcher to load the data for the engineering forum. It uses the path info (in this case /index.html) part of the request to display the appropriate page in the forum UI.
6. The engineering forum is served.
The WAF dispatcher is not responsible for serving static resources. Instead, your servlet container in its default configuration performs the work of serving images, CSS and XSL files, and JSPs. No special interaction with the dispatcher is necessary to use this feature of servlet containers.
II. Equipping Developers
This section provides information needed to develop on the Web Application Framework. Included is a chapter on setting up a development environment and a number of tutorials related to the WAF components.
Table of Contents
7. Developing with WAF ................................................................................................................... 35
8. WAF Application Development Tutorial.....................................................................................49
9. Persistence Tutorial....................................................................................................................... 63
10. Kernel Tutorial ..........................................................................................................................103
11. Services Tutorials...................................................................................................................... 123
12. Presentation (Bebop) Tutorial..................................................................................................153
13. Web Applications Tutorial ....................................................................................................... 165
Chapter 7.
Developing with WAF
This chapter introduces prerequisites to working with WAF as well as the tools that are available to the developer.
It is presumed that anyone developing for WAF has sufficient knowledge in his or her area of expertise and can perform accordingly. For this reason, both high-level concepts, such as database design and programming theory, and basic concepts, such as command line usage and shell scripting, are not addressed.
7.1. Developer Education
A developer undertaking additions and modifications to this complex system needs a sufficient level of appropriate knowledge. In particular, a developer should be knowledgeable and skillful in the fol­lowing areas:
Java — the programming language used in WAF.
Servlets and JSP — the J2EE standards used in WAF.
SQL — used for directly accessing the relational databases in cases where the object-relational
mapping provided by the persistence engine is deemed insufficient.
UML — parts of the Unified Modeling Language’s basic vocabulary, especially those related to
class diagram, are used by the persistence framework.
XML and XSLT — key standards used in the WAF presentation system.
CSS — used to style WAF pages.
Concepts of Object Oriented (OO) programming.
Relational database design and object-relational mapping concepts.
In addition, a knowledge of Perl is necessary for extending the build system. PL/SQL and PL/pgSQL are used to maintain certain denormalizations in the database.
A developer either needs to understand how to install, configure and administrate the chosen database or have an experienced DBA performing those functions.
Finally, anyone who is involved with the installation, configuration and administration of the server(s) and development environments for the WAF implementation requires sufficient skill in the target installation OS, e.g. Red Hat Enterprise Linux AS.
Chapter 14 References contains references to additional resources that can help in understanding WAF.
7.2. Third-Party Development Tools
Tools are very straightforward. Because the WAF system is designed around open standards and open source solutions, a developer is given a wide range of development tools and environments.
Programming is done primarily in Java, for which numerous Integrated Development Environments (IDE) exist. One useful open source IDE is Eclipse. Integration of Eclipse with WAF is covered in Section 7.4 Setting Up Eclipse and WAF. A developer can also use any text editor such as vi or Emacs.
For access to build systems and configuration files, either console or remote access to servers is re­quired. The best choice for remote access is SSH, a protocol suite of network connectivity tools which
36 Chapter 7. Developing with WAF
includes a secure telnet replacement and a secure file-copy utility (scp). Both open source and propri­etary implementations of SSH exist for virtually every OS. An open source implementation of SSH for Unix-like systems can be found at http://www.openssh.org. The site also provides pointers for various alternatives to OpenSSH.
The OpenSSH client is often packaged with the following Unix and Unix-like systems:
Linux
*BSD (FreeBSD, OpenBSD, NetBSD)
Apple OSX
Legacy Unices (Solaris, AIX, HP-UX, SCO, Irix, etc.)
For Win32 systems, a good open source application is PuTTY, found at http://www.chiark.greenend.org.uk/~sgtatham/putty/. It is a Telnet/SSH client which also supports sftp (secure ftp) and scp on a Win32 platform.
The ultimate output of WAF is HTML pages. These can be viewed in any modern Web browser. For the most part, Web browsers will "just work", although each browser may implement standards differently.
For actual development efforts, it is highly recommended to use a version control system, also called software control management (SCM). Several mature systems exist. On the open source side, there is CVS (http://www.cvshome.org). The Red Hat Applications development teams use Perforce for version control. If your code might integrate back into the main development tree, you may want to look into Perforce at http://www.perforce.com.
7.3. Developer Support
WAF provides the Developer Support application to give developers high-level information about each server request. Developer Support shows data such as request duration, number of queries, and information about each query executed. In addition, Developer Support allows developers to adjust their server logging level at runtime.
7.3.1. Enabling Developer Support
To enable Developer Support, edit your enterprise.init file and set the following initializer to
active:
init com.arsdigita.webdevsupport.Initializer {
active = true;
}
To disable Developer Support, modify your enterprise.init file and modify the webdevsup­port Initializer so that active = false.
Once you have enabled Developer Support in your enterprise.init file, you must restart the servlet container in order to use it. E.g. if you are using Tomcat, service tomcat restart is sufficient.
Warning
You should always make sure that Developer Support is disabled in a production system. Developer Support imposes a significant performance penalty on systems as it collects and stores request
debugging information.
Chapter 7. Developing with WAF 37
It is possible to have Developer Support enabled and not running. This still provides a measurable performance hit because it registers a listener. Once enabled in enterprise.init and recognized by the servlet container, you can turn Developer Support on and off via the administrator page. See Section 7.3.2.2 Developer Support Features for more information on controlling Developer Support via the administrator page.
7.3.2. Using Developer Support
7.3.2.1. Accessing Developer Support
To access Developer Support, login as a site-wide administrator and then navigate to /ds/ on your server. For example, if your server is running on your localhost and your Dispatcher Servlet Path is
/ccm, then you would access Developer Support at http://localhost/ccm/ds/.
7.3.2.2. Developer Support Features
Once you have accessed Developer Support, you will find an index page listing up to the last 100 requests to the server as well as several options for configuring your server:
Request Information
Developer Support stores information about the last 100 requests to the server, accessible through the index page. For each request, information such as the duration, the number of database operations executed, the requesting IP address, and the URL requested is available. This information is especially useful for performance-tuning. For example, if you are looking for a slow or expensive page in a certain use case, look for a request with an unusually long duration or large number of queries.
Click on a Request URL to find out detailed information about that individual request. The Request Information page provides details such as the amount of time for various stages of the request, and the text and duration of each SQL operation.
Click on a SQL Operation to find detailed information about that specific operation. The Query Information page provides a stack trace showing the code that led to the SQL operation’s exe­cution. Also, if the server is running on an appropriately configured Oracle database, the page will provide a link to show the explain plan of the query.
Query Log
On the Developer Support index page, next to each request, is a link to a Query Log. A request’s Query Log page presents a text page of all the SQL operations for that request, formatted for easy cut-and-paste into a database client such as SQLPlus or PSQL.
Log4j Logger Adjuster
The Developer Support index page provides a link to a Log4j Logger Adjuster. This Log4j adjuster allows the developer to change the server’s logging level at runtime. See also Section
7.5.1 Log4J
Disable Request Logging
The Disable Request Logging link on the Developer Support index page allows you to toggle at runtime whether Developer Support actively stores information about each request. If you disable request logging, Developer Support will clear all previously stored request information as well as stop recording information about new requests.
38 Chapter 7. Developing with WAF
Show Hits To Developer Support
The Show hits to developer support link on the Developer Support index page toggles the tracking of requests against the Developer Support application itself. As most developers will not be interested in this information, this information is usually left hidden.
7.4. Setting Up Eclipse and WAF
The following sections describes how to set up a WAF development and debugging environment within Eclipse, an open source Integrated Development Environment (IDE).
7.4.1. Installing Eclipse
1. Install WAF — install as normal, using the RPM’s and WAF Autobuild system. For more infor­mation, see the Red Hat Web Application Framework Installation Guide:.
2. Install Eclipse Eclipse can be obtained from http://www.eclipse.org. Install using RPM, if possible.
In order to take advantage of the anti-aliased fonts within Eclipse, use a build of Eclipse designed for the GTK in Red Hat Linux 8.0 and later.
3. You may also wish to install the Perforce, Resin, and JavaCC plugins for Eclipse, which can be found at:
http://sourceforge.net/projects/p4eclipse
http://www.improve-technologies.com/alpha/resin
http://sourceforge.net/projects/eclipse-javacc
4. To install the Perforce and Resin plugins, use the Eclipse Update Manager by selecting Help = Software Updates =Update Manager. Next, add bookmarks for the download URLs listed
by the plugins’ Web sites. The Update Manager can then automatically download and install the Perforce and Resin plugins.
To install the JavaCC plugin, download the plugin and unzip it in Eclipse’s plugin directory. Then, follow the instructions in the plugin’s README file.
5. Install Resin — if you are using the Resin application server. You may download Resin at Cau­cho’s website: http://caucho.com. You may also want to download the source code for Resin in order to trace through its code when debugging.
7.4.2. Eclipse Preferences Configuration
To set preferences within Eclipse, go to Window =
Workbench =
Java =
options:
Unreachable code
Resolvable import statements
Editors. There is a key bindings option where you can select Emacs key bindings.
Compiler =Errors and Warnings. You probably only want to select Error for the
Preferences. Noteworthy preferences include:
Chapter 7. Developing with WAF 39
Tip
It is recommended to only select Warning for the options:
Methods overridden but not package visible
Methods with a constructor name
Hidden catch blocks
Otherwise, your compilations will output hundreds of messages in your Tasks window.
7.4.3. Eclipse Project Configuration
This section covers adding and configuring projects to Eclipse for developing with WAF.
7.4.3.1. Adding Projects
WAF
1. In Eclipse, go to File =
New =Project.
2. Select Java Project.
3. Name the project, e.g. webapp and select your existing WAF core-platform directory as the project directory.
4. In the source tab, make sure that Eclipse lists the WAF source directory, src/. If it is not there, click Add Existing Folders... to add it. You may also want to add the directory for the Junit test code, test/src/.
After adding the existing source folders, click on Create New Folder... and name the folder
_generated. This is where Eclipse will store its JavaCC-generated source files.
5. Select the Libraries tab once you are presented with the Java Settings dialog box.
6. Click on Add JARs and select all the JARs under your WAF Project (typically under lib and etc/lib).
7. Click on Add External JARs and add the following JARs:
$ORACLE_HOME/jdbc/lib/classes12.zip
$RESIN_HOME/lib/jta-spec JAR
$RESIN_HOME/lib/resin.jar
8. Click on the Order and Export tab. Select All.
9. Create a build folder for Eclipse on your file system. Then, enter the path to that build folder in the Build output folder input box.
10. Click on the Finish button.
40 Chapter 7. Developing with WAF
CMS and Other Projects
To add CMS or other projects to Eclipse, follow the same steps as for adding WAF, with the following exceptions:
For CMS and other projects that depend on WAF, you do not need to add external JARs.
Eclipse will pick up these libraries from WAF.
When you reach the Java Settings dialog box, select the Projects tab. For CMS, add a de-
pendency upon WAF. For projects that use both WAF and CMS, add a dependency upon both WAF and CMS.
Create build folders for the respective projects.
7.4.3.2. Configuring Eclipse for Perforce
For each project that you have added, go to the Package Explorer and right-click on it. Then, select
Team =
Share Project. Select the options for Perforce. Now, you should see a Perforce menu and
Perforce options for your source files.
7.4.3.3. Building and Deploying with Eclipse
Eclipse uses its own build directories for compiling your source files. However, you will probably still want to use the WAF build system for building and deploying your application. Therefore, you should create a shell script that will do this for you. Then, you may call this shell script to build and deploy WAF from within Eclipse when you are ready to run your application.
Here is an example shell-script, which should be modified appropriately to fit the target environment.
#!/bin/bash
#setup environment . /etc/profile.d/ccm-config.sh . /etc/profile.d/ccm-devel.sh . /etc/profile.d/ccm-scripts.sh
CCM_HOME=/var/ccm-devel/dev/user/cms_dev ORACLE_HOME="/opt/oracle/product/9.2.0" JAVA_HOME=/opt/IBMJava2-131
CLASSPATH=$CCM_HOME/core-platform/lib/jaas.jar CLASSPATH=$CLASSPATH:$CCM_HOME/core-platform/lib/jce.jar CLASSPATH=$CLASSPATH:$CCM_HOME/core-platform/lib/sunjce_provider.jar CLASSPATH=$CLASSPATH:$CLASSPATH:$ORACLE_HOME/jdbc/lib/classes12.zip CLASSPATH=$CLASSPATH:$CCM_HOME/core-platform/etc/lib/iDoclet.jar export CLASSPATH
export ANT_OPTS="-Xms128m -Xmx128m"
########## #build ##########
#uncomment to make enterprise.init #cd $CCM_HOME #ant make-config #ant make-init
Chapter 7. Developing with WAF 41
#clean cd $CCM_HOME ant clean
#build cd $CCM_HOME ant deploy
#javadoc cd $CCM_HOME ant javadoc ant deploy-api
Example 7-1. Shell-script to build and deploy from within Eclipse
In your ant.properties file, make sure that you have set compile.debug=on. Once you have created this shell script, in Eclipse go to Run =
External Tools =Configure. A dialog box will
appear for configuring external tools. Select New and in the Tool Location input box, select your shell script for deploying WAF. Select the options:
Block until tool terminates
Show execution log on console
Then, select OK. Now, in Eclipse, your shell script will appear under Run =External Tools. Use that to build and deploy WAF from within Eclipse.
7.4.4. Eclipse Debugging Configuration
To do runtime debugging of WAF within Eclipse, you will need to add a new Resin launch config­uration for running WAF and a new Remote Java Application debug configuration for attaching to WAF.
WAF Execution Setup
To be able to run WAF from Eclipse, select Run =
Run. Select Resin and then click on New.
Then, enter information for the following tabs:
Main Tab
To set your project, select the highest-level project, which is the project no other projects are dependent on. This is where you select your resin.conf.
Arguments Tab
In the VM arguments text box, enter the following, all in one line — this line is broken for printing purposes with a "\":
-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=\ dt_socket, server=y, suspend=n,address=8000 -Xms128m -Xmx128m
You may allocate more memory than 128 MB to the server if you wish.
Classpath Tab
Add all the external JARs under $RESIN_HOME/lib to your user classes.
Common Tab
At the bottom of the dialog box, select Display in favorites menu: Run.
You are now finished and may close the dialog box.
42 Chapter 7. Developing with WAF
WAF Debugging Setup
In Eclipse, select Run =Debug. In the dialog box, select Remote Java Application and then click on New. Then enter information for the following tabs:
Connect Tab
Enter a name for your debug configuration, such as webapp-debug. Select the same project previously selected as the main project. Enter the host name (localhost should be fine). Use
8000 as your connection port. Also, select Allow termination of remote VM.
Source Tab
Add all the external JARs under $RESIN_HOME/lib to your source lookup path.
Common Tab
At the bottom of the dialog box, select Display in favorites menu: Debug.
You are now finished and may close the dialog box.
7.4.5. Eclipse Setup Validation
Your Eclipse setup should now be complete. To test this:
1. Select Run =
2. Select Run =Run and pick the WAF configuration. The server’s log output should become visible in the Console. Wait for the server to finish initializing.
3. Select Run = Open a source file, set a breakpoint, and request a page from the server.
If everything is setup correctly, you can now develop with Eclipse and use it to step through your WAF code.
External Tools =[Deploy Script].
Debug and pick the debug configuration. This opens the Debug perspective.
7.5. Using logging for debugging
When code breaks, there are two ways to figure out what went wrong: launch a debugger and step through the program, or add logging statements to see what is going on. In its crudest form, logging means adding a few System.err.println() calls here and there. What are the benefits of logging over using a debugger?
The Practice of Programming by Brian W. Kernighan and Rob Pike
As a personal choice, we tend not to use debuggers beyond getting a stack trace or the value of a variable or two. One reason is that it is easy to get lost in details of complicated data structures and control flow; we find stepping through a program less productive than thinking harder and adding output statements and self-checking code at critical places. Clicking over statements takes longer than scanning the output of judiciously-placed displays. It takes less time to decide where to put print statements than to single-step to the critical section of code, even assuming we know where that is. More important, debugging statements stay with the program; debugger sessions are transient.
Another important use of logging is to provide an audit trail: who did what and when. The predominant pattern in WAF is to write such auditing information to the database rather than the filesystem. The logging framework in WAF is intended to be used primarily for troubleshooting and debugging.
Chapter 7. Developing with WAF 43
7.5.1. Log4J
Using System.out.println() for logging has numerous drawbacks.
1. It degrades performance. There is no easy way to conditionally turn off logging statements system-wide, without resorting to error-prone and maintenance-intensive workarounds such as:
if (DEBUG) {
System.err.println("foo=" + foo);
}
where DEBUG is a system-wide boolean parameter that you can easily set to true or false.
2. Even with the above workaround, you don’t get a fine-grained degree of control. The logging is either on, or off across the board. You could, of course, introduce more than one system-wide flag, like so:
if (WARN) {
System.out.println("warn: bar=" + bar);
}
3. You are constrained to outputting only to the standard out and error devices. What if you need to output to rotated log files? Additional custom code is required to support this requirement.
Going down this path slowly but surely, you may end up developing a general-purpose logging library. Rather than reinventing the wheel, WAF relies on the Log4J library from the Jakarta project of the Apache Foundation. If you are not familiar with it, please skim through the online documentation at http://jakarta.apache.org/log4j/docs/. The rest of this document tries to highlight rather than explain the most salient Log4J features.
The key abstractions that Log4J provides are:
1. Ability to log to multiple devices. See the Javadoc for the Appender1interface and its im­plementing subclasses. There are many types of appenders included in Log4J: file appender, rolling file appender, remote syslog daemon appender, asynchronous appender, etc. Appenders are fairly easy to extend for special purposes. For instance, you could write a database appen­der. (In fact, several have already been written.) See Section 7.5.4 Custom Appenders for more discussion on this.
2. Ability to easily configure the format of the message. For example, each message can automat­ically include the timestamp, thread name, location (file name and line number), full or abbre­viated class and method name from the which the message originated, etc. See the Javadoc for
Layout (at http://jakarta.apache.org/log4j/docs/api/org/apache/log4j/Layout.html) and its sub-
classes.
3. API for logging errors and exceptions. See Section 7.5.2 Always log the Throwable.
4. Different logging levels. The minimum set of possible levels are off, fatal, error, warn,
info, debug, and all. Extending the list of levels is possible but discouraged. You can change
the requested level of logging either by altering your configuration files before system startup, or at runtime. More on this in Section 7.5.3 Runtime logging level manipulation. See also the Javadoc for Level (http://jakarta.apache.org/log4j/docs/api/org/apache/log4j/Level.html).
5. Fine-grained hierarchy of loggers. A logger is the central class that performs all logging op­erations. Loggers are named with dot-delimited names like foo.bar.baz or foo.bar.quux. Loggers form a hierarchy based on their names. You can configure and manipulate loggers in bulk by referring to their parent names. For example, both foo.bar.baz or foo.bar.quux are children of foo.bar.
Each logger has an associated logging level. By setting the logger’s level, you can control the amount of information logged. Log statements are enabled only if the level of statement is
1. http://jakarta.apache.org/log4j/docs/api/org/apache/log4j/Appender.html
44 Chapter 7. Developing with WAF
greater than or equal to the current level of the logger. For example, if you set the foo.bar.baz logger’s level to warn, it will only log message whose level is warn, error, or fatal. By setting the level for the foo.bar logger, you are automatically setting it for foo.bar.baz and
foo.bar.quux, unless overridden on a more specific level.
6. Miscellaneous other items such as filters2and flexible configuration.
The next sections examine some of these benefits in more detail.
7.5.2. Always log the Throwable
In general, if you catch an exception and decide not to rethrow it for whatever reason, you should log it. In doing so, avoid the following anti-pattern:
try {
doSomething();
} catch (FooException ex) {
ex.printStackTrace(); s_log.warn("foo occurred: " + ex.getMessage());
}
Example 7-2. Exception logging anti-pattern
The printStackTrace() method should not be used, because its output goes to the standard error device System.err rather than the devices managed by Log4J.
The getMessage() method should not be used, because it does not include the stack trace. Stack traces are invaluable for tracking down the source of error conditions.
The following pattern should be used instead.
try {
doSomething();
} catch (FooException ex) {
s_log.debug("foo occurred", ex); // or s_log.info("foo occurred", ex); // or s_log.warn("foo occurred", ex); // or s_log.log(Level.WARN, "foo occurred", ex);
}
Example 7-3. Log the Throwable
Note that the various logging methods provided by the Logger3class all take a Throwable parameter, i.e. an Error or Exception. Given a throwable, Log4J will print both the error message obtained via
getMessage() and the stack trace.
However, be careful not to fall into this trap:
2. http://jakarta.apache.org/log4j/docs/api/org/apache/log4j/spi/Filter.html
3. http://jakarta.apache.org/log4j/docs/api/org/apache/log4j/Logger.html
Chapter 7. Developing with WAF 45
try {
doSomething();
} catch (FooException ex) {
s_log.debug(ex);
}
Example 7-4. Swallowed stack trace anti-pattern
There is no debug(Throwable throwable) method — there is only debug(Object msg). Same goes for warn, info, etc. The above statement is essentially equivalent to the following:
s_log.debug(String.valueOf(ex));
This is probably not the intended result.
What if you want to log the current stack trace? The easiest way to do this is to log a new Throwable:
s_log.debug("got here", new Throwable());
Example 7-5. Logging the current stack trace
A variation of this technique is to add a public Throwable field to a class.
public class Frob {
// add the following line temporarily public final Throwable STACK = new Throwable();
}
Example 7-6. Tracking the origin of an object
Once this has been done, you can write code such as this:
class Foobar {
void doStuff(Frob frob) {
if ( !checksOutOK(frob) ) {
s_log.error("Got a sketchy frob", frob.STACK);
}
}
}
This allows you to figure the origin of the frob object at a distant point further down the execution path.
7.5.3. Runtime logging level manipulation
Logging levels are usually configured at system startup via the configuration file. It is possible to change the logger’s level at runtime. One way to do this is via the Developer Support interfaces. (See Section 7.3 Developer Support.) From the programmatic point of view, runtime manipulation of the logging level boils down to something as simple as this:
// assume s_log is instance of Logger s_log.setLevel(Level.WARN);
This sets the logging level for the s_log logger to warn. This means that messages output via, say,
debug(Object msg, Throwable throwable) will be suppressed, because warn is stricter than debug.
The standard pattern for instantiating loggers that is used throughout WAF is as follows:
46 Chapter 7. Developing with WAF
package foo.bar;
import org.apache.log4j.Logger;
public class Baz {
private final static Logger s_log = Logger.getLogger(Baz.class);
}
Example 7-7. Standard way of instantiating loggers
This instantiates a singleton logger named foo.bar.Baz. Note, however, that assigning the logger to a private static variable is merely a matter of syntactic convenience. If you ever need to, you can obtain the same logger from anywhere else in your code.
package foo.frob;
import org.apache.log4j.Logger;
class Nosey {
void doStuff() {
Logger logger = Logger.getLogger("foo.bar.Baz"); logger.debug("got the logger used by the Baz class.");
}
}
Example 7-8. Random access to loggers
There is a couple reasons why this might be useful.
1. This allows you to adjust the logging level of any logger at runtime. If you have a production system that starts exhibiting erratic behavior, you can turn up the logging level in selected classes without shutting down and restarting the system. This is what Developer Support allows you to do.
2. There are times when you want to see what goes on in the class Foo when you call a method reliant on Foo elsewhere in your code.
To elaborate on the last point: Consider the logging output generated by the
com.arsdigita.db.PreparedStatement logger. If the logging level for this logger is set to info, it will log every executed query and its bind variable values. If you are interested in seeing the
query your code generates, you can set this logger’s level to info in your config file. Note, however, that this will result in all queries being logged system-wide. That may be many more than you require. The alternative is to adjust the logging level at runtime:
// assume all the necessary imports
class Frobnivator {
void frobnivate() {
Logger logger = Logger.getLogger("com.arsdigita.db.PreparedStatement"); Level old = logger.getLevel(); logger.setLevel(Level.INFO);
doSomething();
logger.setLevel(old);
}
Chapter 7. Developing with WAF 47
}
Example 7-9. Adjusting logging level temporarily
In the above example, you end up enabling the PreparedStatement logger only for the duration of the doSomething() method. This may significantly cut down the number of queries logged, thus making it easier for you to see what is going on.
The obvious caveat here is that extraneous queries may still end up getting logged in the presence of multiple threads. This is because the logger’s level cannot be adjusted on a per-thread basis - it can only be changed globally.
7.5.4. Custom Appenders
Out of the box, WAF provides two appenders: ConsoleAppender4, wired to your standard output device (usually a TTY), and RollingFileAppender5tied to a file whose location is configurable.
You can also use other appenders. One of the interesting possibilities is the SocketAppender6. It expects a log viewer (or some other log processing application) to listen on the specified TCP port, possibly on a different machine. There are a number of GUI log viewers that work with the socket appender. These viewers give you fine-grained control over your logging output. See, for example,
Chainsaw7and Lumbermill8. Both of these log viewers allow you to filter the output by level,
logger, thread name, etc.
7.5.5. Beware of Buffered Streams
The Java language provides two abstractions for doing output: output streams9for working with sequences of bytes, and writers10for working with sequences of characters. An output stream or a writer can be buffered or unbuffered. For more information, read about BufferedOutputStream and about BufferedWriter12.
When I/O is buffered, it means that bytes or characters that you write do not necessarily go to their intended output device immediately. They are cached in an intermediate buffer. When the buffer fills up, it is flushed — an actual write to the underlying physical device occurs.
Why should you care about this? If you log a message to a buffered output immediately before the system crashes, you may not see the message logged anywhere. The system didn’t have a chance to flush the buffered stream or writer. This situation is fairly rare, but when it does happen, it may stump the unwary troubleshooter. If you want to be absolutely sure you are not losing any logging statements, you must use unbuffered output.
For example, the standard output System.out may be buffered, while the standard error device Sys-
tem.err is usually not. Therefore, System.err is preferable when you want to make sure no logging
is lost.
4. http://jakarta.apache.org/log4j/docs/api/org/apache/log4j/ConsoleAppender.html
5. http://jakarta.apache.org/log4j/docs/api/org/apache/log4j/RollingFileAppender.html
6. http://jakarta.apache.org/log4j/docs/api/org/apache/log4j/net/SocketAppender.html
7. http://jakarta.apache.org/log4j/docs/api/org/apache/log4j/chainsaw/package-summary.html
8. http://traxel.com/lumbermill/
9. http://java.sun.com/j2se/1.3/docs/api/java/io/OutputStream.html
10. http://java.sun.com/j2se/1.3/docs/api/java/io/Writer.html
11. http://java.sun.com/j2se/1.3/docs/api/java/io/BufferedOutputStream.html
12. http://java.sun.com/j2se/1.3/docs/api/java/io/BufferedWriter.html.
11
48 Chapter 7. Developing with WAF
What kind of I/O does log4j use: buffered or unbuffered? It depends on how the particular appender is configured. In the case of FileAppender13, you have the option of configuring it either way. See the methods getBufferedIO()14and setBufferedIO(boolean)15.
For maximum performance, you should use buffered I/O. Only switch to unbuffered I/O when you suspect logged information may be getting lost. By default, the file appender is buffered.
7.5.6. Performance Considerations
For maximum performance, follow these rules:
1. Turn off all unnecessary logging. Set the threshold to error, fatal, or off to minimize the amount of logging produced.
2. As has been pointed out in Section 7.5.5 Beware of Buffered Streams, make sure you are using buffered I/O for logging.
3. If logging statements appear in a performance-critical section of code, they should be wrapped inside an if statement that checks the current level of the logger. The "\" character has been inserted where the lines were artificially broken for printing purposes:
if ( s_log.isDebugEnbled() ) {
Object expensiveMsg = "parameter foo " + foo + " expected by the \
object " +
baz);
}
By making the above logging call conditional, we avoid the high cost of constructing the ex-
pensiveMsg object, if the logging level is to stricter than debug.
frob + " was not supplied. Oh, and the value of baz was " + \
s_log.debug(expensiveMsg);
13. http://jakarta.apache.org/log4j/docs/api/org/apache/log4j/FileAppender.html
14. http://jakarta.apache.org/log4j/docs/api/org/apache/log4j/FileAppender.html#getBufferedIO()
15. http://jakarta.apache.org/log4j/docs/api/org/apache/log4j/FileAppender.html#setBufferedIO(boolean)
Chapter 8.
WAF Application Development Tutorial
This tutorial introduces the concepts and techniques needed to create your own application in the Red Hat Web Application Framework. It describes how to define the application so that it can be packaged and installed wherever the WAF platform is installed.
8.1. Terms and Assumptions
In addition to the the assumptions in Section 1 Assumptions About WAF Developers (e.g. that you have a working understanding of object-oriented programming in Java, the Servlet API, relational schema design, XSLT, and CSS), you should understand the WAF kernel API.
Application
A bundle of data and UI logic that is reachable via the web. Refer to Chapter 6 WAF Component: Web.
Servlet
Code to handle a web request using the J2EE Servlet API. Throughout this document, we assume you are familiar with version 2.3 or later of the Servlet specification. Refer to http://jcp.org/aboutJava/communityprocess/first/jsr053/index.html.
Webapp
A web application package designed for deployment in a servlet container. A webapp instance corresponds to a ServletContext. A WAF webapp will typically contain one Application.
PDL
The Persistence Definition Language, used to define object types in the WAF persistence layer. Refer to Chapter 2 WAF Component: Persistence and Appendix D PDL Syntax.
8.2. Good Practices
One goal of this tutorial is to help you design an application that:
Uses sound patterns when integrating with WAF services.
Can be packaged and installed cleanly in environments other than your own.
One important consideration in developing your own application is namespace. It’s important to pick a well-qualified name for the domain of your application and then to use this qualifier uni­formly throughout your code. For the sample application we develop in this guide, we always use the
com.example.binder namespace for our PDL, Java, and other resource files.
There will be comments along the way telling you how to avoid pitfalls and where to take extra care.
8.3. Directories in the Work Area
When your webapp is complete, the packaging of the source and distribution archive depends on a canonical layout of directories and files. This section discusses where to put them in order to create portable, standard packages.
50 Chapter 8. WAF Application Development Tutorial
The standard layout of files in the build environment of a WAF application follows the guidelines set out in the Tomcat Application Developer’s Guide (http://jakarta.apache.org/tomcat/tomcat-4.1­doc/appdev/index.html), which are based on the Servlet specification.
binder/
The name of the directory you choose to hold your application source and build files. Our exam­ple application is a note binder, so we call our directory binder.
binder/pdl/
The PDL code for this application. As is common with Java code, it is organized into packages.
binder/pdl/com/example/binder/Binder.pdl binder/pdl/com/example/binder/Note.pdl
binder/sql/
Custom SQL for your application. Ordinarily developers will autogenerate their SQL from PDL, but if a custom schema is required, SQL files may be placed here to override automatic SQL generation. See Chapter 9 Persistence Tutorial for more information. Files in this directory are organized by relational database.
binder/src/
The source code and resources of your application reside here. As with library code, the source is organized in packages. For example:
binder/src/com/example/binder/Binder.java binder/src/com/example/binder/Note.java binder/src/com/example/binder/NoteCollection.java binder/src/com/example/binder/BinderServlet.java
binder/web/
The place for files that will become part of your webapp’s document root, such as JSPs, images, and XSL templates. Once deployed, these files will be directly addressable via HTTP.
binder/web/logo.png binder/web/note.jsp
binder/web/WEB-INF/
These are other resources such as compiled Java classes, property files, and resource bundles that are used internally by your application code. Such files are not addressable.
binder/web/WEB-INF/classes binder/web/WEB-INF/lib
Classes and jars that your application code uses go in classes/ and lib/, respectively. When you compile and produce your webapp, these jars and classes will go with it.
8.4. Build Tools
If you choose to use the WAF build tools, you will start out with commands to build and deploy your webapp from these directories. You can also use your own tools as long as you use this basic file layout and follow the guidelines set out in the Jakarta webapp developer’s document specified in Section 8.3 Directories in the Work Area.
Chapter 8. WAF Application Development Tutorial 51
8.5. Modeling Your Application
A new application is represented by a persistent Application object. A typical application uses the WAF persistence layer to store and query the state of an Application data object. An Applica-
tion is more than just storage, however. It is also a set of behaviors, principally having to do with
containment and dispatch. Refer to Section 6.1 Applications to learn more.
You will subclass Application in order to introduce your own persistent properties and your own application-specific behaviors. The data aspect of an Application is described using PDL.
This tutorial will use a sample application called Binder to illustrate how an Application is defined.
8.6. Persistent Object Types
A persistent object type definition is metadata that maps a Java object to storage in a relational schema. As with many things in WAF, Application is one of these object types. As is the pattern for any new application, our example creates a subtype of the Application type and adds properties to it.
model com.example.binder;
import com.arsdigita.web.Application;
// A Binder is an application that has a collection of Notes. // // @author Justin Ross object type Binder extends Application {
component Note[0..n] notes = join binders.binder_id to notes.binder_id;
reference key (binders.binder_id);
}
Example 8-1. binder/pdl/com/example/binder/Binder.pdl
This definition adds just one property to Application, notes. The notes property tracks the set of
Notes belonging to a Binder.
model com.example.binder;
// A Note has a title and and body and is a component of a Binder. // // @author Justin Ross versioned object type Note {
BigInteger[1..1] id = notes.note_id INTEGER; String[1..1] title = notes.title VARCHAR(200); String[0..1] body = notes.body VARCHAR(4000);
object key (id);
}
Example 8-2. binder/pdl/com/example/binder/Note.pdl
Observe that the Note object type is marked versioned. This means that any changes to Note will be recorded in a change log. The WAF versioning service allows you to resurrect a specific version and to list the changes made between two versions. For more information, refer to Section 4.11 Versioning Service.
Once you’ve defined your PDL, the build system will generate relational tables to back your object types.
52 Chapter 8. WAF Application Development Tutorial
8.7. Java Domain Objects
Each object type will have a corresponding domain class written in Java. The domain class exists to provide a Java-native API to your object type’s data and to behaviors that are specific to your application. Refer to Chapter 2 WAF Component: Persistence.
package com.example.binder;
import com.arsdigita.persistence.DataCollection; import com.arsdigita.persistence.DataObject; import com.arsdigita.util.Assert; import com.arsdigita.web.Application; import org.apache.log4j.Logger;
/**
* A collection of <code>Note</code>s. * * @see com.example.binder.Note * @author Justin Ross */
public class Binder extends Application {
private static final Logger s_log = Logger.getLogger(Binder.class);
public static final String BASE_DATA_OBJECT_TYPE =
"com.example.binder.Binder";
static final String NOTES = "notes";
protected String getBaseDataObjectType() {
return BASE_DATA_OBJECT_TYPE;
}
protected Binder(final DataObject data) {
super(data);
}
/**
* Returns all the Notes associated with this Binder. * * @return A <code>NoteCollection</code> of Note objects */
public final NoteCollection getNotes() {
return new NoteCollection((DataCollection) get(NOTES));
}
/**
* Adds <code>note</code> to the set of notes tracked by this * <code>Binder</code>. * * @param note The <code>Note</code> to remove */
public void addNote(final Note note) {
Assert.exists(note, Note.class);
if (s_log.isDebugEnabled()) {
s_log.debug("Adding note " + note + " to " + this);
}
add(NOTES, note);
}
Chapter 8. WAF Application Development Tutorial 53
/**
* Removes <code>note</code> from the set of notes tracked by this * <code>Binder</code>. * * @param note The <code>Note</code> to remove */
public void removeNote(final Note note) {
Assert.exists(note, Note.class);
if (s_log.isDebugEnabled()) {
s_log.debug("Removing note " + note + " from " + this);
}
remove(NOTES, note);
}
public final String getContextPath() {
return "/binder";
}
}
Example 8-3. binder/src/com/example/binder/Binder.java
The BASE_DATA_OBJECT_TYPE string specifies what PDL-defined object type this domain object corresponds to.
The getBaseDataObjectType() method makes the runtime data object type of Binder accessible to WAF persistence.
The DataObject constructor is a bit of plumbing necessary to make your DomainObject work, since a DomainObject wraps a DataObject.
Everything else on Binder is there merely to expose the properties of the Binder object type as defined in the PDL above. Binder’s UI will use the API defined here to fetch data from and to alter Binder objects.
Note
Binder.java uses string constants that correspond to PDL-defined properties. This way the Java
compiler will catch any typos where the property is used, and a change to the PDL-defined property name requires changing the Java code in just one place.
It’s a good idea for any WAF application to use logging to monitor important events in the lifecycles of its objects. In the above example, any time a note is added or removed, we log what happened. If down the line a bug appears and it’s unclear whether notes are getting added to the binder, the developer debugging the code can simply enable correct logging and refer to whether addNote is getting called. For more information about logging in WAF, refer to Section 7.5 Using logging for debugging.
54 Chapter 8. WAF Application Development Tutorial
package com.example.binder;
import com.arsdigita.db.Sequences; import com.arsdigita.domain.DomainObject; import com.arsdigita.persistence.DataObject; import com.arsdigita.util.Assert; import com.arsdigita.util.UncheckedWrapperException; import java.io.IOException; import java.io.Writer; import java.sql.SQLException; import java.math.BigInteger; import org.apache.log4j.Logger;
/**
* A bit of text with a title. * * @see com.example.binder.Note * @author Justin Ross */
public class Note extends DomainObject {
private static final Logger s_log = Logger.getLogger(Note.class);
public static final String BASE_DATA_OBJECT_TYPE =
"com.example.binder.Note";
static final String ID = "id"; static final String TITLE = "title"; static final String BODY = "body";
protected String getBaseDataObjectType() {
return BASE_DATA_OBJECT_TYPE;
}
protected Note(final DataObject data) {
super(data);
if (isNew()) {
try {
set(ID, Sequences.getNextValue().toBigInteger());
} catch (SQLException sqle) {
throw new UncheckedWrapperException(sqle);
}
}
}
/**
* Gets the note’s unique ID. * * @return The <code>BigInteger</code> ID; it cannot be null */
public final BigInteger getID() {
return (BigInteger) get(ID);
}
/**
* Gets the title of the note. * * @return The <code>String</code> title; it cannot be null */
public final String getTitle() {
return (String) get(TITLE);
}
Chapter 8. WAF Application Development Tutorial 55
/**
* Sets the title of the note. * * @param title The <code>String</code> title; it cannot be null */
public final void setTitle(final String title) {
if (s_log.isDebugEnabled()) {
s_log.debug("Setting title of " + this + " to " + title);
}
Assert.exists(title, String.class);
set(TITLE, title);
}
/**
* Gets the body of the note. * * @return The <code>String</code> body; it may be null */
public final String getBody() {
return (String) get(BODY);
}
/**
* Sets the body of the note. * * @param body The <code>String</code> body; it may be null */
public final void setBody(final String body) {
if (s_log.isDebugEnabled()) {
s_log.debug("Setting body of " + this + " to " + body);
}
if (Assert.isEnabled() && body != null) {
Assert.truth(body.length() <= 4000, "The body text is too long");
}
set(BODY, body);
}
/**
* Writes an XML representation of the note and its content. * * @param out A <code>Writer</code> to write the XML to */
public final void render(final Writer out) throws IOException {
out.write("<note id=\"" + getID() + "\">"); out.write("<title>" + getTitle() + "</title>");
final String body = getBody();
if (body != null) {
out.write("<body>" + getBody() + "</body>");
}
out.write("</note>");
}
}
Example 8-4. binder/src/com/example/binder/Note.java
56 Chapter 8. WAF Application Development Tutorial
In many ways, Note.java is just like Binder.java. It declares its data object type; it defines an instantiator; and it adds some plumbing for getting the runtime data object type and for constructing a wrapper DomainObject.
Since the properties of the data object type differ from those of Binder, the Java accessors and mutators are different. Of more interest is the fact that Note has additional business logic for outputting the
Note object as XML.
Note
The setTitle method uses assertions to enforce the contract set out in the object-type definition. The title property is marked [1..1] in Note.pdl, so it cannot have a null value. Therefore, we assert that calling setTitle(null) is a failure condition.
Note
If your code includes assertions that do work in their arguments, make sure to wrap the assertion code in an if statement that checks that assertions are turned on:
if (Assert.isEnabled() && body != null) {
Assert.truth(body.length() <= 4000, "The body text is too long");
}
Example 8-5. Using assertions
Be careful, however, that this is what you really want. Assertions exist to detect unexpected, unrecov­erable program failures. Don’t use them for other types of errors, such as user input validation, since they can be disabled.
The WAF persistence API gives us efficient cursor-oriented access to the elements of an association. The NoteCollection object gives our UI code access to the data for each row returned from a query for multiple Notes.
package com.example.binder;
import com.arsdigita.domain.DomainCollection; import com.arsdigita.persistence.DataCollection; import org.apache.log4j.Logger;
/**
* Represents a collection of {@link com.example.binder.Note notes}. * * @author Justin Ross */
public class NoteCollection extends DomainCollection {
private static final Logger s_log = Logger.getLogger(NoteCollection.class);
public NoteCollection(final DataCollection data) {
super(data);
}
/**
* Gets the title of the current note.
Chapter 8. WAF Application Development Tutorial 57
* * @return The <code>String</code> title; it cannot be null */
public final String getTitle() {
return (String) get(Note.TITLE);
}
/**
* Gets the body of the current note. * * @return The <code>String</code> body; it may be null */
public final String getBody() {
return (String) get(Note.BODY);
}
}
Example 8-6. binder/src/com/example/binder/NoteCollection.java
8.8. Initializing the Application Runtime Environment
To get your application up and running, there are some things you need to do every time the WAF runtime starts up. Initializers exist to help you execute such code.
package com.example.binder;
import com.arsdigita.db.DbHelper; import com.arsdigita.domain.DomainObject; import com.arsdigita.domain.DomainObjectInstantiator; import com.arsdigita.persistence.DataObject; import com.arsdigita.persistence.pdl.ManifestSource; import com.arsdigita.persistence.pdl.NameFilter; import com.arsdigita.runtime.CompoundInitializer; import com.arsdigita.runtime.DomainInitEvent; import com.arsdigita.runtime.PDLInitializer; import com.arsdigita.runtime.RuntimeConfig; import org.apache.log4j.Logger;
/**
* Initializes the binder application. * * @see com.example.binder * @author Justin Ross */
public class Initializer extends CompoundInitializer {
private static final Logger s_log = Logger.getLogger(Initializer.class);
public Initializer() {
final String url = RuntimeConfig.getConfig().getJDBCURL(); final int database = DbHelper.getDatabaseFromURL(url);
add(new PDLInitializer
(new ManifestSource
("binder.pdl.mf",
}
public void init(final DomainInitEvent e) {
new NameFilter(DbHelper.getDatabaseSuffix(database), "pdl"))));
super.init(e);
58 Chapter 8. WAF Application Development Tutorial
e.getFactory().registerInstantiator
(Binder.BASE_DATA_OBJECT_TYPE,
new DomainObjectInstantiator() {
protected final DomainObject doNewInstance
}
}
}
});
(final DataObject data) {
return new Binder(data);
Example 8-7. binder/src/com/example/binder/Initializer
In the section above, you defined object-relational mapping metadata. The PDLInitializer serves to load that metadata into the runtime. This allows the persistence layer to interpret requests for the data behind your persistent object types.
The Binder and Note persistent object types have corresponding domain classes written in Java. The domain APIs will return the Java objects, but to do so we must map the object type to something that can instantiate the Java class.
public void init(final DomainInitEvent e) {
super.init(e);
e.getFactory().registerInstantiator
(Binder.BASE_DATA_OBJECT_TYPE,
new DomainObjectInstantiator() {
protected final DomainObject doNewInstance
}
}
});
(final DataObject data) {
return new Binder(data);
Example 8-8. Coupling data objects to domain object instantiators
When your package is loaded, the package tool will store the initializer class name you provide in the database. When the CCM runtime starts up, it will read out all the registered initializers and run them in order to prepare the environment.
8.9. Creating a Web Interface
WAF uses the Servlet API for handling web requests. Any standard servlet, adapted to use the WAF servlet facilities (com.arsdigita.web.BaseApplicationServlet and related classes), can be used to serve your content. For our example application, we will use a basic Servlet to create our UI . For information on how to create a UI for your application using Bebop, refer to Chapter 12 Presentation (Bebop) Tutorial.
package com.example.binder;
import com.arsdigita.web.Application; import com.arsdigita.web.BaseApplicationServlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger;
Chapter 8. WAF Application Development Tutorial 59
/**
* A servlet to serve pages of the binder application. * * @see com.example.binder.Binder * @author Justin Ross */
public final class BinderServlet extends BaseApplicationServlet {
private static final Logger s_log = Logger.getLogger(BinderServlet.class);
public void doService(final HttpServletRequest sreq,
throws ServletException, IOException {
sresp.getWriter().write("YO");
}
}
final HttpServletResponse sresp, final Application app)
Example 8-9. binder/src/com/example/binder/BinderServlet.java
An Application is associated with a path into the servlet container. The WAF dispatcher will look up your application and use its getContextPath and getServletPath methods to dispatch to your UI code. Our webapp will be mounted as binder in the servlet container, so we specify Binder’s context path to be /binder.
public final String getContextPath() {
return "/binder";
}
Example 8-10. Setting the application’s dispatch destination
When the dispatcher encounters a request for an instance of the binder application, it will look up the appropriate Binder object and use its getContextPath and getServletPath methods to dispatch to the appropriate servlet code, in this case BinderServlet.
By specifying the servlet path this way, we index into the servlet container’s native method of dispatch, by pattern. Your servlet container uses a webapp-standard file web.xml to declare pattern-to-servlet mappings.
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>binder</servlet-name> <servlet-class>com.example.binder.BinderServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>binder</servlet-name> <url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
Example 8-11. binder/web/WEB-INF/web.xml
60 Chapter 8. WAF Application Development Tutorial
8.10. Integrating Your Package With CCM Tools
The WAF environment includes a set of tools for loading and configuring software such as our example application. In order to integrate with those tools, your application needs to supply some information in the right places.
The CCM package loader, part of the ccm command line tool, takes a package and loads its initial configuration, schema, and data. ccm is documented in the Red Hat Web Application Framework Installation Guide.
$ ccm load --interactive binder
This command will ultimately run a Loader, a class that loads a package’s schema and data. So that the CCM tool can invoke the correct loader, a file of the form ${package-key }.load is required directly under your application’s src/ directory.
<?xml version="1.0" encoding="utf-8"?> <load>
<provides>
<table name="binders"/> <table name="notes"/>
<initializer class="com.example.binder.Initializer"/> </provides> <scripts>
<schema directory="binder"/>
<data class="com.example.binder.Loader"/> </scripts>
</load>
Example 8-12. binder/src/binder.load
The table names in theprovidesblock will be checked to ensure they exist when the package tool prepares to load the binder package. The initializer classname dictates what initializer will be run when the CCM runtime starts up. The schema directory given in thescriptsblock tells the base
Loader implementation where to find the schema it will load. The optional data class allows you to
use a Loader implementation specific to your package.
8.11. Creating Upgrade Scripts
Upgrading to new versions of WAF applications may require you to also migrate your data. WAF supports two complementary approaches to data migration: Java upgrade programs and SQL scripts.
Java upgrade programs have full access to the WAF and application APIs. These programs are typ­ically used to do more complex migrations where executing business logic is required to correctly migrate existing data.
SQL upgrade scripts rely on SQL and the embedded database procedural language (e.g., PL/SQL) to migrate data. SQL scripts are typically used to change database schemas. They also may provide superior performance in migrating large amounts of data.
The purpose of this section is to assist you in creating and hooking up your Java upgrade script to take advantage of WAF APIs. The process for performing the actual application upgrade from the installation/administration side using the ccm upgrade tool is covered in the WAF Installation Guide.
Chapter 8. WAF Application Development Tutorial 61
Creating A ccm Tool Upgrade Script in Java
1. Create arbitrary Java code that is accessible via a static main() method.
## file called com/arsdigita/cms/AnUpgrade.java
package com.arsdigita.cms;
public class AnUpgrade {
public static void main(String[] args) {
System.out.println("Sample upgrade running");
}
}
2. Create or edit the upgrade spec file at ${package_name}.upgrade on the classpath. The file described looks like this:
## file called ccm-cms.upgrade <upgrade>
<version from="6.0" to="6.1">
<script class="com.arsdigita.cms.RickshawPublishAPIUpgrade"/> </version> <version from="6.0" to="7.0">
<script class="com.arsdigita.cms.AnUpgrade"/>
<script sql="ccm-cms/upgrade/::database::-6.0-7.0.sql"/> </version>
</upgrade>
Creating a SQL Upgrade Script
1. Create arbitrary SQL code to update the schema or data, and place the code in a file with the
.sql suffix. Group all related functional changes in a single file and assign the file a descriptive
functional name e.g., update-host-unique-index.sql.
2. Update the appropriate parent upgrade script to reference the created SQL script. Parent scripts are stored in sql/ccm-core/upgrade. Each database has its own set of versioned upgrade scripts, e.g., oracle-se-6.0.1-6.1.0.sql.
For Java upgrade scripts, the class for the given version-version pair will be invoked (via its
main() method) when the user runs, e.g., ccm upgrade ccm-cms --from-version 6.0
--to-version 7.0. For SQL scripts, the given SQL file will be loaded as a resource from the
classpath and run. The "::database::" placeholder will be substituted with a short string identifying the current database, oracle-se or postgres, before the resource is loaded.
Note
Only exact matches on both the from and the to versions will trigger script execution.
62 Chapter 8. WAF Application Development Tutorial
Chapter 9.
Persistence Tutorial
This chapter presents a tutorial for using the persistence system. It presumes you are familiar with the concepts covered in Chapter 2 WAF Component: Persistence.
This tutorial will first discuss using Data Objects, which is followed by a tutorial on using the features of PDL. Referenced throughout this tutorial is the Persistence Glossary.
9.1. Data Objects Tutorial
This section has been assembled to help familiarize you with the concepts and functionality of the persistence layer. This section assumes a familiarity with the concepts discussed in Chapter 2 WAF Component: Persistence and in Persistence Glossary. It also assumes that the code is being executed within a standard WAF installation.
This tutorial will walk you through the use of object persistence in the WAF. It begins by showing you how to ensure that your server is set up correctly so that the PDL will compile. It then continues with a simple example of how to create a PDL file and how to write the corresponding Java code to interact with the database.
After covering the basics of using standalone Data Objects, the tutorial describes how to associate objects to each other. It discusses several examples, including how to retrieve Data Objects through arbitrary queries. The tutorial concludes with a list of common mistakes that are made by developers.
9.2. Beginning With Data Objects
This section starts with the basic steps that are required to access information in the database using the persistence layer. It begins by discussing PDL and Data Objects. It then discusses how to create the database schema and how it can be represented in PDL. Finally, it covers using the properties defined in the PDL to access the database in your Java code.
9.2.1. Data Objects and PDL: How are they related?
The persistence layer can be looked at as a way to access objects within the database. Examples of objects that can be stored in the database are Users, Groups, Articles, Images, and Email Addresses. In order to abstract out the information regarding object storage within the database, the persistence layer has implemented the concept of a single Data Object. Data Objects are used by Java classes to handle all interaction with the database. Because this class is provided, other classes do not need to know how to create, retrieve, update, or delete a given object. Data Objects are defined by their Attributes and Object Key.
Every Data Object is associated with a single Object Type. Every object type, in turn, is associated with events and operations that specify how information is stored in the database and how information in the database is mapped to Java variables. The role of the PDL file (see Section 9.2.3.1 The PDL File) is to provide developers with a mechanism to easily specify that object type.
9.2.2. Setting up the Schema
When defining your persistence layer, you need to work through two aspects of your design:
A UML model of your Data Objects.
64 Chapter 9. Persistence Tutorial
A database schema to handle the storage of the Data Objects.
Some designers may feel more comfortable starting with a database design and then designing the objects that use the schema, or vice versa. You may begin with either one, but both should be designed as part of the persistence layer. The UML model is typically designed for the Data Objects using a UML modeling tool.
This tutorial will use a hypothetical database schema composed of publications, magazines, articles, paragraphs, and authors. This goal of this tutorial is to help you better understand how this technology can be used with WAF. In practice, the entire data model below will be automatically generated by the persistence layer using the metadata defined in the PDL file. Therefore, none of the data model below will have to be created by the developer.
The tutorial will begin by using two simple objects: a Publication and a Magazine. The schema will be expanded throughout the tutorial. To see the full schema that is used throughout the tutorial, please see Appendix D PDL Syntax.
create table publications (
publication_id integer
type varchar(400)
name varchar(400)
constraint publications_pub_name_un unique(type, name)
);
create table magazines (
magazine_id integer
issue_number varchar(30)
);
constraint publications_pub_id_nn not null constraint publications_pub_id_pk primary key,
constraint publications_pub_type_nn not null,
constraint publications_pub_name_nn not null,
constraint magazines_magazine_id_fk references publications constraint magazines_magazine_id_pk primary key,
9.2.3. Defining an Object Type in PDL
9.2.3.1. The PDL File
A PDL file is a text file that has a .pdl extension and can be parsed using the PDL grammar (Appendix D PDL Syntax). It is made up of a model declaration followed by block declarations. These block declarations can be object type definitions, association blocks, Dara Query blocks, and Data Operation blocks.
The only required item is the Model declaration, as this declaration informs the compiler of which namespace to use. It is common to have one for all DataQuery and DataOperation definitions and one file for each Object Type. However, it is possible to combine all Object Type definitions into a single file. It is also possible to include DataQuery and DataOperations in their own files or in­line with Object Type Definitions. Data Associations are normally found with one of the Object Type definitions used within the association.
You can find a list of PDL reserved words, as well as the PDL grammar, in Appendix D PDL Syntax.
Chapter 9. Persistence Tutorial 65
9.2.3.2. Model and Object Type
When creating a PDL file, the first line of the file must be the name of the model that defines the namespace for the block definitions in the PDL file. This is similar to the name of the package in a Java class and is necessary to avoid name collisions of object types that have the same name and which exist in different PDL files. This example will use the tutorial model.
The second block of lines within a PDL file can either be a list of namespaces to import (similar to Java’s import statement) or the declaration of the object type itself. See Section 9.2.6 Object Type Inheritance for more information about importing.
The object type definition follows the import statement. The definition of an object type may include attributes and event definitions. The Publication object type defined below has two attributes defined. One of these attributes, id, is also part of the object key, which means that a Publication is uniquely identified by the id attribute value.
The first block of code within the object type definition is a list of persistence attributes and mappings of those attributes to database columns for the given object. Note that the attribute names do not need to be the same as the column names. The attributes are a list of Java variables that a given Data Object class can access. Finally, notices that in addition to the java data type at the beginning of the attribute mapping the SQL data type is also present. This SQL data type allows for DDL generator to generate the correct SQL. For a complete list of supported Java types, see Section D.2.2 PDL Attribute Types
// declare the namespace as "tutorial" using the "model" keyword model tutorial;
// there are not any import statements because Publication does not // extend anything.
// next comes the "object type" keyword followed by the name of // the object type object type Publication {
// the first block of code within the object type is a set of // mappings from the Attribute Java type and Attribute name to // the database column to which they correspond. BigDecimal id = publications.publication_id INTEGER; String name = publications.name VARCHAR(400);
}
9.2.3.3. Object Key
For each object type, you must define an object identifier for the purposes of uniquely identifying object instances at run time. If the object type does not have a super type, the object key syntax is used. If the object has a super type, a reference key must be declared to indicate how this object joins with the supertype. (See Section 9.2.6 Object Type Inheritance for more details.) The following example indicates how to designate that the Publication’s id attribute is the object identifier or key:
model tutorial;
object type Publication {
BigDecimal id = publications.publication_id INTEGER; String name = publications.name VARCHAR(400); String type = publications.name VARCHAR(400);
object key (id);
}
66 Chapter 9. Persistence Tutorial
This object type definition almost models the SQL that we have defined above but it is missing any mention of the unique constraint. To allow for this and to allow the DDL generator to correctly gen­erate unique constraints the persistence layer allows you to specify either a single property or a set of properties as being unique. The syntax for this is as follows:
SINGLE UNIQUE PROPERTY: object type User {
BigDecimal id = users.id INTEGER; unique String[0..1] screenName = users.screen_name VARCHAR(100); ...
}
SET OF UNIQUE PROPERTIES: object type Node {
BigDecimal id = nodes.id INTEGER; Node[0..1] parent = join nodes.parent_id to nodes.id; String[1..1] name = nodes.name VARCHAR(100);
object key (id); unique (parent, name);
}
Therefore, in order to correctly model the publications, we need to add the constraint. The correct, full publications object type defintion is below:
model tutorial;
object type Publication {
BigDecimal id = publications.publication_id INTEGER; String name = publications.name VARCHAR(400); String type = publications.name VARCHAR(400);
object key (id);
unique (name, type);
}
Note
Attributes cannot have the same name as a pdl reserved word without special quoting. For a list of reserved words and escaping techniques, see Section D.2 PDL Reserved Words.
9.2.4. Getting Started with the API
Now that we know how to specify an Object Type using PDL, you can learn how to access the in­formation from Java. The first step is to examine the public API that is provided by the persistence system.
The persistence layer in the Web Application Framework is implemented in the Java package (http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/package-summary.html). This package contains a set of classes and interfaces for working with persistent objects that store themselves in a relational database. Some of the URLs listed contain a visual break with the "\" character for printing formatting.
Chapter 9. Persistence Tutorial 67
SessionManager — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\ Session-
Manager.html.
This class is responsible for initializing the Session class. Users of persistence will use it to get a pointer to the Session class through SessionManager.getSession() (http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\ SessionManager.html#getSession()).
Session — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/Session.html
All persistence operations take place within the context of a session. The Session object contains methods for creating and retrieving data objects, data queries, and data operations.
DataObject — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/DataObject.html
This interface defines the public methods for Data Objects; see Data Object. Data Objects are normally accessed through the use of DomainObjects (http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/domain/DomainObject.html).
OID — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/OID.html
This class represents a unique identifier for a given Data Object. It typically holds the information in the object key (Object Key) of the object type. For the WAF Object type, the OID is the combination of the "acs object type" (e.g., com.arsdigita.User) and the "object id". This is typically used to retrieve specific objects from the database.
DataQuery — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/DataQuery.html
This class represents an interface for retrieving arbitrary information from the database. It is the Java equivalent of the PDL data query described in Section 9.4.1 Data Queries.
DataOperation — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\ DataOper-
ation.html
This class represents an interface for executing arbitrary DML within the database. It is the Java equivalent of the PDL data operation described in Section 9.4.2 Data Operations.
DataCollection — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\ DataCol-
lection.html
This class is used to represent a collection of data objects. DataCollections can be used to efficiently iterate over a large set of DataObjects and access the values of their properties. These can be filtered in a manner similar to DataQueries.
DataAssociation — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\ DataAs-
sociation.html
This class is similar to the Java Collection interface, in that it provides methods that act on the entire set of associations, such as add(DataObject
object) (http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\
DataAssociation.html#add(com.arsdigita.persistence.DataObject)), and methods that return other objects to access the associations, such as
cursor()(http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\
DataAssociation.html#cursor()).
DataAssociationCursor — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\
DataAssociationCursor.html
Like a Java Iterator, this is used to loop over a set of associations. Developers can also add Filters to DataAssociationCursors, thereby restricting the objects returned to the desired subset.
Filter — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/Filter.html
This class is used to filter the results of associations and collections. For instance, if a developer only wants the associated objects with a name starting with "l" then a filter is used.
68 Chapter 9. Persistence Tutorial
PersistenceException — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\
PersistenceException.html
This is an unchecked exception that is thrown whenever there is an error within the persistence system. It typically contains an error message as well as some debugging information.
9.2.5. Creating and Retrieving Objects in Java
Now that you have written your PDL files and your database is loaded, the next step is to create Data Objects to access the information in the database. The main access point to Data Objects is through the Session class. Session objects allow you to instantiate data objects that are part of a logical client session. Specifically, there are three methods that allow you to create and retrieve objects as well as a method allowing you to delete an object.
Suppose you have defined a Publication and want to create a new DataObject so that you can store information about it. The first step is to use the Session.create(String typeName) method. You can pass in the fully qualified name of the Object Type and get a Data Object back that corresponds to the defined events. For instance, to create a new Publication, you can do the following (remove the "\" and make it all one line):
DataObject publication = SessionManager.getSession().create\ ("tutorial.Publication");
If you want to retrieve an object that already exists (and you have the OID), you can use the Ses-
sion.retrieve(OID oid) method. This is similar to the creation method, except that you are
accessing a row that already exists in the database instead of creating a new row (or rows).
If you have an OID and you want to delete the object from the database, Session provides a
delete(OID oid) method that allows you to perform the deletion. For most cases, however, you
will have the DataObject and can just call the local delete() method.
The above methods show how to retrieve or create a single object within the system. The system also provides the ability to retrieve all objects of a given object type. This capability is provided through the method retrieve(String typeName), located in the Session class.
Suppose you want to print out the names of all of the Publications in the system whose ID is less than
100. One way to do this would be to loop from 1 to 100, create the corresponding OID, and look up
each Publication. Another way would be to use a Data Query. However, the simplest way is to take advantage of the retrieve all event that is generated for the Magazine object type. The following example demonstrates this (remove the "\" and make it all one line):
// get all of the Publications in the system. Calling "retrieve" triggers // the "retrieve all" event defined in the PDL file. Note that // the string passed in to the method is the model name (tutorial) // followed by the object type name (Publication) separated by a dot (.). DataCollection pub = SessionManager.getSession().retrieve\ ("tutorial.Publication");
// now we want to filter on the ID of the publication pub.addFilter(pub.getFilterFactory().addLessThan("id", new Integer(100), \ false));
// finally, we can loop through the publications and print out its name // // the query to get the information out of the database is executed // when next() is called for the first time while (pub.next()) {
System.out.println(pub.get("name"));
}
Chapter 9. Persistence Tutorial 69
DataCollections are also useful when it is necessary to perform certain tasks on a large number
of Data Objects. Suppose you want to add the publications to a java.util.Collection that can then be passed around. The following code will create the java.util.Collection (remove the "\" and make it all one line):
DataCollection publication = SessionManager.getSession().retrieve\ ("tutorial.Publication"); Collection collection = new ArrayList();
pub.addFilter(pub.getFilterFactory().addLessThan("id", new Integer(100), \ false));
while (pub.next()) {
collection.add(pub.getDataObject());
}
Note
Please note the following:
Most creation of Data Objects should be handled through the use of Domain Objects. All ACSOb-
jects are also DomainObjects and therefore can use the DomainObject API to retrieve the correct
information. For more information, please see the Section 10.2 Domain Objects Tutorial.
The DataCollection class does NOT extend the java.util.Collection interface, nor does it extend
the java.util.Iterator interface.
9.2.6. Object Type Inheritance
Now that you know how to create and access information for a specific object type, the next logical step in Object-Oriented programming is to create an object type that extends another object type. To address this problem, PDL allows object types to inherit attributes from other object types, similar to inheritance in most Object-Oriented programming languages.
The following example shows a "Magazine" object type extending the "Publication" object type. The definition for the "Magazine" is very similar to that of "Publication" in that it has a block of attributes. It is different, however, in that it does not have an object key definition. Rather, it has a reference
key which indicates how the table containing the Magazine information can be joined to the table
containing the Publication information. If the Magazine tries to define an object key, an error will be thrown.
model tutorial.magazine;
// we do not have to import the tutorial model because both object types // are in the same model. However, we show the import statement here as // an example. import tutorial.*;
object type Magazine extends Publication {
// we need to specify the size of the String attribute so we know // whether it is actually a String or if it is really a Clob String issueNumber = magazines.issue_number VARCHAR(30);
// notice that because it extends Publication, there is not an // explicitly "object key" declaration. Rather, there is
70 Chapter 9. Persistence Tutorial
// a "reference key" declaration and "id" is not defined // as one of the attributes reference key (magazines.magazine_id);
}
9.3. Associations
So far, the documentation has discussed how to create simple Data Objects to access the database. These features, while sufficient to build many systems, lack the ability to relate object types to each other. To address these needs, the persistence system contains the concept of associations.
This document discusses how the persistence layer allows developers to associate Data Objects and how these associations can be saved in the database without needing to involve the Java code in how the associations are actually stored. More concretely, the document first discusses how an association is structured within PDL. It then defines the PDL for Articles, Paragraphs, and Authors and show hows to relate them through Two-Way Associations, Composite Associations, and One Way Associations.
9.3.1. Association Blocks
Most commonly you associate objects to each other when both objects need to know to which objects they are associated. Magazines and Articles, Users and Groups, and Employees and Offices are all examples of this type of association. In the PDL below, an Article Object Type and an "association block" are defined to associate the Articles to Magazines. Association Block definitions are simi­lar to Object Type definitions in that they both have attribute definitions. Instead of the Attribute being set as equal to a single column within the database, the Attribute is set as equal to a Join Path.
object type Article {
BigDecimal id = articles.title; String title = articles.title;
object key (articles.article_id);
}
// this is an "association block" associating "articles" and "magazines" association {
// note that the Attribute Type is an Object Type (Article) // and not a standard Java Type. Also notice the order of the // join path and see the note below. Article[0..n] articles = join magazines.magazine_id
Magazine[0..n] magazines = join articles.article_id
}
to magazine_article_map.magazine_id,
join magazine_article_map.article_id
to articles.article_id;
to magazine_article_map.article_id,
join magazine_article_map.magazine_id
to magazines.magazine_id;
Chapter 9. Persistence Tutorial 71
Note
The order of the join path is important. The information that the developer has must come first. That is, when the developer needs to retreive articles, the information he has is the Magazine (he needs to know for which magazine to get the articles). Therefore, the first line of the join path specifies how to join the magzines table to the mapping table. From then on, it should be in order so that the last section of the join element uses the same table as the first section of the next join
element.
9.3.2. Composite Associations
Composite relationships are a special type of Association. Composite relationships are useful for mod­eling relationships between objects where a contained object cannot exist outside its container object. The main difference between a Composition and a standard Association is that within a Composition, one of the objects cannot exist without the other.
In the following example, a Paragraph is a component of the Article (they therefore have a com­posite relationship). That is, it does not make sense for a Paragraph to exist outside of an article. There are many different ways to signify a relationship as composite but the easiest way is to add the com-
ponent keyword before the component Object Type (in this case, the paragraph is a component of
the article)
object type Paragraph {
BigDecimal id = paragraphs.paragraph_id INTEGER; String text = paragraphs.text VARCHAR(4000);
object key (paragraphs.paragraph_id);
}
association {
Article[1..1] articles = join paragraphs.article_id
// notice the component keyword indicates that if the article does // not exist then the paragraph also does not exist component Paragraph[0..n] paragraphs = join articles.article_id
}
Another way to make the same association is to use the composite keyword on the role that points toward the composite end of an association in order to indicate that the association is a composition. For example:
association {
composite Article[1..1] articles = join paragraphs.article_id
Paragraph[0..n] paragraphs = join articles.article_id
}
The final way to signify the relationship is to use keywords for both object types. This displays the same behavior as the two examples above but it also valid.
association {
composite Article[1..1] articles = join paragraphs.article_id
component Paragraph[0..n] paragraphs = join articles.article_id
to articles.article_id;
to paragraphs.article_id;
to articles.article_id;
to paragraphs.article_id;
to articles.article_id;
to paragraphs.article_id;
72 Chapter 9. Persistence Tutorial
}
9.3.3. Role References
Developers ofen only need to be able to obtain associated information in a single direction. For in­stance, if authors have screen names that are used and can be shared, it is useful to be able to look up the screen name for a given author. However, it may not be as important to look up the author that corresponds to a given screen name. In this case, developers should use a Role Reference. In the following example, the developer wants to be able to easily look up a given screen name for an author.
The PDL below can be used to create Object Types for both ScreenName and Author. Notice that
Author contains a role reference to a ScreenName.
model tutorial; object type ScreenName {
BigDecimal id = screen_names.name_id INTEGER; String screenName = screen_names.screen_name VARCHAR(200); Blob screenIcon = screen_names.screen_icon BLOB;
object key (id);
}
object type Author {
BigInteger[1..1] id = authors.author_id INTEGER; String[1..1] firstName = author.first_name VARCHAR(700); String[1..1] lastName = author.last_name VARCHAR(700); Blob[0..1] portrait = authors.portrait BLOB;
// the following line is the role reference. Notice that it // appears in the definition just like an Attribute. The only // difference is that instead of pointing to a column in the ScreenName[0..1] screenName =
object key (id);
}
join authors.screen_name_id to screen_names.name_id;
9.3.4. Link Attributes
One final feature that is immensely useful for associating objects is the idea of Link Attributes. Often, some sort of relationship is needed for associations. For instance, for Magazines, it is useful to include the page number with the Article. The concept of having Articles associated with Magazines is covered by standard associations but in order to capture a page number with the association, Link Attributes are needed.
// this is an "association block" associating "articles" and "magazines" association {
Article[0..n] articles = join magazines.magazine_id
Magazine[0..n] magazines = join articles.article_id
// the next line is the Link Attribute. Note that it also specifies // the SQL type of INTEGER so that the DDL generator can correctly
to magazine_article_map.magazine_id,
join magazine_article_map.article_id
to articles.article_id;
to magazine_article_map.article_id,
join magazine_article_map.magazine_id
to magazines.magazine_id;
Chapter 9. Persistence Tutorial 73
// create the mapping table with the page_number column. BigDecimal pageNumber = magazine_article_map.page_number INTEGER;
}
For more information about Link Attributes and their use, see Section 9.8 Link Attributes.
9.3.5. Using Java to Access Associations
Now that you have seen how to declare associations within PDL, you can learn the different ways to access the information from Java. In Java, Associations are accessed with two classes: DataAssoci-
ation, similar to a Java Collection, and DataAssociationCursor, similar to a Java Iterator.
9.3.5.1. Using Standard Associations
public Collection getArticles(DataObject obj) {
LinkedList articles = new LinkedList(); DataAssociationCursor cursor =
((DataAssociation) obj.get("articles")).cursor();
while (cursor.next()) {
articles.add(cursor.getDataObject());
}
return articles;
}
The next example shows how to associate one item with another. In this case, you are associating an
Article with a Magazine by adding the article to the "articles" association. By calling save(), you
are signalling for the data object to fire the appropriate insert and update association events (remove the "\" and make it all one line).
public void addArticle(DataObject magazine, DataObject article) {
DataAssociation association = (DataAssociation) magazine.get\
("articles");
association.add(article); magazine.save();
}
Note
There are two important things to realize when dealing with adding items to Associations and iterating through them:
Unlike DataCollection, DataAssociation has been separated into two distinct entities. If you
want to loop through the items in the association, or filter or order the association, use a DataAs-
sociationCursor. If you want to add or remove items from the association, use the DataAsso­ciation object. The DataAssociation is a property of the DataObject and is shared by all code
accessing the data object. The DataAssociationCursor is essentially a local copy of the associ­ation that can be filtered, ordered, and iteratred through without any external consequences.
While this next item is actually a feature of Domain Objects, it is important to mention here as
well. When adding items to associations using the DomainObject (and therefore any ACSObject), there are three choices of which method to use. If the association has an association of 0..n (or any upper bound
dataObject) method or the add(String propertyName, DomainObject dobj) method. If the
1), developers should use the add(String propertyName, DataObject
74 Chapter 9. Persistence Tutorial
association has a Multiplicity of 0..1 or 1..1 (or any upper bound = 1) then developers should use the setAssociation(String attr, DomainObject dobj) method.
9.3.5.2. Using Role References
Role References can be treated in exactly the same way as standard associations. The only practical difference between Role References and standard associations is that Role References are one-way associations and standard associations are two-way associations. Thus, everything outlined in Section
9.3.5 Using Java to Access Associations also applies to Role References.
9.3.5.3. Using Copmposite Associations
Composite Associations are also similar to standard associations. The main difference is that in a composite association, if one item is deleted, the other does not have any real meaning (e.g., if you delete an Article, the Paragraph is meaningless).
Again, these can be accessed exactly as associations except for one significant difference: when the association between an object and its component is deleted, the component is also deleted. For ex­ample, if the association between an Article and a Paragraph were deleted, the Paragraph would be deleted. Also, when the Article is deleted, the Paragraph is deleted.
9.4. Named SQL Events
So far, we have outlined how to interact with the database in a controlled, structured fashion. While creating standard data objects and associations handles most developing needs, sometimes a developer needs to perform database queries that do not fit within the standard realm of objects and associations. It is also often the case that developers are able to perform operations in a single operation that would normally take the system multiple operations. This situation has been handled in two separate ways through the introduction of Data Queries (for selects) and Data Operations (for DML).
9.4.1. Data Queries
Developers often come across situations in which they need information from the database and the persistence layer does not quite do what is needed. Therefore, the system has the ability to execute arbitrary queries through the use of a DataQuery.
9.4.1.1. Retrieving Information from the Database
Executing arbitrary queries through DataQueries is easy. You can retrieve them in the same way that you retrieve an existing data object, and you can execute the query and loop through the results in the same way that you use a DataAssociationCursor.
To begin, you retrieve a query through the Session object using its model and name. Then, you can use next() to loop through the results.
For example, if you want all paragraphs that show up in the magazine with issue number 5A. The first step is to define the query within the PDL file. A DataQuery definition has four sections. It begins with the declaration of the name of the query followed by data type mappings for each returned attribute. It concludes with two code blocks. The first block, the DO block, contains the actual SQL that will be executed. The second block, the MAP block, allows the developer to map database columns to attribute names. The attributes are the values that can be accessed from the Java code.
Chapter 9. Persistence Tutorial 75
To accomplish the task of retrieving the paragraphs as mentioned above, you could declare the fol­lowing DataQuery in your PDL file:
model tutorial;
// the first line indicates that it is a query and the name of the query query paragraphMagazines {
// the next section maps the attributes to the java type so that the // same type is returned regardless of which database driver is used. BigDecimal magazineID; BigDecimal paragraphID; String issueNumber; String text; do {
select m.magazine_id, p.paragraph_id, issue_number, text from magazines m, a, magazine_article_map ma, paragraphs p where ma.magazine_id = m.magazine_id and p.article_id = ma.article_id
} map {
magazineID = m.magazine_id; paragraphID = p.paragraph_id; issueNumber = m.issue_number; text = p.text;
}
}
With this PDL definition, it should be easy to see how the following code does what is desired (remove the "\" and make it all one line).
DataQuery query = SessionManager.getSession().retrieveQuery\ ("tutorial.paragraphMagazines"); query.addEqualsFilter("issueNumber", "5A"); while (query.next()) {
System.out.println((String)query.get("text"));
}
9.4.1.2. Creating Data Objects
The method discussed for retrieving arbitrary information from the database is sufficient to do most of what is needed. However, it is not very convenient since most Java code is written around using
DataObjects. Therefore, most developers want to be able to retrieve DataObjects directly from
the DataQuery. One way to do this is to create a new DataObject for each row returned by the query and then populate that DataObject with the information retrieved. While this works, it is inefficient and inelegant.
To solve this problem, the DataQuery allows the developer to create DataObjects directly from the query. The objects can be defined within the query statement in a fashion similar to Attribute declarations.
Suppose you want to get all magazines that have authors of articles whose last name starts with a given sequence of characters. This is not a standard association because you are actually going through two separate mapping tables. This could be done by getting all articles with authors that match the criteria and then getting all magazines that contain the articles. However, this option would require two separate database hits. Another option is to perform a query and then for every row create the corresponding data object. A third option is to have the persistence layer create the data objects for you. The following example shows how you can allow the persistence layer to perform the work for you.
76 Chapter 9. Persistence Tutorial
The PDL is simply a Data Query with extra Attribute definitions (remove the "\" and make it all one line).
query MagazineToAuthorMapping {
// the next two lines are declaring that objects will be returned Magazine magazine;
Author author;
do {
select publications.name, issue_number, publication_id,
article_id
} map {
// here we map the attributes of the objects to columns returned // by the query. magazine.name = publications.name; magazine.issueNumber = magazines.issue_number; magazine.id = publications.publication_id; author.authorID = authors.author_id; author.firstName = authors.first_name; author.lastName = authors.last_name;
}
}
authors.first_name, authors.last_name, author_id
from magazines, publications, articles, authors,
magazine_article_map, article_author_map
where publications.publication_id = magazines.magazine_id
and magazine_article_map.magazine_id = magazines.magazine_id and maagazine_article_map.article_id = article_author_map.\
and article_author_map.author_id = authors.author_id
This can then be accessed with the Java like most other queries. The \ marks where the line has been wrapped for printing purposes.
DataQuery query = SessionManager.getSession().retrieveQuery\ ("tutorial.MagazineToAuthorMapping");
// the next line adds the filter so that we only get author’s whose last // name begins with the letter "s". Note that we are using a stanard filter // because we need to perform a function on the column and we are positive // that the value is not null. Filter filter = query.addFilter("lower(lastName) like \ ’%’ || :lastNamePrefix"); filter.set("lastNamePrefix", "s");
while (query.next()) {
DataObject myAuthor = query.get("author"); DataObject myMagazine = query.get("magazine"); System.out.println("the author I retrieved is " + myAuthor); System.out.println("the magazine I retrieved is " + myMagazine);
}
9.4.2. Data Operations
As mentioned previously, developers often need to be able to execute arbitrary DML statements that do not fit nicely into the realm of data objects and data associations. To accommodate this need, the system contains the concept of a DataOperation that can be used to execute arbitrary DML statements or PL/SQL functions and procedures.
Chapter 9. Persistence Tutorial 77
9.4.2.1. Executing Arbitrary DML
Data Operations are similiar to DataQueries in both structure and use. However, while they are re­trieved in a fashion similar to DataQueries, they are executed differently. After the query is retrieved, the program can set bind variables, after which it is executed. Suppose you want to create a magazine with ID 4 using all articles in the system that are not yet currently in a magazine. To do this, you could create a new Magazine DataObject, give it an ID of 4, use a DataQuery to get all articles not already in a magazine, add those articles to the magazine through the use of associations, and then save the magazine. Alternately, you can use a DataOperation and execute a single query.
The DataOperation to execute the above query is structured in almost the same way as a Data-
Query. In fact, it can even have an OPTIONS block (although it does not yet have any valid values for
the options block). However, since it does not return many different rows of results, it does not allow attribute mappings before the first do block. The PDL can be defined as follows:
data operation createMagazine {
do {
insert into magazine_article_map (magazine_id, article_id) select :magazineID, article_id from articles where not exists (select 1 from magazine_article_map where magazine_article_map.article_id = articles.article_id)
}
}
Now that the operation is defined, you can set the value of the bind variable magazineID to the correct value and then execute the operation. This can be done with code such as the following (the \ marks where the line has been wrapped for printing purposes):
DataOperation operation = getSession().retrieveDataOperation\ ("tutorial.createMagazine"); // we have to pass in an Integer instead of an int so that JDBC can // handle it correctly operation.setParameter("magazineID", new Integer(4)); operation.execute();
9.4.2.2. Executing PL/SQL
Developers often need to execute PL/SQL procedures and functions. Therefore, it is possible to exe­cute both using a DataOperation with additional syntax. Arguments can be passed to functions ans procedures using Parameter Binding.
Note
The methods described below do not allow users to return cursors from their PL/SQL functions. If this is required, the recommended workaround is to use a CallableStatement directly and bypass the persistence layer entirely.
9.4.2.2.1. PL/SQL Procedures
Suppose you want to execute the following PL/SQL procedure:
create or replace function myPLSQLProc(v_priority in integer) as begin
insert into magazines (magazine_id, title)
78 Chapter 9. Persistence Tutorial
select nvl(max(magazine_id), 0) + 1, :title from magazine_id; end; / show errors
To do this, first include the above statement in your SQL file, so that it will be defined in the database when your package is installed. Next, declare it in your PDL file using a DataOperation:
data operation DataOperationWithPLSQLAndArgs {
do {
select myPLSQLProc(:title) from dual
}
}
You can then execute this data operation just like any other data operation, after binding the title variable.
It is also possible to use OUT parameters within the DataOperation. To do this, the only additional requirement is for the developer to specify the JDBC type of all of the parameters within the query.
Suppose you want to copy the article with the highest ID into a new row with the ID that you pass into the procedure, and you want back the ID for the row that was copied. You can declare a PL/SQL procedure such as the following:
create or replace procedure DataOperationProcWithInOut(
as begin
end; / show errors
v_new_id IN Integer, v_copied_id OUT Integer)
select max(article_id) into v_copied_id from articles;
insert into articles (article_id, title)
select v_new_id, title from articles where article_id = v_copied_id;
insert into article_author_map (article_id, author_id)
select v_new_id, author_id from article_author_map where article_id = v_copied_id;
Adding the JDBC type, the PDL to access this procedure appears as follows:
data operation DataOperationProcWithInOut {
do call {
DataOperationProcWithInOut(:newID, :copiedID)
} map {
newID : INTEGER; copiedID : INTEGER;
}
}
To execute this in Java, you simply need to bind the variable and then retrieve the variable using the
get(String) method in a fashion similar to retrieving a value from a DataQuery. For instance, to
print out the value of the copiedID variable, the following code can be executed:
DataOperation operation = getSession().retrieveDataOperation
operation.set("newID", new Integer(4)); operation.execute(); Integer copiedID = (Integer)operation.get("copiedID"); System.out.println("The copied ID was [" + copiedID.toString() + "]");
("tutorial.DataOperationProcWithInOut");
Chapter 9. Persistence Tutorial 79
Note
The do call and OUT parameters are not available for Postgres because Postgres has not yet imple­mented CallableStatements or OUT parameters.
9.4.2.2.2. PL/SQL Functions
Retrieving a single value back from a function is almost identical to using OUT parameters for proce­dures. First, declare your PL/SQL in your SQL file. For example, you may define the following:
create or replace function DataQueryPLSQLFunction(v_article_id in integer) return number is
v_title varchar(700); begin
select title into v_title from articles
where article_id = v_article_id;
return v_title; end; / show errors
Next, you can define the function as a DataOperation within your PDL file, as follows:
data operation DataOperationWithPLSQLAndArgsAndReturnInPDL {
do call {
:title = DataQueryPLSQLFunction(:articleID)
} map {
title : VARCHAR(700); articleID : Integer;
}
}
Finally, you can retrieve the value for title just like any normal data query, after binding the :ar-
ticleID variable.
It is necessary to declare the types for each variable within the function whether or not it is an OUT parameter.
9.5. Filtering, Ordering, and Binding Parameters
When retrieving information from the database, developers almost always need to be able to filter and order the results that are returned. Therefore, DataQuery, DataCollection, and DataAssocia-
tionCursor objects allow for ordering and filtering. DataQuery and DataOperation also allow
developers to set arbitrary bind variables within the queries and DML statements. This document dis­cusses how these features are implemented and how using the Filter can be overridden to use any arbitrary filtering scheme.
80 Chapter 9. Persistence Tutorial
9.5.1. Filtering
9.5.1.1. Overview
The filtering system is complex, in that it allows developers to create complex expressions by com­bining filters, yet simple in that it provides convenience methods for developers with simple needs.
It is important to realize that by default, Filters simply filter the resulting data from the query. For instance, if you have the following:
query myDataQuery {
BigDecimal articleID; do {
select max(article_id) from articles
} map {
articleID = articles.article_id;
}
}
and then add the filter "lower(title) like ’b%’", the new query will be:
select * from (select max(article_id) from articles) results where lower(title) like ’b%’
and not:
select max(article_id) from articles where lower(title) like ’b%’
This can clearly lead to different results.
9.5.1.2. Simple Filters
For simple filters, you should use the addEqualsFilter(String attributeName, Object
value) or the addNotEqualsFilter(String attributeName, Object value) methods to
filter a DataQuery, DataOperation, DataCollection, or a DataAssociationCursor object. These methods take the name of the attribute and its value, create the correct SQL fragment, and bind the variable. If the system is using Oracle or Postgres and the value is null, the system will create the correct is null or is not null syntax.
In order to specify the filter, you must use the name of the Java Attribute, not the name of the database column. The persistence layer automatically converts the property names to column names using the property mappings defined in the PDL. This layer of abstraction is one of the features that allows de­velopers to change column names without having to update Java code. For example, see the following
DataQuery defined in PDL (remove the "\" and make it all one line):
query UsersGroups { String firstName; String lastName; String groupName; do{
select * from users, groups, membership
where users.user_id = membership.member_id and membership.group_id \ = groups.group_id } map {
firstName=users.first_name;
lastName=users.last_name;
groupName=groups.group_name;
Chapter 9. Persistence Tutorial 81
}
To retrieve all users whose last name is "Smith", do the following:
DataQuery query = session.retrieveQuery("UsersGroups"); query.addEqualsFilter("lastName", "Smith") while (query.next()) {
System.out.println("First name = " + query.get("firstName") +
}
"; Last name = " + query.get("lastName") + "; Group = " + query.get("groupName"));
To get all users whose last name starts with "S", use the addFilter method:
DataQuery query = session.retrieveQuery("UsersGroups"); // FilterFactory is explained below query.addFilter\ (query.getFilterFactory().startsWith("lastName", "S", false)); while (query.next()) {
System.out.println("First name = " + query.get("firstName") +
}
"; Last name = " + query.get("lastName") + "; Group = " + query.get("groupName"));
9.5.1.3. Complex Filters
For more complex queries, it is helpful to understand the role of each interface that deals with Fil-
ters.
Filter — This class represents a single expression for part of a "where" clause. For instance, a
Filter could be "foo = :bar" with a value associated with "bar" (e.g. "foo = 3").
CompoundFilter — This class extends Filter and provides the ability to add filters together
using the AND and OR keywords.
FilterFactory — This class is responsible for handing out filters. It has methods such as "sim-
ple", "equals", "notEquals", "lessThan", "greaterThan", "startsWith", "contains", and "endsWith". If a user is using Oracle or Postgres, all these methods check whether the value is null, and if so, act correctly (e.g., use "foo is null" instead of "foo = null").
DataQuery — This class allows you to add filters as well as get a reference to the FilterFactory. If
you need a FilterFactory but do not have a DataQuery, use Session.getFilterFactory().
If you want to filter the query based on certain conditions, you can incrementally build up your query as follows:
DataQuery query = session.retrieveQuery("UsersGroups"); FilterFactory factory = query.getFilterFactory();
if (beginLetter != null) {
query.addFilter(factory.lessThan("firstLetter", beginLetter, true));
}
if (lastLetter != null) {
query.addFilter(factory.greaterThan("firstLetter", beginLetter, true));
}
while (query.next()) {
82 Chapter 9. Persistence Tutorial
System.out.println("First name = " + query.get("firstName") +
}
"; Last name = " + query.get("lastName") + "; Group = " + query.get("groupName"));
Now suppose you want to get all users with a last name that is the same as the variable lName or is Smith, and with a first name that matches the variable fName or is John. You could do this as follows:
DataQuery query = session.retrieveQuery("UsersGroups"); FilterFactory factory = query.getFilterFactory();
Filter filter1 = factory.or().addFilter(factory.equals("lastName", lName))
(factory.equals("lastName", "Smith")); Filter filter2 = factory.or().addFilter(factory.equals("firstName", fName))
(factory.equals("firstName", "John"));
query.addFilter(factory.and().addFitler(filter1).addFilter(filter2)); while (query.next()) {
System.out.println("First name = " + query.get("firstName") +
}
.addFilter\
.addFilter\
"; Last name = " + query.get("lastName") + "; Group = " + query.get("groupName"));
These could also have been chained together in order to avoid creating any Filter variables, but this was not done here for clarity.
Finally, if you want to add a bunch of "foo = :foo" statements to a query, you can use the convenience methods provided by DataQuery. These methods delegate to FilterFactory and therefore handle the Oracle null problem. For instance:
DataQuery query = session.retrieveQuery("UsersGroups"); if (includeFirstName) {
query.addEqualsFilter("firstName", fName);
}
if (includeLastName) {
query.addEqualsFilter("firstName", fName);
}
while (query.next()) {
System.out.println("First name = " + query.get("firstName") +
}
"; Last name = " + query.get("lastName") + "; Group = " + query.get("groupName"));
Note
When setting dates, java.util.Date objects should be used instead of java.sql.Date objects because java.sql.Dates do not have hours, minutes, or seconds.
When filtering a query that returns data objects (as opposed to simple java types), you must prepend the name of the object attribute. For instance, if you want to retrieve all uses using a query, you would follow the example below. In practice, you want to retrieve a DataCollection from the Session but we are using a query here for demonstration purposes.
Chapter 9. Persistence Tutorial 83
query retrieveAllUsers {
User myUser; do {
select user_id, first_name, last_name from users
} map {
user.id = users.user_id;
user.firstName = users.first_name;
user.lastName = users.last_name;
}
}
DataQuery query = session.retrieveQuery("retrieveAllUsers"); // notice how the attribute name corresponds directly to what // in the map" section of the query and is actully // the query.addEqualsFilter("user.firstName", fName);
while (query.next()) {
}
object type.attribute name
DataObject user = query.get("user"); System.out.println("First name = " + user.get("firstName") +
"; Last name = " + user.get("lastName") + "; Group = " + user.get("groupName"));
9.5.1.4. Restricting the number of rows returned
One common feature that is requested of queries, collections, and associations is to be able to restrict the number of rows that are returned. Specifically, in order to create any sort of pagination or to break up a large set of results in to a series of smaller sets it is necessary to restrict the number of rows returned.
Restricting the number of rows a query returns is easy. To do so, you can simple call
setRange(Integer, Integer) on the data query is question. For instance, if I want results 10-19
for a qury, I can do the following:
DataQuery query = session.retrieveQuery("retrieveAllUsers"); query.setRange(new Integer(10), new Integer(20)); while (query.next()) {
DataObject user = query.get("user"); System.out.println("First name = " + user.get("firstName") +
}
"; Last name = " + user.get("lastName") + "; Group = " + user.get("groupName"));
9.5.1.5. Filtering Using Subqueries
The filtering methods described so far handle most situations. However, sometimes developers need the ability to filter based on the results of a subquery. Therefore, the persistence layer provides a mechanism to allow developers to use named queries within filters. Specifically, this is useful within
IN clauses and EXISTS clauses.
Suppose that you want to retrieve all articles written by authors whose last name begins with the letter "b". One easy way to avoid duplicates is to use an IN clause. To perform this operation, you can create the following two DataQueries:
query retrieveArticlesBasedOnAuthor {
BigDecimal authorID;
do {
84 Chapter 9. Persistence Tutorial
select article_id
from authors, author_article_map
where authors.author_id = author_article_map.author_id
} map {
} }
query retrieveSelectedArticles {
BigDecimal articleID;
String title;
do {
} map {
} }
and lower(last_name) like :lastName || ’%’
authorID = authors.author_id;
select article_id, title from articles
articleID = articles.article_id; title = articles.title;
Next, simply retrieve one query and add the other query as part of the filter, as follows (remove the \ and make it all one line):
Session session = SessionManager.getSession(); DataQuery query = \ session.retrieveQuery("tutorial.retrieveSelectedArticles"); Filter filter = query.addInSubqueryFilter("articleID", \ "tutorial.retrieveAuthorsWithParam"); // we have to set the value for "lastName" since it is a bind variable // in the subquery we added. filter.set("lastName", "b"); System.out.println\ ("The following articles have at least one author whose " +
while (query.next()) {
System.out.println(query.get("title"));
}
"last name starts with ’b’");
The code above will actually execute the following SQL:
select article_id, title from articles where article_id in (select article_id
author_article_map.author_id
with ? = "b"
from authors, author_article_map
where authors.author_id = \
and lower(last_name) like ? || ’%’
Note
While there are other, possibly better ways to obtain the same result, this example is used to demon­strate how the feature works, not as an authoritative example of writing queries.
Chapter 9. Persistence Tutorial 85
9.5.1.6. Filtering Using Functions
The filtering methods discussed so far work well when the developer only needs to filter directly off of columns or values. However, they do not work well if the developer wants a case-insensitive filter or needs to use some other function to manipulate the data in the columns.
To meet this need and to appropriately handle null values, the system provides a method within Fil-
terFactory named compare that allows developers to pass in two expressions and have the system
compare them to each other.
The most common use case for this is a case-insensitive comparison where the developer wants to know whether a string exists in a column, but does not care about the case. For instance, suppose a de­veloper wants to retrieve all articles titled "Disaster Strikes," but does not care about the capitalization. He could use use the following code to achieve this:
DataCollection pub = SessionManager.getSession().retrieve("tutorial.Articles"); Filter filter = pub.addFilter(pub.getFilterFactory().compare("upper(title)",
// we set the title to all upper case so that we do not make oracle do // it for us which would be slower filter.set("title", "DISASTER STRIKES");
FilterFactory.EQUALS, ":title"));
If the developer actually wants all articles with either the word "Disaster" or "Strikes," he can do the following:
DataCollection pub = \ SessionManager.getSession().retrieve("tutorial.Articles"); FilterFactory factory = pub.getFilterFactory(); Filter disasterFilter = \ factory.compare("upper(title)", FilterFactory.CONTAINS,
filter.set("disasterTitle", "DISASTER"); Filter strikesFilter = \ factory.compare("upper(title)", FilterFactory.CONTAINS,
filter.set("disasterTitle", "STRIKES"); pub.addFilter(factory.or()
.addFilter(disasterFilter) .addFilter(strikesFitler));
":disasterTitle"));
":strikesTitle"));
The important thing to realize is that it this method will handle problem with null values faced by Oracle and Postgres, whereas using a standard simple filter will not.
9.5.2. Ordering
Use the addOrder(String order) method to order a DataQuery, DataCollection, and a
DataAssociationCursor object. The addOrder method takes a String as its only parameter
and the format of the string is the optional object type following by a dot and then then required attribute name ([
The string parameter passed to the addOrder(String order) method is used in an ORDER BY clause, which is appended to the SELECT statement. The order is specified by constructing a string representing the ORDER BY clause, but instead of specifying column names, you specify attribute names. The persistence layer automatically converts the attribute names to column names using the property mappings defined in the PDL. For example, see the following DataQuery defined in PDL (remove the "\" and make it all one line):
query UsersGroups {
object type name.]attribute) you wish to order by.
86 Chapter 9. Persistence Tutorial
String firstName; String lastName; String groupName; do{
select *
from users, groups, membership
where users.user_id = membership.member_id and membership.group_id \ = groups.group_id
} map {
firstName=users.first_name;
lastName=users.last_name;
groupName=groups.group_name;
}
}
You can order this query by the user’s last name, as follows:
DataQuery query = session.retrieveQuery("UsersGroups"); query.addOrder("lastName asc"); while (query.next()) {
System.out.println("First name = " + query.get("firstName") +
}
"; Last name = " + query.get("lastName") + "; Group = " + query.get("groupName"));
Finally, you can build up the ORDER BY string in the same way that you build up a filter. For instance:
DataQuery query = session.retrieveQuery("UsersGroups"); query.addOrder("lastName asc") if {careAboutGroupName} {
query.addOrder("groupName"); } while (query.next()) {
System.out.println("First name = " + query.get("firstName") +
}
"; Last name = " + query.get("lastName") + "; Group = " + query.get("groupName"));
The ORDER BY string is any valid ORDER BY clause, except that you specify property names, not column names.
9.5.3. Binding Parameters
Use the setParameter(String parameterName, Object value) method to bind an arbitrary variable within a DataQuery, DataOperation, DataAssociationCursor, or DataCollection. The method getParameter(parameterName) allows you to retrieve the value of a set parameter. The setParameter takes in a string that should match the string within the defined SQL. The Object it takes should specify the value.
This functionality is useful for complicated queries that involve embedding parameters. For example, see the following DataQuery defined in PDL (bind variables are in bold):
query CategoryFamily {
Integer level; BigDecimal categoryID; String name; String description; Boolean isEnabled; do {
select l, c.category_id, c.name, c.description, c.enabled_p
Loading...