Customizing the XSQL Configuration File Name

By default, the XSQL pages framework expects the configuration file to be named XSQLConfig.xml. When moving between development, test, and production environments, you can switch between different versions of an XSQL configuration file. To override the name of the configuration file read by the XSQL page processor, set the Java system property xsql.config.

The simplest technique is to specify a Java VM command-line flag such as -Dxsql.config=MyConfigFile.xml by defining a servlet initialization parameter named xsql.config. Add an <init-param> element to your web.xml file as part of the <servlet> tag that defines the XSQL Servlet as follows:

The servlet initialization parameter is only applicable to the servlet-based use of the XSQL engine. When using the XSQLCommandLine or XSQLRequest programmatic interfaces, use the System parameter instead.

Note:

The configuration file is always read from the CLASSPATH. For example, if you specify a custom configuration parameter file named MyConfigFile.xml, then the XSQL processor attempts to read the XML file as a resource from the CLASSPATH. In a J2EE-style servlet environment, you must place your MyConfigFile.xml in the .\WEB-INF\classes directory (or some other top-level directory that will be found on the CLASSPATH). If both the servlet initialization parameter and the System parameter are provided, then the servlet initialization parameter value is used.

Overriding Client Stylesheets

If the current XSQL page being requested allows it, then you can supply an XSLT stylesheet URL in the request. This technique enables you to either override the default stylesheet or apply a stylesheet where none is applied by default. The client-initiated stylesheet URL is provided by supplying the xml-stylesheet parameter as part of the request. The valid values for this parameter are the following:

Any relative URL interpreted relative to the XSQL page being processed.

Any absolute URL that uses the HTTP protocol scheme, provided it references a trusted host as defined in the XSQL configuration file.

The literal value none. Setting xml-stylesheet=none is useful during development to temporarily "short-circuit" the XSLT stylesheet processing to determine what XML datagram your stylesheet is seeing. Use this technique to determine why a stylesheet is not producing expected results.

You can allow client override of stylesheets for an XSQL page in the following ways:

Setting the allow-client-style configuration parameter to no in the XSQL configuration file

Explicitly including an allow-client-style="no" attribute on the document element of any XSQL page

If client-override of stylesheets has been globally disabled by default in the XSQL configuration file, any page can still enable client-override explicitly by including an allow-client-style="yes" attribute on the document element of that page.

Controlling the Content Type of the Returned Document

Setting the content type of the data that you serve enables the requesting client to correctly interpret the data that you return. If your stylesheet uses an <xsl:output> element, then the XSQL processor infers the media type and encoding of the returned document from the media-type and encoding attributes of <xsl:output>.

The stylesheet in Example 15-1 uses the media-type="application/vnd.ms-excel" attribute on <xsl:output>. This instruction transforms the results of an XSQL page containing a standard query of the hr.employees table into Microsoft Excel format.

Assigning the Stylesheet Dynamically

If you include an <?xml-stylesheet?> instruction at the top of your .xsql file, then the XSQL page processor considers it for use in transforming the resulting XML datagram. Consider the emp_test.xsql page shown in Example 15-2.

The page in Example 15-2 uses the emp.xsl stylesheet to transform the results of the employees query in the server tier before returning the response to the requestor. The processor accesses the stylesheet by the URL provided in the href pseudo-attribute on the <?xml-stylesheet?> processing instruction.

Suppose that you want to change XSLT stylesheets dynamically based on arguments passed to the XSQL servlet. You can achieve this goal by using a lexical parameter in the href attribute of your xml-stylesheet processing instruction, as shown in the following sample instruction:

<?xml-stylesheet type="text/xsl" href="{@filename}.xsl"?>

You can then pass the value of the filename parameter as part of the URL request to XSQL servlet.

You can also use the <xsql:set-page-param> element in an XSQL page to set the value of the parameter based on a SQL query. For example, the XSQL page in Example 15-3 selects the name of the stylesheet to use from a table by assigning the value of a page-private parameter.

Processing XSLT Stylesheets in the Client

Some browsers support processing XSLT stylesheets in the client. These browsers recognize the stylesheet to be processed for an XML document by using an <?xml-stylesheet?> processing instruction. The use of <?xml-stylesheet?> for this purpose is part of the W3C Recommendation from June 29, 1999 entitled "Associating Stylesheets with XML Documents, Version 1.0".

By default, the XSQL pages processor performs XSLT transformations in the server. By adding client="yes" to your <?xml-stylesheet?> processing instruction in your XSQL page, however, you can defer XSLT processing to the client. The processor serves the XML datagram "raw" with the current <?xml-stylesheet?> element at the top of the document.

Providing Multiple Stylesheets

You can include multiple <?xml-stylesheet?> processing instructions at the top of an XSQL page. The instructions can contain an optional media pseudo-attribute. If specified, the processor case-insensitively compares the value of the media pseudo-attribute with the value of the User-Agent string in the HTTP header. If the value of the media pseudo-attribute matches part of the User-Agent string, then the processor selects the current <?xml-stylesheet?> instruction for use. Otherwise, the processor ignores the instruction and continues looking. The processor uses the first matching processing instruction in document order. An instruction without a media pseudo-attribute matches all user agents.

Example 15-4 shows multiple processing instructions at the top of an XSQL file. The processor uses doyouxml-lynx.xsl for Lynx browsers, doyouxml-ie.xsl for Internet Explorer 5.0 or 5.5 browsers, and doyouxml.xsl for all others.

Indicates the MIME type of the associated stylesheet. For XSLT stylesheets, this attribute must be set to the string text/xsl.

This attribute may be present or absent when using the serializer attribute, depending on whether an XSLT stylesheet has to execute before invoking the serializer, or not.

href = "URL"

Indicates the relative or absolute URL to the XSLT stylesheet to be used. If an absolute URL is supplied that uses the http protocol scheme, the IP address of the resource must be a trusted host listed in the XSQL configuration file (by default, named XSQLConfig.xml).

media = "string"

Performs a case-insensitive match on the User-Agent string from the HTTP header sent by the requesting device. This attribute is optional. The current <?xml-stylesheet?> processing instruction is used only if the User-Agent string contains the value of the media attribute; otherwise it is ignored.

client = "boolean"

Defers the processing of the associated XSLT stylesheet to the client if set to yes. The raw XML datagram is sent to the client with the current <?xml-stylesheet?> instruction at the top of the document. The default if not specified is to perform the transformation in the server.

serializer = "string"

By default, the XSQL page processor uses the following:

XML DOM serializer if no XSLT stylesheet is used

XSLT processor serializer if an XSLT stylesheet is used

Specifying this pseudo-attribute indicates that a custom serializer implementation must be used instead.

Valid values are either the name of a custom serializer defined in the <serializerdefs> section of the XSQL configuration file or the string java:fully.qualified.Classname. If both an XSLT stylesheet and the serializer attribute are present, then the processor performs the XSLT transformation first, then invokes the custom serializer to render the final result to the OutputStream or PrintWriter.

Supplying Values for Array-Valued Parameters

Request parameters, session parameters, and page-private parameters can have arrays of strings as values. To treat to the value of a parameter as an array, add two empty square brackets to the end of its name. For example, if an HTML form is posted with four occurrences of a input control named productid, then use the notation productid[] to refer to the array-valued productid parameter. If you refer to an array-valued parameter without using the array-brackets notation, then the XSQL processor uses the value of the first array entry.

Note:

The XSQL processor does not support use of numbers inside the array brackets. That is, you can refer to productid or productid[], but not productid[2].

Suppose that you refer to an array-valued parameter as a lexical substitution parameter inside an action handler attribute value or inside the content of an action handler element. The XSQL page processor converts its value to a comma-delimited list of non-null and non-empty strings in the order that they exist in the array. Example 15-5 shows an XSQL page with an array-valued parameter.

The preceding command sets the productid[] array-valued parameter to the value {"111","222","333","444"}. The XSQL page processor replaces the {@productid[]} expression in the query with the string "111,222,333,444".

Note that you can also pass multi-valued parameters programmatically through the XSQLRequest API, which accepts a java.util.Dictionary of named parameters. You can use a Hashtable and call its put(name,value) method to add String-valued parameters to the request. To add multi-valued parameters, put a value of type String[] instead of type String.

Note:

Only request parameters, page-private parameters, and session parameters can use string arrays. The <xsql:set-stylesheet-param> and <xsql:set-cookie> actions only support working with parameters as simple string values. To refer to a multi-valued parameter in your XSLT stylesheet, use <xsql:include-param> to include the multi-valued parameter into your XSQL datapage, then use an XPath expression in the stylesheet to refer to the values from the datapage.

Setting Array-Valued Page or Session Parameters from Strings

You can set the value of a page-private parameter or session parameter to a string-array value by using the array brackets notation on the name as follows:

By default, when the name of the parameter uses array brackets, the XSQL processor treats the value as a space-or-comma-delimited list and tokenizes it.

The resulting string array value contains these separate tokens. In the preceding examples, the names[] parameter is the string array {"Tom", "Jane", "Joe"} and the dates[] parameter is the string array {"12-APR-1962", "15-JUL-1968"}.

To handle strings that contain spaces, the tokenization algorithm first checks the string for the presence of commas. If at least one comma is found in the string, then commas are used as the token delimiter. For example, the following action sets the value of the names[] parameter to the string array {"Tom Jones", "Jane York"}:

By default, when you set a parameter whose name does not end with the array-brackets, then the string-tokenization does not occur. Thus, the following action sets the parameter names to the literal string "Tom Jones,Jane York":

You can force the string to be tokenized by including the treat-list-as-array="yes" attribute on the <xsql:set-page-param> or <xsql:set-session-param> actions. When this attribute is set, the XSQL processor assigns a comma-delimited string of the tokenized values to the parameter. For example, the following action sets the names parameter to the literal string "Tom,Jane,Joe":

When you are setting the value of a simple string-valued parameter and you are tokenizing the value with treat-list-as-array="yes", you can include the quote-array-values="yes" attribute to surround the comma-delimited values with single-quotes. Thus, the following action assigns the literal string value "'Tom Jones','Jane York','Jimmy'" to the names parameter:

Binding Array-Valued Parameters in SQL and PL/SQL Statements

Where string-valued scalar bind variables are supported in an XSQL page, you can also bind array-valued parameters. Use the array parameter name, for example, myparam[], in the list of parameter names that you supply for the bind-params attribute. This technique enables you to process array-valued parameters in SQL statements and PL/SQL procedures.

The XSQL processor binds array-valued parameters as a nested table object type named XSQL_TABLE_OF_VARCHAR. You must create this type in your current schema with the following DDL statement:

CREATE TYPE xsql_table_of_varchar AS TABLE OF VARCHAR2(2000);

Although the type must have the name xsql_table_of_varchar, you can change the dimension of the VARCHAR2 string if desired. Of course, you have to make it as long as any string value you expect to handle in your array-valued string parameters.

This technique shows that the XSQL processor bound the array-valued someNames[] and someValues[] parameters as table collection types. It iterated over the values and concatenated them to produce the "aa=11:bb=22:cc=33" string value as the return value of the PL/SQL function.

You can mix any number of regular parameters and array-valued parameters in your bind-params string. Use the array-bracket notation for the parameters that you want to be bound as arrays.

Note:

If you run the page in Example 15-7 but you have not created the XSQL_TABLE_OF_VARCHAR type as illustrated earlier, then you receive an error such as the following:

Because the XSQL processor binds array parameters as nested table collection types, you can use the TABLE() operator with the CAST() operator in SQL to treat the nested table bind variable value as a table of values. You can then query this table. This technique is especially useful in subqueries. The page in Example 15-8 uses an array-valued parameter containing employee IDs to restrict the rows queried from hr.employees.

Note that PL/SQL index-by tables work with the OCI JDBC driver but not the JDBC thin driver. By using the nested table collection type XSQL_TABLE_OF_VARCHAR, you can use array-valued parameters with either driver. In this way you avoid losing the programming flexibility of working with array values in PL/SQL.

Setting Error Parameters on Built-in Actions

The XSQL page processor determines whether an action encountered a non-fatal error during its execution. For example, an attempt to insert a row or call a stored procedure can fail with a database exception that will get included in your XSQL data page as an <xsql-error> element.

You can set a page-private parameter in a built-in XSQL action when the action reports a nonfatal error. Use the error-param attribute on the action to enable this feature. For example, to set the parameter "dml-error" when the statement inside the <xsql:dml> action encounters a database error, you can use the technique shown in Example 15-9.

If the execution of the <xsql:dml> action encounters an error, then the XSQL processor sets the page-private parameter dml-error to the string "Error". If the execution is successful, then the XSQL processor does not assign a value to the error parameter. In Example 15-9, if the page-private parameter dml-error already exists, then it retains its current value. If it does not exist, then it continues not to exist.

Using Conditional Logic with Error Parameters

By using the error parameter in combination with <xsql:if-param>, you can achieve conditional behavior in your XSQL page template. For example, assume that your connection definition sets the AUTOCOMMIT flag to false on the connection named demo in the XSQL configuration file. The XSQL page shown in Example 15-10 illustrates how you might roll back the changes made by a previous action if a subsequent action encounters an error.

If you have written custom action handlers, and if your custom actions call reportMissingAttribute(), reportError(), or reportErrorIncludingStatement() to report non-fatal action errors, then they automatically pick up this feature as well.

Formatting XSQL Action Handler Errors

Errors raised by the processing of XSQL action elements are reported as XML elements in a uniform way. This fact enables XSLT stylesheets to detect their presence and optionally format them for presentation.

The action element in error is replaced in the page by the following element:

<xsql-error action="xxx">

Depending on the error the <xsql-error> element contains:

A nested <message> element

A <statement> element with the offending SQL statement

Example 15-11 shows an XSLT stylesheet that uses this information to display error information on the screen.

Including XMLType Query Results in XSQL Pages

Oracle Database supports XMLType for storing and querying XML-based database content. You can exploit database XML features to produce XML for inclusion in your XSQL pages by using one of the following techniques:

<xsql:query> handles any query including columns of type XMLType, but it handles XML markup in CLOB and VARCHAR2 columns as literal text.

<xsql:include-xml> parses and includes a single CLOB or string-based XML document retrieved from a query.

One difference between the preceding approaches is that <xsql:include-xml> parses the literal XML appearing in a CLOB or string value on the fly to turn it into a tree of elements and attributes. In contrast, <xsql:query> leaves XML markup in CLOB or string-valued columns as literal text.

Another difference is that while <xsql:query> can handle query results of any number of columns and rows, <xsql:include-xml> works on a single column of a single row. Accordingly, when using <xsql:include-xml>, the SELECT statement inside it returns a single row containing a single column. The column can either be a CLOB or a VARCHAR2 value containing a well-formed XML document. The XSQL engine parses the XML document and includes it in your XSQL page.

Example 15-12 uses nested XmlAgg() functions to aggregate the results of a dynamically-constructed XML document containing departments and nested employees. The functions aggregate the document into a single "result" document wrapped in a <DepartmentList> element.

You can use the combination of XmlElement() and XmlAgg() to make the database aggregate all of the XML fragments identified by the query into single, well-formed XML document. The functions work together to produce a well-formed result like the following:

You can use the standard XSQL bind variable capabilities in the middle of an XPath expression if you concatenate the bind variable into the expression. For example, to parameterize the value Oscar into a parameter named award-from, you can use an XSQL page like the one shown in Example 15-16.

Handling Posted XML Content

In addition to simplifying the assembly and transformation of XML content, the XSQL pages framework enables you to handle posted XML content. Built-in actions provide the following advantages:

Simplify the handling of posted data from both XML document and HTML forms

Enable data to be posted directly into a database table by using XSU

XSU can perform database inserts, updates, and deletes based on the content of an XML document in canonical form for a target table or view. For a specified table, the canonical XML form of its data is given by one row of XML output from a SELECT * query. When given an XML document in this form, XSU can automate the DML operation.

By combining XSU with XSLT, you can transform XML in any format into the canonical format expected by a given table. XSU can then perform DML on the resulting canonical XML.

The following built-in XSQL actions make it possible for you to exploit this capability from within your XSQL pages:

Insert the optionally transformed XML document that was posted as the value of a request parameter into a table or view.

If you target a database view with your insert, then you can create INSTEAD OF INSERT triggers on the view to further automate the handling of the posted information. For example, an INSTEAD OF INSERT trigger on a view can use PL/SQL to check for the existence of a record and intelligently choose whether to do an INSERT or an UPDATE depending on the result of this check.

Understanding XML Posting Options

The XSQL pages framework can handle posted data in the following scenarios:

A client program sends an HTTP POST message that targets an XSQL page. The request body contains an XML document; the HTTP header reports a ContentType of "text/xml".

In this case, <xsql:insert-request>, <xsql:update-request>, or <xsql:delete-request> can insert, update, or delete the content of the posted XML in the target table. If you transform the posted XML with XSLT, then the posted document is the source for the transformation.

A client program sends an HTTP GET request for an XSQL page, one of whose parameters contains an XML document.

In this case, you can use the <xsql:insert-param> action to insert the content of the posted XML parameter value in the target table. If you transform the posted XML document with XSLT, then the XML document in the parameter value is the source document for this transformation.

A browser submits an HTML form with method="POST" whose action targets an XSQL page. The request body of the HTTP POST message contains an encoded version of the form fields and values with a ContentType of "application/x-www-form-urlencoded".

In this case, the request does not contain an XML document, but an encoded version of the form parameters. To make all three of these cases uniform, however, the XSQL page processor materializes on demand an XML document from the form parameters, session variables, and cookies contained in the request. The XSLT processor transforms this dynamically-materialized XML document into canonical form for DML by using <xsql:insert>, <xsql:update-request>, or <xsql:delete-request>.

When working with posted HTML forms, the dynamically materialized XML document has the form shown in Example 15-17.

If multiple parameters are posted with the same name, then the XSQL processor automatically creates multiple <row> elements to make subsequent processing easier. Assume that a request posts or includes the following parameters and values:

You need to provide an XSLT stylesheet that transforms this materialized XML document containing the request parameters into canonical format for your target table. Thus, you can build an XSQL page as follows:

With this page in place, you can temporarily modify your HTML form to post to the ShowRequestDocument.xsql page. In the browser you will see the "raw" XML for the materialized XML request document, which you can save and use to develop the XSL transformation.

Producing PDF Output with the FOP Serializer

Using the XSQL pages framework support for custom serializers, the oracle.xml.xsql.serializers.XSQLFOPSerializer class provides integration with the Apache FOP processor. The FOP processor renders a PDF document from an XML document containing XSL Formatting Objects.

As described in Table 14-1, the demo directory includes the emptablefo.xsl stylesheet and emptable.xsql page as illustrations. If you get an error trying to use the FOP serializer, then probably you do not have all of the required JAR files in the CLASSPATH. The XSQLFOPSerializer class resides in the separate xml.jar file, which must be included in the CLASSPATH to use the FOP integration. You also need to add the following additional Java archives to your CLASSPATH:

fop.jar - from Apache, version 0.20.3 or higher

batik.jar - from the FOP distribution

avalon-framework-4.0.jar - from FOP distribution

logkit-1.0.jar - from FOP distribution

In case you want to customize the implementation, the source code for the FOP serializer provided in this release is shown in Example 15-18.

Writing Custom XSQL Action Handlers

The XSQL pages engine processes an XSQL page by looking for action elements from the xsql namespace and invoking an appropriate action element handler class to process each action. The processor supports any action that implements the XSQLActionHandler interface. All of the built-in actions implement this interface.

The XSQL engine processes the actions in a page in the following way. For each action in the page, the engine performs the following steps:

Constructs an instance of the action handler class using the default constructor

Initializes the handler instance with the action element object and the page processor context by invoking the method init(Element actionElt,XSQLPageRequest context)

Invokes the method that allows the handler to handle the action handleAction (Node result)

For built-in actions, the engine can map the XSQL action element name to the Java class that implements the handler of the action. Table 30-2, "XSQL Configuration File Settings" lists the built-in actions and their corresponding classes.

For user-defined actions, use the following built-in action, replacing fully.qualified.Classname with the name of your class:

<xsql:action handler="fully.qualified.Classname" ... />

The handler attribute provides the fully-qualified name of the Java class that implements the custom action handler.

Implementing the XSQLActionHandler Interface

To create a custom action handler, provide a class that implements the oracle.xml.xsql.XSQLActionHandler interface. Most custom action handlers extend oracle.xml.xsql.XSQLActionHandlerImpl, which provides a default implementation of the init() method and offers useful helper methods.

When an action handler's handleAction() method is invoked by the XSQL pages processor, a DOM fragment is passed to the action implementation. The action handler appends any dynamically created XML content returned to the page to the root node.

The XSQL processor conceptually replaces the action element in the XSQL page with the content of this document fragment. It is legal for an action handler to append nothing to this fragment if it has no XML content to add to the page.

While writing you custom action handlers, some methods on the XSQLActionHandlerImpl class are helpful. Table 15-2 lists these methods.

Table 15-2 Helpful Methods in the XSQLActionHandlerImpl Class

Method Name

Description

getActionElement

Returns the current action element being handled.

getActionElementContent

Returns the text content of the current action element, with all lexical parameters substituted appropriately.

getPageRequest

Returns the current XSQL pages processor context. Using this object you do the following:

setPageParam()

Set a page parameter value.

getPostedDocument()/setPostedDocument()

Get or set the posted XML document.

translateURL()

Translate a relative URL to an absolute URL.

getRequestObject()/setRequestObject()

Get or set objects in the page request context that can be shared across actions in a single page.

getJDBCConnection()

Gets the JDBC connection in use by this page (possible null if no connection in use).

getRequestType()

Detect whether you are running in the Servlet, Command Line, or Programmatic context. For example, if the request type is Servlet then you can cast the XSQLPageRequest object to the more specific XSQLServletPageRequest to access servlet-specific methods such as getHttpServletRequest, getHttpServletResponse, and getServletContext.

getAttributeAllowingParam

Retrieves the attribute value from an element, resolving any XSQL lexical parameter references that might appear in value of the attribute. Typically this method is applied to the action element itself, but it is also useful for accessing attributes of subelements. To access an attribute value without allowing lexical parameters, use the standard getAttribute() method on the DOM Element interface.

appendSecondaryDocument

Appends the contents of an external XML document to the root of the action handler result content.

addResultElement

Simplifies appending a single element with text content to the root of the action handler result content.

firstColumnOfFirstRow

Returns the first column value of the first row of a SQL statement. Requires the current page to have a connection attribute on its document element, or an error is returned.

getBindVariableCount

Returns the number of tokens in the space-delimited list of bind-params. This number indicates how many bind variables are expected to be bound to parameters.

handleBindVariables

Manages the binding of JDBC bind variables that appear in a prepared statement with the parameter values specified in the bind-params attribute on the current action element. If the statement is already using a number of bind variables prior to call this method, you can pass the number of existing bind variable slots in use as well.

reportErrorIncludingStatement

Reports an error. The error includes the offending (SQL) statement that caused the problem and optionally includes a numeric error code.

reportFatalError

Reports a fatal error.

reportMissingAttribute

Reports an error that a required action handler attribute is missing by using the <xsql-error> element.

reportStatus

Reports action handler status by using the <xsql-status> element.

requiredConnectionProvided

Checks whether a connection is available for this request and outputs an errorgram into the page if no connection is available.

variableValue

Returns the value of a lexical parameter, taking into account all scoping rules that might determine its default value.

Example 15-19 shows a custom action handler named MyIncludeXSQLHandler that leverages one of the built-in action handlers. It uses arbitrary Java code to modify the XML fragment returned by this handler before appending its result to the XSQL page.

You may need to write custom action handlers that work differently based on whether the page is requested through the XSQL servlet, the XSQL command-line utility, or programmatically through the XSQLRequest class.You can invoke getPageRequest() in your action handler implementation to obtain a reference to the XSQLPageRequest interface for the current page request. By calling getRequestType() on the XSQLPageRequest object, you can determine whether the request is coming from the Servlet, Command Line, or Programmatic routes. If the return value is Servlet, then you can access the HTTP servlet request, response, and servlet context objects as shown in Example 15-20.

Example 15-20 Testing for the Servlet Request

XSQLServletPageRequest xspr = (XSQLServletPageRequest)getPageRequest();
if (xspr.getRequestType().equals("Servlet")) {
HttpServletRequest req = xspr.getHttpServletRequest();
HttpServletResponse resp = xspr.getHttpServletResponse();
ServletContext cont = xspr.getServletContext();
// Do something here with req, resp, or cont. Note that writing to the response
// directly from a handler produces unexpected results. All the servlet or your
// custom Serializer to write to the servlet response output stream at the right
// moment later when all action elements have been processed.
}

Using Multivalued Parameters in Custom XSQL Actions

XSQLActionHandlerImpl is the base class for custom XSQL actions. It supports the following:

Array-named lexical parameter substitution

Array-named bind variables

Simple-valued parameters

If your custom actions use methods such as getAttributeAllowingParam(), getActionElementContent(), or handleBindVariables() from this base class, you pick up multi-valued parameter functionality for free in your custom actions.

Use the getParameterValues() method on the XSQLPageRequest interface to explicitly get a parameter value as a String[]. The helper method variableValues() in XSQLActionHandlerImpl enables you to use this functionality from within a custom action handler if you need to do so programmatically.

Implementing Custom XSQL Serializers

You can implement a user-defined serializer class to control how the final XSQL datapage is serialized to a text or binary stream. A user-defined serializer must implement the oracle.xml.xsql.XSQLDocumentSerializer interface. The interface contains the following single method:

Techniques for Using a Custom Serializer

There are two ways to use a custom serializer, depending on whether you need to first perform an XSLT transformation before serializing or not.

To perform an XSLT transformation before using a custom serializer, add the serializer="java:fully.qualified.ClassName" in the <?xml-stylesheet?> processing instruction at the top of your page. The following examples illustrates this technique:

Assigning a Short Name to a Custom Serializer

You can also assign a short name to your custom serializers in the <serializerdefs> section of the XSQL configuration file. You can then use the nickname in the serializer attribute instead to save typing. Note that the short name is case sensitive.

Assume that you have the information shown in Example 15-22 in your XSQL configuration file.

The XSQLPageRequest interface supports both a getWriter() and a getOutputStream() method. Custom serializers can call getOutputStream() to return an OutputStream instance into which binary data can be serialized. When you use the XSQL servlet, writing to this output stream results in writing binary information to the servlet output stream.

The serializer shown in Example 15-23 illustrates an example of writing a dynamic GIF image. In this example the GIF image is a static "ok" icon, but it shows the basic technique that a more sophisticated image serializer needs to use.

Using the XSQL command-line utility, the binary information is written to the target output file. Using the XSQLRequest API, two constructors exist that allow the caller to supply the target OutputStream to use for the results of page processing.

Note that your serializer must either call getWriter() for textual output or getOutputStream() for binary output but not both. Calling both in the same request raises an error.

Using a Custom XSQL Connection Manager for JDBC Datasources

As an alternative to defining your named connections in the XSQL configuration file, you can use one of the two provided XSQLConnectionManager implementations. These implementations enable you to use your servlet container's JDBC Datasource implementation and related connection pooling features.

Consider using this connection manager if your servlet container's datasource implementation does not use the Oracle JDBC driver. Features of the XSQL pages system such as <xsql:ref-cursor-function> and <xsql:include-owa> are not available when you do not use an Oracle JDBC driver.

oracle.xml.xsql.XSQLOracleDatasourceConnectionManager

Consider using this connection manager when your datasource implementation returns JDBC PreparedStatement and CallableStatement objects that implement the oracle.jdbc.PreparedStatement and oracle.jdbc.CallableStatement interfaces. The Oracle Application Server has a datasource implementation that performs this task.

When using either of the preceding alternative connection manager implementations, the value of the connection attribute in your XSQL page template is the JNDI name used to look up your desired datasource. For example, the value of the connection attribute might look like the following:

jdbc/scottDS

java:comp/env/jdbc/MyDatasource

If you are not using the default XSQL pages connection manager, then needed connection pooling functionality must be provided by the alternative connection manager implementation. In the case of the preceding two options based on JDBC datasources, you must properly configure your servlet container to supply the connection pooling. See your servlet container documentation for instructions on how to properly configure the datasources to offer pooled connections.

Writing Custom XSQL Connection Managers

You can provide a custom connection manager to replace the built-in connection management mechanism. To provide a custom connection manager implementation, you must perform the following steps:

You can set your custom connection manager factory as the default connection manager factory by providing the class name in the XSQL configuration file. Set the factory in the following section:

<!--
| Set the name of the XSQL Connection Manager Factory
| implementation. The class must implement the
| oracle.xml.xsql.XSQLConnectionManagerFactory interface.
| If unset, the default is to use the built-in connection
| manager implementation in
| oracle.xml.xsql.XSQLConnectionManagerFactoryImpl
+-->
<connection-manager>
<factory>oracle.xml.xsql.XSQLConnectionManagerFactoryImpl</factory>
</connection-manager>

In addition to specifying the default connection manager factory, you can associate a custom connection factory with a XSQLRequest object by using APIs provided.

The responsibility of the XSQLConnectionManagerFactory is to return an instance of an XSQLConnectionManager for use by the current request. In a multithreaded environment such as a servlet engine, the XSQLConnectionManager object must ensure that a single XSQLConnection instance is not used by two different threads. This aim is realized by marking the connection as in use for the time between the getConnection() and releaseConnection() method calls. The default XSQL connection manager implementation automatically pools named connections and adheres to this thread-safe policy.

If your custom implementation of XSQLConnectionManager implements the optional oracle.xml.xsql.XSQLConnectionManagerCleanup interface, then your connection manager can clean up any resources it has allocated. For example, if your servlet container invokes the destroy() method on the XSQLServlet servlet, which can occur during online administration of the servlet for example, the connection manager has a chance to clean up resources as part of the servlet destruction process.

Accessing Authentication Information in a Custom Connection Manager

To use the HTTP authentication mechanism to get the username and password to connect to the database, write a customized connection manager. You can then invoke a getConnection() method to obtain the needed information.

You can write a Java program that follows these steps:

Pass an instance of the oracle.xml.xsql.XSQLPageRequest interface to the getConnection() method.

Invoke getRequestType() to ensure that the request type is Servlet.

Cast the XSQLPageRequest object to an XSQLServletPageRequest.

Call getHttpServletRequest() on the result of the preceding step.

Obtain the authentication information from the javax.servlet.http.HttpServletResponse object returned by the previous call.

Implementing a Custom XSQLErrorHandler

You may want to control how serious page processor errors such as an unavailable connection are reported to users. You can achieve this task by implementing the oracle.xml.xsql.XSQLErrorHandler interface. The interface contains the following single method signature:

You can provide a class that implements the XSQLErrorHandler interface to customize how the XSQL pages processor writes error messages. The new XSQLError object encapsulates the error information and provides access to the error code, formatted error message, and so on.

You can control which custom XSQLErrorHandler implementation is used in the following distinct ways:

Define the name of a custom XSQLErrorHandler implementation class in the XSQL configuration file. You must provide the fully-qualified class name of your error handler class as the value of the /XSQLConfig/processor/error-handler/class entry.

If the XSQL processor can load this class, and if it correctly implements the XSQLErrorHandler interface, then it uses this class as a singleton and replaces the default implementation globally wherever page processor errors are reported.

Override the error writer on a per page basis by using the errorHandler (or xsql:errorHandler) attribute on the document element of the page. The attribute value is the fully-qualified class name of a class that implements the XSQLErrorHandler interface. This class reports the errors for this page only. The class is instantiated on each page request by the page engine.

You can use a combination of the preceding approaches if needed.

Providing a Custom XSQL Logger Implementation

You can optionally register custom code to handle the logging of the start and end of each XSQL page request. Your custom logger code must provide an implementation of the oracle.xml.xsql.XSQLLoggerFactory and oracle.xml.xsql.XSQLLogger interfaces.

You can provide a class that implements the XSQLLoggerFactory interface to decide how XSQLLogger objects are created (or reused) for logging. The XSQL processor holds a reference to the XSQLLogger object returned by the factory for the duration of a page request. The processor uses it to log the start and end of each page request by invoking the logRequestStart() and logRequestEnd() methods.

The classes in Example 15-25 and Example 15-26 illustrate a trivial implementation of a custom logger. The XSQLLogger implementation in Example 15-25 notes the time the page request started. It then logs the page request end by printing the name of the page request and the elapsed time to System.out.

To register a custom logger factory, edit the XSQLConfig.xml file and provide the name of your custom logger factory class as the content to the /XSQLConfig/processor/logger/factory element. Example 15-27 illustrates this technique.