Skip navigation.

Client Application Developer's Guide

  Previous Next vertical dots separating previous/next from contents/index/pdf Contents View as PDF   Get Adobe Reader

Client Programming with Service Data Objects (SDO)

This chapter describes the Liquid Data client-side data programming model and framework based on Service Data Objects (SDO). It introduces SDO and describes common programming tasks undertaken with SDO. It covers the following topics:

 


What is Service Data Objects (SDO) Programming?

The Service Data Object (SDO) specification defines a Java-based programming architecture and API for data access. A central goal of SDO is to provide client applications with a unified programming model for working with data in a disconnected way, regardless of its physical source or format. SDO thereby simplifies the way applications use data.

SDO specifies a data programming API as well as an architecture. The architecture part of the specification describes the components for enabling data access, such as mediators which serve as the intermediary between the client and back-end sources. In SDO terms, Liquid Data is a member.

In terms of client data programming, SDO has characteristics in common with other data access technologies, such as JDBC and Java Data Objects (JDO). Like JDO, SDO provides a static API for accessing data through typed accessors (for example, getCustomerName()). Like JDBC's RowSet interface, SDO has a dynamic API for accessing data through untyped accessors (for example, getString("CUSTOMER_NAME")). What distinguishes SDO from other technologies, however, is that SDO gives applications both a static and a dynamic API for accessing data, along with a disconnected model for accessing externally persisted data.

 


SDO and Liquid Data

Liquid Data implements the SDO specification as its Java client programming model. In concrete terms, this means that when a client application invokes a read function on a data service through the Data Service Mediator API (also called the Mediator API) or Liquid Data control, it gets the information back as a data object. A data object is the fundamental component of the SDO programming model and represents a unit of structured information.

The role of data objects, along with other key components in the SDO framework, are summarized as follows:

Note: For more on the Data Service Mediator API, see Accessing Data Services from Java Clients.

See also For More Information on page 2-29.

Figure 2-1 SDO Components with Data Graph

SDO Components with Data Graph


 

Data objects are passed between the mediator and client applications in a data graph. A data graph can have only a single root object (for example, CUSTOMERDocument in Figure 2-1). For result involving repeating objects, therefore, a single root element prefixed by "ArrayOf" is introduced to serve as the data graph root node. For more information, see ArrayOf Types.

Liquid Data leverages XMLBeans technology to generate static interfaces from XML. As a result, many features of the underlying XMLBeans technology are available in SDO as well. For more information on XMLBeans, see http://xmlbeans.apache.org.

Furthermore, all SDO types inherit from XmlObject, so factory classes for creating instances and parsing data objects are present from the inherited XmlObject interface.

Looking at an SDO Client Application

This section presents a simple example (Listing 2-1) of an SDO client application. The example gets information from Liquid Data through the Mediator API by instantiating a remote interface to a data service and invoking data service functions. It extracts information for a customer, modifies it, and submits the change to the mediator for update to the source or sources.

Listing 2-1 Sample SDO Client Application

import java.util.Hashtable;
import javax.naming.InitialContext;
import dataServices.customerDB.customer.ArrayOfCUSTOMERDocument;
import dataservices.customerdb.CUSTOMER;
public class ClientApp {
   public static void main(String[] args) throws Exception {
      Hashtable h = new Hashtable();
h.put(Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.WLInitialContextFactory");
h.put(Context.PROVIDER_URL,"t3://localhost:7001");
h.put(Context.SECURITY_PRINCIPAL,"weblogic");
h.put(Context.SECURITY_CREDENTIALS,"weblogic");
Context context = new InitialContext(h);
      // get the Customer data service and run dynamic invocation of data service
CUSTOMER custDS = CUSTOMER.getInstance(context, "RTLApp");
ArrayOfCUSTOMERDocument myCustomer =
(ArrayOfCUSTOMERDocument) custDS.invoke("CUSTOMER", null);
     // get and show customer name
String existingFName =
myCustomer.getArrayOfCUSTOMER().getCUSTOMERArray(0).getFIRSTNAME();
String existingLName =
myCustomer.getArrayOfCUSTOMER().getCUSTOMERArray(0).getLASTNAME();
     System.out.println(" \n---------------- \n Before Change: \n");
System.out.println(existingFName + existingLName);
     // change the customer name
myCustomer.getArrayOfCUSTOMER().getCUSTOMERArray(0).setFIRSTNAME("J.B.");
myCustomer.getArrayOfCUSTOMER().getCUSTOMERArray(0).setLASTNAME("Kwik");
custDS.submit(myCustomer,"ld:DataServices/CustomerDB/CUSTOMER");
     // re-query and print new name
     myCustomer = (ArrayOfCUSTOMERDocument) custDS.invoke("CUSTOMER",null);
String newFName =
myCustomer.getArrayOfCUSTOMER().getCUSTOMERArray(0).getFIRSTNAME();
String newLName =
myCustomer.getArrayOfCUSTOMER().getCUSTOMERArray(0).getLASTNAME();
String newName = newFName.concat(newLName);
     System.out.println(" \n---------------- \n After Change: \n");
System.out.println(newFName + newLName); }
}

The example above includes the following processing steps:

  1. First the application instantiates a remote interface to the Customer data service, passing a JNDI context that identifies the WebLogic Server where Liquid Data is deployed.
  2. It then calls the invoke() function of the data service, pouring the results into an ArrayOfCUSTOMERDocument object.
  3. A new value for the FIRSTNAME and LASTNAME property of the CUSTOMER is set and the change is submitted.
  4. The invoke() function is executed again, and the results are printed to output.

In the sample, an SDO is acquired through the Data Service Mediator API and modified through the SDO static API. Keep in mind that you can acquire data objects through the Liquid Data control as well. Therefore, it is useful to note the difference in the sample between mediator API calls and SDO calls.

The data service interface is instantiated and invoked through mediator API calls as follows:

ArrayOfCUSTOMERDocument myCustomer = 
( ArrayOfCUSTOMERDocument) ds.invoke("CUSTOMER", null);

Once the data object is created, its properties are accessed using the SDO static interface (which returns the actual type of that node):

myCustomer.getArrayOfCUSTOMER().getCUSTOMERArray(0).getFIRSTNAME();

As mentioned elsewhere, the SDO client data programming model includes both static and dynamic interfaces. The equivalent call using the dynamic interface would be as follows:

myCustomer.getArrayOfCUSTOMER().getCUSTOMERArray(0).get("FIRSTNAME");

(This will returns an Object instance that you will need to cast to the type ordinarily returned by the static interface.)

Finally, the change is submitted to the data service mediator for propagation to the back-end source through the submit() function in the mediator interface.

Although code for handling exceptions is not shown in the example, a runtime error in SDO throws an SDOException. If an exception is generated by a data source, it is wrapped in an SDOException.

Note: For more information on the Mediator API, see Accessing Data Services from Java Clients.

For complete documentation on the mediator and SDO APIs, refer to the SDO Update Javadoc.

Looking at a Data Graph

Data objects and data graphs can be serialized and printed to standard output. In fact, viewing a printed data graph when developing client applications can help you understand how data objects are composed.

Listing 2-2 shows a data graph associated with a modified data object. The printout is produced by the following code:

myCustomer.getArrayOfCUSTOMER().getCUSTOMERArray(0).setFIRSTNAME("J.B.");
myCustomer.getArrayOfCUSTOMER().getCUSTOMERArray(0).setLASTNAME("Nimble");
System.out.println(myCustomer.getDataGraph());

Notice the data graph features in the following listing examples:

For more information on data graphs, see Working with Data Graphs.

Listing 2-2 Serialized Data Graph

<com:datagraph xmlns:com="commonj.sdo">
<xsd>
<xs:schema
targetNamespace="ld:DataServices/CustomerDB/CUSTOMER"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:single="ld:DataServices/CustomerDB/CUSTOMER">
<xs:include schemaLocation="CUSTOMER.xsd"/>
<xs:element name="ArrayOfCUSTOMER">
<xs:complexType>
<xs:sequence>
<xs:element ref="single:CUSTOMER" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
</xsd>
<changeSummary>
<CUSTOMER com:ref="/ArrayOfCUSTOMER/CUSTOMER[1]">
<FIRST_NAME>J. B.</FIRST_NAME>
<LAST_NAME>Nimble</LAST_NAME>
</CUSTOMER></changeSummary>
<ns0:ArrayOfCUSTOMER xmlns:ns0="ld:DataServices/CustomerDB/CUSTOMER">
<ns0:CUSTOMER>
<CUSTOMER_ID>CUSTOMER1</CUSTOMER_ID>
<FIRST_NAME>Jack B. </FIRST_NAME><LAST_NAME>Quick</LAST_NAME>
<CUSTOMER_SINCE>2001-10-01</CUSTOMER_SINCE>
<EMAIL_ADDRESS>Jack@hotmail.com</EMAIL_ADDRESS>
<TELEPHONE_NUMBER>2145134119</TELEPHONE_NUMBER>
<SSN>295-13-4119</SSN>
<BIRTH_DAY>1970-01-01</BIRTH_DAY>
<DEFAULT_SHIP_METHOD>AIR</DEFAULT_SHIP_METHOD>
<EMAIL_NOTIFICATION>1</EMAIL_NOTIFICATION>
<NEWS_LETTTER>0</NEWS_LETTTER>
<ONLINE_STATEMENT>1</ONLINE_STATEMENT>
<LOGIN_ID>Jack</LOGIN_ID>
</ns0:CUSTOMER><ns0:CUSTOMER>
<CUSTOMER_ID>CUSTOMER2</CUSTOMER_ID>
<FIRST_NAME>Kevin</FIRST_NAME>
<LAST_NAME>Smith</LAST_NAME>
<CUSTOMER_SINCE>2001-10-01</CUSTOMER_SINCE>
<EMAIL_ADDRESS>JOHN_2@yahoo.com</EMAIL_ADDRESS>
<TELEPHONE_NUMBER>3607467964</TELEPHONE_NUMBER>
<SSN>087-46-7964</SSN>
<BIRTH_DAY>1978-09-21</BIRTH_DAY>
<DEFAULT_SHIP_METHOD>AIR</DEFAULT_SHIP_METHOD>
<EMAIL_NOTIFICATION>1</EMAIL_NOTIFICATION>
<NEWS_LETTTER>0</NEWS_LETTTER>
<ONLINE_STATEMENT>1</ONLINE_STATEMENT>
<LOGIN_ID>Jerry</LOGIN_ID></ns0:CUSTOMER>
<ns0:CUSTOMER>
. . .
</ns0:ArrayOfCUSTOMER>
</com:datagraph>

XML Schema-to-Java Type Mapping

Liquid Data client developers can use the Liquid Data Console to view the XML schema types associated with data services. (See Figure 2-2) The return type tab indicates, for example, whether an element is a string, int, or complex type.

Figure 2-2 Return Types Display in Liquid Data Console


 

Return Types Display in Liquid Data Console


 

The Table 2-3 shows XML schema types correspondence to SDO Java types.

Table 2-3 Schema to Java Data Type Mapping

XML Schema Type

SDO Java Type

xs:anyType

Sequence

xs:anySimpleType

String

xs:anyURI

String

xs:base64Binary

byte[]

xs:boolean

boolean

xs:byte

byte

xs:date

java.util.Calendar (Date)

xs:dateTime

java.util.Calendar

xs:decimal

java.math.BigDecimal

xs:double

double

xs:duration

String

xs:ENTITIES

String

xs:ENTITY

String

xs:float

float

xs:gDay

java.util.Calendar

xs:gMonth

java.util.Calendar

xs:gMonthDay

java.util.Calendar

xs:gYear

java.util.Calendar

xs:gYearMonth

java.util.Calendar

xs:hexBinary

byte[]

xs:ID

String

xs:IDREF

String

xs:IDREFS

String

xs:int

int

xs:integer

java.math.BigInteger

xs:language

String

xs:long

long

xs:Name

String

xs:NCName

String

xs:negativeInteger

java.math.BigInteger

xs:NMTOKEN

String

xs:NMTOKENS

String

xs:nonNegativeInteger

java.math.BigInteger

xs:nonPositiveInteger

java.math.BigInteger

xs:normalizedString

String

xs:NOTATION

String

xs:positiveInteger

java.math.BigInteger

xs:QName

javax.xml.namespace.QName

xs:short

short

xs:string

String

xs:time

java.util.Calendar

xs:token

String

xs:unsignedByte

short

xs:unsignedInt

long

xs:unsignedLong

java.math.BigInteger

xs:unsignedShort

Int

xs:keyref

String

ArrayOf Types

As mentioned elsewhere, data graphs are used to pass data objects between clients and the data service mediator. While a data service function can return multiple elements, a data graph can only have a single root element.

To accommodate functions that return multiple array types, Liquid Data manufactures a root element to serve as the single container for array types. The elements are named with the prefix "ArrayOf". For example, for a function defined to return multiple CUSTOMER elements, the root element is ArrayOfCUSTOMER, as shown in Listing 2-3.

Listing 2-3 Array Root Element


 
<ns0:ArrayOfCUSTOMER xmlns:ns0="ld:DataServices/CustomerDB/CUSTOMER">
<ns0:CUSTOMER>
<CUSTOMER_ID>CUSTOMER1</CUSTOMER_ID>
<FIRST_NAME>Jack B.</FIRST_NAME>
<LAST_NAME>Quick</LAST_NAME>
<CUSTOMER_SINCE>2001-10-01</CUSTOMER_SINCE>
<EMAIL_ADDRESS>Jack@hotmail.com</EMAIL_ADDRESS>
<TELEPHONE_NUMBER>2145554119</TELEPHONE_NUMBER>
<SSN>295-00-4119</SSN>
<BIRTH_DAY>1970-01-01</BIRTH_DAY>
<DEFAULT_SHIP_METHOD>AIR</DEFAULT_SHIP_METHOD>
<EMAIL_NOTIFICATION>1</EMAIL_NOTIFICATION>
<NEWS_LETTTER>0</NEWS_LETTTER>
<ONLINE_STATEMENT>1</ONLINE_STATEMENT>
<LOGIN_ID>Jack</LOGIN_ID>
</ns0:CUSTOMER>
</ns0:ArrayOfCUSTOMER>


 

The array type does not appear in the return type displayed in the Liquid Data Administration console. However, interface functions for it are generated and included in client packages. If ArrayOf types are included in the client package for the data type you want to use, they will include the generated root element. An array is indicated in the console with an asterisk appended to the return type of the function. For example, CUSTOMER* indicates a CUSTOMER array.

Static versus Dynamic Interfaces

As otherwise mentioned (see What is Service Data Objects (SDO) Programming? on page 2-1), SDO specifies both static (strongly typed) and dynamic interfaces for data objects:

Equivalent typed and dynamic interfaces are provided by the Data Service Mediator API, allowing you to work with data objects in either a dynamic or static model from end-to-end, that is, from data acquisition to client-side manipulation.

Table 2-4 outlines the advantages of each approach.

Table 2-4 Typed versus Untyped Interfaces

Access Mode

Advantages...

typed

  • Easy-to-use interface, resulting in code that is more intuitive and easier read and maintain.

  • Compile-time type checking.

  • Enables a pop-up menu in BEA Workshop Source View.

  • Easier for developers to implement.

dynamic

  • Allows discovery

  • Code is easier to maintain— changes to the interface do not require the library to be applied.

  • Allows for a general-purpose coding style.

Note: For more information on the Mediator API, see Accessing Data Services from Java Clients.

The following sections provide more information on the Liquid Data implementation of both kinds of interfaces.

Static Interface

The static interface is a Java interface generated from a data service's XML schema definition, similar to JAXB or XMLBean static interfaces. The interface files, packaged in a JAR, are typically generated by the Liquid Data implementor using WebLogic Workshop.

Note: For information on generating the client JAR file using WebLogic Workshop, see the Data Services Developer's Guide.

There is another way to generate a JAR file. See Setting Up Data Source Aliases for Relational Sources Accessed by Liquid Data, in Advanced Topics.

The generated typed interface contains static accessors for all properties of the XML datatype. If the property is complex (such as CREDIT and ORDER in Figure 2-5), an interface class is generated for the property in the containing package. The interface includes accessors for the properties that make up the complex property.

When developing Data Service Mediator client applications, it is helpful to browse the contents of the generated client package in a development tool (such as Eclipse) to get acquainted with how Liquid Data generates interfaces from data service types. The types of functions that are generated depend on the XML Schema definition for the type. For example, for properties that can have multiple occurrences, as defined in their schema, getPROPERTYArray() functions are generated.

Consider the return type shown in the metadata browser illustrated in Figure 2-5.

Figure 2-5 CUSTOMER Return Type

CUSTOMER Return Type


 

For each complex property—such as the global CUSTOMER element and properties CREDIT, ORDER, and POITEM—separate interfaces are generated with accessors for their contained properties. For each simple attribute, Liquid Data generates set and get methods. For example, the following are generated in the CUSTOMER interface for the CUSTOMERNAME string attribute:

	getCUSTOMERNAME()
setCUSTOMERNAME(String)

For multiple occurrence properties in the return type (indicated by an asterisk in the Return Type tab of the Liquid Data console), functions for getting the array and manipulating array items are generated. In the XML Schema, a property may occur multiple times if it has a maxOccurs attribute set to "unbounded" or greater than 1. Attributes cannot occur multiple times.

For example, the following functions are generated for the CREDIT element:

	getCREDITArray() 
getCREDITArray(int)
addNewCREDIT()
insertNewCREDIT(int)
removeCREDIT(int)
setCREDITArray(int, CREDIT)
setCREDITArray(CREDIT[])
sizeOfCREDITArray()

Because CREDIT is a complex attribute, a separate CREDIT interface with these functions is generated for it in the customer package:

	getCREDITRATING()
getCREDITSCORE()
setCREDITRATING(String)
setCREDITSCORE(int)

Similar methods are generated for the ORDER and its POITEM interfaces.

Note that these additional methods are also generated:

Typed Accessor Method Signatures

The following table lists the rules for the typed (or static) method generation.

Signature Format

Description

Type get[PROPERTY]()

Returns the value of the property. Generated when PROPERTY is an attribute or element with single occurrence.

void set[PROPERTY](
Type newValue)

Sets the value of the property to the newValue. Generated when PROPERTY is an attribute or an element with single occurrence.

boolean isSet[PROPERTY]()

Determines whether the [PROPERTY] element or attribute exists in the document. Generated for elements and attributes that are optional. In schema, any optional element has a minOccurs attribute set to "0"; an optional attribute has a use attribute set to "optional".

void unSet[PROPERTY]()

Removes the [PROPERTY] element or attribute from the document. Generated for elements and attributes that are optional. In schema, and optional element has an minOccurs attribute set to "0"; an optional attribute has a use attribute set to "optional".

Type[] get[PROPERTY]Array()

For multiple occurrence elements, returns all [PROPERTY] elements.

void set[PROPERTY]Array(
Type[] newValue)

Sets all [PROPERTY] elements.

Type get[PROPERTY]Array(
int index)

Returns the [PROPERTY] child element at the specified index.

void set[PROPERTY]Array(
Type newValue,
int index)

Sets the [PROPERTY] child element at the specified index.

int sizeOf[PROPERTY]Array()

Returns the current number of property child elements.

void remove[PROPERTY](
int index)

Removes the [PROPERTY] child element at the specified index.

void insert[PROPERTY](
int index,
[PROPERTY]Type newValue)

Inserts the specified [PROPERTY] child element at the specified index.

void add[PROPERTY](
[PROPERTY]Type newValue)

Adds the specified [PROPERTY] to the end of the list of [PROPERTY] child elements.

boolean isSet[PROPERTY]Array(
int index)

Determines whether the [PROPERTY] element at the specified index is null.

void unset[PROPERTY]Array(
int index)

Unsets the value of [PROPERTY] element at the specified index; that is, sets it to null. Note that after you call unset and then call set, the return value is false.

Dynamic Data Object Interface

The dynamic interface has generic property accessors (such as set() and get()) as well as accessors that get or set data as a specified type, such as String, Date, List, BigInteger, and BigDecimal. These accessor methods take the following form:

The generic get methods return an Object type. Also, with the generic set and get methods you can specify an nth property to get or set. Type in the above list indicates the specific data type to be set or retrieved; for example, setBigDecimal and getBigDecimal. This includes the accessors provided for getting and setting properties as primitive types, which include, for example, setInt(), setDate(), getString(), and so on. For a full list, see the SDO Update Javadoc available at:

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

The propertyName argument indicates the property whose value you want to get or set, and propertyValue is the new value. For example, given a data object myCustomer, the following code sets a value by its property name, LAST_NAME:

	myCustomer.set("LAST_NAME", "Nimble"); 

XPath provides considerable flexibility in how you identify nodes in XML-based information. You can identify properties in SDO accessor arguments by SDO extension of XPath expressions. XPath can find nodes by position, relative position, type, and other criteria.

 


Common SDO Operations and Examples

This section describes common programming tasks involving SDOs. It covers the following:

Instantiating and Populating Data Objects

The first step in using Liquid Data in a client application is to acquire a data object through a typed or untyped interface.

When you instantiate a data object through either interface, in addition to the data object, you get a data graph to which the object is attached and a handle to the root data object in the data graph. By default, change tracking (logging) on the data graph is enabled, which means that any changes performed on the object values are recorded in the change summary, enabling data source updates and rollbacks.

Note: You can populate a new data object by calling a function in the Mediator API or using a Liquid Data Control function. The code samples in this chapter generally use the Mediator API. For more information on using the mediator and Liquid Data Control interfaces, see Accessing Data Services from Java Clients, and Accessing Data Services from Workshop Applications.

Static Interface Instantiation

To instantiate a data object using a typed data service, import the packages that contain the generated data service and data type interfaces from the <app>-ld-client.jar and instantiate the data service object using the getInstance() method. The data type interfaces are contained in a package that has the following prefix to its package path:

	org.openuri.temp.schemas

The following shows a sample of using the typed interface to instantiate a data object:

    import dataservices.rtlservices.CustomerView; 
import retailer.ArrayOfCUSTOMERDocument;

CustomerView custViewDS = CustomerView.getInstance(context, "RTLApp");
ArrayOfCUSTOMERDocument arrCustDoc =
custViewDS.getCustomerView("CUSTOMER3");

Once you have the data service, you can call its public read method, getCustomerView(), to get an instance of the root schema element of the data service, ArrayOfCUSTOMERDocument.

A document type, such as ArrayOfCUSTOMERDocument, is a construct for representing a global, top-level element in a data service schema. It lets you access the contents of the entire result returned by a data service function.

Dynamic Interface Instantiation

To instantiate a data object through a dynamic interface, create a DataService object using the DataServiceFactory class.

The libraries to import from the Liquid Data application client JAR provide the interfaces to the dynamic data services, which can be used as follows:

import com.bea.ld.dsmediator.client.DataServiceFactory;
DataService custDS = 
DataServiceFactory.newXmlService(
context,"RTLApp","ld:DataServices/CustomerDB/CUSTOMER");
ArrayOfCUSTOMERDocument myCustomer = 
(ArrayOfCUSTOMERDocument) custDS.invoke("CUSTOMER", null);

Accessing Data Object Properties

After obtaining a data object, you can access its properties. To access a data object property (similar to instantiating data objects), you can use a typed or untyped interface functions.

Typed Property Access

Liquid Data generates typed (or static) accessors based on the XML type returned by a data service function. As an alternative to untyped data access, you can use typed accessor functions. (Typed functions provide greater ease-of-use than untyped functions.)

For example, the following code example shows how to get the LAST_NAME property of a CUSTOMER instance using typed accessors:

	ArrayOfCUSTOMER arrCust = myCustomer.getArrayOfCUSTOMER();
CUSTOMER[] customer = arrCust.getCUSTOMERArray();
String lastName = customer[0].getLASTNAME();

Untyped Property Access

You can use the untyped (dynamic) API with types that are unknown or not yet deployed at development time. In the untyped interface, the type names are passed as parameters in the untyped accessor call, and the returned object is cast to the type needed. However, in cases where returned type is unbound, you will need to cast the returned object to a List and use an iterator, if necessary. The following is the untyped implementation of the code sample shown in Typed Property Access above, and gets a single CUSTOMER object:

	ArrayOfCUSTOMER arrCust =
(ArrayOfCUSTOMER)myCustomer.get("ArrayOfCUSTOMER");
List customerList = (List) arrCust.get("CUSTOMER[1]");
CUSTOMER customer = (CUSTOMER) customerList.get(0);
String lastName = (String) customer.get("LAST_NAME");

In cases where you are working with an unbounded type (such as ArrayOfCustomer in the above example), and you want to traverse all the objects in the type, you implement an iterator, such as:

	List customerList = (List) arrCust.get("CUSTOMER");
Iterator iterator = customerList.iterator();
while ( iterator.hasNext() ){
if (iterator.next instanceof CUSTOMER){
String lastName = (String) customer.get("LAST_NAME");
}
}

Note that the string specified in the get method matches the name of the element as specified in the data service. For example, the typed get method for returning the customer's last name is getLASTNAME() while the untyped method is get("LAST_NAME") rather than get("LASTNAME").

You can identify properties in SDO accessor arguments by element name, such as LAST_NAME. Accessor functions take property identifiers specified as XPath expressions, as follows:

   customer.get("CUSTOMER_PROFILE[1]/ADDRESS[AddressID="ADDR_10_1"]")

The example gets the ADDRESS at the specified path with the specified addressID. If elements have identical identifier values, all elements are returned. For example. the ADDRESS element also has a CustomerID (a customer can have more than one address), so all addresses would be returned. (Note that the get method returns a DataObject, so you will need to cast the returned object to the appropriate type. For unbound objects, you will need use a List.)

SDO augments standard XPath notation in how you specify index positions in a path in order to identify an instance in an array. The following SDO call can be used to retrieve the last name of a customer in an array of customers:

	String lastName = (String) arrCust.get("CUSTOMER.0/LAST_NAME")

SDO also supports bracketed-style index notations. The following gets the name of the same department as in the previous example:

	String lastName = (String) arrCust.get("CUSTOMER[1]/LAST_NAME")

Notice that the index for the dot-number notation is zero-based, whereas standard XPath notation is one-based. Therefore, both notation examples retrieve the last name of the first customer in an array of properties. Zero-based indexing is more familiar to Java language programmers and allows zero-based counter values in loop constructs to be used in path expressions without having to add 1.

Note: A "query too complex" exception is raised if required JAR files are not in the JVM's CLASSPATH when an XPath path expression is executed. If you encounter this error, make sure that the JAR files xqrl.jar and wlxbean.jar are in the CLASSPATH.

You can get a data object's containing parent data object by doing the following:

	myCustomer.get("..")

You can get the root containing data object by doing the following:

	myCustomer.get("/")

(This is similar to executing myCustomer.getDataGraph().getRootObject().)

Note: For more information on XPath in Liquid Data SDO, see XPath Support in the Untyped SDO API.

Setting Data Object Properties

You can modify data object property values using set() methods. Like get() methods, there are both static and dynamic interfaces for setting properties. However, set methods differ from get methods in that they have an additional argument: the new value of the property. For example, to set the last name of a customer using the dynamic API, you would do the following:

	CUSTOMER customer = (CUSTOMER) myCustomer.get("CUSTOMER");
customer.set("LAST_NAME", "Smith");

The example sets the LAST_NAME field to a new value "Smith". By comparison, an operation that sets a value for a typed property using the static API would be:

	myCustomer.getCUSTOMER().setLASTNAME("Smith"); 

A very important behavioral property of the SDO model is that the back-end data source associated with a modified object (if there is one) is not changed until a submit() method is called on the data service bound to the object. Meanwhile, the old value is recorded in a change summary, the change log kept in the data graph that holds the object. For more information on data graphs, see Working with Data Graphs.

Adding New Data Objects

You can create new a data object (and have a corresponding changed applied to the data sources associated with its data service) by using an add method and then calling submit() on the data service bound to the data object. The lineage of the data (the back-end data sources associated with it) is derived from the data service.

A new data object can be added to a root data object or, more commonly, as a new element in a data object array. In addition, whole new arrays can be added to data objects as well.

The following example demonstrates how to add a data object to an array of objects.

   	CUSTOMERDocument.CUSTOMER newCustomer =
myCustomer.getArrayOfCUSTOMER().addNewCUSTOMER();
int idNo =
myCustomer.getArrayOfCUSTOMER().getCUSTOMERArray().length;
newCustomer.setCUSTOMERID("CUSTOMER" + String.valueOf(idNo));
newCustomer.setFIRSTNAME("Clark");
newCustomer.setLASTNAME("Kent");
newCustomer.setCUSTOMERSINCE(java.util.Calendar.getInstance());
newCustomer.setEMAILADDRESS("kent@dailyplanet.com");
newCustomer.setTELEPHONENUMBER("555-555-5555");
newCustomer.setSSN("509-00-3683");
newCustomer.setDEFAULTSHIPMETHOD("Air");

There are few points to note about adding data objects:

Deleting Data Objects

Just as records can be added to a data source by creating new data objects and submitting the changed data graph, you can similarly remove records by deleting data objects from an existing data graph.

A data object is deleted by removing it from the context of its containing object. When you remove an object from a container, the reference to the item is deleted but not the values. (The values are cleaned up later by Java garbage collection.)

To delete a data object, use the delete() method. For example, the following searches a CUSTOMER array for a customer's name and deletes that customer.

	CUSTOMERDocument.CUSTOMER[] customers =
myCustomer.getArrayOfCUSTOMER().getCUSTOMERArray();
for (int i=0; i < customers.length; i++){
if (customers[i].getFIRSTNAME().equals("Clark") &&
customers[i].getLASTNAME().equals("Kent") )
{
customers[i].delete();
custDS.submit(myCustomer,"ld:DataServices/CustomerDB/CUSTOMER");
          }
        }

If the deleted object contains any child elements, they are deleted as well. However, note that only the data object on which a delete call has explicitly been performed is tracked in the change summary as having been deleted.

Submitting Data Object Changes

To submit data changes, call the submit() method on the data service bound to an object, passing the root changed object and the fully qualified name of the data service bound to the object. Note that the submit() method is part of the mediator API.

The untyped interface submit() method has the following signature:

	abstract public void submit (DataObject do, String dataservice) 
throws java.lang.Exception

The dataservice argument is the fully qualified name of the data service to which you want the data object to be bound. The function for decomposition for that data service is used to establish the lineage of the data object (the correlation between data object properties and back-end data sources), for example:

	custDS.submit(myCustomer,"ld:DataServices/CustomerDB/CUSTOMER");

In this example, the dataservice argument specifies that the CUSTOMER data service to which the myCustomer data object is to be bound.

The typed version of the submit() function only takes the data object as an argument:

	custDS.submit(myCustomer);

After submitting the change, if you want to continue using the object in the client application, it is recommended that you rerun the method used to acquire the data object. This ensures that any side effects of the update operation (at the physical data service level) are incorporated in the data object.

Note that if new objects were added that correspond to relational records in back-end data sources, and if the records have auto-generated primary key fields, the fields are generated in the database source and returned to the client in a property array. The properties include name-value items corresponding to the column name and new auto-generated key value.

Typed Interface Submit

The following example shows how to modify a data object and submit the change using the typed interface:

CUSTOMER custDS = CUSTOMER.getInstance(ctx, "RTLApp");
ArrayOfCUSTOMERDocument myCustomer =
(ArrayOfCUSTOMERDocument) custDS.invoke("CUSTOMER", null);
myCustomer.getArrayOfCUSTOMER().getCUSTOMERArray(0).setLASTNAME("Nimble");
custDS.submit(myCustomer);

Note: For more information, see Enabling SDO Data Source Updates.

Untyped Interface Submit

The following example shows how to modify a data object and submit the change using the untyped interface:

CUSTOMER custDS = CUSTOMER.getInstance(ctx, "RTLApp");
ArrayOfCUSTOMERDocument myCustomer =
(ArrayOfCUSTOMERDocument) custDS.invoke("CUSTOMER", null);
myCustomer.getArrayOfCUSTOMER().getCUSTOMERArray(0).setLASTNAME("Nimble");
custDS.submit(myCustomer,"ld:DataServices/CustomerDB/CUSTOMER");

Introspecting a Data Object

When using the untyped interface, it is often necessary to check the properties of a data object once it is acquired. The Type interface gives client applications the ability to discover information on a data object at runtime. The information includes the type of the data object and its list of properties with their types.

The getType() method returns the Type interface for a data object. The Type interface gives the client application access to the data object's properties, both elements and attributes.

The following example shows how to get the type of a data object and print a property's value:

DataObject o = ...; 
Type type = o.getType();
if (type.getName().equals("CUSTOMER") {
System.out.println(o.getString("CUSTOMERNAME")); }

Once you have the type of the object, you can get the list of properties defined for the type and access their values using the getProperties() method. The following example iterates through the property list of a data object and prints out information about each property:

public void printDataObject(DataObject dataObject, int indent) {
Type type = dataObject.getType();
List properties = type.getProperties();
for (int p=0, size=properties.size(); p < size; p++) {
if (dataObject.isSet(p)) {
Property property = (Property) properties.get(p);
// For many-valued properties, process a list of values
if (property.isMany()) {
List values = dataObject.getList(p);
for (int v=0; count=values.size(); v < count; v++) {
printValue(values.get(v), property, indent);
}
else {
// For single-valued properties, print out the value
printValue(dataObject.get(p), property, indent);
}
}
}
}
}

The following table lists other useful methods in the Type interface.

Table 2-6 Type Interface Methods

Method

Description

java.lang.Class getInstanceClass()

Returns the Java class that this type represents.

java.lang.String getName()

Returns the name of the type.

java.lang.List getProperties

Returns the list of the properties of this type.

Property getProperty(
java.lang.String propertyName)

Returns from all the properties of this type, the one with the specified name. (See Table 2-7 for a list of the methods in the Property class.)

java.lang.String getURI()

Returns the namespace URI of the type.

boolean isInstance(
java.lang.Object object)

Returns whether the specified object is an instance of this type.

Table 2-7 lists the methods of the Property interface.

Table 2-7 Property Interface Methods

Method

Description

Type getContainingType()

Returns the containing type of this property.

java.lang.Object getDefault()

Returns the default value this property will have in a data object where the property hasn't been set

java.lang.String getName()

Returns the name of the property.

Type getType()

Returns the type of the property. (See Table 2-6 for a list of the methods in the Type class.)

boolean isContainment()

Returns whether the property is containment; that is, whether it represents by-value composition.

boolean isMany()

Returns whether the property is many-valued.

Working with Data Graphs

A data graph is the container for objects passed between the client application and the Liquid Data mediator. The root object of the data graph is typically a data object corresponding to the root type of the data service return type, such as a Customer object.

In addition to data objects, a data graph contains metadata on the data object and a change summary. A change summary is a record of client-side data changes. The mediator uses the change summary to propagate those changes to the back-end data sources.

You get a data graph automatically with the data object when you invoke a data service function. You can also create a data graph and attach a data object to it on the client side or replace the root object returned from a data service function invocation.

Note: For a print-out of a data graph, see Looking at a Data Graph.

There are several useful methods on the data graph interface. You can access the root data object of a data graph using the getRootObject() method. To add a root object to an empty data graph, use the createRootObject() method. Note that if the data graph already has a root object, it is overwritten. To get the data graph of an existing data object, use the following form:

	CUSTOMERDocument.getDataGraph() 

The getChangeSummary() method allows you to access the data change log. This is particularly useful when creating data update overrides. These are classes for customizing how data updates are propagated to back-end data sources. For more information on update overrides, see Enabling SDO Data Source Updates.

 


XPath Support in the Untyped SDO API

XPath expressions give you a great deal of flexibility in how you locate data objects and attributes in accessors in the untyped interface. For example, you can filter the results of a get() function invocation based on data elements and values:

	company.get("CUSTOMER[1]/POITEMS/ORDER[ORDERID=3546353]")

Note: For more examples of using XPath expressions with SDO, see Accessing Data Object Properties.

The Liquid Data SDO implementation extends support of XPath 1.0 as specified by the SDO language specification. However, there are a few points to keep in mind regarding the Liquid Data implementation:

 


For More Information

This chapter introduces SDO and covers common operations. For detailed information on SDO, use the following references.

 

Skip navigation bar  Back to Top Previous Next