Skip to content

Commit 025fce1

Browse files
author
Simon Jingzhe Huang
committed
chore: examples optimization
1 parent 2e6f5db commit 025fce1

File tree

5 files changed

+587
-492
lines changed

5 files changed

+587
-492
lines changed

examples/src/main/java/com/expediagroup/sdk/xap/examples/XapSdkDemoTestRun.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
package com.expediagroup.sdk.xap.examples;
1818

1919
import com.expediagroup.sdk.xap.examples.scenarios.lodging.AvailabilityCalendarsQuickStartScenario;
20-
import com.expediagroup.sdk.xap.examples.scenarios.lodging.ListingsHotelIdsSearchScenario;
20+
import com.expediagroup.sdk.xap.examples.scenarios.lodging.HotelIdsSearchEndToEndScenario;
2121
import com.expediagroup.sdk.xap.examples.scenarios.lodging.ListingsQuickStartScenario;
22-
import com.expediagroup.sdk.xap.examples.scenarios.lodging.QuotesQuickStartScenario;
22+
import com.expediagroup.sdk.xap.examples.scenarios.lodging.VrboPropertySearchEndToEndScenario;
2323
import org.slf4j.Logger;
2424
import org.slf4j.LoggerFactory;
2525

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

48-
ListingsHotelIdsSearchScenario listingsHotelIdsSearchScenario =
49-
new ListingsHotelIdsSearchScenario();
50-
listingsHotelIdsSearchScenario.run();
48+
HotelIdsSearchEndToEndScenario hotelIdsSearchEndToEndScenario =
49+
new HotelIdsSearchEndToEndScenario();
50+
hotelIdsSearchEndToEndScenario.run();
5151

52-
QuotesQuickStartScenario quotesQuickStartScenario = new QuotesQuickStartScenario();
53-
quotesQuickStartScenario.run();
52+
VrboPropertySearchEndToEndScenario vrboPropertySearchEndToEndScenario =
53+
new VrboPropertySearchEndToEndScenario();
54+
vrboPropertySearchEndToEndScenario.run();
5455

5556
logger.info(
5657
"=============================== End of Lodging Scenarios ==============================");

examples/src/main/java/com/expediagroup/sdk/xap/examples/scenarios/lodging/AvailabilityCalendarsQuickStartScenario.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
/**
2929
* This example demonstrates how to use Availability Calendar api with simple search.
30-
* In terms of how to get property ids, you can refer to {@link QuotesQuickStartScenario}.
30+
* In terms of how to get property ids, you can refer to {@link VrboPropertySearchEndToEndScenario}.
3131
*
3232
* <p>Note: this is a Vrbo scenario. You need a key that is enabled for Vrbo brand to run this.
3333
*/
Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
/*
2+
* Copyright (C) 2024 Expedia, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.expediagroup.sdk.xap.examples.scenarios.lodging;
18+
19+
import com.expediagroup.sdk.core.model.Response;
20+
import com.expediagroup.sdk.xap.client.XapClient;
21+
import com.expediagroup.sdk.xap.examples.scenarios.XapScenario;
22+
import com.expediagroup.sdk.xap.models.Hotel;
23+
import com.expediagroup.sdk.xap.models.HotelListingsResponse;
24+
import com.expediagroup.sdk.xap.models.PresignedUrlResponse;
25+
import com.expediagroup.sdk.xap.models.RoomType;
26+
import com.expediagroup.sdk.xap.operations.GetFeedDownloadUrlOperation;
27+
import com.expediagroup.sdk.xap.operations.GetFeedDownloadUrlOperationParams;
28+
import com.expediagroup.sdk.xap.operations.GetLodgingListingsOperation;
29+
import com.expediagroup.sdk.xap.operations.GetLodgingListingsOperationParams;
30+
import com.fasterxml.jackson.databind.JsonNode;
31+
import com.fasterxml.jackson.databind.ObjectMapper;
32+
import java.io.BufferedReader;
33+
import java.io.IOException;
34+
import java.io.InputStreamReader;
35+
import java.net.HttpURLConnection;
36+
import java.net.URL;
37+
import java.time.LocalDate;
38+
import java.util.ArrayList;
39+
import java.util.Collections;
40+
import java.util.HashMap;
41+
import java.util.HashSet;
42+
import java.util.List;
43+
import java.util.Map;
44+
import java.util.zip.ZipEntry;
45+
import java.util.zip.ZipInputStream;
46+
import org.apache.commons.lang3.StringUtils;
47+
import org.slf4j.Logger;
48+
import org.slf4j.LoggerFactory;
49+
50+
/**
51+
* This example demonstrates how to retrieve accessible property ids from SDP DownloadURL API and
52+
* then get the content and prices of these properties using the Lodging Listings API.
53+
*
54+
* <p>This is a common scenario for meta site partners. In practice, you can build a cache with the
55+
* property id list, content and prices to improve respond time of your pages.
56+
*/
57+
public class HotelIdsSearchEndToEndScenario implements XapScenario {
58+
59+
private final XapClient client = createClient();
60+
61+
private static final Logger LOGGER =
62+
LoggerFactory.getLogger(HotelIdsSearchEndToEndScenario.class);
63+
64+
/**
65+
* This field limits the number of line to read from the SDP DownloadURL API Listings file to
66+
* reduce time to run the example.
67+
* If the first 20 properties from the file are not accessible OR available when you run this
68+
* example, it may end with "No accessible property ids found." OR NO_RESULT_FOUND. In that case,
69+
* you can adjust the property count to get more properties.
70+
*/
71+
private static final int SAMPLE_ITEMS_RESTRICTION = 20;
72+
73+
public static void main(String[] args) {
74+
new HotelIdsSearchEndToEndScenario().run();
75+
System.exit(0);
76+
}
77+
78+
@Override
79+
public void run() {
80+
LOGGER.info(
81+
"======================== Running HotelIdsSearchEndToEndScenario =======================");
82+
83+
List<String> propertyIds = getPropertyIdsFromDownloadUrl();
84+
HotelListingsResponse hotelListingsResponse = getPropertiesFromLodgingListings(propertyIds);
85+
displayResult(hotelListingsResponse);
86+
87+
LOGGER.info(
88+
"========================== End HotelIdsSearchEndToEndScenario =========================");
89+
}
90+
91+
/**
92+
* Retrieve accessible property ids from SDP DownloadURL API.
93+
*
94+
* @return property ids
95+
*/
96+
private List<String> getPropertyIdsFromDownloadUrl() {
97+
LOGGER.info(
98+
"==================== Executing Step I: getPropertyIdsFromDownloadUrl ===================");
99+
100+
GetFeedDownloadUrlOperationParams getPropertyIdListParams =
101+
GetFeedDownloadUrlOperationParams.builder()
102+
// Use the type LISTINGS to get the list of accessible property ids.
103+
.type(GetFeedDownloadUrlOperationParams.Type.LISTINGS)
104+
// Without any filters, this operation will return the information of all lodging
105+
// properties in en_US by default.
106+
.build();
107+
108+
Response<PresignedUrlResponse> downloadUrlListingsResponse =
109+
client.execute(new GetFeedDownloadUrlOperation(getPropertyIdListParams));
110+
111+
if (downloadUrlListingsResponse.getData() == null
112+
|| downloadUrlListingsResponse.getData().getBestMatchedFile() == null) {
113+
throw new IllegalStateException("No listings file found");
114+
}
115+
116+
// The download URL points to a zip file containing various jsonl files.
117+
// Each line in the jsonl files contains a json object representing a property.
118+
// For demonstration purposes, we will only read a few properties from the file without
119+
// downloading the entire file.
120+
String listingsDownloadUrl = downloadUrlListingsResponse.getData()
121+
.getBestMatchedFile()
122+
.getDownloadUrl();
123+
LOGGER.info("Listings Download URL: {}", listingsDownloadUrl);
124+
125+
// Read property ids from the file.
126+
List<String> propertyIds = getPropertyIdsFromListingsFile(listingsDownloadUrl);
127+
128+
if (propertyIds.isEmpty()) {
129+
throw new IllegalStateException("No accessible property ids found.");
130+
}
131+
LOGGER.info("Accessible Property Ids: {}", propertyIds);
132+
133+
LOGGER.info(
134+
"==================== Step I: getPropertyIdsFromDownloadUrl Executed ====================");
135+
return propertyIds;
136+
}
137+
138+
/**
139+
* Get prices of the properties using the Lodging Listings API.
140+
*
141+
* @param propertyIds The property ids to get the prices.
142+
* @return The response of the Lodging Listings API.
143+
*/
144+
private HotelListingsResponse getPropertiesFromLodgingListings(List<String> propertyIds) {
145+
LOGGER.info(
146+
"================ Step II: Executing getPropertiesFromLodgingListings ===============");
147+
148+
GetLodgingListingsOperationParams getLodgingListingsOperationParams =
149+
GetLodgingListingsOperationParams.builder()
150+
.partnerTransactionId(PARTNER_TRANSACTION_ID)
151+
// Use the property ids read from the file
152+
.ecomHotelIds(new HashSet<>(propertyIds))
153+
// The links to return, WEB includes WS (Web Search Result Page)
154+
// and WD (Web Details Page)
155+
.links(Collections.singletonList(GetLodgingListingsOperationParams.Links.WEB))
156+
// Check-in 5 days from now
157+
.checkIn(LocalDate.now().plusDays(5))
158+
// Check-out 10 days from now
159+
.checkOut(LocalDate.now().plusDays(10))
160+
// Filter the properties that are available only
161+
.availOnly(true)
162+
// Use the default occupancy: 2 adults in one room
163+
.build();
164+
165+
HotelListingsResponse hotelListingsResponse =
166+
client.execute(new GetLodgingListingsOperation(getLodgingListingsOperationParams))
167+
.getData();
168+
169+
LOGGER.info(
170+
"================ Step II: getPropertiesFromLodgingListings Executed ================");
171+
return hotelListingsResponse;
172+
}
173+
174+
/**
175+
* Reads given number of property ids from the file pointed by the download URL.
176+
*
177+
* @param downloadUrl The download URL of the zip file containing the property information.
178+
* @return A list of property ids read from the file.
179+
*/
180+
private List<String> getPropertyIdsFromListingsFile(String downloadUrl) {
181+
List<String> propertyIds = new ArrayList<>();
182+
HttpURLConnection connection = null;
183+
try {
184+
// Open a connection to the URL
185+
URL url = new URL(downloadUrl);
186+
connection = (HttpURLConnection) url.openConnection();
187+
connection.setRequestMethod("GET");
188+
connection.setDoInput(true);
189+
190+
try (ZipInputStream zipStream = new ZipInputStream(connection.getInputStream())) {
191+
ZipEntry entry;
192+
while ((entry = zipStream.getNextEntry()) != null) {
193+
if (entry.getName().endsWith(".jsonl")) {
194+
LOGGER.info("Reading property ids from file: {}", entry.getName());
195+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(zipStream))) {
196+
String line;
197+
ObjectMapper objectMapper = new ObjectMapper();
198+
while ((line = reader.readLine()) != null
199+
&& propertyIds.size() < SAMPLE_ITEMS_RESTRICTION) {
200+
// Parse the property id from the json object
201+
// An example json line from the jsonl file:
202+
/*
203+
{
204+
"propertyId": {
205+
"expedia": "1234567",
206+
"hcom": "123456789",
207+
"vrbo": "123.1234567.7654321"
208+
},
209+
"bookable": {
210+
"expedia": true,
211+
"hcom": true,
212+
"vrbo": true
213+
},
214+
"propertyType": {
215+
"id": 16,
216+
"name": "Apartment"
217+
},
218+
"lastUpdated": "10-27-2024 13:41:16",
219+
"country": "France",
220+
"inventorySource": "vrbo",
221+
"referencePrice": {
222+
"value": "89.52",
223+
"currency": "USD"
224+
},
225+
"vrboPropertyType": {
226+
"instantBook": true
227+
}
228+
}
229+
*/
230+
JsonNode jsonNode = objectMapper.readTree(line);
231+
// Check if the property is accessible from Lodging Listings API
232+
// (Vrbo properties that are not instantBookable are not accessible for now)
233+
if (!jsonNode.get("propertyId").get("vrbo").asText().isEmpty()
234+
&& jsonNode.has("vrboPropertyType")
235+
&& !jsonNode.get("vrboPropertyType").get("instantBook").asBoolean()
236+
) {
237+
// Skip the property if it is a not instant bookable Vrbo property
238+
continue;
239+
} else {
240+
// Get the Expedia property id for the Lodging Listings API
241+
propertyIds.add(jsonNode.get("propertyId").get("expedia").asText());
242+
}
243+
}
244+
}
245+
}
246+
}
247+
}
248+
249+
} catch (IOException e) {
250+
LOGGER.error("Error reading property ids from download URL", e);
251+
} finally {
252+
if (connection != null) {
253+
connection.disconnect();
254+
}
255+
}
256+
return propertyIds;
257+
}
258+
259+
/**
260+
* Display the result of the operations.
261+
*
262+
* @param hotelListingsResponse The response of the Lodging Listings API.
263+
*/
264+
private static void displayResult(HotelListingsResponse hotelListingsResponse) {
265+
LOGGER.info("====================== Executing Step III: DisplayResult =======================");
266+
if (hotelListingsResponse == null || hotelListingsResponse.getHotels() == null
267+
|| hotelListingsResponse.getHotels().isEmpty()) {
268+
throw new IllegalStateException("No properties found.");
269+
}
270+
271+
// The HotelListingsResponse contains a transaction ID for troubleshooting
272+
LOGGER.info("Transaction ID: {}", hotelListingsResponse.getTransactionId());
273+
274+
// To access the properties, iterate through the list of hotel properties
275+
hotelListingsResponse.getHotels().forEach(hotel -> {
276+
// Check if the property is available
277+
if (Hotel.Status.AVAILABLE != hotel.getStatus()) {
278+
LOGGER.info("Property {} is not available.", hotel.getId());
279+
return;
280+
}
281+
LOGGER.info(
282+
"=================================== Property Start ===================================");
283+
// To get the property name
284+
if (StringUtils.isNotEmpty(hotel.getName())) {
285+
LOGGER.info("Property Name: {}", hotel.getName());
286+
}
287+
// To get the property address
288+
if (hotel.getLocation() != null) {
289+
LOGGER.info("Property Address: {}", hotel.getLocation().getAddress());
290+
}
291+
// To get the property thumbnail URL
292+
if (StringUtils.isNotEmpty(hotel.getThumbnailUrl())) {
293+
LOGGER.info("Thumbnail URL: {}", hotel.getThumbnailUrl());
294+
}
295+
// To get the star rating of the property. The value is between 1.0 and 5.0
296+
// in a 0.5 increment.
297+
if (hotel.getStarRating() != null) {
298+
LOGGER.info("Star Rating: {}", hotel.getStarRating().getValue());
299+
}
300+
// To get the guest rating of the property. The value is between 1.0 and 5.0
301+
// in a 0.1 increment.
302+
if (StringUtils.isNotEmpty(hotel.getGuestRating())) {
303+
LOGGER.info("Guest Rating: {}", hotel.getGuestRating());
304+
}
305+
// To get the total number of reviews for the property
306+
if (hotel.getGuestReviewCount() != null) {
307+
LOGGER.info("Review Count: {}", hotel.getGuestReviewCount());
308+
}
309+
if (hotel.getRoomTypes() != null && !hotel.getRoomTypes().isEmpty()) {
310+
// To get the first room type information
311+
RoomType roomType = hotel.getRoomTypes().get(0);
312+
if (StringUtils.isNotEmpty(roomType.getDescription())) {
313+
LOGGER.info("Room Type: {}", roomType.getDescription());
314+
}
315+
if (roomType.getPrice() != null) {
316+
// To get the total price of the room type
317+
if (roomType.getPrice().getTotalPrice() != null) {
318+
LOGGER.info("Price: {}, Currency: {}",
319+
roomType.getPrice().getTotalPrice().getValue(),
320+
roomType.getPrice().getTotalPrice().getCurrency());
321+
}
322+
// To get the average nightly rate of the room type
323+
if (roomType.getPrice().getAvgNightlyRate() != null) {
324+
LOGGER.info("Average Nightly Rate: {}, Currency: {}",
325+
roomType.getPrice().getAvgNightlyRate().getValue(),
326+
roomType.getPrice().getAvgNightlyRate().getCurrency());
327+
}
328+
}
329+
// To get the free cancellation flag of the selected room
330+
if (roomType.getRatePlans() != null && !roomType.getRatePlans().isEmpty()
331+
&& roomType.getRatePlans().get(0).getCancellationPolicy() != null) {
332+
LOGGER.info("Free Cancellation: {}",
333+
roomType.getRatePlans().get(0).getCancellationPolicy().getFreeCancellation());
334+
}
335+
if (roomType.getLinks() != null) {
336+
// To get the deeplink to the Expedia Web Search Result Page
337+
if (roomType.getLinks().getWebSearchResult() != null) {
338+
LOGGER.info("WebSearchResult Link: {}",
339+
roomType.getLinks().getWebSearchResult().getHref());
340+
}
341+
// To get the deeplink to the Expedia Web Details Page
342+
if (roomType.getLinks().getWebDetails() != null) {
343+
LOGGER.info("WebDetails Link: {}", roomType.getLinks().getWebDetails().getHref());
344+
}
345+
}
346+
}
347+
LOGGER.info(
348+
"==================================== Property End ====================================");
349+
});
350+
LOGGER.info("====================== Step III: DisplayResult Executed ========================");
351+
}
352+
}

0 commit comments

Comments
 (0)