From 4281207fed403a597c60a63c3c655981bcce8970 Mon Sep 17 00:00:00 2001 From: Karel Cemus Date: Sun, 12 Nov 2017 15:40:55 +0100 Subject: [PATCH 1/2] Java.getOrElseUpdate test --- .../play/api/cache/redis/impl/JavaCacheSpec.scala | 12 ++++++++++-- .../scala/play/api/cache/redis/impl/package.scala | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/test/scala/play/api/cache/redis/impl/JavaCacheSpec.scala b/src/test/scala/play/api/cache/redis/impl/JavaCacheSpec.scala index 3a1a2eae..2f91e3a2 100644 --- a/src/test/scala/play/api/cache/redis/impl/JavaCacheSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/JavaCacheSpec.scala @@ -1,6 +1,7 @@ package play.api.cache.redis.impl import java.util.Date +import java.util.concurrent.Callable import java.util.concurrent.atomic.AtomicInteger import play.api.cache.redis.{Redis, SimpleObject} @@ -13,9 +14,9 @@ import org.specs2.mutable.Specification */ class JavaCacheSpec extends Specification with Redis { - private type Cache = play.cache.CacheApi + private type Cache = play.cache.SyncCacheApi - private val Cache = injector.instanceOf[ play.cache.CacheApi ] + private val Cache = injector.instanceOf[ play.cache.SyncCacheApi ] private val prefix = "java" @@ -59,6 +60,13 @@ class JavaCacheSpec extends Specification with Redis { counter.get mustEqual 1 } + "getOrElseUpdate" in { + Cache.get[ String ]( s"$prefix-test-getOrElseUpdate" ) must beNull + val orElse = new Callable[ String ] { def call( ) = "value" } + Cache.getOrElseUpdate[ String ]( s"$prefix-test-getOrElseUpdate", orElse ) mustEqual "value" + Cache.get[ String ]( s"$prefix-test-getOrElseUpdate" ) mustEqual "value" + } + "distinct different keys" in { val counter = new AtomicInteger( 0 ) Cache.getOrElseCounting( s"$prefix-test-7A" )( counter ) mustEqual "value" diff --git a/src/test/scala/play/api/cache/redis/impl/package.scala b/src/test/scala/play/api/cache/redis/impl/package.scala index 6f17fd0c..ef718693 100644 --- a/src/test/scala/play/api/cache/redis/impl/package.scala +++ b/src/test/scala/play/api/cache/redis/impl/package.scala @@ -68,11 +68,11 @@ package object impl extends LowPriorityImplicits { def apply[ S <: Any ]( value: Expectable[ S ] ): MatchResult[ S ] = result( test = true, value.description + " is Unit", value.description + " is not Unit", value.evaluate ) } - implicit class JavaAccumulatorCache( val cache: play.cache.CacheApi ) extends AnyVal { + implicit class JavaAccumulatorCache( val cache: play.cache.SyncCacheApi ) extends AnyVal { private type Accumulator = AtomicInteger /** invokes internal getOrElse but it accumulate invocations of orElse clause in the accumulator */ - def getOrElseCounting( key: String )( accumulator: Accumulator ) = cache.getOrElse[ String ]( key, new Callable[ String ] { + def getOrElseCounting( key: String )( accumulator: Accumulator ) = cache.getOrElseUpdate[ String ]( key, new Callable[ String ] { override def call( ): String = { // increment miss counter accumulator.incrementAndGet() From 3bf5a17072fdab0a18acb5b0efd2bb8a9956ac3b Mon Sep 17 00:00:00 2001 From: Karel Cemus Date: Sun, 12 Nov 2017 15:36:45 +0100 Subject: [PATCH 2/2] Java.getOrElseUpdate uses HttpExecutionContext --- .../play/api/cache/redis/impl/JavaRedis.scala | 11 ++++++++--- .../play/api/cache/redis/impl/JavaCacheSpec.scala | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/main/scala/play/api/cache/redis/impl/JavaRedis.scala b/src/main/scala/play/api/cache/redis/impl/JavaRedis.scala index 5338538e..f407833b 100644 --- a/src/main/scala/play/api/cache/redis/impl/JavaRedis.scala +++ b/src/main/scala/play/api/cache/redis/impl/JavaRedis.scala @@ -3,7 +3,6 @@ package play.api.cache.redis.impl import java.util.concurrent.{Callable, CompletionStage} import javax.inject.{Inject, Singleton} -import scala.compat.java8.FutureConverters import scala.concurrent.Future import scala.concurrent.duration._ import scala.reflect.ClassTag @@ -22,7 +21,6 @@ import play.api.cache.redis._ private[ impl ] class JavaRedis @Inject()( internal: CacheAsyncApi, environment: Environment, connector: RedisConnector ) extends play.cache.AsyncCacheApi { import JavaRedis._ - import connector.context def set( key: String, value: scala.Any, expiration: Int ): CompletionStage[ Done ] = set( key, value, expiration.seconds ).toJava @@ -30,7 +28,9 @@ private[ impl ] class JavaRedis @Inject()( internal: CacheAsyncApi, environment: def set( key: String, value: scala.Any ): CompletionStage[ Done ] = set( key, value, Duration.Inf ).toJava - def set( key: String, value: scala.Any, duration: Duration ): Future[ Done ] = + def set( key: String, value: scala.Any, duration: Duration ): Future[ Done ] = { + import connector.context + Future.sequence( Seq( // set the value @@ -41,6 +41,7 @@ private[ impl ] class JavaRedis @Inject()( internal: CacheAsyncApi, environment: ).map { case Seq( done, _ ) => done } + } def remove( key: String ): CompletionStage[ Done ] = internal.remove( key ).toJava @@ -54,6 +55,9 @@ private[ impl ] class JavaRedis @Inject()( internal: CacheAsyncApi, environment: getOrElse[ T ]( key, Some( block ), duration = expiration.seconds ) def getOrElse[ T ]( key: String, callable: Option[ Callable[ CompletionStage[ T ] ] ], duration: Duration = Duration.Inf ): CompletionStage[ T ] = { + import play.core.j.HttpExecutionContext + // save the HTTP context if any and restore it later for orElse clause + implicit val context = HttpExecutionContext.fromThread( connector.context ) // get the tag and decode it def getClassTag = internal.get[ String ]( s"classTag::$key" ) def decodeClassTag( name: String ): ClassTag[ T ] = if ( name == null ) ClassTag.Null.asInstanceOf[ ClassTag[ T ] ] else ClassTag( environment.classLoader.loadClass( name ) ) @@ -80,6 +84,7 @@ private[ impl ] class JavaRedis @Inject()( internal: CacheAsyncApi, environment: } private[ impl ] object JavaRedis { + import scala.compat.java8.FutureConverters private implicit class Java8Compatibility[ T ]( val future: Future[ T ] ) extends AnyVal { @inline def toJava: CompletionStage[ T ] = FutureConverters.toJava( future ) diff --git a/src/test/scala/play/api/cache/redis/impl/JavaCacheSpec.scala b/src/test/scala/play/api/cache/redis/impl/JavaCacheSpec.scala index 2f91e3a2..fe09ed73 100644 --- a/src/test/scala/play/api/cache/redis/impl/JavaCacheSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/JavaCacheSpec.scala @@ -5,6 +5,8 @@ import java.util.concurrent.Callable import java.util.concurrent.atomic.AtomicInteger import play.api.cache.redis.{Redis, SimpleObject} +import play.mvc.Http +import play.test.Helpers import org.joda.time.DateTime import org.specs2.mutable.Specification @@ -67,6 +69,19 @@ class JavaCacheSpec extends Specification with Redis { Cache.get[ String ]( s"$prefix-test-getOrElseUpdate" ) mustEqual "value" } + "getOrElseUpdate uses HttpContext" in { + Cache.get[ String ]( s"$prefix-test-getOrElseUpdate-2" ) must beNull + val request = Helpers.fakeRequest().path( "request-path" ).build() + val context = Helpers.httpContext( request ) + Http.Context.current.set( context ) + val orElse = new Callable[ String ] { + def call( ) = Http.Context.current().request().path() + } + Http.Context.current().request().path() mustEqual "request-path" + Cache.getOrElseUpdate[ String ]( s"$prefix-test-getOrElseUpdate-2", orElse ) mustEqual "request-path" + Cache.get[ String ]( s"$prefix-test-getOrElseUpdate-2" ) mustEqual "request-path" + } + "distinct different keys" in { val counter = new AtomicInteger( 0 ) Cache.getOrElseCounting( s"$prefix-test-7A" )( counter ) mustEqual "value"