Unit 2, Lesson 4

Objective of Unit 2

After you complete this unit, you’ll be able to search for multiple types of travel-related items (hotels and cars) in addition to the transportation covered in the previous unit.

In addition, this unit focuses on creating bookings for hotels, air travel, etc. so you can complete the entire purchase cycle. We’ll finish by putting it all together with a Universal Record — a part of Travelport Universal API that pulls together all the information about trips and travelers.

The goal of Lesson 4

After deciding when, where, and how to travel (the subjects of Unit 1 of our tutorial), the next step is to find an accommodation at the destination.

Hotels are the most common target in searching for accommodation. When Lesson 4 is completed you’ll have a program that can output information like this:

EXTENDED STAY AMERICA-BLOOMINGTON [EA:92802]
           BLOOMINTON MN
           USD59.84   to USD107.99  
           5KM from Mall of America
           AAA says 2 NTM says 2 
           RESERVATION REQUIREMENT IS OTHER
       http://maps.google.com/?q=44.858501,-93.288498
BW PLUS BLOOMINGTON HOTEL      [BW:03174]
           BLOOMINGTON MN
           USD139.99  to USD249.99  
           0KM from Mall of America
           AAA says 3 NTM says 3 
           RESERVATION REQUIREMENT IS OTHER   
	   http://maps.google.com/?q=44.851898,-93.245300

You need to generate the client-side code for the Hotel Service not yet registered. Building the Air and Vehicle support is also recommended to avoid linker problems. The HotelService has a number of ports, as did the AirService we covered previously.

Once you have the client code from Hotel.wsdl (in the directory wsdl/hotel_v26_0 in the provided code), you may want to examine the HotelAvailabiltySearchReq and BaseHotelSearchRsp(Base Type of HotelAvailabiltySearchRsp) as these are the request/response pair of primary importance to the task ahead.

When we work with the hotel availability search request, there is the requirement for the BillingPointOfSaleInfo to be set to inform Travelport Universal API what application is using it. The method Helper.tutorialBPOSInfo() is provided to create this object for you. Similarly, one must always set the target branch and we do so based on the system property travelport.targetBranch (or the environment variable TPTARGETBRANCH).

Warning For any API request other than ping, the billing point of sale and the target branch parameters are needed. These are required so that Travelport Universal API can do the proper accounting of what services are being accessed and by whom.

The primary search parameters are:

  • A HotelStay object representing check-in and check-out dates.
  • A HotelSearchModifiers object which can have many parameters but the number of adults and number of room requested are of primary importance.
  • Either an option to the hotel modifiers to specify a point of interest or a city/airport code that tells the hotel search where the accommodation is desired.

For each of the above, we’ve provided a helper function in lesson 4’s code to make it easy to create these objects.

The return value, BaseHotelSearchRsp is substantially simpler than the return value for air travel searches, but in a similar form. The critical elements of the returned object are the returned collection of HotelSearchResult objects and the children of these objects, the HotelProperty and RateInfo object object. The RateInfo provides information about the pricing of the hotel and the HotelProperty object provides some details about the specific property such as the address and amenities, etc.

If you using another programming language, you may want to see what the request and response pair of messages are for a hotel search and response.

Hotel Search Request

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Header/>
   <soapenv:Body>
      <hot:HotelSearchAvailabilityReq TargetBranch="TRGT_BRCH" xmlns:hot="http://www.travelport.com/schema/hotel_v26_0">
         <com:BillingPointOfSaleInfo OriginApplication="UAPI" xmlns:com="http://www.travelport.com/schema/common_v26_0"/>
         <hot:HotelLocation Location="MSP"/>
         <hot:HotelSearchModifiers NumberOfAdults="2" NumberOfRooms="2">
            <com:PermittedProviders xmlns:com="http://www.travelport.com/schema/common_v26_0">
              <com:Provider Code="1G"/>
            </com:PermittedProviders>
            <com:Distance Units="KM" Value="25" xmlns:com="http://www.travelport.com/schema/common_v26_0"/>
            <com:ReferencePoint xmlns:com="http://www.travelport.com/schema/common_v26_0">Mall of America</com:ReferencePoint>
         </hot:HotelSearchModifiers>
         <hot:HotelStay>
            <hot:CheckinDate>2014-06-19</hot:CheckinDate>
            <hot:CheckoutDate>2014-06-28</hot:CheckoutDate>
         </hot:HotelStay>
      </hot:HotelSearchAvailabilityReq>
   </soapenv:Body>
</soapenv:Envelope> 

Subset of Search Response To Above, With Amenities List Shortened

<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP:Body>
      <hotel:HotelSearchAvailabilityRsp TransactionId="D7FCCB3B0A07761F65354077AFE8D8C4" ResponseTime="7172" xmlns:hotel="http://www.travelport.com/schema/hotel_v26_0" xmlns:common_v26_0="http://www.travelport.com/schema/common_v26_0">
         <common_v26_0:NextResultReference ProviderCode="1G">2OUR+4LUgs/Y5RH7gtSCz5dqVnro2EWMWFuGa1O3qZznXXAmkuC73JtLiE/HH5vpg/HcXdIteLwCKuu98ybn17Lm4mgxMfRJOQbGN2K2pSA=</common_v26_0:NextResultReference>
         <hotel:HotelSearchResult>
            <common_v26_0:VendorLocation ProviderCode="1G" VendorCode="CP" VendorLocationID="03121" Key="7rmNk2LVT6CV2H0ctX/bXQ=="/>
            <hotel:HotelProperty HotelChain="CP" HotelCode="03121" HotelLocation="MSP" Name="CROWNE PLAZA MSP AIRPORT MALL" VendorLocationKey="7rmNk2LVT6CV2H0ctX/bXQ==" HotelTransportation="Walk" ReserveRequirement="Guarantee" ParticipationLevel="Lowest Public Rate" Availability="Available" FeaturedProperty="true" NetTransCommissionInd="A">
               <hotel:PropertyAddress>
                  <hotel:Address>BLOOMINGTON MN</hotel:Address>
               </hotel:PropertyAddress>
               <common_v26_0:CoordinateLocation latitude="44.8583" longitude="-93.2224"/>
              <common_v26_0:Distance Units="KM" Value="2" Direction="NE"/>
               <hotel:HotelRating RatingProvider="AAA">
                  <hotel:Rating>3</hotel:Rating>
               </hotel:HotelRating>
               <hotel:HotelRating RatingProvider="NTM">
                  <hotel:Rating>3</hotel:Rating>
               </hotel:HotelRating>
               <hotel:Amenities>
                  <hotel:Amenity Code="AICO"/>
                  <hotel:Amenity Code="CHCA"/>
                  <hotel:Amenity Code="BRFT"/>
                  <hotel:Amenity Code="CARE"/>
                  <hotel:Amenity Code="COSH"/>
                  <hotel:Amenity Code="CODE"/>
                  <hotel:Amenity Code="ELEV"/>
                  <hotel:Amenity Code="FRTR"/>
                  <hotel:Amenity Code="GARO"/>
                  <hotel:Amenity Code="GISH"/>
                  <hotel:Amenity Code="HAFA"/>
                  <hotel:Amenity Code="LAVA"/>
                  <hotel:Amenity Code="MEPL"/>
                  <hotel:Amenity Code="MEFA"/>
                  <hotel:Amenity Code="MIBA"/>
                  <hotel:Amenity Code="MOIR"/>
                  <hotel:Amenity Code="NSMR"/>
                  <hotel:Amenity Code="PARK"/>
                  <hotel:Amenity Code="FPRK"/>
                  <hotel:Amenity Code="SPAL"/>
                  <hotel:Amenity Code="PHSV"/>
                  <hotel:Amenity Code="INPL"/>
                  <hotel:Amenity Code="OUPL"/>
                  <hotel:Amenity Code="ROSE"/>
                  <hotel:Amenity Code="WTKI"/>
                  <hotel:Amenity Code="SPAA"/>
                  <hotel:Amenity Code="A220"/>
                  <hotel:Amenity Code="D220"/>
                  <hotel:Amenity Code="JGTK"/>
               </hotel:Amenities>
            </hotel:HotelProperty>
            <hotel:RateInfo MinimumAmount="USD141.99" MinAmountRateChanged="false" MaximumAmount="USD224.99" MaxAmountRateChanged="false"/>
         </hotel:HotelSearchResult>

The amenities list, omitted above, is a sequence of four-letter codes that indicate features of the hotel. For example, AICO is air-conditioning.

    <hotel:Amenity Code="AICO"/>

Getting more results

In the interest of simplicity, we did not discuss in the previous lesson exactly how many search results were expected to be returned, and how to request more results if the provider of search results can deliver them.

Travelport Universal API signals in its responses if more results are available for any kind of search. At the Java level, you use the method getNextResultReference to get access to a token that you can use later to tell Travelport what data has previously been returned. You can see the token in the common_v26_0:NextResultReference tag at the top of the XML response.

Warning Historically, the GDSes provided data on green-screen, character-based terminals. These systems had the notion of a screenful of information — the number of lines of text that the user could see before the top lines scrolled off-screen. Some APIs to various GDSes have also used the notion of a screenful of information to represent a partial list of results. We kept the nomenclature of a screen to indicate one burst of information returned.

The typical construction in code for pulling multiple screens of information from a search request looks something like the following Java code. We are using a hotel search here, but it applies to other searches.

do {
     
     
     NextResultReference next = null;
     VendorLocMap NOT_USED = new VendorLocMap();

    
    // run the request, possibly from some middle point
    rsp = port.service(req);            
    // merge everyone into the map
    NOT_USED.mergeAll(rsp.getHotelSearchResult());

    
    List<HotelSearchResult> results = rsp.getHotelSearchResult();
    for (Iterator<HotelSearchResult> iterator = results.iterator(); iterator.hasNext();) {
    HotelSearchResult r = (HotelSearchResult) iterator.next();
    List<HotelProperty> hp = r.getHotelProperty();
    Iterator<HotelProperty> hl = hp.iterator();
    while(hl.hasNext()){                	               	
       HotelProperty p = hl.next();
	if ((p.getAvailability() == null) || (!p.getAvailability().equals(TypeHotelAvailability.AVAILABLE))) {
             continue;
       }
       //we don't want to have to use a credit card or cash to guarantee resv
	if (noDepositOrGuarantee) {
		if(p.getReserveRequirement() != null){		                                                                                                                                             
			if(!p.getReserveRequirement().equals(TypeReserveRequirement.OTHER)){
				continue;
	         	}
	        }
	}
	                
	// check for closest
	if(p.getDistance() != null){
	       int dist = p.getDistance().getValue().intValue();	                
	       if (dist < lowestDistance) {	  							
		       //setProviderCode(NOT_USED.get(p.getVendorLocationKey()).getProviderCode());
	      	       setClosestHotelCode(p.getHotelCode());
		       closest = r;
		       lowestDistance = dist;
		}
       }
		            
      // get the price, check for lowest...
      List<RateInfo> ri = r.getRateInfo();
      Iterator<RateInfo> rateInfo = ri.iterator();
      while(rateInfo.hasNext()){
                RateInfo info = rateInfo.next();
		double min = 0.0;
		if(info.getMinimumAmount() != null){
			min = Helper.parseNumberWithCurrency(info.getMinimumAmount());
		}
		else if(info.getApproximateMinimumStayAmount() != null){
			min = Helper.parseNumberWithCurrency(info.getApproximateMinimumStayAmount());
	      	}
		else if(info.getApproximateMinimumAmount() != null){
       			min = Helper.parseNumberWithCurrency(info.getApproximateMinimumAmount());
		}
		//some places offer a min price of ZERO which is clearly not
		//available so we use half max price just to make the output
		//halfway meaningful
		if (min==0.0) {
		   if(info.getMaximumAmount() != null){
			min = Helper.parseNumberWithCurrency(info.getMaximumAmount())/2;
		   }
		   else if(info.getApproximateAverageMinimumAmount() != null){
		      min =              Helper.parseNumberWithCurrency(info.getApproximateAverageMinimumAmount())/2;
                   }
		   else if(info.getApproximateMaximumAmount() != null){
		      min = Helper.parseNumberWithCurrency(info.getApproximateMaximumAmount())/2;
		   }
		}
		if (min < lowestPrice) {
	           //setProviderCode(NOT_USED.get(p.getVendorLocationKey()).getProviderCode());
	      	   setCheapestHotelCode(p.getHotelCode());
		   cheapest = r;
		   lowestPrice = min;
		   if(info.getRateSupplier() != null){
		      	setRateSupplier(info.getRateSupplier());
		   }
		}
	   }
	   
	   
	   

   
    // is there more?
    if(rsp.getHostToken() != null){
     	setHostTokenRef(rsp.getHostToken());
    }
            
    if (rsp.getNextResultReference().size() > 0) {
       // there is, so prepare for it
       next = rsp.getNextResultReference().get(0);
    }
    // keep track of number of times we've hit the server
    ++screens;
    if (next == null) {
       // no more data
       break;
    }
    // prepare for next round by setting the next value into this
    // request
    req.getNextResultReference().clear();
    req.getNextResultReference().add(next);
} while (screens != maxScreens); 

Noteworthy items from this snippet:

  • The value returned by getNextResultReference is not meaningful other than as a marker to a follow-up request to indicate what part of the full result set has already been seen.

  • Second, the same request object is re-used for each pass around the loop. The request parameters should be the same each time, with the full requests differing only by the next result reference.

  • Finally, the loop here keeps track of how many screens have been read and it stops when MAX_REQUESTS has been reached. This is both good policy and safe. It is good policy because the total list of results may be far larger than you might expect. It is also safe because this protects you from launching a large, or even infinite, number of requests if you have a bug in your program.

The Resulting XML

The XML used to request more information, aka next screen, looks like this for a follow-up to the response shown in the previous XML listing:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Header/>
   <soapenv:Body>
      <hot:HotelSearchAvailabilityReq TargetBranch="TRGT_BRCH" xmlns:hot="http://www.travelport.com/schema/hotel_v26_0">
         <com:BillingPointOfSaleInfo OriginApplication="UAPI" xmlns:com="http://www.travelport.com/schema/common_v26_0"/>
         <com:NextResultReference ProviderCode="1G" xmlns:com="http://www.travelport.com/schema/common_v26_0">2OUR+4LUgs/Y5RH7gtSCz5dqVnro2EWMWFuGa1O3qZznXXAmkuC73JtLiE/HH5vpg/HcXdIteLwCKuu98ybn17Lm4mgxMfRJOQbGN2K2pSA=</com:NextResultReference>
         <hot:HotelLocation Location="MSP"/>
         <hot:HotelSearchModifiers NumberOfAdults="2" NumberOfRooms="2">
            <com:PermittedProviders xmlns:com="http://www.travelport.com/schema/common_v26_0">
               <com:Provider Code="1G"/>
            </com:PermittedProviders>
            <com:Distance Units="KM" Value="25" xmlns:com="http://www.travelport.com/schema/common_v26_0"/>
            <com:ReferencePoint xmlns:com="http://www.travelport.com/schema/common_v26_0">Mall of America</com:ReferencePoint>
         </hot:HotelSearchModifiers>
         <hot:HotelStay>
            <hot:CheckinDate>2014-06-19</hot:CheckinDate>
            <hot:CheckoutDate>2014-06-28</hot:CheckoutDate>
         </hot:HotelStay>
      </hot:HotelSearchAvailabilityReq>
   </soapenv:Body>
</soapenv:Envelope>

As was explained in the previous section concerning the Java code, the request parameters should be the same as the original request, with the only difference between the initial and follow-up requests being the NextResultReference tag.

Searching by location

In this lesson, we will search for hotels that are located near a famous landmark.

To do this, one must provide the name of the landmark but NOT provide a location with a city code as done previously. For example, we are looking for accomodation for a family vacation to Paris, France. We require two hotel rooms and are planning to spend time at Disneyland Paris.

Disneyland Paris neé EuroDisney, is 32km east of the center of Paris. Thus, a hotel search that used the city code “PAR” or any of the Paris airports will unlikely produce good results. We need to search for this landmark.

The key idea for doing a search by landmark is to use a search modifier with the location’s name contained in it. You do that with Java code like this example from Lesson4:

HotelSearchModifiers mods = Lesson4.createModifiers(numAdults, numRooms);
//...
String pointOfInterest = "EuroDisney";
mods.setReferencePoint(pointOfInterest);
Lesson4.addDistanceModifier(mods, searchRadiusInKM); req.setHotelSearchModifiers(mods);

The first line creates a search modifier object, and the parameters represent the need for two rooms with two adults in the party.

Next, we add the attraction to the search modifiers and we do not add anything to the hotel location property of the request of object. We can also add a distance object, created by the helper function, that represents a distance of searchRadiusInKM which can be 5 or 25 or any long value. This is required to tell the geography searching engine of Travelport Universal API how far away from the attraction to consider.

Full validation of XML schemas

If you look at the Java code, you’ll notice this when we set the distance from our attraction point:

Distance distance = new Distance();
distance.setUnits("KM");
distance.setValue(BigInteger.valueOf(km));

A warning about XML schema validation: the only two valid units in the setUnits call above are KM and MI in uppercase letters. The XML schemas provided by Travelport for Universal API correctly list the valid values, but the transformation to Java code has chosen to allow you to supply this value as a string.

Warning It is good practice to turn on full validation when developing your application, no matter the programming language you are using. This means that your system will not attempt to transmit XML sequences that are invalid, and thus likely to be rejected anyway by Travelport’s system when it receives them. There are a few places where Travelport’s system is forgiving, but it is far better to correct your errors before hand and not depend on anything working not provided in the WSDL and XSD files defining the API.

Our supplied helper code for creating the instances of Travelport Universal API’s ports turns on the checking for you like this in PortWrapper.java

provider.getRequestContext().put("schema-validation-enabled", "true"); 

If you have an application finished and ready for deployment to a production system, you can increase performance by turning off this checking. If you have any doubts about your code or you are trying to debug a problem it is best to leave this checking enabled.

Key elements of Lesson 4

The main part of the code for this lesson, contained in Lesson4.java is a loop, as described above in the section about retrieving multiple screens of results. Prior to the loop entry, we set up a HotelSearchAvailabilityRequest with the key parameters based on destination attraction and the dates of travel.

As we go around the loop of reading bunches of results, we keep track of the hotel that has the lowest minimum price and the hotel that is closest (in kilometers) to our attraction. After reading several screens of data, the lowest priced and the closest option displays with some details from the response object, in particular the HotelSearchResult that represents these two preferable choices.

As we have done in the previous lessons concerning decoding the results of air searches, we build a map that tracks the key to “alue mapping for items in the response. In this lesson, the type of this object is the Helper.VendorLocMap. We construct this object as we read each screen of information. We were not able to find any cases where this map provided us needed information that was not present in either in the HotelSearchResult or HotelProperty objects that we were processing but this may vary based on your choice of provider.

Car Hire Exercise

It may be that renting a car in the area near Disneyland Paris is a useful option for the travelers, so we add a car search to see how much it might cost and how that affects the total price of the stay. We’ll expect the family to keep the vehicle for the whole of their trip.

For this exercise, as before, you’ll need to generate the client side code for the WSDL in wsdl/vehicle_v26_0/vehicle.wsdl if you have not done so already. You can follow the same logic we have used in previous lessons:

  • create a request object
  • get the port object representing the functionality
  • call service()
  • decode the response object

We won’t detail too much about how to add this feature to the code for Lesson 4 but only point on some potential difficulties:

  • You need to supply a date and time for pickup and delivery. These, unlike hotel search, are not using the XML objects but just raw strings in the format “2014-08-20T09:00:00”.
  • You should be able to print out the results of the search with the type of vehicleRate, name of the vendor (VendorCode field) and the price (RateForPeriod) like this:
STANDARD                       [ZL:EUR45.00] 

Additional Lesson 4 Exercises

  • As in the previous exercise where we used a different service for a vehicle search, try to convert each price to Thai Bhat. To do this, you can use the UtilCurrencyConversionPortType in the wsdl/Util_v26_0/Util.wsdl definition. The classes to use are CurrencyConversionReq and CurrencyConversionRsp who contain CurrencyConversion objects.

  • There is a large list of amenties that are provided for each hotel. Decode this list and display them to the user. Each amenity is represented by a four letter code. You should create a table to print these out in a nice way for the user. The translation of each of these amenities is farther down on that same page of documentation.


< Return to Unit 1, Lesson 3 | Proceed to Unit 2, Lesson 5 >

Table of Contents


Open Issues in Github Issues