Implementation Notes

Before we proceed with the actual implementation of the order database to SOAP transformation and the SOAP playback client, a high-level overview of the order submission process is in order. Figure 16.4 shows the flow of information from the original order database document to the SOAP server, and the capturing of the SOAP results into a log file.

Figure 16.4. The overall application data flow.


Because our XSLT will need to construct a valid SOAP request message, it would be useful to see a valid, functional SOAP request that has been generated by an existing SOAP client. Fortunately, the Microsoft SOAP Toolkit provides us with a tool that can do just that.

Using the MS SOAP Trace Tool

The SOAP Trace tool is a simple graphical utility that is installed as part of the Microsoft SOAP Toolkit. It allows a SOAP developer to monitor SOAP message traffic between a SOAP client and a SOAP server. Although it is possible to construct a SOAP request message from scratch using the SOAP specification and a text editor, capturing a “live” message and modifying it can be a real time saver.

Figure 16.5 shows how the SOAP Trace facility inserts itself into the SOAP/HTTP request chain.

Figure 16.5. SOAP request tracing.


One modification needs to be made to the SOAP client application to use the SOAP Trace tool: the SOAP request URL needs to access a nonstandard HTTP port (in this case, 8080) instead of the normal port (port 80). In this case, we will be using the Trace tool to monitor a SOAP transaction between the OrderHandlerSOAPTest.vbs script from Chapter 15 and the SOAP service that is declared in OrderHandler.wsdl (from Chapter 15 as well). To begin tracing, perform the following steps (assuming that the sample SOAP service from Chapter 15 is installed and running on the current machine):

1.
Edit OrderHandler.wsdl (in the OrderHandler IIS virtual directory). Locate the <soap:address> child element of the <port> element of the <service> element that reads

<soap:address location="http://localhost/OrderHandler/OrderHandler.wsdl" />

2.
3.
Run the Trace utility (located in the Start/Programs/Microsoft SOAP Toolkit menu).

4.
In the utility, click the File/New/Formatted Trace menu item. The utility will display a dialog box, shown in Figure 16.6.

Figure 16.6. Creating a new SOAP Trace session.


5.
Accept the default options. This will cause the Trace utility to create a new server socket at port 8080 and then forward requests to the server listening at port 80.

The SOAP Trace utility will then show a split window with a tree view on the left side that shows a list of SOAP requests. The top-right panel will show the request as it was sent from the SOAP client, and the bottom-right panel will show the response from the SOAP server. To collect a sample SOAP message, run the OrderHandlerSOAPTest.vbs script from Chapter 15. After the request runs, expand the single element in the tree view of the Trace utility and click on Message #1. The Trace application will display the content of the request message in the top-right panel. To capture the message content, right-click on the top-right panel and select View Source from the context menu, as shown in Figure 16.7.

Figure 16.7. Viewing the contents of the SOAP transaction.


The View Source operation will bring up the contents of the SOAP request in Notepad, where it may be saved to a file or copied to the Clipboard. A sample SOAP message that was captured using the SOAP Trace utility can be found with the source code for this example in SampleMSSoapMsg.xml. One thing that becomes apparent when looking at the message that was generated by the SOAP client is that very little optimization is done by the XML generator when it comes to declaring and using namespaces. Although all the elements in the message belong to one of the following 4 namespaces, 15 distinct namespaces are declared and used within the message:

Now that we have a valid SOAP message in captivity, creating an XSLT that will take the input data from the order database document and generate a SOAP request is relatively straightforward.

Writing OrderToSOAP.xsl

Constructing the XSLT to generate a batch of SOAP requests from an order database document consists mainly of determining where the data from the order database document needs to be plugged in to the SOAP message data in the output document.

In OrderToSOAP.xsl, the first XSL element encountered within the <xsl:stylesheet> element is the <xsl:output> element:

<xsl:output method="xml"/>

This tells the XSL formatter to output well-formed XML. If the method attribute were set to html, for instance, some tags might not be properly terminated (such as <br> or <hr>) based on the assumptions made regarding HTML document structure.

The entire stylesheet consists of two templates. The first template matches the top-level <orders> element from the order database document, and emits the top-level <soap_batch> element. This element will contain the SOAP client messages that will be transmitted by the Java-based SOAP client we will develop later:

<xsl:template match="orders">
  <soap_batch URI="http://localhost/OrderHandler/OrderHandler.wsdl"
      SOAPAction=
      "http://namespaces.strategicxml.com/action/OrderHandler.ProcessOrder">
    <xsl:apply-templates/>
  </soap_batch>
</xsl:template>

The <soap_batch> element includes the URI and SOAPAction attributes that will be needed by our SOAP client to connect to the server and send each request.

The other template matches each individual <order> element and emits a valid SOAP client message into the target document. It is composed of a series of elements that represent the parameters to the ProcessOrder() method on the SOAP server (<strCustomerName>, <strCCNum>, <strEmailName>, and so on).

The most complex transformation that must be performed is in the case of the two array parameters. Because they both follow the same structure, we will look only at the code for the <astrPartNums> array here:

<xsl:element name="astrPartNums">
  <xsl:attribute name="SOAP-ENV:arrayType"
    >xsd:anyType[<xsl:value-of select="count(items/item)"
    />]</xsl:attribute>
  <xsl:attribute name="xsi:type">SOAP-ENV:Array</xsl:attribute>
  <xsl:for-each select="items/item">
    <SOAP-ENV:anyType xsi:type="xsd:string">
      <xsl:value-of select="@part_num"/>
    </SOAP-ENV:anyType>
  </xsl:for-each>
</xsl:element>

The first challenge involves the SOAP-ENV:arrayType attribute of the <astrPartNums> element itself. The value of this attribute must include the actual number of elements in the array. To create this value using XSLT, we can use the <xsl:element> element to explicitly create an element and populate its attributes using the <xsl:attribute> element. The attribute value for SOAP-ENV:arrayType uses <xsl:value-of> with the XPath count() function to return the number of child <item> elements of the <items> sub-element.

Then, within the <astrPartNums> element we must include one <SOAP-ENV:anyType> element for each part number. The <xsl:for-each> element causes the included transform to be executed once for each <item> element in the source document. We first need to extract only the part_num attribute from the <item> element, then later extract the quantity attribute separately. Using <xsl:for-each> prevents us from needing to create an awkward duplicate set of templates using <apply-templates> along with template modes.

Now that the transformation is complete, we need to write the SOAP batch utility that will send these messages to the SOAP server.

Writing the SOAPBatch Utility

The SOAPBatch utility will read a batch of SOAP requests from an XML document and send them to the SOAP server. This is the basic flow of the application:

1.
Create a SAX 2.0 parser and new SOAPBatch class instance.

2.
Register the new SOAPBatch class instance to receive events for the ContentHandler and ErrorHandler interfaces.

3.
Parse the SOAP batch document at the URL given on the program's command line (or the input from stdin, if no URLs are given).

4.
When the top-level <soap_batch> element is recognized, preserve the values of the URI and SOAPAction attributes for use later.

5.
When the <SOAP-ENV:Envelope> open tag is recognized, begin preserving the XML content using a new StringBuffer object instance.

6.
When character data is found, append it to the StringBuffer, if there is one.

7.
When an element close tag is recognized, append it to the StringBuffer (if it exists). If it is the </SOAP-ENV:Envelope> close tag, transmit the XML content to the SOAP server, capture the results, and set the StringBuffer to null (to prevent further content capture).

8.
Steps 5–7 repeat until the end of the input document is reached.

The full source code for the SOAPBatch class is available on the Web site in the SOAPBatch.java file. Although implementing this utility in Java using SAX 2.0 makes certain tasks very easy, there are still a few XML-related issues that require special handling.

Namespaces in SAX 2.0

Unlike SAX 1.0, SAX 2.0 provides explicit support for recognizing and tracking XML namespaces. The old, namespace-ignorant API didn't treat xmlns attributes any differently than non-namespace attributes. The new ContentHandler interface, however, includes two new methods (startPrefixMapping() and endPrefixMapping()) that are used to inform the application when namespaces go in and out of scope. The startElement() and endElement() methods include the element's namespace URI as part of the notification arguments.

For our purposes, the older non-namespace–aware handling would actually make the application much simpler. We need to capture each <SOAP-ENV:Envelope> in its entirety, including any namespaces that are active at the time. Because each SOAP message is a standalone XML document, every namespace that is used within the <SOAP-ENV:Envelope> element must be declared in it.

One possible approach would be to use the special setFeature() method of the XMLReader interface. Although SAX 2.0 is namespace-aware by default, it is possible to set the http://xml.org/sax/features/namespaces feature to false, which would cause the SAX 2.0 library to revert to the old 1.0 namespace behavior.

Although that approach might eventually be made to work, there is another namespace issue that must be dealt with. After the XSLT transformation, the XSL processor moved the declaration of the SOAP-ENV namespace to the enclosing <soap_batch> element. Although this results in much cleaner XML document, it is necessary that this namespace be declared for each <SOAP-ENV:Envelope> message that is transmitted to the SOAP server. Also, every other namespace that is declared must somehow find its way back into an xmlns attribute for the trip to the server.

The workaround for this issue is to maintain the current set of namespace prefixes and URIs in a Java HashMap object. Each time the startPrefixMapping() method is called, the given namespace is added to the m_hmNamespaces HashMap. To ensure that all the namespaces declared for the <soap_batch> element are propagated to the envelope elements, a separate m_hmNSBase member is used to contain the namespaces that were declared in the <soap_batch> element. The following code in startElement() deals with keeping the namespace hash maps up-to-date:

    if (raw.equals("soap_batch")) {
. . .
      m_hmNSBase = m_hmNamespaces;  // preserve the global namespaces for all
      m_hmNamespaces = new HashMap();
. . .
    }  else if (raw.equals("SOAP-ENV:Envelope")) {
      m_hmNamespaces.putAll(m_hmNSBase); // add the global namespaces
. . .
    }

The contents of the m_hmNamespaces hash map include all the namespace prefixes that must be declared to create a standalone XML document from the current element. This is the code in the echoElement() method that emits these declarations:

if (m_hmNamespaces.size() > 0) {
  Iterator i = m_hmNamespaces.keySet().iterator();

  while (i.hasNext()) {
    String strPrefix = (String)i.next();
    String strURI = (String)m_hmNamespaces.get(strPrefix);

    sb.append(" xmlns:" + strPrefix + "= "" + strURI + " "");
  }

  m_hmNamespaces.clear();

The final m_hmNamespaces.clear() call resets the namespace map to receive any new child namespaces that might be declared within the current element.

Escaping Character Data

Another common gotcha that is encountered when the output from an XML parser is used to feed another XML parser is failing to escape markup characters within character data blocks. The characters() notification method uses the xmlEscape() method to ensure that XML special characters (<, >, &, ", and ') are properly escaped using the special built-in entity references (&lt;, &gt;, &amp;, &quot;, and &apos;).

Using the URL Class

The sendRequest() method is the workhorse method of the utility. It takes the XML SOAP message that was collected by the various SAX notification methods and transmits it to the SOAP server using the Java URL class. Because the SOAP request must use the HTTP POST method to submit the SOAP message body, some extra setup of the URL connection is required:

URL url = new URL(m_strRequestURI);

HttpURLConnection huc = (HttpURLConnection)url.openConnection();
huc.setDoOutput(true);

huc.setRequestMethod("POST");
huc.setRequestProperty("Content-Type", "text/xml");
huc.setRequestProperty("SOAPAction", m_strSOAPAction);

This code fragment uses the special HttpURLConnection subclass of the abstract URLConnection class to set the HTTP request method, content type, and special SOAPAction header, and to indicate that output will be sent with the request.

The rest of the method uses a PrintStream to transmit the SOAP message body from the original SOAP batch document to the server. Then the HTTP response code and the SOAP message body of the return message is written to System.out.

Putting It All Together

After the OrderToSOAP.xsl stylesheet has been written and the SOAPBatch program has been compiled, submitting a new batch to the SOAP server becomes a relatively simple process. The following commands will transform an order database document and then transmit it to a SOAP server:

saxon OrderDB.xml OrderToSoap.xsl > soapbatch.xml
java -cp SOAPBatch/classes com.strategicxml.examples.soapbatch.SOAPBatch < soapbatch.xml

In a production environment, this task would most likely be automated using a process scheduler (such as the NT/Windows 2000 AT command).

Note

Although most of the examples in this book use the Saxon XSLT processor, there are a number of other great transformation processors out there. For example, the Apache Foundation's XML Project (xml.apache.org) has the Xalan processor


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.76.250