Skip Headers
Oracle® Application Development Framework Developer's Guide For Forms/4GL Developers
10g (10.1.3.1.0)

Part Number B25947-01
Go to Documentation Home
Home
Go to Book List
Book List
Go to Table of Contents
Contents
Go to Index
Index
Go to Master Index
Master Index
Go to Feedback page
Contact Us

Go to previous page
Previous
Go to next page
Next
View PDF

27.7 Reading and Writing XML

The Extensible Markup Language (XML) standard from the Worldwide Web Consortium (W3C) defines a language-neutral approach for electronic data exchange. Its rigorous set of rules enables the structure inherent in data to be easily encoded and unambiguously interpreted using human-readable text documents.

View objects support the ability to write these XML documents based on their queried data. View objects also support the ability to read XML documents in order to apply changes to data including inserts, updates, and deletes. When you've introduced view links, this XML capability supports reading and writing multi-level nested information for master/detail hierarchies of any complexity. While the XML produced and consumed by view objects follows a canonical format, you can combine the view object's XML features with XML Stylesheet Language Transformations (XSLT) to easily convert between this canonical XML format and any format you need to work with.


Note:

The examples in this section refer to the ReadingAndWritingXML project in the AdvancedViewObjectExamples workspace. See the note at the beginning of this chapter for download instructions.

27.7.1 How to Produce XML for Queried Data

To produce XML from a view object, use the writeXML() method. If offers two ways to control the XML produced:

  1. For precise control over the XML produced, you can specify a view object attribute map indicating which attributes should appear, including which view link accessor attributes should be accessed for nested, detail information:

    Node writeXML(long options, HashMap voAttrMap)
    
    
  2. To producing XML that includes all attributes, you can simply specify a depth-level that indicates how many levels of view link accessor attributes should be traversed to produce the result:

    Node writeXML(int depthCount, long options)
    
    

The options parameter is a integer flag field that can be set to one of the following bit flags:

  • XMLInterface.XML_OPT_ALL_ROWS

    Includes all rows in the view object's row set in the XML.

  • XMLInterface.XML_OPT_LIMIT_RANGE

    Includes only the rows in the current range in the XML.

Using the logical OR operation, you can combine either of the above flags with the XMLInterface.XML_OPT_ASSOC_CONSISTENT flag when you want to include new, unposted rows in the current transaction in the XML output.

Both versions of the writeXML() method accept an optional third argument which is an XSLT stylesheet that, if supplied, is used to transform the XML output before returning it.

27.7.2 What Happens When You Produce XML

When you produce XML using writeXML(), the view object begins by creating a wrapping XML element whose default name matches the name of the view object definition. For example, for a Users view object in the devguide.advanced.xml.queries package, the XML produces will be wrapped in an outermost Users tag.

Then, it converts the attribute data for the appropriate rows into XML elements. By default, each row's data is wrapped in an row element whose name is the name of the view object with the Row suffix. For example, each row of data from a view object named Users is wrapped in an UsersRow element. The elements representing the attribute data for each row appear as nested children inside this row element.

If any of the attributes is a view link accessor attribute, and if the parameters passed to writeXML() enable it, the view object will include the data for the detail rowset returned by the view link accessor. This nested data is wrapped by an element whose name is determined by the name of the view link accessor attribute. The return value of the writeXML() method is an object that implements the standard W3C Node interface, representing the root element of the generated XML.


Note:

The writeXML() method uses view link accessor attributes to programmatically access detail collections. It does not require adding view link instances in the data model.

For example, to produce an XML element for all rows of a Users view object instance, and following view link accessors as many levels deep as exists, Example 27-19 shows the code required.

Example 27-19 Generating XML for All Rows of a View Object to All View Link Levels

ViewObject vo = am.findViewObject("Users");
printXML(vo.writeXML(-1,XMLInterface.XML_OPT_ALL_ROWS));

The Users view object is linked to a ServiceRequests view object showing the service requests created by that user. In turn, the ServiceRequests view object is linked to a ServiceHistories view object providing details on the notes entered for the service request by customers and technicians. Running the code in Example 27-19 produces the XML shown in Example 27-20, reflecting the nested structure defined by the view links.

Example 27-20 XML from a Users View Object with Two Levels of View Linked Details

<Users>
    :
   <User>
      <UserId>316</UserId>
      <UserRole>user</UserRole>
      <EmailAddress>sbaida</EmailAddress>
      <FirstName>Shelli</FirstName>
      <LastName>Baida</LastName>
      <StreetAddress>4715 Sprecher Rd</StreetAddress>
      <City>Madison</City>
      <StateProvince>Wisconsin</StateProvince>
      <PostalCode>53704</PostalCode>
      <CountryId>US</CountryId>
      <UserRequests>
         <ServiceRequestsRow>
            <SvrId>101</SvrId>
            <Status>Closed</Status>
            <RequestDate>2006-04-16 13:32:54.0</RequestDate>
            <ProblemDescription>Agitator does not work</ProblemDescription>
            <ProdId>101</ProdId>
            <CreatedBy>316</CreatedBy>
            <AssignedTo>304</AssignedTo>
            <AssignedDate>2006-04-23 13:32:54.0</AssignedDate>
            <ServiceHistories>
               <ServiceHistoriesRow>
                  <SvrId>101</SvrId>
                  <LineNo>1</LineNo>
                  <SvhDate>2006-04-23 13:32:54.0</SvhDate>
                  <Notes>Asked customer to ensure the lid was closed</Notes>
                  <SvhType>Technician</SvhType>
                  <CreatedBy>304</CreatedBy>
               </ServiceHistoriesRow>
               <ServiceHistoriesRow>
                  <SvrId>101</SvrId>
                  <LineNo>2</LineNo>
                  <SvhDate>2006-04-24 13:32:54.0</SvhDate>
                  <Notes>Problem is fixed</Notes>
                  <SvhType>Customer</SvhType>
                  <CreatedBy>316</CreatedBy>
               </ServiceHistoriesRow>
            </ServiceHistories>
         </ServiceRequestsRow>
      </UserRequests>
   </User>
   :
</Users>

27.7.3 What You May Need to Know

{para}?>

27.7.3.1 Controlling XML Element Names

You can change the default XML element names used in the view object's canonical XML format by setting custom properties:

  • Set the custom attribute-level property named XML_ELEMENT to a value SomeOtherName to change the XML element name used for that attribute to SomeOtherName

    For example, the Email attribute in the Users view object defines this property to change the XML element you see in Example 27-20 to be EmailAddress instead of Email.

  • Set the custom view object-level property named XML_ROW_ELEMENT to a value SomeOtherRowName to change the XML element name used for that attribute to SomeOtherRowName

    For example, the Users view object defines this property to change the XML element name for the rows you see in Example 27-20 to be User instead of UsersRow.

  • To change the name of the element names that wrapper nested row set data from view link attribute accessors, you need to use the View Link Properties panel of the View Link Editor to change the name of the view link accessor attribute.

27.7.3.2 Controlling Element Suppression for Null-Valued Attributes

By default, if a view row attribute is null, then its corresponding element is omitted from the generated XML. You can set the custom attribute-level property named XML_EXPLICIT_NULL to any value (e.g. "true" or "yes") to cause an element to be included for the attribute if its value is null. For example, if an attribute named AssignedDate has this property set, then a row containing a null assigned date will contain a corresponding AssignedDate null="true"/ element. If you want this behavior for all attributes of a view object, you can define the XML_EXPLICIT_NULL custom property at the view object level as a shortcut for defining it on each attribute.

27.7.3.3 Printing or Searching the Generated XML Using XPath

Two of the most common things you might want to do with the XML Node object returned from writeXML() are:

  1. Printing the node to its serialized text representation — to send across the network or save in a file, for example

  2. Searching the generated XML using W3C XPath expressions

Unfortunately, the standard W3C Document Object Model (DOM) API does not include methods for doing either of these useful operations. But there is hope. Since ADF Business Components uses the Oracle XML parser's implementation of the DOM, you can cast the Node return value from writeXML() to the Oracle specific classes XMLNode or XMLElement (in the oracle.xml.parser.v2 package) to access additional useful functionality like:

  • Printing the XML element to its serialized text format using the print() method

  • Searching the XML element in memory with XPath expressions using the selectNodes() method

  • Finding the value of an XPath expression related to the XML element using the valueOf() method.

Example 27-21 shows the printXML() method in the TestClientWriteXML. It casts the Node parameter to an XMLNode and calls the print() method to dump the XML to the console.

Example 27-21 Using the XMLNode's print() Method to Serialize XML

// In TestClientWriteXML.java
private static void printXML(Node n) throws IOException {
  ((XMLNode)n).print(System.out);
}

27.7.3.4 Using the Attribute Map For Fine Control Over Generated XML

When you need fine control over which attributes appear in the generated XML, use the version of the writeXML() method that accepts a HashMap. Example 27-22 shows the interesting lines from a TestClientWriteXML class that use this technique. After creating the HashMap, you put String[]-valued entries into it containing the names of the attributes you want to include in the XML, keyed by the fully-qualified name of the view definition those attributes belong to. The example includes the UserId, Email, StateProvince, and UserRequests attributes from the Users view object, and the SvrId, Status, AssignedDate, and ProblemDescription attributes from the ServiceRequests view object.


Note:

For upward compatibility reasons with earlier versions of ADF Business Components the HashMap expected by the writeXML() method is the one in the com.sun.java.util.collections package.

While processing the view rows for a given view object instance:

  • If an entry exists in the attribute map with a key matching the fully-qualified view definition name for that view object, then only the attributes named in the corresponding String array are included in the XML.

    Furthermore, if the string array includes the name of a view link accessor attribute, then the nested contents of its detail row set are included in the XML. If a view link accessor attribute name does not appear in the string array, then the contents of its detail row set are not included.

  • If no such entry exists in the map, then all attributes for that row are included in the XML.

Example 27-22 Using a View Definition Attribute Map for Fine Control Over Generated XML

HashMap viewDefMap = new HashMap();
viewDefMap.put("devguide.advanced.xml.queries.Users",
        new String[]{"UserId",
                     "Email",
                     "StateProvince",
                     "UserRequests" /* View link accessor attribute */
                     });
viewDefMap.put("devguide.advanced.xml.queries.ServiceRequests",
        new String[]{"SvrId","Status","AssignedDate","ProblemDescription"});  
printXML(vo.writeXML(XMLInterface.XML_OPT_ALL_ROWS,viewDefMap)); 

Running the example produces the XML shown in Example 27-23, including only the exact attributes and view link accessors indicated by the supplied attribute map.

Example 27-23 XML from a Users View Object Produced Using an Attribute Map

<Users>
   <User>
      <UserId>300</UserId>
      <EmailAddress>sking</EmailAddress>
      <StateProvince>Washington</StateProvince>
      <UserRequests>
         <ServiceRequestsRow>
            <SvrId>200</SvrId>
            <Status>Open</Status>
            <AssignedDate null="true"/>
            <ProblemDescription>x</ProblemDescription>
         </ServiceRequestsRow>
      </UserRequests>
   </User>
   <User>
      <UserId>301</UserId>
      <EmailAddress>nkochhar</EmailAddress>
      <StateProvince>Maryland</StateProvince>
   </User>
   :
</Users>

27.7.3.5 Use the Attribute Map Approach with Bi-Directional View Links

If your view objects are related through a view link that you have configured to be bi-directional, then you must use the writeXML() approach that uses the attribute map. If you were to use the writeXML() approach in the presence of bi-directional view links and were to supply a maximum depth of -1 to include all levels of view links that exist, the writeXML() method will go into an infinite loop as it follows the bi-directional view links back and forth, generating deeply nested XML containing duplicate data until it runs out of memory. Use writeXML() with an attribute map instead in this situation. Only by using this approach can you control which view link accessors are included in the XML and which are not to avoid infinite recursion while generating the XML.

27.7.3.6 Transforming Generated XML Using an XSLT Stylesheet

When the canonical XML format produced by writeXML() does not meet your needs, you can supply an XSLT stylesheet as an optional argument. It will produce the XML as it would normally, but then transform that result using the supplied stylesheet before returning the final XML to the caller.

Consider the XSLT stylesheet shown in Example 27-24. It is a simple transformation with a single template that matches the root element of the generated XML from Example 27-23 to create a new CustomerEmailAddresses element in the result. The template uses the xsl:for-each instruction to process all User element children of Users that contain more than one ServiceRequestsRow child element inside a nested UserRequests element. For each User element that qualifies, it creates a Customer element in the result whose Contact attribute is populated from the value of the EmailAddress child element of the User.

Example 27-24 XSLT Stylesheet to Transform Generated XML Into Another Format

<?xml version="1.0" encoding="windows-1252" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <CustomerEmailAddresses>
      <xsl:for-each
           select="/Users/User[count(UserRequests/ServiceRequestsRow) > 1]">
        <xsl:sort select="EmailAddress"/>
        <Customer Contact="{EmailAddress}"/>
      </xsl:for-each>
    </CustomerEmailAddresses>
  </xsl:template>
</xsl:stylesheet>

Example 27-25 shows the interesting lines from a TestClientWriteXML class that put this XSLT stylesheet into action when calling writeXML().

Example 27-25 Passing an XSLT Stylesheet to writeXML() to Transform the Resulting XML

// In TestClientWriteXML.java
XSLStylesheet xsl = getXSLStylesheet();
printXML(vo.writeXML(XMLInterface.XML_OPT_ALL_ROWS,viewDefMap,xsl));

Running the code in Example 27-25 produces the transformed XML shown here:

<CustomerEmailAddresses>
   <Customer Contact="dfaviet"/>
   <Customer Contact="jchen"/>
   <Customer Contact="ngreenbe"/>
</CustomerEmailAddresses>

The getXSLStylesheet() helper method shown in Example 27-26 is also interesting to study since it illustrates how to read a resource like an XSLT stylesheet from the classpath at runtime. The code expects the Example.xsl stylesheet to be in the same directory as the TestClientWriteXML class. By referencing the Class object for the TestClientWriteXML class using the .class operator, the code uses the getResource() method to get a URL to the resource. Then, it passes the URL to the newXSLStylesheet() method of the XSLProcessor class to create a new XSLStylesheet object to return. That object represents the compiled version of the XSLT stylesheet read in from the *.xslfile.

Example 27-26 Reading an XSLT Stylesheet as a Resource from the Classpath

private static XSLStylesheet getXSLStylesheet()
        throws XMLParseException, SAXException,IOException,XSLException {
  String xslurl = "Example.xsl";
  URL xslURL = TestClientWriteXML.class.getResource(xslurl);
  XSLProcessor xslProc = new XSLProcessor();
  return xslProc.newXSLStylesheet(xslURL);
}

Note:

When working with resources like XSLT stylesheets that you want to be included in the output directory along with your compiled Java classes and XML metadata, you can use the Compiler page of the Project Properties dialog to update the Copy File Types to Output Directory field to include .xsl in the semicolon-separated list.

27.7.3.7 Generating XML for a Single Row

In addition to calling writeXML() on a view object, you can call the same method with the same parameters and options on any Row as well. If the Row object on which you call writeXML() is a entity row, you can bitwise-OR the additional XMLInterface.XML_OPT_CHANGES_ONLY flag if you only want the changed entity attributes to appear in the XML.

27.7.4 How to Consume XML Documents to Apply Changes

To have a view object consume an XML document to process inserts, updates, and deletes, use the readXML() method:

void readXML(Element elem, int depthcount)

The canonical format expected by readXML() is the same as what would be produced by a call to the writeXML() method on the same view object. If the XML document to process does not correspond to this canonical format, you can supply an XSLT stylesheet as an optional third argument to readXML() to transform the incoming XML document into the canonical format before it is read for processing.

27.7.5 What Happens When You Consume XML Documents

When a view object consumes an XML document in canonical format, it processes the document to recognize row elements, their attribute element children, and any nested elements representing view link accessor attributes. It processes the document recursively to a maximum level indicated by the depthcount parameter. Passing -1 for the depthcount to request that it process all levels of the XML document.

27.7.5.1 How ViewObject.readXML() Processes an XML Document

For each row element it recognizes, the readXML() method does the following:

  • Identifies the related view object to process the row.

  • Reads the children attribute elements to get the values of the primary key attributes for the row.

  • Performs a findByKey() using the primary key attributes to detect whether the row already exists or not.

  • If the row exists:

    • If the row element contains the marker attribute bc4j-action="remove", then the existing row is deleted.

    • Otherwise, the row's attributes are updated using the values in any attribute element children of the current row element in the XML

  • If the row does not exist, then a new row is created, inserted into the view object's rowset. Its attributes are populated using the values in any attribute element children of the current row element in the XML.

27.7.5.2 Using readXML() to Processes XML for a Single Row

The same readXML() method is also supported on any Row object. The canonical XML format it expects is the same format produced by a call to writeXML() on the same row. You can invoke readXML() method on a row to:

  • Update its attribute values from XML

  • Remove the row, if the bc4j-action="remove" marker attribute is present on the corresponding row element.

  • Insert, update, or delete any nested rows via view link accessors

Consider the XML document shown in Example 27-27. It is in the canonical format expected by a single row in the Technicians view object. Nested inside the root TechniciansRow element, the City attribute represents the technician's city. The nested ExpertiseAreas element corresponds to the ExpertiseAreas view link accessor attribute and contains three ExpertiseAreasRow elements. Each of these includes ProdId elements representing part of the two-attribute primary key of a ExpertiseAreas row. The other primary key attribute, UserId, is inferred from the enclosing parent TechniciansRow element.

Example 27-27 XML Document in Canonical Format to Insert, Update, and Delete Rows

<TechniciansRow>
   <!-- This will update Techncian's City attribute -->
   <City>Padova</City>
   <ExpertiseAreas>
      <!-- This will be an update since it does exist -->
      <ExpertiseAreasRow>
         <ProdId>100</ProdId>
         <ExpertiseLevel>Expert</ExpertiseLevel>
      </ExpertiseAreasRow>
      <!-- This will be an insert since it doesn't exist -->
      <ExpertiseAreasRow>
         <ProdId>110</ProdId>
         <ExpertiseLevel>Expert</ExpertiseLevel>
      </ExpertiseAreasRow>
      <!-- This will be deleted -->
      <ExpertiseAreasRow bc4j-action="remove">
         <ProdId>112</ProdId>
      </ExpertiseAreasRow>        
   </ExpertiseAreas>
</TechniciansRow>

Example 27-28 shows the interesting lines of code from a TestClientReadXML class that applies this XML datagram to a particular row in the Technicians view object, and to the nested set of the technician's areas of expertise via the view link accessor attribute to the ExpertiseAreas view object. TestClientReadXML class performs the following basic steps:

  1. Finds a target row by key (e.g. for technician "ahunold").

  2. Shows the XML produced for the row before changes are applied.

  3. Obtains the parsed XML document with changes to apply using a helper method.

  4. Reads the XML document to apply changes to the row.

  5. Shows the XML with the pending changes applied.

    TestClientReadXML class is using the XMLInterface.XML_OPT_ASSOC_CONSISTENT flag described in Section 27.7.1, "How to Produce XML for Queried Data" to ensure that new, unposted rows are included in the XML.

Example 27-28 Applying Changes to and Existing Row with readXML()

ViewObject vo = am.findViewObject("Technicians");
Key k = new Key(new Object[] { 303 });
// 1. Find a target row by key (e.g. for technician "ahunold")
Row ahunold = vo.findByKey(k, 1)[0];
// 2. Show the XML produced for the row before changes are applied
printXML(ahunold.writeXML(-1, XMLInterface.XML_OPT_ALL_ROWS));
// 3. Obtain parsed XML document with changes to apply using helper method
Element xmlToRead = getInsertUpdateDeleteXMLGram();
printXML(xmlToRead);
// 4. Read the XML document to apply changes to the row
ahunold.readXML(getInsertUpdateDeleteXMLGram(), -1);
// 5. Show the XML with the pending changes applied
printXML(ahunold.writeXML(-1, XMLInterface.XML_OPT_ALL_ROWS | 
                              XMLInterface.XML_OPT_ASSOC_CONSISTENT));

Running the code in Example 27-28 initially displays the "before" version of Alexander Hunold's information. Notice that:

  • The City attribute has the value "Southlake"

  • The expertise area for product 100 has a level of "Qualified"

  • There is an expertise row for product 112, and

  • There is no expertise area row related to product 110.

<TechniciansRow>
   <UserId>303</UserId>
   <UserRole>technician</UserRole>
   <Email>ahunold</Email>
    :
   <City>Southlake</City>
    :
   <ExpertiseAreas>
      <ExpertiseAreasRow>
         <ProdId>100</ProdId>
         <UserId>303</UserId>
         <ExpertiseLevel>Qualified</ExpertiseLevel>
      </ExpertiseAreasRow>
       :
      <ExpertiseAreasRow>
         <ProdId>112</ProdId>
         <UserId>303</UserId>
         <ExpertiseLevel>Expert</ExpertiseLevel>
      </ExpertiseAreasRow>
       :
   </ExpertiseAreas>
</TechniciansRow>


After applying the changes from the XML document using readXML() to the row and printing its XML again using writeXML() you see that:

  • The City is now "Padova"

  • The expertise area for product 100 has a level of "Expert"

  • The expertise row for product 112 is removed, and

  • A new expertise area row for product 110 got created.

<TechniciansRow>
   <UserId>303</UserId>
   <UserRole>technician</UserRole>
   <Email>ahunold</Email>
    :
   <City>Padova</City>
    :
   <ExpertiseAreas>
      <ExpertiseAreasRow>
         <ProdId>110</ProdId>
         <UserId>303</UserId>
         <ExpertiseLevel>Expert</ExpertiseLevel>
      </ExpertiseAreasRow>
      <ExpertiseAreasRow>
         <ProdId>100</ProdId>
         <UserId>303</UserId>
         <ExpertiseLevel>Expert</ExpertiseLevel>
      </ExpertiseAreasRow>
</TechniciansRow>


Note:

The example illustrated using readXML() to apply changes to a single row. If the XML document contained a wrapping Technicians row, including the primary key attribute in each of its one or more nested TechniciansRow elements, then that document could be processed using the readXML() method on the Technicians view object for handling operations for multiple Technician rows.