JavaRush /Blog Java /Random-ES /Creación de un sistema de seguimiento de precios de bille...

Creación de un sistema de seguimiento de precios de billetes de avión: una guía paso a paso [Parte 1]

Publicado en el grupo Random-ES

Contenido:

Creación de un sistema para monitorear los precios de los boletos aéreos: una guía paso a paso [Parte 1] - 1¡Hola a todos, comunidad JavaRush! Hoy hablaremos sobre cómo escribir una aplicación Spring Boot para monitorear los precios de los boletos aéreos paso a paso. El artículo está dirigido a personas que tienen una idea sobre:
  • REST y cómo se crean los puntos finales REST;
  • bases de datos relacionales;
  • el trabajo de maven (en particular, qué es la dependencia);
  • objeto JSON;
  • principios de registro.
Comportamiento esperado:
  1. Puede seleccionar un vuelo para una fecha específica y realizar un seguimiento del precio. El usuario se identifica mediante la dirección de correo electrónico. Tan pronto como se realiza una suscripción a un cambio de precio, el usuario recibe una notificación por correo electrónico.
  2. Cada 30 minutos (este intervalo se configura a través de application.properties) se recalcula el precio mínimo de un vuelo para todas las suscripciones. Si uno de los valores ha bajado, el usuario recibirá una notificación por correo electrónico.
  3. Se eliminarán todas las suscripciones con una fecha de vuelo desactualizada.
  4. A través de la API REST puedes:
    • crear una suscripción;
    • editar;
    • recibir todas las suscripciones por correo electrónico;
    • eliminar suscripción.

Plan de acción para lograr el objetivo.

Debe comenzar con el hecho de que la información sobre los vuelos debe obtenerse de algún lugar. Normalmente, los sitios web proporcionan una API REST abierta a través de la cual se puede recuperar información.

API (interfaz de programación de aplicaciones) es una interfaz a través de la cual puede interactuar con una aplicación. A partir de esto podemos construir un puente hacia lo que es una API REST.

Una API REST es una interfaz de solicitudes REST que se puede utilizar para comunicarse con una aplicación web.

Para ello utilizaremos Skyscanner , o mejor dicho, la API (en el sitio web de Rakuten API ). A continuación, debe elegir el marco adecuado como base básica. El más popular y demandado es el ecosistema Spring y la corona de su creación: Spring Boot. Puede ir a su sitio web oficial o leer el artículo sobre Habré . Para almacenar las suscripciones de los usuarios utilizaremos la base de datos H2 incorporada . Para leer desde JSON a las clases y viceversa, usaremos el Proyecto Jackson ( aquí está el enlace de nuestro recurso ). Usaremos spring-boot-starter-mail para enviar mensajes a los usuarios . Para que la aplicación vuelva a calcular el precio mínimo con una frecuencia determinada, usaremos Spring Scheduler . Para crear una API REST usaremos spring-boot-starter-web . Para no escribir código prestado (captadores, definidores, anulación de iguales y código hash, toString() para objetos), usaremos Project Lombok . Para sentir y ver la API REST, usaremos Swagger 2 e inmediatamente Swagger UI (interfaz de usuario) para el seguimiento en tiempo real. Así es como se ve ahora: Creación de un sistema de seguimiento de precios de billetes de avión: una guía paso a paso [Parte 1] - 2donde hay 4 consultas restantes que corresponden a crear, editar, obtener y eliminar suscripciones.

Explorando la API de Skyscanner

Sigamos el enlace a la API de rakuten . Primero debe registrarse, Creación de un sistema de seguimiento de precios de billetes de avión: una guía paso a paso [Parte 1] - 3todo esto es necesario para recibir una clave única para usar su sitio y realizar solicitudes a las API públicas que se publican en él. Una de estas API es la búsqueda de vuelos de Skyscanner que necesitamos . Ahora descubramos cómo funciona. Busquemos la solicitud GET List Places. La imagen muestra que debe completar los datos e iniciar Test Endpoint , como resultado de lo cual recibimos una respuesta en forma de objeto JSON a la derecha: Creación de un sistema para monitorear los precios de los boletos aéreos: una guía paso a paso [Parte 1] - 4y la solicitud se creará así:

https://skyscanner-skyscanner-flight-search-v1.p.rapidapi.com/apiservices/autosuggest/v1.0/{country}/{currency}/{locale}/?query={query}
y todos los parámetros serán sustituidos en esta fórmula, obtenemos:

https://skyscanner-skyscanner-flight-search-v1.p.rapidapi.com/apiservices/autosuggest/v1.0/UK/GBP/en-GB/?query=Stockholm
y se pasarán dos encabezados a estas solicitudes:

.header("x-rapidapi-host", "skyscanner-skyscanner-flight-search-v1.p.rapidapi.com")
.header("x-rapidapi-key", "sing-up-for-key"),
donde sign-up-for-keyse emite después del registro. Para realizar un seguimiento de la caída del precio, necesitaremos el punto final Buscar cotizaciones . Encuéntralo tú mismo :)

Creando un marco de aplicación basado en Spring Boot

Para crear rápida y fácilmente un proyecto con Spring Boot, puede usar Spring Initializr . Seleccione las siguientes opciones:
  1. proyecto maven
  2. Java
  3. 2.1.10
  4. grupo: el que crea necesario, por ejemplo ru.javarush
  5. artefacto - exactamente igual, por ejemplo, seguimiento de vuelos
  6. en la búsqueda de dependencias buscamos lo siguiente:
    • Web de primavera
    • Remitente de correo Java
    • Datos de primavera JPA
    • Base de datos H2
Y luego haga clic en Generar . Eso es todo: el proyecto terminado se descargará como archivo. Si algo no funciona, puedes utilizar el enlace donde guardé el proyecto deseado . Por supuesto, es mejor que lo haga usted mismo y comprenda cómo funciona. La aplicación constará de tres capas:
  • CONTROLADOR: inicie sesión en la aplicación. La API REST se describirá aquí.
  • SERVICIO es una capa de lógica empresarial. Toda la lógica de la aplicación se describirá aquí.
  • REPOSITORIO: capa para trabajar con la base de datos.
Además, las clases relacionadas con el cliente para la API de búsqueda de vuelos de Skyscanner serán un paquete separado.

Estamos escribiendo a un cliente para solicitudes a la API de búsqueda de vuelos de Skyscanner en el proyecto.

Skyscanner ha proporcionado amablemente un artículo sobre cómo utilizar su API (no crearemos una sesión con una solicitud activa). ¿Qué significa "escribirle a un cliente"? Necesitamos crear una solicitud a una URL específica con ciertos parámetros y preparar un DTO (objeto de transferencia de datos) para los datos que se nos transfieren. Hay cuatro grupos de solicitudes en el sitio:
  1. Búsqueda de vuelos en vivo: no la consideraremos innecesaria por el momento.
  2. Lugares: escribamos.
  3. Explorar precios de vuelos: utilizaremos una solicitud donde podrá obtener toda la información.
  4. Localización: agreguémosla para saber qué datos son compatibles.

Cree un servicio de cliente para la solicitud de localización:

El plan es tan simple como un nabo al vapor: crear una solicitud, mirar los parámetros, mirar la respuesta. Hay dos consultas: Marcadores de lista y Monedas. Comencemos con las monedas. La figura muestra que se trata de una solicitud sin campos adicionales: es necesaria para obtener información sobre las monedas admitidas: Creación de un sistema de seguimiento de precios de billetes de avión: una guía paso a paso [Parte 1] - 6la respuesta tiene la forma de un objeto JSON, que contiene una colección de los mismos objetos, por ejemplo:
{
"Code":"LYD"
"Symbol":"د.ل.‏"
"ThousandsSeparator":","
"DecimalSeparator":"."
"SymbolOnLeft":true
"SpaceBetweenAmountAndSymbol":false
"RoundingCoefficient":0
"DecimalDigits":3
}
Creemos un 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;
}
Dónde:
  • @Data es una anotación del proyecto Lombok y genera todos los métodos getters, setters y anulaciones toString(), equals() y hashCode(). Lo que mejora la legibilidad del código y acelera el tiempo de escritura de objetos POJO ;
  • @JsonProperty("Código") es una anotación del Proyecto Jackson que indica qué campo se asignará a esta variable. Es decir, a la variable código se le asignará un campo en JSON igual a Código .
El artículo oficial de Skyscanner sugiere utilizar la biblioteca UniRest para solicitudes REST . Por lo tanto, escribiremos otro servicio que implementará solicitudes a través de REST. Este será UniRestService . Para hacer esto, agregue una nueva dependencia a maven:
<dependency>
  <groupId>com.mashape.unirest</groupId>
  <artifactId>unirest-java</artifactId>
  <version>1.4.9</version>
</dependency>
A continuación, escribiremos un servicio que realizará solicitudes REST. Por supuesto, para cada cliente/servicio crearemos una interfaz y su implementación:
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);

}
Y su implementación:
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;
   }
}
Su esencia es que todas las solicitudes que nos interesan se crean para solicitudes GET, y este servicio acepta una solicitud ya preparada y agrega los encabezados necesarios como:
.header("x-rapidapi-host", "skyscanner-skyscanner-flight-search-v1.p.rapidapi.com")
.header("x-rapidapi-key", xRapidApiKey)
Para tomar datos de propiedades, use la anotación @Value como se muestra a continuación:
@Value("${x.rapid.api.key}")
private String xRapidApiKey;
Dice que en application.properties habrá una propiedad llamada x.rapid.api.key, que debe inyectarse en esta variable. Nos deshacemos de los valores codificados y derivamos la definición de esta variable del código del programa. Además, cuando publico esta aplicación en GitHub no agrego el valor de esta propiedad. Esto se hace por razones de seguridad. Hemos escrito un servicio que funcionará con solicitudes REST, ahora es el momento de un servicio de localización. Estamos creando una aplicación basada en programación orientada a objetos, por lo que creamos la interfaz LocalizationClient y su implementación 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 implementación 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>>() {
       });
   }
}
Dónde
  • @Autowired es una anotación que dice que necesitas inyectar un objeto en esta clase y usarlo sin crearlo, es decir, sin la operación new Object;
  • @Component es una anotación que dice que este objeto debe agregarse al Contexto de la Aplicación para que luego pueda inyectarse usando la anotación @Autowired;
  • ObjectMapper objectMapper es un objeto del Proyecto Jackson que traduce todo esto en objetos Java.
  • MonedaDTO y 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 inyectar un ObjectMapper en cualquier parte del proyecto, agregué creándolo y agregándolo al ApplicationContext a través de una clase de configuración.
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;
   }
}
La anotación @Configuration le dice a Spring que habrá algunas configuraciones en esta clase. Y sólo por esto agregué ObjectMapper. De manera similar, agregamos PlacesClient y 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;
}
y
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>>() {
       });
   }
}
donde PlacesDto tiene la forma:
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;
}
Y por último, un servicio de atención al cliente que, a partir de los datos necesarios, devolverá el precio mínimo de un vuelo y toda la información necesaria: FlightPriceClient y FlightPriceClientImpl. Implementaremos solo una solicitud, BrowseQuotes. PrecioVueloCliente:
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);
}
VueloPrecioClienteImpl
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;
   }
}
donde FlightClientException se parece a:
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, según datos de PlacesCl
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION