From c84135a0a2d8cc5e25c4801432cbe037a1efaf8b Mon Sep 17 00:00:00 2001 From: Martin Lowe <martin.lowe@eclipse-foundation.org> Date: Mon, 7 Jun 2021 15:05:53 -0400 Subject: [PATCH] Create an endpoint for creating country lists #15 --- .../geoip/client/helper/CountryHelper.java | 48 +++++++++++++++++++ .../model/{Country.java => CountryCSV.java} | 4 +- .../geoip/client/model/ISOCountry.java | 33 +++++++++++++ .../client/resources/CountryResource.java | 19 ++++++++ .../service/impl/CSVNetworkService.java | 6 +-- .../client/resources/CountryResourceTest.java | 48 +++++++++++++++++-- 6 files changed, 148 insertions(+), 10 deletions(-) create mode 100644 src/main/java/org/eclipsefoundation/geoip/client/helper/CountryHelper.java rename src/main/java/org/eclipsefoundation/geoip/client/model/{Country.java => CountryCSV.java} (95%) create mode 100644 src/main/java/org/eclipsefoundation/geoip/client/model/ISOCountry.java diff --git a/src/main/java/org/eclipsefoundation/geoip/client/helper/CountryHelper.java b/src/main/java/org/eclipsefoundation/geoip/client/helper/CountryHelper.java new file mode 100644 index 0000000..731edc7 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/geoip/client/helper/CountryHelper.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2021 Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * Author: Martin Lowe <martin.lowe@eclipse-foundation.org> + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipsefoundation.geoip.client.helper; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.eclipsefoundation.geoip.client.model.ISOCountry; + +/** + * Helper to retrieve and cache countries and country codes in a light manner. + */ +public class CountryHelper { + private static final Map<String, List<ISOCountry>> countryListCache = new HashMap<>(); + + public static List<ISOCountry> getCountries(Locale l) { + // Default to EN locale if not specified + Locale actualLocale = l != null ? l : Locale.ENGLISH; + // get the cached data if it exists + List<ISOCountry> countries = countryListCache.get(actualLocale.getLanguage()); + if (countries == null) { + // generate cached data + String[] isoCountries = Locale.getISOCountries(); + countries = new ArrayList<>(isoCountries.length); + for (String isoCountry : isoCountries) { + Locale c = new Locale("", isoCountry); + countries.add(new ISOCountry(c.getDisplayCountry(actualLocale), c.getCountry())); + } + countryListCache.put(actualLocale.getLanguage(), countries); + } + return new ArrayList<>(countries); + } + + private CountryHelper() { + } +} diff --git a/src/main/java/org/eclipsefoundation/geoip/client/model/Country.java b/src/main/java/org/eclipsefoundation/geoip/client/model/CountryCSV.java similarity index 95% rename from src/main/java/org/eclipsefoundation/geoip/client/model/Country.java rename to src/main/java/org/eclipsefoundation/geoip/client/model/CountryCSV.java index 135ad49..27d1251 100644 --- a/src/main/java/org/eclipsefoundation/geoip/client/model/Country.java +++ b/src/main/java/org/eclipsefoundation/geoip/client/model/CountryCSV.java @@ -17,13 +17,13 @@ import io.quarkus.runtime.annotations.RegisterForReflection; * */ @RegisterForReflection -public class Country { +public class CountryCSV { @CsvBindByName(column = "geoname_id") private String id; @CsvBindByName(column = "country_iso_code") private String countryIsoCode; - public Country() { + public CountryCSV() { } /** diff --git a/src/main/java/org/eclipsefoundation/geoip/client/model/ISOCountry.java b/src/main/java/org/eclipsefoundation/geoip/client/model/ISOCountry.java new file mode 100644 index 0000000..21bf4fb --- /dev/null +++ b/src/main/java/org/eclipsefoundation/geoip/client/model/ISOCountry.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021 Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * Author: Martin Lowe <martin.lowe@eclipse-foundation.org> + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipsefoundation.geoip.client.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ISOCountry { + private String name; + @JsonProperty("iso_code") + private String isoCode; + + public ISOCountry(String name, String isoCode) { + this.name = name; + this.isoCode = isoCode; + } + + public String getName() { + return this.name; + } + + public String getIsoCode() { + return this.isoCode; + } +} diff --git a/src/main/java/org/eclipsefoundation/geoip/client/resources/CountryResource.java b/src/main/java/org/eclipsefoundation/geoip/client/resources/CountryResource.java index f56b996..d9df323 100644 --- a/src/main/java/org/eclipsefoundation/geoip/client/resources/CountryResource.java +++ b/src/main/java/org/eclipsefoundation/geoip/client/resources/CountryResource.java @@ -7,18 +7,22 @@ package org.eclipsefoundation.geoip.client.resources; import java.io.IOException; +import java.util.Locale; +import java.util.MissingResourceException; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; +import org.eclipsefoundation.geoip.client.helper.CountryHelper; import org.eclipsefoundation.geoip.client.helper.InetAddressHelper; import org.eclipsefoundation.geoip.client.model.Error; import org.eclipsefoundation.geoip.client.service.GeoIPService; @@ -59,4 +63,19 @@ public class CountryResource { throw new RuntimeException("Error while converting country record to JSON", e); } } + + @GET + @Path("/all") + public Response getAll(@QueryParam("locale") String locale) { + Locale l = new Locale(locale != null ? locale : "en"); + return Response.ok(CountryHelper.getCountries(isValid(l) ? l : null)).build(); + } + + private boolean isValid(Locale locale) { + try { + return locale.getISO3Language() != null && locale.getISO3Country() != null; + } catch (MissingResourceException e) { + return false; + } + } } diff --git a/src/main/java/org/eclipsefoundation/geoip/client/service/impl/CSVNetworkService.java b/src/main/java/org/eclipsefoundation/geoip/client/service/impl/CSVNetworkService.java index 39fd863..cae92e8 100644 --- a/src/main/java/org/eclipsefoundation/geoip/client/service/impl/CSVNetworkService.java +++ b/src/main/java/org/eclipsefoundation/geoip/client/service/impl/CSVNetworkService.java @@ -20,7 +20,7 @@ import javax.annotation.PostConstruct; import javax.enterprise.context.ApplicationScoped; import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.eclipsefoundation.geoip.client.model.Country; +import org.eclipsefoundation.geoip.client.model.CountryCSV; import org.eclipsefoundation.geoip.client.model.IPVersion; import org.eclipsefoundation.geoip.client.model.SubnetRange; import org.eclipsefoundation.geoip.client.service.NetworkService; @@ -73,9 +73,9 @@ public class CSVNetworkService implements NetworkService { private void loadCountries(String filePath, Map<String, String> container) { try (FileReader reader = new FileReader(filePath)) { // read in all of the country lines as country objects - List<Country> countries = new CsvToBeanBuilder<Country>(reader).withType(Country.class).build().parse(); + List<CountryCSV> countries = new CsvToBeanBuilder<CountryCSV>(reader).withType(CountryCSV.class).build().parse(); // add each of the countries to the container map, mapping ID to the ISO code - for (Country c : countries) { + for (CountryCSV c : countries) { container.put(c.getId(), c.getCountryIsoCode().toLowerCase()); } } catch (FileNotFoundException e) { diff --git a/src/test/java/org/eclipsefoundation/geoip/client/resources/CountryResourceTest.java b/src/test/java/org/eclipsefoundation/geoip/client/resources/CountryResourceTest.java index 70b0593..98b1769 100644 --- a/src/test/java/org/eclipsefoundation/geoip/client/resources/CountryResourceTest.java +++ b/src/test/java/org/eclipsefoundation/geoip/client/resources/CountryResourceTest.java @@ -8,9 +8,17 @@ package org.eclipsefoundation.geoip.client.resources; import static io.restassured.RestAssured.given; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.stream.Stream; + +import org.eclipsefoundation.geoip.client.model.ISOCountry; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import io.quarkus.test.junit.QuarkusTest; +import io.restassured.response.Response; /** * Test the listing resource endpoint, using fake data points to test solely the @@ -25,11 +33,11 @@ public class CountryResourceTest { private static final String VALID_IPV4_ADDRESS = "72.137.192.0"; // Google IE server address private static final String VALID_IPV6_ADDRESS = "2a00:1450:400a:804::2004"; - + @Test public void testCityIPEndpoint() { - given().when().get("/countries/"+VALID_IPV4_ADDRESS).then().statusCode(200); - given().when().get("/countries/"+VALID_IPV6_ADDRESS).then().statusCode(200); + given().when().get("/countries/" + VALID_IPV4_ADDRESS).then().statusCode(200); + given().when().get("/countries/" + VALID_IPV6_ADDRESS).then().statusCode(200); } @Test @@ -41,17 +49,47 @@ public class CountryResourceTest { given().when().get("/countries/1.0.300.0").then().statusCode(400); given().when().get("/countries/1.0.0.300").then().statusCode(400); given().when().get("/countries/sample").then().statusCode(400); - given().when().get("/countries/"+VALID_IPV4_ADDRESS+":8080").then().statusCode(400); + given().when().get("/countries/" + VALID_IPV4_ADDRESS + ":8080").then().statusCode(400); // seems to be an issue with Google Guava code, only gets detected by MaxMind given().when().get("/countries/0.1.1.1").then().statusCode(500); // loopback + unspecified address given().when().get("/countries/127.0.0.1").then().statusCode(400); given().when().get("/countries/0.0.0.0").then().statusCode(400); - + // IPv6 tests given().when().get("/countries/bad:ip:add::res").then().statusCode(400); // loopback + unspecified address given().when().get("/countries/::").then().statusCode(400); given().when().get("/countries/::1").then().statusCode(400); } + + @Test + void countryList_base() { + Response r = given().when().get("/countries/all"); + r.then().statusCode(200); + // assert that United states exists as English by default + Assertions.assertTrue(Stream.of(r.as(ISOCountry[].class)) + .anyMatch(c -> Locale.US.getDisplayCountry().equalsIgnoreCase(c.getName()) + && "us".equalsIgnoreCase(c.getIsoCode()))); + } + + @Test + void countryList_localized() { + Response r = given().when().get("/countries/all?locale=fr"); + r.then().statusCode(200); + // assert that United states exists as French value + Assertions.assertTrue(Stream.of(r.as(ISOCountry[].class)) + .anyMatch(c -> Locale.US.getDisplayCountry(Locale.FRENCH).equalsIgnoreCase(c.getName()) + && "us".equalsIgnoreCase(c.getIsoCode()))); + } + + @Test + void countryList_badLocale() { + Response r = given().when().get("/countries/all?locale=foobar"); + r.then().statusCode(200); + // assert that English value exists as fallback when bad locale passed + Assertions.assertTrue(Stream.of(r.as(ISOCountry[].class)) + .anyMatch(c -> Locale.US.getDisplayCountry().equalsIgnoreCase(c.getName()) + && "us".equalsIgnoreCase(c.getIsoCode()))); + } } -- GitLab