Skip navigation.
Downloads | Product Documentation | Support

Client Application Developer's Guide

# Advanced Topics

This chapter provides information on miscellaneous topics related to client programming. It covers the following topics:

- Applying Filter Data Service Results
- Ordering and Truncating Data Service Results
- Consuming Large Result Sets (Streaming API)
- Using Ad Hoc Queries
- Transaction Considerations
- Setting Up Data Source Aliases for Relational Sources Accessed by Liquid Data
- Setting Up Data Source Aliases for Relational Sources Accessed by Liquid Data

# Applying Filter Data Service Results

The Filter API enables client applications to apply filtering conditions to the information returned by data service functions. In a sense, filtering allows client applications to extend a data service interface by allowing them to specify more about how data objects are to be instantiated and returned by functions.

The Filter API alleviates data service designers from having to anticipate every possible data view that their clients may require and to implement a data service function for each view. Instead, the designer may choose to specify a broader, more generic interface for accessing a business entity and allow client applications to control views as desired through filters.

Objects in the function return set that do not meet the condition are blocked from the results. (The evaluation occurs at the server, so objects that are filtered are not passed over the network. Often they are not even retrieved from the underlying sources.) A filter is similar to a WHERE clause in an XQuery or SQL statement—it applies conditions to a possible result set. You can have multiple filter conditions using `AND` and `OR` operators.

Note: The Javadoc that describes the Filter API is available at:

http://download.oracle.com/docs/cd/E13190_01/liquiddata/docs85/ldapiJavadoc/index.html

## Using Filters

Filtering capabilities are available to mediator and Liquid Data control client applications. You use filter conditions to specify what data you want returned, sort the data, or limit the number of records returned. To use filters in a mediator client application, import the appropriate package and use the supplied interfaces for creating and applying filter conditions. Liquid Data Control clients get the interface automatically. When a function is added to a control, a corresponding WithFilter function is added as well.

The filter package is named as follows:

>     	com.bea.ld.filter.FilterXQuery;

To use a filter, perform the following steps:

1. Create an FilterXQuery object, such as:

>     FilterXQuery myFilter = new FilterXQuery();

2. Add a filter condition to the object using the `addFilter()` method. With this function you can specify what node your filter condition will apply to and specify the number of records to be returned based on a limit; for example, you can specify the filter will apply to customer orders where only orders with an amount over a specified value will be returned.

The `addFilter()` method has the following signature:

>     public void addFilter(java.lang.String appliesTo,
>                           java.lang.String field,
>                           java.lang.String operator,
>                           java.lang.String value,
>                            java.lang.Boolean everyChild)

The method takes the following arguments:

- `appliesTo` indicates the node that filtering affects. That is, if a node specified by the field argument does not meet the condition, `appliesTo` nodes are filtered out.
- `field` is the node against which the filtering condition is tested.
- `operator` and `value` together compose the condition statement. The `operator` parameter specifies the type of comparison to be made against the specified `value`. The section Filter Operators describes the available operators.
- `everyChild` is an optional parameter. It is set to false by default. Specifying true for this parameter indicates that only those child elements that meet the filter criteria will be returned. For example, by specifying an operator of GREATER_THAN (or ">") and a value of 1000, only records for customers where all orders are over 1000 will be returned. A customer that has an order amount less than 1000 will not be returned, although other order amounts might be greater than 1000.

The following is an example of an add filter method where those orders with an order amount greater than 1000 will be returned (note that `everyChild` is not specified, so order amounts below 1000 will be returned):

>          myFilter.addFilter("CUSTOMERS/CUSTOMER/ORDER",
>                             "CUSTOMERS/CUSTOMER/ORDER/ORDER_AMOUNT",
>                             ">",
>                              "1000");

3. Use the Mediator API call `setFilterCondition()` to add the filter to a data service, passing the FilterXQuery instance as an argument. For example,

>     CUSTOMER custDS = CUSTOMER.getInstance(ctx, "RTLApp");
>     custDS.setFilterCondition(myFilter);

4. Invoke the data service function. (For more information on invoking data service functions, see Accessing Data Services from Java Clients.)

## Specifying Filter Effects

If a filter condition applied to a specified element value resolves to false, an element is not included in the result set. The element that is filtered out is specified as the first argument to the `addFilter()` function.

The effects of a filter can vary, depending on the desired results. For example, consider the CUSTOMERS data object shown in Figure 8-1. It contains several complex elements (CUSTOMER and ORDERS) and several simple elements, including ORDER_AMOUNT. You can apply a filter to any elements in this hierarchy.

Figure 8-1 Nested Value Filtering

In general, with nested XML data, a condition such as "CUSTOMER/ORDER/ORDER_AMOUNT > 1000" can affect what objects are returned in several ways. For example, it can cause all CUSTOMER objects to be returned, but filter ORDERS that have an amount less than 1000.

Alternatively, it can cause only CUSTOMER objects to be returned that have at least one large order, and all ORDER objects are returned for every CUSTOMER. Further, it can cause only CUSTOMER objects to be returned for which every ORDER is greater than 1000. For example,

>     	XQueryFilter myFilter = new XQueryFilter();
>     	myFilter.addFilter(			"CUSTOMERS/CUSTOMER",
>     				"CUSTOMERS/CUSTOMER/ORDER/ORDER_AMOUNT",
>     				FilterXQuery.GREATER_THAN,"1000",true);

Note that in the optional fourth parameter `everyChild = true`, which is false by default. By setting this parameter to true, only those CUSTOMER objects for which every ORDER is greater than 1000 will be returned.

The following examples show how filters can be applied in several different ways:

- Returns all CUSTOMER objects but only their large ORDER objects:

>     XQueryFilter myFilter = new XQueryFilter();
>     Filter f1 = myFilter.createFilter(
>                          "CUSTOMERS/CUSTOMER/ORDER/ORDER_AMOUNT",
>                          FilterXQuery.GREATER_THAN,"1000");
>     myFilter.addFilter("CUSTOMERS/CUSTOMER/ORDER", f1);

- Returns only CUSTOMER objects that have at least one large order but view all ORDER objects for such CUSTOMER:

>     XQueryFilter myFilter = new XQueryFilter();
>     myFilter.addFilter("CUSTOMERS/CUSTOMER",
>                         "CUSTOMERS/CUSTOMER/ORDER/ORDER_AMOUNT",
>                          FilterXQuery.GREATER_THAN,"1000");

- Returns only CUSTOMER objects that have at least one large order and return only large ORDER objects:

>     XQueryFilter myFilter = new XQueryFilter();
>     myFilter.addFilter("CUSTOMERS/CUSTOMER",
>                        "CUSTOMERS/CUSTOMER/ORDER/ORDER_AMOUNT",
>                         FilterXQuery.GREATER_THAN,"1000");
>     myFilter.addFilter("CUSTOMERS/CUSTOMER/ORDER",
>                        "CUSTOMERS/CUSTOMER/ORDER/ORDER_AMOUNT",
>                         FilterXQuery.GREATER_THAN,"1000");

The last example is a compound filter—a filter with two conditions.

## Filter Operators

You can use the following operators in filters:

- LESS_THAN (<)
- GREATER_THAN (>)
- LESS_THAN_EQUAL (<=)
- GREATER_THAN_EQUAL (>=)
- EQUAL (=)
- NOT_EQUAL (!=)
- matches (for string equality)
- sql-like (tests whether a string contains a specified pattern)

These compound operators can be applied to more than one filter:

- OR
- NOT
- AND

The following example uses the AND operator to apply a combination of filters to a result set, given a data service instance `customerDS`:

>     FilterXQuery myFilter = new FilterXQuery();
>     Filter f1 = myFilter.createFilter("CUSTOMER_PROFILE/ADDRESS/ISDEFAULT",
>     		 			FilterXQuery.NOT_EQUAL,"0");
>     Filter f2 = myFilter.createFilter("CUSTOMER/ADDRESS/STATUS",
>     					FilterXQuery.EQUAL,
>     					"\"ACTIVE\"");
>     Filter f3 = myFilter.createFilter(f1,f2, FilterXQuery.AND);
>     Customer customerDS = Customer.getInstance(ctx, "RTLApp");
>     CustomerDS.setFilterCondition(myFilter);

# Ordering and Truncating Data Service Results

An ordering condition is a type of filter that lets you specify the order in which results are returned from a data service. They allow you to arrange results in either ascending or descending order based on the value of a specified property.

The ordering methods are in the FilterXQuery class. The following example shows how to use ordering. It gets a list of customer profiles in ascending order based on the dates the person became a customer.

>     	FilterXQuery myFilter = new FilterXQuery();
>     	myFilter.addOrderBy("CUSTOMER_PROFILE","CustomerSince" ,
>     				FilterXQuery.ASCENDING);
>     	ds.setFilterCondition(myFilter);
>     	DataObject objArrayOfCust =
>     				(DataObject) ds.invoke("getCustomer", null);

Similarly, you can set the maximum number of results that can be returned from a function. The `setLimit()` function limits the number of elements in an array element to the specified number. And on a repeating node, it makes sense to specify a limit on the results to be returned. (Setting the limits on non-repeating nodes does not truncate the results.)

The following shows how to use the `setLimit()` method. It limits the number of active address in the result set (filtering out active addresses) to 10 given a data service instance `ds`:

>        FilterXQuery myFilter = new FilterXQuery();
>        Filter f2 = myFilter.createFilter("CUSTOMER_PROFILE/ADDRESS",
>                                           FilterXQuery.EQUAL,"\"INACTIVE\"");
>        myFilter.addFilter("CUSTOMER_PROFILE", f2);
>        myFilter.setLimit("CUSTOMER_PROFILE", "10");
>        ds.setFilterCondition(myFilter);

# Consuming Large Result Sets (Streaming API)

This section discusses further programming topics related to client programming with the Data Service Mediator API. It includes the following topics:

- Using the Streaming Interface
- Writing Data Service Function Results to a File

## Using the Streaming Interface

When a function in the standard data service interface is called, the requested data is first materialized in the system memory of the server machine. If the function is intended to return a large amount of data, in-memory materialization of the data may be impractical. This may be the case, for example, for administrative functions that generate "inventory reports" of the data exposed by Liquid Data. For such cases, Liquid Data can serve information as an output stream.

Liquid Data leverages the WebLogic XML Streaming API for its streaming interface. The WebLogic Streaming API is similar to the standard SAX (Streaming API for XML) interface. However, instead of contending with the complexity of the event handlers used by SAX, the WebLogic Streaming API lets you use stream-based (or pull-based handling of XML documents in which you step through the data object elements. As such, the WebLogic Streaming API affords more control than the SAX interface, in that the consuming application initiates events, such as iterating over attributes or skipping ahead to the next element, instead of reacting to them.

Note: For more information on the WebLogic Streaming API, see "Using the WebLogic XML Streaming API" at http://download.oracle.com/docs/cd/E13222_01/wls/docs81/xml/xml_stream.html.

It is important to note that although serving data as a stream relieves the server from having to materialize large objects in memory, the server is using the request thread while output streaming occurs. This can tie up a thread for quite a while and affect the server's ability to respond to other service requests in a timely fashion. The streaming API is intended for use only for administrative sorts of uses, and should be avoided except at off-peak times or in non-production environments.

Liquid Data limits its use to applications that are local to the WebLogic server. The application must use the local server interface to the WebLogic server to use the Streaming API.

Note: Alternatively, a client application that is remote can use the ToFile interface to have the output written to a file on the server's file system, and then FTP to the server to get the file. For more information, see Writing Data Service Function Results to a File on page 8-11.

You can get Liquid Data information as a stream by using either an ad hoc or an untyped data service interface.

Note: Streaming is not supported through static interfaces.

The streaming interface is in these classes in the `com.bea.ld.dsmediator.client` package:

- StreamingDataService
- StreamingPreparedExpression

Using these interfaces is very similar to using their SDO mediator client API equivalents. However, instead of a document object, they return data as an `XMLInputStream`. For functions that take complex elements (possibly with a large amount of data) as input parameters, `XMLInputStream` is supported as an input argument as well. The following is a example:

>     StreamingDataService ds = StreamingDataServiceFactory.getInstance(
>                                  context,
>                                  "ld:DataServices/RTLServices/Customer");
>     XMLInputStream stream = ds.invoke("getCustomerByCustID", "CUSTOMER0");

The previous example shows the dynamic streaming interface. The following example uses an ad hoc query:

>     String adhocQuery =
>          "declare namespace ns0=\"ld:DataServices/RTLServices/Customer\";\n" +
>          "declare variable $cust_id as xs:string external;\n" +
>          "for $customer in ns0:getCustomerByCustID($cust_id)\n" +
>          "return\n" +
>          "   $customer\n";
>     StreamingPreparedExression expr =
>                     DataServiceFactory.prepareExpression(context, adhocQuery);

If you have external variables in the query string (`adhocQuery` in the above example), you will also need to do the following:

>     	expr.bindString("$cust_id","CUSOMER0");
>     	XMLInputStream xml = expr.executeQuery();

Note: For more information on using the dynamic and ad hoc interfaces, see Using the Dynamic Data Service Interface in Accessing Data Services from Java Clients and Using Ad Hoc Queries on page 8-11. Also, a Javadoc that contains descriptions of the StreamingDataService interface is available in the Javadoc that describes the Filter API is available at:

http://download.oracle.com/docs/cd/E13190_01/liquiddata/docs85/ldapiJavadoc/index.html

Listing 8-1 shows an example of a method that reads the XML input stream. This method uses an attribute iterator to print out attributes and namespaces in an XML event and throws an XMLStream exception if an error occurs.

Listing 8-1 Sample Streaming Application

>     import weblogic.xml.stream.Attribute;
>     import weblogic.xml.stream.AttributeIterator;
>     import weblogic.xml.stream.ChangePrefixMapping;
>     import weblogic.xml.stream.CharacterData;
>     import weblogic.xml.stream.XMLEvent;
>     import weblogic.xml.stream.EndDocument;
>     import weblogic.xml.stream.EndElement;
>     import weblogic.xml.stream.EntityReference;
>     import weblogic.xml.stream.Space;
>     import weblogic.xml.stream.StartDocument;
>     import weblogic.xml.stream.XMLInputStream;
>     import weblogic.xml.stream.XMLInputStreamFactory;
>     import weblogic.xml.stream.XMLName;
>     import weblogic.xml.stream.XMLStreamException;
>     import java.io.FileInputStream;
>     import java.io.FileNotFoundException;
>     public class ComplexParse {

>     public void parse(XMLEvent event)throws XMLStreamException
>     {
>       switch(event.getType()) {
>       case XMLEvent.START_ELEMENT:
>       StartElement startElement = (StartElement) event;
>       System.out.print("<" + startElement.getName().getQualifiedName() );
>       AttributeIterator attributes = startElement.getAttributesAndNamespaces();
>       while(attributes.hasNext()){
>         Attribute attribute = attributes.next();
>         System.out.print(" " + attribute.getName().getQualifiedName() +
>         "='" + attribute.getValue() + "'");
>       }
>       System.out.print(">");
>         break;
>       case XMLEvent.END_ELEMENT:
>         System.out.print("</" + event.getName().getQualifiedName() +">");
>         break;
>       case XMLEvent.SPACE:
>       case XMLEvent.CHARACTER_DATA:
>         CharacterData characterData = (CharacterData) event;
>         System.out.print(characterData.getContent());
>         break;
>       case XMLEvent.COMMENT:
>         // Print comment
>         break;
>       case XMLEvent.PROCESSING_INSTRUCTION:
>         // Print ProcessingInstruction
>         break;
>       case XMLEvent.START_DOCUMENT:
>         // Print StartDocument
>         break;
>       case XMLEvent.END_DOCUMENT:
>         // Print EndDocument
>         break;
>       case XMLEvent.START_PREFIX_MAPPING:
>         // Print StartPrefixMapping
>         break;
>       case XMLEvent.END_PREFIX_MAPPING:
>         // Print EndPrefixMapping
>         break;
>       case XMLEvent.CHANGE_PREFIX_MAPPING:
>         // Print ChangePrefixMapping
>         break;
>       case XMLEvent.ENTITY_REFERENCE:
>         // Print EntityReference
>         break;
>       case XMLEvent.NULL_ELEMENT:
>           throw new XMLStreamException("Attempt to write a null event.");
>       default:
>        throw new XMLStreamException("Attempt to write unknown event["
>                                      +event.getType()+"]");
>       }
>     }

## Writing Data Service Function Results to a File

You can write serialized results of a data service function to a file using a ToFile function. A ToFile function is generated automatically for each function defined in the data service. For security reasons, it writes only to a file on the server's file system.

These functions provide services that are similar to the Streaming API. They are intended for creating reports or an inventory of data service information. However, whereas the Streaming APIs can only be invoked by an application that resides on the same machine as the server, the ToFile functions provided by the on-streaming API can be invoked remotely.

The following example shows how to write to a file from the untyped interface.

>      StreamingDataService sds =
>       	DataServiceFactory.newStreamingDataService(
>          		context,"RTLApp","ld:DataServices/RTLServices/Customer"");
>      sds.writeOutputToFile("getCustomer", null, "streamContent.txt");
>      sds.closeStream();

# Using Ad Hoc Queries

An ad hoc query is an XQuery function that is not stored in a data service, but is instead defined by the client application. You can use an ad hoc query to execute any XQuery function, possibly against a data source defined on a remote Liquid Data server or even with no back-end data source at all.

To use ad hoc queries, use the `PreparedExpression` interface of the Mediator API. The `PreparedExpression` interface is similar to the `PreparedStatement` interface of JDBC. You create the prepared expression by passing the function body as a string in the constructor (along with the JNDI server context and the application name), then call the `executeQuery()` method on the prepared expression as follows:

>     PreparedExpression adHocQuery =
>     	DataServiceFactory.prepareExpression(
>     		context, "RTLApp", "<CustomerID>CUSTOMER0</CustomerID>");
>     XmlObject adHocResult = adHocQuery.executeQuery();

The above sample merely returns an XML node named `CUSTOMER_ID`. A more useful ad hoc query, however, would typically invoke data service functions and process their results in some way.

To invoke data service functions in ad hoc queries, the query needs to import the namespace of the data service to be used. It can then invoke the data service's function. The following returns the results of a data service function named `getCustomers()`, which is in the name space "ld:DataServices/RTLServices/Customer":

>     String queryStr =
>           "declare namespace ns0=\"ld:DataServices/RTLServices/Customer\";" +
>           "<Results>" +
>           "  { for $customer_profile in ns0:getCustomer()" +
>           "     return $customer_profile }" +
>           "</Results>";
>     PreparedExpression adHocQuery =
>           DataServiceFactory.prepareExpression(context,"RTLApp",queryStr );
>     XmlObject objResult = (XmlObject) adHocQuery.executeQuery();

Liquid Data passes information back to the ad hoc query caller as an XMLObject object. Because all typed data objects implement the XMLObject interface, ad hoc query results that conform to a deployed schema can be downcast to the type of the schema.

For data service functions that return arrays, you must create a root element in the ad hoc query as a container for the array because an XmlObject must have a single root type. For example, the data service function getCustomer() invoked in the code sample above returns an array of CUSTOMER_PROFILE elements; therefore, the ad hoc query specifies a container <Results> to hold the returned array.

Security policies defined for a data service apply to the data service calls in an ad hoc query as well. Appropriate credentials must be passed when creating the JNDI initial context in an ad hoc query that uses secured resources. For more information, see Getting a WebLogic JNDI Context for Liquid Data, in Accessing Data Services from Java Clients.

Like the PreparedStatement interface of JDBC, you can bind variables dynamically in ad hoc query expressions. The ad hoc query PreparedExpression interface includes a number of methods for binding values of various types, named in the form `bind` `Type`.

You bind a variable to a value by specifying the variable as a qualified name (qname) and passing the value in the bind method as follows:

>     PreparedExpression adHocQuery = DataServiceFactory.preparedExpression(
>                      context, "RTLApp",
>                     "declare variable $i as xs:int external;
>                      <result><zip>{fn:data($i)}</zip></result>");
>     adHocQuery.bindInt(new QName("i"),94133);
>     XmlObject adHocResult = adHocQuery.executeQuery();

QName stands for qualified name. For more information on qnames, see:

>     http://www.w3.org/TR/xmlschema-2/#QName

Listing 8-2 shows a complete ad hoc query example, using the preparedExpression interface and qualified names to pass values in bind methods.

Listing 8-2 Ad hoc query sample

>     import com.bea.ld.dsmediator.client.DataServiceFactory;
>     import com.bea.ld.dsmediator.client.PreparedExpression;
>     import com.bea.xml.XmlObject;
>     import javax.naming.InitialContext;
>     import javax.naming.NamingException;
>     import javax.xml.namespace.QName;
>     import weblogic.jndi.Environment;

>     public class AdHocQuery
>     {
>        public static InitialContext getInitialContext() throws NamingException {
>         Environment env = new Environment();
>         env.setProviderUrl("t3://localhost:7001");
>         env.setInitialContextFactory("weblogic.jndi.WLInitialContextFactory");
>         env.setSecurityPrincipal("weblogic");
>         env.setSecurityCredentials("weblogic");
>         return new InitialContext(env.getInitialContext().getEnvironment());
>        }

>        public static void main (String args[]) {
>          System.out.println("========== Ad Hoc Client ==========");
>          try {
>           StringBuffer xquery = new StringBuffer();
>           xquery.append("declare variable $p_firstname as xs:string external; \n");
>           xquery.append("declare variable $p_lastname as xs:string external;  \n");

>           xquery.append(
>            "declare namespace ns1=\"ld:DataServices/MyQueries/XQueries\"; \n");
>           xquery.append(
>            "declare namespace ns0=\"ld:DataServices/CustomerDB/CUSTOMER\"; \n\n");

>           xquery.append("<ns1:RESULTS>                                    \n");
>           xquery.append("{                                                \n");
>           xquery.append("    for $customer in ns0:CUSTOMER()              \n");
>           xquery.append("    where ($customer/FIRST_NAME eq $p_firstname  \n");
>           xquery.append("       and $customer/LAST_NAME eq $p_lastname)   \n");
>           xquery.append("    return                                       \n");
>           xquery.append("      $customer                                  \n");
>           xquery.append(" }                                               \n");
>           xquery.append("</ns1:RESULTS>                                   \n");

>              PreparedExpression pe = DataServiceFactory.prepareExpression(
>                             getInitialContext(), "RTLApp",  xquery.toString());
>              pe.bindString(new QName("p_firstname"), "Jack");
>              pe.bindString(new QName("p_lastname"),  "Black");
>              XmlObject results = pe.executeQuery();
>              System.out.println(results);

>            } catch (Exception e) {
>                e.printStackTrace();
>            }
>     }

# Transaction Considerations

The API to Liquid Data is supported internally by stateless EJBs; therefore, the data sources used by Liquid Data must support the trans-attribute settings of the EJB methods. The default settings for the methods are:

- NotSupported is the default for the execute query methods.
- Required is the default for `submit()`.

The trans-attribute for the `submit()` method is Required and cannot be changed. Other methods allow you to set the attribute to a value other than the default value by resetting `ReadTransactionAttribute` when creating a data service. For the `executeQuery` and `executeFunction` methods, you have the option of setting the trans-attribute to Required. You can set trans-attribute for `executeQueryToStream` and `executeFunctionToStream` to `Supported`.

For detailed information about the trans-attribute values of the EJBs, refer to section 17.6.2 of the EJB 2.0 specification. The specification is available at:

>     http://java.sun.com/products/ejb/docs.html.

# Setting Up Data Source Aliases for Relational Sources Accessed by Liquid Data

When you import metadata from relational sources, you can provide logic in your application that maps users to different data sources depending on the user's role. This is accomplished by creating an intercepter and adding an attribute to the RelationalDB annotation for each data service in your application.

The interceptor is a Java class that implements the `SourceBindingProvider` interface. This class provides the logic for mapping a users, depending on their current credentials, to a logical data source name or names. This makes it possible to control the level of access to relational physical source based on the logical data source names. For example, you could have the data source names cgDataSource1, cgDataSourc2, and cgDataSource3 defined on your WebLogic server and define the logic in your class so that an user who is an administrator can access all three data sources, but a normal user only has access to the data source cgDataSource1.

Note: All relational, update overrides, stored procedure data services, or stored procedure XFL files that refer to the same relational data source should also use the same source binding provider; that is, if you specify a source binding provider for at least one of the data service (`.ds`) files, you should set it for the rest of them.

To implement the interceptor logic, do the following:

1. Write a Java class `SQLInterceptor` that implements the interface `com.bea.ld.binds.SourceBindingsProvider` and define a `getBindings()` public method within the class. The signature of this method is:

>     public String getBinding(String genericLocator, boolean isUpdate)

The `genericLocator` parameter specifies the current logical data source name. The `isUpdate` parameter indicates whether a read or an update is occurring. A value of true indicates an update. A value of false indicates a read. The string returned is the logical data source name to which the user is to be mapped. Listing 8-3 shows an example `SQLInterceptor` class.

2. Compile your class into a `.jar` file.
3. In your application, save the `.jar` file in the APP-INF/lib directory of your WebLogic Workshop application.
4. Define the configuration interceptor for the data source in your `.ds` or `.xfl` files (or both if necessary) by adding a `sourceBindingProviderClassName` attribute to the RelationalDB annotation. The attribute must be assigned the name of a valid Java class, which is the name of as your interceptor class. For example (the attribute and Java class are in bold):

>     <relationalDB dbVersion="4" dbType="pointbase" name="cgDataSource" sourceBindingProviderClassName="sql.SQLInterceptor"/>

5. Compile and run you application. The interceptor will be invoked on execution.

Listing 8-3 Interceptor Class Example

>     package sql;

>     public class SqlProvider implements com.bea.ld.bindings.SourceBindingProvider{
>          public String getBinding(String dataSourceName, boolean isUpdate) {

>            weblogic.security.Security security = new weblogic.security.Security();
>            javax.security.auth.Subject subject = security.getCurrentSubject();
>            weblogic.security.SubjectUtils subUtils =
>                                                  new weblogic.security.SubjectUtils();

>             System.out.println(" the user name is " + subUtils.getUsername(subject));

>            if (subUtils.getUsername(subject).equals("weblogic"))
>                dataSourceName = "cgDataSource1";

>            System.out.println("The data source is " + dataSourceName);
>            System.out.println("SDO " + (isUpdate ? " YES " : " NO ") );

>            return dataSourceName;
>          }
>     }

# Setting Up Handlers for Web Services Accessed by Liquid Data

When you import metadata from web services for Liquid Data, you can create SOAP handler for intercepting SOAP requests and responses. The handler will be invoked when a web service method is called. You can chain handlers that are invoked one after another in a specific sequence by defining the sequence in a configuration file.

To create and chain handlers, follow these two steps:

1. Create Java class implements the interface`javax.xml.rpc.handler.GenericHandler`. This will be your handler. (Note that you could create more than one handler. For, example you could have one named WShandler and one named AuditHandler.) Listing 8-4 shows an example implementation of a `GenericHandler` class. Place your handlers in a folder named WShandler in Weblogic Workshop. (For detailed information on how to write handlers, refer to Creating SOAP Message Handlers to Intercept the SOAP Message in the Programming WebLogic Web Services.

Listing 8-4 Example Intercept Handler

>     package WShandler;

>     import java.util.Iterator;
>     import javax.xml.rpc.handler.MessageContext;
>     import javax.xml.rpc.handler.soap.SOAPMessageContext;
>     import javax.xml.soap.SOAPElement;
>     import javax.xml.rpc.handler.HandlerInfo;
>     import javax.xml.rpc.handler.GenericHandler;
>     import javax.xml.namespace.QName;

>     /**
>     * Purpose: Log all messages to the Server console
>     */
>     public class WShandler extends GenericHandler
>     {
>       HandlerInfo hinfo = null;

>       public void init (HandlerInfo hinfo) {
>         this.hinfo = hinfo;
>         System.out.println("*****************************");
>         System.out.println("ConsoleLoggingHandler r: init");
>         System.out.println(
>               "ConsoleLoggingHandler : init HandlerInfo" + hinfo.toString());
>         System.out.println("*****************************");
>        }

>        /**
>         * Handles incoming web service requests and outgoing callback requests
>        */
>        public boolean handleRequest(MessageContext mc) {
>               logSoapMessage(mc, "handleRequest");
>               return true;
>         }

>         /**
>         * Handles outgoing web service responses and
>         * incoming callback responses
>         */
>         public boolean handleResponse(MessageContext mc) {
>            this.logSoapMessage(mc, "handleResponse");
>            return true;
>         }

>         /**
>         * Handles SOAP Faults that may occur during message processing
>         */
>         public boolean handleFault(MessageContext mc){
>           this.logSoapMessage(mc, "handleFault");
>           return true;
>         }

>         public QName[] getHeaders() {
>           QName [] qname = null;
>           return qname;
>         }

>          /**
>          * Log the message to the server console using System.out
>          */
>          protected void logSoapMessage(MessageContext mc, String eventType){
>           try{
>             System.out.println("*****************************");
>             System.out.println("Event: "+eventType);
>             System.out.println("*****************************");
>            }
>            catch( Exception e ){
>              e.printStackTrace();
>            }
>          }

>          /**
>          * Get the method Name from a SOAP Payload.
>          */
>          protected String getMethodName(MessageContext mc){

>            String operationName = null;

>            try{
>              SOAPMessageContext messageContext = (SOAPMessageContext) mc;
>              // assume the operation name is the first element
>              // after SOAP:Body element
>              Iterator i = messageContext.
>                   getMessage().getSOAPPart().getEnvelope().getBody().getChildElements();
>              while ( i.hasNext() )
>              {
>                Object obj = i.next();
>                if(obj instanceof SOAPElement)
>                {
>                  SOAPElement e = (SOAPElement) obj;
>                  operationName = e.getElementName().getLocalName();
>                  break;
>                }
>              }
>            }
>            catch(Exception e){
>              e.printStackTrace();
>            }
>              return operationName;
>          }
>       }

1. Define a configuration file in your application. This file specifies the handler chain and the order in which the handlers will be invoked. The XML in this configuration file must conform to the schema shown in Listing 8-5.

Listing 8-5 Handler Chain Schema

>     <?xml version="1.0" encoding="UTF-8"?>

>     <xs:schema targetNamespace="http://www.bea.com/2003/03/wlw/handler/config/" xmlns="http://www.bea.com/2003/03/wlw/handler/config/" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
>     <xs:element name="wlw-handler-config">
>       <xs:complexType>
>         <xs:sequence>
>           <xs:element name="handler-chain" minOccurs="0" maxOccurs="unbounded">
>             <xs:complexType>
>               <xs:sequence minOccurs="0" maxOccurs="unbounded">
>                 <xs:element name="handler">
>                   <xs:complexType>
>                     <xs:sequence>
>                        <xs:element name="init-param"
>                            minOccurs="0" maxOccurs="unbounded">
>                           <xs:complexType>
>                              <xs:sequence>
>                                 <xs:element name="description"
>                                      type="xs:string" minOccurs="0"/>
>                                  <xs:element name="param-name" type="xs:string"/>
>                                  <xs:element name="param-value" type="xs:string"/>
>                                </xs:sequence>
>                               </xs:complexType>
>                             </xs:element>
>                             <xs:element name="soap-header"
>                                    type="xs:QName" minOccurs="0" maxOccurs="unbounded"/>
>                             <xs:element name="soap-role"
>                                      type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
>                            </xs:sequence>
>                            <xs:attribute name="handler-name"
>                                 type="xs:string" use="optional"/>
>                            <xs:attribute name="handler-class"
>                                 type="xs:string" use="required"/>
>                           </xs:complexType>
>                         </xs:element>
>                        </xs:sequence>
>                        <xs:attribute name="name" type="xs:string" use="required"/>
>                      </xs:complexType>
>                    </xs:element>
>                  </xs:sequence>
>                </xs:complexType>
>              </xs:element>
>            </xs:schema>

The following is an example of the handler chain configuration. In this configuration, there are two chains. One is named LoggingHandler. The other is named SampleHandler. The first chain invokes only one handler named AuditHandler. The handler-class attribute specifies the fully qualified name of the handler.

>     <?xml version="1.0"?>
>     <hc:wlw-handler-config name="sampleHandler" xmlns:hc="http://www.bea.com/2003/03/wlw/handler/config/">
>       <hc:handler-chain name="LoggingHandler">
>         <hc:handler
>            handler-name="handler1"handler-class="WShandler.AuditHandler"/>
>       </hc:handler-chain>
>       <hc:handler-chain name="SampleHandler">
>     	    <hc:handler
>             handler-name="TestHandler1" handler-class="WShandler.WShandler"/>
>     	     <hc:handler handler-name="TestHandler2"
>               handler-class="WShandler.WShandler"/>
>        </hc:handler-chain>
>       </hc:wlw-handler-config>

1. In your Liquid Data application, define the interceptor configuration for the method in the data service to which you want to attach the handler. To do this, add a line similar the bold text shown in the following example:

>     xquery version "1.0" encoding "WINDOWS-1252";

>     (::pragma xds <x:xds xmlns:x="urn:annotations.ld.bea.com"
>      targetType="t:echoStringArray_return"
>      xmlns:t="ld:SampleWS/echoStringArray_return">
>     <creationDate>2005-05-24T12:56:38</creationDate>
>     <webService targetNamespace=
>     "http://soapinterop.org/WSDLInteropTestRpcEnc"
>     wsdl="http://webservice.bea.com:7001/rpc/WSDLInteropTestRpcEncService?WSDL"/></x:xds>::)

>     declare namespace f1 = "ld:SampleWS/echoStringArray_return";

>     import schema namespace t1 = "ld:AnilExplainsWS/echoStringArray_return" at "ld:SampleWS/schemas/echoStringArray_param0.xsd";

>     (::pragma function <f:function xmlns:f="urn:annotations.ld.bea.com" kind="read" nativeName="echoStringArray" nativeLevel1Container="WSDLInteropTestRpcEncService" nativeLevel2Container="WSDLInteropTestRpcEncPort" style="rpc">
>     <params>
>       <param nativeType="null"/>
>     </params>
>     <interceptorConfiguration aliasName="LoggingHandler" fileName="ld:SampleWS/handlerConfiguration.xml" />
>       </f:function>::)

>     declare function f1:echoStringArray($x1 as element(t1:echoStringArray_param0)) as schema-element(t1:echoStringArray_return) external;
>     <interceptorConfiguration aliasName="LoggingHandler" fileName="ld:testHandlerWS/handlerConfiguration.xml">

Here the aliasName attribute specifies the name of the handler chain to be invoked and the fileName attribute specifies the location of the configuration file.

2. Include the JAR file in the library module that defines the handler class referred to in the configuration file.
3. Compile and run your application. Your handlers will be invoked in the order specified in the configuration file.