public void handleActionNode result {Document doc=result.getOwnerDocument; DocumentFragment queryActionFrag=doc.createDocumentFragment; XSQLPageRequest pageRequest=getPageRequest; Ele
Trang 1public void handleAction(Node result) {
Document doc=result.getOwnerDocument();
DocumentFragment queryActionFrag=doc.createDocumentFragment();
XSQLPageRequest pageRequest=getPageRequest();
Element queryElem=doc.createElement(“my-special-query”);
Text queryStr=doc.createTextNode(“SELECT name
FROM product”);
queryElem.appendChild(queryStr);
queryAction=new XSQLQueryHandler();
queryAction.init(pageRequest,queryElem);
try {
queryAction.handleAction(queryActionFrag);
} catch (SQLException e) {
reportError(result,e.getMessage());
}
result.appendChild(queryActionFrag);
}
}
In this case, you create the element named my-special-query and hard-code the SQL that you wish to pass The result will be all of the names for all of the products Since you don’t use the action element at all, you can invoke the action handler with the following simple XSQL:
<?xml version=”1.0” encoding=”UTF-8”?>
<xsql:action handler=”NoXsqlQuery”
xmlns:xsql=”urn:oracle-xsql”
connection=”momnpup”/>
Hopefully, this discussion has given you a firm grasp of the various ways that you can reuse the built-in action handlers within your own action handlers The process is simple and flexible You’ve seen how to call multiple action handlers and even call action handlers that don’t have their own element in the XSQL page You’ll see more use of built-in action handlers as the chapter progresses
JDBC Connections
In the previous discussion, you used built-in action handlers to work with this the database This approach is very easy if you wish to attach the result to the datagram It hides the complexities of JDBC entirely But there are many good reasons that you
Trang 2might need to use JDBC directly If you wish to use the results in an intermediate step prior to returning data, then it may be easier to use a JDBC connection This discussion examines the best way to do this
The following class uses the page’s JDBC connection to execute a simple SQL state-ment The connection is named in the XSQL page and is already part of the JDBC con-nection pool This is much easier than creating a JDBC concon-nection from scratch and having to deal with connection pooling issues yourself It is also easier to configure The invoking XSQL page specifies the connection name and the details are kept in the XSQLConfig.xmlfile
import oracle.xml.xsql.XSQLActionHandlerImpl;
import oracle.xml.xsql.XSQLActionHandler;
import oracle.xml.xsql.XSQLPageRequest;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
import org.w3c.dom.Document;
import org.w3c.dom.Text;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Connection;
import java.sql.SQLException;
public class JdbcHandler extends XSQLActionHandlerImpl {
Connection conn;
public void init(XSQLPageRequest req, Element action) {
super.init(req,action);
conn=req.getJDBCConnection();
}
You grab the connection from the XSQLPageRequest object You don’t have to do this in the init method, but it is a convenient place to do it The connection will be null if there is no connection specified in the XSQL page When you go to use the con-nection, you need to check to see it is null and generate an appropriate error message
if it is
public void handleAction(Node result) {
Document doc=result.getOwnerDocument();
Element resultRoot=doc.createElement(“result-root”);
if (conn!=null) {
Trang 3String sqlStr=”SELECT name FROM product”;
Statement sql=conn.createStatement();
ResultSet resultSet=sql.executeQuery(sqlStr);
while (resultSet.next()){
String val=resultSet.getString(“name”);
Element nameElem=doc.createElement(“NAME”);
Text tNode=doc.createTextNode(val);
nameElem.appendChild(tNode);
resultRoot.appendChild(nameElem);
resultSet.next();
}
result.appendChild(resultRoot);
} catch (SQLException e) {
reportError(result,e.getMessage());
}
} else {
reportError(result,”No Database Connection”);
}
}
}
The handleAction method creates a simple statement, executes it, and grabs the result Once you have the connection object, the full power of JDBC will be at your fin-gertips You can do whatever you need to do What may be new is building XML out
of the result set data This is a simple example in which an element is created for each name returned in the query In the following text, you’ll see how you can avoid this exercise entirely by using the XSU classes
Using the XSU classes
The XSU classes allow you to imitate the behavior of the xsql:query and xsql:dml actions programmatically They take you a step deeper than reusing the built-in actions When you use the OracleXMLQuery and OracleXMLSave classes, you don’t have to pass them an element object Instead, you pass the SQL statement that you want, and the class returns a document Then you just merge the document with the datagram
This example uses the oracle.xml.sql.query.OracleXMLQuery class, which you use for select statements The oracle.xml.sql.dml.OracleXMLSave class
is used for DML statements Only the handleAction method is shown As in the pre-vious example, the connection object, conn, is acquired in the init method
public void handleAction(Node result) {
XMLDocument doc=(XMLDocument)result.getOwnerDocument();
Element resultRoot=doc.createElement(“result-root”);
Trang 4try {
// Get an XML document based on a SQL query
String sqlStr=”SELECT name FROM product”;
OracleXMLQuery xQ=new OracleXMLQuery(conn, sqlStr);
Document queryResultDoc=xQ.getXMLDOM();
//Merge the resulting document in to the datagram
Element queryRoot=queryResultDoc.getDocumentElement();
Node n=doc.adoptNode(queryRoot);
result.appendChild(n);
} catch (Exception e) {
reportError(result,e.getMessage());
}
} else {
reportError(result,”No Database Connection”);
}
}
The result of this query is exactly the same as a plain xsql:query action with the same SQL query Each row is contained in a ROW element, and all of the ROW elements are contained in a ROWSET element If you wish, you can set the name for the rowset element and the row element just as you can with xsql:query action All of the other options of the xsql:query action are available The same is true for OracleXMLSave This example also contains a good example of when Oracle’s XMLDocument class comes in handy When you go to merge the documents, you can easily do so by using the adoptNode method of XMLDocument Merging the document strictly through DOM is quite a bit harder
Adding XMLType Objects
In the previous discussion, you merged two XML documents together If you are stor-ing XML documents in Oracle, this problem can arise often You saw an example of this
in the application that you built earlier in the book with the product document inter-face In that case, you used the xsql:include-owa action along with a PL/SQL pro-cedure to output the XML to the datagram In an action handler, you don’t have to do this Instead, you create a document and merge it in to the document
The following example shows you how to do this You grab your JDBC connection
as before Then, you use the extract() function of the XMLType to get the data
public void handleAction(Node result) {
XMLDocument doc=(XMLDocument)result.getOwnerDocument();
Element resultRoot=doc.createElement(“product-set”);
if (conn!=null) {
Trang 5// execute the query
String sqlStr=”SELECT name,p.doc.extract(‘/product’).getStringVal()
AS product_xml FROM product p”;
Statement sql=conn.createStatement();
ResultSet resultSet=sql.executeQuery(sqlStr);
while (resultSet.next()){
String xmlStr=resultSet.getString(“product_xml”);
InputSource xmlIn=new InputSource(new StringReader(xmlStr));
// parse the xml string JXDocumentBuilderFactory dBF=new JXDocumentBuilderFactory(); DocumentBuilder docBuilder=dBF.newDocumentBuilder(); Document xDoc=docBuilder.parse(xmlIn);
// merge the documents Element productRoot=xDoc.getDocumentElement();
Node n=doc.adoptNode(productRoot);
resultRoot.appendChild(n);
}
result.appendChild(resultRoot);
} catch (Exception e) {
reportError(result,e.getMessage());
}
} else {
reportError(result,”No Database Connection”);
}
}
The query is executed as in the straight JDBC example earlier But the value can’t be added to the datagram as a string All of the special characters would be escaped and you wouldn’t be able to use transformations against the XML Instead, you parse the string into an XML document and then merge the two documents
Parameters and Input
In the previous two sections, you learned how to attach XML to the datagram This is only one part of action handler programming You can also get and set the parameters
of the XSQL page and a stylesheet, as well as access all of the attributes and text of the invoking xsql:action element XSQL doesn’t limit you to just the XSQL page You’ll learn how to access initialization parameters from the XSQLConfig.xml file The last two parts of this section reveal how to interpolate parameters You saw earlier that a built-in action element knows how to interpolate parameters found in the
Trang 6xsql:actionelement Now, you’ll see how to handle parameters in your own action handlers
As you read this section, you’ll see that XSQL gives you many ways to control your custom action handlers You already know how to access all the information of a servlet; you’re about to learn how to get input from the XSQL page and the XSQLCon-fig.xml, also But that’s only half the story You can use both the XSQL parameters and the stylesheet parameters as a powerful output mechanism By setting a parame-ter on a page, your action handler can communicate data with other action handlers Now, it’s time to conquer parameters and input! This subject material might not seem as important as accessing the database and pushing data to the datagram But here you understand how to control and modularize your action handlers and how your action handler can control other action handler’s behavior Just think: You could write the next xsql:query!
Accessing XSQL Data
In the previous section, you saw that a built-in action handler class can make use of the values of the action element You pass the action element to the XSQLActionHandler object and it reads the data But what if you aren’t using a built-in action handler or if some of your code needs to access values of the elements directly? The XmlTypeHandler class developed earlier in the chapter is a good example for this That class grabs sets of XML documents stored as XMLTypes and appends them to the datagram But the SQL used to get the documents was hard-coded Thus, the action handler was invoked like this from the XSQL page:
<xsql:action handler=”XmlTypeHandler”/>
That’s no good If you want to use different SQL, you’ll have to recode your action han-dler One of the key benefits of XSQL is that you can keep the SQL statements outside of your compiled code For something as generically applicable as the XMLTypeHandler, you want to be able to do something like the following:
<xsql:action handler=”XmlTypeHandler” root-element-name=”products”>
SELECT name,
p.doc.extract(‘/product’).getStringVal() AS product_xml
FROM product p
</xsql:action>
The goal is that you can read the SQL statement in just as any other action handler can The XSQL page author can also set the name of the root element that will be returned Of course, you also want to be able to use XSQL parameters in the action ele-ment You’ll learn how to substitute parameter values in the next section For this dis-cussion, the focus is on accessing the values
Trang 7Your first step is to modify the init() method so that you grab the action element and the XSQLPageRequest object Note that this time you cast the action element to
an XSQLElement variable You’ll see why in a moment
public class XmlTypeHandler extends XSQLActionHandlerImpl {
Connection conn;
XMLElement actionElem;
XSQLPageRequest pageRequest;
public void init(XSQLPageRequest req, Element action) {
super.init(req,action);
this.actionElem=(XMLElement)action;
this.pageRequest=req;
this.conn=req.getJDBCConnection();
}
Now that you have the element captured, you’ll want to grab the text value of the action element and the root-element-name attribute value Here’s how you do it: public void handleAction(Node result) {
try {
String sqlStr=actionElem.valueOf(“.”);
String resultRootName=getAttribute(“root-element-name”,actionElem); resultRootName=(resultRootName==null || resultRootName.length()==0?
“xml-doc-set”:resultRootName);
From here, the code is like before The difference is that now you are working with
an SQL statement and resultRootName that are derived from the invoking XSQL page
XMLDocument doc=(XMLDocument)result.getOwnerDocument();
Element resultRoot=doc.createElement(resultRootName);
if (conn!=null) {
Statement sql=conn.createStatement();
ResultSet resultSet=sql.executeQuery(sqlStr);
while (resultSet.next()){
String xmlStr=resultSet.getString(“product_xml”); InputSource xmlIn=new InputSource(new StringReader(xmlStr));
Trang 8JXDocumentBuilderFactory dBF=new JXDocumentBuilderFactory(); DocumentBuilder docBuilder=docBuilderFactory.newDocumentBuilder(); Document xDoc=docBuilder.parse(xmlIn);
// merge the documents
Element productRoot=xDoc.getDocumentElement();
Node n=doc.adoptNode(productRoot);
resultRoot.appendChild(n);
}
result.appendChild(resultRoot);
} else {
reportError(result,”No Database Connection”);
}
} catch (Exception e) {
reportError(result,e.getMessage());
}
}
}
In this example, you grabbed the text value of the action element If your action element has child elements, you can certainly grab the values of those as well For instance, if the name of a child element is “child”, you can do the following to grab the value:
String s=actionElem.valueOf(“child”);
You’ll learn more about creating nested action elements in the section after this next one Now it’s time to complete this example by making the preceding example param-eter ready
Substituting Parameter Values
One of the nicest features of XSQL is that you can pass parameters to action handlers You can put them in the value of an attribute or in the text of an element itself If you are writing an action handler, you want to have the same kind of power For instance,
it would be best if you could invoke the XmlHandler as follows:
<xsql:action handler=”XmlTypeHandler”
root-name=”product-set”>
SELECT name,
p.doc.extract(‘{@xPath-exp}’).getStringVal() AS product_xml
FROM product p
WHERE p.id={@product_id}
Trang 9Oracle provides a couple of ways to substitute the parameters easily For attributes, all you have to do is call the getAttributeAllowingParam() method If there is no parameter set, you’ll need to use a line like the second one to set the default value String resultRootName=getAttributeAllowingParam(“root-name”,actionElem); resultRootName=(resultRootName==null || resultRootName.length()==0?
“xml-doc-set”:resultRootName);
You can get the same behavior by using XSQLUtil.resolveParams() This is useful if you want to know what the original value of the attribute is without parsing String rootNameStr=getAttribute(“root-name”,actionElem);
String rootNameVal=XSQLUtil.resolveParams(rootNameStr,
pageRequest);
rootNameVal=(rootNameVal==null || rootNameVal.length()==0?
“xml-doc-set”:rootNameVal);
This takes care of attributes The next step is to handle the text of an element Again, there are two ways to do this If you are interested in resolving just the text value of the action element, you can do that with the XSQLActionHandlerImpl method getActionElementContent() normalizes the text and returns a string with all parameters already substituted
String s=getActionElementContent();
This method works only if you want the immediate child text of an element What if the action handler has children and you want to access their text? Then you can use the XSQLUtil.resolveParams()method after grabbing the text
XMLElement actionElem=(XMLElement)getActionElement();
String s=actionElem.valueOf(“child”);
String s=XSQLUtil.resolveParams(s, getPageRequest());
The following example shows you how to recursively descend an element, resolving all of the parameters If you want children for your action handler elements, you’ll need something like this It takes a pure DOM approach
public static void resolveElementParams(Element newElem,
XSQLPageRequest pageRequest) {
NamedNodeMap attribs=newElem.getAttributes();
for (int i=0;i<attribs.getLength();i++) {
Node attrib=attribs.item(i);
String valStr=attrib.getNodeValue();
valStr=XSQLUtil.resolveParams(valStr,pageRequest);
attrib.setNodeValue(valStr);
Trang 10The preceding code resolves any attributes that the element may have The next set of code works through all the children nodes If they are text, the parameters will be resolved
NodeList list=newElem.getChildNodes();
for (int i=0;i<list.getLength();i++) {
Node n=list.item(i);
// Text node
if (n.getNodeType()==Node.TEXT_NODE) {
log.println(“found text node”);
String valStr=n.getNodeValue();
log.println(“valStr==”+valStr);
valStr=XSQLUtil.resolveParams(valStr,pageRequest);
log.println(“valStr replaced ==”+valStr);
n.setNodeValue(valStr);
}
The last two lines take care of the recursion If the element has a child element, the method is called again on the child The recursion stops and the method halts when no more children are encountered
if (n.getNodeType()==Node.ELEMENT_NODE) {
resolveElementParams((Element)n,pageRequest);
}
}
}
This method gives you more flexibility when designing action handlers For instance, you can invoke an action handler with this method The hypothetical case is that your action handler chooses what action to take based on the test attribute These could result in different SQL queries or maybe calls to other data sources The problem solved is one of complexity If you need to pass a lot of information to an action han-dler, why not use XML to help you keep it organized? This can be a more sane approach than having to parse text inside your action handlers
<xsql:action handler=”SomeActionHandler”>
<firstChoice test=”{@test1}”>{@valParam1}</oneChoice>
<secondChoice test=”{@test2}”>{@valParam2}</secondChoice>
<thirdChoice test=”{@test3}”>{@valParam3}</thirdChoice>
<otherwise>{@valParam4}</otherwise>
</xsql:action>
In the Java code, you clone the action element first, then the resolveElement-Params:
Element elem=getActionElement().cloneNode(true);