Skip to content

Commit

Permalink
chore: examples optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
Simon Jingzhe Huang committed Nov 15, 2024
1 parent 2e6f5db commit 025fce1
Show file tree
Hide file tree
Showing 5 changed files with 587 additions and 492 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
package com.expediagroup.sdk.xap.examples;

import com.expediagroup.sdk.xap.examples.scenarios.lodging.AvailabilityCalendarsQuickStartScenario;
import com.expediagroup.sdk.xap.examples.scenarios.lodging.ListingsHotelIdsSearchScenario;
import com.expediagroup.sdk.xap.examples.scenarios.lodging.HotelIdsSearchEndToEndScenario;
import com.expediagroup.sdk.xap.examples.scenarios.lodging.ListingsQuickStartScenario;
import com.expediagroup.sdk.xap.examples.scenarios.lodging.QuotesQuickStartScenario;
import com.expediagroup.sdk.xap.examples.scenarios.lodging.VrboPropertySearchEndToEndScenario;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -45,12 +45,13 @@ public static void main(String[] args) {
ListingsQuickStartScenario listingsQuickStartScenario = new ListingsQuickStartScenario();
listingsQuickStartScenario.run();

ListingsHotelIdsSearchScenario listingsHotelIdsSearchScenario =
new ListingsHotelIdsSearchScenario();
listingsHotelIdsSearchScenario.run();
HotelIdsSearchEndToEndScenario hotelIdsSearchEndToEndScenario =
new HotelIdsSearchEndToEndScenario();
hotelIdsSearchEndToEndScenario.run();

QuotesQuickStartScenario quotesQuickStartScenario = new QuotesQuickStartScenario();
quotesQuickStartScenario.run();
VrboPropertySearchEndToEndScenario vrboPropertySearchEndToEndScenario =
new VrboPropertySearchEndToEndScenario();
vrboPropertySearchEndToEndScenario.run();

logger.info(
"=============================== End of Lodging Scenarios ==============================");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

/**
* This example demonstrates how to use Availability Calendar api with simple search.
* In terms of how to get property ids, you can refer to {@link QuotesQuickStartScenario}.
* In terms of how to get property ids, you can refer to {@link VrboPropertySearchEndToEndScenario}.
*
* <p>Note: this is a Vrbo scenario. You need a key that is enabled for Vrbo brand to run this.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,352 @@
/*
* Copyright (C) 2024 Expedia, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.expediagroup.sdk.xap.examples.scenarios.lodging;

import com.expediagroup.sdk.core.model.Response;
import com.expediagroup.sdk.xap.client.XapClient;
import com.expediagroup.sdk.xap.examples.scenarios.XapScenario;
import com.expediagroup.sdk.xap.models.Hotel;
import com.expediagroup.sdk.xap.models.HotelListingsResponse;
import com.expediagroup.sdk.xap.models.PresignedUrlResponse;
import com.expediagroup.sdk.xap.models.RoomType;
import com.expediagroup.sdk.xap.operations.GetFeedDownloadUrlOperation;
import com.expediagroup.sdk.xap.operations.GetFeedDownloadUrlOperationParams;
import com.expediagroup.sdk.xap.operations.GetLodgingListingsOperation;
import com.expediagroup.sdk.xap.operations.GetLodgingListingsOperationParams;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* This example demonstrates how to retrieve accessible property ids from SDP DownloadURL API and
* then get the content and prices of these properties using the Lodging Listings API.
*
* <p>This is a common scenario for meta site partners. In practice, you can build a cache with the
* property id list, content and prices to improve respond time of your pages.
*/
public class HotelIdsSearchEndToEndScenario implements XapScenario {

private final XapClient client = createClient();

private static final Logger LOGGER =
LoggerFactory.getLogger(HotelIdsSearchEndToEndScenario.class);

/**
* This field limits the number of line to read from the SDP DownloadURL API Listings file to
* reduce time to run the example.
* If the first 20 properties from the file are not accessible OR available when you run this
* example, it may end with "No accessible property ids found." OR NO_RESULT_FOUND. In that case,
* you can adjust the property count to get more properties.
*/
private static final int SAMPLE_ITEMS_RESTRICTION = 20;

public static void main(String[] args) {
new HotelIdsSearchEndToEndScenario().run();
System.exit(0);
}

@Override
public void run() {
LOGGER.info(
"======================== Running HotelIdsSearchEndToEndScenario =======================");

List<String> propertyIds = getPropertyIdsFromDownloadUrl();
HotelListingsResponse hotelListingsResponse = getPropertiesFromLodgingListings(propertyIds);
displayResult(hotelListingsResponse);

LOGGER.info(
"========================== End HotelIdsSearchEndToEndScenario =========================");
}

/**
* Retrieve accessible property ids from SDP DownloadURL API.
*
* @return property ids
*/
private List<String> getPropertyIdsFromDownloadUrl() {
LOGGER.info(
"==================== Executing Step I: getPropertyIdsFromDownloadUrl ===================");

GetFeedDownloadUrlOperationParams getPropertyIdListParams =
GetFeedDownloadUrlOperationParams.builder()
// Use the type LISTINGS to get the list of accessible property ids.
.type(GetFeedDownloadUrlOperationParams.Type.LISTINGS)
// Without any filters, this operation will return the information of all lodging
// properties in en_US by default.
.build();

Response<PresignedUrlResponse> downloadUrlListingsResponse =
client.execute(new GetFeedDownloadUrlOperation(getPropertyIdListParams));

if (downloadUrlListingsResponse.getData() == null
|| downloadUrlListingsResponse.getData().getBestMatchedFile() == null) {
throw new IllegalStateException("No listings file found");
}

// The download URL points to a zip file containing various jsonl files.
// Each line in the jsonl files contains a json object representing a property.
// For demonstration purposes, we will only read a few properties from the file without
// downloading the entire file.
String listingsDownloadUrl = downloadUrlListingsResponse.getData()
.getBestMatchedFile()
.getDownloadUrl();
LOGGER.info("Listings Download URL: {}", listingsDownloadUrl);

// Read property ids from the file.
List<String> propertyIds = getPropertyIdsFromListingsFile(listingsDownloadUrl);

if (propertyIds.isEmpty()) {
throw new IllegalStateException("No accessible property ids found.");
}
LOGGER.info("Accessible Property Ids: {}", propertyIds);

LOGGER.info(
"==================== Step I: getPropertyIdsFromDownloadUrl Executed ====================");
return propertyIds;
}

/**
* Get prices of the properties using the Lodging Listings API.
*
* @param propertyIds The property ids to get the prices.
* @return The response of the Lodging Listings API.
*/
private HotelListingsResponse getPropertiesFromLodgingListings(List<String> propertyIds) {
LOGGER.info(
"================ Step II: Executing getPropertiesFromLodgingListings ===============");

GetLodgingListingsOperationParams getLodgingListingsOperationParams =
GetLodgingListingsOperationParams.builder()
.partnerTransactionId(PARTNER_TRANSACTION_ID)
// Use the property ids read from the file
.ecomHotelIds(new HashSet<>(propertyIds))
// The links to return, WEB includes WS (Web Search Result Page)
// and WD (Web Details Page)
.links(Collections.singletonList(GetLodgingListingsOperationParams.Links.WEB))
// Check-in 5 days from now
.checkIn(LocalDate.now().plusDays(5))
// Check-out 10 days from now
.checkOut(LocalDate.now().plusDays(10))
// Filter the properties that are available only
.availOnly(true)
// Use the default occupancy: 2 adults in one room
.build();

HotelListingsResponse hotelListingsResponse =
client.execute(new GetLodgingListingsOperation(getLodgingListingsOperationParams))
.getData();

LOGGER.info(
"================ Step II: getPropertiesFromLodgingListings Executed ================");
return hotelListingsResponse;
}

/**
* Reads given number of property ids from the file pointed by the download URL.
*
* @param downloadUrl The download URL of the zip file containing the property information.
* @return A list of property ids read from the file.
*/
private List<String> getPropertyIdsFromListingsFile(String downloadUrl) {
List<String> propertyIds = new ArrayList<>();
HttpURLConnection connection = null;
try {
// Open a connection to the URL
URL url = new URL(downloadUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setDoInput(true);

try (ZipInputStream zipStream = new ZipInputStream(connection.getInputStream())) {
ZipEntry entry;
while ((entry = zipStream.getNextEntry()) != null) {
if (entry.getName().endsWith(".jsonl")) {
LOGGER.info("Reading property ids from file: {}", entry.getName());
try (BufferedReader reader = new BufferedReader(new InputStreamReader(zipStream))) {
String line;
ObjectMapper objectMapper = new ObjectMapper();
while ((line = reader.readLine()) != null
&& propertyIds.size() < SAMPLE_ITEMS_RESTRICTION) {
// Parse the property id from the json object
// An example json line from the jsonl file:
/*
{
"propertyId": {
"expedia": "1234567",
"hcom": "123456789",
"vrbo": "123.1234567.7654321"
},
"bookable": {
"expedia": true,
"hcom": true,
"vrbo": true
},
"propertyType": {
"id": 16,
"name": "Apartment"
},
"lastUpdated": "10-27-2024 13:41:16",
"country": "France",
"inventorySource": "vrbo",
"referencePrice": {
"value": "89.52",
"currency": "USD"
},
"vrboPropertyType": {
"instantBook": true
}
}
*/
JsonNode jsonNode = objectMapper.readTree(line);
// Check if the property is accessible from Lodging Listings API
// (Vrbo properties that are not instantBookable are not accessible for now)
if (!jsonNode.get("propertyId").get("vrbo").asText().isEmpty()
&& jsonNode.has("vrboPropertyType")
&& !jsonNode.get("vrboPropertyType").get("instantBook").asBoolean()
) {
// Skip the property if it is a not instant bookable Vrbo property
continue;
} else {
// Get the Expedia property id for the Lodging Listings API
propertyIds.add(jsonNode.get("propertyId").get("expedia").asText());
}
}
}
}
}
}

} catch (IOException e) {
LOGGER.error("Error reading property ids from download URL", e);
} finally {
if (connection != null) {
connection.disconnect();
}
}
return propertyIds;
}

/**
* Display the result of the operations.
*
* @param hotelListingsResponse The response of the Lodging Listings API.
*/
private static void displayResult(HotelListingsResponse hotelListingsResponse) {
LOGGER.info("====================== Executing Step III: DisplayResult =======================");
if (hotelListingsResponse == null || hotelListingsResponse.getHotels() == null
|| hotelListingsResponse.getHotels().isEmpty()) {
throw new IllegalStateException("No properties found.");
}

// The HotelListingsResponse contains a transaction ID for troubleshooting
LOGGER.info("Transaction ID: {}", hotelListingsResponse.getTransactionId());

// To access the properties, iterate through the list of hotel properties
hotelListingsResponse.getHotels().forEach(hotel -> {
// Check if the property is available
if (Hotel.Status.AVAILABLE != hotel.getStatus()) {
LOGGER.info("Property {} is not available.", hotel.getId());
return;
}
LOGGER.info(
"=================================== Property Start ===================================");
// To get the property name
if (StringUtils.isNotEmpty(hotel.getName())) {
LOGGER.info("Property Name: {}", hotel.getName());
}
// To get the property address
if (hotel.getLocation() != null) {
LOGGER.info("Property Address: {}", hotel.getLocation().getAddress());
}
// To get the property thumbnail URL
if (StringUtils.isNotEmpty(hotel.getThumbnailUrl())) {
LOGGER.info("Thumbnail URL: {}", hotel.getThumbnailUrl());
}
// To get the star rating of the property. The value is between 1.0 and 5.0
// in a 0.5 increment.
if (hotel.getStarRating() != null) {
LOGGER.info("Star Rating: {}", hotel.getStarRating().getValue());
}
// To get the guest rating of the property. The value is between 1.0 and 5.0
// in a 0.1 increment.
if (StringUtils.isNotEmpty(hotel.getGuestRating())) {
LOGGER.info("Guest Rating: {}", hotel.getGuestRating());
}
// To get the total number of reviews for the property
if (hotel.getGuestReviewCount() != null) {
LOGGER.info("Review Count: {}", hotel.getGuestReviewCount());
}
if (hotel.getRoomTypes() != null && !hotel.getRoomTypes().isEmpty()) {
// To get the first room type information
RoomType roomType = hotel.getRoomTypes().get(0);
if (StringUtils.isNotEmpty(roomType.getDescription())) {
LOGGER.info("Room Type: {}", roomType.getDescription());
}
if (roomType.getPrice() != null) {
// To get the total price of the room type
if (roomType.getPrice().getTotalPrice() != null) {
LOGGER.info("Price: {}, Currency: {}",
roomType.getPrice().getTotalPrice().getValue(),
roomType.getPrice().getTotalPrice().getCurrency());
}
// To get the average nightly rate of the room type
if (roomType.getPrice().getAvgNightlyRate() != null) {
LOGGER.info("Average Nightly Rate: {}, Currency: {}",
roomType.getPrice().getAvgNightlyRate().getValue(),
roomType.getPrice().getAvgNightlyRate().getCurrency());
}
}
// To get the free cancellation flag of the selected room
if (roomType.getRatePlans() != null && !roomType.getRatePlans().isEmpty()
&& roomType.getRatePlans().get(0).getCancellationPolicy() != null) {
LOGGER.info("Free Cancellation: {}",
roomType.getRatePlans().get(0).getCancellationPolicy().getFreeCancellation());
}
if (roomType.getLinks() != null) {
// To get the deeplink to the Expedia Web Search Result Page
if (roomType.getLinks().getWebSearchResult() != null) {
LOGGER.info("WebSearchResult Link: {}",
roomType.getLinks().getWebSearchResult().getHref());
}
// To get the deeplink to the Expedia Web Details Page
if (roomType.getLinks().getWebDetails() != null) {
LOGGER.info("WebDetails Link: {}", roomType.getLinks().getWebDetails().getHref());
}
}
}
LOGGER.info(
"==================================== Property End ====================================");
});
LOGGER.info("====================== Step III: DisplayResult Executed ========================");
}
}
Loading

0 comments on commit 025fce1

Please sign in to comment.