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
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:
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.
iiIntroduction 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:
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 Guideiii
$
#
[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 command 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 information. 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.
ivIntroduction 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.
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 Guidev
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.
viIntroduction 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 architectural view and a closer review of the individual components.
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
2Chapter 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 Overview3
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 business 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). Accordingly, 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 essential to building a web applications. Kernel provides domain objects that represent users, groups, and
permissions.
4Chapter 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 Overview5
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.
6Chapter 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 Objects. 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 Overview7
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.
8Chapter 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. Developers 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 mapping 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.
10Chapter 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 application. 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 transaction 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 administrators 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:
12Chapter 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 membership, 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. Services 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 information. 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.
14Chapter 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 involvement. 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: Services15
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 interface, 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 automatically 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 namegizmo
Company nameAcme, Inc.
DescriptionThis gizmo slices, dices, and juliennes.
Date introducedJuly 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).
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 resourcebundles.
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,
16Chapter 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 preferred 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 globalization 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: Services17
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 discussion 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 supported 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.
18Chapter 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 Ravioliis 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 Definition 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 approach 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 addition, the standard Java APIs for parsing and manipulating structured data are reviewed. This information 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 XMLformatted 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.
20Chapter 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 transformed output to the response output stream. A WAF application’s dispatcher can use the provided
Chapter 5. WAF Component: Presentation21
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 dynamically. 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 WAFApplication.
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
programmertoregisteradefaultcom.arsdigita.kernel.StyleSheetobject
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 templating 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
22Chapter 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 primaryimplementationof thestylesheet resolverinterfaceis theclass
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:
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/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: Presentation23
5.2.2.3. Pattern Generators
The com.arsdigita.templating.PatternGenerator interface provides the mechanism for introducing 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.
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().
24Chapter 5. WAF Component: Presentation
locale
This pattern generator expands to the current kernel execution context locale. ie the value returned 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, ortext-html ifthe parameter valueis text/plain,
text/javascript or text/html respectively. In all other cases no values are generated.
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: Presentation25
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.
26Chapter 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: Presentation27
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.
28Chapter 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 components 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 crossplatform 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 developer 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: Presentation29
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 components themselves, so the following code is permissible:
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 persistence 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 application code requests a redirect, the BaseServlet ensures that the transaction is finished before
proceeding.
32Chapter 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
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 following 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 required. The best choice for remote access is SSH, a protocol suite of network connectivity tools which
36Chapter 7. Developing with WAF
includes a secure telnet replacement and a secure file-copy utility (scp). Both open source and proprietary 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:
For Win32 systems,a goodopensource applicationisPuTTY, foundat
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 webdevsupport 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 WAF37
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 QueryInformation page provides a stack trace showing the code that led to the SQL operation’s execution. 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.
38Chapter 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 information, 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 Caucho’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 WAF39
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.
40Chapter 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.
#uncomment to make enterprise.init
#cd $CCM_HOME
#ant make-config
#ant make-init
Chapter 7. Developing with WAF41
#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 configuration 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 "\":
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.
42Chapter 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 WAF43
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 implementing 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 appender. (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 automatically include the timestamp, thread name, location (file name and line number), full or abbreviated 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 operations. 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
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:
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
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:
46Chapter 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 WAF47
}
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
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 " + \
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 uniformly 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.
50Chapter 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.1doc/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 example 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.
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:
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 Tutorial51
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 VersioningService.
Once you’ve defined your PDL, the build system will generate relational tables to back your object
types.
52Chapter 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.
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 fordebugging.
Example 8-4. binder/src/com/example/binder/Note.java
56Chapter 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, unrecoverable 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.
* 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 Tutorial57
*
* @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.
* 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);
58Chapter 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.
* 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"
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 FrameworkInstallation 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.
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 typically 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 InstallationGuide.
Chapter 8. WAF Application Development Tutorial61
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:
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.
62Chapter 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 WAFComponent: 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 PDLFile) 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.
64Chapter 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.
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 inline 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 Tutorial65
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 TypeInheritance 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);
}
66Chapter 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 generate 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:
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 information 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.
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()).
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.
This interface defines the public methods for Data Objects; see Data Object.
DataObjectsarenormallyaccessedthroughtheuseofDomainObjects
(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.
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.
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.
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.
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.
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.
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):
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 Tutorial69
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
70Chapter 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 WayAssociations.
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 similar 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
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 modeling 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 composite 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);
// 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:
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.
Developers ofen only need to be able to obtain associated information in a single direction. For instance, 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
// 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 {
// 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 Tutorial73
// 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 DataAssociation 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 association 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
74Chapter 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 example, 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 Tutorial75
To accomplish the task of retrieving the paragraphs as mentioned above, you could declare the following 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
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.
76Chapter 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;
// 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.
// 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 Tutorial77
9.4.2.1. Executing Arbitrary DML
Data Operations are similiar to DataQueries in both structure and use. However, while they are retrieved 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 execute 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)
78Chapter 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:
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 Tutorial79
Note
The do call and OUT parameters are not available for Postgres because Postgres has not yet implemented 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 procedures. 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 discusses how these features are implemented and how using the Filter can be overridden to use any
arbitrary filtering scheme.
80Chapter 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 combining 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 developers 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):
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 Tutorial81
}
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:
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:
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 Tutorial83
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 {
84Chapter 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 demonstrate how the feature works, not as an authoritative example of writing queries.
Chapter 9. Persistence Tutorial85
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 developer 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:
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.
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):