Oracle XE + AJAX: Asynchronous XML In Action

advertisement
About the Authors
Przemek Piotrowski has been following Oracle 10g Express Edition
(Oracle XE) development since its early beta stages back in 2005 and can
easily distinguish the new features in this first database from Oracle that’s
free to develop, deploy, and distribute. Oracle XE’s small footprint doesn’t
stop him from taking advantage of many of its advanced features and
plugging them into existing Web architectures.
Mark Townsend is vice-president of database product management in
Oracle’s Server Technology Division. His responsibilities include
requirement analysis, release planning, coordinating database product
management activities, communicating with analysts and the press on
database topics, and the development and delivery of field technical
training. He was product boss for Oracle XE. Mark has been with Oracle
since 1991 and has specialized in the Oracle database for over 15 years.
Cutout: Build dynamic AJAX Web pages with transparent, asynchronous
requests sent directly to Oracle Database 10g Express Edition. Serve
dynamic content that doesn’t require a full-page refresh on the client side
by leveraging the full potential of JavaScript’s Document Object Model,
Oracle XML DB, and Embedded PL/SQL Gateway.
Oracle XE + AJAX: Asynchronous XML in Action
Building a real application
by Przemek Piotrowski and Mark Townsend
This article covers the process of building a real working AJAX
application on top of Oracle’s free database software. The demonstration
takes advantage of features integrated directly into Oracle Database 10g
Express Edition (Oracle XE), the Embedded PL/SQL Gateway and Oracle
XML DB, which let you serve XML content derived from SQL queries
directly over HTTP protocol.
You’ve probably run up against one of the numerous frameworks for
AJAX that were probably Java- or .NET-based. Although they fit large-
scale environments perfectly, smaller projects may not require such
overwhelming resources as a J2EE stack. Besides, understanding the
technology behind XMLHttpRequest may help you use asynchronous
requests better under just about any Web development environment.
The concept is surprisingly simple to make Web applications more
responsive so that they behave like regular desktop software. Waiting for a
page to reload just to see a change in a single place seems like a complete
waste of both time and bandwidth. Having said that, using AJAX to create
Web applications is good not only for the end user, but for the Web server
as well.
Applying this relatively simple technique boosts your Web
applications’ usability many times. The potential of AJAX is so big that
Web applications now have a real chance of replacing desktop programs
without degrading the user experience.
Prerequisites
All you need is access to an Oracle XE database instance and its
WebDAV folders. Oracle XE is much more than just a database server:
Embedded PL/SQL Gateway makes it a regular Web server, while the
built-in WebDAV and FTP listeners let you connect and store files served
over HTTP directly through one of these protocols. This tutorial assumes
that you have Oracle XE installed on your local computer. So that URLs
refer to localhost, you may haveto adjust it to fit your network's
configuration.
I’ll be using the sample HR schema that ships with Oracle XE. By
default it remains unlocked after installation so you need to unlock it first
using the SYSTEM account through SQL*Plus (this command will also
set a new password for HR):
SQL> alter user hr identified by hr account unlock;
User altered.
On Windows accessing Oracle XE’s storage area is trivial with the
Web Folder Access feature of Microsoft Internet Explorer; just select File
> Open and type http://localhost:8080 in the address field and the Open as
Web Folder checkbox. Then you’ll be prompted for a login and password.
Supply hr twice here.
If working under Linux or Windows you may choose to leverage the
FTP server built into the XDB component of Oracle Database 10g. You
activate it by connecting via SQL*Plus as SYSTEM and issuing the
following commands:
Connected to:
Oracle Database 10g Express Edition Release 10.2.0.1.0 - Production
SQL> exec dbms_xdb.setftpport('21');
PL/SQL procedure successfully completed.
SQL> alter system register;
System altered.
The first command enables the FTP listener in the database, while the
latter registers the just-activated listener on PMON (process monitor)
without actually waiting for its 60-second refresh cycle. Now you can
connect to the server with any FTP client or directly from the command
line (ftp localhost).
Now we can upload static files into the database storage area. What
about dynamic content? The Embedded PL/SQL Gateway – introduced for
the first time within Oracle XE – enables you to access stored procedures
directly from a Web location. Using the
http://SERVER_ADDRESS:PORT/apex/SCHEMA_NAME.PROCED
URE_NAME syntax one can use PL/SQL stored procedures to trigger
database events and generate content. For security reasons this feature has
been disabled by default in Oracle XE so you have to unlock it first. Since
this is a component of Application Express you have to modify the
WWV_FLOW_EPG_INCLUDE_MOD_LOCAL function inside the
FLOWS_020100 schema. As shown in Listing 1 below, you have to make
two changes: comment out the return false statement just after begin and
add the name of a stored procedure you’d like to grant external access to
(in this tutorial we’ll be running HR.AJAXE stored procedure listed later).
This strict policy on running stored procedures prevents one from
executing arbitrary PL/SQL code on the server side, which would be a
major security risk. Using allowed procedure names makes it convenient
to limit execution. To finalize this step, grant execute privileges on the
AJAXE procedure to PUBLIC (in SQL*Plus while logged in as SYSTEM
or HR):
SQL> grant execute on hr.ajaxe to public;
Grant succeeded.
Outputting XML from SQL Queries
You can generate XML directly from SQL queries using the PL/SQL
package DBMS_XMLGEN, which doesn’t depend on Java and sois
available on XE. The query process takes four basic steps:
1. Get the context by supplying a valid SQL query
2. Set options on the newly created context using the
DBMS_XMLGEN procedures (optional)
3. Get the XML result by using getXML() to get CLOB or
getXMLType() to obtain XMLType. At this point you can also limit
the numbers of rows returned by the XML engine using the
setMaxRows() procedure on the context
4. Reset the query to perform step 3 again or run closeContext() to free
up allocated resources.
Unfortunately DBMS_XMLGEN has one flaw that you’ll run up
against almost immediately when working with remote XML – HTP.PRN,
whichwill be used to print the output accepts strings of a VARCHAR2
type that can be 4,000 bytes long. This isn’t much for XML to deal with.
The AJAX_XMLHTP procedure was created, which takes a context as a
parameter and using the DBMS_LOB PL/SQL package outputs XML in
4,000-char chunks, leveraging the possibilities standing behind LOBs. See
Listing 2.
This procedure will handle all the processing work for us by setting
the content type to text/xml (required by the XmlHttpRequest object for
DOM processing), setting null handling to output empty tags for null
values, and disabling pretty printing for smaller response size. Also, when
something goes wrong at the query level, e.g., empty result set or invalid
query, the procedure manually outputs the hard-coded "No data found"
message to indicate its state.
The AJAXE procedure will be a core dispatcher of responses. It will
accept two parameters: q – the type of action requested, w – the extra
parameter required for certain types of action (optional). See Listing 3.
The functionality of the sample application will be divided into three
basic use cases:



Filling the SELECT list with elements obtained through the AJAX
request and hooking the event handler to it
Obtaining the results set and rendering it into a HTML table
dynamically
Using simple auto-complete on the HTML input field
Note that both these procedures have to be created in the HR schema
to work. At this point you can test whether the procedure is working for
you by accessing http://localhost:8080/apex/hr.ajaxe?q=count
(remember to adjust the URL to reflect your system’s configuration). A
valid XML document should be outputted at this address. You’re halfway
through.
Hands-on Document Object Model (DOM)
AJAX extensively relies on a set of JavaScript programming
techniques once referred to as DHTML. But now they’re much more
powerful thanks to the unification of the DOM implementation among
Web browser vendors. Back in the days of Netscape Navigator 4 and
Microsoft Internet Explorer 4 one had to use a different syntax for each
browser to manipulate page content dynamically. No need to do that
anymore.
The Document Object Model is a tree-like hierarchical representation
of nodes/HTML elements. The root node of an HTML document is the
<html> tag that usually has two children: <head> and <body> – the
manipulations happen in the second. Since DOM is a vast subject, we’ll
focus only on the most useful properties and methods of JavaScript XML
objects. For the complete DOM specification, see the W3C Web site at
http://w3.org.
Querying There are two core methods of querying an existing DOM tree
DOM
that can be used both on document object and nodes
themselves:

document_or_node.getElementById(id)
Query context for a node with an id attribute equal to the
given ID. Returns a single node or null if not found.

document_or_node.getElementsByTagName(tag_name)
Query context for all nodes with the given tag name.
Always returns a list of nodes (HTMLCollection object)
even if it means that it’s empty (no nodes with the given
tag name were found).
Creating Element is the basic node type that represents a tag element on
nodes
a Web page.
To create a new node of the Element type call the
createElement method on the document object:

document.createElement(name)
Note, however, that this element is initially empty. You
can append other elements or a text to it with the
document.createTextNode(text) method.
Removin To remove a node from the DOM tree you have to find its
g nodes parent and use the following removal function:

some_node.removeChild(old_child)
This function removes the old_child node from the
document in place and returns it.
Note that this method can be slow when used to remove trees
recursively from the main DOM tree. It can drastically
decrease page performance so use the well-known trick of
setting node’s innerHTML attribute to an empty string.
Manipula There are several methods for manipulating the DOM tree,
ting
some of them are:
nodes

existing_node.appendChild(new_node)
This method appends the new_node at the end of the
existing_node’s children list. To be able to insert a node
before some node one needs to make use of the
insertBefore method.

setAttribute(name, value)
HTML requires many elements to have certain attributes
set. For example, this method could be used to set the
href attribute of <a> element.
Again, remember that there are a number of other JavaScript
methods for working with the DOM object. For purposes of this article, an
ajaxTable function was created: it builds up the HTML table from the
XML response obtained through the AJAX request. By default
DBMS_XMLGEN returns a XML tree of the following structure:
<?xml version="1.0"?>
<ROWSET>
<ROW>
<COLUMN1></COLUMN1>
<COLUMN2></COLUMN2>
...
</ROW>
...
</ROWSET>
The code in Listing 4represents the body of the ajaxTable function.
This function appends the DOM tree in Listing 5 to the DIV element
of ID="q" (the TBODY element is required here to render properly under
Internet Explorer).
AJAX = Asynchronous JavaScript and XML
Technologically, AJAX is nothing new. It owes its recent popularity
to the unification of the JavaScript standards that allow access to the
XmlHttpRequest object. XML isn’t new either. It now plays a key role in
data and information interchange and is universally adopted by both the
enterprise and the open source community. The biggest problem with the
Web was that applications needed a full-page refresh after each user action
on the page. It didn’t matter whether it was just sorting table elements or
downloading a whole new page.
XmlHttpRequest object methods and properties.
abort()
Aborts the current request
getAllResponseHeaders()
Returns all headers from the HTTP response
as a string
getResponseHeader(label) Returns the header value specified by the
label
open(method, URL)
Overloaded open() methods assign
open(method, URL, async) parameters to the request object
open(method, URL, async,
username)
open(method, URL, async, The method is one of "GET" or "POST"
(use the latter when the data is sent with a
username, password)
request that exceeds 512 bytes)
send(content)
setRequestHeader(label,
value)
onreadystatechange
readyState
URL might be relative or absolute
The async flag is the heart of AJAX, setting
Sends
theor
request
together
with
the the
it to true
false you
control
whether
specified
contentfor
(can
be null) in the
script is waiting
a response
background
or halts
execution
until the
Sets a label/value
pair
to be included
in the
response
is received
requests header
The event handler watching the requests
You can also supply username and
state
password for direct HTTP authorization Statusthat
of this
the request
given
integer:
note
is not safe,
as by
thethe
script
will
0 uninitial
state
always
remainBasic
visible
to the user
ized
1 open
open() method has been
successfully called
2 sent
The request has been sent
3 receivin After receiving any of the
g
response headers and just
before receiving the body
4 loaded All data transferred
responseText
responseXML
Plain text content of the response
XML content of the response
status
HTTP status of the URL called as a number
(eg. 200, 403, 404)
HTTP status of the URL called as a string
(e.g., OK, Authorization Required, Not
Found)
statusText
There are a number of AJAX frameworks on the market, including
the Dojo Toolkit, Prototype, or Microsoft Atlas to name a few. However,
writing basic handlers alone helps enable a deeper understanding of what’s
happening under AJAX’s hood. First, we need an XmlHttpRequest object
that unfortunately still depends on the browser – Microsoft Internet
Explorer continues handling it using an ActiveX control so the appropriate
requests object creation code is presented in Listing 6. This is the only part
that requires browser-compatibility code. Everything else here is crossbrowser.
As for the handler, a dedicated function to handle all the
asynchronous operations was created. It takes a request method, a URL,
and callback functions as parameters. After the response is obtained the
callback is immediately called with the fetched XML passed as an
argument.
function async(method, url, callback) {
request.open(method, url, true);
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
callback(request.responseXML);
}
}
request.send(null);
}
The callback function will do all the DOM processing based on the
XML content from the response. The callback works directly on the
XMLDocument JavaScript object.
Putting It All Together
Now that we have all the building blocks of the solution, let’s take a
look at the logic behind it. The diagram below separates client side and
server side into three separate blocks: Oracle XE, Web page, and browser
client. Notice that the Web page has been separated here because of its
state-dependent nature – thanks to AJAX it now incorporates the logic to
handle user actions and transform them into HTTP requests, the standard
Web model assumes that it’s the browser that dispatches HTTP requests to
the Web server. That is the key concept in the solution. Take a look at the
diagram below to see the whole interaction process.
1. The browser client sends a direct HTTP request to the Web server
(here: the Embedded PL/SQL Gateway of Oracle XE), the URL of
the request has been supplied in the location bar or through a link.
2. Oracle XE serves a static HTML page that includes AJAX scripts.
This static page is then rendered by the browser and now all the
requests and interaction happen indirectly, as AJAX requests.
3. When the user triggers an event on the Web page by interacting with
page elements such as forms and links, an event handler catches it
and sends the appropriate HTTP request through the AJAX engine.
4. The XMLHttpRequest object requests a remote resource from the
Embedded PL/SQL gateway. At this point, the user doesn’t have to
wait for the script to finish fetching responses from the server, it’s
handled in an asynchronous manner. The script takes as much time
as needed to fetch the response while the user can further interact
with the Web page.
5. The script performs DOM modifications in the background while the
user can trigger the next events on the Web page.
You can now upload the attached HTML file (hr-ajax-demo.html)
into your XE instance through FTP or WebDAV, which contains the
complete code for the sample application. After putting it into the /public/
folder of XE, open http://localhost:8080/public/hr-ajax-demo.html with
your Web browser. After carefully following all the steps of this tutorial
the demo page should pre-load the list of departments and respond to user
interaction almost immediately.
Advantages and Potential Problems
Although programming AJAX isn’t necessarily difficult, the question
is whether asynchronous querying techniques are safe and productionready. AJAX isn’t always a sure choice over standard development
methodologies. There are at least several viewpoints to consider:
Advantages:

Better user experience

Greater responsiveness

Smaller server load, increased performance

Network applications can be updated on-the-fly without
redistributing updates

Separation of content from presentation (easier maintenance)
Potential problems:

Cross-browser incompatibilities

Client-side security concerns
The Real World
Today, AJAX applications are gaining a lot of steam. With Google
leading this trend together AND Yahoo! and Microsoft trailing, the
desktop experience is delivered to users through increasingly dynamic
Web pages. Since the early adoption of AJAX it has matured to be a fullblown solution often chosen by enterprises for commercial solutions.
After the unification of DOM implementations and the adoption of
World Wide Web Consortium (W3C) standards among developers of Web
browsers, it’s now possible to create cross-platform Web applications
more easily than ever before. With a wide range of available AJAX
toolkits, developers can now upgrade Web applications to an asynchronous
architecture at a low cost. AJAX is flexible enough to be plugged into
existing solutions and greatly enhances usability. The movement of
desktop applications to the Web is a matter of time and represents the next
step in software evolution.
Listing 1
Listing 1
create or replace function
wwv_flow_epg_include_mod_local(procedure_name in varchar2)
return boolean
is
begin
-- return false; -- remove this statement when you modify this function
--- Administrator note: the procedure_name input parameter may be in
the format:
--- procedure
-- schema.procedure
-- package.procedure
-- schema.package.procedure
--- If the expected input parameter is a procedure name only, the IN list
code shown below
-- can be modified to itemize the expected procedure names. Otherwise
you must parse the
-- procedure_name parameter and replace the simple code below with
code that will evaluate
-- all of the cases listed above.
-if upper(procedure_name) in (
'HR.AJAXE') then
return TRUE;
else
return FALSE;
end if;
end wwv_flow_epg_include_mod_local;
Listing 2
create or replace procedure ajax_xmlhtp(ctxt in number) as
xml clob;
chnk number := 4000;
begin
owa_util.mime_header('text/xml');
dbms_xmlgen.setnullhandling(ctxt, dbms_xmlgen.empty_tag);
dbms_xmlgen.setprettyprinting(ctxt, false);
xml := dbms_xmlgen.getxml(ctxt);
for i in 1..ceil(length(xml)/chnk) loop
htp.prn(dbms_lob.substr(xml, chnk+1, i+(i-1)*chnk));
end loop;
dbms_xmlgen.closecontext(ctxt);
exception
when others then
htp.print('<?xml version="1.0"?>');
htp.print('<ROWSET><ROW><INFO>No data
found.</INFO></ROW></ROWSET>');
end;
/
Listing 3
create or replace procedure ajaxe(q varchar2, w varchar2 default '') as
ctxt number;
i number;
begin
if q='count' then
ajax_xmlhtp(dbms_xmlgen.newcontext('select count(*)
"EMPLOYEES" from employees'));
elsif q='search' then
i := length(w);
ctxt := dbms_xmlgen.newcontext('select * from employees where
upper(substr(last_name, 0, :LEN))=upper(:PREFIX) order by
last_name');
dbms_xmlgen.setbindvalue(ctxt, 'LEN', i);
dbms_xmlgen.setbindvalue(ctxt, 'PREFIX', w);
ajax_xmlhtp(ctxt);
elsif q='fetch' then
ajax_xmlhtp(dbms_xmlgen.newcontext('select * from employees'));
elsif q='select' then
ajax_xmlhtp(dbms_xmlgen.newcontext('select department_id,
department_name from departments order by 2'));
elsif q='dept' then
ctxt := dbms_xmlgen.newcontext('select * from employees where
department_id=:DEPT');
dbms_xmlgen.setbindvalue(ctxt, 'DEPT', w);
ajax_xmlhtp(ctxt);
end if;
end;
/
Listing 4
function ajaxTable(xml) {
document.getElementById('q').innerHTML = '';
var rows =
xml.documentElement.getElementsByTagName('ROW');
var table = document.createElement('table');
var tbody = document.createElement('tbody');
table.setAttribute('border', '1');
var tr = document.createElement('tr');
for (var h=0; h<rows[0].childNodes.length; h++) {
if (rows[0].childNodes[h].nodeType!=1) {
continue;
}
var th = document.createElement('th');
th.appendChild(document.createTextNode(rows[0].childNodes[h].ta
gName));
tr.appendChild(th);
}
tbody.appendChild(tr);
for (var i=0; i<rows.length; i++) {
var tr = document.createElement('tr');
for (var j=0; j<rows[i].childNodes.length; j++) {
if (rows[i].childNodes[j].nodeType!=1) {
continue;
}
var td = document.createElement('td');
if (rows[i].childNodes[j].childNodes.length!=0) {
td.appendChild(document.createTextNode(rows[i].childNodes[j].fir
stChild.nodeValue));
} else {
td.appendChild(document.createTextNode('-'));
}
tr.appendChild(td);
}
tbody.appendChild(tr);
}
table.appendChild(tbody);
document.getElementById('q').appendChild(table);
}
Listing 5
<table border="1">
<tbody>
<tr>
<th>HEADING1</th>
<th>HEADING2</th>
<th>...</th>
</tr>
<tr>
<td>ROW1, COLUMN1</td>
<td>ROW1, COLUMN2</td>
<td>ROW1, ...</td>
</tr>
...
</tbody>
</table>
Listing 6
var request = false;
try {
request = new XMLHttpRequest();
} catch(e) {
try {
request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e2) {
request = new ActiveXObject("MSXML.XMLHTTP");
}
}
Download