JavaRush /Blog Java /Random-PL /Tworzenie systemu monitorowania cen biletów lotniczych: p...
Roman Beekeeper
Poziom 35

Tworzenie systemu monitorowania cen biletów lotniczych: przewodnik krok po kroku [Część 1]

Opublikowano w grupie Random-PL

Treść:

Tworzenie systemu monitorowania cen biletów lotniczych: przewodnik krok po kroku [Część 1] - 1Witam wszystkich, społeczność JavaRush! Dzisiaj porozmawiamy o tym, jak krok po kroku napisać aplikację Spring Boot do monitorowania cen biletów lotniczych. Artykuł przeznaczony jest dla osób, które mają pojęcie o:
  • REST i sposób budowania punktów końcowych REST;
  • relacyjne bazy danych;
  • praca mavena (w szczególności czym jest zależność);
  • obiekt JSON;
  • zasady logowania.
Spodziewane zachowanie:
  1. Możesz wybrać lot na konkretną datę i śledzić jego cenę. Użytkownik identyfikowany jest poprzez adres e-mail. Gdy tylko zostanie dokonana subskrypcja zmiany ceny, użytkownik otrzyma powiadomienie e-mailem.
  2. Co 30 minut (interwał ten konfiguruje się w aplikacji.properties) dla wszystkich abonamentów przeliczana jest minimalna cena za lot. Jeśli jedna z wartości spadnie, użytkownik otrzyma powiadomienie e-mailem.
  3. Wszystkie subskrypcje z nieaktualną datą emisji zostaną usunięte.
  4. Za pośrednictwem REST API możesz:
    • utwórz subskrypcję;
    • edytować;
    • otrzymywać wszystkie subskrypcje e-mailem;
    • usuń subskrypcję.

Plan działania pozwalający osiągnąć cel

Trzeba zacząć od tego, że skądś trzeba wziąć informacje o lotach. Zazwyczaj strony internetowe udostępniają otwarty interfejs API REST, za pomocą którego można uzyskać informacje.

API (interfejs programowania aplikacji) to interfejs, za pomocą którego można wchodzić w interakcję z aplikacją. Na tej podstawie możemy zbudować pomost do tego, czym jest interfejs API REST.

REST API to interfejs żądań REST, którego można używać do komunikacji z aplikacją internetową.

W tym celu skorzystamy ze Skyscannera , a raczej API (na stronie Rakuten API ). Następnie musisz wybrać odpowiedni framework jako podstawowy fundament. Najpopularniejszym i poszukiwanym jest ekosystem Spring i zwieńczenie ich powstania - Spring Boot. Możesz przejść do ich oficjalnej strony internetowej lub przeczytać artykuł na temat Habré . Do przechowywania subskrypcji użytkowników wykorzystamy wbudowaną bazę danych H2 . Aby czytać z JSON do klas i z powrotem, użyjemy projektu Jackson ( tutaj znajduje się link w naszym zasobie ). Do wysyłania wiadomości do użytkowników będziemy używać spring-boot-starter-mail.Aby aplikacja przeliczyła cenę minimalną z określoną częstotliwością, skorzystamy z Spring Scheduler . Aby utworzyć API REST użyjemy spring-boot-starter-web . Aby nie pisać zapożyczonego kodu (gettery, settery, override równości i hashcode, toString() dla obiektów), skorzystamy z Project Lombok . Aby poczuć i zobaczyć REST API, użyjemy Swagger 2 i natychmiast Swagger UI (interfejs użytkownika) do śledzenia w czasie rzeczywistym. Oto jak to teraz wygląda: Tworzenie systemu monitorowania cen biletów lotniczych: przewodnik krok po kroku [Część 1] - 2gdzie znajdują się 4 pozostałe zapytania, które odpowiadają tworzeniu, edytowaniu, pobieraniu i usuwaniu subskrypcji.

Poznajemy API Skyscanner

Skorzystajmy z linku do rakuten api . Najpierw musisz się zarejestrować.Wszystko Tworzenie systemu monitorowania cen biletów lotniczych: przewodnik krok po kroku [Część 1] - 3to jest potrzebne, aby otrzymać unikalny klucz umożliwiający korzystanie z ich witryny i wysyłanie żądań do publicznych interfejsów API, które są na niej opublikowane. Jednym z tych interfejsów API jest wyszukiwarka lotów Skyscanner, której potrzebujemy . Teraz zastanówmy się, jak to działa. Znajdźmy żądanie GET List Places. Na obrazku widać, że należy uzupełnić dane i uruchomić Test Endpoint , w efekcie czego po prawej stronie otrzymamy odpowiedź w postaci obiektu JSON: Tworzenie systemu monitorowania cen biletów lotniczych: przewodnik krok po kroku [Część 1] - 4a żądanie zostanie utworzone w następujący sposób:

https://skyscanner-skyscanner-flight-search-v1.p.rapidapi.com/apiservices/autosuggest/v1.0/{country}/{currency}/{locale}/?query={query}
i wszystkie parametry zostaną podstawione do tego wzoru, otrzymamy:

https://skyscanner-skyscanner-flight-search-v1.p.rapidapi.com/apiservices/autosuggest/v1.0/UK/GBP/en-GB/?query=Stockholm
i do tych żądań zostaną przekazane dwa nagłówki:

.header("x-rapidapi-host", "skyscanner-skyscanner-flight-search-v1.p.rapidapi.com")
.header("x-rapidapi-key", "sing-up-for-key"),
jeżeli sign-up-for-keyjest wydawane po rejestracji. Aby śledzić spadek cen, będziemy potrzebować punktu końcowego Przeglądaj oferty . Znajdź to sam :)

Tworzenie frameworka aplikacji w oparciu o Spring Boot

Aby szybko i łatwo utworzyć projekt za pomocą Spring Boot, możesz użyć Spring Individualizr . Wybierz następujące opcje:
  1. Projekt Mavena
  2. Jawa
  3. 2.1.10
  4. group - cokolwiek uznasz za konieczne, na przykład ru.javarush
  5. artefakt - dokładnie taki sam, na przykład monitorowanie lotów
  6. w poszukiwaniu zależności szukamy:
    • Wiosenna sieć
    • Nadawca poczty Java
    • Dane wiosenne Jpa
    • Baza danych H2
A następnie kliknij opcję Generuj . To wszystko: gotowy projekt zostanie pobrany jako archiwum. Jeśli coś nie wyjdzie, możesz skorzystać z linku, w którym zapisałem wybrany projekt . Oczywiście lepiej zrobić to samodzielnie i zrozumieć, jak to działa. Aplikacja będzie składać się z trzech warstw:
  • KONTROLER - zaloguj się do aplikacji. API REST zostanie opisane tutaj
  • USŁUGA to warstwa logiki biznesowej. Cała logika aplikacji zostanie opisana tutaj.
  • REPOZYTORIUM - warstwa do pracy z bazą danych.
Oddzielnym pakietem będą także zajęcia związane z klientem dla API Skyscanner Flight Search.

Piszemy klienta na zapytania do API Skyscanner Flight Search w projekcie

Skyscanner udostępnił artykuł na temat korzystania z ich API (nie będziemy tworzyć sesji z aktywnym żądaniem). Co to znaczy „napisać klienta”? Musimy utworzyć żądanie na konkretny adres URL z określonymi parametrami i przygotować DTO (obiekt transferu danych) dla przesyłanych z powrotem do nas danych. Na stronie znajdują się cztery grupy żądań:
  1. Wyszukiwanie lotów na żywo - w tej chwili nie uznamy tego za niepotrzebne.
  2. Miejsca - napiszmy.
  3. Przeglądaj ceny lotów – skorzystamy z jednego zapytania, w którym uzyskasz wszystkie informacje.
  4. Lokalizacja - dodajmy ją, abyśmy wiedzieli jakie dane są obsługiwane.

Utwórz usługę klienta dla żądania lokalizacji:

Plan jest prosty jak rzepa na parze: utwórz zapytanie, spójrz na parametry, spójrz na odpowiedź. Istnieją dwa zapytania: Lista znaczników i Waluty. Zacznijmy od walut. Z rysunku wynika, że ​​jest to żądanie bez dodatkowych pól: potrzebne jest do uzyskania informacji o obsługiwanych walutach: Tworzenie systemu monitorowania cen biletów lotniczych: przewodnik krok po kroku [Część 1] - 6Odpowiedź ma postać obiektu JSON, który zawiera zbiór takich samych obiektów, np.:
{
"Code":"LYD"
"Symbol":"د.ل.‏"
"ThousandsSeparator":","
"DecimalSeparator":"."
"SymbolOnLeft":true
"SpaceBetweenAmountAndSymbol":false
"RoundingCoefficient":0
"DecimalDigits":3
}
Utwórzmy CurrencyDto dla tego obiektu:
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;
}
Gdzie:
  • @Data to adnotacja z projektu Lombok , która generuje wszystkie metody pobierające, ustawiające i przesłaniające metody toString(), równości() i hashCode(). Co poprawia czytelność kodu i przyspiesza czas zapisu obiektów POJO ;
  • @JsonProperty("Kod") to adnotacja z projektu Jackson, która informuje, jakie pole zostanie przypisane do tej zmiennej. Oznacza to, że do zmiennej code zostanie przypisane pole w formacie JSON równe Code .
Oficjalny artykuł Skyscanner sugeruje używanie biblioteki UniRest do żądań REST . Dlatego napiszemy kolejną usługę, która będzie realizować żądania poprzez REST. Będzie to UniRestService . Aby to zrobić, dodaj nową zależność do maven:
<dependency>
  <groupId>com.mashape.unirest</groupId>
  <artifactId>unirest-java</artifactId>
  <version>1.4.9</version>
</dependency>
Następnie napiszemy usługę, która będzie wykonywać żądania REST. Oczywiście dla każdego klienta/usługi stworzymy interfejs i jego implementację:
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);

}
I jego realizacja:
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;
   }
}
Jej istotą jest to, że wszystkie interesujące nas żądania tworzone są pod żądania GET, a usługa ta przyjmuje gotowe żądanie i dodaje niezbędne nagłówki takie jak:
.header("x-rapidapi-host", "skyscanner-skyscanner-flight-search-v1.p.rapidapi.com")
.header("x-rapidapi-key", xRapidApiKey)
Aby pobrać dane z właściwości, użyj adnotacji @Value, jak pokazano poniżej:
@Value("${x.rapid.api.key}")
private String xRapidApiKey;
Mówi, że w application.properties będzie właściwość o nazwie x.rapid.api.key, którą należy wstrzyknąć do tej zmiennej. Pozbywamy się zakodowanych na stałe wartości i wyprowadzamy definicję tej zmiennej z kodu programu. Co więcej, publikując tę ​​aplikację na GitHubie, nie dodaję wartości tej właściwości. Odbywa się to ze względów bezpieczeństwa. Napisaliśmy usługę, która będzie działać z żądaniami REST, teraz czas na usługę Lokalizacji. Budujemy aplikację w oparciu o OOP, dlatego tworzymy interfejs LocalizationClient i jego implementację LocalizationClientImpl :
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();

}
i wdrożenie LocalizationClientImpl
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>>() {
       });
   }
}
Gdzie
  • @Autowired to adnotacja mówiąca, że ​​należy wstrzyknąć obiekt do tej klasy i używać go bez jego tworzenia, czyli bez nowej operacji Object;
  • @Component to adnotacja mówiąca, że ​​ten obiekt musi zostać dodany do kontekstu aplikacji, aby można go było później wstrzyknąć za pomocą adnotacji @Autowired;
  • ObjectMapper objectMapper to obiekt z projektu Jackson, który tłumaczy to wszystko na obiekty Java.
  • WalutaDTO i krajDto:
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;
}
Aby wstrzyknąć ObjectMapper do dowolnej części projektu, dodałem utworzenie i dodanie go do ApplicationContext za pośrednictwem klasy konfiguracyjnej.
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;
   }
}
Adnotacja @Configuration informuje Springa, że ​​w tej klasie będą pewne konfiguracje. I właśnie w tym celu dodałem ObjectMapper. Podobnie dodajemy PlacesClient i 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;
}
I
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>>() {
       });
   }
}
gdzie PlacesDto ma postać:
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;
}
I wreszcie obsługa klienta, która na podstawie niezbędnych danych zwróci cenę minimalną za lot i wszystkie niezbędne informacje: FlightPriceClient i FlightPriceClientImpl. Zrealizujemy tylko jedno żądanie, przeglądajQuotes. Cena lotuKlient:
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);
}
LotCenaKlientImpl
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;
   }
}
gdzie FlightClientException wygląda następująco:
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;
}
W rezultacie, według danych PlacesCl
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION