Consuming a Web Services Call

Just as with building a Web services call, consuming one takes a similar, stream-lined approach. There isn't nearly the wide-ranging scope of a Web services workflow, so the effort to design and build the consumer is comparatively minor.

In this section, we will implement both of the Web services that we built in Chapter 6—the mortgage calculator and the weather forecaster. If you recall, the mortgage calculator was built on a Java-based development platform, whereas the weather forecaster was built on a COM-based platform. However, we will approach both in a very similar manner because the Web services interface will mask those details from us. What will concern us is the technology that we use to consume the Web services. First up will be a Java application that consumes the weather forecaster service. We will follow that up with an ASP Web application that consumes the mortgage calculator.

Consuming the Weather Forecast Web Service via Java

For our consumer scenario, we will take a very direct approach: building a very simple Java application that exposes the functionality of the Web service. We will continue to use IBM's Visual Age for Java as our development tool, but any Java IDE should suffice here.

For this example, we will build the application's interface using Java Swing, although nothing prevents us from using AWT (Abstract Windows Toolkit), the original interface class for Java. Swing is part of the Java Foundation Classes (JFC) that provides a series of containers and controls for building user interfaces very quickly. I have chosen to use Swing over the AWT because it is currently the more popular choice and because the Swing support in Visual Age is pretty good. We won't go into the details of Swing here, so, if you are interested in learning more about it, I recommend getting a book dedicated to that technology, such as Definitive Guide to Swing for Java 2, Second Edition, by John Zukowski.

As I said earlier, our design and development efforts will be focused on our technology platform, not that of the weather forecaster. We will be working with XML and HTTP only, so its implementation details will truly be abstracted away from our concerns.

Designing the Consumer

When designing the consumer of a Web service, there are two main areas of concern: the integration layer and the presentation layer. (See Figure 8-1.) The integration layer is responsible for interacting with the Web service itself. This includes not only the direct communication piece, but also the building of request documents and the handling of response documents. The presentation layer is then responsible for collecting the information from the user and presenting the data to the user.

click to expand
Figure 8-1: The integration and presentation layers in a consumer application

As we discussed in Chapter 2 ("Web Services Architecture"), a consumer of a Web service doesn't necessarily interact with a user. It may simply be facilitating the use of the Web service logic to a system or through its own Web service to another consumer. Because this type of consumer would contain a subset of an application-based consumer, our examples will all take the direct approach and expose the functionality to a user.

Designing the Integration Layer

The integration layer is where all the real work is done on the consumer side of a Web services interaction. It is where data for the request is assembled, the data is communicated over to the Web service, and the response is collected and parsed into some usable form for the presentation layer. Again, it is important to keep in mind that these two layers (presentation and integration) are logical, not physical. There may or may not be a physical distinction in these responsibilities between methods or entities. It is simply important to account for the functionality and to keep future reuse in mind.

As we saw in building the Java-based Web service, Visual Age provides a lot of skeleton code that gives your class a structure. It might be tempting to add your logic directly to this existing code, but this temptation should be resisted. For instance, in our Java Web service, we could have added the responder logic directly to the doPost and doGet methods. Instead, we created a couple of separate private methods that contained this logic.

  • Skeleton or stub code refers to code that is automatically developed by a routine, typically through a developer tool. This code contains little or no logic, but it provides empty containers and helpers (functions, procedures, and such) that are appropriate for the language and environment you are working in.

In this scenario, we have even more of a reason to resist overextending the skeleton code because the skeleton structure may need to change. Whenever you make changes to your interface, it may also require a change to the code (such as if you add or modify the controls on the interface.) If you have Visual Age regenerate the code, it may mean overwriting some of your own hard work. To avoid this, you are better off making your own custom classes so that they are insulated from these modifications.

We can essentially accomplish most of our work within a single method call. This makes sense if you consider that our consumer is essentially acting as a proxy to a single Web service call. We will name the method callWeatherWS. This method will work directly with the controls on the application interface, so there will be no parameters or return values defined for this call.

The next step in designing the integration layer is to determine how we are going to assemble the data for the request and parse the response. We are looking at only one value going out to the Web service, so we will likely build that document programmatically. We could be looking at many nodes coming back, so, in Listing 8-1, let's review the schema definition for the Web service response.

Listing 8-1: The Weather Forecaster Response Schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"   elementFormDefault="qualified">      <xsd:element name="weatherCheck">           <xsd:complexType>                <xsd:sequence>                     <xsd:element name="barometer" type="xsd:string"/>                     <xsd:element name="city" type="xsd:string"/>                     <xsd:element name="dewpoint" type="xsd:decimal"/>                     <xsd:element name="errorMessage" type="xsd:string"                        minOccurs="0"/>                     <xsd:element name="feelsLike" type="xsd:string"/>                     <xsd:element name="forecast" type="xsd:string"/>                     <xsd:element name="humidity" type="xsd:string"/>                     <xsd:element name="imageID" type="xsd:string"/>                     <xsd:element name="imageSmall" type="xsd:uri"/>                     <xsd:element name="imageBig" type="xsd:uri"/>                     <xsd:element name="reportedTime" type="xsd:string"/>                     <xsd:element name="state" type="xsd:string"/>                     <xsd:element name="temperature" type="xsd:decimal"/>                     <xsd:element name="uvIndex" type="xsd:decimal"/>                     <xsd:element name="uvWarning" type="xsd:string"/>                     <xsd:element name="visibility" type="xsd:string"/>                     <xsd:element name="weatherAlert" type="xsd:boolean"/>                     <xsd:element name="windDescription" type="xsd:string"/>                </xsd:sequence>           </xsd:complexType>      </xsd:element> </xsd:schema> 
end example

We have potentially eighteen fields of data to extract from the response, but it will likely be far less. Because we aren't dealing with a Web application, XSL doesn't make sense as an alternative. Instead, we will manually extract the data through our parser as necessary. Because of the flat structure and the anticipated volume, this should be a fairly simple process.

The only other determination that we need to make for the integration layer at the design stage is what parser and communication classes will be used to execute the actual communication. Many options are available, but they vary in performance and features, and so we need to make sure that all of our requirements will be met.

For this application, we will utilize the IBM parser that we used for the mortgage calculator service. We will also use the DOM class for working with the data. For our communication, we will utilize IBM's WebDAV (Web-based Distributed Authoring and Versioning) implementation. WebDAV is a standardized set of extensions defined by the IETF (Internet Engineering Task Force) for working with files on remote servers over HTTP. It will provide us with the ability to send an HTTP POST request to a URL containing our XML data request document.

We will identify the other classes as they come up, but the following list includes all of the imports for our application:

  • java.awt.*;

  • javax.swing.*;

  • java.io.*;

  • java.net.*;

  • org.w3c.dom.NodeList;

  • org.w3c.dom.Element;

  • org.w3c.dom.Document;

  • com.ibm.xml.parsers.RevalidatingDOMParser;

  • com.ibm.webdav.Resource;

  • org.xml.sax.InputSource;

Designing the Presentation Layer

Before starting on the design of our interface, we need to first identify what information needs to be collected and presented. We already know that we will have only one field going in the request (the ZIP code). Coming back, we have the eighteen fields that we saw earlier, but we aren't likely to want them all.

Because we are the consumers of this Web service, it is entirely our discretion which data we use and which we cast aside. For this application, we are primarily interested in the base weather information. Although it is technically possible, it isn't practical to use the image links in our Java application, so we can immediately drop those three fields. For simplicity's sake, we will pare down our list of response data to the following: temperature, "feels like" temperature, city, state, reported time, forecast, and wind description. If you are like me, you don't understand what the other information means, and it won't affect what you do or how you dress when you go out! Thus, we've reduced the number of fields from eighteen to seven.

We can now turn our attention towards building the interface for our application. Within Visual Age, it's fairly easy to build the application's presentation using the Visual Composition editor. In this view, various controls and objects can be dropped into the window of your application. We can assign values to these controls (like text labels, colors, and such) through the properties dialog box for each. The result of this view is a Java Bean. When so directed, Visual Age can then produce the skeleton code necessary for adding the logic behind these controls and the events related to them.

Our interface will be a very simplistic window that contains two text boxes, a button, and several text labels. (See Figure 8-2.) The text box is where the ZIP code for the forecast can be entered, and the Get Forecast button will trigger the use of the service. As you can see in the figure, this button is tied to our callWeatherWS function. This is exactly how events, such as clicking on a button, can be tied to logic within the application.

click to expand
Figure 8-2: Java application consumer interface in the Visual Composition editor

Labels on our interface clearly point out where our temperature and relative temperature will be presented. In the text area beneath the "Get Forecast" button, we will present the time of the forecast along with the area identified by the ZIP code provided by the user. The forecast and location needed to be contained in a text area control because the volume was too large to contain within a text field or label.

Beneath the temperatures, we have two additional text labels that we will use to present information from the service. The upper text label will provide the forecast itself, and the lower will present the wind information. These could have been text fields, but I used labels instead because they are not editable and the user will not be misled into thinking they are meant to collect input data. As text labels, they will also blend into the background when empty.

So, those are the design decisions for our presentation layer and essentially the entire consumer. We now need to implement the designs we have discussed and run a few tests to confirm our success.

Implementing the Consumer

The consumer application will fall into two elements that we need to build: the interface window and the callWeatherWS method. Although this might seem to fall in line with our logical model of the integration and presentation layers, there is interaction between the two, which necessitates crossing that boundary within each element. Specifically, our method will be responsible for presenting the information on the interface and the presentation layer doesn't just include the physical screen, but the logic for formatting and presenting the data on that screen.

Developing the Application Interface

With the conveniences of using Swing and Visual Age as our development tools, our ability to develop a good interface depends on our own creativity and ability to position controls just as we want. The IDE generates everything else we need to have a functioning application. Of course, we do need to construct the interface for our application through the Visual Composition editor as we saw in Figure 8-2. Doing so isn't difficult, but it can be frustrating if you aren't used to GUI tools or are used to other GUI tools that behave differently than this one.

Once the window is assembled and the properties are set how we would like, the only thing to do is tie the button event to our class. This is done by simply right-clicking on the button and selecting "event to code". This feature even allows you to define a new method, so you could use this to define the skeleton structure for callWeatherWS. Otherwise, you would select the existing call you want to have 'triggered' when a user clicks on the button, as shown in Figure 8-3.

click to expand
Figure 8-3: Tying the Button Click event to logic in Visual Age

This completes the development of the interface for our application. Now we get to add the code for our application's logic.

Developing the Application Logic

Using the "event to code" feature, we should at least have a skeleton for our callWeatherWS method. It should look something like this:

 /**  * Comment */ public void callWeatherWS(){      return; } 

It isn't much, but it is a start! Earlier, we identified the technologies that we would utilize for our application and now it is time to put them to work. Let's get started by looking at the steps we need to perform.

Whenever Web services are consumed, three essential steps have to be executed in sequence:

  1. Build the request.

  2. Invoke the Web service.

  3. Parse the response.

Even in rare instances in which the request or the response is nondescript, these steps are performed at some level. For instance, if a Web service is simply broadcasting the same information to everyone that makes a request, you may not be sending any XML data, but there is data in the header of your HTTP request, which makes it valid. In most normal cases, each of these steps is critical to the consumer's ability to interact with the Web service. Performing these steps out of sequence when they are necessary is obviously ineffective because each step builds on the completion of the previous one.

Building the Request

For this Web service, our request consists of a single piece of data: the ZIP code. This makes the task very simple and justifies a decision to build the request manually. (Later, we will look at an example for which this is not practical.)

If we review the request schema (Listing 8-2), we can identify the exact structure that we need for our request document.

Listing 8-2: The Weather Forecaster Request Schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"   elementFormDefault="qualified">      <xsd:element name="weatherCheck">           <xsd:complexType>                <xsd:sequence>                     <xsd:element name="zipCode" type="xsd:string"/>                </xsd:sequence>           </xsd:complexType>      </xsd:element> </xsd:schema> 
end example

We have a root element called weatherCheck and an element called zipCode within it. All we need to do is reference the data in the text field in our Swing interface. Regardless of how you designed your screen, your text field will have a name assigned to it and will have a method for getting the data contained within it called getText (courtesy of Visual Age). Thus, building our request document can be accomplished by a single line that should look something like this:

 String myData = "<?xml version='1.0'?><weatherCheck><zipCode>"+   vjzipCode.getText() + "</zipCode></weatherCheck>"; 

Now that we have our request document, we need to send it off to the weather forecaster Web service—which is where our connection comes into play.

Invoking the Web service

Invoking the Web service is very similar to invoking any other object or component in practice. Technically, it is very similar to other remote invocation methods: much more is going on behind the scenes to handle the process. Because we are working with a stateless environment, the connection is established through the request and disconnected once the response is provided.

To invoke the Web service, the first piece of information we need is the address for the Web service. Like a data connection, this could be retrieved from an external source, but we will simply define a local variable for our application.

 String url = "http://www.architectingwebservices.com/weather"; 

We decided earlier that we would utilize the WebDAV extensions for our connectivity to the Web service. Specifically, its resource class is a powerful tool that suits most of our needs for communicating with a Web service. The resource constructor will take our URL as its parameter. Once built, we need to use its performWith method to send the request document that we built. The result of this is a string containing the response from the Web service. Of course, we need to catch any exceptions that might occur during this process. The most common exceptions during this step will involve connectivity between the Web service and our application.

 try {       // get a resource that exists       Resource resource = new Resource(url);       String contents = new String(resource.performWith(myData)); } catch (Exception exc) {       System.out.println("TestPOST exception: "+exc); } 

Tip 

Initially, it's a good idea to output any exceptions to the console to aid with troubleshooting. Once we are ready to deploy, we will want to change this output to the application interface and simplify it if we don't want to expose detailed errors to the user.

Now that we have made our request and received a response, it is time to turn it over to our parser. It will be responsible for deciphering the information within that response document.

Parsing the Response

Most of the code will actually involve the parsing of the response from the Web service. We will be working with seven of the eighteen elements returned to us. We saw the response schema earlier in Listing 8-1 (if you would like to review it again).

As the consumer, we might look at this response as being bloated for our purposes, but, in the grand scheme of things, the throwaway data won't affect us on any meaningful level. Although it is feasible for a provider to provide custom responses for each request, the savings would more than likely be offset by a larger request. Remember that our request is only one field of data and that any request incorporating a custom format preference for the response would be bigger. This approach can make sense in some scenarios, but this is not one of them.

We can approach getting the data out of the response document in several ways. Although we decided earlier that we would use the parser from IBM, any parser would work. However, loading it and accessing the data through it might work a little differently. In this case, we need to use the InputSource from IBM's SAX implementation, which allows us to read in XML data. However, the InputSource requires a byte or character stream, and this XML data is contained in a string. To bridge the gap, we will need to use the StringReader class to turn the XML string into the stream we require.

 try{      //Parsing the request and capturing it in the request document      RevalidatingDOMParser parser = new RevalidatingDOMParser();      parser.parse(new InputSource(new StringReader(contents))); } catch (Exception E){      System.out.println("Parser exception: "+ E); } 

Now that we have the data in a parser, we will save it to a document object so we can access our data. We will then go one step further and declare an element as the root of that document so that we can easily reference the data. Declaring this root element, as seen here, gives us the starting point we need to reliably and efficiently reference the data programmatically.

 Document results = parser.getDocument(); Element docRoot = results.getDocumentElement(); 

We are now in a position to reference the data directly through this root element. However, one of the things that we need to consider is whether we can trust the data that was returned to us. We have performed no validation on the response document, and everything we have done up until now has been schema independent. As the consumer, we need to decide how we want to handle the possibility of bad data that could range from illegal XML (which would have already generated an exception) to a single field missing that we don't even intend to use (like imageID). Do we want to "error out" on the entire response for any problem or only when something drastic happens?

A direct reference on a nonexistent node will generate an exception, and the application will abort if it isn't caught. If we want to get the data that is available, we are better off using a method that is less direct, such as the getElementsByTagName method. This method assembles a list of nodes that have a specific tag name, and, if no nodes are found, the list is simply empty. This would allow us to bypass a missing windDescription element and continue to collect the other elements that are present.

After building our node list, we can simply check the length, and, if it is greater than zero, we retrieve the first child and set the appropriate control on our interface to its value. Following is an example of this approach for the temperature element:

 elements = docRoot.getElementsByTagName("temperature"); if (elements.getLength() > 0) {      ivjtemperature.setText(elements.item(0).getFirstChild().getNodeValue()); } 

This approach can accommodate another scenario: an instance in which a duplicate node is present. We are simply taking the first instance and ignoring any duplicates, and this approach makes our consumer very resilient and capable of handling all but the most severe of errors produced by the Web service. Obviously, it would be nice to assume perfection on the part of the Web service, but, as a consumer, we must take the realistic approach and protect our integrity as much as possible from the worst-case scenario.

Note 

Consumers of Web services need to make a decision: Is bad data better than no data when handling payload integrity issues? This is more of an issue with Web services because their scope may be targeted for the masses, which means they are providing more data than the majority of their consumers actually want. The extreme approach would advocate throwing out the entire payload if some of the data is found to be of questionable integrity. The more flexible approach would be concerned with only the integrity of the data needed. Good cases can be made for either approach, but the criticality of this issue varies with the type of the data involved. Whereas a zero tolerance for errors is warranted for financial services, such a restriction might not be necessary for a weather service.

Something else we might decide to do here is to handle the exception to this instance. If we are making a duplicate call to the weather forecaster, we might want to make sure that we erase any preexisting values in the ivjtemperature text label. This would be accomplished by tagging on the following else case:

 else {       ivjtemperature.setText(""); } 

Once we have done this for each of our seven elements, our parsing of the response is complete. You can see in these statements exactly how the logical integration and presentation layers are overlapping within the application's logic. We are both parsing the response and presenting it to the user within the exact same command, which is fine, but it is important that you account for those two functions somewhere within your consumer.

Pulling It All Together

Now that we have implemented all of the application's logical components, it is time to bring it all together. We walked through building our callWeatherWS method in pieces, so we should take a look at it in its entirety, including variable declarations. (See Listing 8-3.)

Listing 8-3: The callWeatherWS() Method

start example
 public void callWeatherWS() {      NodeList elements;      Element elem;      Element root; String url = "http://www.architectingwebservices.com/weather"; String myData = "<?xml version='1.0'?><weatherCheck><zipCode>"+   vjzipCode.getText() + "</zipCode></weatherCheck>"; try {      // get a resource that exists      Resource resource = new Resource(url);      String contents = new String(resource.performWith(myData));      try {           //Parsing the request and capturing it in the request document           RevalidatingDOMParser parser = new RevalidatingDOMParser();           parser.parse(new InputSource(new StringReader(contents)));           Document results = parser.getDocument();           //Referencing the response document object           Element docRoot = results.getDocumentElement();           elements = docRoot.getElementsByTagName("temperature");           if (elements.getLength() > 0)           {                ivjtemperature.setText(elements.item(0).getFirstChild().                  getNodeValue());           }           else           {                ivjtemperature.setText("");           }           elements = docRoot.getElementsByTagName("feelsLike");           if (elements.getLength() > 0)           {                ivjfeelsLike.setText(elements.item(0).getFirstChild().                  getNodeValue());           }           else           {                ivjfeelsLike.setText("");           }           elements = docRoot.getElementsByTagName("forecast");           if (elements.getLength() > 0)           {                ivjforecast.setText("Forecast calls for "+                elements.item(0).getFirstChild().getNodeValue());           }           else           {                ivjforecast.setText("");           }           elements = docRoot.getElementsByTagName("windDescription");           if (elements.getLength() > 0)           {           ivjwindDescription.setText("Wind"+             elements.item(0).getFirstChild().getNodeValue());           }           else           {                ivjwindDescription.setText("");           }           ivjSummaryText.setText("Forecast for ");           elements = docRoot.getElementsByTagName("city");           if (elements.getLength() > 0)           {                ivjSummaryText.setText(ivjSummaryText.getText() +                   elements.item(0).getFirstChild().getNodeValue());           }           elements = docRoot.getElementsByTagName("state");           if (elements.getLength() > 0)           {                ivjSummaryText.setText(ivjSummaryText.getText()+ ", "+                  elements.item(0).getFirstChild().getNodeValue());           }           elements = docRoot.getElementsByTagName("reportedTime");           if (elements.getLength() > 0)           {                ivjSummaryText.setText(ivjSummaryText.getText() + "at"+                  elements.item(0).getFirstChild().getNodeValue());           }      }      catch (Exception E){           ivjSummaryText.setText("There was a problem interpretting the                   results from this location code. Please check your zip code and                   try again.");}           }      catch (Exception exc) {           ivjSummaryText.setText("There was a problem with connecting to the             Weather Forecaster. Please try again later.");      } } 
end example

Testing the Consumer

Now that we have our application completed, we need to test it to make sure that it works as expected. Doing so involves tests in which we expect (or at least hope) that everything goes correctly, as well as those tests in which we expect to see problems, such as a loss of connectivity to the Web service. Fortunately, a Java application is very easy to test, especially within the Visual Age IDE. We simply need to select the "running man" button at the top left of our screen, or right-click on our application and select Run.

If the application runs successfully, it should generate an interface that looks similar to that shown in Figure 8-4. An error should, at worst, cause some or all of the fields to be blank with an error message appearing below the Get Forecast button.

click to expand
Figure 8-4: A successful run of our weather forecaster consumer application

Next, in consuming our mortgage calculator Web service, we will increase the level of complexity.

Consuming the Mortgage Calculator Web Service via ASP

If you recall, our mortgage calculator is a Web service that calculates an amortization plan given a set of data points. This is a Java-based service, but, as we saw with our weather forecaster application, this won't be a factor in our design or implementation.

Unlike our previous example, we will actually integrate this service into an existing application. This presents a few more challenges because we don't have the liberty of just building an application from scratch and focus on exposing the functionality of the Web service. Specifically, we need to spend a little more time up front understanding the current application's process and outline a clear plan for integrating the Web services functionality.

Analyzing the Existing Application

We will be incorporating the mortgage calculator Web service into the Find-A-Home Realtors Web site. Find-A-Home specializes in high-profile home sales, and it has not had much success with moving some of their homes. Additionally, they get a lot of calls from prospective buyers that don't realize the homes are out of their price range. By providing an online payment calculator, Find-A-Home is hoping that it can generate more interest in its listings, as well as screen out unqualified home-seekers.

The Find-A-Home site is hosted on a Windows 2000 server and consists entirely of ASP pages, no COM objects. It is a fairly low-tech customer and needs a low-maintenance solution to which changes can be made very quickly without calling in high-priced talent (which could change, of course, if they could just sell a few of their homes).

The site is very basic, essentially consisting of two tiers: the main page we see in Figure 8-5 and the details page in Figure 8-6. The main page acts as a catalog page, which then connects the user to individual homes with descriptions and pricing. Some information on Find-A-Home Realtors is also featured, but this has no significance to us.

click to expand
Figure 8-5: The Web site for Find-A-Home Realtors

click to expand
Figure 8-6: Find-A-Home Realtors home details view

Our challenge is to incorporate the mortgage calculator Web service into the site so that it appears as an integrated feature of Find-A-Home Realtors. Because of the existing application platform, we will be building a Microsoft-based solution. This makes our choice of parser—MSXML—a no-brainer. More specifically, we will use the MSXML 4 Technology Preview to maintain consistency with the rest of the examples in this book.

Designing the Consumer

Because we aren't writing this Web service consumer from scratch, this design process will be a little different from our weather service consumer. In that scenario, we led with the integration layer, which we followed up with the presentation layer design. Because we are integrating this functionality into an existing application, we will benefit from doing the reverse here. This is especially true in this scenario because we are dealing strictly with ASP pages. Our logic will inherently be tied to the pages that actually present the information to the user, so we need to identify the pages that we will be modifying.

Designing the Presentation Layer

The first thing we need to do is determine what information needs to be collected and what data needs to be displayed. We have a large group of data to select from, so let's review the schema for the request again. (See Listing 8-4.) If you recall, this Web service call was simplified by the fact the request and response schemas are exactly the same.

Listing 8-4: The Mortgage Calculator Request/Response Interface Schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/20001/XMLSchema">      <xsd:element name="calcInteraction">           <xsd:complexType>                <xsd:sequence>                     <xsd:element name="amortizationPeriod" type="xsd:decimal"                       minOccurs="0"/>                     <xsd:element name="effectiveAnnualRate" type="xsd:decimal"                       minOccurs="0"/>                     <xsd:element name="interestRate" type="xsd:decimal"                       minOccurs="0"/>                     <xsd:element name="interestRatePerPayment" type="xsd:decimal"                       minOccurs="0"/>                     <xsd:element name="paymentAmount" type="xsd:decimal"                       minOccurs="0"/>                     <xsd:element name="paymentsPerYear" type="xsd:decimal"                       minOccurs="0"/>                     <xsd:element name="principalAmount" type="xsd:decimal"                       minOccurs="0"/>                     <xsd:element name="timesPerYear" type="xsd:decimal"                       minOccurs="0"/>                     <xsd:element name="totalInterestCost" type="xsd:decimal"                       minOccurs="0"/>                </xsd:sequence>           </xsd:complexType>      </xsd:element> </xsd:schema> 
end example

Many things can be done with this service, but we only want to calculate payments based on the base loan information: principal, down payment, interest rate, and the loan period. We have three of these four available, with down payment being the odd element. Fortunately, we can handle that on our end by reducing the principal amount before calling the Web service.

Because nothing else seems to be of interest, it looks like we have identified four input parameters and one output parameter, the payment. The next thing we need to do is determine how we want to integrate the calculator into the site.

Note 

One thing to note here is that we don't have a source for the current interest rate. The plan will be for Find-A-Home to update its site daily with its interest rate. Not only is this a constantly updated figure, but many factors can affect this rate, such as the loan amount, the loan period, and the user's credit rating. Find-A-Home will be using an average rate that can be used for the purposes of the site. Still, we are probably identifying an opportunity for the mortgage calculator provider to expand its service, or even an opportunity for another provider that already has access to that information. This would generate a multipartner scenario like we discussed back in Chapter 2 ("Web Services Architecture").

The logical placement for this service would be on the details page. (See Figure 8-6.) We don't want it to take away from the main purpose of the page, which is to present the information on the house, so we will set it aside on the left as a sidebar. Placing it here provides easy access to the functionality and yet prevents it from being distracting of the existing content.

Next, we need to determine how to handle the results from this call. Our two options are to push to another page or simply handle it within this view. Handling it within the view is the better choice because we don't want to take away from the current purpose of this page. Also, a page with just a price on it without the information on the home is only slightly useful. Taking this approach, we can simply present the calculated payment beneath the price at the bottom of the details page. This solution will also allow the user easy access to the calculator interface for subsequent calculations if they want to tweak variables such as the amount of the down payment or number of payments.

Although it is fairly simple, we should also probably map out this process. (See Figure 8-7.) In the diagram, we see that the page is calling itself to trigger the Web service (Mortgage Calculator). This will help us in communicating the design to others and make sure that we are consistent in our treatment of the application. This is a good exercise to go through because it will have great significance in the design of our next application and probably in many of the Web services consumers you build.

click to expand
Figure 8-7: The process flow for the Find-A-Home Realtors updated Web site

This completes the design of the presentation layer for the Find-A-Home Realtors Web site. We now need to turn our attention to the design of the integration layer, which will make all of this functionality a reality.

Designing the Integration Layer—ASP vs. COM?

Although the integration layer consumes the most effort in the implementation of our consumer, the design is somewhat dictated by the requirements and existing application structure. The logic will obviously be in ASP, so there are no objects to define like we had for our Java application.

One of the things that should be understood at this point is that the scalability of this application will be limited because of the decision to use ASP instead of COM objects for the logic. Although this is consistently true when both are properly implemented, regardless of the task, it is especially true for applications in which server-to-server communication is occurring. Because of the nature of HTTP and TCP/IP, it will always be most efficient to have that process occurring within their own dedicated thread instead of a shared process space.

Without diving too much into the details, you should realize that any ASP pages contained within a site or virtual directory would run in a shared process. You can isolate virtual directories and sites from each other, but all pages within those directories will share that process.

Conversely, a COM object can run in its own process space. This effectively allows it to act as a client when communicating with the Web service, which is optimal for HTTP communication. Either one will work, but the COM solution would be optimal. For this application, the anticipated volume would be too low to merit a more costly approach and the ASP-based solution should be adequate for their needs. In the next section we will build another consumer that will need scalability, so we will build a more robust solution using COM objects for our application logic.

Note 

.NET will affect this decision considerably, if for no other reason than ASP. NET pages are compiled code and can be cached to provide a much improved response rate over the previous ASP processor.

Implementing the Consumer

Now that we have made our design decisions and technology decisions (or those that were available to us), we are ready to proceed with the implementation phase of our Web service consumer. Like the Java application we built, we have two main components to build: the application interface and the application logic. Unlike the Java application, both components are tied closely physically within our ASP page. They should still be implemented separately, perhaps even more so than in the Java example because the skill sets are different for the two. Some very good HTML developers do not do any ASP programming, and some very good ASP developers are lousy at building good application interfaces! Maintaining this approach allows you to have different roles focus on different components, especially within bigger projects.

Developing the Application Interface

For the application's interface, we will need to develop some HTML to define the inputs for the calculator service. Depending on the tool you are using (and perhaps the mode within that tool, for instance, Design versus Source for Visual Interdev), this may involve actual coding or dropping controls on a GUI tool as we did for our Java application. Personally, I still prefer to edit the source directly because any GUI, due to its very nature, has to make assumptions about what you are trying to accomplish, and there hasn't been an algorithm developed yet that can read my mind.

We determined in our design that we want to locate the interface for our service on a sidebar within the details page. This layout can be defined in a number of ways, but I think a table makes the most sense to use here. It will make it look like a part of the page and yet distinguish it enough to give it some recognition.

We need to capture within the table the appropriate data elements. Remember that, for the calculator service, we are concerned with four: principal, down payment, interest rate, and loan period. Of these four, only two can be manipulated by the user: down payment and loan period. The other two are set by Find-A-Home and cannot be edited. It would still be a good idea to have that information present within the table to give the user a complete view of all the data utilized for the calculation.

The down payment should probably be an open text field and the loan period restricted to a choice between the traditional 15- and 30-year finance plans on a home. Of course, we also need to have a submit button for the user to click on once he or she is done entering the data. The HTML code defining the interface we have just specified can be seen in Listing 8-5.

Listing 8-5: The Mortgage Calculator Presentation Code

start example
 <table border= "1" size="100%" align="left">      <tr>            <td align="middle" width="100%" height="100%">                 <font color="black" face="verdana" size="2"><b>Calculate<br>your                   Payment </b><hr>                 <form method="post" action="details_new.asp?                   <%=Request.QueryString%>">                      <p><font size=1>Principal:<br>                      <b><i><%=FormatCurrency(price*1000000)%></i></b>                      <p>Amount Down:<br>                      <input type="text" size="9" maxlength="8" name="downPayment"                        value="<%=Request("downPayment")%>">                      <p>Interest Rate:<br>                      <input type="hidden" name="rate" value="6.25">                      <i><b>6.25% Fixed</b></i>                      <p>Loan Period:<br>                      <b><i>15</i></b>                      <input type="radio" name="period" value="15" align=baseline                        <% if Request("period") = "15" then Response.Write                        ("Checked")%>>                      or <b><i>30</i></b>                      <input type="radio" name="period" value="30" align=absBottom                        <% if Request("period") = "30" then Response.Write                        ("Checked")%>>                      <p><input type="submit" name="Submit" value="Submit"></p>                 </form></font>            </td>      </tr> </table> 
end example

If you noticed, we also added some ASP code to this form to add some intelligence to it. First, we are passing the querystring through our form post to help keep track of what home is currently being viewed. We are also using some code to populate the form values so the user can see what was selected after submission. This is a standard nicety that most HTML interfaces incorporate to reduce the data entry within a Web application.

Now that we have the code defining our interface, we need to incorporate it into the details page on the site. (See Figure 8-8.) The details of how it's integrated aren't important, but, if you do want to see the complete code for the page, you can do so on http://www.architectingwebservices.com.

click to expand
Figure 8-8: Find-A-Home Realtors home details view with mortgage calculator

This nearly completes the application interface for our Web service. We haven't discussed the presentation for the calculated payment, but that will simply be an added line under the price. We will leave that for when we parse the response from the Web service.

Developing the Application Logic

Just as with the Java application, we have three fundamental tasks we need to perform for this Web service call: building the request, establishing the connection, and parsing the response. These tasks must be done in sequence to facilitate the consumption of this Web service correctly.

Building the Request

The request for this application is a little more involved than it was in our Java application. Instead of one field, we have three to incorporate into our document. Fortunately, every field is defined in the Web services interface schema as optional (see Listing 8-4), so we don't need to physically account for the other six elements in our document.

Because we are still dealing with only three data elements, it still makes sense to build the document dynamically. In fact, I would even stick with building the document as a string instead of creating a DOM object as seen below. (I'm actually hedging a little bit here because I know that I won't need the document in a DOM for sending to the Web service. If I did, I would probably change my stance on this because we would end up loading the string in a DOM anyway.)

 strRequest = "<calcInteraction>" strRequest = strRequest & "<amortizationPeriod>" & Request("Period") &   "</amortizationPeriod>" strRequest = strRequest & "<interestRate>" & Request("rate") & "</interestRate>" strRequest = strRequest & "<principalAmount>" &   cstr(price*1000000-Request("downPayment")) & "</principalAmount>" strRequest = strRequest & "</calcInteraction>" 

You'll notice that every value is a value from the form we just built, with the exception of price (the local variable already used to display the price of the house on that page). Everything else about this is fairly straightforward.

Establishing the Connection

The MSXML parser is quite enjoyable to work with because it contains not only the functionality to access, manipulate, and format XML data, but also the mechanisms for sending that data to other systems. Unlike the Java application, we need to work with only one library to utilize all of this functionality. Say what you will about the pros and cons of IBM's Java and Microsoft's ASP/COM development platforms, but the Microsoft parser wins this battle, hands down.

To facilitate the connection to the Web service, we first need to define the URL where it can be reached.

 strServiceURL = "http://www.architectingwebservices.com/mortgageCalc" 

We then need to use the XMLHTTP class of the MSXML parser to make our HTTP request. Once instantiated, its properties can be set through the open method. For this example, we will want to use the post method and set the URL to our strServiceURL string, and, because we don't want to make the call asynchronously, we set that flag to False.

 set objXMLConnect = Server.createobject("Microsoft.XMLHTTP") objXMLConnect.open "POST", strServiceURL, False 

All we have done at this point is opened an object on our side and set its properties; we haven't actually made the request yet. (That's a good thing because we haven't specified our request document yet!) Both of these are done through the send method of our XMLHTTP object.

 objXMLConnect.send (strRequest) 

With that, we are actually done with our connection to the Web service. Remember that, because the connection is stateless, no close needs to be executed. We established the connection, made the request, received a response, and closed the connection all with that one send command. The response is contained in the responseText property of our XMLHTTP object, so let's press forward and parse it.

Parsing the Response

The parsing of this particular response couldn't be too much easier because we want to reference only a single element (the payment). We already have the physical response in the property of our XMLHTTP object in a string format, so what we need to do is extract the value that is significant to us.

I prefer to use the DOM structure when accessing data, so we will take that approach here. We could use the SAX reader in the MSXML parser if this were a COM object, but, because we are doing this in ASP with VBScript, an event-driven system would be a little problematic! Here we see the standard code for creating and using the DOM to load our response document:

 Set objXMLDoc = Server.CreateObject("Microsoft.XMLDOM") objXMLDoc.async = false objXMLDoc.loadXML objXMLConnect.responseText 

Now that we have the data in a DOM structure, access is greatly simplified through the selectSingleNode method:

 objXMLDoc.selectSingleNode ("paymentCalculator/paymentAmount").text 

We will format this value using the formatCurrency procedure and place it underneath the home price in our page layout. (See Figure 8-9.) With this accomplished, we have completed the implementation for our Web service consumer. Before moving on to testing the application, let's go ahead and pull all of these code fragments we have seen into a cohesive block.

click to expand
Figure 8-9: Find-A-Home Realtors home details view with calculated payment

Pulling It All Together

Because the details page is submitting the form to itself, we can contain all of this logic in a single condition within that ASP page. (See Listing 8-6.) The ideal candidate for identifying that condition is the down payment value from our interface form. If it is empty, we don't want to request a calculation because we don't have the necessary data to build a valid request. You might question whether we can build a valid request without having the user select the loan period (either the 15- or 30-year option), but, having built the Web service, we know that, if the service isn't supplied a loan period, it defaults to 30 years.

Listing 8-6: The Mortgage Calculator Consumer Code

start example
 <% if request("downPayment") <> "" then      Dim objXMLDoc 'As XMLDOM      Dim objXMLConnect 'As XMLHTTP      set objXMLDoc = Server.CreateObject("MSXML2.DOMDocument")      set objXMLConnect = Server.createobject("MSXML2.XMLHTTP")      Dim strResponse 'As String      Dim strServiceURL 'As String      Dim strRequest      'Build the request Document      strRequest = "<calcInteraction>"      strRequest = strRequest & "<amortizationPeriod>" & Request("Period") &        "</amortizationPeriod>"      strRequest = strRequest & "<interestRate>" & Request("rate") &        "</interestRate>"      strRequest = strRequest & "<principalAmount>" &        cstr(price*1000000-Request("downPayment")) & "</principalAmount>"      strRequest = strRequest & "</calcInteraction>"      'Set the Web service URL      strServiceURL = "http://www.architectingwebservices.com/mortgageCalc"      'Make the request of the Web service      objXMLConnect.open "POST", strServiceURL, False      objXMLConnect.send (strRequest)      'Load the response into a DOM      objXMLDoc.async = false      objXMLDoc.loadXML objXMLConnect.responseText      %>      <tr><td height="10"></td></tr>      <tr>            <td align="middle">                 <font color="black" face="verdana">Your payment is only<b><i>                 <%=FormatCurrency(objXMLDoc.selectSingleNode                   ("paymentCalculator/paymentAmount").text)%>                 *</i></b>            </td>      </tr>      <tr><td height="20"></td></tr>      <tr>            <td align="middle">                 <font color="black" face="verdana" size="1"><i>*This payment is                   subject to taxes and insurance premiums that cannot be calculated                   at this time.</i>            </td>      </tr>      <%      Set objXMLDoc = nothing      Set objXMLConnect = nothing end if %> 
end example

Testing the Consumer

Testing the consumer is similar to testing the Java application in that the two main states to test are connected and disconnected. In a connected state, everything should work just fine. When connectivity is lost, however, no price value is shown when the form is submitted, and no message explaining the problem to the user is communicated either.

A modification we should make to this application is to add the error handling necessary to catch a connection failure and provide a user-friendly message. This essentially means utilizing the infamous On Error Resume Next line and doing our own error handling.

To make this change, add the On Error Resume Next line right after our condition on the down payment value has been met. Then, right before loading the response into our DOM object, we should add the following code:

 If err.number <> 0 then %> <tr><td height="20"></td></tr>      <tr>           <td align="middle">                <font color="black" face="verdana" size="2"><i>There was a problem                  calculating this payment at this time. Please try again later.</i>           </td>      </tr> <% else 

Then, immediately before you set the objXMLDoc and objXMLConnect objects equal to nothing, you want to add an end if to complete the conditional loop. After setting those objects, you could go ahead and add the line On Error GoTo 0 to resume normal error handling as seen in bold case in the following code:

 <% end if      Set objXMLDoc = nothing      Set objXMLConnect = nothing      On Error GoTo 0 end if %> 

Now, whenever an error does occur within the application, users at least get a message letting them know that the payment could not be calculated. This can be verified by disconnecting either the Web service provider or consumer from the network.

After running through these tests, our Web service is ready to be rolled out. Hopefully, you've gotten the impression that consuming Web services isn't particularly hard. Although your observation would be accurate, you also haven't consumed a Web services workflow, which will demand a little more effort. That is next!

Consuming a Web Services Workflow

For our final Web services consumer, we are going to encounter a scenario similar to our mortgage calculator consumer, but this time we will be consuming a Web services workflow. We will be adding this Web service to an existing application, but our previous consumers had to support only a single task: one request and one response. A consumer using a Web services workflow will require integration at several points, not just a single call for a single function.

As we saw while building them, a Web services workflow is more complex than a Web services call. Now we will see how this complexity carries over to the consumer. As I alluded to earlier, we would hope that the adoption effort is far less effort for the sake of mass adoption of our Web service. The first thing we need to recognize as a consumer is that adopting a workflow is more effort than a call due to the multiple interactions (either three or five depending on the presentation model we adopt for this specific Web service). This translates into a linear growth of effort from a call to a workflow, which shouldn't be too bad based on the relative ease with which we built the prior consumers.

However, an experienced developer will quickly recognize that a linear effort isn't realistic when complexity is also a factor. The good news is that the extra effort for the consumer is at the beginning, in the design phase. We will have many options to choose from, and it is important to make as many of those decisions as possible before we start the implementation phase. If we can come to our decisions beforehand, doing so will not only lead to a much better process and end product, but it should also help us to achieve linear growth in the implementation phase.

Even though consuming a Web services workflow is more complex, it will still use the same building blocks as the previous exercises. We will thus be required to build upon the concepts used for those consumers and extend them into some new areas. Consuming a Web service such as this demonstrates some of the capabilities and limits of what can be accomplished today with a feature-rich Web services workflow.

Our Scenario

We have a company, AAA Conferences, which sponsors conferences of all types all over the nation. Just like other event companies such as a CMP or DCI that sponsor technical conferences, this company does the same, but on a much broader scope of topics.

AAA Conferences has an existing and fairly robust Web site that allows users to browse its list of conferences and to register for any that they wish to attend. It accepts credit card transactions for the registration, so the company has some good exposure to Web applications as opposed to just Web sites.

The company has recently established a partnership with Milton hotels, a nationwide chain that offers a whole range of rooms in every major city in the United States. Through this partnership, AAA Conferences has been able to broker good discounts for all conference attendees anywhere in the United States.

Currently, prospective attendees need to call in to make a hotel reservation, which makes it difficult for both AAA Conferences and Milton to keep track of who is actually taking advantage of their partnership. In response to AAA Conferences and several other partners, Milton has developed a Web service that exposes their hotel reservation system (HRS) to partners over the Internet. This service is capable of checking room availability and providing additional content on its many hotels (such as images and marketing materials), as well as making reservations.

AAA Conferences wants to integrate this Web service into its site immediately and is looking to us to do it. Company officials want it implemented as quickly as possible because several conferences are scheduled for this very month. However, at the same time, the company doesn't want to compromise the quality of its Web site.

Analyzing the Existing Application

Now that we have our scenario, we need to do some analysis on the current application. (Fortunately, we are familiar with the Web service provided by Milton, so we don't need to spend time analyzing it.) AAA Conferences runs its site on a Microsoft-based platform, and the company officials shy away from putting too much logic in ASP. The company has a good staff, but they are new to Web services and need our assistance in just this implementation. The company's staff will be able to maintain any VB COM/ASP solution we develop.

Because we are integrating the Web service into a Web application, it's probably a good idea to start our analysis by walking through the site. Specifically, we want to explore the current registration process so that we can get some ideas on how we can integrate the service into the site. We will want to go all the way through the process, from selection of the conference to registration, payment, and confirmation.

Note 

This entire application is actually available online for viewing if you want to walk through it yourself. T he address is http://www.architectingwebservices.com/AAA.

If we start at the company's homepage, we will see a listing of this month's conferences, a couple of highlighted conferences, and a drop-down list of all upcoming conferences. (See Figure 8-10.)

click to expand
Figure 8-10: AAA Conferences homepage

Clicking on any conference on the home page, or selecting a conference from the drop-down list, takes us to the conference details page (Figure 8-11). Here we can see the location, the date, and description for the event.

click to expand
Figure 8-11: View conference details page

If we choose to register, we can click on the button on the details page to bring up the registration page. (See Figure 8-12.) Here we enter all of the personal information for our registration.

click to expand
Figure 8-12: Personal registration page

Once we submit this information, we come to the payment page (Figure 8-13). This is a standard online credit card form that accepts all the necessary information to make a transaction for payment of the conference. AAA Conferences does not accept any other form of payment for their registration fees.

click to expand
Figure 8-13: Credit card payment page

Submitting the credit card data brings us to the usual confirmation page where the consumer has one last chance to back out of the transaction. (See Figure 8-14.) The confirmation page also features a disclaimer to advise the user that the registration fee is nonrefundable.

click to expand
Figure 8-14: Registration confirmation page

Accepting the charges then takes you to the obligatory "Thanks for your order" page (Figure 8-15). Rejecting the charge on the previous screen takes you back to the homepage.

click to expand
Figure 8-15: Registration confirmed page

We likely did not cover some other areas of the site in this process, but they are of no real concern for this project. Let's go ahead and build a flow diagram of this process. (See Figure 8-16.) Such a diagram will be helpful in examining options for integrating the Web services workflow during our design phase, which is next.

click to expand
Figure 8-16: AAA Conferences registration process workflow

Designing the Consumer

In looking at these consumers we have been developing, it should become clear that the consumer is much more of a logical entity than it is a physical one. In our first scenario, we built a Java application that was specifically built for consuming the weather Web service. In the second scenario, we had to integrate the consumer into a page of a Web site. In this instance, the consumer will actually be spread out among different interfaces at different points. The AAA Conferences application will actually become the consumer, and we will provide the enhancements necessary to incorporate this functionality.

Because we are working with an existing application, we will start our design on the presentation layer. We will identify how and where we want to incorporate the Web service into the existing interface. We will then address the design of the integration layer to support our presentation layer design.

Designing the Presentation Layer

The design of our presentation layer will take a slightly different approach for this application. We will be concerned with the interfaces, but we first need to back up and look at the process as a whole. We need to take more of a story-boarding approach to developing the presentation layer because we are dealing with so many steps. We actually took this same approach for the Find-A-Home consumer, which was easily identified because it was a single page that integrated a Web services call. Here, we are looking at two processes with multiple steps (workflows), so, before designing the screens, we need to identify what is happening behind them.

To determine a design of this scale, we need to break this process into two steps: identifying the processes and then integrating the processes. If we don't have the exact functionality determined for each process, trying to integrate the two will likely be a very redundant and frustrating process.

Identifying the Two Processes

In our presentation layer design, our main objective is to incorporate the two processes and identify what needs to be added or changed on the AAA site. We have already mapped out the current registration process in Figure 8-16. We also know from our experience with the hotel reservation Web service that we have potentially five functions available to us:

  • Availability form

  • Availability request

  • Hotel detail view

  • Reservation form

  • Reservation request

These five constitute the maximum amount of functionality that we can count on from the Web service. In fact, along with this functionality comes stylesheets that can define the presentation for the entire process. Because we want to integrate this into an existing process, we will not want to utilize this as an isolated Web service. This means that we can eliminate two of the five steps: the availability and registration forms.

Looking at the remaining three functions, only the hotel detail view may be eliminated. Because this request produces only hotel-specific information that consists of five image references and some marketing text, the information is of questionable value. After all, the main appeal of these hotels is the pricing and the one-stop shopping convenience. Because we want to integrate this Web service quickly, dropping this function is certainly worth considering.

The risk of leaving the function out, however, is whether users are willing to make reservations for a hotel without having much information about it. Although this is a possibility, sacrificing this functionality might make the success of this new hotel reservation process less certain. Let's plan on going ahead and integrating this functionality, and we will determine later how much of the content we actually utilize.

To minimize the workload of the hotel detail view, our application could automatically make the Web services request for every availability match (similar to the hidden Web service model we discussed in Chapter 5). This would eliminate an extra process step and a separate hotel detail screen for the user. Because this isn't a travel site producing several matches per request, we don't need the information quite so streamlined, as provided by the service owner. Depending on the criteria selected by the user, up to three hotels may be returned for a given availability request, which would mean three extra calls to the service. However, because we are programming this logic in a COM object, our performance shouldn't suffer too much from those extra requests.

Note 

You should be aware that this type of design decision is feasible only because the responses from these requests are exposed on a data level to us. Without the ability to manipulate the data on our side, matching up the data from these two requests would be problematic, if not impossible.

This leaves us with essentially two exposed steps to integrate from the Web service. The first step is the availability request, which we will supplement with a back end request to the detail view process. The second function will then be the reservation request. If we map the process to these functions and add the necessary interfaces, we will end up with five steps in our Web services consumer: selection of hotel criteria (hotelCriteria), hotel selection (hotelAvailability), reservation request (registration), reservation confirmation (confirmation), and a reservation confirmed page (confirmed). (See Figure 8-17.) Now that we have our Web services process mapped out, let's proceed with integrating the two.

click to expand
Figure 8-17: Reservation Web service consumer process workflow

Integrating the Two Processes

If we look at our two processes, we have one consisting of six steps and one consisting of five. One of the key objectives in designing a Web application is to minimize the number of clicks necessary to accomplish a given task. To help in this effort, we should try to find the lowest common denominator of these two processes in an attempt to create one straightforward process for the user. We should end up with no more than eleven total steps if we design it properly. This is because, no matter your skill and savvy as a designer, if two processes have absolutely no overlapping functionality or usability, you are essentially stacking one process on top of another. However, you should always try to lower the total number of steps when possible.

In this scenario, we have some clearly overlapping functionality. For instance, the payment, reservation confirmation, and confirmed steps can essentially be merged together. Unfortunately, the merging of steps within these two processes ends there. We have two more steps that we need to integrate from our Web services workflow: availability request and hotel selection.

What we need to do next is identify the entry points for these two steps in the current registration process. This determines where to expose the hotel registration process to the user and how to do it. We don't want to be so subtle that it is glossed over, but we also want to protect the existing process for those users who want to just register for the conference. Our choices for entry essentially fall between the conference selection and payment steps of the registration process.

Because each step is a transaction in the registration process, we can't very easily lead them off to the hotel registration process within one of these steps. Taking this approach, we would either be asking users to enter their personal information or payment information, and at the same time asking them if they want to make a hotel reservation. This doesn't flow very well, and so we will likely need to interrupt the current flow with a new screen that asks them if they also want to reserve a hotel room. (See Figure 8-18.) This approach may not be as integrated as we might like it, but it certainly isn't overly subtle!

click to expand
Figure 8-18: Adding the hotel option step to the registration process

Once we establish this intercept point, we can branch our availability request and hotel selection off of it. This could then lead directly back into the payment step of our registration process. The issue here might be that we are moving too quickly through the process for the user. We would essentially move from a list of hotels that are available and go directly on to the payment information.

If we look back to where we integrated the two registration confirmations, we might start to reconsider that decision. After all, one is confirming a single registration fee. It doesn't matter when you actually attend the conference. The hotel confirmation is for specific dates, and the charges are even dependent on those dates. Although we can augment the information presented in that step, we should probably maintain our own confirmation step prior to forwarding the user to the payment step.

If we look at where we are, we will see that we have all of our steps accounted for. We should go ahead and develop our modified registration process diagram (Figure 8-19) so that we have clearly accounted for each step. With some shades and patterns, we can even identify which steps come from which process, which are brand new, and which are modified. This will help us tremendously during the implementation of our presentation layer.

click to expand
Figure 8-19: AAA Conferences modified registration process

Between the two processes, we need to make very few big decisions on a screen level in the modified process. Either the information already exists in the existing process, or the Web service requires it to process a request. We will leave the other details on the screens to the implementation phase of the project and the Web developers who handle those issues best. This means that we are ready to move on to the integration layer.

Designing the Integration Layer

In this scenario, the integration layer becomes the true middleware tier that it was intended to be in our architecture models from Chapter 2. This layer facilitates the functionality necessary at the steps that involve the Web service, and this process involves defining some logic and some data integration.

If we take a different view of the modified registration process (see Figure 8-20), we can see where the Web service interaction takes place in the process. It is here where the integration layer comes into play. It is also helpful to identify in this view the different data elements that must be collected to build the requests for the Web service.

click to expand
Figure 8-20: Web service interaction within the registration process

Although we already knew we were dealing with three Web service requests at two steps, it is always reassuring to confirm that with the presentation layer design. We see in our flow that the availability request and hotel detail calls are both made at the availability view, and that the reservation request is made at the reservation confirmed step.

The next thing we need to do is design the logical components for making these Web service requests. We have already determined that we will use a COM component for our logic, so we have an interface to define.

Defining the COM Interface

The first design decision to make is how to handle the availability/hotel details request. We could define two different methods and allow the ASP to control that, but this would not benefit as much from the potential COM scalability and performance. We could define one method that handles both Web service requests, but any changes to this process would be more difficult to make.

Although officials at AAA Conferences could change their minds later, it is more likely that they will maintain this consecutive-calls approach because their reasons for doing so today are not likely to change down the road. The company won't become a travel agency overnight, and, if it does, it has bigger issues to deal with than this little COM object. What we will do is allow the call to the object to dictate whether the additional detail information should be requested with the availability matches. This will affect our component's interface, which is appropriate because that is what we need to look at next.

We need to determine what parameters to pass to our component and what information it will return. The return is probably a little easier to handle first: we want the data in a string. A lot of information of arbitrary sizes will be coming back, so we are most likely to want it in some XML/HTML format. We will leave these details for later when we define our data integration points.

The parameter structure for this hotel availability/details request will not be so easy to define. We could take the same approach as the return, defining a single string for the input and pass in XML. This would be somewhat self-defeating, though, because one of the reasons we are using the COM object is to avoid having to work with XML directly in the ASP page. If we defined the parameter as a string, the ASP would then be responsible for converting the form values into XML.

If we look at the data that is necessary to make the availability request (as shown in the following list), we see that it contains a lot of the information we will be collecting in our HTML form:

  • Check-in date

  • Check-out date

  • Number of adults

  • Number of children

  • City and state

  • Coupon code

  • Price preference

  • Bed size preference

  • Smoking preference

  • Requested hotel amenities

Because all the data necessary to initiate this step is coming from the form, we could pass the request object to our method and be done with it. However, not all the information needs to be collected by the user. For instance, the city, state, and coupon code are all defined by AAA Conferences for a given event. We could pass these as hidden form values, but this makes them vulnerable to manipulation by the user. Because the agreement applies to the cities in which AAA Conferences is holding events, we should probably be more protective of that information. So, if we add these data elements to the details flag we defined earlier, we will end up with a method that looks something like:

 Public Function checkAvailability(myRequest As Request, couponCode As String,   eventCity As String, eventState As String, getDetails As Boolean) As String 

Caution 

At some point, you will have to account for the individual form values within the Request object. If you pass it as an object instead of enumerating each form field (as we have done here), you are potentially passing bad data to other components. Although corrupt data could cause an error, catching this kind of an error is only protecting malicious users from themselves because it would involve tampering with our HTML form. Although we could catch this exception, we will not do so in this implementation. You can test this case for yourself at http://www.architectingwebservices.com/AAA.

Next, we need to define the method for our reservation request process. We could take the same approach as the checkAvailability method and pass it the request object, but remember that the reservation request is made long after the data is collected. That information will need to be persisted somewhere other than the request object. AAA currently uses the session object for long-term state management (of variables only, no objects), so that is what we will use as well.

Unlike the availability request function, the session will contain all of our data, so no other parameters are necessary. We will also receive the return value through a string. Putting this together then gives us the following method:

 Public Function makeReservation(mySession As Session) As String 

Now that we have our COM interface defined, it is time to turn our attention to how we will expose this data through the registration process. We will be dealing with more data than in any of the other examples, so this topic needs some special consideration.

Defining the Data Integration Points

Before proceeding on to our implementation, we need to spend a little more time on defining how the data from our Web service requests will integrate with the data from the registration process. Part of this was accomplished through Figure 8-20 and identifying where the data elements required by our Web service will be collected. We know that we can fairly easily capture that data in the appropriate format to make our requests of the Web service. Because these requests are somewhat substantial, we will aid this process by providing templates for each request just as we did for some of the responses in our Web services. With that defined, what we need to focus our attention on now is integrating the data from our Web service into the registration application.

We know that we are receiving the data from the Web service in an XML format. We also know that we are presenting the data to the user via HTML. So far, so good! What we must determine now is how to get the data from the one format to the other. The options we have include element extraction from a DOM, SAX, manual parsing, and XSL stylesheets. As usual, different methods have different benefits, depending on the situation.

What we have are two different points at which the data produced from the Web service must be presented to the user. One is where we present the availability matches based on the criteria submitted. This could potentially be zero or several matches (based on the constraints of the Web service). Because the data volume is so variable, this is a good opportunity to use an XSL stylesheet. The question then is whether to run this transformation within the COM object or within the ASP page.

Besides the performance benefit of running the process inside the object, we also have to consider reusability. More than likely, this data will be contained in a DOM at some point if we are working with it. Because it is necessary for the data to be contained in a DOM to perform the transformation, it makes sense to do it there. Otherwise, we would be loading the XML data into another DOM in the ASP to perform the transformation.

The other curve thrown at us in this scenario is that the response from the checkAvailability call will provide data from two different Web service calls: one for the actual availability request and another for the hotel details. At some point, this data needs to be integrated if it is to be returned as a single XML string. Although we will handle this issue in our implementation phase, we need to consider how this will affect the XSL transformation approach. Do we run two transformations separately and then combine them? If so, that would be a lot of effort for what will become a single data set. Or do we run a transformation on one of the data sets that generates the stylesheet for the next transformation, essentially combining the two data sets? Although this is feasible, it is not simple and certainly not maintainable for AAA Conferences. Instead of either of these approaches, let's assume that we will combine the data and run a single transformation that produces the desired final output for our application.

The other data interaction point is the reservation request. This is a little more similar to our calculator service in which we were interested in only a single piece of data from the response. In this situation, the data element we want is the confirmation number, so that is what we will pass directly through the response.

With our data integration points defined, at least at a high level, we have completed enough of the design to confidently move on to the implementation of our Web service consumer. Undoubtedly, there will be more design decisions to come, but they either should be on a micro level relative to these decisions or they will simply not reveal themselves until we start the implementation process.

Implementing the Consumer

This consumer is much larger than the previous consumers, so we will be developing far more. This consumer will manifest itself through four different entity types: ASP pages, a COM object, XML templates, and XSL stylesheets. Together, all four will be able to provide something that previously was not possible: integrating two different processes via the Internet.

ASP Pages

The first thing we will work on is creating the new ASP pages and modifying the necessary existing pages. If we refer back to Figure 8-19, we can see exactly where these changes will need to take place. The best way to approach the ASP pages is to step through the process like we did in our analysis and take it a page at a time. I will spare you from walking through all the code in these pages. Instead, I will present you with a screenshot of their view and highlight only the most interesting code bits.

hotelOption.asp

The first page we need to generate is the hotel options page—the page where the user is first presented with the opportunity to make a hotel reservation using the new functionality on the site. (See Figure 8-21.) This really is a very simple page with no logic, so there isn't any complexity in the code behind it.

click to expand
Figure 8-21: Hotel option web page

hotelCriteria.asp

If the user chooses to make a hotel reservation, the hotel criteria page is the next one presented. (See Figure 8-22.) Here, all the user's criteria will be collected and combined with AAA Conference's data to make the availability request of the Web service. The available dates utilize a checkbox input field, which presents the possibility of having a gap between dates. For example, the user could select the checkboxes for Sunday and Wednesday and not Monday or Tuesday. We don't want to allow this kind of gap, so we will add some client-side script to eliminate that possibility.

click to expand
Figure 8-22: Hotel criteria web page

The interesting bit of code in this interface comes through the building of the available nights at the top of the form. What we are doing is taking the start and end dates of the conference and calculating all the valid nights necessary for an accommodating hotel reservation, starting with the night before the first day and ending with the evening of the last day.

 <%for x = 1 to (datediff("d",startDate,endDate)+2)%>      <td align="center">;nbsp;           <FONT face=Verdana size="1">;nbsp;           <%=weekdayname(datepart("w",Dateadd("d",x-2,startDate)))%>&nbsp;<BR>           <%=datepart("m",Dateadd("d",x-2,startDate))%>/             <%=datepart("d",Dateadd("d",x-2,startDate))%><BR>           <input type="checkbox" name="resDate"             value="<%=dateadd("d",x-2,startDate)%>">      </td> <%next%> 

One other thing to note here is that, instead of giving users an option to enter their preferred price range, AAA Conferences wanted to instead try a more general approach by listing the criteria of cheapest available, midrange, and best room available. This approach was taken because AAA thought that a price was too dependent upon the area and that these three options might present the user with a more realistic expectation of the rooms that are available for a given range of prices in that area.

This sounds complicated, but its execution is elementary. Behind these values are basic dollar amounts that should generate the desired result. For instance, behind the cheapest available option is a value of $50. Behind the best room available is a value of $500. Although it might be hard to find a room that actually costs that much within the Milton system, it at least communicates that the sky is the limit for this request.

hotelAvailability.asp

Once the criteria are submitted, the hotel availability page presents the results to the user. There may have been no matches, or several, depending on the criteria submitted. This information is laid out in simple rows with the fields identified at the top. (See Figure 8-23.) To the right are the buttons for actually selecting one of these hotels. The options at the bottom are for making another request using different criteria or escaping the process entirely.

click to expand
Figure 8-23: Hotel selection web page

This page is actually making the call to our checkAvailability method, so it is worthwhile to show that request here. Notice how the return value is later displayed "as is" within a table cell on the page. This means that the response is properly formatted for presentation directly to the user. We will look at that functionality later in the "COM Object" section.

 <% set objWS = Server.Createobject("hotelResWS.WSRequest") myResponse = objWS.CheckAvailability(Request,"AAA456",city,state,true) set objWS = nothing %> ... <TD colspan="2"><%=myResponse%></TD> ... 

hotelConfirmation.asp

This is the additional page that we added when reflecting on our process flow. Here we are confirming just the selection of the hotel and giving one more convenient escape out of their selection. (See Figure 8-24.)

click to expand
Figure 8-24: Hotel confirmation web page

Although this page doesn't have any code that's too exciting, it is worth recognizing that we are storing some data in session variables here. This information will be referenced later when we make the registration request. Notice that one of these elements is a session ID, that is from the reservation Web service. (Hence, the mSessionID name to identify it as Milton's.) Part of our compliance in consuming this Web service is that we maintain the session ID provided after our initial request to the service for the length of the session. This allows the Web service to maintain state on its side between the availability and reservation requests.

 session("hotelID") = Request("hotelID") session("roomType") = Request("roomType") session("mSessionID") = Request("session") 

confirmation.asp

This page has only a few minor text changes that present some of the information concerning the hotel component of the registration. (See Figure 8-25.) Everything here is entirely visual, not functional.

click to expand
Figure 8-25: Registration confirmation web page

confirmed.asp

This process is completed with the registration confirmed page. We are adding on this page the call to the makeReservation method and providing a confirmation to the user when the reservation is successful. (See Figure 8-26.)

click to expand
Figure 8-26: Registration confirmed web page

It is worth noting here that we are using the session variable hotelID to know whether to trigger the reservation request. We are setting that only on the hotel confirmation page, and we are deleting it whenever the user exits the process, so it should be safe to use for that purpose.

 <% if session("hotelID") <> "" then      set objWS = Server.Createobject("hotelResWS.WSRequest")      myResponse = objWS.makeReservation(Session)      set objWS = nothing end if %> 

If the request encounters an error during the process, the Web service will respond with an error tag within the XML document. Because there is nothing we could do to compensate for an error, we just need to let the user know if the reservation failed to go through and direct him or her to the appropriate phone number. Instead of loading the response into a DOM to make this determination, I am simply searching for a substring that contains the error tag name, which should be quicker and just as effective. This simple string search can be very effective in situations such as this where you need to check for the presence of a specific element.

 <% if instr(myResponse,"<Error>") <> 0 then %>      We are sorry, but there was a problem with your reservation request with        Milton hotels.<br>Please call <b><i>1-800-4MILTON</i></b> to make your        reservations for the conference. <% else %>      Your reservation with Milton Hotels has been confirmed. Your reservation        number is<br> <b><i><%=myResponse%></i></b><p>Please call<b><i>1-        800-4MILTON</i></b> 24-hours ahead of time if you need to cancel your        reservation. <% end if %> 

XML Templates

With our ASP pages completed, it is now time to build our request templates. These documents will help in ensuring that our requests are valid as well as helping in the performance of our XML handling. Any consistent document structure containing dynamic data will benefit from this approach, and these requests are no different.

For the most part, these templates are taken directly from the Web services interface schemas. When appropriate, we have removed optional nodes that will go unused and repopulated the data when it is static.

Availability Request Template

For this template, we have populated the stage and sessionID service variables. Although this may be a secondary request, most of the time it is not, and the logic will benefit from assuming it does not have to manipulate that element unless it has a value on hand.

We have also populated the adult and child values because all reservations will be done on an individual basis. If Milton and AAA Conferences determine that they want to extend their discounts to the families of the conference attendees, a change will need to be made to accommodate this.

We have also defined a preferences node but have left it devoid of any of its children: amenity, bedSize, and smoking. If these properties are provided, the nodes will be created and added to the preferences node. If none are added, the preferences node will be deleted. The thinking here is that the probability of at least one of the children being necessary is high, but we don't know which one it might be. Even though the preference node must have a child node added or removed, this is most likely the most efficient structure for this node.

The optional landmark field has also been removed because all hotels will be searched for by only city and state. If this requirement changed, this template would need to be changed, but, more importantly, the logic in our COM object would have to change, which would be far more significant. You can see the final template for our availability request in Listing 8-7.

Listing 8-7: availabilityRequestTemplate.xml

start example
 <?xml version="1.0" encoding="UTF-8"?> <hrs:reservationWS xmlns:hrs="urn:reservationWS">      <hrs:serviceVariables>           <sessionID>0</sessionID>           <stage>1</stage>      </hrs:serviceVariables>      <hrs:payload>           <hrs:availabilityInteraction>                <availabilityRequest>                     <checkInDate/>                     <checkOutDate/>                     <numberAdults>1</numberAdults>                     <numberChildren>0</numberChildren>                     <locale>                          <cityState>                               <city/>                               <state/>                          </cityState>                     </locale>                     <couponCode/>                     <approxPrice/>                     <preferences/>                </availabilityRequest>           </hrs:availabilityInteraction>      </hrs:payload> </hrs:reservationWS> 
end example

Detail Request Template

The detail request template (Listing 8-8) is the smallest because all it needs to contain is the ID for the hotel for which content should be retrieved. We have already populated the stage variable because we know this is a stage 2 request.

Listing 8-8: detailRequestTemplate.xml

start example
 <?xml version="1.0" encoding="UTF-8"?> <hrs:reservationWS xmlns:hrs="urn:reservationWS">      <hrs:serviceVariables>           <sessionID/>           <stage>2</stage>      </hrs:serviceVariables>      <hrs:payload>           <hrs:detailInteraction>                <detailRequest>                     <hotelID/>                </detailRequest>           </hrs:detailInteraction>      </hrs:payload> </hrs:reservationWS> 
end example

Reservation Request Template

The reservation request template is the one in which we have deviated the most from the service's interface schema. Although we are only populating the stage element as we did in the other templates, we are removing several optional nodes from the schema. First is the second address node under the homeAddress element. More times than not, this data will not be provided by the user, so we will create it when necessary as opposed to deleting it when it is not.

Finally, we also removed the entire billAddress element and its children. It is optional because when it is not present, the home address is used by default. AAA Conferences already requests that the user's address entered in the registration form match the billing address for the credit card that is used for payment. You can see the entire request template in Listing 8-9.

Listing 8-9: reservationRequestTemplate.xml

start example
 <?xml version="1.0" encoding="UTF-8"?> <hrs:reservationWS xmlns:hrs="urn:reservationWS">      <hrs:serviceVariables xmlns:hrs="urn:serviceVariables">           <sessionID/>           <stage>3</stage>      </hrs:serviceVariables>      <hrs:payload>           <hrs:reservationInteraction xmlns:hrs="urn:reservationInteraction">                <hrs:reservationRequest xmlns:hrs=" urn:reservationRequest">                     <hrs:hotelData>                          <hotelID/>                          <roomType/>                     </hrs:hotelData>                     <hrs:personalData>                          <homeAddress>                               <address/>                               <city/>                               <state/>                               <zipCode/>                          </homeAddress>                          <firstName/>                          <lastName/>                          <homePhone/>                          <hrs:ccData>                               <number/>                               <issuer/>                               <expirationDate/>                               <nameOfOwner/>                          </hrs:ccData>                     </hrs:personalData>                </hrs:reservationRequest>           </hrs:reservationInteraction>      </hrs:payload> </hrs:reservationWS> 
end example

COM Object

Now that we have completed the development of our ASP pages and templates, we have some code to put together in our COM objects to bring this all together. As we defined earlier, we have two public methods: checkAvailability and makeReservation. As with our two previous consumers, we need a way to store the address of the Web service. For this purpose, we have declared a global constant that can be referenced by both functions:

 Const strServiceURL = "https://www.architectingwebservices.com/   hrsws/embedded/default.asp" 

We should also recognize the library references that we will need to make to incorporate the appropriate functionality for this component. As with the other examples, we will be using the Microsoft, XML v4.0 parser (currently the MSXML4 Technical Preview). Because we will be working with the request and session objects, we will also need to reference the Microsoft Active Server Pages Object Library.

The development of these COM objects will be made easier if you have, in fact, developed the ASP pages already. These pages can call the functions in their various stages of development if they are running within the development environment. This can help you to debug whether the component is behaving correctly or if your logic is working correctly.

Caution 

Although running the component in the Visual Studio IDE is a great way to debug distributed applications, certain combinations of platforms and tool versions have kept this from being successful. Microsoft has an article on this issue (http://support.microsoft.com/support/kb/articles/Q259/7/25.ASP), which contains workaround information if you have trouble running in this mode.

checkAvailability

This method is responsible for making the availability request and making the second request for each matching hotel, retrieving its content. That may not sound like much, but several steps need to be performed both before and after these requests to make this information applicable and usable.

We can actually group these tasks similarly to how we grouped the application logic for our previous consumers. The one exception from that process is that we won't be programmatically parsing the response. We will be using an XSL stylesheet to do that for us. However, we also have an extra task that we need to perform: we need to enhance the response before sending it on for the transformation.

We need to add the appropriate data from the secondary request for the hotel details. However, this process has some added complexity because we need to pass data through that transformation to maintain AAA's existing registration process.

Because the XSL will now be responsible for all of the data on the page, it needs to maintain the current application's state, such as what conference is being registered. This may appear to complicate the process (the effect of which is actually rather slight), but the alternative is to add a step in the ASP by loading the response into a DOM and then performing another transformation that incorporates this state data.

This leaves our list of tasks for this method at (1) build the request, (2) invoke the Web service, and (3) modify the response. This XSL approach works well because the transformation of the data can be treated as further tweaking of the response without pushing responsibility to the ASP.

Building the Request

Building this request will be a little more complex than anything we have encountered yet. We have a few challenges due to the variation and even existence of criteria data. Additionally, we will need to do some massaging of the submitted data due to the format of the submitted data (HTML form fields.) This is the data that we will use for our Web service request, so we need to spend a little effort prepping this data for use within our payload.

The first challenge we will tackle is the reservation dates, and it is a good example of the problems we can encounter in this area. We have a variable number of days for which the user wishes to check for room availability. It could be one night during the conference, or all four or five nights. This information was collected in a series of checkboxes, so we will have a string delimited by commas to parse through.

The easiest way to accomplish this is by splitting the string into an array. We can then use the LBound and UBound functions to reference the first and last days in the array. This process is shown in the following code:

 myArray = Split(myRequest.Form("resDate"), ",") dtCheckIn = myArray(LBound(myArray)) dtCheckOut = myArray(UBound(myArray)) 

Now that we have the date of arrival and the date of departure, we need to format them for use as an XML date data type. This takes the form of "yyyy-mm-dd". Unfortunately, there is no VB function that will convert a date to this format automatically, so we will need to build it manually. It looks something like this:

 strCheckIn = DatePart("yyyy", dtCheckIn) & "-" If Len(DatePart("m", dtCheckIn)) < 2 Then strCheckIn = strCheckIn & "0" strCheckIn = strCheckIn & DatePart("m", dtCheckIn) & "-" If Len(DatePart("d", dtCheckIn)) < 2 Then strCheckIn = strCheckIn & "0" strCheckIn = strCheckIn & DatePart("d", dtCheckIn) 

Once this is done for both the check-in and check-out dates, they are ready to use in our request document. The next challenge arises from the amenities element, which is another element that was exposed through an HTML checkbox, meaning that zero or multiple values may be provided. Unlike the date, we don't need to find the minimum and maximum values within the values, so we can simply split this string into an array for now:

     myArray = Split(myRequest.Form("amenities"), ",") 

Now that we have all our data in a usable format, we can proceed with building the request document for the availability request service. As we saw before, we have developed a template to aid in building each of our requests. All we need to do is load the template into a DOM, create a reference to it through a node object, and we will be ready to start putting data in it.

 objXMLDoc.async = False booltest = objXMLDoc.Load("http://www.architectingwebservices.com/aaa   /templates/availabilityRequestTemplate.xml") Set objXMLNode = objXMLDoc.selectSingleNode("hrs:reservationWS/hrs:payload/   hrs:availabilityInteraction/availabilityRequest") 

With our template now loaded, we can proceed to set the values of the various nodes. We will start with the variables that don't require any further checking or qualification:

 objXMLNode.selectSingleNode("checkInDate").nodeTypedValue = strCheckIn objXMLNode.selectSingleNode("checkOutDate").nodeTypedValue = strCheckOut objXMLNode.selectSingleNode("locale/cityState/city").nodeTypedValue = eventCity objXMLNode.selectSingleNode("locale/cityState/state").nodeTypedValue = eventState objXMLNode.selectSingleNode("approxPrice").nodeTypedValue =   CInt(myRequest.Form("pricerange")) objXMLNode.selectSingleNode("couponCode").nodeTypedValue = couponCode 

Tip 

Notice how I used the CInt conversion call to format the pricerange variable for storing it in the approxPrice node. It is always a good idea to type the data correctly when using the nodeTypedValue property for setting the node value. The conversion doesn't happen automatically, and it will likely generate an error if it is incorrectly typed.

We now come to a few tricky little variables that require some special attention. The session variable in the service variables may or may not exist, so we need to first check for its existence. If it is present, then we will add it. Otherwise, it will remain the default value defined in our template, zero.

 If myRequest.Form("session") <> "" Then   objXMLDoc.selectSingleNode("hrs:reservationWS/hrs:serviceVariables/sessionID").   nodeTypedValue = CInt(myRequest.Form("session")) 

We will now need a different approach to handle a couple of similar variables where, if they do exist, we need to create the node, set its value, and then append it in the proper location. Following is the code for handling the smoking element. The bedSize element would need to be handled in a similar manner.

 If myRequest.Form("smoking") <> "" Then      Set objXMLChild = objXMLDoc.createElement("smoking")      objXMLChild.nodeTypedValue = CBool(myRequest.Form("smoking"))      objXMLNode.selectSingleNode("preferences").appendChild objXMLChild End If 

Another slightly different scenario exists for populating the amenity value(s). Here we will step through the array, creating and adding an element for each instance within it.

 For Each amenity In myArray      Set objXMLChild = objXMLDoc.createElement("amenity")      objXMLChild.nodeTypedValue = CStr(amenity)      objXMLNode.selectSingleNode("preferences").appendChild objXMLChild Next 

Caution 

It is tempting to think that we could make this process more efficient by pulling the createElement call out of the loop. Although it is rational to think that this might work, in reality it does not, at least if you are adding elements in the same level within the DOM. In this scenario, each subsequent appendChild call overwrites any prior nodes added. You must create a new element for this process to work correctly. If you want to add the same node type to a different location in the DOM, this would be an acceptable approach.

Finally, we need to remove the preferences node if we did not add any children to it:

 If objXMLNode.selectSingleNode("preferences").hasChildNodes = False Then   objXMLNode.removeChild (objXMLNode.selectSingleNode("preferences")) 

Establishing the Connection

As we saw in the previous consumer, making the Web service request with the MSXML parser's XMLHTTP class is fairly simple and direct. We have added a little more complexity to this particular scenario because the service requires a client certificate. Once our server installs the client certificate (see Chapter 6), we need to simply reference it using the setOption property of our XMLHTTP class. Then we make the request just as we did before:

 objXMLConnect.setOption SXH_OPTION_SELECT_CLIENT_SSL_CERT, "AAACert" objXMLConnect.open "POST", strServiceURL, False objXMLConnect.send (objXMLDoc.xml) objXMLAvailResponse.loadXML (objXMLConnect.responseText) 

Modifying the Response

In this scenario, we are modifying the response instead of just parsing it. As we discussed earlier, the XSL will be providing the final response to our caller, but we need to ensure that it has all the necessary data to maintain state for the application through the hotel selection process.

The variables we need to maintain are the conference ID and the arrival and departure dates. We will look here specifically at the code for the conference ID. First, we need to create a node that will represent the conference ID. Next, we set the value to the conference variable in our request object, and, finally, we append the node to our response document. I have selected the serviceVariables element because it is a fairly safe location, and we won't be sending this document back to the Web service:

 Set objXMLChild = objXMLDoc.createElement("conference") objXMLChild.nodeTypedValue = CInt(myRequest.Form("conference")) objXMLAvailResponse.selectSingleNode("hrs:reservationWS/   hrs:serviceVariables").appendChild objXMLChild 

Now that we have added these variables, this response is ready to be transformed. But, before we can do that, we need to add the content from the hotel details response. Just like we did for the availability request, we will be loading a template to help us generate a valid request document for the hotel details. Once this template is loaded, we will load a node list with all instances of the hotel ID in our availability response and loop through it, setting the hotel ID for each request. We also maintain a separate node instance that keeps track of which hotel we are working with. Once the request is made, we will create an element called description and use this node to locate it within our DOM. You can see the logic for integrating the hotel detail content into the availability function in Listing 8-10.

Listing 8-10: The Hotel Detail View Integration

start example
 logicSet objXMLNodeList = objXMLAvailResponse.getElementsByTagName("hotelID") Set objXMLNode = objXMLAvailResponse.selectSingleNode("hrs:reservationWS/   hrs:payload/hrs:availabilityInteraction/availabilityResults/hotel") For x = 0 To objXMLNodeList.length - 1      If x > 0 Then Set objXMLNode = objXMLNode.nextSibling      objXMLDoc.selectSingleNode("hrs:reservationWS/hrs:payload/        hrs:detailInteraction/detailRequest/hotelID").nodeTypedValue =        objXMLNodeList.Item(x).nodeTypedValue      '—Make the request      Set objXMLChild = objXMLDoc.createElement("description")      objXMLChild.Text = objXMLDetResponse.selectSingleNode        ("hrs:reservationWS/hrs:payload/hrs:detailInteraction/hotelContent/        hotelDescription").Text      objXMLNode.appendChild objXMLChild Next 
end example

And, finally, we are ready to transform our data to the final output format:

 checkAvailability = objXMLAvailResponse.transformNode   (objXSLDoc.documentElement) 

We will look at the XSLT in the stylesheet we are using later in the "XSL Stylesheet" section. It was more important to define our data structure than it was to have our stylesheet available now, but built on assumptions.

Pulling It All Together

Before moving on, let's go ahead and review the checkAvailability method in its entirety. (See Listing 8-11.) We omitted some of the details on purpose so that we could concentrate on the core issues, but we should now review the entire code set to make sure that we understand how this all comes together to provide the correct response.

Listing 8-11: The checkAvailability Method

start example
 Public Function checkAvailability(myRequest As Request, couponCode As String,   eventCity As String, eventState As String, getDetails As Boolean) As String Dim objXMLDoc As MSXML2.DOMDocument40 Dim objXMLNode As MSXML2.IXMLDOMNode Dim objXMLChild As MSXML2.IXMLDOMNode Dim objXMLConnect As MSXML2.ServerXMLHTTP40 Dim objXMLAvailResponse As MSXML2.DOMDocument40 Dim objXMLDetResponse As MSXML2.DOMDocument40 Dim objXMLNodeList As MSXML2.IXMLDOMNodeList Dim objXSLDoc As MSXML2.DOMDocument40 Dim dtCheckIn As Date Dim dtCheckOut As Date Dim myArray As Variant Dim arrLen As Integer Dim price As Integer Dim strCheckIn As String Dim strCheckOut As String Dim booltest As Boolean Set objXMLDoc = New DOMDocument40 Set objXSLDoc = New DOMDocument40 Set objXMLAvailResponse = New DOMDocument40 Set objXMLConnect = New MSXML2.ServerXMLHTTP40 'Extract arrival and departure dates arrLen = 0 myArray = Split(myRequest.Form("resDate"), ",") For Each i In myArray      arrLen = arrLen + 1 Next dtCheckIn = myArray(0) dtCheckOut = myArray(arrLen - 1) 'Format arrival and departure dates for XML date format strCheckIn = DatePart("yyyy", dtCheckIn) & "-" If Len(DatePart("m", dtCheckIn)) < 2 Then strCheckIn = strCheckIn & "0"      strCheckIn = strCheckIn & DatePart("m", dtCheckIn) & "-" If Len(DatePart("d", dtCheckIn)) < 2 Then strCheckIn = strCheckIn & "0" strCheckIn = strCheckIn & DatePart("d", dtCheckIn) strCheckOut = DatePart("yyyy", dtCheckOut) & "-" If Len(DatePart("m", dtCheckOut)) < 2 Then strCheckOut = strCheckOut & "0" strCheckOut = strCheckOut & DatePart("m", dtCheckOut) & "-" If Len(DatePart("d", dtCheckOut)) < 2 Then strCheckOut = strCheckOut & "0" strCheckOut = strCheckOut & DatePart("d", dtCheckOut) 'Extract amenities myArray = Split(myRequest.Form("amenities"), ",") 'Load Template objXMLDoc.async = False objXSLDoc.Load ("http://www.architectingwebservices.com/aaa/transforms/   availabilityResponse.xsl") booltest = objXMLDoc.Load("http://www.architectingwebservices.com/aaa/   templates/availabilityRequestTemplate.xml") Set objXMLNode = objXMLDoc.selectSingleNode("hrs:reservationWS/hrs:payload/   hrs:availabilityInteraction/availabilityRequest") 'Set the values in the template objXMLNode.selectSingleNode("checkInDate").nodeTypedValue = strCheckIn objXMLNode.selectSingleNode("checkOutDate").nodeTypedValue = strCheckOut objXMLNode.selectSingleNode("locale/cityState/city").nodeTypedValue =   eventCity objXMLNode.selectSingleNode("locale/cityState/state").nodeTypedValue =   eventState objXMLNode.selectSingleNode("approxPrice").nodeTypedValue =   CInt(myRequest.Form("pricerange")) objXMLNode.selectSingleNode("couponCode").nodeTypedValue = couponCode 'Check to see if there is an existing session If myRequest.Form("session") <> "" Then   objXMLDoc.selectSingleNode("hrs:reservationWS/    hrs:serviceVariables/sessionID").nodeTypedValue =    CInt(myRequest.Form("session")) 'Check to see if smoking should be created If myRequest.Form("smoking") <> "" Then       Set objXMLChild = objXMLDoc.createElement("smoking")       objXMLChild.nodeTypedValue = CBool(myRequest.Form("smoking"))       objXMLNode.selectSingleNode("preferences").appendChild objXMLChild End If 'Check to see if bedsize should be created If myRequest.Form("bed") <> "" Then      Set objXMLChild = objXMLDoc.createElement("bedSize")      objXMLChild.nodeTypedValue = CStr(myRequest.Form("bed"))      objXMLNode.selectSingleNode("preferences").appendChild objXMLChild End If 'create amenity element For Each amenity In myArray      Set objXMLChild = objXMLDoc.createElement("amenity")      objXMLChild.nodeTypedValue = CStr(amenity)      objXMLNode.selectSingleNode("preferences").appendChild objXMLChild Next 'If no preferences have been set, then remove the preferences element If objXMLNode.selectSingleNode("preferences").hasChildNodes = False Then   objXMLNode.removeChild (objXMLNode.selectSingleNode("preferences")) 'Call Availability Service objXMLConnect.setOption SXH_OPTION_SELECT_CLIENT_SSL_CERT, "AAACert" objXMLConnect.open "POST", strServiceURL, False objXMLConnect.send (objXMLDoc.xml) objXMLAvailResponse.loadXML (objXMLConnect.responseText) 'variables necessary for AAA conference registration process Set objXMLChild = objXMLDoc.createElement("conference") objXMLChild.nodeTypedValue = CInt(myRequest.Form("conference")) objXMLAvailResponse.selectSingleNode("hrs:reservationWS/   hrs:serviceVariables").appendChild objXMLChild Set objXMLChild = objXMLDoc.createElement("checkIn") objXMLChild.nodeTypedValue = CDate(dtCheckIn) objXMLAvailResponse.selectSingleNode("hrs:reservationWS/   hrs:serviceVariables").appendChild objXMLChild Set objXMLChild = objXMLDoc.createElement("checkOut") objXMLChild.nodeTypedValue = CDate(dtCheckOut) objXMLAvailResponse.selectSingleNode("hrs:reservationWS/   hrs:serviceVariables").appendChild objXMLChild 'Add hotel details if requested If getDetails Then      'now that we know we need it, let's create it      Set objXMLDetResponse = New DOMDocument40      'load the detail request template      booltest = objXMLDoc.Load("http://www.architectingwebservices.com/aaa/        templates/detailRequestTemplate.xml")      'set the sessionID for the detail request      objXMLDoc.selectSingleNode("hrs:reservationWS/hrs:serviceVariables/        sessionID").nodeTypedValue = objXMLAvailResponse.selectSingleNode        ("hrs:reservationWS/hrs:serviceVariables/sessionID").nodeTypedValue      'Get hotel ID's from availability response      Set objXMLNodeList =        objXMLAvailResponse.getElementsByTagName("hotelID")      Set objXMLNode =        objXMLAvailResponse.selectSingleNode("hrs:reservationWS/hrs:payload/        hrs:availabilityInteraction/availabilityResults/hotel")      For x = 0 To objXMLNodeList.length - 1           If x > 0 Then Set objXMLNode = objXMLNode.nextSibling           objXMLDoc.selectSingleNode("hrs:reservationWS/hrs:payload/             hrs:detailInteraction/detailRequest/hotelID").nodeTypedValue =             objXMLNodeList.Item(x).nodeTypedValue           'Call Detail Service           objXMLConnect.setOption SXH_OPTION_SELECT_CLIENT_SSL_CERT,             "AAACert"           objXMLConnect.open "POST", strServiceURL, False           objXMLConnect.send (objXMLDoc.xml)           objXMLDetResponse.loadXML (objXMLConnect.responseText)           Set objXMLChild = objXMLDoc.createElement("description")           objXMLChild.Text = objXMLDetResponse.selectSingleNode             ("hrs:reservationWS/hrs:payload/hrs:detailInteraction/             hotelContent/hotelDescription").Text           objXMLNode.appendChild objXMLChild      Next End If checkAvailability =   objXMLAvailResponse.transformNode(objXSLDoc.documentElement) Set objXMLDoc = Nothing Set objXSLDoc = Nothing Set objXMLAvailResponse = Nothing      Set objXMLConnect = Nothing      Set objXMLNode = Nothing      Set objXMLChild = Nothing End Function 
end example

makeReservation

The makeReservation method is much simpler than our checkAvailability method simply because it is a single request and is providing only a single piece of information back to us. This will follow the more traditional task list of build the request, establish the connection, and parse the response.

Instead of using a request object as our input parameter, we will be utilizing a session object. Accessing the information within it will be virtually the same, and there are no other parameters outside of the session object to be concerned with.

Building the Request

In building this request, we will load the reservation request template and assign a node to the root of the reservationRequest node as our reference. It is most effective here because nearly all of the nodes we will work with are contained within this node.

 objXMLDoc.async = False booltest = objXMLDoc.Load("http://www.architectingwebservices.com/aaa/   templates/reservationRequestTemplate.xml") Set objXMLNode = objXMLDoc.selectSingleNode("hrs:reservationWS/hrs:payload/   hrs:reservationInteraction/hrs:reservationRequest") 

Setting the node values will be terribly unexciting compared with the checkAvailability method.

 objXMLNode.selectSingleNode("hrs:hotelData/hotelID").nodeTypedValue =   CInt(mySession("hotelID")) 

The only mildly interesting change in this straightforward approach is when a second address line is submitted through the address form. In this case, we need to create the second address node and add it to the homeAddress node because we chose to leave it out of the template for efficiency's sake.

 If mySession("address2") <> "" Then      Set objXMLChild = objXMLDoc.createElement("address")      objXMLChild.nodeTypedValue = mySession("address2")      objXMLNode.selectSingleNode("hrs:personalData/homeAddress").appendChild        objXMLChild End If 

Establishing the Connection

The connection for the makeReservation method is nearly identical to the checkAvailability method. The only difference is that we are reusing the objXMLDoc object that contained our request to capture our response. In the checkAvailability method, we continued to use that object for other tasks, but it is available for us to reuse in this method:

 objXMLConnect.setOption SXH_OPTION_SELECT_CLIENT_SSL_CERT, "AAACert" objXMLConnect.open "POST", strServiceURL, False objXMLConnect.send (objXMLDoc.xml) objXMLDoc.loadXML (objXMLConnect.responseText) 

Parsing the Response

Parsing the response for this method is very short and direct. We are interested in only one piece of data in the response, the confirmation number:

 makeReservation = objXMLDoc.selectSingleNode("hrs:reservationWS/   hrs:payload/hrs:reservationInteraction/hrs:reservationResponse/   confirmationID").nodeTypedValue 

Pulling It All Together

This completes the implementation of the makeReservation method in our COM object. Because this was the last function within the component to be developed, this also makes the object completed. Let's go ahead and put all the code for the makeReservation function together to see it in its entirety. (See Listing 8-12).

Listing 8-12: The makeReservation Method

start example
 Public Function makeReservation(mySession As Session) As String Dim objXMLDoc As MSXML2.DOMDocument40 Dim objXMLNode As MSXML2.IXMLDOMNode Dim objXMLChild As MSXML2.IXMLDOMNode Dim objXMLConnect As MSXML2.ServerXMLHTTP40 Dim booltest As Boolean Set objXMLDoc = New DOMDocument40 Set objXMLConnect = New MSXML2.ServerXMLHTTP40 'Loading Template objXMLDoc.async = False booltest = objXMLDoc.Load("http://www.architectingwebservices.com/aaa/   templates/reservationRequestTemplate.xml") Set objXMLNode = objXMLDoc.selectSingleNode("hrs:reservationWS/hrs:payload/   hrs:reservationInteraction/hrs:reservationRequest") 'set the sessionID for the reservation request objXMLDoc.selectSingleNode("hrs:reservationWS/hrs:serviceVariables/   sessionID").nodeTypedValue = CInt(mySession("mSessionID")) objXMLNode.selectSingleNode("hrs:hotelData/hotelID").nodeTypedValue =   CInt(mySession("hotelID")) objXMLNode.selectSingleNode("hrs:hotelData/roomType").nodeTypedValue =   mySession("roomType") objXMLNode.selectSingleNode("hrs:personalData/homeAddress/   address").nodeTypedValue = mySession("address1") If mySession("address2") <> "" Then      Set objXMLChild = objXMLDoc.createElement("address")      objXMLChild.nodeTypedValue = mySession("address2")      objXMLNode.selectSingleNode("hrs:personalData/homeAddress").appendChild        objXMLChild End If objXMLNode.selectSingleNode("hrs:personalData/homeAddress/   city").nodeTypedValue = mySession("city") objXMLNode.selectSingleNode("hrs:personalData/homeAddress/   state").nodeTypedValue = mySession("state") objXMLNode.selectSingleNode("hrs:personalData/homeAddress/   zipCode").nodeTypedValue = mySession("zipCode") objXMLNode.selectSingleNode("hrs:personalData/firstName").nodeTypedValue =   mySession("firstName") objXMLNode.selectSingleNode("hrs:personalData/lastName").nodeTypedValue =   mySession("lastName") objXMLNode.selectSingleNode("hrs:personalData/homePhone").nodeTypedValue =   mySession("phone") objXMLNode.selectSingleNode("hrs:personalData/hrs:ccData/   number").nodeTypedValue = mySession("ccNumber") objXMLNode.selectSingleNode("hrs:personalData/hrs:ccData/   issuer").nodeTypedValue = mySession("ccType") objXMLNode.selectSingleNode("hrs:personalData/hrs:ccData/   expirationDate").nodeTypedValue = mySession("ccExpYear") & "-" &   mySession("ccExpMonth") & "-01" objXMLNode.selectSingleNode("hrs:personalData/hrs:ccData/   nameOfOwner").nodeTypedValue = mySession("ccOwner") 'Call reservation Service objXMLConnect.setOption SXH_OPTION_SELECT_CLIENT_SSL_CERT, "AAACert" objXMLConnect.open "POST", strServiceURL, False objXMLConnect.send (objXMLDoc.xml) objXMLDoc.loadXML (objXMLConnect.responseText) makeReservation = objXMLDoc.selectSingleNode("hrs:reservationWS/hrs:payload/   hrs:reservationInteraction/hrs:reservationResponse/   /confirmationID").nodeTypedValue Set objXMLDoc = Nothing Set objXMLConnect = Nothing Set objXMLNode = Nothing Set objXMLChild = Nothing End Function 
end example

The only thing left to look at in our implementation now is our XSL stylesheet for transforming the checkAvailability response before handing it back to its caller.

XSL Stylesheet

As we saw in Chapter 4 ("Using XML"), the XSLT language is a very simple, tag-based language. It can be very effective in manipulating XML data, but it can also be equally frustrating at times. Inevitably, you will want to do something that is seemingly impossible that could be done quickly with one or two lines of VBScript or Java.

Fortunately, what we want to accomplish is not too sophisticated: our objective is to generate the view that we saw back in Figure 8-22. We want to have our matching hotels aligned in rows, and we need all the necessary information to be contained in a form element that can be passed when the select button is clicked.

When developing stylesheets, you will benefit from limiting the amount of data you pass through the transformation process. We could have easily incorporated the entire hotel selection page within this stylesheet. However, there would be no need for it. Although it might be more convenient in some ways, you can get a stylesheet to work with a base page just as well as if it were all one page. Also, be aware that any data you send through a transformation must be XML compliant, which places additional demands on any data that is not crucial to the transformation process.

Tip 

Because most HTML editors do not yet support XHTML, it can be challenging to build XML-compliant HTML pages. The best approach is to build the page in your HTML editor of choice and then "clean it up" in one of the many XML editors available, such as XML Spy. This will ensure that you are building your creative content appropriately for your Web services.

One of the tasks we are charged with in our XSL transformation is to maintain some process variables through hidden fields. If you take the approach of building these tags directly, as you would with VBScript, you will quickly get frustrated. Instead, you need to recognize the power of the attribute tag. You can use it to build any tag dynamically, like this one:

 <input type="hidden" name="session">      <xsl:attribute name="value">           <xsl:value-of select="//sessionID"/>      </xsl:attribute> </input> 

Another feature of XSLT that we are using is its formatting capabilities. Through them, we actually have greater control than we do with VBScript. With a roomRate value of 190, the code

 <xsl:value-of select="format-number(roomRate,'$#.00')"/> 

produces an output of $190.00. This level of control allows an incredible number of possibilities in working with data in XSLT.

Our stylesheet (Listing 8-13) will generate a table with one row of headers and build a row for each hotel element that is returned. This output can then be easily placed anywhere on a page, but, as we saw in our hotelAvailability.asp page, we already have a designated place for it.

Listing 8-13: The checkAvailability Response Stylesheet

start example
 <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   xmlns:fo="http://www.w3.org/1999/XSL/Format"> <xsl:template match = "/"><!—apply to entire result set —>      <table width="100%" cellspacing="10">           <tr><!—list column headers —>                <td align="center">                     <font face="Verdana" size="1"><b>Distance to                     <br/>Conference</b>                     </font>                </td>                <td align="center">                     <font face="Verdana" size="1"><b>Price*</b></font>                </td>                <td align="center">                     <font face="Verdana" size="1"><b>Room Type</b></font>                </td>                <td align="center">                     <font face="Verdana" size="1"><b>Hotel Name</b></font>                </td>                <td align="center">                     <font face="Verdana" size="1"><b>Description</b></font>                </td>                <td align="center"></td>           </tr>           <tr><td colspan="6"><hr width="75%" size="2"/></td></tr>           <xsl:for-each select="//hotel">                <!—list each matching room's specifics —>                <form action="hotelConfirmation.asp" method="post">                     <input type="hidden" name="roomType">                          <xsl:attribute name="value">                               <xsl:value-of select="roomType"/>                          </xsl:attribute>                     </input>                     <input type="hidden" name="hotelID">                          <xsl:attribute name="value">                               <xsl:value-of select="hotelID"/>                          </xsl:attribute>                     </input>                     <input type="hidden" name="session">                          <xsl:attribute name="value">                               <xsl:value-of select="//sessionID"/>                          </xsl:attribute>                     </input>                     <input type="hidden" name="conference">                          <xsl:attribute name="value">                               <xsl:value-of select="//conference"/>                          </xsl:attribute>                     </input>                     <input type="hidden" name="checkIn">                          <xsl:attribute name="value">                               <xsl:value-of select="//checkIn"/>                          </xsl:attribute>                     </input>                     <input type="hidden" name="checkOut">                          <xsl:attribute name="value">                               <xsl:value-of select="//checkOut"/>                          </xsl:attribute>                     </input>                     <input type="hidden" name="price">                          <xsl:attribute name="value">                               <xsl:value-of select="roomRate"/>                          </xsl:attribute>                     </input>                     <input type="hidden" name="hotelName">                          <xsl:attribute name="value">                               <xsl:value-of select="hotelName"/>                          </xsl:attribute>                     </input>                     <tr>                          <td align="center">                               <font face="Verdana" size="1">                                   <xsl:value-of select="proximity"/> miles                               </font>                          </td>                          <td align="center">                               <font face="Verdana" size="1">                                   <xsl:value-of select="format-number(roomRate,                                      '$#.00')"/>                               </font>                          </td>                          <td align="center">                               <font face="Verdana" size="1">                                    <xsl:value-of select="roomType"/>                               </font>                          </td>                          <td align="center">                               <font face="Verdana" size="1">                                    <xsl:value-of select="hotelName"/>                               </font>                          </td>                          <td align="left">                               <font face="Verdana" size="1">                                    <xsl:value-of select="description"/>                               </font>                          </td>                          <td align="right">                               <input type="submit" value="Select"/>                          </td>                     </tr>                </form>                <tr><td colspan="6"><hr width="75%" size="2"/></td></tr>           </xsl:for-each>           <!—completed presentation of each matching room —>           <tr>                <td colspan="6" align="center">                     <font face="Verdana" size="1"><i>* This price reflects the                       discount given to all conference attendees</i>                     </font>                </td>           </tr>      </table> </xsl:template> </xsl:stylesheet> 
end example

Testing the Consumer

This actually completes the integration of the hotel reservation Web service into the AAA Conferences Web site. As is typical with more complex applications, they have many more areas where things can go wrong with the application when compared to the previous consumers. As a result, multiple scenarios should be tested, beyond just connectivity. The best way to address these scenarios is to ask the following questions and test them out:

  • What happens if connectivity is lost between the user and the AAA Conferences site during a Web service request?

  • What happens if connectivity between the application and the Web service is lost?

  • Is it possible for a user to spoof another user's session?

  • Is it possible for a user to erroneously get another user's data through the application?

  • Is it possible for a user to be abandoned within the application due to a failure somewhere?

These are all valid questions that should be answered when testing your implementation of a Web services consumer. When these questions have been answered, it is appropriate to stress test the entire process, as we discussed in Chapter 6. This is where the Web Application Stress Tool can be very helpful because it can simulate user sessions, which you will need for an application that implements Web services workflows. Once you are satisfied with those test results, you can be confident that your application can be safely deployed.




Architecting Web Services
Architecting Web Services
ISBN: 1893115585
EAN: 2147483647
Year: 2001
Pages: 77

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net