diff --git a/.gitignore b/.gitignore index ee869bc..f44dcb8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,6 @@ .idea -project -target -logs -project/project -project/target target +logs/ tmp .history dist diff --git a/.travis.yml b/.travis.yml index c01440a..5d1c8ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: scala script: - - sbt clean compile test publishLocal - - cd sample/java && sbt clean compile test + - sbt clean compile test jdk: - oraclejdk8 +scala: + - 2.12.4 \ No newline at end of file diff --git a/README.md b/README.md index 602d25c..1b8e800 100644 --- a/README.md +++ b/README.md @@ -66,8 +66,6 @@ object ApplicationBuild extends Build { val appDependencies = Seq( // Add your project dependencies here, - javaCore, - javaJdbc, "com.edulify" %% "geolocation" % "2.1.0" ) @@ -84,13 +82,13 @@ object ApplicationBuild extends Build { Since there is support to Freegeoip and Maxmind, there is also two modules that you enable, depending on which one you want to use. To eanble the module, just add the following line to you `conf/application.conf` file: ``` -play.modules.enabled += "com.edulify.modules.geolocation.providers.FreegeoipModule" +play.modules.enabled += "com.edulify.modules.geolocation.FreegeoipModule" ``` Or, in case you want to use Maxmind instead: ``` -play.modules.enabled += "com.edulify.modules.geolocation.providers.MaxmindModule" +play.modules.enabled += "com.edulify.modules.geolocation.MaxmindModule" ``` ## Configurations @@ -112,14 +110,13 @@ geolocation { on = true ttl = 10s } - maxmind.license = "your-maxmind-license" } ``` Also, notice that the cache uses the cache support offered by Playframework. A complete configuration can be found below: ``` -play.modules.enabled += "com.edulify.modules.geolocation.providers.FreegeoipModule" +play.modules.enabled += "com.edulify.modules.geolocation.FreegeoipModule" geolocation { cache { diff --git a/app/com/edulify/modules/geolocation/providers/FreegeoipModule.java b/app/com/edulify/modules/geolocation/providers/FreegeoipModule.java deleted file mode 100644 index 247b13f..0000000 --- a/app/com/edulify/modules/geolocation/providers/FreegeoipModule.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.edulify.modules.geolocation.providers; - -import com.edulify.modules.geolocation.GeolocationProvider; -import com.google.inject.AbstractModule; - -public class FreegeoipModule extends AbstractModule { - - @Override - protected void configure() { - bind(GeolocationProvider.class).to(FreegeoipProvider.class); - } -} diff --git a/app/com/edulify/modules/geolocation/providers/MaxmindModule.java b/app/com/edulify/modules/geolocation/providers/MaxmindModule.java deleted file mode 100644 index a640eb1..0000000 --- a/app/com/edulify/modules/geolocation/providers/MaxmindModule.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.edulify.modules.geolocation.providers; - -import com.edulify.modules.geolocation.GeolocationProvider; -import com.google.inject.AbstractModule; - -public class MaxmindModule extends AbstractModule { - - @Override - protected void configure() { - bind(GeolocationProvider.class).to(MaxmindProvider.class); - } -} diff --git a/app/com/edulify/modules/geolocation/providers/MaxmindProvider.java b/app/com/edulify/modules/geolocation/providers/MaxmindProvider.java deleted file mode 100644 index 179d718..0000000 --- a/app/com/edulify/modules/geolocation/providers/MaxmindProvider.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.edulify.modules.geolocation.providers; - -import com.edulify.modules.geolocation.Geolocation; -import com.edulify.modules.geolocation.GeolocationProvider; -import play.Configuration; -import play.libs.F; -import play.libs.concurrent.HttpExecution; -import play.libs.ws.WSClient; - -import javax.inject.Inject; -import javax.inject.Singleton; -import java.util.concurrent.CompletionStage; - -@Singleton -public class MaxmindProvider implements GeolocationProvider { - - private String license; - - private WSClient ws; - private Configuration configuration; - - @Inject - public MaxmindProvider(WSClient ws, Configuration configuration) { - this.ws = ws; - this.configuration = configuration; - this.license = this.configuration.getString("geolocation.maxmind.license"); - } - - @Override - public CompletionStage get(final String ip) { - String url = String.format("https://geoip.maxmind.com/a?l=%s&i=%s", license, ip); - return ws.url(url) - .get() - .thenApplyAsync(response -> { - if (response.getStatus() != 200) return null; - - String body = response.getBody(); - if ("(null),IP_NOT_FOUND".equals(body)) return null; - return body; - }, HttpExecution.defaultContext()) - .thenApplyAsync(body -> { - if (body == null) return Geolocation.empty(); - return new Geolocation(ip, body); - }, HttpExecution.defaultContext()); - } -} diff --git a/build.sbt b/build.sbt index 366802c..b998d90 100644 --- a/build.sbt +++ b/build.sbt @@ -1,67 +1,138 @@ -name := "geolocation" -version := "2.1.0" - -scalaVersion := "2.11.7" - -lazy val root = (project in file(".")).enablePlugins(PlayJava) - -libraryDependencies ++= Seq( - javaCore, - javaWs, - cache, - "org.mockito" % "mockito-core" % "2.0.43-beta" % Test, - "com.jayway.awaitility" % "awaitility" % "1.7.0" % Test +val javacSettings = Seq( + "-source", "1.8", + "-target", "1.8", + "-Xlint:deprecation", + "-Xlint:unchecked" ) -testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-q") - -organization := "com.edulify" - -organizationName := "Edulify.com" - -organizationHomepage := Some(new URL("https://edulify.com")) - -publishMavenStyle := true - -publishArtifact in Test := false - -pomIncludeRepository := { _ => false } - -publishTo := { - if (version.value.trim.endsWith("SNAPSHOT")) - Some(Resolver.sonatypeRepo("snapshots")) - else - Some("releases" at "https://oss.sonatype.org/service/local/staging/deploy/maven2") -} - -startYear := Some(2013) - -description := "This is a geolocation module for Playframework." - -licenses := Seq("The Apache Software License, Version 2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0.txt")) +lazy val commonSettings = Seq( + scalaVersion := "2.12.4", + organization := "com.edulify", + organizationName := "Edulify.com", + organizationHomepage := Some(new URL("https://edulify.com")), + startYear := Some(2013), + licenses := Seq("The Apache Software License, Version 2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0.txt")), + homepage := Some(url("http://edulify.github.io/play-geolocation-module.edulify.com/")), + pomExtra := + + https://github.com/edulify/play-geolocation-module.edulify.com + scm:git:git@github.com:edulify/play-geolocation-module.edulify.com.git + scm:git:https://github.com/edulify/play-geolocation-module.edulify.com.git + + + + megazord + Megazord + contact [at] edulify.com + https://github.com/megazord + + + ranierivalenca + Ranieri Valença + ranierivalenca [at] edulify.com + https://github.com/ranierivalenca + +, + publishMavenStyle := true, + pomIncludeRepository := { _ => false }, + publishTo := { Some(if (isSnapshot.value) Resolver.sonatypeRepo("snapshots") else "releases" at "https://oss.sonatype.org/service/local/staging/deploy/maven2") }, + scalacOptions := Seq("-feature", "-deprecation"), + javacOptions in (Compile, doc) ++= javacSettings, + javacOptions in Test ++= javacSettings, + javacOptions in IntegrationTest ++= javacSettings, + testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-q", "-a"), +) -homepage := Some(url("http://edulify.github.io/play-geolocation-module.edulify.com/")) +val disableDocs = Seq[Setting[_]]( + sources in (Compile, doc) := Seq.empty, + publishArtifact in (Compile, packageDoc) := false +) -pomExtra := - - https://github.com/edulify/play-geolocation-module.edulify.com - scm:git:git@github.com:edulify/play-geolocation-module.edulify.com.git - scm:git:https://github.com/edulify/play-geolocation-module.edulify.com.git - - - - megazord - Megazord - contact [at] edulify.com - https://github.com/megazord - - - ranierivalenca - Ranieri Valença - ranierivalenca [at] edulify.com - https://github.com/ranierivalenca - - +val disablePublishing = Seq[Setting[_]]( + publishArtifact := false, + // The above is enough for Maven repos but it doesn't prevent publishing of ivy.xml files + publish := {}, + publishLocal := {} +) -scalacOptions := Seq("-feature", "-deprecation") +val playWsStandalone = "com.typesafe.play" %% "play-ahc-ws-standalone" % "1.1.3" +val junit = "junit" % "junit" % "4.12" % Test +val junitInterface = "com.novocode" % "junit-interface" % "0.11" % Test +val mockito = "org.mockito" % "mockito-core" % "2.11.0" % Test +val hamcrest = "org.hamcrest" % "hamcrest-core" % "1.3" % Test + +lazy val root = (project in file(".")) + .enablePlugins(PlayMinimalJava) + .aggregate(geolocation, `geolocation-guice`, freegeoip, `freegeoip-guice`, `maxmind-geoip2web`, `maxmind-geoip2web-guice`, `javaSample`) + .settings(commonSettings, disablePublishing, disableDocs, name := "edulify-geolocation") + +lazy val geolocation = (project in file("geolocation")) + .settings( + name := "geolocation", + description := "This is a geolocation module for Playframework.", + commonSettings + ) + .settings(libraryDependencies ++= Seq( + component("play-cache"), + ehcache % Test, // Should be removed with deprecated GeolocationService and GeolocationCache + junit, + junitInterface, + mockito + )) + +lazy val `geolocation-guice` = (project in file("geolocation-guice")) + .settings(commonSettings) + .settings(libraryDependencies ++= Seq( + guice, + hamcrest + )) + .dependsOn(geolocation % "test->test;compile->compile") + +lazy val freegeoip = (project in file("freegeoip")) + .settings(commonSettings) + .settings(libraryDependencies ++= Seq( + playWsStandalone + )) + .dependsOn(geolocation) + +lazy val `freegeoip-guice` = (project in file("freegeoip-guice")) + .settings(commonSettings) + .settings(libraryDependencies ++= Seq( + component("play-server") % Test, + javaCore % Test, + component("play-test") % Test, + component("play-ahc-ws") % Test, + component("play-akka-http-server") % Test + )) + .dependsOn(freegeoip, `geolocation-guice` % "test->test;compile->compile") + +lazy val `maxmind-geoip2web` = (project in file("maxmind-geoip2web")) + .settings(commonSettings) + .settings(libraryDependencies ++= Seq( + playWsStandalone + )) + .dependsOn(geolocation) + +lazy val `maxmind-geoip2web-guice` = (project in file("maxmind-geoip2web-guice")) + .settings(commonSettings) + .settings(libraryDependencies ++= Seq( + component("play-server") % Test, + javaCore % Test, + component("play-ahc-ws") % Test, + component("play-akka-http-server") % Test + )) + .dependsOn(`maxmind-geoip2web`, `geolocation-guice` % "test->test;compile->compile") + +lazy val `javaSample` = (project in file("sample/java")) + .enablePlugins(PlayMinimalJava) + .settings(routesGenerator := InjectedRoutesGenerator) + .settings( + commonSettings, + disableDocs, + disablePublishing + ) + .settings(libraryDependencies ++= Seq( + ehcache, ws + )) + .dependsOn(`freegeoip-guice`) diff --git a/conf/application.conf b/conf/application.conf deleted file mode 100644 index 85eb438..0000000 --- a/conf/application.conf +++ /dev/null @@ -1,8 +0,0 @@ -geolocation { - provider = "com.edulify.modules.geolocation.providers.FreegeoipProvider" - cache { - on = true - ttl = 100 - } - maxmind.license = "abc123" -} \ No newline at end of file diff --git a/freegeoip-guice/src/main/java/com/edulify/modules/geolocation/FreegeoipModule.java b/freegeoip-guice/src/main/java/com/edulify/modules/geolocation/FreegeoipModule.java new file mode 100644 index 0000000..d63decb --- /dev/null +++ b/freegeoip-guice/src/main/java/com/edulify/modules/geolocation/FreegeoipModule.java @@ -0,0 +1,31 @@ +package com.edulify.modules.geolocation; + +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.typesafe.config.Config; +import play.Environment; +import play.libs.ws.StandaloneWSClient; + +import java.util.concurrent.Executor; + +public class FreegeoipModule extends AbstractModule { + + private final Environment environment; + private final Config config; + + public FreegeoipModule(Environment environment, Config config) + { + this.environment = environment; + this.config = config; + } + + @Override + protected void configure() { + install(new GeolocationModule(environment, config)); + } + + @Provides + public GeolocationProvider geolocationProvider(StandaloneWSClient wsClient, Executor wsExecutor) { + return new FreegeoipProvider(wsClient, wsExecutor); + } +} diff --git a/freegeoip-guice/src/main/resources/reference.conf b/freegeoip-guice/src/main/resources/reference.conf new file mode 100644 index 0000000..af0d301 --- /dev/null +++ b/freegeoip-guice/src/main/resources/reference.conf @@ -0,0 +1 @@ +play.modules.enabled += "com.edulify.modules.geolocation.FreegeoipModule" diff --git a/freegeoip-guice/src/test/java/com/edulify/modules/geolocation/FreegeoipProviderTest.java b/freegeoip-guice/src/test/java/com/edulify/modules/geolocation/FreegeoipProviderTest.java new file mode 100644 index 0000000..c4895fe --- /dev/null +++ b/freegeoip-guice/src/test/java/com/edulify/modules/geolocation/FreegeoipProviderTest.java @@ -0,0 +1,96 @@ +package com.edulify.modules.geolocation; + +import com.google.common.collect.ImmutableMap; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import play.libs.ws.StandaloneWSClient; +import play.libs.ws.StandaloneWSRequest; +import play.libs.ws.WSClient; +import play.routing.RoutingDsl; +import play.server.Server; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static play.mvc.Results.ok; + +public class FreegeoipProviderTest { + + private Server server; + + private StandaloneWSClient ws; + + private GeolocationProvider freeGeoIpProvider; + private ExecutorService executor; + + @Before + public void setUp() throws Exception { + server = Server.forRouter((components) -> RoutingDsl.fromComponents(components) + .GET("/ip/:ipAddress").routeTo((String ipAddress) -> { + + final String countryCode = ImmutableMap.of( + "185.129.62.62", "{\"ip\":\"185.129.62.62\",\"country_code\":\"DK\",\"country_name\":\"Denmark\",\"region_code\":\"\",\"region_name\":\"\",\"city\":\"\",\"zip_code\":\"\",\"time_zone\":\"Europe/Copenhagen\",\"latitude\":55.7123,\"longitude\":12.0564,\"metro_code\":0}", + "88.85.80.69", "{\"ip\":\"88.85.80.69\",\"country_code\":\"NL\",\"country_name\":\"Netherlands\",\"region_code\":\"\",\"region_name\":\"\",\"city\":\"\",\"zip_code\":\"\",\"time_zone\":\"Europe/Amsterdam\",\"latitude\":52.3824,\"longitude\":4.8995,\"metro_code\":0}") + .entrySet().stream() + .filter(entry -> entry.getKey().equals(ipAddress)) + .map(Map.Entry::getValue) + .findAny().orElse("{\"ip\":\""+ipAddress+"\",\"country_code\":\"\",\"country_name\":\"\",\"region_code\":\"\",\"region_name\":\"\",\"city\":\"\",\"zip_code\":\"\",\"time_zone\":\"\",\"latitude\":0,\"longitude\":0,\"metro_code\":0}"); + + return ok(countryCode); + }) + .build()); + + WSClient wsClient = play.test.WSTestClient.newClient(server.httpPort()); + ws = new StandaloneWSClient() + { + @Override + public Object getUnderlying() + { + return wsClient.getUnderlying(); + } + + @Override + public StandaloneWSRequest url(String url) + { + return wsClient.url(url); + } + + @Override + public void close() throws IOException + { + wsClient.close(); + } + }; + + executor = Executors.newSingleThreadExecutor(); + freeGeoIpProvider = new FreegeoipProvider(ws, executor, "/ip/%s"); + } + + @After + public void tearDown() throws Exception { + try { + ws.close(); + } + finally { + executor.shutdown(); + server.stop(); + } + } + + @Test + public void get() throws Exception { + Geolocation geolocation = freeGeoIpProvider.get("185.129.62.62").toCompletableFuture().get(10, TimeUnit.SECONDS); + assertEquals("DK", geolocation.getCountryCode()); + + geolocation = freeGeoIpProvider.get("88.85.80.69").toCompletableFuture().get(10, TimeUnit.SECONDS); + assertEquals("NL", geolocation.getCountryCode()); + + geolocation = freeGeoIpProvider.get("172.16.1.5").toCompletableFuture().get(10, TimeUnit.SECONDS); + assertEquals("", geolocation.getCountryCode()); + } +} diff --git a/app/com/edulify/modules/geolocation/providers/FreegeoipProvider.java b/freegeoip/src/main/java/com/edulify/modules/geolocation/FreegeoipProvider.java similarity index 71% rename from app/com/edulify/modules/geolocation/providers/FreegeoipProvider.java rename to freegeoip/src/main/java/com/edulify/modules/geolocation/FreegeoipProvider.java index 7c9502d..d21dc11 100644 --- a/app/com/edulify/modules/geolocation/providers/FreegeoipProvider.java +++ b/freegeoip/src/main/java/com/edulify/modules/geolocation/FreegeoipProvider.java @@ -1,39 +1,44 @@ -package com.edulify.modules.geolocation.providers; +package com.edulify.modules.geolocation; -import com.edulify.modules.geolocation.Geolocation; -import com.edulify.modules.geolocation.GeolocationProvider; import com.fasterxml.jackson.databind.JsonNode; -import play.libs.concurrent.HttpExecution; -import play.libs.ws.WSClient; +import play.libs.Json; +import play.libs.ws.StandaloneWSClient; -import javax.inject.Inject; -import javax.inject.Singleton; import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; -@Singleton public class FreegeoipProvider implements GeolocationProvider { - private WSClient ws; + private StandaloneWSClient ws; - @Inject - public FreegeoipProvider(WSClient ws) { + private final String urlFormat; + + private final Executor threadToRunOn; + + public FreegeoipProvider(StandaloneWSClient ws, Executor threadToRunOn, String urlFormat) { this.ws = ws; + this.urlFormat = urlFormat; + this.threadToRunOn = threadToRunOn; + } + + FreegeoipProvider(StandaloneWSClient ws, Executor threadToRunOn) { + this(ws, threadToRunOn, "http://freegeoip.net/json/%s"); } @Override public CompletionStage get(String ip) { - String url = String.format("http://freegeoip.net/json/%s", ip); + String url = String.format(urlFormat, ip); return ws.url(url) .get() .thenApplyAsync(response -> { if (response.getStatus() != 200) return null; if (response.getBody().contains("not found")) return null; - return response.asJson(); - }, HttpExecution.defaultContext()) + return Json.parse(response.getBodyAsBytes().toArray()); + }, threadToRunOn) .thenApplyAsync(json -> { if (json == null) return Geolocation.empty(); return asGeolocation(json); - }, HttpExecution.defaultContext()); + }, threadToRunOn); } private Geolocation asGeolocation(JsonNode json) { diff --git a/geolocation-guice/src/main/java/com/edulify/modules/geolocation/GeolocationModule.java b/geolocation-guice/src/main/java/com/edulify/modules/geolocation/GeolocationModule.java new file mode 100644 index 0000000..612e8a8 --- /dev/null +++ b/geolocation-guice/src/main/java/com/edulify/modules/geolocation/GeolocationModule.java @@ -0,0 +1,31 @@ +package com.edulify.modules.geolocation; + +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.typesafe.config.Config; +import play.Environment; +import play.cache.AsyncCacheApi; + +import javax.inject.Named; + +public class GeolocationModule extends AbstractModule { + + private final Environment environment; + private final Config config; + + public GeolocationModule(Environment environment, Config config) + { + this.environment = environment; + this.config = config; + } + + @Override + protected void configure() { } + + @Named("CachedGeolocationProvider") + @Provides + public GeolocationProvider cachedGeolocationProvider(GeolocationProvider provider, AsyncCacheApi cacheApi) + { + return new CachedProvider(cacheApi, config.getInt("geolocation.cache.ttl"), provider); + } +} diff --git a/geolocation-guice/src/test/java/com/edulify/modules/geolocation/GeolocationCacheTest.java b/geolocation-guice/src/test/java/com/edulify/modules/geolocation/GeolocationCacheTest.java new file mode 100644 index 0000000..23b8174 --- /dev/null +++ b/geolocation-guice/src/test/java/com/edulify/modules/geolocation/GeolocationCacheTest.java @@ -0,0 +1,52 @@ +package com.edulify.modules.geolocation; + +import com.google.common.collect.ImmutableMap; +import org.junit.Test; +import play.Configuration; +import play.cache.CacheApi; + +import static org.mockito.Mockito.*; + +/** + * @deprecated Deprecated as of 2.2.0. Source should be removed. + */ +@Deprecated +public class GeolocationCacheTest { + + private final String ipAddress = "192.30.252.129"; + private final String countryCode = "BR"; + + @Test + public void shouldAddGeolocationToCacheWhenCacheIsOn() { + final CacheApi cacheApi = mock(CacheApi.class); + final GeolocationCache geolocationCache = new GeolocationCache( + new Configuration(ImmutableMap.of( + "geolocation.cache.on", true, + "geolocation.cache.ttl", 5000L + )), + cacheApi + ); + + final Geolocation geolocation = new Geolocation(ipAddress, countryCode); + geolocationCache.set(geolocation); + + verify(cacheApi).set(anyString(), eq(geolocation), eq(5000)); + } + + @Test + public void shouldNotAddGeolocationToCacheWhenCacheIsOff() { + final CacheApi cacheApi = mock(CacheApi.class); + final GeolocationCache geolocationCache = new GeolocationCache( + new Configuration(ImmutableMap.of( + "geolocation.cache.on", false, + "geolocation.cache.ttl", 5000L + )), + cacheApi + ); + + final Geolocation geolocation = new Geolocation(ipAddress, countryCode); + geolocationCache.set(geolocation); + + verify(cacheApi, never()).set(anyString(), eq(geolocation), eq(5000)); + } +} diff --git a/geolocation-guice/src/test/java/com/edulify/modules/geolocation/GeolocationServiceTest.java b/geolocation-guice/src/test/java/com/edulify/modules/geolocation/GeolocationServiceTest.java new file mode 100644 index 0000000..ecef81c --- /dev/null +++ b/geolocation-guice/src/test/java/com/edulify/modules/geolocation/GeolocationServiceTest.java @@ -0,0 +1,35 @@ +package com.edulify.modules.geolocation; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import static org.mockito.Mockito.*; + +/** + * @deprecated Deprecated as of 2.2.0. Source should be removed. + */ +@Deprecated +public class GeolocationServiceTest { + + @Test + public void shouldGetAGeolocationForAGivenIp() throws Exception { + + final GeolocationProvider provider = mock(GeolocationProvider.class); + + String ipAddress = "192.30.252.129"; + String countryCode = "BR"; + final Geolocation geolocation = new Geolocation(ipAddress, countryCode); + when(provider.get(ipAddress)).thenReturn(CompletableFuture.completedFuture(geolocation)); + final GeolocationCache cache = mock(GeolocationCache.class); + + GeolocationService service = new GeolocationService(provider, cache); + Geolocation target = service.getGeolocation(ipAddress).toCompletableFuture().get(1, TimeUnit.SECONDS); + + verify(cache, timeout(100).times(1)).set(geolocation); + + Assert.assertSame(target, geolocation); + } +} diff --git a/geolocation/src/main/java/com/edulify/modules/geolocation/CachedProvider.java b/geolocation/src/main/java/com/edulify/modules/geolocation/CachedProvider.java new file mode 100644 index 0000000..ca7ef4c --- /dev/null +++ b/geolocation/src/main/java/com/edulify/modules/geolocation/CachedProvider.java @@ -0,0 +1,25 @@ +package com.edulify.modules.geolocation; + +import play.cache.AsyncCacheApi; + +import java.util.concurrent.CompletionStage; + +public class CachedProvider implements GeolocationProvider +{ + private final AsyncCacheApi cache; + + private final int timeToLive; + + private final GeolocationProvider provider; + + public CachedProvider(AsyncCacheApi cache, int timeToLive, GeolocationProvider provider) { + this.cache = cache; + this.timeToLive = timeToLive; + this.provider = provider; + } + + @Override + public CompletionStage get(String ip) { + return cache.getOrElseUpdate(ip, () -> provider.get(ip), timeToLive); + } +} diff --git a/app/com/edulify/modules/geolocation/Geolocation.java b/geolocation/src/main/java/com/edulify/modules/geolocation/Geolocation.java similarity index 100% rename from app/com/edulify/modules/geolocation/Geolocation.java rename to geolocation/src/main/java/com/edulify/modules/geolocation/Geolocation.java diff --git a/app/com/edulify/modules/geolocation/GeolocationCache.java b/geolocation/src/main/java/com/edulify/modules/geolocation/GeolocationCache.java similarity index 89% rename from app/com/edulify/modules/geolocation/GeolocationCache.java rename to geolocation/src/main/java/com/edulify/modules/geolocation/GeolocationCache.java index b25bd21..693e1fe 100644 --- a/app/com/edulify/modules/geolocation/GeolocationCache.java +++ b/geolocation/src/main/java/com/edulify/modules/geolocation/GeolocationCache.java @@ -6,6 +6,13 @@ import javax.inject.Inject; import javax.inject.Singleton; + +/** + * The GeolocationCache API. + * + * @deprecated Deprecated as of 2.2.0. Use {@link CachedProvider}. + */ +@Deprecated @Singleton public class GeolocationCache { diff --git a/app/com/edulify/modules/geolocation/GeolocationProvider.java b/geolocation/src/main/java/com/edulify/modules/geolocation/GeolocationProvider.java similarity index 100% rename from app/com/edulify/modules/geolocation/GeolocationProvider.java rename to geolocation/src/main/java/com/edulify/modules/geolocation/GeolocationProvider.java diff --git a/app/com/edulify/modules/geolocation/GeolocationService.java b/geolocation/src/main/java/com/edulify/modules/geolocation/GeolocationService.java similarity index 60% rename from app/com/edulify/modules/geolocation/GeolocationService.java rename to geolocation/src/main/java/com/edulify/modules/geolocation/GeolocationService.java index b3ba9ca..6e6bb65 100644 --- a/app/com/edulify/modules/geolocation/GeolocationService.java +++ b/geolocation/src/main/java/com/edulify/modules/geolocation/GeolocationService.java @@ -3,9 +3,14 @@ import play.libs.concurrent.HttpExecution; import javax.inject.Inject; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +/** + * Wrapper for {@link GeolocationProvider} to cache results. + * + * @deprecated Deprecated as of 2.2.0. Use {@link CachedProvider}. + */ +@Deprecated public final class GeolocationService { private GeolocationProvider provider; @@ -18,11 +23,9 @@ public GeolocationService(GeolocationProvider provider, GeolocationCache cache) } public CompletionStage getGeolocation(String ip) { - Geolocation geolocation = cache.get(ip); - if (geolocation != null) return CompletableFuture.completedFuture(geolocation); + final CompletionStage stage = provider.get(ip); + stage.thenAcceptAsync(cache::set, HttpExecution.defaultContext()); - CompletionStage promise = provider.get(ip); - promise.thenAcceptAsync(cache::set, HttpExecution.defaultContext()); - return promise; + return stage; } } diff --git a/geolocation/src/main/resources/reference.conf b/geolocation/src/main/resources/reference.conf new file mode 100644 index 0000000..8ec375f --- /dev/null +++ b/geolocation/src/main/resources/reference.conf @@ -0,0 +1,4 @@ +geolocation.cache { + on = true + ttl = 100 +} diff --git a/geolocation/src/test/java/com/edulify/modules/geolocation/CachedProviderTest.java b/geolocation/src/test/java/com/edulify/modules/geolocation/CachedProviderTest.java new file mode 100644 index 0000000..f6d9f43 --- /dev/null +++ b/geolocation/src/test/java/com/edulify/modules/geolocation/CachedProviderTest.java @@ -0,0 +1,87 @@ +package com.edulify.modules.geolocation; + +import akka.Done; +import org.junit.Test; +import play.cache.AsyncCacheApi; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; + +import static org.mockito.Mockito.*; + +public class CachedProviderTest { + @Test + public void get() throws Exception { + + GeolocationProvider provider = mock(GeolocationProvider.class); + when(provider.get("any address")).thenReturn(CompletableFuture.completedFuture(Geolocation.empty())); + + AsyncCacheApi cacheApi = new StubCacheApi(); + + GeolocationProvider target = new CachedProvider(cacheApi, 100, provider); + + target.get("any address").toCompletableFuture().get(1, TimeUnit.SECONDS); + target.get("any address").toCompletableFuture().get(1, TimeUnit.SECONDS); + verify(provider, times(1)).get("any address"); + } + + private class StubCacheApi implements AsyncCacheApi { + + private Map cache = new HashMap<>(1); + + @SuppressWarnings("unchecked") + @Override + public CompletionStage getOrElseUpdate(String key, Callable> block, int expiration) { + if (cache.containsKey(key)) { + return CompletableFuture.completedFuture((T) Geolocation.empty()); // We test only method invocations, not method call results so we don't need any value here. + } + cache.put(key, key); + // See Scala.asScalaWithFuture(block) + try { + return block.call(); + } catch (RuntimeException | Error e) { + throw e; + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + // ---- Rest are stubs. Just to implement interface. ISP violation. + + @Override + public CompletionStage get(String key) { + return null; // Testing white box. No need to implement all methods. + } + + @Override + public CompletionStage getOrElseUpdate( + String key, Callable> block + ) { + return null; // Testing white box. No need to implement all methods. + } + + @Override + public CompletionStage set(String key, Object value, int expiration) { + return null; // Testing white box. No need to implement all methods. + } + + @Override + public CompletionStage set(String key, Object value) { + return null; // Testing white box. No need to implement all methods. + } + + @Override + public CompletionStage remove(String key) { + return null; // Testing white box. No need to implement all methods. + } + + @Override + public CompletionStage removeAll() { + return null; // Testing white box. No need to implement all methods. + } + } +} diff --git a/maxmind-geoip2web-guice/src/main/java/com/edulify/modules/geolocation/MaxmindModule.java b/maxmind-geoip2web-guice/src/main/java/com/edulify/modules/geolocation/MaxmindModule.java new file mode 100644 index 0000000..29894e6 --- /dev/null +++ b/maxmind-geoip2web-guice/src/main/java/com/edulify/modules/geolocation/MaxmindModule.java @@ -0,0 +1,31 @@ +package com.edulify.modules.geolocation; + +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.typesafe.config.Config; +import play.Environment; +import play.libs.ws.StandaloneWSClient; + +import java.util.concurrent.Executor; + +public class MaxmindModule extends AbstractModule { + + private final Environment environment; + private final Config config; + + public MaxmindModule(Environment environment, Config config) + { + this.environment = environment; + this.config = config; + } + + @Override + protected void configure() { + install(new GeolocationModule(environment, config)); + } + + @Provides + public GeolocationProvider geolocationProvider(StandaloneWSClient wsClient, Executor wsExecutor) { + return new MaxmindProvider(wsClient, config.getString("geolocation.maxmind.license"), wsExecutor); + } +} diff --git a/maxmind-geoip2web-guice/src/main/resources/reference.conf b/maxmind-geoip2web-guice/src/main/resources/reference.conf new file mode 100644 index 0000000..5d1d34c --- /dev/null +++ b/maxmind-geoip2web-guice/src/main/resources/reference.conf @@ -0,0 +1,3 @@ +play.modules.enabled += "com.edulify.modules.geolocation.MaxmindModule" + +geolocation.maxmind.license = ~ diff --git a/maxmind-geoip2web-guice/src/test/java/com/edulify/modules/geolocation/MaxmindProviderTest.java b/maxmind-geoip2web-guice/src/test/java/com/edulify/modules/geolocation/MaxmindProviderTest.java new file mode 100644 index 0000000..1eb74cb --- /dev/null +++ b/maxmind-geoip2web-guice/src/test/java/com/edulify/modules/geolocation/MaxmindProviderTest.java @@ -0,0 +1,96 @@ +package com.edulify.modules.geolocation; + +import com.google.common.collect.ImmutableMap; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import play.libs.ws.StandaloneWSClient; +import play.libs.ws.StandaloneWSRequest; +import play.libs.ws.WSClient; +import play.mvc.Http; +import play.routing.RoutingDsl; +import play.server.Server; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static play.mvc.Results.ok; + +public class MaxmindProviderTest { + + private Server server; + + private StandaloneWSClient ws; + + private GeolocationProvider maxmindProvider; + private ExecutorService executor; + + @Before + public void setUp() throws Exception { + server = Server.forRouter((components) -> RoutingDsl.fromComponents(components) + .GET("/ip").routeTo(() -> { + final Http.Context context = Http.Context.current.get();// Deprecated hack because Java can't do string interpolation. + + final String countryCode = ImmutableMap.of("ip1", "country1", "ip2", "country2").entrySet().stream() + .filter(entry -> entry.getKey().equals(context.request().getQueryString("i"))) + .map(Map.Entry::getValue) + .findAny().orElse("(null),IP_NOT_FOUND"); + + return ok(countryCode); + }) + .build()); + + WSClient wsClient = play.test.WSTestClient.newClient(server.httpPort()); + ws = new StandaloneWSClient() + { + @Override + public Object getUnderlying() + { + return wsClient.getUnderlying(); + } + + @Override + public StandaloneWSRequest url(String url) + { + return wsClient.url(url); + } + + @Override + public void close() throws IOException + { + wsClient.close(); + } + }; + + executor = Executors.newSingleThreadExecutor(); + maxmindProvider = new MaxmindProvider(ws, "any test string", executor, "/ip?l=%s"); + } + + @After + public void tearDown() throws Exception { + try { + ws.close(); + } + finally { + executor.shutdown(); + server.stop(); + } + } + + @Test + public void get() throws Exception { + Geolocation geolocation = maxmindProvider.get("ip1").toCompletableFuture().get(10, TimeUnit.SECONDS); + assertEquals("country1", geolocation.getCountryCode()); + + geolocation = maxmindProvider.get("ip2").toCompletableFuture().get(10, TimeUnit.SECONDS); + assertEquals("country2", geolocation.getCountryCode()); + + geolocation = maxmindProvider.get("not at list").toCompletableFuture().get(10, TimeUnit.SECONDS); + assertEquals("", geolocation.getCountryCode()); + } + +} diff --git a/maxmind-geoip2web/src/main/java/com/edulify/modules/geolocation/MaxmindProvider.java b/maxmind-geoip2web/src/main/java/com/edulify/modules/geolocation/MaxmindProvider.java new file mode 100644 index 0000000..dcfa183 --- /dev/null +++ b/maxmind-geoip2web/src/main/java/com/edulify/modules/geolocation/MaxmindProvider.java @@ -0,0 +1,47 @@ +package com.edulify.modules.geolocation; + +import play.libs.ws.StandaloneWSClient; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; + +public class MaxmindProvider implements GeolocationProvider { + + private final Executor threadToRunOn; + + private final StandaloneWSClient ws; + + private final String license; + + private final String baseUrl; + + MaxmindProvider(StandaloneWSClient ws, String license, Executor threadToRunOn, String urlFormat) { + this.ws = ws; + this.license = license; + this.threadToRunOn = threadToRunOn; + this.baseUrl = urlFormat; + } + + public MaxmindProvider(StandaloneWSClient ws, String license, Executor threadToRunOn) { + this(ws, license, threadToRunOn, "https://geoip.maxmind.com/a?l=%s"); + } + + @Override + public CompletionStage get(final String ip) { + return ws.url(baseUrl) // WSRequest is not a value-object. Adding query parameters will modify it's state instead of + .addQueryParameter("l", license) // creating a new instance. That is why we FORCED to store all request + .addQueryParameter("i", ip) // parameters and re-creating request each time rather re-using same instance. + .get() + .thenApplyAsync(response -> { + if (response.getStatus() != 200) return null; + + String body = response.getBody(); + if ("(null),IP_NOT_FOUND".equals(body)) return null; + return body; + }, threadToRunOn) + .thenApplyAsync(body -> { + if (body == null) return Geolocation.empty(); + return new Geolocation(ip, body); + }, threadToRunOn); + } +} diff --git a/project/build.properties b/project/build.properties index 43b8278..9abea12 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.11 +sbt.version=1.0.3 diff --git a/project/plugins.sbt b/project/plugins.sbt index 67531ab..697c787 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,6 +2,6 @@ logLevel := Level.Warn // Use the Play sbt plugin for Play projects -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % System.getProperty("play.version", "2.5.9")) +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % System.getProperty("play.version", "2.6.7")) -addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.3") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0") diff --git a/sample/java/.gitignore b/sample/java/.gitignore deleted file mode 100644 index 8570daf..0000000 --- a/sample/java/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -logs -project/project -project/target -target -tmp -.history -dist -/.idea -/*.iml -/out -/.idea_modules -/.classpath -/.project -/RUNNING_PID -/.settings diff --git a/sample/java/app/com/edulify/modules/ws/standalone/StandaloneAhcWSModule.java b/sample/java/app/com/edulify/modules/ws/standalone/StandaloneAhcWSModule.java new file mode 100644 index 0000000..5d14a1d --- /dev/null +++ b/sample/java/app/com/edulify/modules/ws/standalone/StandaloneAhcWSModule.java @@ -0,0 +1,46 @@ +package com.edulify.modules.ws.standalone; + +import akka.stream.Materializer; +import play.api.Configuration; +import play.api.Environment; +import play.api.inject.Binding; +import play.api.inject.Module; +import play.libs.ws.StandaloneWSClient; +import play.libs.ws.ahc.StandaloneAhcWSClient; +import play.shaded.ahc.org.asynchttpclient.AsyncHttpClient; +import scala.collection.Seq; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; + +/** + * Play not supplies such a module by default. + */ +public class StandaloneAhcWSModule extends Module +{ + + @Override + public Seq> bindings(Environment environment, Configuration configuration) { + return seq( + // AsyncHttpClientProvider is added by the Scala API + bind(StandaloneWSClient.class).toProvider(StandaloneAhcWSModule.AhcWSClientProvider.class) + ); + } + + @Singleton + public static class AhcWSClientProvider implements Provider + { + private final StandaloneWSClient client; + + @Inject + public AhcWSClientProvider(AsyncHttpClient asyncHttpClient, Materializer materializer) { + client = new StandaloneAhcWSClient(asyncHttpClient, materializer); + } + + @Override + public StandaloneWSClient get() { + return client; + } + } +} diff --git a/sample/java/app/controllers/Application.java b/sample/java/app/controllers/Application.java index dd976fc..188f3d7 100644 --- a/sample/java/app/controllers/Application.java +++ b/sample/java/app/controllers/Application.java @@ -1,14 +1,14 @@ package controllers; import com.edulify.modules.geolocation.Geolocation; -import play.mvc.*; - -import views.html.*; +import com.edulify.modules.geolocation.GeolocationProvider; +import play.mvc.Controller; +import play.mvc.Result; +import views.html.geoData; +import views.html.index; import javax.inject.Inject; - -import com.edulify.modules.geolocation.GeolocationService; - +import javax.inject.Named; import java.util.concurrent.CompletionStage; import java.util.function.Function; @@ -17,11 +17,11 @@ public class Application extends Controller { - private GeolocationService geolocationService; + private GeolocationProvider geolocationService; @Inject - public Application(GeolocationService geolocationService) { - this.geolocationService = geolocationService; + public Application(@Named("CachedGeolocationProvider") GeolocationProvider geolocationProvider) { + this.geolocationService = geolocationProvider; } public Result index() { @@ -29,17 +29,17 @@ public Result index() { } public CompletionStage getCountry(final String addr) { - return geolocationService.getGeolocation(addr) + return geolocationService.get(addr) .thenApplyAsync(geolocation -> ok(index.render(formHeader(geolocation, addr, Geolocation::getCountryName)))); } public CompletionStage getCountryCode(final String addr) { - return geolocationService.getGeolocation(addr) + return geolocationService.get(addr) .thenApplyAsync(geolocation -> ok(index.render(formHeader(geolocation, addr)))); } public CompletionStage getGeolocation(final String addr) { - return geolocationService.getGeolocation(addr) + return geolocationService.get(addr) .thenApplyAsync(geolocation -> ok(geoData.render(geolocation, formHeader(geolocation, addr)))); } diff --git a/sample/java/build.sbt b/sample/java/build.sbt deleted file mode 100644 index b6dac72..0000000 --- a/sample/java/build.sbt +++ /dev/null @@ -1,19 +0,0 @@ -name := "geolocation-java-sample" - -version := "1.0-SNAPSHOT" - -lazy val root = (project in file(".")).enablePlugins(PlayJava) - -scalaVersion := "2.11.7" - -routesGenerator := InjectedRoutesGenerator - -libraryDependencies ++= Seq( - // Add your project dependencies here, - javaCore, - "com.edulify" %% "geolocation" % "2.1.0" -) - -resolvers ++= Seq( - Resolver.url("Edulify Repository", url("https://edulify.github.io/modules/releases/"))(Resolver.ivyStylePatterns) -) diff --git a/sample/java/conf/application.conf b/sample/java/conf/application.conf index 1640f63..ac941b0 100644 --- a/sample/java/conf/application.conf +++ b/sample/java/conf/application.conf @@ -5,18 +5,13 @@ # ~~~~~ # The secret key is used to secure cryptographics functions. # If you deploy your application to several instances be sure to use the same key! -play.crypto.secret="i[7Ve:r2mSr`PKm1x@kVYm0XfqW[vLarVbjGuP/Q8I8p@iZ8RLVPSiq@6ZElMyEr" +play.http.secret.key="i[7Ve:r2mSr`PKm1x@kVYm0XfqW[vLarVbjGuP/Q8I8p@iZ8RLVPSiq@6ZElMyEr" # The application languages # ~~~~~ -application.langs="en" +play.i18n.langs=["en"] -play.modules.enabled += "com.edulify.modules.geolocation.providers.FreegeoipModule" - -geolocation { - timeout = 1s - cache { - on = true - ttl = 10s - } -} \ No newline at end of file +play.modules { + enabled += "com.edulify.modules.geolocation.FreegeoipModule" + enabled += "com.edulify.modules.ws.standalone.StandaloneAhcWSModule" +} diff --git a/sample/java/conf/reference.conf b/sample/java/conf/reference.conf deleted file mode 100644 index 77aaa62..0000000 --- a/sample/java/conf/reference.conf +++ /dev/null @@ -1,5 +0,0 @@ -play { - modules { - enabled += "com.edulify.modules.geolocation.providers.FreegeoipModule" - } -} diff --git a/sample/java/project/build.properties b/sample/java/project/build.properties deleted file mode 100644 index 817bc38..0000000 --- a/sample/java/project/build.properties +++ /dev/null @@ -1 +0,0 @@ -sbt.version=0.13.9 diff --git a/sample/java/project/plugins.sbt b/sample/java/project/plugins.sbt deleted file mode 100644 index fbb149e..0000000 --- a/sample/java/project/plugins.sbt +++ /dev/null @@ -1,9 +0,0 @@ -// Comment to get more information during initialization -logLevel := Level.Warn - -// The Typesafe repository -resolvers += Resolver.typesafeRepo("releases") - -// Use the Play sbt plugin for Play projects -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % System.getProperty("play.version", "2.5.9")) - diff --git a/test/com/edulify/modules/geolocation/GeolocationCacheTest.java b/test/com/edulify/modules/geolocation/GeolocationCacheTest.java deleted file mode 100644 index d7fd3ff..0000000 --- a/test/com/edulify/modules/geolocation/GeolocationCacheTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.edulify.modules.geolocation; - -import org.hamcrest.CoreMatchers; -import org.junit.Assert; -import org.junit.Test; -import play.Application; -import play.test.Helpers; - -import java.util.HashMap; -import java.util.Map; - -public class GeolocationCacheTest { - - private final String ipAddress = "192.30.252.129"; - private final String countryCode = "BR"; - - @Test - public void shouldAddGeolocationToCacheWhenCacheIsOn() { - Application application = getApplication(true); - - Helpers.running(application, () -> { - Geolocation geolocation = new Geolocation(ipAddress, countryCode); - GeolocationCache cache = application.injector().instanceOf(GeolocationCache.class); - - cache.set(geolocation); - Assert.assertThat(cache.get(ipAddress), CoreMatchers.notNullValue()); - }); - } - - @Test - public void shouldNotAddGeolocationToCacheWhenCacheIsOff() { - Application application = getApplication(false); - - Helpers.running(application, () -> { - Geolocation geolocation = new Geolocation(ipAddress, countryCode); - GeolocationCache cache = application.injector().instanceOf(GeolocationCache.class); - - cache.set(geolocation); - Assert.assertThat(cache.get(ipAddress), CoreMatchers.nullValue()); - }); - } - - private Application getApplication(boolean cacheOn) { - Map config = new HashMap<>(); - config.put("geolocation.cache.on", cacheOn); - return Helpers.fakeApplication(config); - } -} diff --git a/test/com/edulify/modules/geolocation/GeolocationServiceTest.java b/test/com/edulify/modules/geolocation/GeolocationServiceTest.java deleted file mode 100644 index 1f999c3..0000000 --- a/test/com/edulify/modules/geolocation/GeolocationServiceTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.edulify.modules.geolocation; - -import org.junit.*; -import org.hamcrest.*; -import org.mockito.Mockito; - -import play.*; -import play.test.WithApplication; -import play.inject.guice.GuiceApplicationBuilder; - -import java.io.File; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.TimeUnit; - -import static com.jayway.awaitility.Awaitility.*; -import static play.inject.Bindings.bind; - -public class GeolocationServiceTest extends WithApplication { - - private final String ipAddress = "192.30.252.129"; - private final String countryCode = "BR"; - - @Override - public Application provideApplication() { - Geolocation geolocation = new Geolocation(ipAddress, countryCode); - - GeolocationProvider provider = Mockito.mock(GeolocationProvider.class); - Mockito.when(provider.get(ipAddress)).thenReturn(CompletableFuture.completedFuture(geolocation)); - - return new GuiceApplicationBuilder() - .in(new File(".")) - .in(Mode.TEST) - .configure("geolocation.cache.on", true) - .bindings(bind(GeolocationProvider.class).toInstance(provider)) - .build(); - } - - @Test - public void shouldGetAGeolocationForAGivenIp() throws Exception { - GeolocationService service = app.injector().instanceOf(GeolocationService.class); - Geolocation geolocation = service.getGeolocation(ipAddress).toCompletableFuture().get(1, TimeUnit.SECONDS); - - Assert.assertThat(geolocation.getIp(), CoreMatchers.equalTo(ipAddress)); - Assert.assertThat(geolocation.getCountryCode(), CoreMatchers.equalTo(countryCode)); - } - - @Test - public void shouldSetAGeolocationCache() throws InterruptedException { - GeolocationService service = app.injector().instanceOf(GeolocationService.class); - GeolocationCache cache = app.injector().instanceOf(GeolocationCache.class); - - CompletionStage promise = service.getGeolocation(ipAddress); - - await().atMost(5, TimeUnit.SECONDS).until(() -> { - return promise.toCompletableFuture().isDone(); - }); - - Geolocation geolocation = cache.get(ipAddress); - Assert.assertThat(geolocation, CoreMatchers.notNullValue()); - } -} diff --git a/version.sbt b/version.sbt new file mode 100644 index 0000000..0192e94 --- /dev/null +++ b/version.sbt @@ -0,0 +1 @@ +version in ThisBuild := "2.1.0" \ No newline at end of file