| J2EE & Web Services
Building XML-Enabled JavaServer Faces Applications
By Yuli Vasiliev
Learn how to build a JavaServer Faces application that interacts with Oracle XML DB.
JavaServer Faces (JSF) technology is a new server-side
user interface (UI) component framework that is quickly becoming the
standard Web-application framework for J2EE applications. The big
advantage of JSF technology is that it enables Web developers to
apply the Model-View-Controller (MVC) principles, thus achieving a
clean separation between the model and presentation layers of a Web
application. Practically, such a separation allows developers to take
advantage of the division of labor—Java developers can
concentrate on the application back-end and integration code, and
page authors, who need not have any programming expertise, can focus
on designing the Web pages of the application, laying out the UI
components on the pages and wiring them to the application back-end
objects. Obviously, this approach makes it easier to develop
more-flexible, -scalable, and -maintainable solutions. For example,
adding XML support to an existing JSF application most likely will
not require making any changes to the JSF pages of that application.
Instead, only the application back-end needs to be revised.
While the best way to learn new technologies is to use
them to solve a real problem, in this article I will explain how to
develop a simple JSF application that works on the idea of XML and
interacts with Oracle XML DB,
a feature of Oracle Database that enables high-performance native
storage and retrieval of XML content. Proceeding to the sample that
accompanies this article, you will start by developing a simple
newsletter application that is based on the JSF framework and
originally doesn't provide any XML support. Once you have a
non-XML-enabled JSF application, I'll focus on how to add XML
functionality to it. The main goal here is to demonstrate different
ways in which you can add XML functionality to an existing JSF
application.
Requirements
As a JSF application is
essentially a standard Java Web application running on a Web
container that must support Servlet 2.3 and JSP 1.2 (or higher), this
article assumes that you already have an installed version of a Web
container, such as Oracle Application Server Containers for J2EE
(OC4J) 10g 10.1.2 or Tomcat 5.0. Another software package
required to develop JSF applications is the JSF Reference
implementation. Fortunately, Oracle
JDeveloper, starting with release 10.1.3, comes with built-in support
for JSF. At the time of this writing, Oracle JDeveloper 10.1.3
Developer Preview is available for download. This preview not only
includes the JSF Reference implementation bundled with JDeveloper IDE
but also provides the developer with a rich set of tools designed to
simplify development of JSF applications. These tools include a
JSF-enabled JSP Visual Editor that renders the JSF components in the
visual editor and a JSF page flow diagrammer that allows you to
visually design a JSF navigation model.
Developing JSF Applications with JDeveloper
In spite of the fact that you can
produce all the JSF pages, Java source code, and configuration files
for the sample application by using a simple text editor, such as
Notepad or Vi, and then compile the Java sources from a command-line
prompt by using the javac compiler, this article assumes that you
will use Oracle JDeveloper 10g release 10.1.3 or later to
handle all these tasks. Generally, there are a lot of reasons why you
might want to choose JDeveloper 10g as your development tool
for building a JSF application that interacts with the database.
First of all, using JDeveloper 10g will allow you to take
advantage of a wide range of tools that can be used for developing,
debugging, testing, tuning, and deploying your JSF application.
Moreover, JDeveloper 10g is optimized to assist developers in
producing a multitier architecture for database applications. The
latter is especially useful when you're developing complex JSF
applications—recall that one of the key advantages of using JSF
technology is the ability to organize Web applications along the MVC
architecture, which allows developers to separate the presentation
from the business logic of an application.
Note, however, that the focus of this article is not on
how to use JSF with JDeveloper. (Many other resources are available for that purpose.) Clearly, using JDeveloper can help
you simplify and speed up the development process for building the
sample application that accompanies the article. Nevertheless, to
build the sample, you can still do without JDeveloper.
Building a Simple JSF Application Without XML
Support
This section walks you step by step through the process
of building a JSF application organized along the MVC architecture.
When you've completed the section, you will have a simple
newsletter application (actually, it doesn't send the
newsletters but lets the user subscribe) that originally doesn't
provide XML functionality. The next sections will show you how to add
XML functionality to that JSF application.
Developing model beans.
This subsection focuses on developing the model objects, whose
purpose is to handle application data, including moving data in and
out of the database. In particular, you'll see how to create
the model beans that are meant to hold and manipulate the login and
subscriber information, respectively. However, as you will learn in
the next subsections, the application doesn't use these model
objects directly. Instead, it uses the LoginBean
and SubscriberBean backing
beans that extend the Login
and Subscriber model
beans, respectively, allowing for greater separation of the model
layer from the presentation layer. The LoginBean
and SubscriberBean backing
beans are discussed in the “Developing Backing Beans” subsection later in this section.
Now that you have an idea of what you
will be building, let's get started. If you're using
Oracle JDeveloper, you start by creating a new application. Name the
application jsfsubscr,
specify the same name for Application
Package Prefix, and leave Directory
Name at its default setting. For Application
Template, choose Web
Application [JSF, JSP, EJB] (this template is available in
Oracle JDeveloper starting with release 10.1.3). As a result,
JDeveloper will create the jsfsubscr
application that consists of two projects, namely Model
and ViewController. You
use the Model project for
the model objects that will contain the business logic for the
application. In contrast, the ViewController
project is for the JSP pages, configuration files, backing beans, and
other components that make up the presentation layer and define
application behavior.
Before you
proceed with the jsfsubscr
application, make sure to specify that the ViewController
project depends upon the Model
project. To do this, open the Project
Properties\Dependencies dialog for the ViewController
project and click the Model.jpr
checkbox.
You start out with a Login
class in the Model
project. This model bean is intended to hold the user credentials:
package jsfsubscr.model;
public class Login {
private String email; private String password;
public String getEmail() { return email; } public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
To hold the information submitted by
a subscriber, the application will use a Subscriber
class that extends Login:
package jsfsubscr.model;
public class Subscriber extends Login {
private String name; private String phone; private boolean subscribed;
public String getName() { return name; } public void setName(String name) { this.name = name; }
public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; }
public boolean isSubscribed() { return subscribed; } public void setSubscribed(boolean subscribed) { this.subscribed = subscribed; }
}
Accessing the
database. Another important business object used in the sample
application is the DBUtils
data access object, which provides methods for accessing and
manipulating the application data stored in the database. You
also create this class in the Model
project. Of course, you could add the database code directly
into the model beans discussed above. However, it is always a good
idea to have a separate layer for business objects intended to
communicate with a database. Consider the DBUtils
class shown in Listing 1 (in sample code download).
Note that each public method of the
DBUtil class uses the
throws clause to declare
the exception that the method explicitly throws using the Java throw
statement. If an exception occurs, the method simply rethrows it to
the next-higher-level exception handler defined in the calling code.
A new exception is created with the original cause attached, thus
allowing you to rethrow the exception to the calling code without
losing the original cause of the exception. Since the methods of
DBUtil simply rethrow
exceptions, taking no further action, you might want to know where
the rethrown exceptions are handled. This will become clear when you
start developing the application backing beans discussed in the
“Developing Backing Beans” subsection. In particular,
you'll learn that UI-specific code needs to know the details of
the failure in order to return an appropriate result string to the
navigation handler.
Looking through the code shown in
Listing 1, you may notice that all the SQL statements used there
query the only database table, namely subscriber.
Make sure to create the subscriber
table in the database as follows:
CREATE TABLE subscriber ( email VARCHAR2(50) PRIMARY KEY, password VARCHAR2(30), name VARCHAR2(30), phone VARCHAR2(15), subscribed NUMBER(1) )
Before you begin to use the DBUtils
class, you have to create a getDBUtils
public method that will return an instance of that class to the code
that uses the class methods. Actually, you might create the
getDBUtils method within
the DBUtils class itself,
as well as declare a private static property there to hold an
instance of the class. Alternatively, you might put the getDBUtils
method in a separate Utils
class in the Model
project, as shown below:
package jsfsubscr.model;
public class Utils {
private static DBUtils dbUtils;
public synchronized static DBUtils getDBUtils() throws Exception{ if (dbUtils == null) try { dbUtils = (DBUtils)Class.forName("jsfsubscr.model.DBUtils").newInstance(); } catch (Exception e) { throw new Exception (e.getMessage()); } return dbUtils; } }
Now that you have created a data
access object that provides the necessary methods for accessing and
manipulating data stored in the database and defined a method that
will return an instance of that data access object, you can turn back
to the model objects discussed earlier and enhance their
functionality by adding the methods that will operate on the data.
You start by adding a doLogin
method to the Login model
bean. Consider the following doLogin
method:
public void doLogin() throws Exception { try { subscriber = Utils.getDBUtils().select(this.email); if(!password.equals(subscriber.getPassword().trim())) throw new Exception("Wrong Password"); } catch (Exception e) { throw new Exception(e.toString()); } }
Also, make sure to add the subscriber
property to the Login
class:
private Subscriber subscriber;
Next, create the get accessor to
allow the calling code to access the subscriber
property:
public Subscriber getSubscriber() { return subscriber; }
As you can see, the subscriber
property is used to hold the user information retrieved from the
database. Note that you don't need to define the set accessor
for the subscriber
property, because that property is supposed to be written to only
within the doLogin method
but never in the code that will use the Login
class. That's why it is best to give only read access to the
subscriber property.
Another important thing to note about the doLogin
method is that this method focuses entirely on the database and
doesn't contain any UI-specific code.
Next, turning back to the Subscriber
class, add the following two public methods to the class:
public void save() throws Exception { try{ Utils.getDBUtils().insert(this); } catch (Exception e) { throw new Exception(e.toString()); } } public void remove() throws Exception { try{ Utils.getDBUtils().delete(this); } catch (Exception e) { throw new Exception(e.toString()); } }
As you can see, neither of the above methods contains
any UI-specific code. You might be wondering why we needed the sample
application to achieve such a clear separation between its layers. In
fact, this separation will make it easier to demonstrate which layers
of the application really need to be modified and which layers can
stay intact when you begin XML-enabling the application.
Developing
backing beans. This subsection focuses on the backing beans you
will use in the sample application. The next subsection illustrates
how to configure the application to use the backing beans discussed
here.
The most important features of a
backing bean are the properties and methods associated with the UI
components used on the page. For the sample application, you don't
need to create backing beans from scratch. Instead, you extend the
model beans discussed earlier with UI-specific code. Move
on now to the ViewController
project and create the LoginBean
backing bean extending the Login
model bean as follows:
package jsfsubscr.view;
import java.util.logging.Level; import java.util.logging.Logger; import javax.faces.application.FacesMessage; import javax.faces.context.FacesContext; import javax.faces.el.ValueBinding; import jsfsubscr.model.Login;
public class LoginBean extends Login { public LoginBean() { } public String login() { FacesContext ctx = FacesContext.getCurrentInstance(); ValueBinding bnd = ctx.getApplication().createValueBinding("#{subscr}"); bnd.setValue(ctx, null); try { doLogin(); } catch (Exception e) { Logger.getLogger("jsfsubscr.view").log(Level.SEVERE, e.getMessage(), e); ctx.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, e.getMessage(),"")); return "loginFailure"; } return "loginSuccess"; } }
As shown here, the LoginBean
bean extends the Login
bean with a single method, namely login.
As you will learn later in the “Designing JSF Pages” subsection, the login
method is an action method and is called when a user clicks on the
Login button on the login.jsp page. The first thing the login
method does is to set the subscr
managed bean to null, because it may hold the information about the
previous user. (As you will learn in the “Using the Managed
Bean Facility” subsection, the subscr
managed bean represents a SubscriberBean
object that is used in the application to hold and manipulate the
current user's information.) Next, the login
method calls the doLogin
method and returns a logical outcome string for the navigation
handler. In general, the navigation handler is responsible for
choosing the next JSF page to be displayed based on either the
logical outcome of an action method in a backing bean or the
hardcoded outcome that the action attribute of a UICommand component
defines. In the example, the login
action method generates a logical outcome string depending on whether
the username and password are legitimate.
Next, in the ViewController
project you create the SubscriberBean
that extends the Subscriber
model bean discussed previously:
package jsfsubscr.view;
import java.util.logging.Level; import java.util.logging.Logger; import javax.faces.application.FacesMessage; import javax.faces.context.FacesContext; import jsfsubscr.model.Subscriber;
public class SubscriberBean extends Subscriber { public SubscriberBean() { } public String unsubscribe() { try { remove(); } catch (Exception e) { Logger.getLogger("jsfsubscr.view").log(Level.SEVERE, e.getMessage(), e); FacesContext ctx = FacesContext.getCurrentInstance(); ctx.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, e.getMessage(),"")); return "unsubscrFailure"; } return "unsubscrSuccess"; } public String subscribe() { try { save(); } catch (Exception e) { Logger.getLogger("jsfsubscr.view").log(Level.SEVERE, e.getMessage(), e); FacesContext ctx = FacesContext.getCurrentInstance(); ctx.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, e.getMessage(),"")); return "subscrFailure"; } return "subscrSuccess"; } }
The backing beans discussed here extend the model beans
with UI-specific code. Of course you might do without model beans at
all, encapsulating all the business logic—including database
access calls—in backing beans. However, this would violate the
separation of presentation and business logic, making the code less
maintainable.
Using the
managed bean facility. Thankfully, you don't need to write
code responsible for creating backing bean instances—JSF
provides an efficient mechanism, called the managed bean facility,
that makes the beans available to the application when the UI
components reference them. You configure the managed bean facility in
the application configuration file, normally named faces-config.xml.
This file is also used to define converters, validators, and
navigation rules (among other JSF application elements) for the
application to use. In Oracle JDeveloper 10.1.3, the faces-config.xml
configuration file is automatically created in the ViewController
project when you create a new application based on the Web
Application [JSF, JSP, EJB] template. Moreover, JDeveloper
provides you with the graphical interface for specifying managed
beans and navigation rules in the faces-config.xml file.
Now add the managed bean declarations
for SubscriberBean and
LoginBean to the
faces-config.xml file as follows:
<faces-config xmlns="http://java.sun.com/JSF/Configuration"> ... <managed-bean> <managed-bean-name>subscr</managed-bean-name> <managed-bean-class>jsfsubscr.view.SubscriberBean</managed-bean-class> <managed-bean-scope>session</managed-bean-scope>
<managed-property> <property-name>email</property-name> <value>#{login.subscriber.email}</value> </managed-property> <managed-property> <property-name>password</property-name> <value>#{login.subscriber.password}</value> </managed-property> <managed-property> <property-name>name</property-name> <value>#{login.subscriber.name}</value> </managed-property> <managed-property> <property-name>phone</property-name> <value>#{login.subscriber.phone}</value> </managed-property>
</managed-bean> <managed-bean> <managed-bean-name>login</managed-bean-name> <managed-bean-class>jsfsubscr.view.LoginBean</managed-bean-class> <managed-bean-scope> session</managed-bean-scope> </managed-bean> ... </faces-config>
Note that the subscr
bean contains four managed-property elements. Each of them uses a
value-binding expression inside its value element to associate the
corresponding property of the subscr
bean with the appropriate property of the Subscriber
object, which itself is a property of another bean, namely login.
In fact, this example illustrates a standard mechanism that is
normally used in JSF applications to chain beans together.
Configuring
navigation rules. As I mentioned previously, the navigation handler is
responsible for selecting the next JSF page to be displayed based on
either the logical outcome of an action method in a backing bean or
the outcome that the action attribute of a UICommand component
defines. In either case, however, the action outcome must match an
outcome in a navigation rule defined in the application configuration
file. The navigation rules for the sample application are shown in
Listing 2 (sample code download).
Looking through the navigation rules
shown in Listing 2 (Oracle JDeveloper 10.1.3 includes a JSF
navigation model diagrammer, in fact), you may notice that the
presentation layer of the sample application contains only three
pages: login.jsp, welcome.jsp, and subscribe.jsp. The first page of
the sample application is login.jsp. On this page, the user can
either enter his credentials to log in or click on the subscription
link to move on to the subscribe.jsp page. Upon successful login the
user is taken to the welcome.jsp page, which displays a welcome
message, the user's subscription information, the logout link,
and the unsubscribe link that references the unsubscribe
method in the SubscriberBean.
Designing JSF
pages. Next, you create JSF
pages for the application in the ViewController
project. In Oracle JDeveloper 10.1.3, you can build a JSF enabled JSP
page by using the Create
JavaServer Faces (JSF) JSP wizard, which is invoked by
selecting File->New->Web-Tier->JSF->JSF JSP. This wizard
automatically creates the backing bean for the generated JSF page and
inserts the corresponding managed bean element in the
faces-config.xml file. But because you have already created backing
beans and configured the faces-config.xml file for the sample
application, you might want to invoke another JDeveloper's
wizard: Create JavaServer Pages
(JSP) by selecting File->New->Web-Tier->JSP->JSP.
This wizard also allows you to create a JSF JSP page -- but unlike
the former it doesn't automatically create the backing bean for
that page.
Because the login.jsp page is the first
page the user sees when the application starts up, you might
want to start designing JSF pages for the sample application with
this page. The source for the login.jsp is shown in Listing 3 (sample
code download).
You can see in the listing that the
login.jsp contains two forms. The first includes a commandLink tag
whose action attribute is set to subscribe.
The user clicks on this link to move on to the subscribe.jsp page.
The second is a login form, which includes the tags that represent
the fields for entering credentials as well as the commandButton tag
that represents the Login
button used to submit the form. Specifically, when the user clicks on
the Login button on the login.jsp page, the login
method of the LoginBean
instance is invoked.
As mentioned, the user is taken to the subscribe.jsp
page by clicking on the subscribe link on the login.jsp page. On the
subscribe.jsp page, the user must fill out all the fields and then
click on the subscribe button to save the information in the
database. Listing 4 includes the subscribe.jsp file.
Looking through the listing, you may
notice that the selectBooleanCheckbox tag contains the validator
attributethat refers to the subscribedCheck
method of the SubscriberBean
backing bean. This method simply checks whether the user has checked
the subscribed checkbox.
If the checkbox is checked, it takes no further action. Otherwise,
the subscribedCheck
method generates a new FacesMessage, then associates the
generated FacesMessage with the subscribed
component, and finally sets a false flag that indicates the value of
the component is not valid.
public void subscribedCheck(FacesContext context, UIComponent component, Object value) { if (value.toString()=="false") { FacesMessage message = new FacesMessage("You must check this box to subscribe"); message.setSeverity(FacesMessage.SEVERITY_ERROR); String componentId = component.getClientId(context); context.addMessage(componentId, message); ((EditableValueHolder) component).setValid(false); } }
Also, make sure to place the
following import statements after the existing import statements in
SubscriberBean.java:
import javax.faces.component.EditableValueHolder; import javax.faces.component.UIComponent;
Now you can get back to developing JSF pages. As
mentioned, upon successful login the user goes to the welcome.jsp
page, which displays the user's subscription information and
allows the user to cancel her or his subscription by clicking on the
unsubscribe link. Listing 5 (sample code) provides the source for the
welcome.jsp page.
Servlet
configuration. Finally, before you can deploy the sample
application inside your application server, you need to create a Web
application deployment descriptor. The fact is that JSF applications,
like all J2EE Web applications, are configured via elements contained
in a web.xml deployment descriptor. For a JSF application, the
deployment descriptor above all must specify the servlet that will
process JSF requests as well as the servlet mapping for the
processing servlet. Oracle JDeveloper 10.1.3 automatically generates
this file when you create an application based on the Web
Application [JSF, JSP, EJB] application template. In most
cases, you won't need to change something in the web.xml file
that JDeveloper generates. The web.xml deployment descriptor
for the sample application might look like sample code Listing 6.
To test out the sample application
from JDeveloper, you simply right-click the login.jsp page in the
Application Navigator
window and choose Run from
the context menu. As a result, JDeveloper runs the application on its
embedded OC4J server, loading the login.jsp page in your browser, as
shown in Figure 1.
Figure 1. The first page of the application
On the login.jsp page, you click the subscription link
to open the subscribe.jsp page in which you are supposed to enter
your credentials and additional information about yourself like that
shown in Figure 2.

Figure 2. Providing information for subscription
When you have submitted the information entered on the
subscribe.jsp page by clicking the Subscribe button, you are taken
back to the login.jsp page where you are supposed to enter your
credentials and click the Login button to login. Upon successful
login you will be taken to the welcome.jsp page, which displays a
welcome message, and the information that you provided when you
subscribed. The welcome.jsp page should look like that shown in
Figure 3.
Figure 3. Viewing the user's subscription
information
At this point, you have created a
simple JSF application that interacts with Oracle, moving relational
data in and out of the database. The next sections show how to
XML-enable this application so that it not only can talk XML to
Oracle XML DB but also itself can operate on XML data.
Moving an Existing JSF Application to XML
Now that you have a JSF application,
you might want to add XML functionality to it. Probably the first
question you might ask is, “What should I do to make my JSF
application talk XML to Oracle XML DB?” To start with,
you might simply rewrite the SQL queries used in the DBUtils
data access object, so that they can be used to access and manipulate
XML instead of relational data. You can do this easily because Oracle
XML DB provides the XML extensions of SQL that allow you to
operate on XML data with standard SQL commands such as SELECT,
UPDATE, and INSERT.
Designing XML
Database. Before you proceed with XML-enabling the sample
application, you need to create all the necessary database objects to
store information about subscribers in XML format. To start with, you
might want to create a subscriberXML
table of XMLType designed
to store subscriber records in XML format. It is important to note
that XMLType is the native
datatype used for storing XML data in Oracle Database. One way to
create an XMLType table is
by registering an annotated XML schema with the database. It is
assumed that you will use an XML schema annotated with information to
automatically generate object types and object tables. To register a
subscriber annotated XML
schema, you might use the PL/SQL code shown in sample code Listing 7.
The above XML schema defines the
structure of a subscriber XML document and is registered under the
URL http://localhost:8080/public/subscriber.xsd. (Note that Tomcat
users using the default port 8080 may face a port conflict here.)
When you register this schema against the database, Oracle will
automatically create the subscr_t
object type and subscriberXML
table of XMLType. Since
the subscriberXML table is
constrained to the above XML schema, only the subscriber
XML documents that conform to that XML schema can be stored in that
table. When a subscriber
XML document is validated against the schema, Oracle XML DB will
decompose the document contents and store them as an instance of
subscr_t in the
subscriberXML table. Note,
however, that Oracle XML DB will not perform a full XML schema
validation when you insert or update XML content stored in the
subscriberXML table.
Instead it will simply check whether the XML document conforms to the
object-relational storage. For example, if you try to insert a
subscriber XML document
whose password node contains a value larger than 30 characters,
Oracle XML DB will generate the following error message:
ERROR at line 1: ORA-30951: Element or attribute at Xpath /SUBSCRIBER/PASSWORD exceeds maximum length
But if you try to insert a subscriber
XML document whose password node contains a value consisting of a
single character, Oracle XML DB will have no objection. This is
despite the fact that defining a one-character password violates the
constraints defined in the subscriber
XML schema for the password node:
<xs:simpleType name="PasswordType"> <xs:restriction base="xs:string"> <xs:minLength value="2"/> <xs:maxLength value="30"/> </xs:restriction> </xs:simpleType>
As you see here, a value of the
password node cannot be
fewer than 2 characters according to the above constraints.
To avoid inserting invalid data, you
need to explicitly call the schemaValidate
member procedure of XMLType.
One way of doing this is to define a TRIGGER BEFORE INSERT on
the XMLType table, as
follows:
CREATE TRIGGER validSubscr BEFORE INSERT ON subscriberXML FOR EACH ROW DECLARE newdoc XMLType; BEGIn newdoc := :new.object_value; xmltype.schemavalidate(newdoc); END; /
If you try now to insert a subscriber
XML document whose password node contains a value consisting of a
single character, Oracle XML DB will generate the following error
message:
ERROR at line 1: ORA-31154: invalid XML document ORA-19202: Error occurred in XML processing LSX-00221: "q" is too short (minimum length is 2) ORA-06512: at "SYS.XMLTYPE", line 333 ORA-06512: at "SCOTT.VALIDSUBSCR", line 5 ORA-04088: error during execution of trigger 'SCOTT.VALIDSUBSCR'
As this code shows, storing the
application data in an XML Schema-based XMLType
table allows you to constrain the data according to the rules
specified in the XML schema. Although JSF has extensive support for
validation, using XML schema constraints can also be useful in some
situations. The fact is that the XML Schema specification offers
more-sophisticated validation capabilities than JSF. For example, you
might need to create a validator that ensures that the user has
entered reasonable values in the fields corresponding to the related
components. In fact, solving this problem by means of JSF can be
quite tricky, because the validation mechanism JSF uses is intended
to validate a single component. In contrast, XML Schema definition
language allows you not only to specify validation rules for single
elements but also to define uniqueness and reference constraints on
the contents of multiple elements.
Turning back to the subscriberXML
table that Oracle had to automatically create when you registered the
subscriber XML schema
discussed above, make sure to define the PRIMARY
KEY constraint on the EMAIL element to ensure that the value
of the EMAIL element is NOT NULL
and is unique across a set of XML documents stored in the
subscriberXML table. You
can do this with the following SQL command:
ALTER TABLE subscriberXML ADD constraint EMAIL_PRIMARYKEY PRIMARY KEY (xmldata."EMAIL");
Table altered.
Interacting
with Oracle XML DB. Now that you have created the database schema
to store subscriber XML
documents, you can return to the JSF application discussed in the
preceding section. As mentioned earlier, to make the application talk
XML to Oracle XML DB, you simply need to rewrite the SQL
queries used in the DBUtils
data access object. You might start by changing the sql string in the
select method of DBUtils
as follows:
String sql = "SELECT EXTRACTVALUE(object_value, '//EMAIL')," + "EXTRACTVALUE(object_value, '//PASSWORD')," + "EXTRACTVALUE(object_value, '//NAME')," + "EXTRACTVALUE(object_value, '//PHONE')," + "EXTRACTVALUE(object_value, '//SUBSCRIBED')" + "FROM subscriberXML WHERE EXTRACTVALUE(object_value, '//EMAIL')=" + "'" + email + "'";
Next, move on to the insert
method and change the SQL string as follows:
String sql = "INSERT INTO subscriberXML VALUES(XMLTYPE('<SUBSCRIBER>" + "<EMAIL>" + email + "</EMAIL>" + "<PASSWORD>" + password + "</PASSWORD>" + "<NAME>" + name + "</NAME>" + "<PHONE>" + phone + "</PHONE>" + "<SUBSCRIBED>" + subscribed + "</SUBSCRIBED>" + "</SUBSCRIBER>').createSchemaBasedXML('http://localhost:8080/public/subscriber.xsd'))";
Finally, change the SQL string in the
delete method of DBUtils
as follows:
String sql = "DELETE SubscriberXML WHERE EXTRACTVALUE(object_value, '//EMAIL')=" + "'" + email + "'";
That's it. Note that you simply
had to change the SQL queries, which the data access object uses, in
order to make the sample application talk XML to Oracle XML DB. If
you now test the sample application, you will see that it works as
before and that the changes made do not affect the application
behavior from the user's point of view. This is an example of
how you can benefit from using the MVC architecture—you can
enhance the application functionality with new features by making
changes to only one application layer. In the above example, to add
XML functionality to the application, you didn't even need to
revise the model layer entirely. Instead, you simply made changes to
the strings representing SQL queries that the DBUtils
data access object uses.
Further XML Enabling
The main weakness of the application discussed in the
preceding section is that it still operates on non-XML data. As
discussed above, the application simply uses SQL/XML queries to move
the necessary information in and out of the XML database. However,
you can go further and make the application itself operate on XML
data. Taking it one step further, the application not only can hold
its data in XML but also can transform the XML structures used in the
application into other XML documents as needed, using XSLT.
To implement the above-mentioned
functionality, you will need to modify only the model layer of the
sample application—the view layer, including backing beans,
will stay intact. You start out with the Login
model bean. Sample code Listing 8 shows the code in the Login.java
after the required corrections have been made.
In regards to Listing 8, the main
thing to note is that the Login
model bean has been changed to use a DOM document to hold the login
data. The trick is to rewrite the set
and get methods of the
properties designed to hold the login data so that these methods
access and manipulate the data stored in a DOM document. For the
first step, you create a DOM document in the constructor of the Login
class. Then you add a single element to this DOM document. Next you
rewrite all the accessor methods that are used to access and
manipulate properties intended to hold the login data so that they
operate on data stored in the DOM document. As you can see, in this
simple example you don't even need to navigate the nodes of the
DOM tree because it actually contains a single node, namely
SUBSCRIBER. Instead, you manipulate the attributes of the SUBSCRIBER
node. Finally, note that the Login.java
class uses the Oracle XML Parser v2 library that is a part of Oracle
XML Developer's Kit (XDK) 10g. If you use Oracle JDeveloper,
there is nothing to worry about since it includes the Oracle XDK Java
libraries by default. Moreover, you can always add newer releases of
XDK libraries to JDeveloper. To do this, you use the Tools->Manage
Libraries dialog box, where you can add new libraries as needed.
Once you have finished modifying the Login.java file,
you should move on to the Subscriber.java file. Sample code Listing 9
shows the code for the Subscriber.java file that has been modified to
hold the information about a subscriber in a DOM document.
Finally, move on to the DBUtils.java
file, and modify it as shown in Listing 10. Examining the code
presented there, you may note that only the insert
and select methods have
been modified and that others have no differences compared to the
DBUtils class code shown
in Listing 1. It is important to understand that both the insert
and select methods are
responsible for exchanging data between the application and the
database.
The select
method starts by defining the following SQL query string for querying
the database:
SELECT (SYS_NC_ROWINFO$).getStringVal() FROM subscriberXML
WHERE EXTRACTVALUE(object_value, '//EMAIL')=" + "'" + email + "'"
Executing the above query allows you to obtain a row as
a string that might look like this:
<SUBSCRIBER> <EMAIL>foo@mail.com</EMAIL> <PASSWORD>pswd</PASSWORD> <NAME>Joe</NAME> <PHONE>(650)475-3457>/PHONE> <SUBSCRIBED>1</SUBSCRIBED> </SUBSCRIBER>
Next, you use the getBinaryStream
method of the ResultSet
object to convert the data retrieved from the database into an
InputStream:
InputStream inStream=rsltSet.getBinaryStream(1);
Next you create an XSLProcessor
object that is then used to apply the ElemToAttr.xsl XSLT
stylesheet to the XML data source obtained from the InputStream:
XSLProcessor processor = new XSLProcessor(); URL xslInput = new URL("http://localhost:8080/public/ElemToAttr.xsl"); XSLStylesheet stylesheet = processor.newXSLStylesheet(xslInput); XMLDocumentFragment result = processor.processXSL(stylesheet, inStream, null);
As a result, you obtain an
XMLDocumentFragment that contains XML data like this:
<SUBSCRIBER EMAIL=" foo@mail.com" PASSWORD="pswd"
NAME="Joe" PHONE="(650)475-3457" SUBSCRIBED="1"/>
As you can see, the ElemToAttr.xsl stylesheet simply
transforms the SUBSCRIBER XML node obtained from the database into
another SUBSCRIBER XML node that is then used in the application. The
above example assumes that you have stored the ElemToAttr.xsl
stylesheet file in the public folder of Oracle XML DB repository. One
way to do this is by using FTP protocol. For the first step, you
create the following ElemToAttr.xsl file:
<?xml version='1.0' encoding='utf-8' ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"><xsl:output method="xml"/>
<xsl:template match="/">
<xsl:for-each select="/SUBSCRIBER" >
<xsl:element name='{name()}'>
<xsl:for-each select="*" >
<xsl:attribute name='{name()}'>
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Then, you copy the ElemToAttr.xsl file to the user home
directory. (In Windows, if no home directory is specified, the
%SystemDrive% is assumed to be the home directory.) Finally, you
upload the file to XML repository by using a standard command-line
FTP tool as follows:
ftp> open localhost 2100 Connected to localhost. 220 localhost FTP Server (Oracle XML DB/Oracle Database 10g Enterprise Edition Release 10.1.0.2.0) ready. User (localhost:(none)): usr 331 pass required for USR Password: 230 USR logged in
ftp> cd /public 250 CWD Command successful
ftp> put ElemToAttr.xsl 200 PORT Command successful 150 ASCII Data Connection 226 ASCII Transfer Complete ftp: 512 bytes sent in 0.00 Seconds 512000.00Kbytes/sec
ftp> quit 221 QUIT Goodbye.
Returning to the select
method, you employ the DOM 3.0 LS API functions to transform the
XMLDocumentFragment generated by the XSLProcessor
into a string:
DOMImplementation imp = subscriber.getSubscr_node().getOwnerDocument().getImplementation(); DOMImplementationLS impls=(DOMImplementationLS) imp; LSSerializer domWriter = impls.createLSSerializer(); String subscrXML = domWriter.writeToString(result);
Finally, you obtain an XMLElement from the subscrXML
String representing a SUBSCRIBER node transformed for the application
to use:
DOMParser dp = new DOMParser(); dp.parse(new StringReader(subscrXML)); XMLDocument xd = dp.getDocument(); XMLElement elm = (XMLElement) xd.getDocumentElement(); subscriber.setSubscr_node((Element)elm);
Unlike the select
method that obtains XML data from the database and then transforms
that data into XML for the application to use, the insert
method performs the opposite operation, producing XML to be stored in
the database from the XML data that the application uses. To perform
this transformation, the insert
method uses the AttrToElem.xsl stylesheet, shown below:
<?xml version='1.0' encoding='utf-8' ?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="xml"/> <xsl:template match="/"> <xsl:for-each select="/SUBSCRIBER"> <xsl:element name="{name()}"> <xsl:for-each select="@*"> <xsl:element name="{name()}"> <xsl:value-of select="."/> </xsl:element> </xsl:for-each> </xsl:element> </xsl:for-each> </xsl:template> </xsl:stylesheet>
To upload this file into the public folder in Oracle XML
DB repository, you might use FTP protocol, as discussed previously.
When you have finished modifying the Login.java,
Subscriber.java, and DBUtils.java files, you can test out the sample
application. Despite the fact that it should work as before from the
user's point of view, you have just created an XML-enabled JSF
application that not only talks XML to Oracle XML DB but also itself
operates on XML content using the capabilities of DOM 3.0 and XSLT.
Conclusion
In this article I have demonstrated a few ways to XML-enable an existing JSF application. If your JSF
application uses an Oracle database as its repository for the
application data, the simplest way to make your JSF application talk
XML to the database is first to create the necessary database objects
for storing the application data in XML format and then use SQL/XML
functions to manipulate XML with SQL statements. A weakness of this
approach is that the application itself still operates on non-XML
data. Therefore, you might want to proceed with XML-enabling your JSF
application, so that it not only can talk XML to Oracle XML DB but
also itself can manipulate XML as needed.
Yuli Vasiliev is a software developer, freelance author, and consultant who focuses mainly on Oracle objects and Oracle XML technology.
Send us your comments
|