|
Building a Web Store with Struts & ADF Frameworks
Building a Web
Store with Struts & ADF Frameworks
JDeveloper 10g Release
10.1.2 Version Author: Steve Muench, Oracle ADF Development Team Contributions from Tony JewtshenkoDate: May 4, 2005
Abstract
By
exploring the details of a sample application built using two popular
off-the-shelf J2EE frameworks, Apache Struts and Oracle ADF, this paper
illustrates how developers can build J2EE-compliant applications with maximum
developer productivity using a framework-based approach. In the process, it
highlights the full lifecycle support that the Oracle JDeveloper 10g IDE
provides for framework-based development using Struts and ADF.
| NOTE: |
This version of the ADF Toy
Store Demo is designed to be used with JDeveloper 10g, release 10.1.2. If you
are familiar with the previous version of the demo for JDeveloper 9.0.5.2, you
might want to skip to the Overview of
Changes from ADF Toy Store 9.0.5.2 Version section. You
need to re-run the supplied Toystore.sql database setup
script due to changes in the underlying stored procedures the demo is now
using.
|
| NOTE: |
This article complements the
ADF
Data Binding Primer and ADF/Struts Overview [1] whitepaper by explaining
the implementation details of a complete application built using Oracle ADF and
Apache Struts. The present article provides an overview of the concepts
necessary to understand the web store demo implementation, but please see this
other whitepaper for additional information on the underlying features. In
addition, the
JDeveloper
10g and Oracle ADF Online Documentation [2],
JDeveloper 10g
Tutorials [3],
JDeveloper 10g
Samples [4] are great resources to be aware of, too, as are the related
whitepapers
ADF
Business Components Benefits in a Nutshell [5],
ADF
Business Components J2EE Design Pattern Catalog [6], and
Most
Commonly Used Methods in ADF Business Components [7].
While
this document should print fine in Internet Explorer, if you prefer, a
PDF
version of this paper [8] is also available.
|
Contents Lessons from the Past Advice
for the Future Rebuilding a Web Storefront with Struts and ADF Demo Installation and
Setup Quick Tour Through the Demo Browsing Products and Adding Them to Your
Cart Checking Out and Signing In Register a New User and Editing an
Existing User's Profile Trying Out the Demo in Another
Language Dissecting the Demo How the Application is Organized Into Packages and Projects Advantages of a Model/View/Controller
Architecture Implementing the Model Layer Using ADF
Business Components Testing
Business Tier Components with JUnit Implementing the Controller
Layer with the Apache Struts Framework Understanding ADF/Struts Integration Building the View Layer with
JSP Pages and JSTL Struts and ADF Features for Building
Multilingual Applications Using ADF, XSQL Pages, XSLT, and XML Schema Together Implementing the View Layer Using ADF
UIX Customizing the Default Framework
Behavior Additional
Points of Interest Around the Demo Deployment and
Packaging Considerations Getting Started on
Your Own ADF-Based Applications Conclusion Overview of
Changes from ADF Toy Store 9.0.5.2 Version Database Setup Controller Layer Changes Changes to Improve Performance
& Scalability JSP Web Tier Changes Added new ADF UIX View Layer Bugs Fixed Related Documents Appendix 1: Known Issues Appendix 2: Configuring Toy Store Datasources on Apache
Tomcat
Lessons from the Past
The initial release of Sun's
Java
Pet Store Demo [9] was a watershed event. Thirsty for guidance on
implementing real-world J2EE applications, Java developers dove into its cool
pools of code like parched creatures of the Kalahari. But after exploring the
depths of its refreshing routines, many returned to the surface wondering why
application infrastructure code dominated the demo.
Obscured by repetitive implementations of J2EE design patterns, the more
interesting business functionality of the web storefront
was hard to find.
On further analysis, one point was clear to
developers: for their own applications they would need to
reimplement the same design pattern drudgery. Common sense
dictated a framework approach, but developers would have to decide whether to
build their own or leverage existing ones. To make a more informed decision,
they read books like Core J2EE Patterns: Best
Practices and Design Strategies which gave the design patterns names,
organized them into functional layers, and explained how a typical J2EE
application should use
fifteen key patterns [10] together.
Another book, EJB Design Patterns: Advanced Patterns,
Processes, and Idioms, came with a handy poster in back, detailing
twenty-one design pattern tips and diagrams for easy cubicle-wall reference.
These and other resources clarified that correctly and efficiently coding all
these patterns from scratch would be no trivial task.
While their
instincts undoubtedly warned them otherwise, many developers opted anyway for
the "do-it-yourself" approach on their first J2EE application projects. A year
later, many were still struggling to deliver feature-complete, well-performing
applications.
Advice
for the Future
On the opening page of his book
Expert One-on-One: J2EE Design and Development
(Wrox Press), Rod Johnson offers an observation on this phenomenon:
The return on investment for many J2EE projects is
disappointing. Delivered systems are too often slow and unduly complex.
Development time is often disproportionate to the complexity of business
requirements.
Why? Not so much because of the shortcomings of J2EE
as because J2EE is often used badly. This often results from approaches to
architecture and development that ignore real world problems. A major
contributing factor is the emphasis in many J2EE publications on the J2EE
specifications rather than the real world problems people use them to address.
Many issues that commonly arise in real applications are simply ignored.
Throughout the rest of his book, Rod debunks many
myths about J2EE development and offers pragmatic guidance about which J2EE
technologies to use under what circumstances. On page 166, he begins a section
on frameworks and how they can help:
Many common
problems (beyond those addressed by J2EE application servers) have been solved
well by open source or commercial packages and frameworks. In such cases,
designing and implementing a proprietary solution may be wasted effort. By
adopting an existing solution, we are free to devote all our effort to meeting
business requirements.
After commenting that existing
frameworks can mean a slightly steeper learning curve, Rod later motivates why
this trade-off is worthwhile to gain a strong application infrastructure. On
page 395, he clearly explains the benefits:
Using a
strong standard infrastructure can deliver better applications, faster. A
strong infrastructure makes this possible by achieving the following
goals:
- Allowing application code to
concentrate on implementing business logic and other application functionality
with a minimum of distraction. This reduces time to market by reducing
development effort, and reduces costs throughout the project lifecycle by
making application code more maintainable (because it is simpler and focused on
the problem domain). This is the ultimate goal, which many of the following
goals help us to achieve.
- Separating
configuration from Java code
- Facilitating the
use of OO design by eliminating the need for common
compromises.
- Eliminating code duplication, by
solving each problem only once. Once we have a good solution for a problem such
as a complex API we should always use that solution, in whatever components or
classes that encounter the problem
- Concealing
the complexity of J2EE APIs. We've already seen this with JDBC; other APIs that
are candidate for a higher-level of abstraction include JNDI and EJB
access
- Ensuring correct error handling. We
saw the importance of this when working with JDBC in Chapter
9.
- Facilitating internationalization if
required.
- Enhancing productivity without
compromising architectural principles. Without adequate infrastructure, it is
tempting to cut corners by adopting quick, hacky solutions that will cause
ongoing problems. Appropriate infrastructure should encourage and facilitate
the application of sound design principles.
- Achieving consistency between applications within an organization. If all
applications use the same infrastructure as well as the same application server
and underlying technologies, productivity will be maximized, teamwork more
effective, and risk reduced.
- Ensuring that
applications are easy to test. Where possible, a framework should allow
application code to be tested without deployment on an application
server.
Several existing
application frameworks provide ready-to-use implementations of the kind of
strong application infrastructure that Rod recommends. If you use
these frameworks, you won't have to design, code, debug,
and maintain your own infrastructure code.
In this whitepaper, we
examine two existing J2EE frameworks by studying a working sample application.
By patterning the sample application after the "classic" Java Pet Store Demo,
we've made it easier for readers familiar with the original demo to compare the
developer productivity that a framework-based J2EE development approach can
provide.
Rebuilding a Web Storefront with Struts and ADF
The ADF Toy
Store demo is a simple web storefront application adhering to the
Model/View/Controller (MVC) design pattern. It is implemented using two
existing J2EE application frameworks:
Apache Struts [11] and
Oracle
Application Development Framework [12] (ADF). Both the Struts and ADF
frameworks have been iteratively developed to support the requirements of
communities of application developers building real-world applications. Many
aspects of their design and implementation echo the pragmatic suggestions that
Rod Johnson details throughout his book.
As with all MVC-style web
applications, the ADF Toy Store has the basic architecture illustrated in
Figure 1:
- The
model layer represents the business information needed by
the application,
- The controller
layer handles user input, interfaces with the model layer, and picks
the presentation
- The view
layer presents the model data to the
end-user.
The model layer consists of one
or more business services that expose application
functionality and access to model data through a business
service interface that is easy to test. These business services, in turn, rely
on query components to retrieve that data and on
business objects to validate and persist any new or
modified data. Code implementing the business delegate
design pattern abstracts the details of locating and using the business
services. When JavaServer pages are used for the view layer along with a
cleanly separated controller layer, many J2EE books refer
to the architecture, shown in Figure 1, as a best practices
"JSP Model 2" architecture. The number "2" is used because this MVC-based
architecture for JSP is an evolution over first-generation JSP-based
approaches.
 Figure 1: Best Practices "JSP Model 2"
MVC Web Application Architecture
By
dissecting the framework-based implementation of our ADF Toy Store demo, we'll
learn how ADF simplifies building all aspects of the model layer, and how the
Struts and ADF frameworks cooperate to simplify implementing the view and
controller layers. In the process, we'll also see plenty of evidence for how
Oracle JDeveloper 10g provides a productive environment covering the full
development lifecycle for building these kinds of MVC-style business
applications.
Before diving into the explanation of the demo, let's
make sure you can open and run the demo in JDeveloper 10g. The next section
details the steps to get the demo setup correctly on your
system.
Demo Installation and
Setup
These instructions assume that you are running
Oracle JDeveloper 10g [13]
production, version 10.1.2. The demo will not work with earlier versions of
JDeveloper.
We also assume that you have access to an Oracle
database, and privileges to create new user accounts to setup the sample
data.
| NOTE: |
ADF is designed to work with any relational database,
and has been tested with Oracle, Oracle Lite, DB2, and SQLServer. The
Using
BC4J with Foreign Datasources [14] whitepaper covers the details (which are
still valid for Oracle ADF as well), but to make the demo explanation easier to
follow, herein we've made the simplifying assumption that you're using the
Oracle database, version 8.1.7 or later.
|
- Download the
adftoystore_10_1_2.zip [15]
file if you haven't already.
-
Extract the
contents of the adftoystore_10_1_2.zip file with the
standard JDK jar utility into a convenient directory.
jar xvf adftoystore_10_1_2.zip
This will create a directory
adftoystore, and subdirectories. These instructions assume
you've extracted the adftoystore.zip file into the root
directory C:\ thus ending up with a demo "root" directory of
C:\adftoystore.
| NOTE: |
If the
jar command does not work on your system, double-check that
you have included the JDKHOME/bin
subdirectory in your system path. If you downloaded the full version of Oracle
JDeveloper 10g, then it comes with a 1.4.2 JDK in the
JDEVHOME/jdk directory. |
-
Create the TOYSTORE and
TOYSTORE_STATEMGMT user accounts in the database using the
provided SQL script.
Run the SQL script
./adftoystore/DatabaseSetup/CreateToyStoreUsers.sql like
this:
cd C:\adftoystore\DatabaseSetup
sqlplus /nolog @CreateToyStoreUsers.sql
After entering your SYS account's
password, the script will create the TOYSTORE and
TOYSTORE_STATEMGMT user accounts. The
TOYSTORE schema will contain the ADF Toy Store application
tables, while the TOYSTORE_STATEMGMT schema will be used by
the ADF state management facility (described later in this whitepaper) to store
pending data across web pages.
-
Create the
application tables for the ADF Toy Store demo, along with some sample
data.
Run the SQL script
./adftoystore/DatabaseSetup/ToyStore.sql like this:
sqlplus toystore/toystore @ToyStore.sql
| NOTE: |
If you have a version of the Oracle database
prior to Oracle 10g, the command purge
recyclebin at the end of this script will give an error. It's
harmless and you can just ignore it. |
-
Setup two database connections in the JDeveloper 10g IDE corresponding to
the two database accounts we created above.
Define connections in
the JDeveloper 10g IDE named...
toystore, corresponding to the TOYSTORE
user (password TOYSTORE) and
toystore_statemgmt corresponding to the
TOYSTORE_STATEMGMT user (password
TOYSTORE).
| NOTE: |
The
two connection names are case-sensitive and should be
typed in lowercase as shown. |
To save some typing, you can
import these two connections from the supplied
jdev_toystore_connections.xml file in the
./adftoystore/DatabaseSetup directory. To do so, select the
Database category folder in the Connections Navigator and
choose Import Connections... from the right-mouse
menu. Supply the jdev_toystore_connections.xml file name as
the file to import from. After importing the two named connections, you should
test each connection by selecting it, double-clicking to bring up the
Connection Wizard, and visiting the
Test tab. If clicking on the Test
Connection button does not yield a "Success!" message, then correct
the connection details on the Connection tab to work for
the database to which you want to connect. By default, the connections are
defined against a database on your local machine listening on port
1521 with a SID of
ORCL.
-
Insure that the
JUnit Extension for JDeveloper is installed.
JUnit [16] is the defacto standard tool
for building regression tests for Java applications. Oracle JDeveloper 10g
features native support for creating and running JUnit tests, but this feature
is installed as a separately-downloadable IDE extension. You can tell if you
already have the JUnit Extension installed by selecting File |
New... from the JDeveloper main menu, and verifying that you have
a Unit Tests (JUnit) subcategory under the
General top-level category in the New
Gallery.
If you do not already have the JUnit
extension installed, then download it from
here [17]. You'll find
it along with all the other extensions available for JDeveloper in the
JDeveloper
Extension Exchange [18] on OTN. To complete the installation of the
extension, first exit from JDeveloper if you are currently running it. With
JDeveloper not running, extract the contents of the
downloaded zip file into the ./jdev/lib/ext subdirectory
under your JDeveloper installation home directory. Then, restart
JDeveloper.
Finally, you should verify that the
junit3.8.1 subdirectory exists in your JDeveloper
installation home. This directory will automatically get created the first time
you run any JUnit wizard from the Unit Tests (JUnit)
category of the New Gallery. However if you don't plan on creating any JUnit
tests yourself yet, you can do the following steps to make sure the directory
gets setup correctly. Assuming your current directory is the JDeveloper
installation home directory...
-
jar
xvf jdev/lib/ext/junit_addin.jar junit3.8.1.zip
This
extracts the junit3.8.1.zip file from the
junit_addin.jar archive. This zip file contains the
distribution of JUnit with which JDeveloper has been tested.
-
jar xvf junit3.8.1.zip
This
extracts the contents of the junit3.8.1.zip file into the
JDeveloper installation home
directory.
- Open the
./adftoystore/ADFToyStore.jws workspace in JDeveloper
10g
-
Run the application inside the JDeveloper
10g IDE by running the index.jsp page in the
ToyStoreViewController.jpr project as shown in
Figure 2.
 Figure 2: Running
the ADF Toy Store Application Inside JDeveloper 10g
| NOTE: |
Since
index.jsp is configured as the Default Run
Target on the Runner panel of the project
properties for the ToyStoreViewController project, you can
also simply click the Run icon in the IDE toolbar when this project is active
to run the application, or pick the Run menu item on the
ToyStoreViewController project's right-mouse menu. You can
see the project properties by clicking on it in the Navigator and selecting
Property Properties... from the right-mouse
menu. |
Running the index.jsp page from
inside JDeveloper will startup the embedded Oracle Application Server 10g
Oracle Containers for J2EE (OC4J) server, launch your default browser, and
cause it to request the URL:
http://yourmachine:8988/ADFToyStore/index.jsp
If everything is working correctly, you will see the
home page of the ADF Toy Store demo, as shown in Figure 3.
| NOTE: |
If following the steps above didn't produce the above demo
home page as expected, see Appendix 1 for a list of known
issues and troubleshooting tips. |
 Figure 3: ADF
Toy Store Demo Home Page
| NOTE: |
After exploring
the demo using the embedded Oracle Containers for J2EE (OC4J) instance that is
built-in to JDeveloper 10g, if you want to install the demo on an external OC4J
instance, Oracle Application Server, Tomcat, or other supported server see
Deployment and
Packaging Considerations |
Quick Tour Through the Demo
Before we
dive into explaining how the demo was built, let's begin with a quick overview
of the end-user functionality of our web storefront application.
Browsing Products and Adding Them to Your
Cart
The ADF Toy Store is a fictitious online store that sells
toys. The products for sale are organized into five categories: Accessories,
Games, Party Supplies, Toys, and Models. From the home page, you can browse
products in the store in two ways:
- Selecting a category name to see the products in that category,
or
- Using the What are you looking
for? search box in the banner to find products by name, regardless
of what category they belong to.
If the
list contains more than three products, they are presented a page at a time.
You can use the Next or Previous
links that appear above the item list to browse through the complete
list.
Clicking on the name of a product shows you a list of the
different product items for sale. For example, clicking on the name of a
product like Pinata, you will see a list of the different
kinds of piñatas that are available as shown in
Figure 4.
 Figure 4: Browsing Different Kinds of Items for a Product Type
To see a
detailed description and a picture of any product, just click on its
name.
On any page where the
button appears, you can click
on it to add one of those items to your shopping cart.
You can see
what items you have in your shopping cart at any time by clicking on the
button, which shows a page listing the
items and quantities you have selected so far, as shown in
Figure 5.
 Figure 5: Shopping Cart
Display
To adjust the quantities of the items in the
cart, just type over the current value in the Quantity
field for one or more items, and click the
button to see the recalculated
shopping cart total. You can remove an item from your cart either by clicking
the button, or by adjusting the
item to have a zero quantity.
Checking Out and Signing In
From the Shopping Cart page,
by clicking on the button you
can proceed to the Review Checkout page. From there, you can review your
purchase and if you are happy with it, press the
button to continue.
If
you have not already signed into the Toy Store as a registered user, you will
be prompted to sign in at this point to continue with the checkout process. The
sign in page looks like what you see in Figure 6. The user
named j2ee is already registered, with a password of
j2ee, so you can provide these credentials to
continue.
| NOTE: |
If instead you want to register as a new user, you
can click the Register as New User link. See the next
section for details...
|
 Figure 6: ADF Toy
Store Sign-in Page
After successfully signing in, you will proceed to the page where you can
confirm your shipping and payment details. Here, to see some of the application
validation logic that's implemented in the demo, you can try:
- Entering an invalid state abbreviation of
ZA for the country USA
- Entering a credit card number that is not comprised of 16
digits
and pressing the
button. You should see the multiple
validation errors as shown in Figure 7 .
 Figure 7: Shipping Information Validation Errors On Form
Submission
After fixing
those errors by entering a valid state abbreviation like CA, and filling out a
full 16-digit credit card number, try causing some additional validation errors
by:
- Entering a date in the past for the
expiration date of your credit card
- Blanking
out a required field like Last Name.
You
should again see the relevant set of remaining validation errors that need to
be corrected when you press the
button again as shown in Figure 8.
 Figure 8: Additional Shipping Information Validation Errors
| NOTE: |
When you submit this web page, without having to write any code, the data
is automatically communicated to the underlying business objects, which in
turn, enforce their declarative business rules. These rules get enforced by
your ADF-powered business components as part of normal operation, and work
consistently with any kind of user interface technology. Rather than simply
presenting the first error that is raised, ADF allows you to easily present the
user with a maximal set of errors that have been flagged in a single
round-trip, so the user can fix all the problems in one go.
|
After correcting these final validation problems and submitting again,
your order will be placed, and you'll see the final "Thank You" page, with a
reference to your order reference number. Clicking on the hyperlinked order
reference number takes you to an order summary page which is implemented using
the XML/XSLT-based
Oracle
XSQL Pages [19] publishing framework instead of JSP Pages, to illustrate
that multiple view-rendering technologies are possible.
Register a New User and Editing an
Existing User's Profile
If you are not currently logged-in as a
registered user of the web store, clicking the
button brings you to the "Sign In" page
as shown in Figure 6. From there, you can register as a new
user by clicking on the Register as a New User link. This
brings you to a form to complete with the necessary registration details.
This user registration page is another place in our application
where it's easy to observe how business rules get enforced by the ADF
framework. For example, if while filling out the form you try to:
- Enter a user name that has already been chosen by
another user
- Forget to provide a
password
- Enter an email address that is not
properly formed
then when you submit the
form, you'll see the full set of errors related to your registration as shown
in Figure 9  Figure 9: Validation
for New User Accounts In Action
| NOTE: |
Under
the covers, the business object that represents a user account is declaratively
enforcing mandatory attributes, reusing a custom business rule to validate the
country and state combination, using a built-in validation rule to enforce
uniqueness of the primary key attribute, and validating the correct formatting
of email addresses using a custom Email datatype. All of the
custom error messages are localized to the current browser user's locale (i.e.
language + territory). None of this behavior requires developer-written code to
coordinate.
|
If you are already logged into the site as a
registered user, you will see the
icon in the toolbar. Clicking on it brings you the page where you can edit your
account details as shown in Figure 10. Of course, since we're
working with the same underlying business object representing user accounts
here in this "Update Account" form, the same validation will be enforced as
above.
 Figure 10: Editing Account DetailsTrying Out the Demo in Another
Language
The demo is built using the internationalization features
supported by Struts and ADF Business Components, and it ships with support for
three languages: English (the default), Italian, and German. The Struts and ADF
frameworks automatically sense the language you want to see based on your
browser settings. So, you can see what the demo looks like in Italian by simply
setting your browser language preferences appropriately.
In
Mozilla Firefox [20]
1.0, you select your preferred languages on the General
panel of the Tools | Options... menu by clicking the
(Languages) button as shown in
Figure 11. You can add "Italian [it]" and then press
(Move Up) to move it to the top of the list.
 Figure 11: Changing the Preferred Language in Mozilla
Firefox 0.9
In Internet
Explorer, you can do the same thing from the General tab
of the Tools | Internet
Options... dialog by clicking on the
(Languages...) button.
Since Apache Struts
caches the browser-user's preferred language at the servlet session level, you
will need to close your browser window, and open a new one before you'll see
the demo change into Italian. A quick way to relaunch your preferred browser
with the right URL for the demo is to find the Target URL in the JDeveloper 10g
log window as shown in Figure 12 and click on
it.
 Figure 12: Clicking on the Target URL in
Log to Relaunch Browser
This will
bring up the ADF Toy Store demo again, but this time in Italian. After adding
the same items to your shopping cart again, it will look like what you see in
Figure 13.
 Figure 13: Shopping Cart
With Preferred Browser Language Set to Italian
You can set your
browser's preferred language back to English using similar steps, and close and
relaunch the browser window to proceed in English again. At this point we've
seen the key functionality in the demo, so it's time to dive in to understand
how it has all been built.
Dissecting the Demo
In
this section we explain the demo in detail, highlighting the interesting
details of how:
- The demo is architected
into Model, View, and Controller layers
- The
Model layer uses ADF's business service, data
access, and business object
components
- The business services can be
tested using JUnit
- The Controller layer
uses Struts actions to coordinate application flow
- The View layer uses standard Struts and JSTL tag libraries to simplify
building the web UI
- The ADF features for
seamless Struts integration work
- The features
of Struts and ADF are used to deliver a multilingual
application
- The your can use ADF with other
view layer technologies like Oracle XSQL Pages
- The default framework behavior can be customized fit your
needs.
| NOTE: |
To follow along, we assume
you have followed the instructions in the Demo Installation and
Setup section and
have the ADFToyStore.jws workspace open in the JDeveloper
IDE, and your default browser open to the ADF Toy Store home page as shown in
Figure 3.
|
How the Application is Organized Into Packages and Projects
Like all applications built in Java, the ADF Toy Store demo is comprised
of a set of classes, organized hierarchically into packages.
Figure 14 illustrates the key packages in the demo. We have
used the package naming to make it clear how the application classes break down
into model, view, and controller layers, as well as to clarify which classes
are part of our framework customizations and regression testing suite.
 Figure 14: Java Package Hierarchy for ADF Toy Store
demo
When building applications that leverage existing frameworks, your
application-specific classes inherit default functionality from an appropriate
framework base class. They inherit core behavior from their superclass, and add
application-specific logic and metadata. Typically, the only code needed in
your classes is the code that is specific to your application's business
functionality. Figure 15 shows some representative
examples of classes in the ADF Toy Store demo that inherit their behavior from
a framework:
- The main business service
component
toystore.model.services.ToyStoreService extends
the ADF framework base class
oracle.jbo.server.ApplicationModuleImpl, adding custom
business service methods and an application-specific "data model" of named
collections of data transfer objects (also known as value
objects) exposed to the client.
- An
example query component
toystore.model.dataaccess.ProductsInCategory extends the ADF
framework base class oracle.jbo.server.ViewObjectImpl,
adding an application-specific SQL query for products in a particular category
and providing custom methods to encapsulate the setting of its bind
parameters.
- An example business object
toystore.model.businessobjects.Account extends the ADF
framework base class oracle.jbo.server.EntityImpl, adding
application-specific attributes for user accounts and specifying several
business rules that govern an account's validity.
- The data transfer object
toystore.model.dataaccess.common.ShoppingCartRow extends the
ADF framework base interface oracle.jbo.Row, adding typesafe
access to the application-specific attributes in the row of shopping cart
information.
- The action
toystore.controller.strutsactions.PlaceOrderAction extends
the Struts framework base class
org.apache.struts.action.Action, adding application-specific
controller logic needed before rendering the HTML form to collect shipping
information for the order being placed.
- The
test case
toystore.test.unittests.CreateAnOrderTest extends
the JUnit framework base class junit.framework.TestCase,
adding application-specific testing logic that exercises the
ToyStoreService business service by simulating the creation
of an order after adding items to the shopping
cart.
 Figure 15: Example of Demo Classes that Extend Frameworks
JDeveloper 10g provides two constructs to organize our work: workspaces
and projects. Projects contain a set of files that get compiled (and perhaps
deployed) as a unit, and workspaces are a list of projects that go together to
comprise a complete application. Theoretically, we can build any application
with all of the files in a single project, but typically
we organize our work into a number of separate projects to divide up the work
into more logical groupings.
As shown in
Figure 16, the ADF Toy Store application is comprised
of a ADFToyStore workspace containing the following seven
projects:
-
ToyStoreModel.jpr
This project contains
the components in the toystore.model.* package tree,
including the main business service
toystore.model.services.ToyStoreService and all the business
object and data access components on which it relies to provide its application
functionality and model data to the client. It also contains the translated
resources (in English, Italian, and German) related to these
components.
-
ToyStoreViewController.jpr
This project
contains
- the JSP pages that comprise the
user interface of the web store, and the view-layer resource files in the
toystore.view package of the translatable text that appears
in all the pages.
- the Struts configuration
file
struts-config.xml and the source code for all of the
classes in the toystore.controller.* package tree. This
includes the Struts actions that coordinate the interaction between the
business service and the view-layer
pages.
-
ToyStoreViewControllerUIX.jpr
This is a
parallel view/controller project that illustrates the same application built
using a view layer comprised of Oracle ADF UIX pages instead of JSP pages. This
project contains
- the UIX pages that
comprise the user interface of the web store, and the view-layer resource files
in the
toystore.view package of the translatable text that
appears in all the pages.
- the Struts
configuration file
struts-config.xml and the source code for
all of the classes in the toystore.controller.* package
tree. This includes the Struts actions that coordinate the interaction between
the business service and the view-layer
pages.
-
FwkExtensions.jpr
This project contains
the classes in the toystore.fwk.* package tree that extend
the base ADF and Struts framework facilities to augment and/or customize the
default framework behavior. These customizations are not specific to the web
storefront and could be easily reused in another Struts/ADF
application.
-
Testing.jpr
This project contains the
classes in the toystore.test.* package tree, including a
JUnit regression test suite, test fixture, and unit tests for various aspects
of the ToyStoreService component.
-
DatabaseSetup.jpr
This project
contains the two SQL scripts used to setup the database schema for the demo, as
well as a JDeveloper database diagram of the Toy Store database
design.
-
Documentation.jpr
This project contains a
copy of this whitepaper in the readme.html
file.
 Figure 16: The ADFToyStore Workspace in the JDeveloper Application
NavigatorAdvantages of a Model/View/Controller
Architecture
First generation JSP applications freely mixed code
"scriptlets" into the page among the HTML presentation tags. The code for
parameter evaluation, data access, business rules enforcement, transaction
management, error handling, and page flow was simply typed right into the same
JSP file that would also eventually format the data for
the end-user to see. Having everything in one file and being able to see
compilation errors by refreshing the browser lent an immediacy to development
that enticed many developers to follow this approach. However, this hybrid
approach more often than not produced pages that were impossible to read.
Attempts to alter the look and feel of the pages, unless performed by the
original developer, could lead to hours of staring at the file, hunting for the
unintended typographical error.
Code scriptlets in JSP pages began
to fall out of favor as JSP 1.1's tag libraries allowed many common tasks to be
performed using easier-to-read elements and attributes. However, the popularity
of tag libraries that performed SQL data access or EJB component interaction
directly from the JSP page was still an indication that developers were not
correctly separating the presentation layer from the application layer. In
these first generation JSP applications, the model, view, and controller layers
were hopelessly intertwined.
As these applications evolved,
attempts to respond quickly to new business needs requiring an updated look and
feel or modified web page flow were greatly complicated by this "heavy page"
approach. Developers bitten by the maintenance nightmares of the
first-generation approach immediately understood the benefits that the Model,
View, and Controller separation has to offer. In a nutshell, with an MVC
architecture:
- Application look and feel
can change without affecting core application logic
- Page flow and error handling are centralized and removed from individual
pages
- Simpler-looking web pages can be
understood and modified by less technical team
members
With its advantages now clear,
let's begin to look at how our Toy Store demo implements the Model, View, and
Controller layers of its architecture.
Implementing the Model Layer Using ADF
Business Components
The model layer is comprised of business
services, query components, business objects, and collections of data transfer
objects that the business service exposes to the controller and view layers. In
this section we'll highlight some examples of these model layer components from
the ADF Toy Store demo and briefly explain how they leverage the ADF framework
for their implementation.
Considering Model Layer Approaches: EJB-Centric or
Web-Tier-Centric?
Before exploring the ADF Toy Store model layer
implementation in detail, we should first stop to consider the important choice
of whether the model layer will be implemented using:
- EJB Technology deployed to the J2EE EJB Tier,
or
- JavaBeans Technology deployed to the J2EE
Web Tier.
As illustrated by the two
separate sample applications provided by Sun's "J2EE
Blueprints" demo team, the approach you choose for your model layer can have a
major impact on the application's underlying implementation. The
architecture
documentation [21] that accompanies the more recent Adventure Builder demo
explains:
The
Java
Pet Store [9] application illustrates how to write a Website application in
an EJB-centric manner. The
Adventure Builder [22] application
illustrates the other option: how to write a Website application in a
Web-centric manner. EJB is a key technology in the J2EE platform, but not all
J2EE applications need to use it.
The document goes
on to explain some of the motivations behind making the choice:
One important design consideration is mapping application
modules and functionality to the different tiers and technologies on the J2EE
platform. Some choices are obvious, such as having a web tier when a web
browser client is required. Other choices may depend on several factors. Issues
such as data access and transactional needs, security, portability and
modularity of design, lead to deciding how to optimally map the application
modules to the client, Web, EJB, and EIS (data storage) tiers. An important
question is whether to use an EJB tier. Based on the application's needs, one
might choose not to use enterprise beans and the EJB container and tier. The
expertise of the development team also affects this decision. For example, a
team with strong Web-tier and SQL skills may find it easier to write a Web-only
application especially when they are new to the EJB technology and are pressed
for time to learn it.
Using the ADF framework, you
build your J2EE application using a consistent development approach that is
independent of your choice of deployment tier for your model layer. You
develop, test, and debug the application using a model layer built from
high-performance, well-architected, XML-configured JavaBeans. At any time
during the development process, you can choose to deploy your model layer as
JavaBeans to the J2EE Web Tier, or as an EJB Session Bean to the EJB tier. Some
business requirements that might nudge you in the direction of an EJB tier
deployment include the need to:
- Coordinate
ADF-powered services with other Session Beans in the same
transaction
- Leverage method-level security on
your ADF-backed services.
Since the ADF
framework provides an implementation of the best-practices
Business Delegate [23] design
pattern, your model and view layers are isolated from these deployment details.
Even if you change your mind mid-project on your preferred model-layer
deployment architecture, none of your application code needs to change. In
fact, you can try out both deployment options and pick the one that delivers
best performance for your particular application scenario. In other words,
using the ADF framework, you don't have to decide up front on an EJB-Centric or
Web-Tier-Centric approach, and you can change your mind at any time, without
rearchitecting your system.
For the purposes of this demo, we have
selected to deploy the ADF Toy Store demo's model layer to the J2EE web tier to
keep the demo as easy to follow as possible for the widest audience of Java
developers. For the reasons we've just mentioned, redeploying the model layer
to the EJB Tier would be a painless step for those wanting to take an
EJB-centric approach.
Implementing Business Services with ADF Application Module
Components
While Oracle ADF supports using virtually any kind of
Java class as a business service, the ADF Business Components option we provide
gives you the highest level of built-in application-building functionality and
developer productivity.
Business services built using the ADF
business components are called application modules. These
service components are:
- Cleanly
architected with a client-side business service interface
and server-tier implementation
- Efficiently
implemented as JavaBeans, but deployable as EJB Session Beans as necessary,
with support for container-managed transactions
- Automatically configured at runtime from XML metadata and created through
framework-supplied factories
- Easily used by
clients through ADF's implementation of the Business
Delegate design pattern
- Cleverly
designed to expose "active" collections of updateable data transfer objects
that interact with your business objects without
code
All of these features can be
summarized by saying that ADF-powered service components make the J2EE
developer's life a lot simpler. The key ADF framework components that cooperate
to provide the business service implementation are:
- Application Modules to build transactional
business services
- View
Objects and View Links to build collections of
updateable data transfer objects based on SQL queries
- Entity Objects and
Associations to encapsulate business rules and persistence
details of domain business objects and express the relationships between
them
- Domains - to build
custom datatypes, where necessary
Our
toystore.model.services.ToyStoreService application module
is the heart of our application. It is a JavaBean component that implements the
business service interface shown in
Example 1.
Example 1: ToyStoreService Business
Interface
package toystore.model.services.common;
public interface ToyStoreService extends ApplicationModule {
boolean validSignon(String username, String password);
boolean adjustQuantityInCart(String[] itemid, long[] qty);
boolean isCartEmpty();
long currentQuantityInCart(String itemid);
Double getCartTotal();
boolean adjustQuantitiesInCartAsStringArrays(String[] itemid,String[] qtyStrings);
String finalizeOrder();
void createNewOrder(String currentUsername);
void prepareToShowCategory(String id);
void prepareToShowProduct(String id);
void prepareToShowProductDetails(String id);
void prepareToSearch(String searchFor);
void prepareToCreateNewAccount();
void prepareToShowReviewOrder(String id);
boolean prepareToEditAccountInfoFor(String username);
} |
As shown in Figure 17, the
ToyStoreService component is implemented as a set of files.
The Application Navigator presents the component as a single, logical icon,
while the Structure Window's Sources folder shows the
detailed implementation files comprising that component:
ToyStoreService.xml - Service definition
file
ToyStoreService.java -
Service interface
ToyStoreServiceImpl.java - Service
implementation
ToyStoreServiceClient.java - Service client
proxy
| NOTE: |
There are four
ways to navigate to the files that comprise your ADF business
components:
- You can click on the component
in the Application Navigator and select one of the Go to
Class... options at the bottom of the right-mouse menu. For an
application module, you will see Go to Application Module
Class..., for example.
- You can
double-click on the source file name in the Structure Window's
Sources folder.
- You can
select Navigate | Go to Java Class... from the menu,
or type its key equivalent
Ctrl+Minus, and start
typing in the name of the class to get quick file-name completion (regardless
of what package the class is in).
- You can
select Navigate | Go to Recent Files... from the
menu, or type its key equivalent
Ctrl+=, and select it
from a list of recent files you've
edited.
|
The client proxy class is
created when you've exposed custom methods to be accessed by clients, alongside
the custom service interface that the ADF design time creates for your
component. The client proxy class implements your custom component interface,
and is used automatically at runtime when client layer is deployed in a
separate tier from the business service layer or when using
Batch Mode.
The application module
editor in JDeveloper automatically keeps the XML component definition file and
business service interface in sync with the declarative options you set using
the editor. For example, the business service interface automatically appears
in your project as soon as you mark any custom methods as part of the service
interface on the Client Methods panel in the editor. As
we'll see in the other sections below, all ADF business components follow this
basic pattern for the names of the files that comprise their definition,
implementation, and interface.
 Figure 17: Structure
Window Showing Sources Comprising ToyStoreService Application Module
| NOTE: |
The JDeveloper Application Navigator can show packages
either in a flat Package List mode, or in a hierarchical
Package Tree mode as we're using above. To switch between
the modes, click on your Application Sources folder and
use the pop-down toolbar button to choose the mode you prefer as shown here.
The button just to the right of this gives you control over sorting your
components under Application Sources by type. By default,
they will just sort alphabetically. If you turn on the Sort by
Type mode, then they'll be sorted by object type, then
alphabetically.
 |
If
you have a look inside the ToyStoreServiceImpl.java file,
you'll see it contains the code implementing the business service interface
methods, and some JDeveloper-generated convenience methods to access
collections of data transfer objects. In order to more clearly identify
custom code from JDeveloper-generated code, we've
surrounded all of the developer-written, application-specific code with marking
comments like:
//--[Begin Custom Code]--
and
//--[End Custom Code]--
You'll see these same marking comments in all of the
ADF-based JavaBean components in the application.
Exposing Model Data to the View and
Controller Layers
When implementing Model/View/Controller (MVC)
applications by hand, best practice techniques steer
developers to expose model data to the controller and view layers using a
HashMap object. This "model data map" gives the client
layers a single object that represents the entire "data model" needed for the
current application task. Using the model data map, the controller and view
layers can easily find any collections of data transfer objects using a
convenient string key name.
For example, the model data required
to display the summary of an order might include:
- Account information for the customer placing the
order
- Order information
- Order line item information to show the items and quantities
the customer purchases
- Shipping option
information to drive a poplist of delivery
choices
Figure 18
illustrates what the model data map object would look like for such a task.
 Figure 18: Find Named Collections of Data Transfer
Objects Using a Model Data Map
Example 2 shows the typical code required to find the
collection of data transfer objects for line items from a model data
map.
Example 2: Finding a Collection of
Data Transfer Objects from a Model Data Map
/*
* Find the collection of line item data transfer objects from the
* model data map using string key "LineItems"
*/
Collection lineItems = (Collection)modelDataMap.get("LineItems");
/*
* Iterate over the LineItem data transfer objects in the collection
*/
Iterator iter = lineItems.iterator();
while (iter.hasNext()) {
LineItem line = (LineItem)iter.next();
// Work with the line item values using getter/setter methods
Long quantity = line.getQuantity();
// etc.
} |
ADF Application
Modules Implement Your Model Data Map For You
The model data map
discussed above is a necessary feature of any MVC application. Business
services implemented as ADF application modules inherit a built-in model data
map implementation. The application module cooperates with ADF view object
components to allow you to build your model data map declaratively.
You create view object components to encapsulate SQL statements that will
produce the required collections of data transfer objects. Then you define your
model data map at design-time by adding instances of these
view object components to your application module using the appropriate panel
of the Application Module Editor shown in Figure 19.
The names that appear in the Data Model list on the right
are the string key names that you'll use at runtime to find the collection of
data transfer objects produced by this view object instance. Of course, you can
pick any names you like.
 Figure 19: Declaratively Define Your Model Data Map Using the Application Module
Editor
The names
appear in a tree control to illustrate visually any master/detail coordination
that the ADF framework is doing on your behalf among the collections of data
transfer objects. For example, the indentation in
Figure 19 shows that the collection named
Orders will automatically contain only those orders for the
current account data transfer object in the Accounts
collection, and the LineItems collection will contain the
line items for the current order data transfer object.
Figure 20 shows the same
ToyStoreService application module in a visual way using the
UML diagramming support for ADF components in JDeveloper 10g. Each of the named
collections in its data model map appears, and master/detail relationships are
indicated with their cardinality using lines and arrows between the
collections.
 Figure 20: Visual Diagram of the
ToyStoreService's Data Model
Since the application module component implements the model data map for
you, at runtime the view or controller layer can lookup a particular collection
of data transfer objects by name using the instance of the application module
service component that it is working with using syntax as shown in
Example 3.
Example 3: Finding a Collection of Data
Transfer Objects Using an Application Module
/*
* Find the collection of line item data transfer objects from the
* model data map implemented by the application module component
* using string key "LineItems".
*/
LineItems lineItems = (LineItems)yourAppModule.findViewObject("LineItems");
/*
* Iterate over the LineItem data transfer objects in the collection
*/
while (lineItems.hasNext()) {
LineItemRow line = (LineItemRow)lineItems.next();
// Work with the line item values using getter/setter methods
Long quantity = line.getQuantity();
// etc.
} |
If you do not want to
work with typesafe collections of data transfer objects, you can opt to work
with ADF's generic collection implementation
(oracle.jbo.RowSet) and generic data transfer object
implementation (oracle.jbo.Row) by writing code like this
instead:
RowSet lineItems = (RowSet)yourAppModule.findViewObject("LineItems");
while (lineItems.hasNext()) {
Row line = lineItems.next();
Long quantity = (Long)line.getAttribute("Quantity");
/* etc. */
}
In addition to the useful
findViewObject() method to access collections of data
transfer objects from the built-in model data map, business services like our
ToyStoreService inherit several other useful methods related
to flexibly working with application data. They are beyond the scope of this
article since we didn't require their use in the ADF Toy Store demo, but
JDeveloper's
Online Help system [2] covers all of the framework API's in its reference
documentation if you are curious for more details.
| NOTE: |
To find
the ADF framework API documentation, with JDeveloper running do the following.
Launch the help system with Help | Help Topics...
and then expand the Reference node to see the
Business Components for Java category. All the JavaDoc is
there. If you prefer to browse the JavaDoc with your own favorite browser, then
expand the bc4j*doc.jar files in the
JDEVHOME/jdev/doc/ohj directory
into a convenient directory and go for it!
|
Implementing Domain Business
Objects Using ADF Entity Object Components
Business objects built
using ADF are called entity objects. Like application
module components, your entity objects are JavaBeans that extend a framework
base class, are configured from XML metadata, and are created by factories.
They cooperate automatically with other ADF framework components to help make
application building easier. The distinguishing role of entity objects is to be
the software implementation of the domain business
entities in your real-world business object model.
Since
developers typically use the Unified Modeling Language (UML) to visualize their
business object model, we can use JDeveloper 10g's UML modeling features for
ADF to do just this. Figure 21 shows where to find the
Business Objects UML model in the ToyStoreModel
project.
 Figure 21: Finding the "Business
Objects" UML Model in ToyStoreModel Project
You can see the UML
diagram named "Business Objects" in the toystore.uml package
by double-clicking on it. Figure 22 shows what you will see when
you open the diagram. It's the business object model for the ADF Toy Store
demo. Here, I've set the display options for the diagram shapes to only show
the object attributes to keep things simple, but you can easily turn on
additional options to show methods and other UML artifacts on the diagram,
too.
 Figure 22: UML
Diagram of the ADF Toy Store Domain Business Objects
For each real-world business entity in the application domain, an ADF
entity object:
- Defines the names and
datatypes of the attributes required to model its business
data
- Declares how it is associated to and/or
composed of other entities in the model
- Encapsulates the business rules governing the entity and any composed
entities
- Handles the persistence of changes
made to business objects
By default,
each entity object inherits high-performance, relational-database persistence
functionality from the ADF framework, too. Custom persistence schemes can be
implemented by overriding one framework method in your domain-specific entity
subclass. For example, some ADF framework users are doing this to adapt their
entity objects to use an existing PL/SQL package API for updating information
in their base tables.
One built-in feature we can notice from the
UML model in Figure 22 is that the
Orders entity object uses a DBSequence
type for its Orderid attribute. By configuring an entity
object to have a datatype of DBSequence, the ADF framework
automatically handles the common case of primary key values assigned from a
database trigger, without having to write Java code.
Our UML
business model in Figure 22
visualizes several other interesting things about the relationships between our
entity object components. In particular, it shows:
- The associations and compositions between
entities
- The cardinality of the association
(one-to-one, one-to-many, etc.)
- The
programmatic navigation possible between entities (the
arrowheads)
For example,
Orders are composed of one or more
Lineitem and each Lineitem is associated
with an Item. Each Item is associated
many-to-1 with Supplier, and one-to-one with
Inventory. The arrowheads imply, for example, that code in
Lineitem can call
getItem().getInventory() to access the instance of the
Inventory object that tracks the items quantity in stock. In
fact, if you look at the finalizeOrder() method in the
ToyStoreServiceImpl.java class, you'll see programmatic
association traversal at work as shown in the code snippet below, accessing the
inventory object for the item being ordered on the current order line item, and
setting the inventory quantity on hand to the adjusted quantity.
// Decrement Inventory Quantity for current line item amount
InventoryImpl inv = newLine.getLineitem().getItem().getInventory();
double currentQty = inv.getQty().doubleValue();
double newQuantity = currentQty - (newLine.getQuantity().doubleValue());
inv.setQty(new Number(newQuantity));
As you can see in
Figure 23, which shows the
toystore.model.businessobject package in the JDeveloper
Application Navigator, entity object components like Account
are comprised of a number of constituent files:
Account.xml - Entity definition file
AccountImpl.java - Entity
implementation
AccountImplMsgBundle.java - Entity message
bundle
 Figure 23: Domain Business Objects and
Associations in the ToyStoreModel Project
All of the declarative aspects of the
Account entity object definition are kept in the
Account.xml file. This includes attribute definitions,
declarative business rules, and database table/column mapping information. As
we saw with application module components, you never have to hand-modify the
declarative XML yourself. The multi-panel Entity Object Editor in JDeveloper
shows you all of your entity component's settings and lets you easily configure
its declarative behavior. An important thing to notice is that entity objects
do not have a client-accessible interface as application
module components do. No client interface is required since entity objects are
not meant to be directly accessed by the controller or view layers. Entity
objects are private to the model layer by design.
Underlying Database Schema for the Toy
Store Demo
To get an overall view of the database design that our
business object layer maps onto for its persistence, visit the
DatabaseSetup project in the ADFToyStore
workspace, and double-click on the "Toystore Database
Design" diagram under the toystore.db package in
the Application Sources folder. This will bring up the
JDeveloper 10g database diagrammer showing the ADF Toy Store table design as
shown in Figure 24. With the exception of the
STATES_FOR_COUNTRY table that is used by our State/Country
validation rule, all of the other tables are identical to those used in the
original Java Pet Store Demo.
 Figure 24: ADF Toy
Store Database DesignSupported Approaches for Implementing
Business Rules
Figure 25 shows the
Validation panel in the Entity Object Editor for the
Account object, illustrating the object-level and attribute
level business rules that we've defined for this component.
 Figure 25: Validation Rules Panel in the Entity
Object Editor for "Account"
Table 1 shows the declarative business rules that have been
enabled for the Account and Orders entity
objects.
Table 1: Examples of Declarative
Business Rules In Use By Demo Business Objects
| Component Name |
Declarative
Business Rule |
Account |
UniquePKValidationBean checks
that the new username entered doesn't conflict with one already in
use.
VerifyStateForCountry
checks that the state/province code is valid for the country code provided for
the account's home address.
ListValidationBean checks that the
Country attribute value is a country code from the
toystore.model.dataaccess.CountryList view object's default
rowset.
|
Order |
VerifyStateForCountry checks
that the state/province code is valid for the country code provided for the
order's shipping address.
- Method validation
rule
validateCreditCardExpiration() that raises an exception
if the credit card number provided for the order is
expired.
|
Rules like the UniquePKValidationBean,
ListValidationBean, and Method are
supplied with the framework. As we'll see more in detail later in this paper,
the VerifyStateForCountry rule is a declarative rule whose
implementation we've written ourselves. Once a custom rule is written, other
developers can use it declaratively just like any of the supplied rules by
setting specific usage-specific parameter values that will drive the rule
evaluation. In general, validation rules can be set at object-level and
attribute-level with the following three implementation choices:
-
Use one or more pre-supplied rules
This only requires picking the rule type and setting any properties that
govern its behavior. For example, a RangeValidationRule
might have LowValue and HighValue
parameters that must be supplied to define the range.
-
Use one or more custom method validation rules
Defining method validation rules causes the framework to evaluate various
validateSomething() methods that
you've written in your entity object's implementation class. These rules are
appropriate for complex validation that don't make sense to generalize into a
custom rule to be reused by other entities.
-
Using custom business rules
Custom rules are JavaBeans that
implement the JbiValidator interface in the
oracle.jbo.server.rules package. Once defined, they can be
packaged as reusable rule libraries and put to work declaratively by other
developers on the team.
The
validateCreditCardExpiration() method on the
Orders entity object illustrates a custom validation method.
Figure 26 shows the Method validation
rule that we've defined on Orders to engage the
validateCreditCardExpiration() method.
 Figure 26: Method Validation Rule in Use for "Orders"
Entity
Since
this custom method-based validation rule depends on two different attributes
(Creditcard and Exprdate) we implement it
as a validation rule at the business object level, instead of at the
attribute-level. Here we're illustrating how to use code-based validation as an
alternative to declarative business rules like the
VerifyStateForCountryRule that is also associated to this
Orders business object.
The expected signature
of validation methods invokeable by the Method business
rule is:
public boolean
validateSomeNameYouChoose()
This means that our custom validation methods should return
true if the validation succeeds. For validation failures, we
can throw an oracle.jbo.ValidationException for an
object-level exception, or an oracle.jbo.AttrValException
for an attribute-level exception, accompanied by a custom error message. Of
course, if the framework's generic exception message is adequate, we can just
return false to indicate failure as well.
By
throwing an attribute validation exception, the error is attributed to a
specific attribute instead of to the object as a whole. This attribute name
information is then available to the controller-layer code that handles errors
so it can decide where to present the error to the user.
Example 4 shows what the code for the
validateCreditCardExpiration() method looks like.
Example 4: Custom Validation Method to Verify Credit
Card Expiration Date
public boolean validateCreditCardExpiration() {
if (getCreditcard() != null) {
ExpirationDate expDate = getExprdate();
/*
* If the expiration date is not in the future, then throw an error
* that the card is expired
*/
if (expDate != null && !expDate.isFutureDate()) {
throw new AttrValException(
OrdersImplMsgBundle.class,
OrdersImplMsgBundle.EXPIRED_CREDITCARD,
getDefinitionObject().getFullName(),
getDefinitionObject().getAttributeDef(EXPRDATE).getName(),
null,null);
}
}
return true;
} |
| NOTE: |
See the
Business
Rules in BC4J [24] (PDF) whitepaper for a systematic discussion of how to
classify business rules and implement them with ADF Entity Objects. The
article's tips are all valid for Oracle ADF as well.
|
Using View Object Components to Simplify
All Aspects of Data Access
One of the most frequent and
fundamental tasks that business application developers do is access business
information for iteration, presentation, and modification. As simple proof of
this fact, we need only note that virtually every page of the ADF Toy Store
demo relies on displaying or editing business information. For example:
- while browsing, the user sees pages showing
store categories, products, and items
- the
Register as a New User page captures new data from the
user
- the Edit Profile
page allows the user to update existing account information
- the Your Cart page shows the items the
user has added to their shopping cart.
Unfortunately, in the J2EE world without frameworks,
this omnipresent data-access task is fraught with implementation complexity in
the name of adhering to J2EE best practices design patterns. Just look at what
developers are encouraged to do by most J2EE design pattern books:
- Write code using the JDBC API to implement the
Fast Lane Reader [25] pattern to
query database data,
- Encapsulate that JDBC
code in a
Data Access Object [26] to isolate
the retrieval method in case the datasource changes later
- Write code to implement the
Transfer Object [27] pattern.
These data transfer objects (or value
objects) reflect the structure of the JDBC result
rows.
- Write code to implement the
Value List Handler [28]
pattern to iterate over the rows in the JDBC
ResultSet and
construct instances of row-like data transfer objects holding copies of the
queried row data.
- Implement the
Transfer Object
Assembler [29] pattern to aggregate multiple collections of data transfer
objects into a single object for further minimizing network round
trips.
Of course, these suggestions are
not bad in and of themselves. All of their functionality is interesting and
valid. However, their hand-coded implementation implies a
ton of uninteresting, infrastructure-level programming tasks that should not be
required by application developers. This observation becomes even more
painfully clear when we consider that only really
interesting things changing in each usage scenario of the
above patterns is:
- What SQL query is
required to retrieve the data I need?
- What are
the attribute names and datatypes of the data transfer object needed to
transport a row of data from the result set of this query to the
client?
As with many hand-coded J2EE
development techniques, there is a better way! Rod Johnson devotes all of
chapter 9 in Expert One-on-One: J2EE Design and
Development to "Practical Data Access", and he recommends developing a
generic JavaBean component that can handle all of these gory details of data
access. Specific query components then extends this generic "query bean" to add
the usage-specific details like the SQL statement.
This is
precisely what the ADF framework provides with its View
Object component. Like the other ADF framework components we've seen, your view
objects are JavaBeans that extend a framework base class, are configured from
XML metadata, and are created by factories. Once configured with a SQL
statement you want it to execute, each view object component automatically
implements all of the following design patterns for you:
Data Access
Object [26],
Fast
Lane Reader [25], and
Transfer
Object [27]. They also cooperate with the application module component to
provide an implementation of the
Transfer
Object Assembler [29] pattern, too.
While defining your view object, in
addition to setting up your query (or letting the editor build it for you), you
also configure the attribute names and datatypes of the data transfer object
that will carry the query's result row data to the client.
Figure 27 illustrates this relationship between the view object
and its "view object row" data transfer object. When the view object produces
collections of data transfer objects after executing a query, each object in
the collection is an instance of the view object row class.
 Figure 27: View Object and Its View Object Row "Data Transfer
Object"
Of course,
the ADF design time editor defaults the names and datatypes for you based on
metadata it obtains from your query statement, but you can change the names and
datatypes of the attributes in the view object row as required. The declarative
information about the query and the view-specific data transfer object
attributes is saved in the view object's XML file.
In addition to
the ViewName.xml file that all
ADF components have, as shown in Figure 28, the view
object component can comprise a number of optional additional files as
well:
- View Object implementation file
(
ViewNameImpl.java) used to
customize the behavior of the view object component or implement custom
methods.
- View Object interface
(
ViewName.java) appears
automatically when you expose one or more of your custom view object methods on
the Client Methods panel of the View Object
Editor.
- View Row implementation file
(
ViewNameRowImpl.java) used to
customize the behavior of the data transfer object associated with this view
object.
- View Row interface
(
ViewNameRow.java) appears
automatically when you expose one or more of your custom view row (data
transfer object) methods on the Client Row Methods panel
of the View Object Editor.
- View Row message
bundle
(
ViewNameRowImplMsgBundle.java)
appears automatically when you define any built-in prompts, tooltips, format
masks, etc., for your view object.
Client
proxy classes can be generated for view object and/or view rows as well if you
have exposed custom methods on either or both of these.
For
example, toystore.model.dataaccess.Accounts is implemented
by the following set of files:
Accounts.xml - View Object definition
file
AccountsImpl.java -
View Object implementation
AccountsRowImpl.java - View Row
implementation
AccountsRowImplMsgBundle.java - View Row
message bundle
 Figure 28: Data Access Objects in the ToyStoreModel
Project
Looking at the
files associated with other view objects in the same package like
ItemsForSale, LineItems, and
ReviewOrder, we see that the additional files only appear as
needed. In the case of ReviewOrder, we didn't need to
customize the view or view row code, and we didn't need a view row message
bundle, so only the ReviewOrder.xml file exists. Everything
necessary for this particular query is captured in metadata. The
LineItems view object illustrates using only code in the
view row class. The ItemsForSale view object is an example
that uses custom code in the ItemsForSaleImpl.java class to
encapsulate bind variable parameter setting, and it exposes these custom
methods through the automatically-maintained
ItemsForSale.java client interface.
Other view
objects in the demo that contain custom methods to encapsulate their bind
variable details are ProductsInCategory,
FindProducts, and ProductList. By
exposing custom view object interfaces, your controller layer can contain code
like Example 5 to set query parameters without being aware
that the query is being performed using SQL.
Example 5: Setting View Object Bind Variable Values via a Custom Method
/*
* Find the ProductList collection of data transfer objects from the
* application module's data model map by name.
*/
ProductList productList = (ProductList)appModule.findViewObject("ProductList");
/*
* Set query bind parameters using custom method that encapsulates
* WHERE clause handling inside the view object component so that
* controller and view layers never work with SQL directly.
*/
productList.setProductIdToFind(request.getParameter("id")); |
By default, ADF creates instances of a
generic data transfer object (oracle.jbo.Row), but by
checking the Expose Accessors to the Client checkbox on
the Java panel of the View Object Editor, you can expose a
typesafe data transfer object interface that lets you access the data using
compile-time-checked accessor methods like getQuantity()
instead of using generic, runtime-evaluated accessor methods like
getAttribute("Quantity").
At runtime, your
view object components handle:
- Encapsulating the data access code
- Querying the data using optimized, best-practices JDBC
techniques
- Constructing instances of the data
transfer objects for query result rows
- Browsing data a page at a time
- Exposing
one or more collections of data transfer objects based on the view object's
query
While a view object
can be used to produce multiple collections of data
transfer objects, and can iterate each collection using one or more iterators,
in 90% of the cases, developers need to just iterate through a single
collection of results. To cater to this frequent use case, the base
ViewObject framework component implements the
RowSet interface and delegates that interface's methods to
an aggregated instance called the "default rowset". Similarly, all
RowSet objects implement the
RowSetIterator interface, and delegate its methods to an
aggregated instance called the "default iterator".
Figure 29 illustrates how the pieces fit together. This
setup allows developers in most common cases to work with a single view object
instance at runtime, and easily iterate its default collection of data transfer
objects.
 Figure 29: View Object Aggregates a
Default RowSet for Ease of Use
Since
view objects encapsulate how data is accessed, it's possible to retrieve data
from datasources other than the normal SQL database query without changing
other parts of your application. As a concrete example of this,
CountryList, CreditCardList,
ExpirationYearList, and
ShippingOptionsList view objects in the ADF Toy Store demo
"query" their data from a standard Java *.properties
file.
View Object queries can automatically be related
master/detail by creating additional components called View Links. A view link
connects a source and a target view object, and defines (in XML, of course!)
which attributes in the source view object row need to correspond to the
attributes in the target view object row. By creating view links between view
objects, you make it possible for the framework to automate the coordination of
the correlated queries on your behalf. You also get the programmatic benefit of
being able to call "view link accessor" methods to traverse from one row to its
detail collection of correlated rows very easily.
How Framework Components
Cooperate to Simplify Data Modification
Easily querying and
iterating collections of data transfer objects illustrates only part of the
view object's value proposition for application developers. View objects
cooperate with entity objects and application modules to dramatically simplify
creating, updating, and deleting business information as well. In order to
appreciate the work this powerful trio is saving us, we'll again study the
manual steps that J2EE developers do to modify data when working
without a framework.
The controller layer
provides data from the end-user to the business service in the form of one or
more instances of data transfer objects. The task at hand might involve
creating, updating or deleting instances of business objects in the model
layer. The steps for creating new data typically require business service code
to:
- Create a new instance of the
appropriate back-end business object
- Populate
the attributes of the business object based on corresponding data in the data
transfer object(s)
- Commit the
transaction
Steps for updating existing
data typically require business service code to:
- Lookup the existing instance of the correct back-end business object by
primary key, using appropriate attribute value(s) from the data transfer
object
- Update the attributes of the business
object instance based on appropriate, corresponding data in the data transfer
objects, ideally only updating the attributes that the client
actually changed in case attribute-level auditing is being
done inside the business object layer
- Commit
the transaction
Steps for removing
existing data typically involve:
- Looking up
the existing instance of the correct back-end business object by primary key,
using appropriate attribute value(s) from the data transfer
object.
- Deleting the instance by invoking an
appropriate delete method on it.
- Committing
the transaction
The above steps are those
required for the business service to handle a single
instance of an incoming a data transfer object from the controller layer. In
reality, the incoming data might be a collection of data
transfer objects, or a set of collections of one or more different kinds of
data transfer objects. In that case, you would combine the steps above with
iteration and conditional logic to lookup the correct back-end business object
based on the kind of data transfer object coming in.
As with much
of the uninteresting application infrastructure code that too many J2EE
developers are used to writing themselves, this code is not mind-bendingly
difficult to write. However, the unmistakable déjà vu you
feel each time you write the code above tells any good programmer that this is
a task that can be generalized and driven through metadata to cut down on
hand-written code.
The only pieces of information that are
unique to each situation are:
- Which back-end business objects are related to the data in this
data transfer object?
- How do data transfer
object attributes correspond to back-end business object
attributes?
Using this information, our
implementation of the
Value List
Handler [28] pattern could be enhanced to support updating the data transfer
objects and automatically coordinating them with the back-end business
objects.
Luckily, this information is
precisely what ADF view objects store in their XML
metadata file, along with the other metadata about the SQL query and the
attribute names and datatypes of the associated data transfer object. This
means that view objects can be related to back-end entity objects in a
declarative way, and they store this "attribute map" information for use at
runtime to completely automate the coordination between client-modified data
transfer objects and the back-end business objects to which they relate. This
makes creating, updating, and deleting back-end business data very easy because
the controller layer can simply work with the collections of data transfer
objects (DTO's) to:
- Add new DTO instances
to any collection
- Update the attributes of
existing DTO instances
- Remove existing DTO
instances from any collection
- Find a DTO
instance in a collection by primary key
All of the changes made on the data transfer objects in a view object's
RowSet collection are delegated automatically back to the appropriate entity
objects without user-written code to orchestrate that cooperation. Since the
entity objects encapsulate the business rules as we explored in a previous
section, this delegation of DTO attribute setting to appropriate entity object
attributes causes any relevant business rules to be evaluated. Of course, any
failures are thrown as exceptions that can be trapped and presented to the
user.
Developers using the ADF framework for their model layer
have control over how "eagerly" this delegation between DTO attribute
modifications and entity object attribute modifications is performed. For
rich-client applications with highly-interactive graphical user interfaces
built in Swing, the delegation can be set to be "immediate". This causes errors
raised by business rules in the underlying entity objects to be thrown
immediately for interactive user feedback as expected.
For
web-style applications, the entity-level delegation of changes made to the
collections of DTO's can be done in more of a "batch" style. To avoid
frustrating the end user by presenting one error at a time to be corrected, ADF
offers additional facilities for bundling a maximal set of
errors raised by entity-level validation failures. This "bundled exception
mode" allows your web application to present all errors to the user in a single
round-trip to the browser. The built-in ADF/Struts integration leverages this
bundled exception mode.
| NOTE: |
While out of scope for this
whitepaper, in addition to the tight integration that ADF provides for working
with Struts, it also offers:
- Support for
building rich, databound Swing-based GUI's, deployable in both two-tier and
three-tier styles, using the
ADF
JClient [30] facilities.
- Automatic exposure
of application modules as web services, including built-in view object XML
marshalling features for producing and consuming complex business
documents
|
Recall from above the
entity objects representing the domain business objects are not directly
accessible to clients. The controller layer can modify entity objects only
indirectly in one of these three ways. Two of the three
ways involve invoking a business service method. That method's private
implementation code can:
- directly create,
lookup and modify, or lookup and delete entity instances, or
- update view object row DTO's in a view object's
rowset.
Alternatively, the controller
layer can directly update any data transfer object "rows" itself, in any
collection in the application module's model data map.
In all
three cases, any changes effected successfully to entity object instances are
held in the entity object cache until the transaction is committed. The
application module contains a reference to a transaction object that
coordinates the entity object caches during commit or rollback. This
coordination insures that any new, modified, or removed entity instances are
validated if required, and then persisted appropriately. In effect, the
application module component represents the "unit of work" and any entities
modified by interaction with the application module business service are
automatically "enrolled" in the unit of work, without requiring
developer-written code. Of course, when you choose to deploy your application
module component as an EJB Session Bean, the ADF transaction object
automatically becomes a "slave" to the container-managed transaction so that it
participates correctly in transactions that can span multiple session
beans.
Figure 30 summarizes in a single
picture how the ADF framework components interact. All of the components can be
accessed freely by Business Service implementation code inside your application
module component. The component interfaces on the left side of the
double-dotted line can be used by the view and controller layers as
well.
 Figure 30: How ADF Business Components Relate
And Which Are Client-Accessible
| NOTE: |
The ADF
transaction object supports the use of J2EE datasources as well as simple JDBC
connection URL's for interaction with the database.
|
Creating Smarter Custom Data Types
Using Domains
The Java language comes with a number of basic
datatypes like String, Long,
Float, etc. The ADF framework adds a few additional types
called "domains" that live in the oracle.jbo.domain.*
package. These additional types include:
Number
Date
DBSequence
ClobDomain
BlobDomain
Array
These are
designed to improve performance and ease-of-use when working with corresponding
types in the Oracle database.
In addition, there are times when
you need to create your own custom data types to capture some frequently used
concept like an email address or a credit card number that require custom
value-level validation whenever they are constructed. The ADF framework
provides the simple feature of domains to support this use case.
Figure 31 shows the three custom datatypes we've
created for the ADF Toy Store demo. The
toystore.model.datatypes.CreditCardNumber is a String-based
datatype with construction-time validation that the credit card number has 16
digits. The toystore.model.datatypes.Email domain is another
String-based datatype that checks to make sure its value has
the format of a valid email address.
 Figure 31: Custom DataTypes in the ToyStoreModel Project
The
toystore.model.datatypes.ExpirationDate type models
string-based data in the database stored in the format
MM/YY, and internally treats it as a date for comparing the
expiration date with the current date for detecting if a credit card expiration
date is in the future or the past.
Once you've created custom
domain types, your entity objects and view objects can use these new types in
the same way that they can use any of the built-in domains or the base Java
types.
Testing
Business Tier Components with JUnit
Testing is an important
consideration when implementing your J2EE model layer. J2EE experts like Rod
Johnson recommend building the business services that support your model layer
in a way that allows them to be easily tested outside of
the J2EE runtime container environment where they will ultimately be deployed.
Being able to run regression tests on business services from the command line
simplifies the testing process and allows it to be more easily automated.
Simplifying and automating regression tests leads to higher quality software
since it encourages engineers to develop more tests and run them more often
after making changes to the system.
The open source
JUnit [31] testing framework is the
defacto standard that most Java development teams use to write and run their
regression tests. Oracle JDeveloper 10g features native support for working
with the JUnit testing framework (available as a small, separate download
explained in the Demo Installation and
Setup section above). One of the wizards
available in the Unit Tests (JUnit) category of the
New... Gallery allows you to create a skeleton
Business Components Test Suite. This wizard allows you to
pick an application module component, and a particular configuration that you'd
like to use for testing, and then it generates you:
- A JUnit test fixture that encapsulates getting an instance of
the desired application module
- An example
JUnit test case class which uses this fixture and asserts that all expected
view object instances exist in the application module's model data
map.
- A JUnit Test class that runs all of the
test cases (initially just one).
By using
the above wizard and the built-in JDeveloper refactoring tools, I renamed the
packages containing the test runner, the unit test cases, and the test fixture
into separate packages, and then created several additional tests to exercise
the ToyStoreService application module component.
Running the RunAllTests.java class in the
Testing.jpr project launches the Swing-based JUnit test
runner as shown in Figure 32. This result is what you'll
see if all of the seventeen regression tests pass. If any failures occur, the
progress bar turns red and error messages in the log window alert you to the
failing test.
| NOTE: |
When trying to debug a test failure, just
debug the RunAllTests.java class instead of just running it
to use the JDeveloper debugging features to find the problem.
|
 Figure 32: Successful Run of ADF Toy Store JUnit Test
Suite
By
developing this suite of business service regression tests earlier during the
development cycle, I was able to rerun my JUnit test suite after any major new
feature additions I made as well as after the many project refactorings that I
performed as I continually improved the demonstration. Any problems that my
changes might have produced inadvertently were instantly uncovered by the
regression tests so that I could easily fix them before moving on to the next
task.
| NOTE: |
Running the tests within the IDE requires installing
the optional JUnit extension for JDeveloper, available from OTN as a separate
download. The installation instructions are in the Demo Installation and
Setup
section of this document.
|
Implementing the Controller
Layer with the Apache Struts Framework
In the MVC architecture,
all of the logic that processes user input, handles interaction with business
services, and decides what page the user should see lives in the controller
layer. The best practice approach for your web application's controller layer
is to implement the
Front Controller [32] design
pattern. Based on the type of incoming request, the front controller delegates
the handling of the request to more task-specific controller logic. The front
controller is typically implemented as a servlet and configured in
web.xml to handle all requests for URL's related to your
application. While you could implement this controller
servlet by hand, the popular Apache Struts [11] framework
provides a ready-made implementation that handles the job well.
Handling Model Interaction and Page
Flow With Struts Actions
Using Struts, you describe the "page
flow" of your web application by:
-
assigning logical names to the actions your application needs to perform,
and
- indicating the possible "routes" the user
can follow between the actions.
Simple Page Flow Example
For example, in a simple web store application, the user might inspect
the contents of her shopping cart by using an action named
/yourcart. Using Struts, actions are executed when the
end-user browses a URL matching the *.do suffix, so the URL
for the /yourcart action might be:
http://yourcompany.com/ADFToyStore/yourcart.do
While looking at her shopping cart, the user might
begin the checkout procedure by clicking on a link to the
/reviewcheckout action, letting her confirm what she is
about to purchase.
A visual representation of these actions and
the links that connect them might look like Figure 33. The
/yourcart and /reviewcheckout actions
show as gear icons, while the JSP pages that represent the view layer are shown
as page icons. The links between the actions, known as "forwards" in Struts
parlance, are shown here as solid lines pointing from the source action to the
target page or action.
 Figure 33: Struts Actions
and Forwards
The dotted lines represent web page hyperlinks or HTML forms which
target the action that their corresponding arrows are pointing to. While the
above is a trivial example, you can imagine that a visual diagram of your
Struts actions and pages for a more complex application would form an
easy-to-understand "page flow" showing where the user can go and how they get
can get from one page to another.
As you might imagine, the
controller-layer behavior associated with each action is written in an
associated Java class. These action classes orchestrate what happens in the web
tier in response to each request. Figure 34 illustrates the typical
steps that occur each time the user clicks on a link representing a Struts
action.
- The controller receives a page
request and routes it to the appropriate action class
- The action acquires an instance of a business service, via the
business delegate, and invokes a method on it
- The business delegate implementation delegates the method call to the
business service
- The business service uses
one or more query components to retrieve business data from SQL queries, and
exposes the results as one or more collections of data transfer objects in its
"model data map"
- The business delegate
exposes the "model data map" to the view layer
- The controller selects an appropriate view and forwards the request to
the view layer for rendering
- The view layer
finds the collections of data transfer objects from the "model data map" and
iterates over them to render the data to the
end-user.
 Figure 34: Typical MVC
Processing Steps with Struts and JSP
The Postback-Pattern
Notice
back in Figure 33 how the /yourcart
action and the yourcart.jsp page are in a kind of symbiotic
relationship. The /yourcart action prepares the data to be
displayed by yourcart.jsp, and the
yourcart.jsp page targets any links and submitted HTML forms
back to the /yourcart action so it can handle the
controller-layer details and decide what to do next. When an action and a page
are mutually dependent like this, it's often said that they implement the
"Postback Pattern"; the action prepares the data for the page to render, and
the page posts-back to the action to handle its events.
Figure 35 shows the two different ways that JDeveloper
10g's Struts Page Flow Diagrammer supports implementing the "Postback Pattern"
using Struts and ADF. We've already seen the first approach above. You see a
discrete icon for both the action and the page, as well as connecting lines in
both directions between the two. The alternative is to use the ADF "DataPage"
which represents the combination of the action and the page involved in
implementing the "Postback Pattern" with a single icon.
 Figure 35: Pages and Actions Implementing a "Postback
Pattern"
As
you might expect, using the "DataPage" approach makes for much easier to read
page flow diagrams for applications of any non-trivial size. As we'll see
shortly, the ADF Toy Store controller layer is implemented using this DataPage
approach.
| NOTE: |
The DataPage is a design time abstraction that
simplifies implementing pages that adhere to the Postback Pattern in a
Struts-based MVC architecture. At runtime, a DataPage is just a standard Struts
action and a page to which it forwards control by default, identical to a
blending of the Struts DispatchAction [33]and
ForwardAction [34]
functionalities.
|
The
Struts Configuration File
Along with other configuration
information, Struts keeps the mapping of action names to action handler classes
in the struts-config.xml file. As shown in the
ToyStoreViewController project in
Figure 36, this configuration file lives in the
standard WEB-INF directory of your application, among the
other elements that comprise your Web Content.
 Figure 36: Locating the Struts Configuration
File
When you double-click on the struts-config.xml file,
by default, JDeveloper 10g shows you a visual page flow diagram. Let's take a
look at what one of these diagrams looks like by studying the page flow of the
ADF Toy Store demo.
The
ADF Toy Store Struts Page Flow
Figure 37 shows
the page flow diagram for the ADF Toy Store Demo's controller layer. Each
DataPage is implemented by the combination of a JSP page and an Struts action
that prepares that page's model data to render and handles its post-back
events. Since these page/action pairs render as a single icon, the page flow
diagram is tidy and easy to understand.
 Figure 37: ADF Toy Store demo Struts Action Flow
By clicking on the
Source tab of the editor, you can see the XML source code
for the Struts action mappings for the ADF Toy Store demo.
Searching for /yourcart, you will find the XML tags shown in
Example 6. Under the Search
main menu, you can use either the Find... or
Incremental Find Forward
(Ctrl+E) options to
locate it.
Example 6: Action Mapping
Definition in struts-config.xml Configuration File
<struts-config>
:
<action-mappings>
:
<!-- Show your shopping cart -->
<action path="/yourcart"
className="oracle.adf.controller.struts.actions.DataActionMapping"
type="toystore.controller.strutsactions.YourCartAction"
name="DataForm"
parameter="/WEB-INF/jsp/yourcart.jsp"
unknown="false">
<set-property property="modelReference" value="WEB_INF_jsp_yourcartUIModel"/>
<forward name="reviewcheckout" path="/reviewcheckout.do"/>
</action>
:
</action-mappings>
:
</struts-config> |
Among other pieces of configuration
information, the struts-config.xml file contains one
<action> tag for each action in your application. The
<forward> element nested inside Struts action tags provides a
logical name like "reviewcheckout" for the available page
navigation "routes" the user can take from the "/yourcart"
page. In this case, there is only a single forward route that the user can
follow to navigate to review the items for checkout.
The value of
the parameter attribute tells us that our
/yourcart action is related to the
yourcart.jsp page that lives in the
/WEB-INF/jsp directory. We can also see that it is mapped to
the YourCartAction class in the
toystore.controller.strutsaction package. This action
contains the controller logic to handle the "UpdateCart" and
"RemoveItem" events, generated by the user's clicking on
appropriate buttons in the page, by invoking the
adjustQuantitiesInCartAsStringArrays() on the
ToyStoreService to adjust shopping cart quantities. It also
contains a line of code to retrieve the shopping cart total from the business
service for display in the page.
The
YourCartAction class extends the
ToyStoreDataForwardAction class in the
FwkExtensions project. This framework extension class, in
turn, inherits a goodly amount of built-in behavior from the ADF
DataForwardAction, adding a few handy helper methods in the
process that are useful to many of the actions in the Toy Store Demo. The value
of the modelReference property that we see points to some
declarative metadata that ADF uses to simplify data binding.
| NOTE: |
We explore the features of the DataForwardAction, the
effect that these custom action properties have, and explain all the various
framework extensions classes in use in the demo in a lot more detail later in
the Understanding ADF/Struts Integration section below.
|
Table 2 gives a description of what each of the
actions does.
Table 2: Key Struts Action
Mappings in the ADF Toy Store demo
| Action Path Name |
Data Page |
/home |
Toy
Store home page
|
/showcategory |
Category page, showing products in a category |
/showproduct |
Product page, showing product items
available
|
/showproductdetails |
Product Details page |
/yourcart |
Shopping Cart page |
/search |
Store-wide Search Results page |
/signin |
Signin page |
/register |
Register New User page |
/editaccount |
Edit Existing Account page |
/reviewcheckout |
Review Checkout page |
/confirmshippinginfo |
Confirm Shipping Information page |
/thankyou |
Thank You page,showing order tracking number |
/revieworder |
Review Order page |
/revieworderxml |
Retrieve "review order" information as
XML
|
Additional Struts Design Time
Support
Whenever the struts-config.xml file
is active, the JDeveloper Structure Window and Property Inspector provide
additional design time support. The Structure Window shows a tree reflecting
the internal structure of the configuration file. You can click on elements in
the tree to see or edit their properties in the Property Inspector. In
Figure 38 you can see all of the ADF Toy
Store Demo's Struts action mappings under the Action
Mappings folder in the tree and the properties of the selected
"/yourcart" action in the Property Inspector.
 Figure 38: Structure Window and Property
Inspector Support for Struts
By
selecting an appropriate container node, like Form Beans
or Action Mappings for example, you can use the
New option in the right-mouse menu to create a new
entry of that kind. You can then use the Property Inspector to configure its
various properties as you would expect. Alternatively, you can click
Edit in the right-mouse menu to edit any of the
configuration file items through the Struts Configuration Editor dialog shown
in Figure 39. Other right-mouse options certain nodes
allow quick navigation to related files. For example, to quickly jump to the
code of any of your Struts actions, you can select an action and pick
Go to Code on its right-mouse menu.
 Figure 39: Oracle JDeveloper 10g Struts Configuration
File Editor
As we've seen, for working with Struts configuration information you can
achieve everything you need to do using a combination of the Struts Page Flow
Diagram, the Structure Window, and the Property Inspector. Keep in mind that if
you do need (or prefer!) to edit the XML source of the file directory,
JDeveloper 10g is happy to oblige and will keep the page flow diagram in sync
with your manual edits automatically. The choice is yours.
Using a JavaBean Data Control to
Process HTML Form Input
To complement its features for managing
controller-layer action classes, Struts also helps developers process HTML form
input in their actions. The Struts controller servlet populates the attributes
of a so-called Form Bean with the parameters from the
posted HTML form. Then, the developer writing the action class can work with
the HTML form values using the form bean's getter and setter methods.
However, since the flexible ADF
BindingContainerActionForm that we've seen above works with
any kind of ADF Data Control, we can use it to capture transient HTML form
values as well. This approach gives us a single way to do all kinds of data
binding instead of having to mix and match two different approaches.
We're going to leverage this approach for our sign-in page to capture the
username and password properties from the
HTML form. All we need to do is create a simple JavaBean class with
username and password properties like the
toystore.model.beans.LoginBean class that you'll find the
ADF Toy Store Demo's ToyStoreModel project:
package toystore.model.beans;
public class LoginBean {
String _username;
String _password;
public LoginBean() {}
public void setUsername(String username) { _username = username; }
public String getUsername() { return _username; }
public void setPassword(String password) { _password = password; }
public String getPassword() { return _password; }
}
Then, we can turn this JavaBean into an ADF Data
Control by selecting it in the Application Navigator and
choosing the Create Data Control... option on the
right-mouse menu. This operation adds an additional
LoginBean.xml metadata file alongside our Java code to
capture some data binding properties required by the ADF binding layer. At this
point, the LoginBeanDataControl appears in the standard ADF
Data Control Palette and is available for data binding
like the ToyStoreService Data Control.
We've
used this approach to build the databound login form in the
signin.jsp page in the
ToyStoreViewController project. Just as with the pages
showing bound-data from the ToyStoreService, our
signin.jsp page will show and post user-entered data related
to the simple LoginBeanDataControl without having to write
any code to perform this data binding coordination.
At runtime the
struts controller will populate the single, generic ADF
BindingContainerActionForm form bean with the values of the
username and password HTML form fields
from the signin page when the user clicks to submit the form. The ADF binding
layer will handle "pushing" those submitted values back into the appropriate
binding objects as part of the default request-handling lifecycle. This means
we can access the submitted username and
password values from appropriate ADF binding objects in the
onVerifySignin() event-handler method of the
SignInAction class. It will be this event-handler method
that will handle the sign-in form's postback when the user submits the
form.
Example 7 shows the
onVerifySignin() method in the form bean. Using the
getBindingValue() helper method that it inherits from the
ToyStoreDataForwardAction, it verifies that neither the
username nor password properties are
blank, and then calls the:
public boolean validSignon(String username, String password)
method on the ToyStoreService
business service interface to verify whether the username/password combination
represents a valid webstore user. If any validation check fails, the method
adds a Struts ActionError object to the
ActionErrors collection so the view layer can present
appropriate error messages to the user. We'll dive into more details about how
the error string keys, like those represented by the
INVALIDLOGIN constant, are translated into user-readable
messages in the appropriate language in the Struts and ADF Features for Building
Multilingual Applications
section below.
Example 7: Validating
Application Username / Password in Event Handler Method
// Method in the SignInAction Struts Action class
public void onVerifySignin(DataActionContext ctx) {
HttpServletRequest request = ctx.getHttpServletRequest();
String target = request.getParameter("target");
if (isNullOrEmpty(target)) {
target = "welcome";
}
final String username = (String)getBindingValue("username",ctx);
final String password = (String)getBindingValue("password",ctx);
if (isNullOrEmpty(username)) {
ctx.getActionErrors().add("username", new ActionError(BLANKUSERNAME));
}
else if (isNullOrEmpty(password)) {
ctx.getActionErrors().add("password", new ActionError(BLANKPASSWORD));
}
else {
ToyStoreService ts = getToyStoreService(ctx);
if (ts.validSignon(username, password)) {
AppUserInfo.signIn(request, username);
ctx.setActionForward(target);
return;
}
else {
ctx.getActionErrors().add(ActionErrors.GLOBAL_ERROR,
new ActionError(INVALIDLOGIN));
}
}
saveErrors(ctx);
} |
If the login form submission validation above
fails, then the user is returned to the signin page to try
again. If it succeeds, then the code above calls a helper
method to flag the current user as signed in, and then returns the appropriate
page to forward the request to. Since several different actions in the demo can
cause the user to login, this action uses the target
parameter to return the correct "next" page in the flow, based on which action
caused the user to need to log in.
Another thing to notice in the
SignInAction class is its overridden method
initializeBindingsForPage(). This is a method that the
customized ToyStoreDataForwardAction adds to the base
request-handling lifecycle to make it more clear where code lives that
initialize the values of binding objects for the case when a page is not
handling any post-back events. The method leverages the
setBindingValue() helper method we've added to insure that
the username and password bindings that will show in the
signin.jsp form have null values when the
login page comes up initially.
/*
* Binding initialization logic for this page.
*
* Blank out the username and password values if we're coming
* into the signin page.
*/
protected void initializeBindingsForPage(DataActionContext ctx) {
setBindingValue("username",null,ctx);
setBindingValue("password",null,ctx);
}
Understanding ADF/Struts Integration
As we've seen above,
Struts is a handy framework for implementing the controller layer of your web
application. As we'll see in more detail in the Building the View Layer with
JSP Pages and JSTL
section, Struts also provides some JSP tag libraries to simplify building your
view layer. However, it effectively provides virtually no built-in
functionality to assist with implementing the model layer, arguably the most
important and time-consuming part of your application development. The model
layer is where all of your business logic and data access functionality
resides. To complete the strong MVC application infrastructure that J2EE
developers require, you need a productive approach to building the model layer
that integrates nicely with Struts. This section explains how the Oracle ADF
framework is the perfect companion to Struts in this venture.
In
addition to the business component building-blocks that ADF provides to
accelerate model layer development, it also provides specific additional
functionality to dovetail with Struts' controller and view layer features. For
example, ADF provides:
- Comprehensive
declarative data binding support for "wiring" your view layer pages and your
model layer data together in a bidirectional way without writing
code.
- High-performance Business Delegate
implementation that finds and pools business service
components
- Declarative, database-backed state
management to easily support complex transactions spanning multiple
pages
Of course, JDeveloper 10g provides
a productive design time environment for putting all of these feature to work
in your own J2EE applications.
Brief Overview of ADF Binding and Controller Concepts
| NOTE: |
My companion
ADF
Data Binding Primer and ADF/Struts Overview [1] whitepaper explains all of
Oracle ADF's data-binding and Struts-related features by explaining the
implementation of three simple ADF/Struts applications. Here we provide
highlights of that information but focus primarily on how we've used those
features to implement the ADF Toy Store Demo.
|
Oracle ADF
includes a "data control" abstraction layer for back-end business services and
a binding layer for declaratively connecting front-end user interface controls
to data supplied by any data control. These ingredients, based on
JSR
227 [35], provide you a consistent and pluggable model layer for your J2EE
applications that is on its way to becoming a J2EE standard.
Figure 40 illustrates where the ADF Data Control and
ADF Bindings fit into the overall ADF Model, View, Controller, and Business
Services architecture.
 Figure 40: Oracle ADF
Architecture for J2EE ApplicationsKey Concepts In the ADF Binding
Layer
The key data binding concepts in Oracle ADF are the
following:
-
Data
Controls
A data control abstracts the implementation of
a business service, allowing the binding layer to access the data from all
services in a consistent way.
-
Iterator Bindings and Control Bindings
Bindings are lightweight objects that decouple back-end data and
front-end UI display. An iterator binding provides a consistent way to work
with a collection of data objects supplied by a data control. Control bindings
provide a standard interface for UI components to interact with an iterator's
data or to invoke "action" methods for preparing model data and handling
events. Bindings also expose key metadata to simplify building dynamic,
multi-lingual user interfaces.
-
Binding Containers
A binding container
is a named group of related iterator and control bindings that you use together
for a particular page (or panel) of your application. A binding container is
also known as a "UI Model" since it provides the appropriate subset of model
data for a specific UI.
-
Binding
Context
The binding context provides the data
environment for your application. It contains all of the data controls and
binding containers that your application can
access.
JDeveloper 10g supports these
data binding concepts at design time in a rich way. Once you've created a
business service, you can expose it as a Data Control and it will appear in the
Data Control palette. Figure 41 shows what our
ToyStoreService data control, based on our
ToyStoreServiceImpl application module component, looks like
in the Data Control Palette. We can see all of the data collections in its
model data map, as well as the custom methods on the ToyStoreService service
interface under the Operations folder.
 Figure 41: ToyStoreService Data Control in
the Data Control Palette
| NOTE: |
Services built as ADF Application Modules are automatically exposed as
data controls by JDeveloper 10g. By default, the data control based on an
application module named
YourModule will be named
YourModuleDataControl. To change
the name to something else, add a custom application module property named
DATA_CONTROL_NAME whose value is the data control name you
want to use instead. The ToyStoreService in the demo sets
this property to the value "ToyStoreService", to avoid the
longer ToyStoreServiceDataControl name.
For
other kinds of business service implementations, there is an additional
Create Data Control... step to perform, and their name can
be changed by editing the Id property of the data control in
the Property Inspector.
|
As you are creating pages with
databound content -- through drag and drop from the Data Control Palette or by
explicit creation -- JDeveloper maintains metadata about your application's
Binding Context. This information describes the data controls your application
is using, the different binding containers that you've created, and the binding
objects they contain. The DataBindings.cpx file is the place
JDeveloper stores this Binding Context metadata. After clicking on this file in
the Navigator, as shown in Figure 42, the Structure
Window displays the Data Controls in use by your application, as well as the
various binding containers (also known as "UI Models") that support the
different pages in your application. Clicking on something in the Structure
Window like the ToyStoreService, the Property Inspector
shows you its properties.
 Figure 42: Binding
Context Info in Navigator, Structure Window, and Property Inspector
While looking
at a given JSP page, you can click on UI Model tab of the Structure Window as
shown in Figure 43 to see the details of the binding objects
it contains. By clicking on a particular binding object, you can see and edit
its properties in the Property Inspector, as well as through the more
structured object editor by selecting Edit... on the
right-mouse menu.
 Figure 43: UI Model Tab Showing
Binding Container for the Search PageADF Controller-Layer Functionality for
Struts
To simplify using the ADF binding layer in Apache Struts
applications, Oracle ADF also provides Struts-specific controller-layer
components to seamlessly integrate Struts with the ADF Binding layer. These
components include:
- A servlet filter
named
ADFBindingFilter that coordinates the use of the
business delegate, business service pooling, and
state-management.
- A multi-purpose Struts
DynaActionForm called
BindingContainerActionForm that
eliminates the need to create Struts form beans for each page you
build.
- A powerful Struts Action class named
DataAction that implements an easy-to-customize
request-handling lifecycle with automatic support for the ADF data binding
functionality, and
- A standard Struts
ActionMapping class called
DataActionMapping that captures
addition declarative metadata in standard action mapping extension properties
used to automate the data binding.
Rounding off the JDeveloper 10g IDE support for using ADF and Struts
together is the Visual Struts Page Flow diagrammer and the concept of the ADF
"DataPage". The DataPage simplifies working with a page and a Struts action
that want to cooperate to implement the "Postback Pattern." JDeveloper 10g
represents the combination of a web page and a
DataForwardAction that prepares its data and handled its
events into a single node in the Struts diagram. The
DataForwardAction combines the ADF
DataAction's lifecycle features and DispatchAction [33]-like
event-handling with the implicit page forwarding behavior and event-handling of
the basic Struts ForwardAction [34].
Overview of Declarative
DataForwardAction Features
The
DataForwardAction inherits event-handling functionality
(from DataAction) that is similar to that offered by the
Struts DispatchAction [33].
Your HTML forms or hyperlinks can include a special parameter named
event in the request, whose value represents the name of an
event to be "fired" and handled by the DataAction being targeted. So, sending a
parameter event=YourName as a request parameter for form
field will "fire" the YourName event.
For HTML
buttons, whose value attribute serves as
both the user-visible button label and the value of the
control, we support the alternative event-signalling approach of naming the
button event_YourName so that the button can have any
value, and hence any user-visible label, that is
appropriate to the current user's language.
The ADF DataAction
supports three key features around the handling of named events.
When an event named YourEvent fires, then...
- If you have a
public void
onYourEvent(DataActionContext ctx) method
in the data action class handling the request, it will be invoked to handle the
event with custom code.
-
If you have an action
binding in the current binding container named YourEvent, it
will be invoked.
When used in combination with 1, your
event-handler code needs to explicitly invoke the default
action for the current event by using code like:
if (ctx.getEventActionBinding() != null) {
PageLifecycle p = (PageLifecycle)getPageLifecycle(ctx);
p.invokeActionBinding(ctx,ctx.getEventActionBinding().getName());
}
-
If you have a Struts forward named
YourEvent, it will be used to determine the next page to
forward control to.
When used in combination with 1, if your
event-handling code invokes ctx.setActionForward(), then
your programmatically set forward takes
precedence.
With these basics fresh in our
minds, we can now dive deeper into understanding the ADF/Struts integration and
how it's put to work in the ADF Toy Store Demo.
Lifecycle of a Web Page Request Using
Struts and ADF
Figure 44 shows
all of the "moving parts" together in a single sequence diagram to show the
lifecycle of a web page request using the Struts and ADF frameworks in
tandem.
 Figure 44: Lifecycle of a
Web Page Request Using Struts and ADF
- A web request for
http://yourserver/yourapp/some.do arrives
-
The ADFBindingFilter finds the ADF Binding
Context in the HTTP Session, and if not yet present, initializes it for the
first time.
During Binding Context initialization, the
ADFBindingFilter:
-
Consults the servlet context initialization parameter named
CpxFileName and appends the *.cpx file
extension to its value to determine the name of the
Binding Context metadata file. By default the parameter value will be
"DataBindings", so it will look for a file named
DataBindings.cpx.
- Reads
the Binding Context metadata file to discover the Data Control definitions and
BindingContainer names.
- Constructs an
instance of each Data Control, and a reference to each BindingContainer. The
contents of each binding container are loaded lazily on
their first use by some page.
- The
ADFBindingFilter invokes the
beginRequest() method on each Data Control in the binding
context. This gives every data control a notification at the start of every
request where they can perform any necessary setup.
- A Data Control based on an ADF ApplicationModule uses this
beginRequest notification to acquire an instance of the
ApplicationModule from the ApplicationModule pool.
-
The Struts RequestProcessor forwards control to the
appropriate action class for the "/some" action mapping as
configured in struts-config.xml.
| NOTE: |
The
Struts ActionServlet is what initially forwards control to
the RequestProcessor but since
RequestProessor does all the work, we don't bother showing
the ActionServlet in the diagram. |
-
If the Struts action class is or extends the ADF
DataAction class, then it goes through a set of processing
steps (known collectively as the request-processing "lifecycle") that
include:
-
Finding the
BindingContainer...
...or initializing it if it's the first time it
has been used. During initialization, the binding objects are created based on
the binding container corresponding *UIModel.xml metadata
file.
- Executing the iterator bindings if they
haven't been yet, in the
prepareModel()
phase
- Updating posted data (if present) into
corresponding binding objects, during the
processUpdateModel() phase
- Executing custom controller layer logic in the
invokeCustomMethod() phase, and
- Deciding on what page to display next during the
findForward() phase.
Each phase of the lifecycle can be overridden by the developer and each method
is passed the DataActionContext object that gives you access
to the BindingContext, the BindingContainer, and all Struts-related elements
like the servlet request and response as well as the action mapping and action
form. The action returns an appropriate Struts ActionForward
indicating which action or page we want to navigate to next.
| NOTE: |
If the Struts action class does not extend DataAction, then it must find
(and if necessary, initialize) the binding container manually and manage
working with the bindings itself. The
ADF
Data Binding Primer [1] whitepaper has example code illustrating how to do
this. |
- The Struts
RequestProcessor forwards control to the action or page
returned by the action as the "next" one to forward control
to.
- Assuming it's a JSP page, the page
accesses and iterates over collection of data transfer objects from the control
bindings, to write out the formatted web page destined to appear in the
browser.
- The Struts
RequestProcessor returns control to the
ActionServlet which returns control back to the servlet
container.
- The
ADFBindingFilter invokes the endRequest()
method on each Data Control in the binding context. This gives every data
control a notification at the end of every request where they can perform any
necessary resource cleanup.
- A Data Control
based on an ADF ApplicationModule uses this
endRequest
notification to release the instance of the ApplicationModule back to the
ApplicationModule pool.
- The user sees the
resulting page in the browser.
The
sequence diagram illustrates that the binding container is available both for
model data manipulation by the controller layer as well as model data iteration
and rendering by the view layer.
Configuring Your J2EE Web
Application to Use Struts and ADF
When you use JDeveloper to
build your ADF-based Struts application, your web application is automatically
configured for you by the design time tools. However, it's useful to understand
what the tool is setting up for you so that you understand how the setup pieces
fit together. Being a J2EE web application, it's understandable that several of
the configuration steps involve the standard web.xml file.
We'll highlight the interesting aspects of the setup steps in this
section.
The Struts
Servlet and ADF Binding Filter
First, in order to use Struts you
need to have the Struts front-controller servlet named
ActionServlet configured and mapped to a URL pattern. In the
ADF Toy Store we're using the conventional *.do URL pattern
to map requests to the ActionServlet. The relevant portions
of the web.xml file, located under the
WEB-INF directory in the Web Content
folder of the ToyStoreViewController project, look like
this:
<web-app>
:
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
:
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
:
</web-app>
The second step is setting up the
ADFBindingFilter and configuring it to be engaged when
*.jsp or Struts ActionServlet-related
URL's are processed. The relevant portions of the web.xml
file look like this:
<web-app>
:
<filter>
<filter-name>ADFBindingFilter</filter-name>
<filter-class>oracle.adf.model.servlet.ADFBindingFilter</filter-class>
<!-- Optional initialization parameter to control default encoding
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
-->
</filter>
<filter-mapping>
<filter-name>ADFBindingFilter</filter-name>
<servlet-name>action</servlet-name>
</filter-mapping>
<filter-mapping>
<filter-name>ADFBindingFilter</filter-name>
<url-pattern>/*.jsp</url-pattern>
</filter-mapping>
:
</web-app>
The ADFBindingFilter needs to know
where it should look for the ADF metadata file that describes the binding
context for your application. It reads a servlet context initialization
parameter named CpxFileName to determine the file name to
look for. We see in the ADF Toy Store's web.xml file, that
this parameter is using the default value of DataBindings.
This means that the ADFBindingFilter will use the
DataBindings.cpx file to read this information.
<!-- web.xml file -->
<web-app>
<description>web.xml file for the ADF Toy Store demo application</description>
<context-param>
<param-name>CpxFileName</param-name>
<param-value>DataBindings</param-value>
</context-param>
:
</web-app>
The Data
Binding Context Metadata File
You can see the
DataBindings.cpx file under the Application
Sources folder in the ToyStoreViewController
project. It is an XML file that describes all of the ADF data controls and
binding containers used by the ADF Toy Store application. Peeking inside it
reveals that the ADF Toy Store demo has just two data controls:
ToyStoreService and LoginBeanDataControl.
You can also see the same information in the Structure Window tree display when
the DataBindings.cpx file is active in the navigator or code
editor.
<!-- DataBindings.cpx File -->
<JboProject id="DataBindings" ... >
<Contents>
<DataControl
id="ToyStoreService"
SubType="DCBC4J"
SupportsFindMode="true"
SupportsTransactions="true"
Package="toystore.model.services"
FactoryClass="oracle.adf.model.bc4j.DataControlFactoryImpl"
Configuration="ToyStoreServiceLocal" >
<Parameters >
<Parameter
name="Sync"
value="Immediate" >
</Parameter>
</Parameters>
</DataControl>
<DataControl
id="LoginBeanDataControl"
SubType="DCJavaBean"
SupportsFindMode="false"
SupportsTransactions="false"
FactoryClass="oracle.adf.model.generic.DataControlFactoryImpl"
Definition="toystore.model.beans.LoginBean"
BeanClass="toystore.model.beans.LoginBean" >
</DataControl>
:
</Contents>
</JboProject>
Notice that the ADF Business Components-based data
control has properties named Package and
Configuration. The values of these attributes are used by
the ADF implementation of the Business Delegate pattern to lookup an
appropriate instance of the business service whose configuration properties are
defined by the configuration named ToyStoreServiceLocal.
Given the package name, the ADF runtime first looks for a
bc4j.xcfg file in the common subdirectory under the
directory corresponding to the package name. In our example above, it will open
and read the resource named
./toystore/model/services/common/bc4j.xcfg from the runtime
classpath since the package name provided is
toystore.model.services. Inside this file, the
ToyStoreServiceLocal configuration contains a property
indicating the fully-qualified name of the application module that will be used
as our business service implementation.
| NOTE: |
Configurations are a
named set of configuration properties that ADF uses to simplify application
deployment. To see or edit the configurations for any application module, after
selecting the component in the Application Navigator, select the
Configurations... option on the right-mouse
menu.
|
This best-practice abstraction of the business service
lookup lets us easily change model layer deployment strategy without affecting
the rest of our application. As long as you're using ADF Business Components to
build your model layer, then to change an ADF/Struts application from using a
simple JavaBean-based business service to using an application module deployed
as an EJB Session Bean instead, the only change required is changing the value
of the Configuration attribute above in the data control
metadata. This alternative configuration would contain the lookup details for
the EJB session bean, cleanly factored out into XML configuration information
instead of being lost somewhere in your code. Of course, the ADF design time
sets up these multiple configurations for you automatically and gives you
productive dialogs to edit the configuration parameters at design time.
Overview of the
ADF DataAction and BindingContainerActionForm
When you build HTML
forms to allow the user to enter or modify application data, as we discussed
above, the Struts infrastructure abstracts the form data into an instance of a
FormBean for easier processing by your actions. Three very frequent tasks that
actions need to perform in service of HTML forms are:
- Populating a FormBean with appropriate default values to
display a data entry form
- Populating a FormBean with existing row data to
display an data editing form
- Updating the model layer from a FormBean containing user-submitted
changes
Oracle ADF provides two key
ingredients that collaborate to make implementing these scenarios easy:
- A multi-purpose Struts DynaActionForm called
BindingContainerActionForm that eliminates the need to
create separate Struts form beans for each page you build.
- A powerful Struts Action class named
DataAction that implements an easy-to-customize
request-handling lifecycle with automatic support for the ADF data binding
functionality
If you use the
/register DataPage to register a new user in the ADF Toy
Store site as shown back in Figure 9, you'll notice that the
country is defaulted to "USA". This defaulting happens automatically because
the Accounts view object being used by this data entry form
is related to the underlying Account entity object, and that
Account entity object has a default value of
"US" defined for its Country attribute.
When the user clicks the (Save Changes)
button on the "Register as a New User" form, the form is submitted back to the
/register.do action. The Struts framework populates the
contents of the form parameters into the FormBean configured for this action
mapping, "DataForm". As we can see in the form-beans section
of struts-config.xml, the "DataForm" form bean is using the
ADF BindingContainerActionForm class.
<form-beans>
<form-bean name="loginform"
type="toystore.controller.strutsformbeans.LoginForm"/>
<form-bean name="DataForm"
type="oracle.adf.controller.struts.forms.BindingContainerActionForm"/>
</form-beans>
This clever form bean is a Struts DynaActionForm that
takes on the properties corresponding to names of the bindings in the current
BindingContainer. The /register action is configured to use
the RegisterAction class in the
ToyStoreViewController project. This action class extends
the default ADF DataForwardAction that applies the data in
the form bean to the appropriate bindings during the "update model" phase of
the its request-processing lifecycle. The bindings turn around and carry out
the out the work of populating the target row in the default rowset of the
Accounts view object. Figure 45
illustrates this, and helps explain what happens next.
 Figure 45: How New Account Information Gets To the
Database
| NOTE: |
The
Account and Signon entities are modeled
separately, and related by a 1-to-1 association, due to the desire to reuse the
existing schema of the "classic" Java Pet Store demo which had these two
entities modeled separately.
|
Specifically the steps involved
in posting the changes from the browser to the database go like this:
- The Struts
RequestProcessor
populates the form bean properties with the values from the submitted form
parameters, and forwards control to the
RegisterAction.
- The
RegisterAction, which inherits its lifecycle handling
functionality from the ADF DataForwardAction, finds its
binding container and updates the model bindings with corresponding values from
the BindingContainerActionForm form bean.
- The control value bindings are bound to the current row in an
iterator binding based on the
Accounts view object, so
setting the values on the bindings sets these values on the underlying view row
attributes.
- The view row delegates the
attribute setter calls to the correct, underlying entity object attributes,
causing business rules to be evaluated
- Since
the (Save Changes) button is named
event_Save, the RegisterAction invokes
the onSave() event handling method. It inherits this
event-handling functionality from the ADF DataForwardAction.
This method calls the invokeEventAction() helper method to
invoke the action named "Save" in the binding container.
This action binding is bound to the built-in "Commit"
operation on the ToyStoreService data control, so the
transaction is committed.
- On transaction
commit, if no validation errors have occurred, the entity objects persist their
changes into their underlying tables.
When the ADF view object row is populated, since the view object was
associated to underlying entity objects at design time, the framework delegates
the attribute setting down to the entity objects. These entity objects contain
business rules that get evaluated and which succeed or fail based on the values
the user has entered. If any business rules are violated, all exceptions are
collected into a single, wrapping "bundled exception" by the ADF framework
which is saved in the DataActionContext so that later when
the ADF DataAction lifecycle method reportErrors() is
called, they may be translated into Struts Action Errors for reporting to the
user.
| NOTE: |
In case you're curious to check out the code,
the source for the ADF Struts actions lives in the
./adfc/src directory in the
adf-controllersrc.zip file. You can just use the
Ctrl+Minus short cut to
find the class by name, and JDeveloper will find the source code in this
archive automatically.
|
Building Multi-Page Units of Work with
Automatic State Management
When your applications need to support
an end-user task requiring data entry on many different web pages to complete,
you have to invent a scheme for "carrying" the pending data between pages. For
example, consider a web application that lets employees file expense reports.
The task requires entering many expense line items, potentially requiring
multiple pages to complete the task. As the user is entering these details,
where do you store the pending expense report data as the end-user enters it
bit by bit?
The most common approaches in use today are to store
the pending data in:
- HTTP Session
Objects
- Hidden Form Fields
- Temporary database "staging
tables"
All of these approaches have
downsides, though:
- For some J2EE application
servers, data stored in the HTTP Session cannot be reliably accessed in a
"server farm". Even using servers like Oracle Application Server that
do support clustering and replication of session state,
the more and larger the objects you add to your HTTP Session, the more traffic
you generate between servers to keep each other's HTTP Session objects
synchronized.
- Storing pending data in hidden
form fields, in all but the simplest of cases, requires inventing a scheme to
reflect the objects' field structure in the names of the hidden fields, and
increases the size of every page downloaded.
- Since pending data might not yet be complete, it often is impractical to
commit it directly into the underlying application tables, which might raise
database constraint violations. So, pending data could be stored in some
staging tables that allow in-progress work to be saved and loaded later.
However, either a special set of tables need to be created for each kind of
pending data you need to store, or you need to invent a generic scheme for
serializing the pending data, which is not fun.
The ADF framework provides a built-in state management facility for your
business services that addresses all of these issues out of the box. Any
application module instance can "snapshot" its pending state to the database
and "reactivate" that state from the database at a future point in time. The
pending state is serialized by generic framework code into a single XML
document, containing only the pending changes, and saved in a single round-trip
to the database in a generic table named PS_TXN, managed by
ADF. This approach has the following benefits:
- Works on any J2EE-compliant application server
- Works well in application server farms of any
size
- Has low overhead
- Avoids application server HTTP Session synchronization
traffic
- Doesn't increase the size of web
pages delivered to the user
- Uses the same,
generic mechanism for all applications you
build
The database-backed session
management occurs on a different JDBC connection from the application data
access and persistence. Typically you will want to have the state management
table in a separate schema from the application tables. You can control this by
setting the value of the jbo.server.internal_connection
configuration property to either the name of a JDBC
datasource name, or a JDBC URL connection string. The ADF Toy Store demo sets
the value of jbo.server.internal_connection to use the
jdbc/toystore_statemgmtDS JDBC datasource name so that the
PS_TXN table is created and managed in the
TOYSTORE_STATEMGMT schema, instead of the
TOYSTORE schema where the application tables live.
The ADF application module pool supports three different usage patterns
for how application module components are used by incoming browser-user
requests. The three modes are called:
-
Stateless Mode
This retains no business service component
state, forcing pending changes to commit/rollback in the current
request.
-
Stateful Mode
This
allows pending state to span multiple page requests, using the built-in state
management mechanism just described.
-
Reserved
Mode
This dedicates an instance of the
application module component to the current browser session until later
released in stateless mode, or until the browser session times out.
| NOTE: |
Reserved mode is
provided for upward compatibility with JDeveloper 3.2 but is no longer
recommended for new applications as it does not offer the same high scalability
as the Stateful and Stateless
modes.
|
When we picked the name "Stateful Mode", we honestly
made a really unfortunate choice of terms. We've since come to learn that the
word "stateful" conjures up ideas of terrible scalability in web developers'
minds. Luckily, it's just the name that's unfortunate; the actual functionality
is great. In hindsight, we should have called "Stateful Mode" something like
"Managed State with Stateless Performance Mode".
Using "Stateful
Mode", you can build web applications with performance very near that of a
completely stateless application, but with the programming simplicity of a
stateful application. To deliver on this promise, the ADF application module
pool implements an algorithm known as "Stateless with Session Affinity". Across
multiple page requests in a "stateful mode" page flow, the pool
attempts to return the same component instance used by the
current session on its most recent request in the current page flow. If this
instance is still available in the pool, and it has not been used by another
session in the meantime, this scenario gives the highest performance.
If instead the pool needed to hand out that instance to service another
browser user session in the meantime, or if the request has come into a
different server in the server farm, then the pool grabs any available business
service component instance and "reactivates" the current browser user's pending
state from the database. This means that if the user had created 5 new rows,
updated 2 rows, and deleted 1 row, that these same pending changes would be
present after reactivating the state from the database. Also, view object
iterator position information is included in the "state snapshot", so that if
on the last request your iterator was pointing at the third row in a collection
of twenty, your current row will still be row number three after state
reactivation.
The executive summary is that if you size the
application module pool for the load your application needs to handle, then you
get
excellent performance and
scalability [36].
Stateful Mode is the ADF
default mode of operation. Since the task of filling the user's shopping cart
and buying the items is all part of a logical transaction, this default works
well for the Toy Store Demo.
To look at one example, let's take the
ADF Toy Store Demo's shopping cart. It is implemented as an ADF view object
named toystore.model.dataaccess.ShoppingCart in the
ToyStoreModel project, This view object has all
transient attributes and no SQL query. The
YourCartAction Struts action class calls the
ToyStoreService business interface method
adjustQuantitiesInCartAsStringArrays() to add, change, or
remove items from the cart. Since we're using "Stateful Mode", the application
code does not have to worry about how to store the pending shopping cart data.
By visiting the Tuning panel of the View Object editor for
the ShoppingCart view object, we indicate declaratively that we want all
transient attributes of this view object to be passivated by the framework's
state management mechanism. Just checking the checkbox there is the only work
required to leverage this feature.
On each subsequent request, our
actions can access the ShoppingCart view object to work with
its collection of data transfer objects, and programmatically adjust the
contents of the view object's default rowset.
| NOTE: |
To readers who
may be familiar with the Oracle Forms product, the view object is being used
here like a "non database block" in Oracle Forms.
|
The final
action in the demo's page flow, the "/thankyou" action uses
the "Stateless" release mode to indicate that we no longer need any more
pending state to be managed. In order to release in stateless mode, it
overrides the main handleLifecycle() method of the
DataForwardAction and adds a call to the helper method
releaseToyStoreServiceStateless(), which it inherits from
the ToyStoreServiceDataForwardAction:
/* From ToyStoreServiceDataForwardAction */
protected void releaseToyStoreServiceStateless(DataActionContext ctx) {
releaseDataControlStateless(DATACONTROLNAME, ctx);
}
This method turns around and invokes a helper method on
the ToyStoreDataForwardAction, passing in the name of the
data control to release in stateless mode. As you can see in the method below,
it finds this data control and invokes its releaseState()
method, so that all pending statement management info is released at the end of
the request.
/* From ToyStoreDataForwardAction */
protected void releaseDataControlStateless(String dcName,
DataActionContext ctx) {
DCDataControl dc = ctx.getBindingContext().findDataControl(dcName);
if (dc != null) {
dc.resetState();
}
}
Releasing an application module to the pool in
stateless mode automatically cleans up any database-backed pending state that
was being managed by the framework on its behalf.
Understanding Optional Failover Support
The ADF state management support offers an optional feature
called failover mode that allows your application to
continue uninterrupted in case a user ends up failing-over from one application
server instance to another. When failover mode is enabled,
the ADF state manager eagerly saves the pending middle-tier transactional state
of an application module instance to the passivation store each time it is
released to the application module pool in "Managed State" mode. This
effectively translates to a passivation store "hit" for each HTTP page request.
In addition, the ADF data control saves a passivation id in an HTTP browser
cookie so if a subsequent HTTP request is handled by a
different application server, its application module pool
can recover the pending state of your existing transaction and continue working
without the user noticing that the application server failed-over.
| NOTE: |
The default passivation store is the database if using the Oracle
database, and the file system if using a non-Oracle database. You can choose a
non-default behavior by setting the jbo.passivationstore
configuration parameter to file or
database.
|
| NOTE: |
With browser cookies
enabled, the different application servers need not be clustered to support
this failover mode. If your browser has cookies disabled,
failover mode will still work provided that you mark your web application as
distributable in the web.xml and deploy it to a J2EE
application server cluster. This works because each time the passivation id is
updated in the HTTP Session attribute holding the so-called ADF "session
cookie", the cluster will replicate that change to other nodes so any of the
cluster nodes will reflect the most recent passivation id.
|
In contrast, when the failover mode is disabled, the ADF
state manager will save the pending state of an in-progress transaction to the
passivation store only as needed, avoiding the eager passivation store hit on
each request. As long as the application module pool's session affinity
algorithm succeeds in servicing your browser session with the same application
module instance from request to request, no pending state need be written to
the passivation store. However, should unexpected user load — or an undersized
Referenced Pool Size setting — force the pool to handle a
request from another session using the application module
instance that your session had previously referenced, the pool will snapshot
that instance's pending state to the passivation store before allowing the
other session to check it out of the pool. As long as your next page request is
handled by the same application server, the pool uses the
passivation id of your most recent state snapshot — saved as an attribute in
your HTTP Session attribute — to reactivate your pending
changes from the passivation store into another application module instance.
However, if your request is routed to a different
application server instance, with failover mode disabled your pending
transaction will not be able to continue seamlessly.
You control
whether failover mode is used by setting the Failover Transaction
State Upon Managed Release setting on the Pooling and
Scalability panel of the Business Component
Configuration editor. The default for this setting, which
corresponds to the jbo.dofailover configuration property, is
true, erring on the side of safety rather than absolute
performance. The ADF Toy Store demo ships with the property changed to
false to improve runtime performance at the cost of not
supporting the application failover feature.
Trying Out
the Failover Feature in the Demo
The failover feature sounds
complicated, but it's easy to demonstrate. You can enable failover mode for the
ADF Toy Store demo by selecting the ToyStoreService in the
Application Navigator and choosing
Configurations... from the right-mouse menu. Select the
ToyStoreServiceLocal configuration and click on the
(Edit) button. Visit the Pooling and
Scalability panel and check the Failover Transaction State
Upon Managed Release property.
After running the
ToyStoreViewController project to start the demo on the
embedded OC4J container built-in to JDeveloper 10g, try the following:
- Add several items to your shopping
cart
-
Without closing
your browser window, terminate the OC4J application server to simulate a
hardware failure on your application server machine.
To do this,
select the View | Run Manager menu option to display
the Run Manager. Find the Embedded OC4J
Server process in the list, and select it. Finally, choose the
Terminate menu option from the right-mouse menu item
as shown in Figure 46.
 Figure 46: Terminating the OC4J Server to Simulate a Server Failure
- Re-run the ADF Toy Store demo by running the
ToyStoreController project
again..
After restarting the application
server — in this example, we've restarted the embedded OC4J application server
in JDeveloper — the browser window you left open in step 2 above will be able
to continue where it left off, with all shopping cart items intact.
In the ADF Toy Store application, the pending shopping cart information
is not stored in the HTTP session state the way most applications do. Instead,
with a declarative checkbox on the ShoppingCart component at
design time, we indicate that we'd like this component's pending data to be
managed for us. And the framework takes care of the rest.
Building the View Layer with
JSP Pages and JSTL
As Figure 47 shows,
the majority of the view layer in the ADF Toy Store demo is implemented using
JSP pages, with the exception of one example illustrating how to use an
XML/XSLT-based approach as an alternative. We've consciously placed the pages
in subdirectories of the WEB-INF directory so that users
cannot directly browse the pages. We want to force them to go through our
controller layer for every request, and not be able to bookmark and return to
URL's that short-circuit the controller layer by going straight to the JSP
page. Our controller layer can forward control to pages
under WEB-INF, but they cannot be browsed directly based on
rules laid out in the J2EE specifications. So this is a safe, best-practice
approach for guaranteeing that our controller layer is in total control.
 Figure 47: Display-Related Resources and Pages in
the ToyStoreView Project
| NOTE: |
The careful reader might rightfully ask, "What is the
index.jsp file above doing in the root directory of the web
content? I thought you just said that all JSP's go under the
/WEB-INF directory. What gives?"
Here's why. We
want the user be able to access the simple URL:
http://localhost:8888/ADFToyStore
to get to the home page of the web store instead of
having to remember to type the home.do at the end of the URL
like:
http://localhost:8888/ADFToyStore/home.do
Attempts to configure home.do as one
of the standard "welcome files" in web.xml simply didn't
work as expected. So, as a fallback plan, I configured
index.jsp as the welcome file, and have a one-line JSP page
that does a <jsp:forward page="home.do"/>. This gets
the job done.
|
The toystore.view package
contains the ToyStoreResources.properties file and the
GlobalErrors.properties file that store translatable strings
used by our view layer pages, for the default language (English). The
additional versions of these two files with the "_it" and
"_de" in the name contain the Italian and German
translations of the same strings, respectively.
DataBindings.cpx is the ADF Binding
Context file that store metadata about data controls and binding
container names. The *UIModel.xml files contain the detailed
binding container metadata, describing the bindings each one contains. Clicking
on DataBindings.cpx in JDeveloper's Application Navigator,
and looking at its details in the Structure Window, you can see that our demo
only has a single data control named ToyStoreService
defined.
Accessing and
Iterating Over Model Data
Let's start by looking at one of the
view layer JSP pages to study how it's built, the
yourcart.jsp page. Recall from the
Lifecycle of a Web Page Request Using
Struts and ADF section that the
ADFBindingFilter, DataAction, and
DataControl collaborate to handle the details of acquiring
an instance of your application module, making it available to the controller
and view layers as a data control, and then releasing it at the end of the
request. Using the DataActionContext object that the
DataAction creates at the beginning of each request, your
controller layer code can access:
- The
BindingContainer, to work with any of the bindings
- The BindingContext, to work with any of the data
controls
Once you have the data control
in "hand", you can access its data provider and cast it to the application
module's business service interface and can directly or indirectly access the
collections of data transfer objects in its model data map. Or, by working with
the bindings, you can avoid the need directly work with the business service if
you choose. Remember that given the roles that the controller layer and the
view layer play in the MVC architecture, the controller can
modify the model data, whereas the view layer can only
iterate over it to present it.
Quick Overview of JSTL and Its Expression Language
The
popular
JSP
Standard Tag Library [38] (JSTL) provides a set of handy tags that virtually
all JSP web page developers need. Using the JSTL tags, your JSP pages can read
and write attributes from page, request, session, and application scope, easily
iterate over their values, output their values into your page where needed, and
conditionalize the rendering of the page. You have always been able to
perform these tasks in a JSP page: it's just that with
JSTL, you can do them without resorting to pesky JSP scriptlets that make your
page harder to manage.
Using the JSTL tags, you identify which
objects and properties you want to work with by using a simple dot notation
called the JSTL Expression Language, known as "EL" for short. To distinguish
themselves from literal values, these EL expressions appear in the attribute
values of JSTL tags surrounded by the ${ and
} like this:
${expression}.
For
example, to refer to the value of an attribute named
bindings, you would use the expression
${bindings}. If the object returned is a JavaBean with
properties of its own, or a
Map [39] with named members, you can
use a dot notation to refer to its members. For example, if the object named
bindings is a map that contains a member named
ShoppingCart, then you can refer to it using the expression
${bindings.ShoppingCart}.
ADF binding objects
are easy to use with any client technology that can interact with JavaBean's
and the Java Collections Framework, which includes the JSTL tag library. On
each request, the ADF framework's DataAction makes the
current binding container available as an attribute named
bindings and the current Binding Context available as an
attribute named data. You can use EL expressions in JSTL
tags to refer to any information in these contexts.
When working
with JSTL tags, in addition to the standard context-sensitive "Code Insight"
provided for the tag names and tag attributes, as shown in
Figure 48, JDeveloper 10g also provides EL-specific Code
Insight for helping you create your EL expressions.
 Figure 48: Code Insight in JDeveloper 10g for EL
Expressions
We'll see in the next sections that all the JSP pages in the ADF Toy
Store demo make use of JSTL and EL expressions for presenting the data exposed
by the ADF binding layer.
| NOTE: |
Click
here [40] or
here [41]
for handy quick references to JSTL and its Expression Language. (PDF
format)
|
Peeking Into Your Shopping Cart
Let's start by taking a
peek at how the shopping cart page works. The /yourcart Data
Page, which used the related /WEB-INF/jsp/yourcart.jsp page
for its view-layer rendering, is configured like this:
<action path="/yourcart"
className="oracle.adf.controller.struts.actions.DataActionMapping"
type="toystore.controller.strutsactions.YourCartAction"
name="DataForm"
parameter="/WEB-INF/jsp/yourcart.jsp"
unknown="false">
<set-property property="modelReference"
value="WEB_INF_jsp_yourcartUIModel"/>
<forward name="reviewcheckout" path="/reviewcheckout.do"/>
</action>
It is mapped to the YourCartAction
action class, which performs the setup necessary to retrieve the appropriate
model data. We can see from the value of the modelReference
property above that the binding container name for the
/yourcart DataPage is
WEB_INF_jsp_yourcartUIModel (a name generated for us by the
ADF design time tools). With the yourcart.jsp page active,
clicking on the UI Model tab of the Structure Window shows
the contents of the binding container for the page as shown in
Figure 49. There is one iterator binding
(ShoppingCartIterator), and one range binding
(ShoppingCart).
 Figure 49: UI Model Tab Showing Binding Container for yourcart.jsp
At
runtime, the default behavior of the DataForwardAction is to
forward control for rendering to its "companion" JSP page. In this case, our
ShowProductDetails action is configured to forward to the
yourcart.jsp page.
The page starts by declaring
four tag libraries:
- The Struts Bean tag
library, whose tags will be prefixed by
bean:
- The Struts HTML tag
library, whose tags will be prefixed by
html:
- The JSTL Core tag
library, whose tags will be prefixed by
c:
- The ADF tag library, whose tags will be prefixed by
adf:
We're using the
Struts Bean tag library to access the handy <bean:message>
tag, which makes it easy to include translatable text strings into our pages,
based on string keys like "details.title",
"cart.addItem", and
"images.buttons.addtocart".
We're using the
Struts HTML tag library to access the <html:errors> tag, which
makes it easy to displays any errors that occur during runtime processing at
the top of the page in a standard way.
We're using the JSTL Core
tag library to iterate our data collections and include values of their
attributes in the page. The JSTL tags in use in this page are:
-
<c:choose>,
<c:when>, and <c:otherwise> which work like
an if/then/else
statement to conditionalize the display based on whether your shopping cart is
empty or not.
<c:choose>
<c:when test="${not empty bindings.ShoppingCart.rangeSet}">
<form action="<c:url value='yourcart.do'/>" method="post">
<!-- etc. -->
</form>
</c:when>
<c:otherwise>
<br><br><bean:message key="cart.empty"/>
</c:otherwise>
</c:choose>
-
<c:forEach> to
iterate over the items in your shopping cart, and <c:out> to
display attribute values of those items.
<c:forEach var="Row" items="${bindings.ShoppingCart.rangeSet}" >
<tr bgcolor="#f3f3f3">
<td>
<a href="<c:url value='yourcart.do?event=removeItem&id=${Row.Itemid}'/>"
><img src="<bean:message key='images.buttons.removefromcart'/>"
border="0" alt="<bean:message key="cart.removeItem"/>"></a>
</td>
<td><c:out value="${Row.Itemid}"/></td>
<!-- etc. -->
</tr>
</c:forEach>
We're using the ADF tag
library to use the <adf:render> tag that outputs formatted
attribute data based on language-sensitive format masks that you can define in
your business components.
The output that appears in the browser
is shown in Figure 50.
 Figure 50: Your Cart Page
Recall that the contents of the shopping cart was kept in transient view
object rows and populated programmatically as the user adds and removes items
from the cart. So, in the /yourcart example from the
previous section, there was no database query involved in rendering the
page.
A Page
Showing Results of a Query With Bind Variables
Next we'll take a
look at a page like the /showproductdetails DataPage that
presents queried database information. The
/showproductdetails Data Page, which used the related
showproductdetails.jsp page for its view-layer rendering, is
configured like this:
<action path="/showproductdetails"
className="oracle.adf.controller.struts.actions.DataActionMapping"
type="toystore.controller.strutsactions.ShowProductDetailsAction"
name="DataForm"
parameter="/WEB-INF/jsp/showproductdetails.jsp"
unknown="false">
<set-property property="modelReference"
value="WEB_INF_jsp_showproductdetailsUIModel"/>
<forward name="addToCart" path="/yourcart.do"/>
</action>
It is mapped to the
ShowProductDetailsAction action class, which performs the
setup necessary to retrieve the appropriate model data. That action contains
the code shown in Example 8.
Example 8: Code to Initialize Model for ShowProductDetails Page
/* From: toystore.controller.strutsactions.ShowProductDetailsAction */
/**
* Model initialization logic for this page.
*
* When data action is not handling an event-postback, call
* ToyStoreService method prepareToShowProductDetails() to set
* required bind variables.
*
* @param ctx The DataAction context.
*/
protected void initializeModelForPage(DataActionContext ctx) {
String id = ctx.getHttpServletRequest().getParameter("id");
getToyStoreService(ctx).prepareToShowProductDetails(id);
} |
We customized the DataAction lifecycle in a
framework extension class to add the additional
initializeModelForPage() method. We override this method in
our actions set bind variable values in our view object's queries
before the DataAction's prepareModel()
lifecycle phase will cause the queries to be executed. This is a common use
case that our additional lifecycle method makes easier to handle.
| NOTE: |
If we don't set the bind variables before the
prepareModel() phase, then we can get errors like:
JBO-27122: SQL error during statement preparation. Statement: ...
caused by the underlying database error message:
ORA-01008: not all variables bound
|
If you look in the
ToyStoreDataForwardAction, you'll see where we've introduced
this new lifecycle method by overriding the existing
prepareModel() method and augmenting its default behavior to
call initializeModelForPage() first if we are not handling
any events:
/* From: toystore.fwk.controller.ToyStoreDataAction */
/**
* Overridden method.
*
* Add two new overrideable methods into the ADF DataAction lifecycle as
* part of the prepareModel() processing to make handling the page
* initialization use case easier.
*/
protected void prepareModel(DataActionContext ctx) throws Exception {
if (!handlingEvents(ctx)) {
initializeModelForPage(ctx);
}
super.prepareModel(ctx);
if (!handlingEvents(ctx)) {
initializeBindingsForPage(ctx);
}
}
The body of the
initializeModelForPage() method back in
Example 8 uses the DataActionContext to
retrieve the id parameter from the
HttpServletRequest and then pass it as an argument to the
prepareToShowProductDetails() method on our
ToyStoreService business service interface. Behind the
tier-independent ToyStoreService interface is the
ToyStoreServiceImpl implementation class, whose
prepareToShowProductDetails() method looks like this.
/* From: toystore.model.services.ToyStoreServiceImpl */
public void prepareToShowProductDetails(String id) {
getFindItems().setItemToFind(id);
getFindItems().executeQuery();
}
It simply encapsulates the setting of the item id to
find, and re-execution of the view object's query. The
getFindItems() method is a view object instance getter
method that the ADF design time tools generate for you when you add a view
object instance named "FindItems" to your application
module's data model. The view layer will be able to access these data transfer
objects representing the view object query's results by using appropriate
control value binding objects. Under the covers, these control value bindings
are related to an iterator binding that knows how to obtain the data from the
"FindItems" collection from the model data map.
Notice again back in Example 8 that we're using the
getToyStoreService() helper method from the
ToyStoreServiceDataForwardAction superclass that returns our
business service's custom service interface named
ToyStoreService.
/* From toystore.controller.strutsactions.ToyStoreServiceDataForwardAction */
protected ToyStoreService getToyStoreService(DataActionContext ctx) {
return (ToyStoreService) getApplicationModule(DATACONTROLNAME, ctx);
}
This code, in turn, calls the
getApplicationModule() helper method from
its superclass:
ToyStoreDataForwardAction. As you can see below, that method
finds the data control by name, then if it is of the expected type for a data
control based on an ADF Application Module, it returns the instance of the
service as an ApplicationModule interface to work
with.
/* From toystore.fwk.controller.ToyStoreDataForwardAction */
protected ApplicationModule getApplicationModule(String dataControlName,
DataActionContext ctx) {
DCDataControl dc = ctx.getBindingContext().findDataControl(dataControlName);
if ((dc != null) && dc instanceof DCJboDataControl) {
return (ApplicationModule) dc.getDataProvider();
}
return null;
}
At runtime, after the
initializeModelForPage() method has set the appropriate bind
variable values, the default behavior of the
DataForwardAction is to forward control for rendering to its
"companion" JSP page. In this case, our ShowProductDetails
action is configured to forward to the
showproductdetails.jsp page that you see in
Example 9. The tag libraries in use are the same
ones as for the yourcart.jsp page. The only difference here
is that the page displays data a single row from
FindItemsIterator, so we don't need a range binding in the
binding container and don't need a <c:forEach> loop. We also
don't have any conditional display logic going on, so this page has no
<c:choose> or <c:if> tags.
Example 9: The showproductdetails.jsp page
<%@page import="org.apache.struts.action.ActionErrors" %>
<%@ page contentType="text/html;charset=UTF-8"%>
<%@ taglib uri="http://xmlns.oracle.com/adf/ui/jsp/adftags" prefix="adf"%>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c"%>
<html>
<head>
<title><bean:message key="details.title"/></title>
<%@ include file="standardHead.jsp"%>
</head>
<body bgcolor="white">
<%@ include file="header.jsp"%>
<%@ include file="navbar.jsp"%>
<html:errors bundle="GlobalErrors"
property="<%= ActionErrors.GLOBAL_ERROR %>"/>
<table bgcolor="white" width="100%">
<tr>
<td>
<font size="5" color="#003399">
<c:out value="${bindings.Name}"/>
</font>
</td>
<td>
<adf:render model="bindings.ListPrice"/>
</td>
<td>
<c:out value="${bindings.InStock}"/>
</td>
<td>
<a href="<c:url
value='showproductdetails.do?event=addToCart&id=${bindings.ItemId}'
/>"
><img src="<bean:message key='images.buttons.addtocart'/>" border="0"
alt="<bean:message key="cart.addItem"/>"></a>
</td>
</tr>
<tr>
<td class="wrap" colspan="4" valign="middle">
<img align="left" border="0"
src="images/<c:out value="${bindings.Picture}"/>">
<c:out value="${bindings.Description}"/>
</td>
</tr>
</table>
</body>
</html>
|
The output that appears in the browser is
shown in Figure 51.
 Figure 51: Show Product Details Page
All of the JSP
pages in the ADF Toy Store demo follow this basic approach for iterating and
formatting data.
Using Bindings and JSTL To Build a Reusable Paging Control
Three different pages in the demo offer the ability to view results a
page at a time:
/search - showing results from a storewide product
search
/showcategory -
showing the products in a category
/showproduct - showing available items of given
product type
We would like to create a
reusable paging control that displays feedback like 1 - 3 of
18 and conditionally shows Previous and
Next links when it makes sense. The pieces of information
we need to do the job are:
- Total Number of
Rows
- First Row Number Shown on the Current
Page
- Number of Rows Shown per
Page
Each of these pages includes an
appropriate RangeBinding in their respective binding container. The range
binding is an ADF binding object designed for presenting rows of data in a
grid, optionally navigating them a page (or "range") at a time. You set the
size of the range on the related iterator binding to be the number of rows that
you want to appear on a page at a time. If you set the range size to the value
-1, then all rows in the data set will display at
once.
Figure 52 shows the binding
container for the /search DataPage, with its
FindProducts range binding, and the underlying
FindProductsIterator iterator binding and the range binding
is related to.
 Figure 52: UI Model Tab
Showing Binding Container for Search Page
At
runtime, the range binding exposes properties that we can access via JSTL
expressions to retrieve interesting information about the collection to which
its related iterator bound. Some of these properties include:
estimatedRowCount - an
estimate of the rows in the collection
rangeStart - the zero-based row number that appears at
the top the current range of rows
rangeSize - the number of rows to show per
page
Using these three pieces of
information we can calculate all of the bits of information we need.
Example 10 shows the code of our
pagingControl.jsp that you'll find in the
ToyStoreViewController project. You can see that it
leverages the following different JSTL tags, using EL expressions to access the
binding objects it needs:
<c:set> - to set page-local variable values to use later
in the page
-
<c:choose> - to
perform if/then/else logic without writing scriptlet code
(along
with its nested <c:when> and <c:otherwise>
tags).
<c:out> - to output
the value of an EL-expression
<c:url> - performs URL rewriting if necessary to allow
the application to work whether cookies are enabled or disabled by the
browser.
The
Previous and Next links that are
conditionally created are generated to have the
event=Previous and event=Next parameters
in their respective URL's. This will cause the action bindings of the matching
Next and Previous names to be declaratively executed in the
current page's binding container if they exist.
Example 10: Reusable JSTL Paging Control
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c"%>
<%--
| Setup page variables uses for displaying the navigation
| "control" at the top that looks like:
| Previous N - M of Z Next
+--%>
<c:set var="rangeBinding" value="${bindings[param.rangeBindingName]}"/>
<c:set var="totalRows" value="${rangeBinding.estimatedRowCount}"/>
<c:set var="firstRowShown" value="${rangeBinding.rangeStart + 1}"/>
<c:choose>
<c:when test="${not empty param.extraParams}">
<c:set var="queryStrBase" value='?${param.extraParams}&'/>
</c:when>
<c:otherwise>
<c:set var="queryStrBase" value="?"/>
</c:otherwise>
</c:choose>
<c:choose>
<c:when test="${rangeBinding.rangeSize == -1}">
<c:set var="rowsPerPage" value="${totalRows}"/>
</c:when>
<c:otherwise>
<c:set var="rowsPerPage" value="${rangeBinding.rangeSize}"/>
</c:otherwise>
</c:choose>
<c:choose>
<c:when test="${firstRowShown + rowsPerPage - 1 > totalRows}">
<c:set var="lastRowShown" value="${totalRows}"/>
</c:when>
<c:otherwise>
<c:set var="lastRowShown" value="${firstRowShown + rowsPerPage - 1}"/>
</c:otherwise>
</c:choose>
<c:if test="${totalRows > rangeBinding.rangeSize}">
<c:choose>
<c:when test="${firstRowShown > 1}">
<a href="<c:out value="${param.targetPageName}${queryStrBase}"/>event=Previous"
><bean:message key="paging.previous"/></a>
</c:when>
<c:otherwise>
<font color="#e0e0e0"><bean:message key="paging.previous"/></font>
</c:otherwise>
</c:choose>
</c:if>
<c:out value="${rangeBinding.rangeStart + 1}"/>-
<c:out value="${lastRowShown}"/> <bean:message key="paging.of"/>
<c:out value="${totalRows}"/>
<c:if test="${totalRows > rangeBinding.rangeSize}">
<c:choose>
<c:when test="${lastRowShown < totalRows}">
<a href="<c:out value="${param.targetPageName}${queryStrBase}"/>event=Next"
><bean:message key="paging.next"/></a>
</c:when>
<c:otherwise>
<font color="#e0e0e0"><bean:message key="paging.next"/></font>
</c:otherwise>
</c:choose>
</c:if> |
If you look inside the
search.jsp page's source code, you'll see that it makes use
of this reusable pagingControl.jsp page using
<jsp:include>, passing a couple of parameters needed by the
page with nested <jsp:param> tags. Notice that it uses a
<c:choose> tag that uses the EL not empty
operator to test whether any products have been found or not. If no products
were found, it outputs a specific "No matching products" message instead of
showing an empty table of results.
<c:choose>
<c:when test="${not empty bindings.FindProducts.rangeSet}">
<jsp:include page="pagingControl.jsp">
<jsp:param name="rangeBindingName" value="FindProducts"/>
<jsp:param name="targetPageName" value="search.do"/>
</jsp:include>
<table border="0" bgcolor="#003399">
<!-- etc. -->
</table>
</c:when>
<c:otherwise>
<br><br><bean:message key="search.nomatchingproducts"/>
</c:otherwise>
</c:choose>
The first thing that the
pagingControl.jsp does is take the string-valued
rangeBindingName parameter that we pass in, and lookup the
range binding object having that name using the syntax:
<c:set var="rangeBinding" value="${bindings[param.rangeBindingName]}"/>
Inside the pagingControl.jsp, it can
use EL expressions to refer to properties of that range binding to do its job
in rendering the paging control display. Notice that instead of using the EL
dot notation like ${bindings.SomeRangeBinding}, this
expression uses array-index-style notation using square brackets. This allows
the member name your are trying to access, in this case on the
bindings object, to be provided by another
expression value, instead of providing the name as a
literal.
One detail you might be asking yourself is, "Where do I
set the number of rows per page to display?" Excellent question! Clicking on
the FindProductsIterator in the UI Model tab shown in
Figure 52 and looking at the Property Inspector,
we can see that the Range Size property is set to the
value 3. This means that at runtime, this iterator will
present its results in pages of up to 3 rows at a time. To change to show five
at a time, we would only need to modify this declarative iterator property to
the value 5 instead of changing code. If you need to support
functionality allowing the user to change the number of rows displayed per
page, keep in mind that you can also call setRangeSize() on
your iterator binding.
A Data Entry Form Using Traditional
Form Layout Approach
The /editaccount DataPage
illustrates an example of a page that displays a data entry form to edit user
profile information. It uses a traditional JSP page layout approach of placing
each control to use inside an HTML form.
Setting Up the Model Layer
Data
The EditAccountAction sets up the model
layer in its initializeModelForPage() method as we've seen
above. It calls the custom service method
prepareToEditAccountInfoFor() on the
ToyStoreService interface, passing in the name of the
current user as an argument.
The implementation of this method in
the ToyStoreServiceImpl class looks what you see in
Example 11. It performs the following three basic
steps:
- Creates a
oracle.jbo.Key object based on the current user's name
passed in
- Looks up an existing row in the
Accounts view object by passing this key to the
findByKey() method on the view object.
- Sets that row as the current row in the view
object.
Example 11: ToyStoreService Method to Prepare to Edit Account Info
/* From: toystore.model.services.ToyStoreServiceImpl */
public boolean prepareToEditAccountInfoFor(String username) {
Key k = new Key(new Object[] { username });
ViewObject vo = getAccounts();
/*
* We don't want the view object to execute any other query
* than the one row we will be finding by key, so we mark
* it's max fetch size to zero.
*/
vo.setMaxFetchSize(0);
Row[] r = vo.findByKey(k, 1);
if (r.length < 1) {
return false;
}
Row rowFound = r[0];
/*
* Set the row we found as the current row in the VO
*/
vo.setCurrentRow(rowFound);
return true;
} |
The
Layout of the HTML Form
In the corresponding JSP page, named
editexistingaccount.jsp, we use the
<html:form> tag from the Struts HTML tag library to implement
the "Postback Pattern" by having its action post back to the DataPage like
this:
<html:form action="/editaccount.do" method="post">
At runtime, the Struts <html:form> tag
sees the action attribute value of
/updateaccount.do and uses it, along with its action mapping
information, to determine that the FormBean named DataForm
is the one that should be used to render this form. The
DataForm form bean is defined in
struts-config.xml to use our ADF
BindingContainerActionForm.
Since we're
rendering the data entry form for just a single "row" of user account
information, we don't need to use the use the JSTL <c:forEach>
in this page and don't need a range binding in our binding container. We simply
format the individual fields in the form, using normal HTML table tags to get
the prompts and controls to line up nicely. As this page shows off several
different techniques in use, we'll try to highlight each of the important ones
in turn.
The Binding
Container for This Data Page
Figure 53 shows the binding container for the
EditAcount page. Notice that we have basic attribute
bindings for all of the Accounts attributes
except for Country, which is a list
binding (its icon shows a poplist in it). We have two iterator bindings:
AccountsIterator for the main Accounts
information we're editing, and CountryListIterator that will
supply a poplist with the valid country names the user can choose for the
Country attribute.
 Figure 53: UI Model Tab Showing Binding
Container for EditAccount Page
We
also have an action binding named save that is bound to the
built-in Commit operation on the
ToyStoreService data control.
Showing Read-Only Data in a
Form
Example 12 shows the tags in the page
that output the HTML table row containing the prompt and data for the
Username property. Since the username in this application is
not updateable once it's been created, we don't need to render an HTML form
control for the data. Using the <c:out> tag, we can easily
just output the value of the field for display using its corresponding binding
object. The <bean:message> tags are outputting translatable
strings from the default ToyStoreResources.properties
properties file to display the tooltip and the label for the username.
Example 12: Showing Read-Only Data Using c:out
<%-- Username field --%>
<tr>
<th align="right" title="<bean:message key="account.username.tooltip"/>">
<bean:message key="account.username.label"/>
</th>
<td title="<bean:message key="account.username.tooltip"/>">
<c:out value="${bindings.Username}"/>
</td>
</tr> |
Creating Input Fields in the Form
When data needs to be
entered or edited, you can use a number of other tags in the Struts HTML
library to render databound controls. Example 13 shows
using the <html:password> tag to show the
Password property.
<html:password property="Password" size="25" maxlength="30"/>
Recall that the ADF
BindingContainerActionForm presents Struts (and here in
particular, the Struts HTML tag library's tags) with a DynaActionForm bean
having properties that are named after, and "wired to", the bindings in your
current binding container, so when the <html:password> tag
gets and sets the value of the Password property on this
form bean, behind the scenes ADF is coordinating the properties of that form
bean with the corresponding binding objects.
Example 13: Use Struts HTML Tags to Create
Data-Bound Form Controls
<%-- Password field --%>
<tr>
<th align="right" title="<bean:message key="account.password.tooltip"/>">
<bean:message key="dataentryform.mandatory"/>
<bean:message key="account.password.label"/>
</th>
<td title="<bean:message key="account.password.tooltip"/>">
<html:password property="Password" size="25" maxlength="30"/>
</td>
<td><html:errors property="Password"/></td>
</tr> |
The example also illustrates using the Struts
HTML tag <html:errors> to display any validation errors that
are specific to the Password attribute. Of course, when the
form is first rendered there won't be any validation errors, so this table cell
will be empty. However, if the user submits the form and validation errors in
the model layer are thrown, when this page is rendered again, any errors
related to Password will show up next to the
Password field on the screen. Also, since we know this field
is mandatory, we've included a <bean:message> tag to show the
string corresponding to the key "dataentryform.mandatory" as
a visual marker to the user that the field is required. By default, we render
an asterisk.
Using EL to
Tap Into Labels, Tooltips, and Other Metadata
ADF entity object
and view object components have a number of built-in features that allow
developers to define control hints like locale-sensitive labels, tooltips, and
format masks. The ADF binding layer exposes this metadata directly on the
binding objects for convenient access by your view layer pages. The
<c:out> tags shown in Example 14 illustrate the EL expressions for the
tooltip and label information that have been associated with the business
object attributes or the view object attributes. If an entity object has
defined a tooltip for one of its attributes named Firstname,
for example, then this tooltip is inherited by any view objects that include
Firstname. Of course, the view object can also override
these control hints if necessary.
Example 14: Accessing ADF Binding Control Hints and Metadata
<%-- Firstname field --%>
<tr>
<th align="right" title="<c:out value='${bindings.Firstname.tooltip}'/>">
<c:if test="${bindings.Firstname.mandatory}">
<bean:message key="dataentryform.mandatory"/>
</c:if>
<c:out value="${bindings.Firstname.label}"/>
</th>
<td>
<html:text property="Firstname" size="30" maxlength="35"/>
</td>
<td><html:errors property="Firstname"/></td>
</tr>
|
Each binding object exposes runtime metadata
about the objects to which it is bound that you can access at runtime using EL
expressions. For example, the control value binding for an attribute exposes
information about the underlying attribute in the model layer. In
Example 14 we see an example of using this
metadata to detect at runtime whether a given attribute, like
Firstname, is mandatory or not. Combined with the JSTL tag
<c:if>, we can use this information to conditionally output
the mandatory marker on a required field.
<c:if test="${bindings.Firstname.mandatory}">
<bean:message key="dataentryform.mandatory"/>
</c:if>
One helpful tip to remember is that to get a quick
review of all the available properties on a binding object, you can just click
on the binding in the UI Model tab of the Structure Window and press the
F1 key. The online help topic for the
appropriate binding object appears in an IDE window for your
reference.
Including a
Data-Bound Poplist Control
Finally, we look at an example of a
data-bound form control like a poplist showing the country where a user
resides. As shown in Figure 54 their are two dimensions
to the control:
- The value of the underlying
Country binding, reflected by the selection in the list,
and
- The list of all available country names to
chose from.
 Figure 54: Poplist Has Both Current Value and List of Valid Values
ADF provides more
sophisticated binding objects to handle controls list this that have multiple
facets to their data binding requirements. The ADF List Binding caters
specifically to poplist-type controls that need to manage both a current bound
attribute value, as well as a list of valid choices to present to the user. For
hierarchical data, ADF supplies a Tree Binding object that can come in handy on
occasion as well.
By clicking on the UI Model
tab of the Structure Window while the
editexistingaccount.jsp is active, you'll see the bindings
we saw back in Figure 53. If you select the
CountryListIterator and look in the Property Inspector,
you'll see that it has a range size of -1. This value
indicates that you want all rows to appear in the list of countries, instead of
only a partial set.
| NOTE: |
The iterator binding range size defaults
to 10. For iterators driving the list of choices in a list
binding, you will nearly always want to set the range size to be
-1 as we've done here.
|
Clicking on the
Country binding and selecting
Edit... from the right-mouse menu, you will see the
List Binding Editor shown in Figure 55. It allows you
to see the binding metadata required to support the Country
poplist:
- The datasource for the list of
available choices comes from
CountryListIterator
- The
iterator whose current row will be used to determine the current value of the
binding and to update in case of selecting a new item from the list is
AccountsIterator.
- The
(source,target) attribute pairs show that the value of the
Code property from the selected row in the
CountryList will be set into the Country
property on the current row of the target
AccountsIterator.
If
you click on the LOV Display Attributes tab, you can
observe that the Description attribute from the
CountryListIterator is indicated as the value to display to
the user in the list.
 Figure 55: List Binding
Editor for the Country List Binding
Example 15 shows how to use the
<html:select> and <html:optionsCollection> to
leverage this Country list binding to put the poplist onto
our page. The <html:select> is bound to the
Country property of the form bean, which is non other than
our list binding object. The <html:optionsCollection> gets its
data from the nested, List-valued
displayData property of that same Country
binding. The beans in this display data collection each have a
prompt and an index property, so we
indicate to use those as the label and
value (respectively) for each option in the list.
| NOTE: |
For bandwidth optimization, the ADF binding layer expects the
non-visible values of a list binding to be the zero-based index number in their
displayData collection. The ADF list binding handles translating the underlying
Country value (like "IT" for example)
into an index position (like 86) in the list of values both
on read and write of the binding value.
|
Example 15: Using html:select to Render a Data-Bound
PopList
<%-- Country field --%>
<tr>
<th align="right" title="<c:out value='${bindings.Country.tooltip}'/>">
<c:if test="${bindings.Country.mandatory}">
<bean:message key="dataentryform.mandatory"/>
</c:if>
<c:out value="${bindings.Country.label}"/>
</th>
<td>
<html:select property="Country" >
<html:optionsCollection label="prompt"
value="index"
property="Country.displayData" />
</html:select>
</td>
<td><html:errors property="Country"/></td>
</tr> |
Rendering Data Entry Forms in a More Generic Way Using Metadata
In contrast to the more "traditional" technique explained above, the Toy
Store demo also includes another page that renders a data entry form in a more
generic, metadata-driven way. Both forms render the same set of controls for
Accounts data, so it's even easier to compare the two
approaches and pick the one that will suit your
applications best.
The Register
New User Page
Example 16 shows the
registernewuser.jsp page (used by the
/register DataPage) which renders the data entry form
allowing users to register on the site for the first time. The
results produced in the browser of this page are nearly
identical to the editexistingaccount.jsp page we looked at
above, but as you can see from the example, the whole form is rendered by the
single <jsp:include page="formControl.jsp"> tag. This tag
works like a reusable component, including the contents of the
formControl.jsp page. The nested
<jsp:param> tags pass three parameters to the reusable
component page:
dataPage
- The name of the current datapage
saveButtonLabelKey - The message bundle key to the
label to display on the (Save) button
saveButtonEvent - The name of the event to
association with the pressing of the (Save)
button.
Example 16: Register
New User Page
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<html>
<head>
<title><bean:message key="registernewuser.title"/></title>
<link href="css/ToyStore.css" rel="stylesheet">
</head>
<body bgcolor="white">
<jsp:include page="header.jsp" flush="true"/>
<jsp:include page="navbar.jsp" flush="true"/>
<h2><bean:message key="registernewuser.header"/></h2>
<jsp:include page="formControl.jsp">
<jsp:param name="dataPage" value="register"/>
<jsp:param name="saveButtonLabelKey" value="dataentryform.register"/>
<jsp:param name="saveButtonEvent" value="save"/>
</jsp:include>
</body>
</html> |
So the actual work being done lies in the
formControl.jsp "component" page. The page builds a data
entry form with one data-bound control for each control value binding in the
current binding container.
Diving into the Generic Form Control Page
The page begins
with some examples of using the <c:choose>,
<c:if>, and <c:set> tags to conditionally set
up the values of page local variables named eventName,
buttonLabel, and buttonLabelKey based on
whether and which of the expected input parameters were provided. We'll use
these variables later in the page as part of constructing the
(Save) button at the bottom of the generated form.
<c:choose>
<c:when test="${not empty param.saveButtonEvent}">
<c:set var="eventName" value="${param.saveButtonEvent}"/>
</c:when>
<c:otherwise>
<c:set var="eventName" value="Commit"/>
</c:otherwise>
</c:choose>
<c:if test="${not empty param.saveButtonLabel}">
<c:set var="buttonLabel" value="${param.saveButtonLabel}"/>
</c:if>
<c:if test="${not empty param.saveButtonLabelKey}">
<c:set var="buttonLabelKey" value="${param.saveButtonLabelKey}"/>
</c:if>
The formControl.jsp page goes on to
use <html:errors> tag as part of a "global errors" section of
the input form, where any errors that are not attribute-specific will show
up:
<center>
<table border="0">
<tr>
<td><html:errors bundle="GlobalErrors"
property="<%= ActionErrors.GLOBAL_ERROR %>"/></td>
</tr>
</table>
</center>
Next the form uses the value of the
dataPage parameter passed in by the
jsp:include as part of opening the
<html:form> tag. Notice that since we cannot use EL
expressions directly in the <html:form> tag's action
attribute, we use <c:set> to first set a page local variable
named name with the EL-expression value we want, then we use a JSP scriptlet to
pass the value of this name variable to the action
attribute:
<c:set var="name" value="/${param.dataPage}.do"/>
<html:form action='<%= pageContext.getAttribute("name")%>'>
<!-- etc. -->
</html:form>
The form includes the standard hidden field that the ADF
controller layer uses to detect whether the user has tried to submit the same
form multiple times in rapid succession:
<input type="hidden" name="<c:out value='${bindings.statetokenid}'/>"
value="<c:out value='${bindings.statetoken}'/>"/>
Iterating Over the Control Value Bindings to Build the Form
Next we begin the loop that will create an HTML form field for each
control value binding in the binding container. Inside the
<table> tag, we have the following
<c:forEach> iteration:
<c:forEach var="curBinding" items="${bindings.ctrlBindingList}">
<% JUControlBinding cb =
(JUControlBinding)pageContext.getAttribute("curBinding");
if (cb instanceof JUCtrlValueBinding &&
!(cb instanceof JUCtrlRangeBinding) &&
!(cb instanceof JUCtrlHierNodeBinding)) { %>
<!-- Build control for current control value binding in here -->
<% } %>
</c:forEach>
The <c:forEach> loop iterates over the
list of control value bindings from the binding container. Since this list
might include control action bindings, we need to skip
over those when rendering the input controls. Since we want to keep things
simple, we'll also skip over the RangBindings and TreeBindings, too. The EL
expression language doesn't have a built-in instanceof
operator, so we're using a JSP scriptlet to use a regular Java-language
if statement to perform the combination of
instanceof checks.
| NOTE: |
We could have decided
to generically render a set of buttons for any of the action bindings found in
the binding container, which would be of type
JUCtrlActionBinding in the
oracle.jbo.uicli.binding package, but I wanted to keep this
example simple and just render a single (Save) button on
the form.
|
Since we specified the
var="curBinding" attribute on the
<c:forEach> tag, inside the loop we can refer to this
curBinding loop variable to access the current control value
binding as part of our generic form input control generation. Notice how we're
making use of the binding properties in our EL expressions like
tooltip, mandatory, and
label to access this metadata from the current control
binding.
<c:forEach var="curBinding" items="${bindings.ctrlBindingList}">
<% JUControlBinding cb =
(JUControlBinding)pageContext.getAttribute("curBinding");
if (cb instanceof JUCtrlValueBinding &&
!(cb instanceof JUCtrlRangeBinding) &&
!(cb instanceof JUCtrlHierNodeBinding)) { %>
<tr>
<th align="right" title="<c:out value='${curBinding.tooltip}'/>">
<c:if test="${curBinding.mandatory}">* </c:if>
<c:out value="${curBinding.label}"/>
</th>
<td>
<c:set var="name" value="bindings.${curBinding.name}"/>
<adf:inputrender model='<%= pageContext.getAttribute("name")%>'/>
</td>
<c:set var="name" value="${curBinding.name}"/>
<td>
<html:errors property='<%= pageContext.getAttribute("name") %>'/>
</td>
</tr>
<% } %>
</c:forEach>
To actually render the HTML form control, we use the
<adf:inputrender> tag which is setup to render an appropriate
tag based on the datatype of the current binding's attribute value. As we'll
see, we can also use some additional attribute metadata to customize the way
that the <adf:inputrender> tag does its job. We repeat our
trick of using <c:set> to set a local page variable named
name to the concatenation of the string
"bindings." with the name of the current binding, which is
what the <adf:inputrender> tag wants to "see" as the value of
its model attribute.
Finally, we use the
<html:errors> tag to show any attribute-level validation
errors that might occur next to the control to which they are relevant. We
again use the <c:set> trick to get the value of the
<html:errors> tag's property attribute to
be the name of the current binding.
Last, but not least, we use a
<c:choose> to put the appropriately-labeled
(Save) button at the bottom of the form. We're based on
whether the user specified a button label or a button label key, we either use
the literal label string, or employ the bean:message tag to lookup the label
key for us. We're using our page local variable eventName
that we setup at the top of the page to fill-in the right name for the button
to generate that event when clicked by the user.
<c:choose>
<c:when test="${not empty buttonLabel}">
<input name="event_<c:out value="${eventName}"/>" type="submit"
value='<c:out value="${buttonLabel}"/>'/>
</c:when>
<c:when test="${not empty buttonLabelKey}">
<input name="event_<c:out value="${eventName}"/>" type="submit"
value='<bean:message name="buttonLabelKey"/>'>
</c:when>
<c:otherwise>
<input name="event_<c:out value="${eventName}"/>" type="submit"
value='Submit'/>
</c:otherwise>
</c:choose>
Using Metadata to
Tailor the Custom Edit Field Renderers
Each ADF Business Component
supports setting custom properties that can be read at runtime and be used to
drive metadata-driven behavior. For ADF entity objects and view objects which
have attributes, you can also set custom properties on individual
attributes as well.
To edit attribute
properties, expand the Attributes heading in the respective object editor, and
click on the name of the attribute whose properties you want to modify. Then,
click on the Attribute Properties tab on the editor panel
on the right. Figure 56 shows what this looks
like for editing the custom attribute properties of the
Country attribute of the Accounts view
object.
The <adf:inputrender> tag implementation
consults the value of the EditRenderer attribute property to
see if the attribute has specified a custom renderer. If none is specified, a
default heuristic is used to pick an appropriate control.
 Figure 56: Custom Attribute Properties for
Country Attribute in Accounts View Object
In this
example, we've specified the class name
toystore.fwk.view.ListBindingPoplistRenderer which
implements a customized poplist renderer for ADF list bindings. This custom
field renderer extends the default
oracle.jdeveloper.html.StaticPickList renderer to populate
some of its properties based on information it can retrieve from the list
binding object. The source code for the custom renderer (from the
FwkExtensions project in the demo) is shown in
Example 17. You can see that the code accesses the
JUControlBinding object from the datasource, and after
checking that its a JUCtrlListBinding, calls the
getDisplayData() method on the list binding to access the
list display data. In order to populate the String[]
variables for the labels and the values, it iterates over the display data
collection and adds the prompt attribute from each bean in
the collection to the label array. Since the ADF binding layer will expect the
value coming back from the page to be the numerical row number, we populate the
values array by converting the loop variable z to a string
on each iteration. The net effect is that when our generic
formControl.jsp "component" page renders an HTML form for
the bindings in the current binding container, the Country
binding will render as a data-bound poplist populated from the display data
collection configured of value objects in the model data map named
"CountryList".
Example 17: Custom Field Renderer Used by adf:inputrenderer Tag
package toystore.fwk.view;
import java.util.List;
import java.util.Map;
import oracle.jbo.Row;
import oracle.jbo.html.BindingContainerDataSource;
import oracle.jbo.uicli.binding.JUControlBinding;
import oracle.jbo.uicli.binding.JUCtrlListBinding;
import oracle.jdeveloper.html.StaticPickList;
/**
* Extends the oracle.jdeveloper.html.StaticPickList renderer to drive
* off of a list binding.
*/
public class ListBindingPoplistRenderer extends StaticPickList {
/**
* Overrides renderToString() in StaticPickList
*/
public String renderToString(Row row) {
BindingContainerDataSource ds = (BindingContainerDataSource)getDatasource();
JUControlBinding b = ds.getControlBinding();
String[] labels = null;
String[] values = null;
if (b instanceof JUCtrlListBinding) {
JUCtrlListBinding listBinding = (JUCtrlListBinding)b;
List valueList = listBinding.getDisplayData();
int size = valueList.size();
values = new String[size];
labels = new String[size];
for (int z = 0; z < size; z++) {
labels[z] = (String)((Map)valueList.get(z)).get("prompt");
values[z] = Integer.toString(z);
}
setValue(Integer.toString(listBinding.getSelectedIndex()));
}
setDataSource(labels,values);
return super.renderToString(row);
}
} |
If you adopt a generic data form rendering
technique like this in your applications, you can more easily insure that all
data entry forms in your application look and act similarly since they are all
rendered by the same generic code.
Struts and ADF Features for Building
Multilingual Applications
Above we've seen the use of
translatable strings in a few example JSP pages, as well as references to
translatable attribute labels, tooltips, and format masks. In this section
we'll quickly cover the features provided by the Struts and ADF frameworks for
building applications that need to support user interfaces in multiple
languages.
Struts Message
Resource File Support
Struts provides a basic facility for using
translated messages stored in standard Java *.properties
files. There is a default message resource file, but you can define secondary
message resources as well. The ADF Toy Store demo uses:
-
The default Struts resource message file
In
./ToyStoreView/src/toystore/view/ToyStoreResources.properties
-
A secondary resource message file for global errors identified
by the key "GlobalErrors"
In
./ToyStoreView/src/toystore/view/GlobalErrors.properties
You make Struts aware of the names of your message
resource files in struts-config.xml as shown in
Example 18. The
<message-resources> element with no key
attribute defines the location of the default message resource. Secondary
message resources identify their key by specifying a value for the
key attribute.
Example 18: Configuring Default and Secondary Struts Message Resource Files
<struts-config>
:
<!--
| This entry tells Struts where to find the default application
| resources properties file
+-->
<message-resources parameter="toystore.view.ToyStoreResources"/>
<!--
| Resource used to render globals errors with <html:errors>
+-->
<message-resources key="GlobalErrors" parameter="toystore.view.GlobalErrors"/>
</struts-config> |
The messages are stored in a properties file
that pairs a string key with a text message. For example, two lines that appear
in ToyStoreResources.properties are:
:
cart.addItem=Add Item to Your Shopping Cart
cart.removeItem=Remove Item from Your Cart
:
You provide translations of the messages in the message
resource file by creating a properties file in the same directory with the same
name, except with the locale suffix appended to it. For example, the Italian
translations of the messages in ToyStoreResources.properties
are in the ToyStoreResources_it.properties file in the same
directory. The translated message resource files contain the same string
key as the default language, but with the translated
version of the default language's message string. For example, the above two
messages in English look like this in the Italian version of the message
resource file:
:
cart.addItem=Aggiungi al carrello
cart.removeItem=Togli dal carrello
:
If there are messages that do not
need to be translated, there is no need to repeat them in
the translated message resource files. If a message cannot
be found in the locale-specific message bundle, the one from the default
message resource file will be used as a fallback.
| NOTE: |
If you want
to provide messages that are sensitive both to the language
and the country components of the locale, you can use a
suffix that includes both like this:
ToyStoreResources_de_CH.properties. This would be used when
a user has set their locale to use the German language (de)
for the country of Switzerland (CH).
|
As
we've seen in the sections above, you include a message resource string in a
JSP page using the Struts "Bean" tag library's <bean:message>
tag, providing the string key to lookup like this:
<bean:message key="cart.addItem"/>
At runtime, the Struts <bean:message>
tag attempts to return the string for the most specifically matching message
resource file that is available. For example, if the user's locale is Swiss
German (de_CH) then it will:
- Return the
string from
ToyStoreResources_de_CH.properties file if it
exists, then
- Try finding it in the
ToyStoreResources_de.properties file if it exists,
then
- Return the string matching the key from
the default message resource file
Struts
infers the locale of the current browser user by looking at HTTP request header
properties that browsers send with each request, indicating an ordered list of
the user's preferred languages. Specifically, on each request through the
Struts RequestProcessor, the default implementation of
processLocale() method checks to see if the HTTP Session
attribute whose name is defined by the Action.LOCALE_KEY
constant is present. If the session attribute exists, processing continues. If
it does not exist, Struts infers the locale and saves it in that same session
attribute. The net result is that the browser user's language is determined
once per session by Struts.
Custom ADF Component Message Bundles
ADF entity objects and
view objects can have an optional, associated Java message bundle. This
component-specific message bundle stores locale-sensitive values of
attribute-level control hints like label, tooltip, format mask, and others, as
well as component-specific exception messages. The ADF design time wizards
automatically handle the maintenance of the component-specific message bundle
Java class for the default language. You are responsible
for creating translated message bundles with a locale-specific suffix on the
end of the name.
For example, for an entity object named
toystore.model.businessobjects.Account, the message bundle
will be named AccountImplMsgBundle.java and will look like
Example 19. Notice that for a component in package
toystore.model.businessobjects, the message bundles (as well
as the custom client interfaces) are created automatically in the
toystore.model.businessobjects.common
subpackage. This package naming scheme emphasizes the fact that message bundle
classes and client interfaces are common to both the
client tier and the server tier. All of the *Impl.java
classes and XML files in the toystore.model.* packages are
not shared by both tiers. Their use is restricted to the server tier. Allowing
the client or web tiers work only with interfaces (and a
minimal number of classes like message bundles) is a best-practice technique
that the ADF design time automatically encourages through consistent naming
patterns and built-in deployment packaging support.
Example 19: Default Version of the Account Entity Object's
Resource Bundle
package toystore.model.businessobjects.common;
import oracle.jbo.common.JboResourceBundle;
import oracle.jbo.*;
import toystore.fwk.exceptions.ErrorMessages;
// ---------------------------------------------------------------
// --- File generated by Oracle Business Components for Java.
// ---------------------------------------------------------------
public class AccountImplMsgBundle extends JboResourceBundle {
public AccountImplMsgBundle() {}
/**
* @return an array of key-value pairs.
*/
public Object[][] getContents() {
return super.getMergedArray(sMessageStrings, super.getContents());
}
static final Object[][] sMessageStrings = {
{"Country_Rule_0", "Invalid country code"},
{"Addr1_LABEL", "Street Address"},
{"Addr1_TOOLTIP", "Enter your street address"},
/* etc. */
{"Zip_LABEL", "Postal Code"},
{"Zip_TOOLTIP", "Enter your postal code"}
};
} |
In the Toy Store Demo, we've also added by
hand an extra entity-specific exception message to the two-dimensional array of
strings like this:
static final Object[][] sMessageStrings = {
:
{ErrorMessages.ENTITY_ALREADY_EXISTS,
"Another user has already chosen this name. Please try another."},
:
}
Just as the order of the string keys in the Struts
message resource properties files was not meaningful, the order here of the
{String,String}
elements in the Object array is not meaningful
either.
To create a translated version of this resource bundle,
for example for Italian, you would create the
AccountImplMsgBundle_it class in the
toystore.model.businessobjects.common
package. As a shortcut, you can:
- Copy
AccountImplMsgBundle.java to
AccountImplMsgBundle_it.java
-
Rename the class declaration in the new
AccountImplMsgBundle_it.java and make it extend the default
language message bundle class, changing:
public class AccountImplMsgBundle extends JboResourceBundle
to:
public class AccountImplMsgBundle_it extends AccountImplMsgBundle
-
Rename the default constructor in
the new AccountImplMsgBundle_it.java file from:
public AccountImplMsgBundle(){}
to:
public AccountImplMsgBundle_it(){}
- Edit the user-visible strings to be
in Italian.
This will give you a
translated message bundle like Example 20 Example 20: Italian Version of the Account Entity Object's
Resource Bundle
package toystore.model.businessobjects.common;
import oracle.jbo.common.JboResourceBundle;
import toystore.fwk.exceptions.ErrorMessages;
/**
* Italian translations of Account entity object control hints
* Traduzioni italiane dei control hint dell'entity object Account
*/
public class AccountImplMsgBundle_it extends AccountImplMsgBundle {
public AccountImplMsgBundle_it() {}
public Object[][] getContents() {
return super.getMergedArray(sMessageStrings, super.getContents());
}
static final Object[][] sMessageStrings = {
{"Country_Rule_0", "Codice di paese inesistente"},
{ErrorMessages.ENTITY_ALREADY_EXISTS,
"Un altro utente ha già scelto questo nome. Prova un altro."},
{"Addr1_LABEL", "Indirizzo"},
{"Addr1_TOOLTIP", "Inserisci il tuo indirizzo"},
/* ecc. */
{"Zip_LABEL", "CAP"},
{"Zip_TOOLTIP", "Inserisci il tuo codice di avviamento postale"}
};
} |
The message bundle classes extend the
oracle.jbo.common.JboResourceBundle framework base class so
the messages can inherit the message "merging" functionality. The merging
occurs when your message bundle's getContents() method
invokes super.getMergedArray(). This allows the translated
bundles to only include messages that need translating while other messages are
inherited from the superclass bundle. To insure that the message merging works
correctly, translated resource bundles should also include this overridden
getContents() method.
View objects also support
custom message bundles for storing locale-sensitive values of control hints.
For a view object named
some.pkg.ViewObjectName, the
custom message bundle will be named
some.pkg.common.ViewObjectNameRowImplMsgBundle.java.
Table 3 lists the components in the ADF Toy Store demo
that have associated custom message bundles.
Table 3: ADF Components With a Custom Message Bundle
| toystore.model.* Component |
Contains |
*.businessobjects.Account |
- Default labels and tooltips
for
Account attributes
Account-specific error message for the generic
EntityAlreadyExists custom exception in the
toystore.fwk.model.businessobjects package
- Custom error message for the
ListValidationBean rule attached to the Country attribute
which checks if the country code is one of the value codes from the
toystore.model.dataaccess.CountryList view object's default
rowset.
|
*.businessobjects.Item |
Indicates format mask for the
Listprice attribute and the default number formatter class
to use.
|
*.businessobjects.Orders |
Contains custom validation error message thrown by the
validateCreditCardExpiration() validation
method.
|
*.businessobjects.Signon |
- Default labels and tooltips
for
Signon attributes
Signon-specific error message for the generic
EntityAlreadyExists custom exception in the
toystore.fwk.model.businessobjects package
|
*.dataaccess.ShoppingCart |
Indicates format mask for the
Listprice and ExtendedTotal attributes
and the default number formatter class to use.
|
*.dataaccess.Accounts |
Control hints to hide Status and
Userid attributes.
|
Since the business service supporting the model layer could be deployed
remotely from the web tier that is accessing it, the model layer tracks its own
per-user-session notion of the current user's locale. The
ADFBindingFilter also performs a similar preferred language
inference to automatically set the current language context on the ADF binding
context. Each ADF data control then picks up its locale off that binding
context.
Using ADF, XSQL Pages, XSLT, and XML Schema Together
Oracle
XSQL Pages [19] is a flexible publishing platform for XML-based information
that comes with the free
Oracle XML Developers
Kit for Java [42]. You create server-side "datapage" templates out of XML
documents mixed with "action handler" tags which can assist in building all or
parts of your XML-based datapage. It comes with a number of built-in action
handlers for pulling XML content from SQL queries, stored procedures, web
services, and other sources, and easily combining them with
XSLT [43] transformations to produce
any kind data format required by the requesting user. The most typical formats
are HTML, XML, or Text, but it also supports producing,PDF (when used in
combination with Apache FOP [44]),
SVG, WML, and others.
Custom XSQL Action Handler for ADF Iterator Bindings
When
one of the built-in action handlers doesn't fit the bill, you can
write your own
custom action handlers [45] to assist the publishing engine in getting XML
data or performing other programmatic tasks as part of the XSQL page template
processing.
Since Oracle ADF business components feature automatic,
bidirectional support for working with XML messages, we can easily conjure up a
custom XSQL action handler to allow an Oracle XSQL Page to include XML data
from an ADF view object. Example 21 shows the code
required to accomplish this.
Example 21: Custom XSQL Action Handler for ADF View Object Iterators
package toystore.fwk.xsql;
// imports removed for brevity
public class ADFViewObject extends XSQLActionHandlerImpl {
public void handleAction(Node result) throws SQLException {
XSQLPageRequest req = getPageRequest();
if (req.getRequestType().equalsIgnoreCase("servlet")) {
XSQLServletPageRequest xsqlHttpReq = (XSQLServletPageRequest) req;
HttpServletRequest servletReq = xsqlHttpReq.getHttpServletRequest();
DCBindingContainer bc = (DCBindingContainer) servletReq.getAttribute("bindings");
if (bc != null) {
Element action = getActionElement();
String iteratorBinding = getAttributeAllowingParam("iterator", action);
DCIteratorBinding iter = bc.findIteratorBinding(iteratorBinding);
DCDataControl dc = iter.getDataControl();
if (dc instanceof DCJboDataControl) {
ViewObject vo = iter.getViewObject();
if (vo != null) {
Node n = vo.writeXML(-1, XMLInterface.XML_OPT_ALL_ROWS);
((XMLDocument) result.getOwnerDocument()).adoptNode(n);
result.appendChild(n);
}
}
}
}
}
} |
| NOTE: |
In this release, the only kinds of data
collections that support XML reading and writing are those created using ADF
View Objects. Accordingly, the code tests to make sure that the data control
related to the iterator whose name you specify is ADF Business Components-based
Data Control (DCJboDataControl), that is, one that is based
on an ADF Application Module. In the future, we plan to support
readXML() and writeXML() on ADF data
collections of all kinds.
|
This custom action handler can
then be used inside an XSQL page template using the syntax:
<xsql:action handler="toystore.fwk.xsql.ADFViewObject" iterator="YourIteratorName"/>
We'll see a couple of interesting uses of this handler
in the next couple of sections.
Designing and Serving Schema-Compliant XML Datagrams
Let's
say we wanted to allow a user to request a review of their order in XML format.
This might be used as part of some workflow automation process so that the
order that has been placed can be audited automatically. Generally when two
programs need to exchange XML, they first agree on an XML Schema that describes
the structure of the XML to be exchanged, and then at runtime they exchange
documents that comply with that schema.
Using the JDeveloper 10g
XML Schema Designer, I designed the "Toystore Orders" XML Schema that you see
in Figure 57. This allowed me to create the XML schema
with very little knowledge of the low-level details of XML Schema
itself.
Each of the rounded boxes represents an XML element in the
schema. Attributes, like the id attribute of an Order, appear nested within
their element's box. When an element needs to contain a sequence of
subelements, I dragged and dropped a sequence connector
from the component palette onto the
diagram. I defined some custom types like PersonType and
ItemType which then I can reuse in other parts of the schema
as I've done with the OrderedBy element (reusing
PersonType) and the Line element (reusing
ItemType).
 Figure 57: Toystore
Order Schema in JDeveloper XML Schema Designer
To serve up an XML
datagram for my order review that complies with this XML schema, I did the
following:
- Created a
/revieworderxml DataPage in the Struts Page Flow
Diagram
- Double-clicked on this new DataPage
node and specified
revieworderAsXML.xsql as the name of the
view-layer page.
- Clicked again on the
/revieworderxml DataPage in the Struts Page Flow Diagram and
then click on the UI Model tab of the Structure Pane to see <No
Bindings>
- Right-moused on
<No Bindings> and selected Create UI
Model.
- Right-moused on the
binding container node just created and selected Create Binding
> Data > Iterator to create an iterator binding for the
ReviewOrder data collection in the ToyStoreService data
control.
-
Added an
<xsql:action> tag to the XSQL page template to use the custom
action handler we created in the previous section, indicating the name of the
iterator I want to use like this:
<Page xmlns:xsql="urn:oracle-xsql">
<xsql:action handler="toystore.fwk.xsql.ADFViewObject"
iterator="ReviewOrderIterator"/>
</Page>
The underlying
ReviewOrder view object component has a query that is
defined to use a bind variable. We need to set the bind variable using an
overridden initializeModelForPage() method as we've done in
previous examples. Therefore, I customized the DataForwardAction class for the
/revieworderxml to use the
ReviewOrderAction class that you see in the demo. It looks
like this:
package toystore.controller.strutsactions;
import oracle.adf.controller.struts.actions.DataActionContext;
/**
* ADF DataAction for the "/revieworderxml"
* and "/revieworder" action mapping (data page).
*
* @author Steve Muench
*/
public class ReviewOrderAction extends ToyStoreServiceDataForwardAction {
/**
* Model initialization logic for this page.
*/
protected void initializeModelForPage(DataActionContext ctx) {
String id = ctx.getHttpServletRequest().getParameter("id");
getToyStoreService(ctx).prepareToShowReviewOrder(id);
}
/**
* Illustrate using an alternative mechanism, implemented in the
* base ToyStoreServiceDataForwardAction class, to release all
* data controls in use by the current binding container in
* stateless mode.
*/
protected boolean releaseStateless() {
return true;
}
}
If we were to access the
/revieworderxml datapage at this stage using a URL
like:
http://localhost:8988/ADFToyStore/revieworderxml.do?id=1001
we would see results like that you see in
Example 22, reflecting the canonical XML format produced by
the ADF view object.
Example 22: XSQL DataPage Showing
Canonical XML View Object XML Format
<Page>
<ReviewOrder>
<ReviewOrderRow>
<Orderid>1001</Orderid>
<Orderdate>2004-06-15</Orderdate>
<Totalprice>19.98</Totalprice>
<Userid>j2ee</Userid>
<Firstname>Jay</Firstname>
<Lastname>Tooey</Lastname>
<ReviewLineItems>
<ReviewLineItemsRow>
<Itemid>EST-27</Itemid>
<Name>White Dice</Name>
<Quantity>1</Quantity>
<Unitprice>3.99</Unitprice>
<Extended>3.99</Extended>
</ReviewLineItemsRow>
<ReviewLineItemsRow>
<Itemid>EST-18</Itemid>
<Name>Apollo-13 Rocket</Name>
<Quantity>1</Quantity>
<Unitprice>15.99</Unitprice>
<Extended>15.99</Extended>
</ReviewLineItemsRow>
</ReviewLineItems>
</ReviewOrderRow>
</ReviewOrder>
</Page> |
To transform this into the format expected by
our XML Schema "contract", we need to create an XSLT stylesheet like the one
shown in Example 23 which transforms the above
canonical XML format into the "Toystore Orders" XML Schema-compliant
syntax.
Example 23: XSLT Transformation
Converts Canonical XML to Comply with Schema
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="urn:oracle-toystore-order"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:template match="/">
<xsl:apply-templates select="Page/ReviewOrder/ReviewOrderRow"/>
</xsl:template>
<xsl:template match="ReviewOrderRow">
<Order id="{Orderid}" xsi:schemaLocation="urn:oracle-toystore-order
schemas/Order.xsd" >
<OrderDate><xsl:value-of select="Orderdate"/></OrderDate>
<OrderTotal><xsl:value-of select="Totalprice"/></OrderTotal>
<OrderedBy>
<GivenName><xsl:value-of select="Firstname"/></GivenName>
<FamilyName><xsl:value-of select="Lastname"/></FamilyName>
</OrderedBy>
<Lines>
<xsl:apply-templates select="ReviewLineItems/ReviewLineItemsRow"/>
</Lines>
</Order>
</xsl:template>
<xsl:template match="ReviewLineItemsRow">
<Line id="{position()}">
<ItemId><xsl:value-of select="Itemid"/></ItemId>
<Description><xsl:value-of select="Name"/></Description>
<Quantity><xsl:value-of select="Quantity"/></Quantity>
<UnitPrice><xsl:value-of select="Unitprice"/></UnitPrice>
<LineTotal><xsl:value-of select="Extended"/></LineTotal>
</Line>
</xsl:template>
</xsl:transform> |
Finally, we need to augment our XSQL page
template to engage this XSLT stylesheet to transform the XSQL datapage before
returning to the client. This entails adding one extra line to the top of the
revieworderAsXML.xsql page that looks like this:
<?xml-stylesheet type="text/xsl" href="revieworderASXML.xsl"?>
With that additional line in the template, then
requesting the XML order review produces the schema-compliant datagram like
this:
<Order id="1001" xsi:schemaLocation="urn:oracle-toystore-order schemas/Order.xsd"
xmlns="urn:oracle-toystore-order"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<OrderDate>2004-06-15</OrderDate>
<OrderTotal>19.98</OrderTotal>
<OrderedBy>
<GivenName>Jay</GivenName>
<FamilyName>Tooey</FamilyName>
</OrderedBy>
<Lines>
<Line id="1">
<ItemId>EST-27</ItemId>
<Description>White Dice</Description>
<Quantity>1</Quantity>
<UnitPrice>3.99</UnitPrice>
<LineTotal>3.99</LineTotal>
</Line>
<Line id="2">
<ItemId>EST-18</ItemId>
<Description>Apollo-13 Rocket</Description>
<Quantity>1</Quantity>
<UnitPrice>15.99</UnitPrice>
<LineTotal>15.99</LineTotal>
</Line>
</Lines>
</Order>
Transforming
the XML Using XSLT
After placing an order at the ADF Toy Store
web site the user sees a link that they can bookmark showing their order
review. We've designed this /revieworder page as a DataPage
related to an XSQL template similar to the above
/revieworderxml action. Since the model layer setup was
identical to the XML-flavored order review above, we are reusing the same
ReviewOrderAction class that we studied above. Rather than
double-clicking on the /revieworder DataPage in the Struts
Page Flow diagram to create a new action class, instead I just used the
Property Inspector to set the value of its type property to
toystore.controller.strutsactions.ReviewOrderAction, the
same class we used above.
The revieworder.xsql
template is the same as the one used above, with one exception: the name of the
XSLT stylesheet is different.
<?xml-stylesheet type="text/xsl" href="revieworder.xsl"?>
<Page xmlns:xsql="urn:oracle-xsql">
<xsql:set-stylesheet-param name="orderId" value="{@id}"/>
<xsql:action handler="toystore.fwk.xsql.ADFViewObject"
iterator="ReviewOrderIterator"/>
</Page>
With this in place, clicking on the link from the ADF
Toy Store "Thank You" page after placing an order will show you the
XML/XSLT-based Order Review that you see in Figure 58.
You can study the revieworder.xsl stylesheet in the
./WEB-INF/xsql directory to see how a stylesheet that
produces HTML is just as simple as using it to transform XML into a different
XML format. Remember that the XML document that it is transforming is the
canonical XML produced by the ADF view object that we saw back in
Example 22.
 Figure 58: Overview
of Your Order Built Using ADF and XSQLImplementing the View Layer Using ADF
UIX
| NOTE: |
While implementing a work-alike version of the ADF Toy
Store user interface with ADF UIX, we we're able to show off all of the rich,
built-in controls that ADF UIX provides. For a comprehensive example of some of
these more sophisticated components, see
Building
J2EE Applications with Oracle JHeadstart for ADF [46] which exercises a lot
of the different ADF UIX components as part of the tutorial.
|
Overview
ADF UIX is an XML-based
alternative to JavaServer Pages and one of the plugable view-layer technologies
you can use when building web applications with the Oracle ADF framework. It
offers a declarative approach for describing web pages as a set of data-bound
UI components and supports "skinnable" rendering of the same pages using
rendering pages ADF UIX supports a variety of clients, including HTML-compliant
browsers and mobile devices. ADF UIX includes a rich set of nearly 100 built-in
user interface components — such as LOV, Table, hGrid, Color Picker, Calendar,
and many others — that you can use and customize.
Similar to the
ADF Business Components technology we've explored for the model layer in
sections above, ADF UIX is a proven view-layer technology that has been used
for years inside Oracle by in the Oracle e-Business Suite teams to build web
applications with a sophisticated, interactive, and consistent user interface.
Oracle's ADF UIX engineers and architects have participated in the Java
Community Process expert group for the JavaServer Faces standard and heavily
influenced its design based on their years of experience in having built and
enhanced Oracle ADF UIX. It should come then as no surprise that the JavaServer
Faces standard is architected very similarly to Oracle ADF UIX which predated
it by many years.
| NOTE: |
Oracle's JSF-compliant set of web
components called "ADF Faces" that provide the same set of functionality as the
ADF UIX components will be a part of the upcoming major 10.1.3 release of
Oracle JDeveloper 10g. A migration tool to convert ADF UIX pages to JavaServer
Faces JSP pages using the equivalent ADF Faces components is planned to be
available in the future.
|
Shared Controller, Model, and Framework Extension Layers
They use the same controller, model, and framework extension layers as
the JSP-based view layer in the ToyStoreViewcontroller
project. There's only one key difference between the JSP-based implementation
and the ADF UIX-based implementation: the web pages are
*.uix files instead of *.jsp files. Since
the ADF data binding metdata files — representing the binding container for
each page — are an XML file whose name reflects the name of and directory
containing the web page to which they correspond, another minor difference is
simply in the names of the *UIModel.xml binding files. Other
than the differences in file name, the logical contents of the data binding
metadata for JSP-based and UIX-based pages should be virtually
identical.
Everything else about the demo is shared as you would
hope.
Running the ADF UIX View
Layer
The ADF UIX pages that comprise the UIX front-end for the
demo are the *.uix files in the
ToyStoreViewControllerUIX project. You'll find them in the
WEB-INF/uix subdirectory under the Web
Content folder in the Application Navigator. To run the ADF UIX
version, simply run this project inside JDeveloper 10g. Your default browser
will launch and browse the URL:
http://localhost:8988/ADFToyStoreUIX/index.jsp
which is registered as the default run target for that project. From that
point, the demo looks and behaves like its JSP-based counterpart in the
ToyStoreViewController project that we've studied up to this
point in this paper.
Visual
Page Editing for ADF UIX Pages
JDeveloper 10g features a WYSIWYG
visual page designer for ADF UIX pages, which is what we used to create the set
of UIX pages that comprise the demo. Since the ADF UIX pages are well-formed
XML files whose valid contents are described by an XML Schema, the visual
editor is able to give you more structured, visual feedback of which components
make sense where in the page.
ADF UIX Supports ADF Data Binding
ADF UIX supports the
same ADF databinding layer that we've used above in our JSP-based view layer.
Just as in the JSP version of the view layer, we leverage the standard EL
expression language to identify the ADF binding objects that handle the
connection between the UI controls and the backend data. UIX pages that use the
EL expression language for binding indicate this fact using the
expressionLanguage="el" attribute in their root
<page> element like this:
<page xmlns="http://xmlns.oracle.com/uix/controller"
xmlns:ui="http://xmlns.oracle.com/uix/ui"
xmlns:ctrl="http://xmlns.oracle.com/uix/controller"
xmlns:html="http://www.w3.org/TR/REC-html40"
expressionLanguage="el">
:
<!-- page content here -->
</page>
This is the default approach that JDeveloper 10g release
10.1.2 uses when creating ADF UIX pages. In this section we highlight a few
examples of the kinds of binding expressions you'll find the ADF Toy Store UIX
pages.
Attribute Binding
Example
The principal way that you "wire up" a UIX component to a
backend data source is via the model attribute on the
component. In this example, the EL expression refers to an attribute binding
named Address in the current binding container for the
page.
<messageTextInput model="${bindings.Address}"/>
Table/Range Binding
Example
You'll see that the <table> components in
use in our pages refer to an ADF table/range binding in their model attribute's
EL expression in order to work with
N rows of data at a time. The
number of rows per page is controlled by the rangeSize
property of the ADF iterator binding with which the table/range binding is
associated in the binding metadata.
<table model="${bindings.ProductsInCategory}">
You'll also see an inline EL-function in use for table
column headers. This also makes reference to a table/range binding. The example
below from the yourcartUIX.uix page, is from the column
showing the quantity ordered.
<sortableHeader model="${ctrl:createSortableHeaderModel(bindings.ShoppingCart,'Quantity')}"/>
List Binding
Example
An ADF list binding encapsulates two "dimensions" of
back-end data binding:
- The attribute
representing the current, selected value in the list, and
- The collection of valid choices the user can pick
from.
In this example, we see that a
<messageChoice> component — which displayes as a dropdown list
with an associated prompt — refers to the Cardtype list
binding in its binding container with the EL expression
${bindings.Cardtype}. The childData
attribute of the control's <contents> contains a second EL
expression that refers to the displayData collection that
the list binding exposes. The contents element will "stamp out" a instance of
its nested content for each "row" in the childData
collection. On each iteration, the EL Expression
${uix.current} refers to the current object in the
collection. The net-effect in this example is that the list of valid choices is
produced using data supplied by the list binding object.
<messageChoice model="${bindings.Cardtype}"
prompt="${uix.data.nls['placeorder.cardtype']}"
disabled="false">
<contents childData="${bindings.Cardtype.displayData}">
<option model="${uix.current}"/>
</contents>
</messageChoice>
Binding Context
Example
Since errors are raised by the model in general, the model
attribute EL expression for the general-purpose <messageBox>
component just needs to refer to the ADF binding context:
<messageBox model="${data}"/>
Tips for Building
the UIX Pages
While building the UIX pages for the demo, we
followed a number of tips should prove useful to highlight.
-
Think Ahead About Disabled Users
As explained in the
Making
ADF UIX Pages Accessible [47] chapter of the
Oracle
ADF UIX Developer's Guide [48] ADF UIX already dramatically simplifies
building pages that are accessible to users with disabilities. In the Toy Store
Demo, as we were building our UIX pages, we also added an
<html:noscript> element to inform visually impaired users
using screen reader utilities that their browser must support javascript. If
you don't provide a "noscript" tag the browser will not display any message if
Javascript is disabled on the client's browser. Note that we specify the
html namespace prefix since <noscript> is
not a tag in the UIX namespace.
| TIP: |
While ADF UIX saves you from
having to code at the HTML level in most cases, it's handy to know that you may
add any legal HTML tag to a UIX page by prefixing it by the
html namespace. This HTML namespace gets defined for you at
the top of the UIX page when you create the page. |
-
Define a Data Provider for Translatable
Strings
For localization and internationalization
purposes, we can use *.properties-based resource strings in
the same was as we did in the JSP version of the view layer. In each page that
we created, we added a data provider to define where our string resources
reside using a snippet of UIX tags like this:
:
<content>
<dataScope xmlns="http://xmlns.oracle.com/uix/ui">
<provider>
<data name="nls">
<bundle class="toystore.view.ToyStoreResources"/>
</data>
</provider>
:
By using Expression Language (EL) instead of hardcoded
strings for strings properties such as in Page Titles, Button Text, Error
strings, etc, your application will be multi-lingual enabled. For example:
:
<head title="${uix.data.nls['index.title']}"/>
:
| NOTE: |
The ADF UIX documentation usually specifies the EL
expression convention for strings as follows: ${nls.title}
While this convention is correct, it doesn't support string keys whose names
themselves happen to have dots in them like "index.title".
In order to specify a string key name that includes a dot in the name, we use
the alternative EL syntax of:
${uix.data.nls['index.title']} |
For more
detailed information on UIX's multilingual capabilities see the
Developing
Multilingual J2EE Web Applications using Oracle JDeveloper 10g [49]
whitepaper on OTN.
-
Use the
PageLayout Templatized Component
To take advantage of
UIX's powerful "Look and Feel" functionality, we added a
<pageLayout> element to each page to structure the UIX page
into discrete and well defined rendering areas. The named children tags of the
<pageLayout> parent tag can contain distinct, logical bits of
content which we can rearrange in a global, consistent way using UIX's "Look
and Feel" capabilities. We can change the Look and Feel, or "skin", of an
application without changing the tags that define its page content. We explore
the ADF Toy Store look and feel we developed for the demo in the
The ToyStore ADF UIX Look and Feel section below.
To add a
<pageLayout> to any blank UIX page, open the page in the Page
Flow Diagram to edit, then with the design pane active, click on the center of
the page and right-mouse click, and select "Insert inside form - form0", and
choose pageLayout as illustrated in Figure 59.
 Figure 59: Inserting a PageLayout Component
| TIP: |
Using
JDeveloper 10g's Structure Window, you can see all of the logical "named
children" regions for the <pageLayout> tag and you can use the
right-mouse Insert inside... menu item to easily and accurately insert new
content into the right named child area. |
-
Implement Conditional Rendering with EL
Expressions
An example of where we needed to perform
conditional rendering was for the Global Buttons. These buttons are in the top
right side of the header area and include Cart, Login, Logout, Edit Account,
and Help. Which of the buttons render depends on whether the user is logged in
or not. We use an EL expression as the value of the
globalButton component's rendered
property to specify when it should render. As shown in
Figure 60, the "Edit Account" button has EL
defining that the button will render only when the
sessionScope variable named UserLoggedIn
is not empty. The result is that it will render only once
the user is logged in. We used this technique anywhere we needed conditional
rendering in our pages.
 Figure 60: Render
Edit Account Button Conditionally Using EL
-
Use the Data Control Palette to Drop DataBound
Controls
Much of the data displayed in our ADF Toy Store
UIX pages is in tabular format. Adding the tables was easy. As shown in
Figure 61, we just set the Drag and Drop
As poplist in the Data Control Palette to Read-Only
Table, found the appropriate data collection, and dragged it onto
the UIX page we were building.
 Figure 61: Dropping Databound Components from Data Control Palette
Once created, we
set the table properties to suit our needs for the page. For example, to avoid
having the "Select" column appear, we double clicked on it in the visual
editor, and set the "Advanced Property" rendered to
false. This will remove the selection column from our table.
Since we didn't need to offer table header column sorting for the
demo's functionality, we clicked on each header and set its sortable property
to no as shown in Figure 62.
| NOTE: |
These changes can also be made using the normal Property Inspector
without bringing up the modal properties dialog. |
 Figure 62: Disabling Sorting on a Table Column
In some cases we
needed to make a value in each row be a link instead of just a normal text
value. The ADF UIX editor allows us to right-mouse on a component and select a
Convert... option as shown in
Figure 63 to quickly change a display component into
a link, for example. We used this feature in numerous places while building the
pages.
 Figure 63: Converting Component Types
in UIX Visual Editor
To finish off the job,
after converting the text control to a link, we set the text and the link
destination properties. An example of showing this for a propduct name link is
shown in Figure 64.
 Figure 64: Setting Properties of Converted Link Component
-
Leverage MessageBox for Automatic Error Message
Display
ADF UIX provides built in exception and error
handling for message enabled components such as
messageTextInput, messageChoice, etc...,
when used in conjunction with the default messageBox
component. Anywhere we anticipated the possibility that error messages might
display, we've used this UIX messageBox.
<messageBox model="${data}"/>
Notice that like other UIX component we've discussed in
the data binding section above, it only needs an EL reference to a single ADF
binding layer object — in this case ${data} refers to the root ADF
BindingContext —and the UIX component handles the rest.
Try submitting an invalid user ID or password when signing in, or
invalid State / Country pairing when registering a new account or specifying
the shipping address to see how the ADF model layer error messages are
presented in the user interface.
The ToyStore ADF UIX Look and Feel
ADF UIX lets you change the appearance or look and feel of an application
without having to rewrite the UIX code that implements the application's user
interface. UIX provides two built in Looks and Feels (LAFs) —
blaf and minimal — which can be extended
for your custom applications. "BLAF" is an acroynm for the
Browser Look and
Feel [50] that Oracle e-Business suite follows. "Minimal" is a simpler
alternative that provides a basic look that tries to minimize the number of
downloaded images.
Running the ADF Toy Store demo's UIX view
controller layer using the default blaf Look and Feel, it
would look like what you see in Figure 65.
 Figure 65: ADF Toy Store Using BLAF Look and
Feel
Style sheets
provide a centralized mechanism for defining and altering the appearance of
pages separate from the content they contain. UIX Styles include an XML Style
Sheet Language (XSS) — based on Cascading Style Sheets (CSS) — for defining
environment-specific style sheets. In order to define a new ADF UIX look and
feel for the ADF Toy Store demo — also known as a "skin" — we needed to create
the following files:
-
toystorestyle.xss
An XML stylesheet
file that contains styles needed for our ToyStore ADF Skin. This xss file
extends the simple-desktop.xss file that is supplied with
ADF UIX as a base for such customizations.
-
pageLayout.uit
This
is the ToyStore ADF UIX pageLayout template that replaces the default renderer
for the ADF UIX pageLayout component.
-
sidBar.uit
This is
the ToyStore ADF UIX template that replaces the default renderer for the ADF
UIX sideBar component.
-
toystore-laf.xml
This
is the configuration file for the ToyStore ADF look and feel. In this file we
define what ADF look and feel to extend, what renderer to replace, etc.
With these files in place, in order to
get our application to use our new Toystore look and feel, we needed to edit
the uix-config.xml file in the WEB-INF
directory. The two key entries are:
-
Register the Toystore Look and Feel
<configurations xmlns="http://xmlns.oracle.com/uix/config">
:
<application-configuration>
<look-and-feels>
<look-and-feel-config>WEB-INF/toystore-laf.xml</look-and-feel-config>
</look-and-feels>
:
</application-configuration>
:
</configurations>
This will allow us to refer to the custom look and feel
as "toystore".
-
Select the Toystore Look and Feel as Default
<configurations xmlns="http://xmlns.oracle.com/uix/config">
:
<default-configuration>
:
<look-and-feel>toystore</look-and-feel>
:
</default-configuration>
</configurations>
With the ADF Toy Store look and
feel in place, the demo switches at runtime to look nearly exactly like the JSP
version, as shown in Figure 66.
 Figure 66: ADF Toy Store Look and Feel in
Action
If you
experiment by opening one of the ADF Toy Store demo's UIX pages in the ADF UIX
visual editor, you'll experience first-hand how it gives true WYSISYG (what you
see is what you get) feedback by showing you what the page will look like using
the default look and feel we've selected.
Customizing the Default Framework
Behavior
Two of the biggest benefits of framework-based J2EE
development are:
- Your application
components stand on the shoulders of the base framework
functionality
- When you need to make
application-wide changes, you can extend the base
framework
For example, if the base ADF
framework EntityImpl class does not support a feature that
you need all of your entity objects to have, not a
problem. No need to file an enhancement request with Oracle Corporation and
wait until the ADF Development team implements your desired feature for you.
Just take the bull by the horns and add the feature yourself by making a
framework customization.
Just create a Java class that extends
oracle.jbo.server.EntityImpl and add your additional
functionality into that class like this:
public class ToyStoreEntityImpl extends oracle.jbo.server.EntityImpl {
/*
* Any new or customized entity object behavior goes here
*/
}
Then, when you create your entity objects for your
application, just set up your components to extend from
ToyStoreEntityImpl instead of from the default
EntityImpl base class.
Figure 67 illustrates how this looks for one of
the entity objects in the ADF Toy Store demo like Account
which does exactly this.
 Figure 67: Application Components Can Extend a Customized Framework Base
Class
The same opportunity that is available for customizing ADF framework base
classes also exists for many aspects of the Struts framework, too. In this
section we highlight the ADF framework customizations that were made to support
the ADF Toy Store demo. They all live in the FwkExtensions
project.
| NOTE: |
As with all code in the ADF Toy Store demo, the
framework customizations are copiously commented to explain what's going on in
the classes, so please look into the code in the
FwkExtensions project for more details on what each
framework extension is doing.
|
ADF Framework Customizations for
the Controller Layer
In the
toystore.fwk.controller package we have the
ToyStoreDataForwardAction, which extends the base ADF
DataForwardAction to make the following
customizations:
-
Added two additional
DataAction lifecycle methods named initializeModelForPage()
and initializeBindingsForPage() which fire just before and
just after the default prepareModel() lifecycle method in
the situation when no "postback" events are being handled by the action. By
default they do nothing: they live to be overridden by subclasses.
initializeModelForPage() is
useful for calling custom methods to set bind variables in your business
service queries before the prepareModel() phase goes about
executing the iterators in your binding container. This setup could be done
with a separate DataAction having a separate binding container and a custom
method invocation association with it, however that meant that every page
requiring bind variable setup would have required an extra action in the page
flow model and I wanted to keep it as absolutely simple as
possible.
initializeBindingsForPage() can be used to
programmatically modify the values of bindings before the page has a chance to
see them.
- Added a
helper method
getApplicationModule() to retrieve an
ApplicationModule based on its Data Control name.
- Customized the default way that a "tree" of bundled ADF exceptions get
translated into Struts
ActionError objects for display to
the user by overriding the reportErrors()
method.
- Added a boolean method
releaseStateless() that can be overridden by a subclassing
action to return true in order to indicate that all data controls in use by the
current binding container should be released stateless at the end of the
request. By default, an ADF ApplicationModule will have its pending state
managed by the framework. There's also an alternative approach illustrated in
this class with the releaseDataControlStateless() method
that subclasses can explicitly call to release a data control by name in
stateless mode at the end of the request.
- Added an
evalEL() helper method that subclassing
actions can call to evaluate an EL expression.
- Added an
invokeEventAction() helper method that
subclassing actions can call in their onEventName event-handler methods to
carry out the default declarative before of invoking the action binding whose
name matches the name of the event being handled before or after writing other
custom code in the event handler method.
- Added
convenience methods to
findControlBinding(),
getBindingValue(), and setBindingValue().
These methods save a few lines of code requires to find control bindings, and
to get/set their value.
In the same
toystore.fwk.controller package, we also have the
ToyStoreErrorHandler class. This class is installed as a
custom error handler by the ToyStoreDataForwardAction's
overridden method implementation of the DataAction's
handleLifecycle() method. The ADF error handler will be
notified any time an exception is thrown by the ADF binding layer. The one
customized feature that we've implemented here is to disable the appending of
product codes in the JboException objects (as well as in any
nested JboException's they may contain due to the ADF "bundled exception"
feature. By calling the JboException's method
setAppendCodes(false), when the error messages are displayed
as strings they will not include the
JBO-NNNNN product code and error
number.
Lastly, in the toystore.fwk.controller
package we also have the ToyStoreInitModelListener class.
This class is referenced in the web.xml file in the
ToyStoreViewControllerUIX project's
public_html/WEB-INF directory. This class extends the base
UIX InitModelListener class and adds a tiny bit of code that
converts the Struts ActionError objects into the format that
ADF UIX expects for presentation in its <messageBox>
component.
ADF
Framework Customizations for the Model Layer Business Objects
In
the toystore.fwk.model.businessobjects package, we created
the ToyStoreEntityImpl class that extends the base ADF
entity implementation base class to add the following features:
- Declarative ability to force attribute values to
|