JavaRush /Blogue Java /Random-PT /Criando um sistema de monitoramento de preços de passagen...
Roman Beekeeper
Nível 35

Criando um sistema de monitoramento de preços de passagens aéreas: um guia passo a passo [Parte 1]

Publicado no grupo Random-PT

Contente:

Criando um sistema de monitoramento de preços de passagens aéreas: um guia passo a passo [Parte 1] - 1Olá a todos, comunidade JavaRush! Hoje falaremos sobre como escrever um aplicativo Spring Boot para monitorar preços de passagens aéreas passo a passo. O artigo é destinado a pessoas que têm uma ideia sobre:
  • REST e como os endpoints REST são construídos;
  • bancos de dados relacionais;
  • o trabalho do maven (em particular, o que é dependência);
  • Objeto JSON;
  • princípios de registro.
Comportamento esperado:
  1. Você pode selecionar um voo para uma data específica e acompanhar o preço dele. O usuário é identificado pelo endereço de e-mail. Assim que for feita a assinatura de uma alteração de preço, o usuário receberá uma notificação por e-mail.
  2. A cada 30 minutos (este intervalo é configurado através de application.properties) o preço mínimo de um voo é recalculado para todas as assinaturas. Caso um dos valores tenha diminuído, o usuário receberá uma notificação por e-mail.
  3. Todas as assinaturas com data de voo desatualizada serão excluídas.
  4. Através da API REST você pode:
    • crie uma assinatura;
    • editar;
    • receber todas as assinaturas por email;
    • excluir assinatura.

Plano de ação para atingir a meta

Você precisa começar com o fato de que as informações sobre voos precisam ser retiradas de algum lugar. Normalmente, os sites fornecem uma API REST aberta por meio da qual as informações podem ser recuperadas.

API (interface de programação de aplicativos) é uma interface por meio da qual você pode interagir com um aplicativo. A partir disso podemos construir uma ponte para o que é uma API REST.

Uma API REST é uma interface de solicitações REST que pode ser usada para se comunicar com um aplicativo da web.

Para isso, utilizaremos o Skyscanner , ou melhor, a API (no site da Rakuten API ). Em seguida, você precisa escolher a estrutura certa como base básica. O mais popular e procurado é o ecossistema Spring e a coroa de sua criação - Spring Boot. Você pode acessar o site oficial ou ler o artigo sobre Habré . Para armazenar assinaturas de usuários, usaremos o banco de dados H2 integrado . Para ler JSON para classes e vice-versa, usaremos o Projeto Jackson ( aqui está o link em nosso recurso ). Usaremos spring-boot-starter-mail para enviar mensagens aos usuários . Para que a aplicação recalcule o preço mínimo em uma determinada frequência, usaremos Spring Scheduler . Para criar uma API REST usaremos spring-boot-starter-web . Para não escrever código emprestado (getters, setters, override equals e hashcode, toString() para objetos), usaremos o Projeto Lombok . Para sentir e ver a API REST, usaremos o Swagger 2 e imediatamente o Swagger UI (interface do usuário) para rastreamento em tempo real. Esta é a aparência agora: Criando um sistema de monitoramento de preços de passagens aéreas: um guia passo a passo [Parte 1] - 2onde existem 4 consultas restantes que correspondem à criação, edição, obtenção e exclusão de assinaturas.

Explorando a API do Skyscanner

Vamos seguir o link para a API rakuten . Primeiro você precisa se registrar, Criando um sistema de monitoramento de preços de passagens aéreas: um guia passo a passo [Parte 1] - 3tudo isso é necessário para receber uma chave exclusiva para usar seu site e fazer solicitações às APIs públicas que estão postadas nele. Uma dessas APIs é o Skyscanner Flight Search que precisamos . Agora vamos descobrir como isso funciona. Vamos encontrar a solicitação GET List Places. A imagem mostra que você precisa preencher os dados e iniciar o Test Endpoint , como resultado obtemos uma resposta na forma de um objeto JSON à direita: Criando um sistema de monitoramento de preços de passagens aéreas: um guia passo a passo [Parte 1] - 4e a solicitação será criada assim:

https://skyscanner-skyscanner-flight-search-v1.p.rapidapi.com/apiservices/autosuggest/v1.0/{country}/{currency}/{locale}/?query={query}
e todos os parâmetros serão substituídos nesta fórmula, obtemos:

https://skyscanner-skyscanner-flight-search-v1.p.rapidapi.com/apiservices/autosuggest/v1.0/UK/GBP/en-GB/?query=Stockholm
e dois cabeçalhos serão passados ​​para estas solicitações:

.header("x-rapidapi-host", "skyscanner-skyscanner-flight-search-v1.p.rapidapi.com")
.header("x-rapidapi-key", "sing-up-for-key"),
onde sign-up-for-keyé emitido após o registro. Para rastrear a queda de preço, precisaremos do endpoint Browse Quotes . Encontre você mesmo :)

Criando uma estrutura de aplicativo baseada em Spring Boot

Para criar um projeto de forma rápida e fácil com Spring Boot, você pode usar Spring Initializr . Selecione as seguintes opções:
  1. Projeto Maven
  2. Java
  3. 2.1.10
  4. grupo - o que você achar necessário, por exemplo ru.javarush
  5. artefato - exatamente o mesmo, por exemplo, monitoramento de voos
  6. na busca por dependências procuramos o seguinte:
    • Primavera Web
    • Remetente de correio Java
    • Spring Data Jpa
    • Banco de dados H2
E então clique em Gerar . É isso: o projeto finalizado será baixado como um arquivo. Se algo não der certo, você pode usar o link onde salvei o projeto desejado . Claro, é melhor fazer isso sozinho e entender como funciona. A aplicação consistirá em três camadas:
  • CONTROLADOR - faça login no aplicativo. A API REST será descrita aqui
  • SERVICE é uma camada de lógica de negócios. Toda a lógica da aplicação será descrita aqui.
  • REPOSITÓRIO - camada para trabalhar com o banco de dados.
Além disso, as aulas relacionadas ao cliente da API Skyscanner Flight Search serão um pacote separado.

Estamos escrevendo um cliente para solicitações à API Skyscanner Flight Search no projeto

O Skyscanner gentilmente forneceu um artigo sobre como usar sua API (não criaremos uma sessão com uma solicitação ativa). O que significa “escrever para um cliente”? Precisamos criar uma solicitação para uma URL específica com determinados parâmetros e preparar um DTO (objeto de transferência de dados) para os dados transferidos de volta para nós. Existem quatro grupos de solicitações no site:
  1. Pesquisa de voo ao vivo - não consideraremos isso desnecessário no momento.
  2. Lugares - vamos escrever.
  3. Consulte os preços dos voos - usaremos uma solicitação onde você poderá obter todas as informações.
  4. Localização - vamos adicioná-la para sabermos quais dados são suportados.

Crie um serviço de cliente para a solicitação de localização:

O plano é tão simples quanto um nabo cozido no vapor: crie uma solicitação, observe os parâmetros, observe a resposta. Existem duas consultas: Marcadores de lista e Moedas. Vamos começar com Moedas. A figura mostra que se trata de uma solicitação sem campos adicionais: é necessária para obter informações sobre as moedas suportadas: Criando um sistema de monitoramento de preços de passagens aéreas: um guia passo a passo [Parte 1] - 6A resposta está na forma de um objeto JSON, que contém uma coleção dos mesmos objetos, por exemplo:
{
"Code":"LYD"
"Symbol":"د.ل.‏"
"ThousandsSeparator":","
"DecimalSeparator":"."
"SymbolOnLeft":true
"SpaceBetweenAmountAndSymbol":false
"RoundingCoefficient":0
"DecimalDigits":3
}
Vamos criar um CurrencyDto para este objeto:
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;
}
Onde:
  • @Data é uma anotação do projeto Lombok e gera todos os métodos getters, setters, substituições toString(), equals() e hashCode(). O que melhora a legibilidade do código e acelera o tempo de gravação de objetos POJO ;
  • @JsonProperty("Code") é uma anotação do Projeto Jackson que informa qual campo será atribuído a esta variável. Ou seja, será atribuído à variável code um campo em JSON igual a Code .
O artigo oficial do Skyscanner sugere o uso da biblioteca UniRest para solicitações REST . Portanto, escreveremos outro serviço que implementará solicitações via REST. Este será UniRestService . Para fazer isso, adicione uma nova dependência ao maven:
<dependency>
  <groupId>com.mashape.unirest</groupId>
  <artifactId>unirest-java</artifactId>
  <version>1.4.9</version>
</dependency>
A seguir, escreveremos um serviço que realizará solicitações REST. Claro que para cada cliente/serviço iremos criar uma interface e sua implementação:
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);

}
E sua implementação:
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;
   }
}
Sua essência é que todas as solicitações que nos interessam sejam criadas para solicitações GET, e este serviço aceita uma solicitação pronta e adiciona os cabeçalhos necessários como:
.header("x-rapidapi-host", "skyscanner-skyscanner-flight-search-v1.p.rapidapi.com")
.header("x-rapidapi-key", xRapidApiKey)
Para obter dados de propriedades, use a anotação @Value conforme mostrado abaixo:
@Value("${x.rapid.api.key}")
private String xRapidApiKey;
Diz que em application.properties haverá uma propriedade chamada x.rapid.api.key, que precisa ser injetada nesta variável. Nos livramos dos valores codificados e derivamos a definição dessa variável do código do programa. Além disso, quando publico esta aplicação no GitHub não adiciono o valor desta propriedade. Isso é feito por razões de segurança. Escrevemos um serviço que funcionará com solicitações REST, agora é hora de um serviço de localização. Estamos construindo uma aplicação baseada em OOP, então criamos a interface LocalizationClient e sua implementação 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();

}
e implementação de 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>>() {
       });
   }
}
Onde
  • @Autowired é uma anotação que diz que você precisa injetar um objeto nesta classe e utilizá-lo sem criá-lo, ou seja, sem a nova operação Object;
  • @Component é uma anotação que diz que este objeto deve ser adicionado ao Contexto da Aplicação para que posteriormente possa ser injetado usando a anotação @Autowired;
  • ObjectMapper objectMapper é um objeto do Projeto Jackson que traduz tudo isso em objetos Java.
  • MoedaDTO e PaísDto:
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;
}
Para injetar um ObjectMapper em qualquer parte do projeto, adicionei a criação e adiciono-o ao ApplicationContext por meio de uma classe de configuração.
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;
   }
}
A anotação @Configuration informa ao Spring que haverá algumas configurações nesta classe. E só para isso adicionei ObjectMapper. Da mesma forma, adicionamos PlacesClient e 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;
}
e
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>>() {
       });
   }
}
onde PlacesDto tem o formato:
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;
}
E por último, um serviço de atendimento ao cliente que, com base nos dados necessários, retornará o preço mínimo de um voo e todas as informações necessárias: FlightPriceClient e FlightPriceClientImpl. Implementaremos apenas uma solicitação, BrowseQuotes. FlightPriceCliente:
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;
   }
}
onde FlightClientException se parece com:
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;
}
Como resultado, de acordo com dados da PlacesCl
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION