Java tutorial
package com.hotelbeds.distribution.hotel_api_sdk; /* * #%L * hotel-api-sdk * %% * Copyright (C) 2015 HOTELBEDS, S.L.U. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 2.1 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Lesser Public License for more details. * * You should have received a copy of the GNU General Lesser Public * License along with this program. If not, see * <http://www.gnu.org/licenses/lgpl-2.1.html>. * #L% */ import java.io.IOException; import java.io.InputStream; import java.net.SocketTimeoutException; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; import java.time.LocalDate; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSession; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import com.hotelbeds.distribution.hotel_api_sdk.helpers.Availability; import com.hotelbeds.distribution.hotel_api_sdk.helpers.Booking; import com.hotelbeds.distribution.hotel_api_sdk.helpers.BookingCheck; import com.hotelbeds.distribution.hotel_api_sdk.helpers.BookingList; import com.hotelbeds.distribution.hotel_api_sdk.helpers.LoggingRequestInterceptor; import com.hotelbeds.distribution.hotel_api_sdk.helpers.Voucher; import com.hotelbeds.distribution.hotel_api_sdk.types.AllowedMethod; import com.hotelbeds.distribution.hotel_api_sdk.types.CancellationFlags; import com.hotelbeds.distribution.hotel_api_sdk.types.ContentType; import com.hotelbeds.distribution.hotel_api_sdk.types.HotelApiPaths; import com.hotelbeds.distribution.hotel_api_sdk.types.HotelApiSDKException; import com.hotelbeds.distribution.hotel_api_sdk.types.HotelApiService; import com.hotelbeds.distribution.hotel_api_sdk.types.HotelApiVersion; import com.hotelbeds.distribution.hotel_api_sdk.types.HotelContentPaths; import com.hotelbeds.distribution.hotel_api_sdk.types.HotelbedsError; import com.hotelbeds.hotelapimodel.auto.common.SimpleTypes.BookingListFilterStatus; import com.hotelbeds.hotelapimodel.auto.common.SimpleTypes.BookingListFilterType; import com.hotelbeds.hotelapimodel.auto.messages.AbstractGenericRequest; import com.hotelbeds.hotelapimodel.auto.messages.AvailabilityRQ; import com.hotelbeds.hotelapimodel.auto.messages.AvailabilityRS; import com.hotelbeds.hotelapimodel.auto.messages.BookingCancellationRS; import com.hotelbeds.hotelapimodel.auto.messages.BookingDetailRS; import com.hotelbeds.hotelapimodel.auto.messages.BookingListRS; import com.hotelbeds.hotelapimodel.auto.messages.BookingRQ; import com.hotelbeds.hotelapimodel.auto.messages.BookingRS; import com.hotelbeds.hotelapimodel.auto.messages.BookingVoucherRQ; import com.hotelbeds.hotelapimodel.auto.messages.BookingVoucherRS; import com.hotelbeds.hotelapimodel.auto.messages.CheckRateRQ; import com.hotelbeds.hotelapimodel.auto.messages.CheckRateRS; import com.hotelbeds.hotelapimodel.auto.messages.GenericResponse; import com.hotelbeds.hotelapimodel.auto.messages.StatusRS; import com.hotelbeds.hotelapimodel.auto.util.AssignUtils; import com.hotelbeds.hotelapimodel.auto.util.ObjectJoiner; import com.hotelbeds.hotelcontentapi.auto.convert.json.DateSerializer; import com.hotelbeds.hotelcontentapi.auto.messages.AbstractGenericContentRequest; import com.hotelbeds.hotelcontentapi.auto.messages.AbstractGenericContentResponse; import com.hotelbeds.hotelcontentapi.auto.messages.Accommodation; import com.hotelbeds.hotelcontentapi.auto.messages.Board; import com.hotelbeds.hotelcontentapi.auto.messages.Category; import com.hotelbeds.hotelcontentapi.auto.messages.Chain; import com.hotelbeds.hotelcontentapi.auto.messages.Country; import com.hotelbeds.hotelcontentapi.auto.messages.Currency; import com.hotelbeds.hotelcontentapi.auto.messages.Destination; import com.hotelbeds.hotelcontentapi.auto.messages.Facility; import com.hotelbeds.hotelcontentapi.auto.messages.FacilityGroup; import com.hotelbeds.hotelcontentapi.auto.messages.FacilityType; import com.hotelbeds.hotelcontentapi.auto.messages.GroupCategory; import com.hotelbeds.hotelcontentapi.auto.messages.Hotel; import com.hotelbeds.hotelcontentapi.auto.messages.HotelDetailsRQ; import com.hotelbeds.hotelcontentapi.auto.messages.HotelDetailsRS; import com.hotelbeds.hotelcontentapi.auto.messages.ImageType; import com.hotelbeds.hotelcontentapi.auto.messages.Issue; import com.hotelbeds.hotelcontentapi.auto.messages.Language; import com.hotelbeds.hotelcontentapi.auto.messages.Promotion; import com.hotelbeds.hotelcontentapi.auto.messages.RateCommentDetailsRQ; import com.hotelbeds.hotelcontentapi.auto.messages.RateCommentDetailsRS; import com.hotelbeds.hotelcontentapi.auto.messages.RateComments; import com.hotelbeds.hotelcontentapi.auto.messages.Room; import com.hotelbeds.hotelcontentapi.auto.messages.Segment; import com.hotelbeds.hotelcontentapi.auto.messages.Terminal; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Data; import lombok.extern.slf4j.Slf4j; import okhttp3.Headers; import okhttp3.HttpUrl; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; import okio.Buffer; import okio.BufferedSource; /** * Copyright (c) Hotelbeds Technology S.L.U. All rights reserved. */ @Slf4j @Data public class HotelApiClient implements AutoCloseable { private static final String EXTRA_PARAM_PREFIX = "extra_"; public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); public static final String APPLICATION_JSON_HEADER = "application/json"; public static final String APIKEY_PROPERTY_NAME = "hotelapi.apikey"; public static final String SHAREDSECRET_PROPERTY_NAME = "hotelapi.sharedsecret"; public static final String VERSION_PROPERTY_NAME = "hotelapi.version"; public static final String VERSION_PAYMENT_PROPERTY_NAME = "hotelapi.payversion"; public static final String SERVICE_PROPERTY_NAME = "hotelapi.service"; public static final String HOTELAPI_PROPERTIES_FILE_NAME = "hotelapi.properties"; public static final String CONTENT_TYPE_HEADER = "Content-Type"; public static final String CONTENT_LENGTH_HEADER = "Content-Length"; public static final String CONTENT_ENCODING_HEADER = "Content-Encoding"; public static final String DEFAULT_LANGUAGE = "ENG"; private static final int REST_TEMPLATE_READ_TIME_OUT = 5000; private static final String HOTEL_API_URL_PROPERTY = "hotel-api.url"; private static final String HOTEL_CONTENT_URL_PROPERTY = "hotel-content.url"; private static final String API_KEY_HEADER_NAME = "Api-Key"; private static final String SIGNATURE_HEADER_NAME = "X-Signature"; private final String apiKey; private final String sharedSecret; private final HotelApiVersion hotelApiVersion; private final HotelApiVersion hotelApiPaymentVersion; private final HotelApiService hotelApiService; private String defaultLanguage; private boolean defaultUseSecondaryLanguage; private Properties properties = null; private OkHttpClient restTemplate = null; private boolean initialised = false; private int readTimeout = REST_TEMPLATE_READ_TIME_OUT; private int connectTimeout = REST_TEMPLATE_READ_TIME_OUT; private int connectionRequestTimeout = REST_TEMPLATE_READ_TIME_OUT; private ExecutorService executorService = null; private ObjectMapper mapper = null; private final String alternativeHotelApiPath; private final String alternativeHotelContentPath; public HotelApiClient() { this((String) null, null); } public HotelApiClient(HotelApiService service) { this(service, null, null); } public HotelApiClient(HotelApiVersion version, HotelApiService service) { this(version, HotelApiVersion.DEFAULT_PAYMENT, service, null, null); } public HotelApiClient(HotelApiVersion version, HotelApiVersion paymentVersion, HotelApiService service) { this(version, paymentVersion, service, null, null); } public HotelApiClient(String apiKey, String sharedSecret) { this(HotelApiVersion.DEFAULT, HotelApiVersion.DEFAULT_PAYMENT, HotelApiService.TEST, apiKey, sharedSecret); } public HotelApiClient(HotelApiService service, String apiKey, String sharedSecret) { this(HotelApiVersion.DEFAULT, HotelApiVersion.DEFAULT_PAYMENT, service, apiKey, sharedSecret); } public HotelApiClient(HotelApiVersion version, HotelApiService service, String apiKey, String sharedSecret) { this(version, HotelApiVersion.DEFAULT_PAYMENT, service, apiKey, sharedSecret); } public HotelApiClient(HotelApiVersion version, HotelApiVersion paymentVersion, HotelApiService service, String apiKey, String sharedSecret) { this.apiKey = getHotelApiKey(apiKey); this.sharedSecret = getHotelApiSharedSecret(sharedSecret); hotelApiVersion = getHotelApiVersion(version); hotelApiPaymentVersion = getHotelApiPaymentVersion(paymentVersion); hotelApiService = getHotelApiService(service); if (StringUtils.isBlank(this.apiKey) || hotelApiVersion == null || hotelApiService == null || StringUtils.isBlank(this.sharedSecret)) { throw new IllegalArgumentException( "HotelApiClient cannot be created without specifying an API key, Shared Secret, the Hotel API version and the service you are connecting to."); } alternativeHotelApiPath = getHotelApiUrl(); alternativeHotelContentPath = getHotelContentUrl(); properties = new Properties(); } public void init() { // @formatter:off restTemplate = new OkHttpClient.Builder().writeTimeout(connectionRequestTimeout, TimeUnit.MILLISECONDS) .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS) .readTimeout(readTimeout, TimeUnit.MILLISECONDS).addInterceptor(new LoggingRequestInterceptor()) .hostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; } }).build(); // @formatter:on initialised = true; executorService = Executors.newFixedThreadPool(8); mapper = new ObjectMapper(); mapper.findAndRegisterModules(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } public void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; if (isInitialised()) { log.warn("HotelAPIClient is already initialised, new timeout will have no effect."); } } public void setConnectTimeout(int connectTimeout) { this.connectTimeout = connectTimeout; if (isInitialised()) { log.warn("HotelAPIClient is already initialised, new timeout will have no effect."); } } public void setConnectionRequestTimeout(int connectionRequestTimeout) { this.connectionRequestTimeout = connectionRequestTimeout; if (isInitialised()) { log.warn("HotelAPIClient is already initialised, new timeout will have no effect."); } } private String getHotelApiProperty(String propertyName) { if (properties == null) { try (InputStream hotelApiPropertiesIS = ClassLoader .getSystemResourceAsStream(HOTELAPI_PROPERTIES_FILE_NAME)) { properties = new Properties(); if (hotelApiPropertiesIS != null) { properties.load(hotelApiPropertiesIS); } } catch (IOException e) { log.error("Error loading properties (){}.", HOTELAPI_PROPERTIES_FILE_NAME, e); } } return properties.getProperty(propertyName); } private String getHotelApiUrl() { String result = null; String alternativeUrl = getValueFromProperties("Alternative Hotel Api Url", HOTEL_API_URL_PROPERTY); if (alternativeUrl != null) { result = alternativeUrl; } return result; } private String getHotelContentUrl() { String result = null; String alternativeUrl = getValueFromProperties("Alternative Hotel Content Url", HOTEL_CONTENT_URL_PROPERTY); if (alternativeUrl != null) { result = alternativeUrl; } return result; } private String getHotelApiKey(String providedDefault) { String result = providedDefault; String fromProperties = getValueFromProperties("Api Key", APIKEY_PROPERTY_NAME); if (fromProperties != null) { result = fromProperties; } return result; } private String getHotelApiSharedSecret(String providedDefault) { String result = providedDefault; String fromProperties = getValueFromProperties("Shared Secret", SHAREDSECRET_PROPERTY_NAME); if (fromProperties != null) { result = fromProperties.trim(); } return result; } private HotelApiVersion getHotelApiVersion(HotelApiVersion providedDefault) { HotelApiVersion result = providedDefault; String fromProperties = getValueFromProperties("HotelAPI version", VERSION_PROPERTY_NAME); if (fromProperties != null) { try { result = HotelApiVersion.valueOf(fromProperties.trim()); } catch (Exception e) { log.error("Incorrect value provided for HotelAPI version: {}, it has to be one of {}. Using {}", new Object[] { fromProperties, HotelApiVersion.values(), providedDefault }); result = providedDefault; } } return result; } private HotelApiVersion getHotelApiPaymentVersion(HotelApiVersion providedDefault) { HotelApiVersion result = providedDefault; String fromProperties = getValueFromProperties("HotelAPI payment version", VERSION_PAYMENT_PROPERTY_NAME); if (fromProperties != null) { try { result = HotelApiVersion.valueOf(fromProperties.trim()); } catch (Exception e) { log.error( "Incorrect value provided for HotelAPI payment version: {}, it has to be one of {}. Using {}", new Object[] { fromProperties, HotelApiVersion.values(), providedDefault }); result = providedDefault; } } return result; } private HotelApiService getHotelApiService(HotelApiService providedDefault) { HotelApiService result = providedDefault; String fromProperties = getValueFromProperties("HotelAPI service", SERVICE_PROPERTY_NAME); if (fromProperties != null) { try { result = HotelApiService.valueOf(fromProperties.trim()); } catch (Exception e) { log.error("Incorrect value provided for HotelAPI service: {}, it has to be one of {}. Using {}", new Object[] { fromProperties, HotelApiService.values(), providedDefault }); result = providedDefault; } } return result; } private String getValueFromProperties(String name, String propertyName) { String apiKey = System.getProperty(propertyName); if (apiKey == null) { apiKey = getHotelApiProperty(propertyName); if (apiKey != null) { log.debug("{} loaded from properties file. {}", name, apiKey); } else { log.debug("No {} loaded from properties, value not specified.", name); } } else { apiKey = apiKey.trim(); log.debug("{} loaded from system properties. {}", name, apiKey); } return apiKey; } //TODO Fix so it does return an object of the proper type, else throw an error if failed //TODO Documentation pending public AvailabilityRS availability(Availability availability) throws HotelApiSDKException { return doAvailability(availability.toAvailabilityRQ()); } //TODO Fix so it does return an object of the proper type, else throw an error if failed //TODO Documentation pending public AvailabilityRS doAvailability(final AvailabilityRQ request) throws HotelApiSDKException { return (AvailabilityRS) callRemoteAPI(request, HotelApiPaths.AVAILABILITY); } // TODO Fix so it does return an object of the proper type, else throw an error if failed // TODO Documentation pending public BookingListRS list(LocalDate start, LocalDate end, int from, int to, BookingListFilterStatus status, BookingListFilterType filterType) throws HotelApiSDKException { return list(start, end, from, to, status, filterType, null, null, null, null, null); } // TODO Fix so it does return an object of the proper type, else throw an error if failed // TODO Documentation pending public BookingListRS list(LocalDate start, LocalDate end, int from, int to, BookingListFilterStatus status, BookingListFilterType filterType, Properties properties, List<String> countries, List<String> destinations, String clientReference, List<Integer> hotels) throws HotelApiSDKException { final Map<String, String> params = new HashMap<>(); params.put("start", start.toString()); params.put("end", end.toString()); params.put("from", Integer.toString(from)); params.put("to", Integer.toString(to)); if (status != null) { params.put("status", status.name()); } if (filterType != null) { params.put("filterType", filterType.name()); } if (countries != null && !countries.isEmpty()) { params.put("country", String.join(",", countries)); } if (destinations != null && !destinations.isEmpty()) { params.put("destination", String.join(",", destinations)); } if (hotels != null && !hotels.isEmpty()) { params.put("hotel", hotels.stream().map(hotelCode -> hotelCode.toString()).collect(Collectors.joining(","))); } if (clientReference != null) { params.put("clientReference", clientReference); } addPropertiesAsParams(properties, params); return (BookingListRS) callRemoteAPI(params, HotelApiPaths.BOOKING_LIST); } //TODO Fix so it does return an object of the proper type, else throw an error if failed //TODO Documentation pending public BookingListRS list(BookingList bookingList) throws HotelApiSDKException { return list(bookingList.getFromDate(), bookingList.getToDate(), bookingList.getFrom(), bookingList.getTo(), bookingList.getStatus(), bookingList.getFilterType(), bookingList.getProperties(), bookingList.getCountries(), bookingList.getDestinations(), bookingList.getClientReference(), bookingList.getHotels()); } //TODO Fix so it does return an object of the proper type, else throw an error if failed //TODO Documentation pending public BookingDetailRS detail(String bookingId, Properties properties) throws HotelApiSDKException { final Map<String, String> params = new HashMap<>(); params.put("bookingId", bookingId); addPropertiesAsParams(properties, params); return (BookingDetailRS) callRemoteAPI(params, HotelApiPaths.BOOKING_DETAIL); } public void addPropertiesAsParams(Properties properties, final Map<String, String> params) { if (properties != null) { for (Object name : properties.keySet()) { String propertyName = (String) name; params.put(EXTRA_PARAM_PREFIX + propertyName, properties.getProperty(propertyName)); } } } //TODO Fix so it does return an object of the proper type, else throw an error if failed //TODO Documentation pending public BookingDetailRS detail(String bookingId) throws HotelApiSDKException { return detail(bookingId, null); } //TODO Fix so it does return an object of the proper type, else throw an error if failed //TODO Documentation pending public BookingRS confirm(Booking booking) throws HotelApiSDKException { return doBookingConfirm(booking.toBookingRQ()); } //TODO Fix so it does return an object of the proper type, else throw an error if failed //TODO Documentation pending public BookingRS doBookingConfirm(BookingRQ request) throws HotelApiSDKException { if (request.getPaymentData() != null && request.getPaymentData().getPaymentCard() != null) { final Map<String, String> params = new HashMap<>(); params.put("version", hotelApiPaymentVersion.getVersion()); return (BookingRS) callRemoteAPI(request, params, HotelApiPaths.BOOKING_CONFIRM); } else { return (BookingRS) callRemoteAPI(request, HotelApiPaths.BOOKING_CONFIRM); } } //TODO Fix so it does return an object of the proper type, else throw an error if failed //TODO Documentation pending public BookingVoucherRS voucher(Voucher voucher) throws HotelApiSDKException { return doBookingVoucher(voucher.getBookingId(), voucher.toBookingVoucherRQ()); } //TODO Fix so it does return an object of the proper type, else throw an error if failed //TODO Documentation pending public BookingVoucherRS doBookingVoucher(String bookingId, BookingVoucherRQ request) throws HotelApiSDKException { final Map<String, String> params = new HashMap<>(); params.put("bookingId", bookingId); return (BookingVoucherRS) callRemoteAPI(request, params, HotelApiPaths.BOOKING_VOUCHER); } public BookingCancellationRS cancel(String bookingId) throws HotelApiSDKException { return cancel(bookingId, false); } public BookingCancellationRS cancel(String bookingId, boolean isSimulation) throws HotelApiSDKException { return cancel(bookingId, isSimulation, null); } //TODO Fix so it does return an object of the proper type, else throw an error if failed //TODO Documentation pending public BookingCancellationRS cancel(String bookingId, boolean isSimulation, Properties properties) throws HotelApiSDKException { final Map<String, String> params = new HashMap<>(); params.put("bookingId", bookingId); params.put("cancellationFlag", isSimulation ? CancellationFlags.SIMULATION.name() : CancellationFlags.CANCELLATION.name()); addPropertiesAsParams(properties, params); return (BookingCancellationRS) callRemoteAPI(params, HotelApiPaths.BOOKING_CANCEL); } //TODO Fix so it does return an object of the proper type, else throw an error if failed //TODO Documentation pending public CheckRateRS check(BookingCheck bookingCheck) throws HotelApiSDKException { return doCheckRate(bookingCheck.toCheckRateRQ()); } //TODO Fix so it does return an object of the proper type, else throw an error if failed //TODO Documentation pending public CheckRateRS doCheckRate(CheckRateRQ request) throws HotelApiSDKException { return (CheckRateRS) callRemoteAPI(request, HotelApiPaths.CHECK_AVAIL); } //TODO Fix so it does return an object of the proper type, else throw an error if failed //TODO Documentation pending public StatusRS status() throws HotelApiSDKException { return (StatusRS) callRemoteAPI(HotelApiPaths.STATUS); } ///////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////// //////////////////////// HOTEL CONTENT ///////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////// public RateCommentDetailsRS getRateCommentDetail(Integer contract, Integer incoming, Integer... rates) throws HotelApiSDKException { return getRateCommentDetail(LocalDate.now(), contract, incoming, rates); } public RateCommentDetailsRS getRateCommentDetail(LocalDate date, Integer contract, Integer incoming, Integer... rates) throws HotelApiSDKException { return getRateCommentDetail(date, ObjectJoiner.join("|", contract, incoming, ObjectJoiner.join(",", Arrays.asList(rates)))); } public RateCommentDetailsRS getRateCommentDetail(String rateCommentId) throws HotelApiSDKException { return getRateCommentDetail(LocalDate.now(), rateCommentId); } public RateCommentDetailsRS getRateCommentDetail(LocalDate date, String rateCommentId) throws HotelApiSDKException { RateCommentDetailsRQ request = new RateCommentDetailsRQ(); final Map<String, String> params = new HashMap<>(); request.setDate(date); ContentType.RATECOMMENT_DETAIL.addCommonParameters(request, params); params.put("code", rateCommentId); // Validate, date cannot be null params.put("date", DateSerializer.REST_FORMATTER.format(request.getDate())); return (RateCommentDetailsRS) callRemoteContentAPI(request, params, ContentType.RATECOMMENT_DETAIL); } public Hotel getHotel(final int code, final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { HotelDetailsRQ request = new HotelDetailsRQ(); request.setLanguage(language); request.setUseSecondaryLanguage(useSecondaryLanguage); request.setFields(new String[] { "all" }); final Map<String, String> params = new HashMap<>(); ContentType.HOTEL_DETAIL.addCommonParameters(request, params); params.put("code", Integer.toString(code)); HotelDetailsRS hotelDetailRS = (HotelDetailsRS) callRemoteContentAPI(request, params, ContentType.HOTEL_DETAIL); if (hotelDetailRS.getHotel() != null) { return hotelDetailRS.getHotel(); } else { throw new HotelApiSDKException(new HotelbedsError("Hotel not found", Integer.toString(code))); } } public List<Destination> getAllDestinations(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.DESTINATION); } public Stream<Destination> destinationsStream() throws HotelApiSDKException { checkDefaultValues(); return destinationsStream(defaultLanguage, defaultUseSecondaryLanguage); } public Stream<Destination> destinationsStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.DESTINATION); } public List<Country> getAllCountries(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.COUNTRY); } public Stream<Country> countriesStream() throws HotelApiSDKException { checkDefaultValues(); return countriesStream(defaultLanguage, defaultUseSecondaryLanguage); } public Stream<Country> countriesStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.COUNTRY); } public List<Hotel> getAllHotels(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.HOTEL); } public Stream<Hotel> hotelsStream() throws HotelApiSDKException { checkDefaultValues(); return hotelsStream(defaultLanguage, defaultUseSecondaryLanguage); } private void checkDefaultValues() { if (StringUtils.isBlank(defaultLanguage)) { throw new IllegalArgumentException("You must specify a language or set the default language"); } } public Stream<Hotel> hotelsStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.HOTEL); } //////////////// // Gemeric Types //////////////// // public BoardsRS getBoards(BoardsRQ request) throws HotelApiSDKException { // final Map<String, String> params = new HashMap<>(); // addCommonParameters(request, ContentType.BOARD, params); // return (BoardsRS) callRemoteContentAPI(request, params, ContentType.BOARD); // } public List<Board> getAllBoards(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.BOARD); } public Stream<Board> boardsStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.BOARD); } public List<Chain> getAllChains(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.CHAIN); } public Stream<Chain> chainsStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.CHAIN); } public List<Accommodation> getAllAccommodations(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.ACCOMODATION); } public Stream<Accommodation> accommodationsStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.ACCOMODATION); } public List<Category> getAllCategories(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.CATEGORY); } public Stream<Category> categoriesStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.CATEGORY); } public List<RateComments> getAllRateComments(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.RATECOMMENT); } public Stream<RateComments> rateCommentsStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.RATECOMMENT); } public List<Currency> getAllCurrencies(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.CURRENCY); } public Stream<Currency> currenciesStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.CURRENCY); } public List<Facility> getAllFacilities(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.FACILITY); } public Stream<Facility> facilitiesStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.FACILITY); } public List<FacilityGroup> getAllFacilityGroups(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.FACILITY_GROUP); } public Stream<FacilityGroup> facilityGroupsStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.FACILITY_GROUP); } public List<FacilityType> getAllFacilityTypes(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.FACILITY_TYPE); } public Stream<FacilityType> facilityTypesStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.FACILITY_TYPE); } public List<Issue> getAllIssues(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.ISSUE); } public Stream<Issue> issuesStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.ISSUE); } public List<Language> getAllLanguages(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.LANGUAGE); } public Stream<Language> languagesStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.LANGUAGE); } public List<Promotion> getAllPromotions(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.PROMOTION); } public Stream<Promotion> promotionsStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.PROMOTION); } public List<Room> getAllRooms(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.ROOM); } public Stream<Room> roomsStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.ROOM); } public List<Segment> getAllSegments(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.SEGMENT); } public Stream<Segment> segmentsStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.SEGMENT); } public List<Terminal> getAllTerminals(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.TERMINAL); } public Stream<Terminal> terminalsStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.TERMINAL); } public List<ImageType> getAllImageTypes(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.IMAGE_TYPE); } public Stream<ImageType> imageTypesStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.IMAGE_TYPE); } public List<GroupCategory> getAllGroupCategories(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getAllElements(language, useSecondaryLanguage, ContentType.GROUP_CATEGORY); } public Stream<GroupCategory> groupCategoriesStream(final String language, final boolean useSecondaryLanguage) throws HotelApiSDKException { return getStreamOf(language, useSecondaryLanguage, ContentType.GROUP_CATEGORY); } @Data private class RemoteApiCallable implements Callable<AbstractGenericContentResponse> { private final ContentType type; private final AbstractGenericContentRequest abstractGenericContentRequest; private final Map<String, String> callableParams; @Override public AbstractGenericContentResponse call() throws Exception { return callRemoteContentAPI(abstractGenericContentRequest, callableParams, type); } } private <T> List<T> getAllElements(final String language, final boolean useSecondaryLanguage, ContentType type) throws HotelApiSDKException { try { return StreamSupport .stream(new ContentElementSpliterator<T>(this, type, generateDefaultFullRequest(language, useSecondaryLanguage, type)), false) .collect(Collectors.toList()); } catch (InstantiationException | IllegalAccessException e) { throw new HotelApiSDKException( new HotelbedsError("SDK Configuration error", e.getCause().getMessage())); } } private <T> Stream<T> getStreamOf(final String language, final boolean useSecondaryLanguage, ContentType type) throws HotelApiSDKException { try { return StreamSupport.stream(new ContentElementSpliterator<T>(this, type, generateDefaultFullRequest(language, useSecondaryLanguage, type)), false); } catch (InstantiationException | IllegalAccessException e) { throw new HotelApiSDKException( new HotelbedsError("SDK Configuration error", e.getCause().getMessage())); } } private AbstractGenericContentRequest generateDefaultFullRequest(final String language, final boolean useSecondaryLanguage, ContentType type) throws InstantiationException, IllegalAccessException { final AbstractGenericContentRequest abstractGenericContentRequest; final Map<String, String> params = new HashMap<>(); abstractGenericContentRequest = type.getRequestClass().newInstance(); abstractGenericContentRequest.setLanguage(language); abstractGenericContentRequest.setUseSecondaryLanguage(useSecondaryLanguage); abstractGenericContentRequest.setFields(new String[] { "all" }); type.addCommonParameters(abstractGenericContentRequest, params); return abstractGenericContentRequest; } ///////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////// //////////////////////// INTERNALS ///////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////// private GenericResponse callRemoteAPI(HotelApiPaths path) throws HotelApiSDKException { return callRemoteAPI(null, null, path); } private GenericResponse callRemoteAPI(final Map<String, String> params, HotelApiPaths path) throws HotelApiSDKException { return callRemoteAPI(null, params, path); } private GenericResponse callRemoteAPI(final AbstractGenericRequest request, HotelApiPaths path) throws HotelApiSDKException { return callRemoteAPI(request, null, path); } private String obtainUrlFromPath(final Map<String, String> params, HotelApiPaths path) { final String url; if ((AllowedMethod.GET == path.getAllowedMethod() || AllowedMethod.DELETE == path.getAllowedMethod()) && !path.getAllowedParams().isEmpty()) { HttpUrl.Builder urlBuilder = HttpUrl .parse(path.getUrl(hotelApiService, hotelApiVersion, params, alternativeHotelApiPath)) .newBuilder(); for (String param : path.getAllowedParams()) { String value = params.get(param); if (value != null) { urlBuilder.addQueryParameter(param, value); } } url = urlBuilder.build().toString(); } else { url = path.getUrl(hotelApiService, hotelApiVersion, params, alternativeHotelApiPath); } return url; } private Request.Builder generateRequestBuilder(final AbstractGenericRequest abstractGenericRequest, final AllowedMethod allowedMethod, final String url) throws HotelApiSDKException { Request.Builder requestBuilder = new Request.Builder().headers(getHeaders(allowedMethod)).url(url); switch (allowedMethod) { case DELETE: requestBuilder.delete(transformToRequestBody(abstractGenericRequest)); break; case POST: requestBuilder.post(transformToRequestBody(abstractGenericRequest)); break; default: break; } return requestBuilder; } private GenericResponse callRemoteAPI(final AbstractGenericRequest abstractGenericRequest, final Map<String, String> params, HotelApiPaths path) throws HotelApiSDKException { if (isInitialised()) { final String url = obtainUrlFromPath(params, path); try { Request.Builder requestBuilder = generateRequestBuilder(abstractGenericRequest, path.getAllowedMethod(), url); Response response = restTemplate.newCall(requestBuilder.build()).execute(); try (ResponseBody body = response.body()) { BufferedSource source = body.source(); source.request(Long.MAX_VALUE); Buffer buffer = source.buffer(); Charset charset = AssignUtils.UTF8; if (body.contentType() != null) { try { charset = body.contentType().charset(AssignUtils.UTF8); } catch (UnsupportedCharsetException e) { log.error("Response body could not be decoded {}", e.getMessage()); } } String theContent = buffer.readString(charset); if (response.headers().get(CONTENT_TYPE_HEADER).toLowerCase() .startsWith(HotelApiClient.APPLICATION_JSON_HEADER)) { GenericResponse genericResponse = transformToGenericResponse(theContent, path.getResponseClass()); if (genericResponse.getError() != null) { throw new HotelApiSDKException(genericResponse.getError()); } return genericResponse; } else { throw new HotelApiSDKException(new HotelbedsError("Invalid response", "Wrong content type" + response.headers().get(CONTENT_TYPE_HEADER))); } } } catch (HotelApiSDKException e) { throw e; } catch (IOException e) { if (e.getCause() != null && e.getCause() instanceof SocketTimeoutException) { throw new HotelApiSDKException(new HotelbedsError("Timeout", e.getMessage()), e); } else { throw new HotelApiSDKException(new HotelbedsError("Error accessing API", e.getMessage()), e); } } catch (Exception e) { throw new HotelApiSDKException(new HotelbedsError(e.getClass().getName(), e.getMessage()), e); } } else { throw new HotelApiSDKException(new HotelbedsError("HotelAPIClient not initialised", "You have to call init() first, to be able to use this object.")); } } private GenericResponse transformToGenericResponse(String content, Class<? extends GenericResponse> responseClass) throws HotelApiSDKException { try { return mapper.readValue(content, responseClass); } catch (IOException e) { log.error("Error parsing JSON response: ", e); throw new HotelApiSDKException(new HotelbedsError("Error parsing JSON response", e.getMessage())); } } private String obtainUrlFromContentPath(final Map<String, String> params, HotelContentPaths path) { final String url; if (!path.getAllowedParams().isEmpty()) { HttpUrl.Builder urlBuilder = HttpUrl .parse(path.getUrl(hotelApiService, hotelApiVersion, params, alternativeHotelContentPath)) .newBuilder(); for (String param : path.getAllowedParams()) { String value = params.get(param); if (value != null) { urlBuilder.addQueryParameter(param, value); } } url = urlBuilder.build().toString(); } else { url = path.getUrl(hotelApiService, hotelApiVersion, params, alternativeHotelContentPath); } return url; } AbstractGenericContentResponse callRemoteContentAPI( final AbstractGenericContentRequest abstractGenericContentResponse, final Map<String, String> params, ContentType type) throws HotelApiSDKException { HotelContentPaths path = type.getPath(); if (isInitialised()) { final AllowedMethod allowedMethod = AllowedMethod.GET; final String url = obtainUrlFromContentPath(params, path); try { Request.Builder requestBuilder = new Request.Builder().headers(getHeaders(allowedMethod)).url(url); Response response = restTemplate.newCall(requestBuilder.build()).execute(); try (ResponseBody body = response.body()) { BufferedSource source = body.source(); source.request(Long.MAX_VALUE); Buffer buffer = source.buffer(); Charset charset = AssignUtils.UTF8; if (body.contentType() != null) { try { charset = body.contentType().charset(AssignUtils.UTF8); } catch (UnsupportedCharsetException e) { log.error("Response body could not be decoded {}", e.getMessage()); } } String theContent = buffer.readString(charset); if (response.headers().get(CONTENT_TYPE_HEADER).toLowerCase() .startsWith(HotelApiClient.APPLICATION_JSON_HEADER)) { AbstractGenericContentResponse genericResponse = transformToGenericContentResponse( theContent, type.getResponseClass()); if (genericResponse.getError() != null) { throw new HotelApiSDKException(genericResponse.getError()); } return genericResponse; } else { throw new HotelApiSDKException(new HotelbedsError("Invalid response", "Wrong content type" + response.headers().get(CONTENT_TYPE_HEADER))); } } } catch (HotelApiSDKException e) { throw e; } catch (IOException e) { if (e.getCause() != null && e.getCause() instanceof SocketTimeoutException) { throw new HotelApiSDKException(new HotelbedsError("Timeout", e.getCause().getMessage())); } else if (e.getCause() != null) { throw new HotelApiSDKException( new HotelbedsError("Error accessing API", e.getCause().getMessage())); } else { throw new HotelApiSDKException(new HotelbedsError("Error accessing API", e.getMessage())); } } catch (Exception e) { throw new HotelApiSDKException(new HotelbedsError(e.getClass().getName(), e.getMessage()), e); } } else { throw new HotelApiSDKException(new HotelbedsError("HotelAPIClient not initialised", "You have to call init() first, to be able to use this object.")); } } private AbstractGenericContentResponse transformToGenericContentResponse(String content, Class<? extends AbstractGenericContentResponse> responseClass) throws HotelApiSDKException { try { return mapper.readValue(content, responseClass); } catch (IOException e) { log.error("Error parsing JSON response: ", e); throw new HotelApiSDKException(new HotelbedsError("Error parsing JSON response", e.getMessage())); } } private RequestBody transformToRequestBody(AbstractGenericRequest request) throws HotelApiSDKException { try { return RequestBody.create(JSON, mapper.writeValueAsString(request)); } catch (IOException e) { log.error("Error parsing JSON response: ", e); throw new HotelApiSDKException(new HotelbedsError("Error parsing JSON response", e.getMessage())); } } private Headers getHeaders(AllowedMethod httpMethod) { Headers.Builder headersBuilder = new Headers.Builder(); headersBuilder.add(API_KEY_HEADER_NAME, apiKey); headersBuilder.add("User-Agent", "hotel-api-sdk-java, " + getClass().getPackage().getImplementationVersion()); // Hash the Api Key + Shared Secret + Current timestamp in seconds headersBuilder.add(SIGNATURE_HEADER_NAME, DigestUtils.sha256Hex(apiKey + sharedSecret + System.currentTimeMillis() / 1000)); switch (httpMethod) { case POST: // case PUT: headersBuilder.add("Content-Type", APPLICATION_JSON_HEADER); case GET: case DELETE: headersBuilder.add("Accept", APPLICATION_JSON_HEADER); break; default: break; } return headersBuilder.build(); } @Override public void close() { try { if (executorService != null) { executorService.shutdownNow(); } } catch (Exception e) { log.error("Error closing HotelAPI client resources", e); } } }