JavaRush /Java блог /Random UA /Створення системи моніторингу цін на авіаквитки: покроков...
Roman Beekeeper
35 рівень

Створення системи моніторингу цін на авіаквитки: покрокове керівництво [Частина 1]

Стаття з групи Random UA

Зміст:

Створення системи моніторингу цін на авіаквитки: покрокове керівництво [Частина 1] - 1Всім привіт, JavaRush спільнота! Сьогодні поговоримо про те, як крок за кроком написати Spring Boot додаток для моніторингу цін на авіаквитки. Стаття розрахована на людей, які мають уявлення про:
  • REST і те, як будуються REST ендпоінти;
  • реляційних баз даних;
  • роботі maven (зокрема, що таке dependency-залежність);
  • JSON об'єкт;
  • принципи логування.
Очікувана поведінка:
  1. Можна вибрати переліт на дату і відстежувати ціну на нього. Користувач ідентифікується за email-адресаою. Як тільки оформляється підписка на зміну ціни, користувач отримує повідомлення на пошту.
  2. Кожні 30 хвабон (це проміжок налаштовується через application.properties) перераховується мінімальна ціна за переліт для всіх підписок. Якщо якась стала нижчою, користувач отримає повідомлення на пошту.
  3. Усі передплати зі застарілою датою перельоту видаляються.
  4. Через REST API можна:
    • створити передплату;
    • редагувати;
    • отримати всі передплати по email;
    • видалити передплату.

План дій для досягнення мети

Почати потрібно з того, що інформацію про перельоти потрібно звідкись брати. Зазвичай сайти надають відкритий API REST, за допомогою якого можна отримати інформацію.

API (application programming interface) - це інтерфейс, за допомогою якого можна взаємодіяти з програмою. З цього можна перекинути місток на те, що таке REST API.

REST API — це інтерфейс із REST запитів, за допомогою якого можна спілкуватися з веб-програмою.

Для цього будемо використовувати Skyscanner , а точніше, API (на сайті Rakuten API ). Далі потрібно вибрати правильну framework як базовий фундамент. Найпопулярніший і найпопулярніший - це екосистема Spring і вінець їх творіння - Spring Boot. Можна зайти на їх сайт, а можна прочитати статейку на хабрі . Щоб зберігати підписки користувачів будемо використовувати вбудовану базу даних H2 . Для читання з JSON до класів і назад будемо використовувати Jackson Project ( ось і посилання на нашому ресурсі ). Для передачі повідомлень користувачам використовуватимемо spring-boot-starter-mail Для того, щоб у додатку із заданою періодичністю виконувався перерахунок мінімальної ціни, будемо використовуватиSpring Scheduler . Для створення REST API будемо використовувати spring-boot-starter-web . Щоб не писати borrowed code (гетери, сеттери, перевизначати equals and hashcode, toString() для об'єктів), будемо використовувати Project Lombok . Щоб помацати та подивитися REST API, скористаємося Swagger 2 і одразу Swagger UI (user interface) для відстеження в режимі реального часу. Ось як це виглядає зараз: Створення системи моніторингу цін на авіаквитки: покрокове керівництво [Частина 1] - 2де є 4 rest запити, які відповідають створенню, редагування, отримання та видалення передплат.

Досліджуємо Skyscanner API

Перейдемо за посиланням на rakuten api . Спочатку потрібно зареєструватися Створення системи моніторингу цін на авіаквитки: покрокове керівництво [Частина 1] - 3Все це потрібно, щоб отримати унікальний ключ до використання їх сайту та робити запити на публічні API, які викладені на ньому. Один з таких api і є потрібний нам Skyscanner Flight Search . Тепер розберемося як це працює. Знайдемо запит GET List Places. На малюнку показано, що потрібно заповнити дані і почати Test Endpoint , в результаті чого отримуємо відповідь у вигляді об'єкта JSON праворуч: Створення системи моніторингу цін на авіаквитки: покрокове керівництво [Частина 1] - 4причому запит буде створюватися так:

https://skyscanner-skyscanner-flight-search-v1.p.rapidapi.com/apiservices/autosuggest/v1.0/{country}/{currency}/{locale}/?query={query}
і всі параметри будуть підставлені до цієї формули, отримаємо:

https://skyscanner-skyscanner-flight-search-v1.p.rapidapi.com/apiservices/autosuggest/v1.0/UK/GBP/en-GB/?query=Stockholm
і до цих запитів будуть передаватися два заголовки:

.header("x-rapidapi-host", "skyscanner-skyscanner-flight-search-v1.p.rapidapi.com")
.header("x-rapidapi-key", "sing-up-for-key"),
де sign-up-for-keyвидається після реєстрації. Нам для відстеження падіння ціни буде потрібен Browse Quotes ендпоінт. Знайдіть його самі :)

Створюємо каркас програми на основі Spring Boot

Щоб швидко та зручно створити проект зі Spring Boot, можна скористатися Spring Initializr . Вибираємо наступні опції:
  1. Maven project
  2. Java
  3. 2.1.10
  4. group - який вважаєте за потрібне, наприклад com.codegym
  5. artifact — так само, наприклад flights-monitoring
  6. у пошуку dependency шукаємо такі:
    • Spring Web
    • Java Mail Sender
    • Spring Data Jpa
    • H2 Database
І далі натискаємо Generate . Все: готовий проект завантажується як архів. Якщо щось не вийде, можна скористатися посиланням, де я зберіг потрібний проект . Звичайно, краще самому це зробити і зрозуміти, як це працює. Додаток складатиметься з трьох шарів:
  • CONTROLLER – вхід до програми. Тут буде описано REST API
  • SERVICE – шар бізнес-логіки. Вся логіка роботи додаток буде описано тут.
  • REPOSITORY – шар роботи з базою даних.
Також окремим пакетом будуть лежати класи, що належать до клієнта для Skyscanner Flight Search API.

Пишемо у проекті клієнт для запитів на Skyscanner Flight Search API

Skyscanner люб'язно надали статтю на тему, як користуватися їх API (ми не будемо створювати сесію з активним запитом). Що означає "писати клієнт"? Нам потрібно створити запит на певну URL-адресау з певними параметрами та заготовити DTO (data transfer object) для даних, що передаються назад до нас. На сайті є чотири групи запитів:
  1. Live Flight Search - не будемо розглядати як непотрібний на даний момент.
  2. Places – напишемо.
  3. Browse Flight Prices – скористаємося одним запитом, де можна взяти всю інформацію.
  4. Localisation - додамо його, що знати які підтримуються дані.

Створюємо клієнт сервіс для Localisation запиту:

План простий як парена ріпа: створюємо запит, дивимося які параметри, дивимося, яка відповідь. Там два запити List markers та Currencies. Почнемо з Currencies. На малюнку видно, що це запит без додаткових полів: він потрібен, щоб отримати інформацію про підтримувані валюти: Створення системи моніторингу цін на авіаквитки: покрокове керівництво [Частина 1] - 6Відповідь у вигляді об'єкта JSON, в якому колекція одних і тих же об'єктів, наприклад:
{
"Code":"LYD"
"Symbol":"د.ل.‏"
"ThousandsSeparator":","
"DecimalSeparator":"."
"SymbolOnLeft":true
"SpaceBetweenAmountAndSymbol":false
"RoundingCoefficient":0
"DecimalDigits":3
}
Створимо CurrencyDto для цього об'єкта:
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

/**
* Data transfer object for Currency.
*/
@Data
public class CurrencyDto {

   @JsonProperty("Code")
   private String code;

   @JsonProperty("Symbol")
   private String symbol;

   @JsonProperty("ThousandsSeparator")
   private String thousandsSeparator;

   @JsonProperty("DecimalSeparator")
   private String decimalSeparator;

   @JsonProperty("SymbolOnLeft")
   private boolean symbolOnLeft;

   @JsonProperty("SpaceBetweenAmountAndSymbol")
   private boolean spaceBetweenAmountAndSymbol;

   @JsonProperty("RoundingCoefficient")
   private int roundingCoefficient;

   @JsonProperty("DecimalDigits")
   private int decimalDigits;
}
Де:
  • @Data - анотація з Lombok проекту і генерує всі гетери, сетери, перевизначає toString(), equals() та hashCode() методи. Чим покращує читабельність коду та прискорює час написання POJO об'єктів;
  • @JsonProperty("Code") - це інструкція з Jackson Project, яка говорить, яке поле буде присвоюватися цій змінній. Тобто поле в JSON, що дорівнює Code, присвоюватиметься змінній code .
Офіційна стаття від Skyscanner пропонує використовувати для REST запитів бібліотеку UniRest . Тому напишемо ще один сервіс, який реалізовуватиме запити через REST. Це буде UniRestService . Для цього додамо в maven нову залежність:
<dependency>
  <groupId>com.mashape.unirest</groupId>
  <artifactId>unirest-java</artifactId>
  <version>1.4.9</version>
</dependency>
Далі напишемо сервіс, який виконуватиме REST запити. Зрозуміло, для кожного клієнта/сервісу ми будемо створювати interface та його реалізацію:
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;

/**
* Service, which is manipulating with Rest calls.
*/
public interface UniRestService {

   /**
   * Create GET request based on provided {@param path} with needed headers.
   *
   * @param path provided path with all the needed data
   * @return {@link HttpResponse<jsonnode>} response object.
   */
   HttpResponse<jsonnode> get(String path);

}
І його реалізація:
import com.github.romankh3.flightsmonitoring.exception.FlightClientException;
import com.github.romankh3.flightsmonitoring.client.service.UniRestService;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/**
* {@inheritDoc}
*/
@Slf4j
@Service
public class UniRestServiceImpl implements UniRestService {

   public static final String HOST = "https://skyscanner-skyscanner-flight-search-v1.p.rapidapi.com";

   public static final String PLACES_FORMAT = "/apiservices/autosuggest/v1.0/%s/%s/%s/?query=%s";
   public static final String CURRENCIES_FORMAT = "/apiservices/reference/v1.0/currencies";
   public static final String COUNTRIES_FORMAT = "/apiservices/reference/v1.0/countries/%s";

   public static final String PLACES_KEY = "Places";
   public static final String CURRENCIES_KEY = "Currencies";
   public static final String COUNTRIES_KEY = "Countries";

   @Value("${x.rapid.api.key}")
   private String xRapidApiKey;

   /**
    * {@inheritDoc}
    */
   @Override
   public HttpResponse<jsonnode> get(String path) {
       HttpResponse<jsonnode> response = null;
       try {
           response = Unirest.get(HOST + path)
                   .header("x-rapidapi-host", "skyscanner-skyscanner-flight-search-v1.p.rapidapi.com")
                   .header("x-rapidapi-key", xRapidApiKey)
                   .asJson();
       } catch (UnirestException e) {
           throw new FlightClientException(String.format("Request failed, path=%s", HOST + path), e);
       }

       log.info("Response from Get request, on path={}, statusCode={}, response={}", path, response.getStatus(), response.getBody().toString());
       return response;
   }
}
Суть його в тому, що всі запити, що нас цікавлять, створюються для GET реквестів, і цей сервіс приймає вже готовий сформований запит, додає йому необхідні заголовки виду:
.header("x-rapidapi-host", "skyscanner-skyscanner-flight-search-v1.p.rapidapi.com")
.header("x-rapidapi-key", xRapidApiKey)
Щоб взяти дані з пропертей, використовується інструкція @Value, як показано нижче:
@Value("${x.rapid.api.key}")
private String xRapidApiKey;
Вона говорить про те, що application.properties лежатиме проперти з ім'ям x.rapid.api.key, яку потрібно ін'єктувати в цю змінну. Ми позбавляємося захардкоджених значень та виводимо визначення цієї змінної з програмного коду. Більш того, коли я публікую цю програму на GitHub я не додаю значення цієї проперті. Це робиться з міркувань безпеки. Написали сервіс, який працюватиме із REST запитами, тепер настав час сервісу для Localisation. Ми ж будуємо додаток виходячи з ООП, тому створюємо інтерфейс LocalisationClient та його реалізацію LocalisationClientImpl :
import com.github.romankh3.flightsmonitoring.client.dto.CountryDto;
import com.github.romankh3.flightsmonitoring.client.dto.CurrencyDto;
import java.io.IOException;
import java.util.List;

/**
* Client for SkyScanner localisation.
*/
public interface LocalisationClient {

   /**
    * Retrieve the market countries that SkyScanner flight search API support. Most suppliers (airlines,
    * travel agents and car hire dealers) set their fares based on the market (or country of purchase).
    * It is therefore necessary to specify the market country in every query.
    *
    * @param locale locale of the response.
    *
    * @return the collection of the {@link CountryDto} objects.
    *
    * @throws IOException
    */
   List<CountryDto> retrieveCountries(String locale);

   /**
    * Retrieve the currencies that we ScyScanner flight search API.
    *
    * @return the collection of the {@link CurrencyDto} objects.
    *
    * @throws IOException
    */
   List<CurrencyDto> retrieveCurrencies();

}
та реалізація LocalisationClientImpl
import static com.github.romankh3.flightsmonitoring.client.service.impl.UniRestServiceImpl.COUNTRIES_FORMAT;
import static com.github.romankh3.flightsmonitoring.client.service.impl.UniRestServiceImpl.COUNTRIES_KEY;
import static com.github.romankh3.flightsmonitoring.client.service.impl.UniRestServiceImpl.CURRENCIES_FORMAT;
import static com.github.romankh3.flightsmonitoring.client.service.impl.UniRestServiceImpl.CURRENCIES_KEY;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.romankh3.flightsmonitoring.client.dto.CountryDto;
import com.github.romankh3.flightsmonitoring.client.dto.CurrencyDto;
import com.github.romankh3.flightsmonitoring.client.service.LocalisationClient;
import com.github.romankh3.flightsmonitoring.client.service.UniRestService;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.exceptions.UnirestException;
import java.io.IOException;
import java.util.List;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
* {@inheritDoc}
*/
@Component
public class LocalisationClientImpl implements LocalisationClient {

   @Autowired
   private UniRestService uniRestService;

   @Autowired
   private ObjectMapper objectMapper;

   /**
    * {@inheritDoc}
    */
   @Override
   public List<CountryDto> retrieveCountries(String locale) throws IOException {
       HttpResponse<JsonNode> response = uniRestService.get(String.format(COUNTRIES_FORMAT, locale));

       if (response.getStatus() != HttpStatus.SC_OK) {
           return null;
       }

       String jsonList = response.getBody().getObject().get(COUNTRIES_KEY).toString();

       return objectMapper.readValue(jsonList, new TypeReference<List<CountryDto>>() {
       });
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public List<CurrencyDto> retrieveCurrencies() throws IOException, UnirestException {

       HttpResponse<JsonNode> response = uniRestService.get(CURRENCIES_FORMAT);
       if (response.getStatus() != HttpStatus.SC_OK) {
           return null;
       }

       String jsonList = response.getBody().getObject().get(CURRENCIES_KEY).toString();

       return objectMapper.readValue(jsonList, new TypeReference<List<CurrencyDto>>() {
       });
   }
}
де
  • @Autowired - це інструкція, яка говорить про те, що потрібно ін'єктувати об'єкт у цей клас і використовувати його без створення, тобто без операції new Object;
  • @Component - анотація, яка говорить, що цей об'єкт потрібно додати до Application Context, щоб надалі його можна було ін'єктувати за допомогою анотації @Autowired;
  • ObjectMapper objectMapper — об'єкт з Jackson Project, який перекладає все це в Java об'єкти.
  • CurrencyDTO та CountryDto:
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

/**
* Data transfer object for Currency.
*/
@Data
public class CurrencyDto {

   @JsonProperty("Code")
   private String code;

   @JsonProperty("Symbol")
   private String symbol;

   @JsonProperty("ThousandsSeparator")
   private String thousandsSeparator;

   @JsonProperty("DecimalSeparator")
   private String decimalSeparator;

   @JsonProperty("SymbolOnLeft")
   private boolean symbolOnLeft;

   @JsonProperty("SpaceBetweenAmountAndSymbol")
   private boolean spaceBetweenAmountAndSymbol;

   @JsonProperty("RoundingCoefficient")
   private int roundingCoefficient;

   @JsonProperty("DecimalDigits")
   private int decimalDigits;
}
	и
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

/**
* Data transfer object for Country.
*/
@Data
public class CountryDto {

   @JsonProperty("Code")
   private String code;

   @JsonProperty("Name")
   private String name;
}
Щоб ін'єктувати ObjectMapper у будь-яку частину проекту, я додав створення та додавання його до ApplicationContext через конфігураційний клас.
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* {@link Configuration} class.
*/
@Configuration
public class Config {

   @Bean
   public ObjectMapper objectMapper() {
       ObjectMapper objectMapper = new ObjectMapper();
       objectMapper.registerModule(new JavaTimeModule());
       return objectMapper;
   }
}
Інструкція @Configuration говорить Spring'у, що у цьому класі будуть якісь зміни. І саме для цього я додав ObjectMapper. За образом і подобою додаємо PlacesClient і PlacesClientImpl:
import com.github.romankh3.flightsmonitoring.client.dto.PlaceDto;
import com.github.romankh3.flightsmonitoring.client.dto.PlacesDto;
import com.mashape.unirest.http.exceptions.UnirestException;
import java.io.IOException;
import java.util.List;

/**
* SkyScanner client.
*/
public interface PlacesClient {

   /**
    * Get a list of places that match a query string based on arguments.
    *
    * @param query the code of the city.
    * @param country the code of the country.
    * @param currency the code of the currency.
    * @param locale the code of the locale.
    * @return the collection of the {@link PlaceDto} objects.
    */
   List<PlacesDto> retrieveListPlaces(String query, String country, String currency, String locale)
           throws IOException, UnirestException;
}
and
import static com.github.romankh3.flightsmonitoring.client.service.impl.UniRestServiceImpl.PLACES_FORMAT;
import static com.github.romankh3.flightsmonitoring.client.service.impl.UniRestServiceImpl.PLACES_KEY;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.romankh3.flightsmonitoring.client.dto.PlacesDto;
import com.github.romankh3.flightsmonitoring.client.service.PlacesClient;
import com.github.romankh3.flightsmonitoring.client.service.UniRestService;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.exceptions.UnirestException;
import java.io.IOException;
import java.util.List;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* {@inheritDoc}
*/
@Service
public class PlacesClientImpl implements PlacesClient {

   @Autowired
   private UniRestService uniRestService;

   @Autowired
   private ObjectMapper objectMapper;


   /**
    * {@inheritDoc}
    */
   @Override
   public List<PlacesDto> retrieveListPlaces(String query, String country, String currency, String locale)
           throws IOException, UnirestException {
       HttpResponse<JsonNode> response = uniRestService
               .get(String.format(PLACES_FORMAT, country, currency, locale, query));

       if (response.getStatus() != HttpStatus.SC_OK) {
           return null;
       }

       String jsonList = response.getBody().getObject().get(PLACES_KEY).toString();

       return objectMapper.readValue(jsonList, new TypeReference<List<PlacesDto>>() {
       });
   }
}
де PlacesDto має вигляд:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.github.romankh3.flightsmonitoring.client.service.PlacesClient;
import lombok.Data;

/**
* Using for {@link PlacesClient}.
*/
@Data
public class PlacesDto {

   @JsonProperty("PlaceId")
   private String placeId;

   @JsonProperty("PlaceName")
   private String placeName;

   @JsonProperty("CountryId")
   private String countryId;

   @JsonProperty("RegionId")
   private String regionId;

   @JsonProperty("CityId")
   private String cityId;

   @JsonProperty("CountryName")
   private String countryName;
}
І нарешті клієнт сервіс, який за потрібними даними повертатиме мінімальну ціну на переліт і всю необхідну інформацію: FlightPriceClient та FlightPriceClientImpl. Реалізовуватимемо лише один запит browseQuotes. FlightPriceClient:
import com.github.romankh3.flightsmonitoring.client.dto.FlightPricesDto;

/**
* Browse flight prices.
*/
public interface FlightPricesClient {

   /**
    * Browse quotes for current flight based on provided arguments. One-way ticket.
    *
    * @param country the country from
    * @param currency the currency to get price
    * @param locale locale for the response
    * @param originPlace origin place
    * @param destinationPlace destination place
    * @param outboundPartialDate outbound date
    * @return {@link FlightPricesDto} object.
    */
   FlightPricesDto browseQuotes(String country, String currency, String locale, String originPlace,
           String destinationPlace, String outboundPartialDate);

   /**
    * Browse quotes for current flight based on provided arguments. Round trip ticket.
    *
    * @param country the country from
    * @param currency the currency to get price
    * @param locale locale for the response
    * @param originPlace origin place
    * @param destinationPlace destination place
    * @param outboundPartialDate outbound date
    * @param inboundPartialDate inbound date
    * @return {@link FlightPricesDto} object.
    */
   FlightPricesDto browseQuotes(String country, String currency, String locale, String originPlace,
           String destinationPlace, String outboundPartialDate, String inboundPartialDate);
}
FlightPriceClientImpl
import static com.github.romankh3.flightsmonitoring.client.service.impl.UniRestServiceImpl.CURRENCIES_KEY;
import static com.github.romankh3.flightsmonitoring.client.service.impl.UniRestServiceImpl.PLACES_KEY;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.romankh3.flightsmonitoring.client.dto.CarrierDto;
import com.github.romankh3.flightsmonitoring.client.dto.CurrencyDto;
import com.github.romankh3.flightsmonitoring.client.dto.FlightPricesDto;
import com.github.romankh3.flightsmonitoring.client.dto.PlaceDto;
import com.github.romankh3.flightsmonitoring.client.dto.QuoteDto;
import com.github.romankh3.flightsmonitoring.client.dto.ValidationErrorDto;
import com.github.romankh3.flightsmonitoring.client.service.FlightPricesClient;
import com.github.romankh3.flightsmonitoring.client.service.UniRestService;
import com.github.romankh3.flightsmonitoring.exception.FlightClientException;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import java.io.IOException;
import java.util.List;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* {@inheritDoc}
*/
@Service
public class FlightPricesClientImpl implements FlightPricesClient {

   public static final String BROWSE_QUOTES_FORMAT = "/apiservices/browsequotes/v1.0/%s/%s/%s/%s/%s/%s";
   public static final String OPTIONAL_BROWSE_QUOTES_FORMAT = BROWSE_QUOTES_FORMAT + "?inboundpartialdate=%s";

   public static final String QUOTES_KEY = "Quotes";
   public static final String ROUTES_KEY = "Routes";
   public static final String DATES_KEY = "Dates";
   public static final String CARRIERS_KEY = "Carriers";
   public static final String VALIDATIONS_KEY = "ValidationErrors";

   @Autowired
   private UniRestService uniRestService;

   @Autowired
   private ObjectMapper objectMapper;

   /**
    * {@inheritDoc}
    */
   @Override
   public FlightPricesDto browseQuotes(String country, String currency, String locale, String originPlace,
           String destinationPlace, String outboundPartialDate) {

       HttpResponse<JsonNode> response = uniRestService.get(String
               .format(BROWSE_QUOTES_FORMAT, country, currency, locale, originPlace, destinationPlace,
                       outboundPartialDate));
       return mapToObject(response);
   }

   public FlightPricesDto browseQuotes(String country, String currency, String locale, String originPlace,
           String destinationPlace, String outboundPartialDate, String inboundPartialDate) {
       HttpResponse<JsonNode> response = uniRestService.get(String
               .format(OPTIONAL_BROWSE_QUOTES_FORMAT, country, currency, locale, originPlace, destinationPlace,
                       outboundPartialDate, inboundPartialDate));
       return mapToObject(response);
   }

   private FlightPricesDto mapToObject(HttpResponse<JsonNode> response) {
       if (response.getStatus() == HttpStatus.SC_OK) {
           FlightPricesDto flightPricesDto = new FlightPricesDto();
           flightPricesDto.setQuotas(readValue(response.getBody().getObject().get(QUOTES_KEY).toString(),
                   new TypeReference<List<QuoteDto>>() {
                   }));
           flightPricesDto.setCarriers(readValue(response.getBody().getObject().get(CARRIERS_KEY).toString(),
                   new TypeReference<List<CarrierDto>>() {
                   }));
           flightPricesDto.setCurrencies(readValue(response.getBody().getObject().get(CURRENCIES_KEY).toString(),
                   new TypeReference<List<CurrencyDto>>() {
                   }));
           flightPricesDto.setPlaces(readValue(response.getBody().getObject().get(PLACES_KEY).toString(),
                   new TypeReference<List<PlaceDto>>() {
                   }));
           return flightPricesDto;
       }
       throw new FlightClientException(String.format("There are validation errors. statusCode = %s", response.getStatus()),
               readValue(response.getBody().getObject().get(VALIDATIONS_KEY).toString(),
                       new TypeReference<List<ValidationErrorDto>>() {
                       }));
   }

   private <T> List<T> readValue(String resultAsString, TypeReference<List<T>> valueTypeRef) {
       List<T> list;
       try {
           list = objectMapper.readValue(resultAsString, valueTypeRef);
       } catch (IOException e) {
           throw new FlightClientException("Object Mapping failure.", e);
       }
       return list;
   }
}
де FlightClientException має вигляд:
import com.github.romankh3.flightsmonitoring.client.dto.ValidationErrorDto;
import java.util.List;

/**
* A {@link RuntimeException} that is thrown in case of an flight monitoring failures.
*/
public final class FlightClientException extends RuntimeException {

   public FlightClientException(String message) {
       super(message);
   }

   public FlightClientException(String message, Throwable throwable) {
       super(message, throwable);
   }

   public FlightClientException(String message, List<ValidationErrorDto> errors) {
       super(message);
       this.validationErrorDtos = errors;
   }

   private List<ValidationErrorDto> validationErrorDtos;
}
У результаті за даними із PlacesCl
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ