diff --git a/examples/src/main/java/com/expediagroup/sdk/xap/examples/XapSdkDemoTestRun.java b/examples/src/main/java/com/expediagroup/sdk/xap/examples/XapSdkDemoTestRun.java
index 4813bd1..f2c985f 100644
--- a/examples/src/main/java/com/expediagroup/sdk/xap/examples/XapSdkDemoTestRun.java
+++ b/examples/src/main/java/com/expediagroup/sdk/xap/examples/XapSdkDemoTestRun.java
@@ -19,8 +19,9 @@
import com.expediagroup.sdk.xap.examples.scenarios.car.CarDetailsQuickStartScenario;
import com.expediagroup.sdk.xap.examples.scenarios.car.CarListingsQuickStartScenario;
import com.expediagroup.sdk.xap.examples.scenarios.lodging.AvailabilityCalendarsQuickStartScenario;
+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;
@@ -46,8 +47,13 @@ public static void main(String[] args) {
ListingsQuickStartScenario listingsQuickStartScenario = new ListingsQuickStartScenario();
listingsQuickStartScenario.run();
- QuotesQuickStartScenario quotesQuickStartScenario = new QuotesQuickStartScenario();
- quotesQuickStartScenario.run();
+ HotelIdsSearchEndToEndScenario hotelIdsSearchEndToEndScenario =
+ new HotelIdsSearchEndToEndScenario();
+ hotelIdsSearchEndToEndScenario.run();
+
+ VrboPropertySearchEndToEndScenario vrboPropertySearchEndToEndScenario =
+ new VrboPropertySearchEndToEndScenario();
+ vrboPropertySearchEndToEndScenario.run();
logger.info(
"=============================== End of Lodging Scenarios ==============================");
diff --git a/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/AvailabilityCalendarsQuickStartScenario.java b/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/AvailabilityCalendarsQuickStartScenario.java
index 279fe7e..b439fdb 100644
--- a/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/AvailabilityCalendarsQuickStartScenario.java
+++ b/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/AvailabilityCalendarsQuickStartScenario.java
@@ -27,7 +27,9 @@
/**
* This example demonstrates how to use Availability Calendar api with simple search.
- * Note: this is a Vrbo scenario. You need a key that is enabled for Vrbo brand to run this.
+ * In terms of how to get property ids, you can refer to {@link VrboPropertySearchEndToEndScenario}.
+ *
+ * Note: this is a Vrbo scenario. You need a key that is enabled for Vrbo brand to run this.
*/
public class AvailabilityCalendarsQuickStartScenario implements VrboScenario {
@@ -56,7 +58,7 @@ public void run() {
GetLodgingAvailabilityCalendarsOperationParams.builder()
.partnerTransactionId(PARTNER_TRANSACTION_ID)
// Set of Expedia Property IDs.
- .propertyIds(new HashSet<>(Arrays.asList("87704892", "12410858")))
+ .propertyIds(new HashSet<>(Arrays.asList("87704892", "36960201")))
.build();
XapClient xapClient = createClient();
diff --git a/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/HotelIdsSearchEndToEndScenario.java b/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/HotelIdsSearchEndToEndScenario.java
new file mode 100644
index 0000000..168adfb
--- /dev/null
+++ b/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/HotelIdsSearchEndToEndScenario.java
@@ -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.
+ *
+ *
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 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 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 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 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 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 getPropertyIdsFromListingsFile(String downloadUrl) {
+ List 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 not an 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 ========================");
+ }
+}
diff --git a/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/ListingsQuickStartScenario.java b/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/ListingsQuickStartScenario.java
index 2591d50..141eb76 100644
--- a/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/ListingsQuickStartScenario.java
+++ b/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/ListingsQuickStartScenario.java
@@ -128,13 +128,13 @@ public void run() {
// To access the properties, iterate through the list of hotel properties
hotelListingsResponse.getHotels().forEach(hotel -> {
- LOGGER.info(
- "=================================== Property Start ===================================");
// Check if the property is available
if (Hotel.Status.AVAILABLE != hotel.getStatus()) {
- LOGGER.info("Property is not available.");
+ 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());
diff --git a/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/QuotesQuickStartScenario.java b/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/QuotesQuickStartScenario.java
index 8855780..7e1f807 100644
--- a/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/QuotesQuickStartScenario.java
+++ b/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/QuotesQuickStartScenario.java
@@ -32,7 +32,8 @@
import org.slf4j.LoggerFactory;
/**
- * This example demonstrates how to use quotes api with simple search.
+ * This example demonstrates how to search for property quotes with property IDs in
+ * Lodging Quotes API.
* Note: this is a Vrbo scenario. You need a key that is enabled for Vrbo brand to run this.
*/
public class QuotesQuickStartScenario implements VrboScenario {
@@ -73,7 +74,7 @@ public void run() {
// Check-out 10 days from now
.checkOut(LocalDate.now().plusDays(10))
// Set of Expedia Property IDs.
- .propertyIds(new HashSet<>(Arrays.asList("87704892", "12410858")))
+ .propertyIds(new HashSet<>(Arrays.asList("87704892", "36960201")))
// The links to return, WEB includes WS (Web Search Result Page) and
// WD (Web Details Page)
.links(Collections.singletonList(GetLodgingQuotesOperationParams.Links.WEB))
diff --git a/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/VrboPropertySearchEndToEndScenario.java b/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/VrboPropertySearchEndToEndScenario.java
new file mode 100644
index 0000000..e4396c7
--- /dev/null
+++ b/examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/VrboPropertySearchEndToEndScenario.java
@@ -0,0 +1,491 @@
+/*
+ * 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.models.LodgingQuotesResponse;
+import com.expediagroup.sdk.xap.models.LodgingRoomType;
+import com.expediagroup.sdk.xap.models.PresignedUrlResponse;
+import com.expediagroup.sdk.xap.models.Property;
+import com.expediagroup.sdk.xap.models.Room;
+import com.expediagroup.sdk.xap.operations.GetFeedDownloadUrlOperation;
+import com.expediagroup.sdk.xap.operations.GetFeedDownloadUrlOperationParams;
+import com.expediagroup.sdk.xap.operations.GetLodgingQuotesOperation;
+import com.expediagroup.sdk.xap.operations.GetLodgingQuotesOperationParams;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This example demonstrates how to retrieve accessible Vrbo property ids and location content from
+ * SDP DownloadURL API and then get the prices of these properties using the Lodging Quotes API.
+ *
+ * This is a common scenario for Vrbo partners. In practice, you can cache the property id
+ * list along with content that does not change frequently (name, description, address, amenities,
+ * etc.) to reduce heavy API calls and get only the prices of these properties in real-time from
+ * the Lodging Quotes API.
+ *
+ *
Note: this is a Vrbo scenario. You need a key that is enabled for Vrbo brand to run this.
+ */
+public class VrboPropertySearchEndToEndScenario implements VrboScenario {
+
+ private final XapClient client = createClient();
+
+ private static final Logger LOGGER =
+ LoggerFactory.getLogger(VrboPropertySearchEndToEndScenario.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;
+
+ /**
+ * A property id to location map. This mocks a cache in this example to store the static content
+ * of the properties.
+ */
+ private static final Map PROPERTY_ID_AND_LOCATION_CACHE = new HashMap<>();
+
+ public static void main(String[] args) {
+ new VrboPropertySearchEndToEndScenario().run();
+ System.exit(0);
+ }
+
+ @Override
+ public void run() {
+ LOGGER.info(
+ "====================== Running VrboPropertySearchEndToEndScenario ======================");
+
+ List propertyIds = getPropertyIdsFromDownloadUrl();
+ cachePropertyLocationFromDownloadUrl(propertyIds);
+ LodgingQuotesResponse lodgingQuotesResponse = getPropertyPriceFromLodgingQuotes(propertyIds);
+ displayResult(lodgingQuotesResponse);
+
+ LOGGER.info(
+ "======================= End VrboPropertySearchEndToEndScenario =========================");
+ }
+
+ private List getPropertyIdsFromDownloadUrl() {
+ LOGGER.info(
+ "==================== Executing Step I: getPropertyIdsFromDownloadUrl ===================");
+
+ GetFeedDownloadUrlOperationParams getPropertyIdListParams =
+ GetFeedDownloadUrlOperationParams.builder()
+ // Use the type VACATION_RENTAL to get the list of accessible Vrbo property ids.
+ .type(GetFeedDownloadUrlOperationParams.Type.VACATION_RENTAL)
+ // Without any filters, this operation will return the information of all Vrbo
+ // properties in en_US by default.
+ .build();
+
+ Response downloadUrlListingsResponse =
+ client.execute(new GetFeedDownloadUrlOperation(getPropertyIdListParams));
+
+ if (downloadUrlListingsResponse.getData() == null
+ || downloadUrlListingsResponse.getData().getBestMatchedFile() == null) {
+ throw new IllegalStateException("No vacation rental 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 Vrbo property.
+ // For demonstration purposes, we will only read a few properties from the file without
+ // downloading the entire file.
+ String vacationRentalDownloadUrl = downloadUrlListingsResponse.getData()
+ .getBestMatchedFile()
+ .getDownloadUrl();
+ LOGGER.info("Vacation Rental Download URL: {}", vacationRentalDownloadUrl);
+
+ // Read property ids from the file.
+ List propertyIds = getPropertyIdsFromVacationRentalFile(vacationRentalDownloadUrl
+ );
+
+ if (propertyIds.isEmpty()) {
+ throw new IllegalStateException("No accessible Vrbo property ids found.");
+ }
+ LOGGER.info("Accessible Vrbo Property Ids: {}", propertyIds);
+
+ LOGGER.info(
+ "==================== Step I: getPropertyIdsFromDownloadUrl Executed ====================");
+ return propertyIds;
+ }
+
+ /**
+ * Cache the location content from SDP DownloadURL API.
+ *
+ * @param propertyIds The property ids that need the location content.
+ */
+ private void cachePropertyLocationFromDownloadUrl(List propertyIds) {
+ LOGGER.info(
+ "================ Executing Step II: CachePropertyLocationFromDownloadUrl ===============");
+ GetFeedDownloadUrlOperationParams getPropertyLocationParams =
+ GetFeedDownloadUrlOperationParams.builder()
+ // Use the type LOCATIONS to get the address of accessible properties.
+ .type(GetFeedDownloadUrlOperationParams.Type.LOCATIONS)
+ // Filter the properties by brand.
+ .brand(GetFeedDownloadUrlOperationParams.Brand.VRBO)
+ .build();
+
+ Response downloadUrlLocationsResponse =
+ client.execute(new GetFeedDownloadUrlOperation(getPropertyLocationParams));
+
+ if (downloadUrlLocationsResponse.getData() == null
+ || downloadUrlLocationsResponse.getData().getBestMatchedFile() == null) {
+ throw new IllegalStateException("No location file found");
+ }
+
+ String locationsDownloadUrl = downloadUrlLocationsResponse.getData()
+ .getBestMatchedFile()
+ .getDownloadUrl();
+ LOGGER.info("Locations Download URL: {}", locationsDownloadUrl);
+
+ // Read and cache property locations from the file.
+ cachePropertyLocationFromLocationsFile(locationsDownloadUrl, propertyIds);
+
+ LOGGER.info(
+ "================= Step II: CachePropertyLocationFromDownloadUrl Executed ===============");
+ }
+
+ /**
+ * Get prices of the properties using the Lodging Quotes API.
+ *
+ * @param propertyIds The property ids to get the prices.
+ * @return The response of the Lodging Quotes API.
+ */
+ private LodgingQuotesResponse getPropertyPriceFromLodgingQuotes(List propertyIds) {
+ LOGGER.info(
+ "================= Executing Step III: GetPropertyPriceFromLodgingQuotes ================");
+
+ // Build the occupancy
+ ArrayList rooms = new ArrayList<>();
+ // The first room, with 2 adult
+ rooms.add(Room.builder().adults(2L).childAges(null).build());
+
+ // Build the query parameters with GetLodgingQuotesOperationParams
+ GetLodgingQuotesOperationParams quotesOperationParams =
+ GetLodgingQuotesOperationParams.builder()
+ .partnerTransactionId(PARTNER_TRANSACTION_ID)
+ // Check-in 5 days from now
+ .checkIn(LocalDate.now().plusDays(5))
+ // Check-out 10 days from now
+ .checkOut(LocalDate.now().plusDays(10))
+ // Set of Expedia Property IDs.
+ .propertyIds(new HashSet<>(propertyIds))
+ // The links to return, WEB includes WS (Web Search Result Page) and
+ // WD (Web Details Page)
+ .links(Collections.singletonList(GetLodgingQuotesOperationParams.Links.WEB))
+ .rooms(rooms)
+ .build();
+
+ LodgingQuotesResponse lodgingQuotesResponse =
+ client.execute(new GetLodgingQuotesOperation(quotesOperationParams))
+ .getData();
+
+ LOGGER.info(
+ "================= Step III: GetPropertyPriceFromLodgingQuotes Executed =================");
+ return lodgingQuotesResponse;
+ }
+
+ /**
+ * 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 getPropertyIdsFromVacationRentalFile(String downloadUrl) {
+ List 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": "987654321",
+ "vrbo": "123.1234567.7654321"
+ },
+ "country": "France",
+ "propertySize": {
+ "measurement": 441,
+ "units": "SQUARE_FEET"
+ },
+ "maxOccupancy": 4,
+ "bathrooms": {
+ "numberOfBathrooms": 1
+ },
+ "bedrooms": {
+ "numberOfBedrooms": 2
+ },
+ "houseRules": {
+ "partyOrEventRules": {
+ "partiesOrEventsPermitted": false,
+ "ownerPartyFreeText": "No events allowed"
+ },
+ "smokingRules": {
+ "smokingPermitted": false,
+ "ownerSmokingFreeText": "Smoking is not permitted"
+ },
+ "petRules": {
+ "petsPermitted": true,
+ "ownerPetsFreeText": "Pets allowed"
+ },
+ "childRules": {
+ "childrenPermitted": true,
+ "ownerChildrenFreeText": "Children allowed: ages 0-17 "
+ }
+ },
+ "propertyManager": {
+ "name": "RĂ©sidences Louis",
+ "hostType": "Professional"
+ },
+ "premierHost": true,
+ "propertyLiveDate": "2022-05-31"
+ }
+ */
+ JsonNode jsonNode = objectMapper.readTree(line);
+ 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;
+ }
+
+ /**
+ * Caches the location content of the properties from the file pointed by the download URL.
+ *
+ * @param locationsDownloadUrl The download URL of the zip file containing the property locations.
+ * @param propertyIds The property ids to get the location content.
+ */
+ private void cachePropertyLocationFromLocationsFile(String locationsDownloadUrl,
+ List propertyIds) {
+ HttpURLConnection connection = null;
+ try {
+ // Open a connection to the URL
+ URL url = new URL(locationsDownloadUrl);
+ 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 locations from file: {}", entry.getName());
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(zipStream))) {
+ String line;
+ ObjectMapper objectMapper = new ObjectMapper();
+ while ((line = reader.readLine()) != null
+ && PROPERTY_ID_AND_LOCATION_CACHE.size() < propertyIds.size()) {
+ // Parse the property location from the json object
+ // An example json line from the jsonl file:
+ /*
+ {
+ "propertyId": {
+ "expedia": "1234567",
+ "hcom": "987654321",
+ "vrbo": "123.1234567.1234567"
+ },
+ "propertyType": {
+ "id": 16,
+ "name": "Apartment"
+ },
+ "propertyName": "Vrbo Property Name",
+ "address1": "",
+ "address2": "",
+ "city": "Newark",
+ "province": "Delaware",
+ "country": "United States",
+ "postalCode": "19711",
+ "geoLocation": {
+ "latitude": "10.999999",
+ "longitude": "-10.999999",
+ "obfuscated": false
+ },
+ "locationAttribute": {
+ "neighborhood": {
+ "id": "553248635976468695",
+ "name": "Westmoreland"
+ },
+ "city": {
+ "id": "8946",
+ "name": "Newark"
+ },
+ "region": {
+ "id": "6055689",
+ "name": "North Wilmington"
+ },
+ "airport": {
+ "id": "6028579",
+ "code": "ILG",
+ "name": "Wilmington, DE (ILG-New Castle)",
+ "distance": "13.17",
+ "unit": "km"
+ },
+ "distanceFromCityCenter": {
+ "distance": "1.24",
+ "unit": "km"
+ }
+ }
+ }
+ */
+ JsonNode jsonNode = objectMapper.readTree(line);
+ // Check if the property id is in the list
+ if (propertyIds.contains(jsonNode.get("propertyId").get("expedia").asText())) {
+ // Get the location content of the property
+ String location = jsonNode.get("propertyName").asText() + ", "
+ + jsonNode.get("city").asText() + ", "
+ + jsonNode.get("province").asText() + ", "
+ + jsonNode.get("country").asText();
+ // Store the location content in the cache
+ PROPERTY_ID_AND_LOCATION_CACHE.put(
+ jsonNode.get("propertyId")
+ .get("expedia")
+ .asText(),
+ location);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ } catch (IOException e) {
+ LOGGER.error("Error reading property locations from download URL", e);
+ } finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ }
+
+ /**
+ * Display the result of the operations.
+ *
+ * @param lodgingQuotesResponse The response of the Lodging Quotes API.
+ */
+ private static void displayResult(LodgingQuotesResponse lodgingQuotesResponse) {
+ LOGGER.info("======================= Executing Step IV: DisplayResult =======================");
+ if (lodgingQuotesResponse == null || lodgingQuotesResponse.getProperties() == null
+ || lodgingQuotesResponse.getProperties().isEmpty()) {
+ throw new IllegalStateException("No properties found.");
+ }
+
+ // The HotelListingsResponse contains a transaction ID for troubleshooting
+ LOGGER.info("Transaction ID: {}", lodgingQuotesResponse.getTransactionId());
+
+ // To access the properties, iterate through the list of hotel properties
+ lodgingQuotesResponse.getProperties().forEach(property -> {
+ // Check if the property is available
+ if (Property.Status.AVAILABLE != property.getStatus()) {
+ LOGGER.info("Property {} is not available.", property.getId());
+ return;
+ }
+ LOGGER.info(
+ "=================================== Property Start ===================================");
+ String propertyId = property.getId();
+
+ // Get the location content of the property from the cache
+ LOGGER.info("Property Id: {}", propertyId);
+ LOGGER.info("Cached Property Location: {}", PROPERTY_ID_AND_LOCATION_CACHE.get(propertyId));
+
+ // Get the price of the property from the room type
+ if (property.getRoomTypes() != null && !property.getRoomTypes().isEmpty()) {
+ // To get the first room type information
+ LodgingRoomType roomType = property.getRoomTypes().get(0);
+
+ 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 website Search Result Page
+ if (roomType.getLinks().getWebSearchResult() != null) {
+ LOGGER.info("WebSearchResult Link: {}",
+ roomType.getLinks().getWebSearchResult().getHref());
+ }
+ // To get the deeplink to the website Details Page
+ if (roomType.getLinks().getWebDetails() != null) {
+ LOGGER.info("WebDetails Link: {}", roomType.getLinks().getWebDetails().getHref());
+ }
+ }
+ }
+ LOGGER.info(
+ "==================================== Property End ====================================");
+ });
+ LOGGER.info("======================= Step IV: DisplayResult Executed ========================");
+ }
+}