From 751c727f387e07242f496de7cdf50585e00f875b Mon Sep 17 00:00:00 2001 From: Karel Cemus Date: Sun, 24 Sep 2017 17:06:07 +0200 Subject: [PATCH 1/8] Code clean up and refactoring --- src/main/resources/reference.conf | 4 +- .../play/api/cache/redis/Expiration.scala | 41 +++++++++++++++++ .../redis/connector/AkkaSerializer.scala | 2 +- .../redis/connector/ExpectedFuture.scala | 2 +- .../redis/connector/RedisConnectorImpl.scala | 2 - .../cache/redis/exception/exceptions.scala | 38 ---------------- .../package.scala => exceptions.scala} | 39 +++++++++++++++- .../api/cache/redis/impl/AsyncRedis.scala | 6 ++- .../play/api/cache/redis/impl/Builders.scala | 1 - .../play/api/cache/redis/impl/JavaRedis.scala | 4 +- .../api/cache/redis/impl/RecoveryPolicy.scala | 2 +- .../api/cache/redis/impl/RedisCache.scala | 44 +++++++++---------- .../play/api/cache/redis/impl/SyncRedis.scala | 4 +- .../scala/play/api/cache/redis/impl/dsl.scala | 2 +- .../scala/play/api/cache/redis/package.scala | 6 +-- .../api/cache/redis/util/Expiration.scala | 43 ------------------ src/test/resources/reference.conf | 8 +++- .../redis/connector/FailingConnector.scala | 3 +- .../redis/connector/RedisConnectorSpec.scala | 1 - .../redis/impl/AsynchronousCacheSpec.scala | 1 - .../cache/redis/impl/RecoveryPolicySpec.scala | 2 +- .../play/api/cache/redis/impl/package.scala | 3 -- .../api/cache/redis/util/ExpirationSpec.scala | 4 +- 23 files changed, 129 insertions(+), 133 deletions(-) create mode 100644 src/main/scala/play/api/cache/redis/Expiration.scala delete mode 100644 src/main/scala/play/api/cache/redis/exception/exceptions.scala rename src/main/scala/play/api/cache/redis/{exception/package.scala => exceptions.scala} (55%) delete mode 100644 src/main/scala/play/api/cache/redis/util/Expiration.scala diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index 510690a9..67a661a5 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -33,7 +33,7 @@ play.cache.redis { # # for more advanced settings such as a cluster or connection # string see below. - #s + # # # # for advanced users, there is the second way to configure @@ -202,8 +202,6 @@ play.cache.redis { # Akka configuration # ================== akka { - log-dead-letters = off - log-dead-letters-during-shutdown = off actor { serialization-bindings { diff --git a/src/main/scala/play/api/cache/redis/Expiration.scala b/src/main/scala/play/api/cache/redis/Expiration.scala new file mode 100644 index 00000000..9d92f528 --- /dev/null +++ b/src/main/scala/play/api/cache/redis/Expiration.scala @@ -0,0 +1,41 @@ +package play.api.cache.redis + +import scala.concurrent.duration._ +import scala.language.implicitConversions + +/** + * Provides implicit converters to convert expiration date into duration, which is accepted by CacheApi. + * The conversion is performed from now, i.e., the formula is: + * + * {{{ + * expireAt in seconds - now in seconds = duration in seconds + * }}} + * + * @author Karel Cemus + */ +private[ redis ] trait ExpirationImplicits { + import java.time.{LocalDateTime, ZoneId} + import java.util.Date + import org.joda.time.DateTime + + implicit def javaDate2AsExpiration( expireAt: Date ): Expiration = new Expiration( expireAt.getTime ) + + @deprecated( message = "Since Play 2.6 org.joda.time is removed and replaced ba Java 8 DateTime API. Use java.time.LocalDateTime instead.", since = "2.0.0" ) + implicit def jodaDate2AsExpiration( expireAt: DateTime ): Expiration = new Expiration( expireAt.getMillis ) + + implicit def java8Date2AsExpiration( expireAt: LocalDateTime ): Expiration = new Expiration( expireAt.atZone( ZoneId.systemDefault() ).toEpochSecond * 1000 ) +} + +/** + * computes cache duration from the given expiration date time. + * + * @param expireAt The class accepts timestamp in milliseconds since 1970 + */ +class Expiration( val expireAt: Long ) extends AnyVal { + + /** returns now in milliseconds */ + private def now = System.currentTimeMillis() + + /** converts given timestamp indication expiration date into duration from now */ + def asExpiration = ( expireAt - now ).milliseconds +} diff --git a/src/main/scala/play/api/cache/redis/connector/AkkaSerializer.scala b/src/main/scala/play/api/cache/redis/connector/AkkaSerializer.scala index c0ff19e9..8959f6d2 100644 --- a/src/main/scala/play/api/cache/redis/connector/AkkaSerializer.scala +++ b/src/main/scala/play/api/cache/redis/connector/AkkaSerializer.scala @@ -6,7 +6,7 @@ import scala.language.implicitConversions import scala.reflect.ClassTag import scala.util._ -import play.api.cache.redis.exception._ +import play.api.cache.redis._ import akka.actor.ActorSystem import akka.serialization._ diff --git a/src/main/scala/play/api/cache/redis/connector/ExpectedFuture.scala b/src/main/scala/play/api/cache/redis/connector/ExpectedFuture.scala index 1effecd0..add60d56 100644 --- a/src/main/scala/play/api/cache/redis/connector/ExpectedFuture.scala +++ b/src/main/scala/play/api/cache/redis/connector/ExpectedFuture.scala @@ -2,7 +2,7 @@ package play.api.cache.redis.connector import scala.concurrent.{ExecutionContext, Future} -import play.api.cache.redis.exception._ +import play.api.cache.redis._ /** * The extended future implements advanced response handling. diff --git a/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala b/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala index a3932b8e..9b15819e 100644 --- a/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala +++ b/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala @@ -22,8 +22,6 @@ import redis._ */ private[ connector ] class RedisConnectorImpl( serializer: AkkaSerializer, configuration: RedisInstance, redis: RedisCommands )( implicit system: ActorSystem ) extends RedisConnector { - import exception._ - // implicit ask timeout implicit val timeout = akka.util.Timeout( configuration.timeout ) diff --git a/src/main/scala/play/api/cache/redis/exception/exceptions.scala b/src/main/scala/play/api/cache/redis/exception/exceptions.scala deleted file mode 100644 index 38e083bd..00000000 --- a/src/main/scala/play/api/cache/redis/exception/exceptions.scala +++ /dev/null @@ -1,38 +0,0 @@ -package play.api.cache.redis.exception - -/** - * Generic exception produced by the library indicating internal failure - * - * @author Karel Cemus - */ -sealed abstract class RedisException( message: String, cause: Throwable ) extends RuntimeException( message, cause ) { - def this( message: String ) = this( message, null ) -} - -/** - * Request timeouts - * - * @author Karel Cemus - */ -case class TimeoutException( cause: Throwable ) extends RedisException( "Command execution timed out", cause ) - -/** - * Command execution failed with exception - * - * @author Karel Cemus - */ -case class ExecutionFailedException( key: Option[ String ], command: String, cause: Throwable ) extends RedisException( s"Execution of '$command'${ key.map( key => s" for key '$key'" ) getOrElse "" } failed", cause ) - -/** - * Request succeeded but returned unexpected value - * - * @author Karel Cemus - */ -case class UnexpectedResponseException( key: Option[ String ], command: String ) extends RedisException( s"Command '$command'${ key.map( key => s" for key '$key'" ) getOrElse "" } returned unexpected response" ) - -/** - * Value serialization or deserialization failed. - * - * @author Karel Cemus - */ -case class SerializationException( key: String, message: String, cause: Throwable ) extends RedisException( s"$message for $key", cause ) diff --git a/src/main/scala/play/api/cache/redis/exception/package.scala b/src/main/scala/play/api/cache/redis/exceptions.scala similarity index 55% rename from src/main/scala/play/api/cache/redis/exception/package.scala rename to src/main/scala/play/api/cache/redis/exceptions.scala index 018ee82c..fbbed39b 100644 --- a/src/main/scala/play/api/cache/redis/exception/package.scala +++ b/src/main/scala/play/api/cache/redis/exceptions.scala @@ -1,11 +1,48 @@ package play.api.cache.redis +/** + * Generic exception produced by the library indicating internal failure + * + * @author Karel Cemus + */ +sealed abstract class RedisException( message: String, cause: Throwable ) extends RuntimeException( message, cause ) { + def this( message: String ) = this( message, null ) +} + +/** + * Request timeouts + * + * @author Karel Cemus + */ +case class TimeoutException( cause: Throwable ) extends RedisException( "Command execution timed out", cause ) + +/** + * Command execution failed with exception + * + * @author Karel Cemus + */ +case class ExecutionFailedException( key: Option[ String ], command: String, cause: Throwable ) extends RedisException( s"Execution of '$command'${ key.map( key => s" for key '$key'" ) getOrElse "" } failed", cause ) + +/** + * Request succeeded but returned unexpected value + * + * @author Karel Cemus + */ +case class UnexpectedResponseException( key: Option[ String ], command: String ) extends RedisException( s"Command '$command'${ key.map( key => s" for key '$key'" ) getOrElse "" } returned unexpected response" ) + +/** + * Value serialization or deserialization failed. + * + * @author Karel Cemus + */ +case class SerializationException( key: String, message: String, cause: Throwable ) extends RedisException( s"$message for $key", cause ) + /** * Helper trait providing simplified and unified API to exception handling in play-redis * * @author Karel Cemus */ -package object exception { +trait ExceptionImplicits { /** helper throwing UnsupportedOperationException */ @throws[ UnsupportedOperationException ] diff --git a/src/main/scala/play/api/cache/redis/impl/AsyncRedis.scala b/src/main/scala/play/api/cache/redis/impl/AsyncRedis.scala index 119cc824..90320b2d 100644 --- a/src/main/scala/play/api/cache/redis/impl/AsyncRedis.scala +++ b/src/main/scala/play/api/cache/redis/impl/AsyncRedis.scala @@ -11,7 +11,11 @@ import play.api.cache.redis._ * * @author Karel Cemus */ -private[ impl ] class AsyncRedis( name: String, redis: RedisConnector, policy: RecoveryPolicy ) extends RedisCache( name, redis )( Builders.AsynchronousBuilder, policy ) with CacheAsyncApi with play.api.cache.AsyncCacheApi { +private[ impl ] class AsyncRedis( name: String, redis: RedisConnector, policy: RecoveryPolicy ) + extends RedisCache( name, redis )( Builders.AsynchronousBuilder, policy ) + with play.api.cache.AsyncCacheApi + with CacheAsyncApi +{ def getOrElseUpdate[ T: ClassTag ]( key: String, expiration: Duration )( orElse: => Future[ T ] ) = getOrFuture[ T ]( key, expiration )( orElse ) diff --git a/src/main/scala/play/api/cache/redis/impl/Builders.scala b/src/main/scala/play/api/cache/redis/impl/Builders.scala index 339680a0..7c2e545f 100644 --- a/src/main/scala/play/api/cache/redis/impl/Builders.scala +++ b/src/main/scala/play/api/cache/redis/impl/Builders.scala @@ -11,7 +11,6 @@ import scala.language.higherKinds object Builders { import play.api.cache.redis._ - import play.api.cache.redis.exception._ import akka.pattern.AskTimeoutException 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 ac29e9fe..23ccd57d 100644 --- a/src/main/scala/play/api/cache/redis/impl/JavaRedis.scala +++ b/src/main/scala/play/api/cache/redis/impl/JavaRedis.scala @@ -69,8 +69,8 @@ private[ impl ] class JavaRedis( name: String, internal: CacheAsyncApi, environm getValue.flatMap { case Some( value ) => Future.successful( Some( value ) ) case None => callable.fold[ Future[ Option[ T ] ] ]( Future successful None )( savedOrElse ) - }.map { - play.libs.Scala.orNull( _ ) + }.map[ T ] { + play.libs.Scala.orNull }.toJava } diff --git a/src/main/scala/play/api/cache/redis/impl/RecoveryPolicy.scala b/src/main/scala/play/api/cache/redis/impl/RecoveryPolicy.scala index eaa98e81..c75f495f 100644 --- a/src/main/scala/play/api/cache/redis/impl/RecoveryPolicy.scala +++ b/src/main/scala/play/api/cache/redis/impl/RecoveryPolicy.scala @@ -5,7 +5,7 @@ import javax.inject.Inject import scala.concurrent.Future import play.api.Logger -import play.api.cache.redis.exception._ +import play.api.cache.redis._ /** Recovery policy triggers when a request fails. Based on the implementation, * it may try it again, recover with a default value or just simply log the diff --git a/src/main/scala/play/api/cache/redis/impl/RedisCache.scala b/src/main/scala/play/api/cache/redis/impl/RedisCache.scala index 5a4ebd77..7dcd7209 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisCache.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisCache.scala @@ -14,44 +14,44 @@ private[ impl ] class RedisCache[ Result[ _ ] ]( name: String, redis: RedisConne import redis.{context, timeout} import dsl._ - override def get[ T: ClassTag ]( key: String ) = + def get[ T: ClassTag ]( key: String ) = redis.get[ T ]( key ).recoverWithDefault( None ) - override def getAll[ T: ClassTag ]( keys: String* ): Result[ Seq[ Option[ T ] ] ] = + def getAll[ T: ClassTag ]( keys: String* ): Result[ Seq[ Option[ T ] ] ] = redis.mGet[ T ]( keys: _* ).recoverWithDefault( keys.toList.map( _ => None ) ) - override def set( key: String, value: Any, expiration: Duration ) = + def set( key: String, value: Any, expiration: Duration ) = redis.set( key, value, expiration ).recoverWithDone - override def setIfNotExists( key: String, value: Any, expiration: Duration ) = + def setIfNotExists( key: String, value: Any, expiration: Duration ) = redis.setIfNotExists( key, value ).map { result => if ( result && expiration.isFinite( ) ) redis.expire( key, expiration ) result }.recoverWithDefault( true ) - override def setAll( keyValues: (String, Any)* ): Result[ Done ] = + def setAll( keyValues: (String, Any)* ): Result[ Done ] = redis.mSet( keyValues: _* ).recoverWithDone - override def setAllIfNotExist( keyValues: (String, Any)* ): Result[ Boolean ] = + def setAllIfNotExist( keyValues: (String, Any)* ): Result[ Boolean ] = redis.mSetIfNotExist( keyValues: _* ).recoverWithDefault( true ) - override def append( key: String, value: String, expiration: Duration ): Result[ Done ] = + def append( key: String, value: String, expiration: Duration ): Result[ Done ] = redis.append( key, value ).flatMap { result => // if the new string length is equal to the appended string, it means they should equal // when the finite duration is required, set it if ( result == value.length && expiration.isFinite() ) redis.expire( key, expiration ) else Future.successful[ Unit ]( Unit ) }.recoverWithDone - override def expire( key: String, expiration: Duration ) = + def expire( key: String, expiration: Duration ) = redis.expire( key, expiration ).recoverWithDone - override def matching( pattern: String ) = + def matching( pattern: String ) = redis.matching( pattern ).recoverWithDefault( Seq.empty[ String ] ) - override def getOrElse[ T: ClassTag ]( key: String, expiration: Duration )( orElse: => T ) = + def getOrElse[ T: ClassTag ]( key: String, expiration: Duration )( orElse: => T ) = getOrFuture( key, expiration )( orElse.toFuture ).recoverWithDefault( orElse ) - override def getOrFuture[ T: ClassTag ]( key: String, expiration: Duration )( orElse: => Future[ T ] ): Future[ T ] = + def getOrFuture[ T: ClassTag ]( key: String, expiration: Duration )( orElse: => Future[ T ] ): Future[ T ] = redis.get[ T ]( key ).flatMap { // cache hit, return the unwrapped value case Some( value: T ) => value.toFuture @@ -59,37 +59,37 @@ private[ impl ] class RedisCache[ Result[ _ ] ]( name: String, redis: RedisConne case None => orElse flatMap ( value => redis.set( key, value, expiration ) map ( _ => value ) ) }.recoverWithFuture( orElse ) - override def remove( key: String ) = + def remove( key: String ) = redis.remove( key ).recoverWithDone - override def remove( key1: String, key2: String, keys: String* ) = + def remove( key1: String, key2: String, keys: String* ) = redis.remove( key1 +: key2 +: keys: _* ).recoverWithDone - override def removeAll( keys: String* ): Result[ Done ] = + def removeAll( keys: String* ): Result[ Done ] = redis.remove( keys: _* ).recoverWithDone - override def removeMatching( pattern: String ): Result[ Done ] = + def removeMatching( pattern: String ): Result[ Done ] = redis.matching( pattern ).flatMap( keys => redis.remove( keys: _* ) ).recoverWithDone - override def invalidate( ) = + def invalidate( ) = redis.invalidate( ).recoverWithDone - override def exists( key: String ) = + def exists( key: String ) = redis.exists( key ).recoverWithDefault( false ) - override def increment( key: String, by: Long ) = + def increment( key: String, by: Long ) = redis.increment( key, by ).recoverWithDefault( by ) - override def decrement( key: String, by: Long ) = + def decrement( key: String, by: Long ) = increment( key, -by ) - override def list[ T: ClassTag ]( key: String ): RedisList[ T, Result ] = + def list[ T: ClassTag ]( key: String ): RedisList[ T, Result ] = new RedisListImpl( key, redis ) - override def set[ T: ClassTag ]( key: String ): RedisSet[ T, Result ] = + def set[ T: ClassTag ]( key: String ): RedisSet[ T, Result ] = new RedisSetImpl( key, redis ) - override def map[ T: ClassTag ]( key: String ): RedisMap[ T, Result ] = + def map[ T: ClassTag ]( key: String ): RedisMap[ T, Result ] = new RedisMapImpl( key, redis ) override def toString = s"RedisCache(name=$name)" diff --git a/src/main/scala/play/api/cache/redis/impl/SyncRedis.scala b/src/main/scala/play/api/cache/redis/impl/SyncRedis.scala index c771604c..602c269d 100644 --- a/src/main/scala/play/api/cache/redis/impl/SyncRedis.scala +++ b/src/main/scala/play/api/cache/redis/impl/SyncRedis.scala @@ -11,7 +11,9 @@ import play.api.cache.redis._ * @author Karel Cemus */ private[ impl ] class SyncRedis( name: String, redis: RedisConnector, policy: RecoveryPolicy ) - extends RedisCache( name: String, redis )( Builders.SynchronousBuilder, policy ) with CacheApi { + extends RedisCache( name: String, redis )( Builders.SynchronousBuilder, policy ) + with CacheApi +{ // implicit ask timeout and execution context import redis.{context, timeout} diff --git a/src/main/scala/play/api/cache/redis/impl/dsl.scala b/src/main/scala/play/api/cache/redis/impl/dsl.scala index bb3c37fa..1bfc210c 100644 --- a/src/main/scala/play/api/cache/redis/impl/dsl.scala +++ b/src/main/scala/play/api/cache/redis/impl/dsl.scala @@ -28,7 +28,7 @@ private[ impl ] object dsl { @inline def recoverWithFuture( default: => Future[ T ] )( implicit policy: RecoveryPolicy, context: ExecutionContext ): Future[ T ] = future recoverWith { // recover from known exceptions - case failure: exception.RedisException => policy.recoverFrom( future, default, failure ) + case failure: RedisException => policy.recoverFrom( future, default, failure ) } } diff --git a/src/main/scala/play/api/cache/redis/package.scala b/src/main/scala/play/api/cache/redis/package.scala index 2bef6c6b..97282a22 100644 --- a/src/main/scala/play/api/cache/redis/package.scala +++ b/src/main/scala/play/api/cache/redis/package.scala @@ -3,10 +3,10 @@ package play.api.cache /** * @author Karel Cemus */ -package object redis extends AnyRef with util.Expiration { +package object redis extends AnyRef with ExpirationImplicits with ExceptionImplicits { - type Done = akka.Done - private[ redis ] val Done: Done = akka.Done + @inline type Done = akka.Done + @inline private[ redis ] val Done: Done = akka.Done type SynchronousResult[ A ] = A type AsynchronousResult[ A ] = scala.concurrent.Future[ A ] diff --git a/src/main/scala/play/api/cache/redis/util/Expiration.scala b/src/main/scala/play/api/cache/redis/util/Expiration.scala deleted file mode 100644 index 362417d9..00000000 --- a/src/main/scala/play/api/cache/redis/util/Expiration.scala +++ /dev/null @@ -1,43 +0,0 @@ -package play.api.cache.redis.util - -import java.util.Date - -import scala.concurrent.duration._ -import scala.language.implicitConversions - -import org.joda.time.DateTime - -/** - * Provides implicit converters to convert expiration date into duration, which is accepted by CacheApi. - * The conversion is performed from now, i.e., the formula is: - * - * {{{ - * expireAt in seconds - now in seconds = duration in seconds - * }}} - * - * @author Karel Cemus - */ -private[ redis ] trait Expiration { - - /** - * converts given timestamp indication expiration date into duration from now - * - * @param expireAt The class accepts timestamp in milliseconds since 1970 - */ - class AsExpiration private( expireAt: Long ) { - - def this( expireAt: DateTime ) = this( expireAt.getMillis ) - - def this( expireAt: Date ) = this( expireAt.getTime ) - - /** returns now in milliseconds */ - private def now = new Date( ).getTime - - /** converts given timestamp indication expiration date into duration from now */ - def asExpiration = ( expireAt - now ).milliseconds - } - - implicit def javaDate2AsExpiration( expireAt: Date ): AsExpiration = new AsExpiration( expireAt ) - - implicit def jodaDate2AsExpiration( expireAt: DateTime ): AsExpiration = new AsExpiration( expireAt ) -} diff --git a/src/test/resources/reference.conf b/src/test/resources/reference.conf index 37dc9229..f7655b41 100644 --- a/src/test/resources/reference.conf +++ b/src/test/resources/reference.conf @@ -1,14 +1,18 @@ # ================== # Akka configuration # ================== -# disables warning -akka.actor.warn-about-java-serializer-usage = off # connection timeout in milliseconds play.cache.redis.timeout: 3s akka { + log-dead-letters = off + log-dead-letters-during-shutdown = off + actor { + # disables warning + warn-about-java-serializer-usage = off + serialization-bindings { "play.api.cache.redis.impl.UnserializableObject" = failing } diff --git a/src/test/scala/play/api/cache/redis/connector/FailingConnector.scala b/src/test/scala/play/api/cache/redis/connector/FailingConnector.scala index dcdbf777..6a2cd7de 100644 --- a/src/test/scala/play/api/cache/redis/connector/FailingConnector.scala +++ b/src/test/scala/play/api/cache/redis/connector/FailingConnector.scala @@ -4,8 +4,7 @@ import scala.concurrent._ import scala.concurrent.duration.Duration import scala.reflect.ClassTag -import play.api.cache.redis.Synchronization -import play.api.cache.redis.exception.ExecutionFailedException +import play.api.cache.redis.{ExecutionFailedException, Synchronization} /** * @author Karel Cemus diff --git a/src/test/scala/play/api/cache/redis/connector/RedisConnectorSpec.scala b/src/test/scala/play/api/cache/redis/connector/RedisConnectorSpec.scala index 119db0b3..0f3fa98a 100644 --- a/src/test/scala/play/api/cache/redis/connector/RedisConnectorSpec.scala +++ b/src/test/scala/play/api/cache/redis/connector/RedisConnectorSpec.scala @@ -5,7 +5,6 @@ import java.util.Date import scala.concurrent.duration._ import play.api.cache.redis._ -import play.api.cache.redis.exception.ExecutionFailedException import org.joda.time.DateTime import org.specs2.mutable.Specification diff --git a/src/test/scala/play/api/cache/redis/impl/AsynchronousCacheSpec.scala b/src/test/scala/play/api/cache/redis/impl/AsynchronousCacheSpec.scala index 0b440eba..d39bd82a 100644 --- a/src/test/scala/play/api/cache/redis/impl/AsynchronousCacheSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/AsynchronousCacheSpec.scala @@ -8,7 +8,6 @@ import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global import play.api.cache.redis._ -import play.api.cache.redis.exception._ import org.joda.time.DateTime import org.specs2.mutable.Specification diff --git a/src/test/scala/play/api/cache/redis/impl/RecoveryPolicySpec.scala b/src/test/scala/play/api/cache/redis/impl/RecoveryPolicySpec.scala index 3800b44a..ce795830 100644 --- a/src/test/scala/play/api/cache/redis/impl/RecoveryPolicySpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RecoveryPolicySpec.scala @@ -3,7 +3,7 @@ package play.api.cache.redis.impl import scala.concurrent.Future import scala.reflect.ClassTag -import play.api.cache.redis.exception._ +import play.api.cache.redis._ import akka.pattern.AskTimeoutException import org.specs2.matcher.Matcher 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..8c7c3c15 100644 --- a/src/test/scala/play/api/cache/redis/impl/package.scala +++ b/src/test/scala/play/api/cache/redis/impl/package.scala @@ -3,12 +3,9 @@ package play.api.cache.redis import java.util.concurrent.Callable import java.util.concurrent.atomic.AtomicInteger -import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import scala.language.{higherKinds, implicitConversions} -import play.api.cache.redis.exception.ExecutionFailedException - import org.specs2.matcher._ /** diff --git a/src/test/scala/play/api/cache/redis/util/ExpirationSpec.scala b/src/test/scala/play/api/cache/redis/util/ExpirationSpec.scala index 5c5afc52..3f2c1886 100644 --- a/src/test/scala/play/api/cache/redis/util/ExpirationSpec.scala +++ b/src/test/scala/play/api/cache/redis/util/ExpirationSpec.scala @@ -2,7 +2,7 @@ package play.api.cache.redis.util import java.util.Date -import play.api.cache.redis.{CacheApi, Redis} +import play.api.cache.redis.{CacheApi, Expiration, ExpirationImplicits, Redis} import org.joda.time.DateTime import org.specs2.mutable.Specification @@ -10,7 +10,7 @@ import org.specs2.mutable.Specification /** *

This specification tests expiration conversion

*/ -class ExpirationSpec extends Specification with Redis with Expiration { +class ExpirationSpec extends Specification with Redis with ExpirationImplicits { private val Cache = injector.instanceOf[ CacheApi ] From d0e3ca35ed2356ced2eb1b670c40069f8ecdf77a Mon Sep 17 00:00:00 2001 From: Karel Cemus Date: Sun, 24 Sep 2017 23:08:38 +0200 Subject: [PATCH 2/8] Introduced redis runtime carring name, context, and the policy --- .../redis/{impl => }/RecoveryPolicy.scala | 11 +- .../cache/redis/RedisCacheComponents.scala | 19 ++-- .../api/cache/redis/RedisCacheModule.scala | 106 ++++++++++-------- .../play/api/cache/redis/RedisRuntime.scala | 48 ++++++++ .../redis/connector/RedisConnector.scala | 7 -- .../redis/connector/RedisConnectorImpl.scala | 17 +-- .../connector/RedisConnectorProvider.scala | 6 +- .../api/cache/redis/impl/AsyncRedis.scala | 4 +- .../play/api/cache/redis/impl/Builders.scala | 18 ++- .../play/api/cache/redis/impl/JavaRedis.scala | 5 +- .../api/cache/redis/impl/RedisCache.scala | 7 +- .../api/cache/redis/impl/RedisListImpl.scala | 3 +- .../api/cache/redis/impl/RedisMapImpl.scala | 3 +- .../api/cache/redis/impl/RedisSetImpl.scala | 3 +- .../play/api/cache/redis/impl/SyncRedis.scala | 7 +- .../scala/play/api/cache/redis/impl/dsl.scala | 10 +- .../redis/impl/AsynchronousCacheSpec.scala | 12 +- .../api/cache/redis/impl/RedisListSpec.scala | 10 +- .../api/cache/redis/impl/RedisMapSpecs.scala | 10 +- .../api/cache/redis/impl/RedisSetSpecs.scala | 10 +- .../play/api/cache/redis/impl/package.scala | 4 +- 21 files changed, 188 insertions(+), 132 deletions(-) rename src/main/scala/play/api/cache/redis/{impl => }/RecoveryPolicy.scala (90%) create mode 100644 src/main/scala/play/api/cache/redis/RedisRuntime.scala diff --git a/src/main/scala/play/api/cache/redis/impl/RecoveryPolicy.scala b/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala similarity index 90% rename from src/main/scala/play/api/cache/redis/impl/RecoveryPolicy.scala rename to src/main/scala/play/api/cache/redis/RecoveryPolicy.scala index c75f495f..765a06c3 100644 --- a/src/main/scala/play/api/cache/redis/impl/RecoveryPolicy.scala +++ b/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala @@ -1,11 +1,10 @@ -package play.api.cache.redis.impl +package play.api.cache.redis import javax.inject.Inject import scala.concurrent.Future import play.api.Logger -import play.api.cache.redis._ /** Recovery policy triggers when a request fails. Based on the implementation, * it may try it again, recover with a default value or just simply log the @@ -118,7 +117,7 @@ trait RecoverWithDefault extends RecoveryPolicy { * * @author Karel Cemus */ -private[ impl ] class LogAndFailPolicy @Inject( )( ) extends FailThrough with DetailedReports +private[ redis ] class LogAndFailPolicy @Inject( )( ) extends FailThrough with DetailedReports /** When the command fails, it logs the failure and returns default value * to prevent application failure. The returned value is neutral to the @@ -126,7 +125,7 @@ private[ impl ] class LogAndFailPolicy @Inject( )( ) extends FailThrough with De * * @author Karel Cemus */ -private[ impl ] class LogAndDefaultPolicy @Inject( )( ) extends RecoverWithDefault with DetailedReports +private[ redis ] class LogAndDefaultPolicy @Inject( )( ) extends RecoverWithDefault with DetailedReports /** When the command fails, it logs the failure and returns default value @@ -138,7 +137,7 @@ private[ impl ] class LogAndDefaultPolicy @Inject( )( ) extends RecoverWithDefau * * @author Karel Cemus */ -private[ impl ] class LogCondensedAndDefaultPolicy @Inject( )( ) extends RecoverWithDefault with CondensedReports +private[ redis ] class LogCondensedAndDefaultPolicy @Inject( )( ) extends RecoverWithDefault with CondensedReports /** When the command fails, it logs the failure and fails the whole operation. @@ -148,4 +147,4 @@ private[ impl ] class LogCondensedAndDefaultPolicy @Inject( )( ) extends Recover * * @author Karel Cemus */ -private[ impl ] class LogCondensedAndFailPolicy @Inject( )( ) extends FailThrough with CondensedReports +private[ redis ] class LogCondensedAndFailPolicy @Inject( )( ) extends FailThrough with CondensedReports diff --git a/src/main/scala/play/api/cache/redis/RedisCacheComponents.scala b/src/main/scala/play/api/cache/redis/RedisCacheComponents.scala index 9ce41ea3..344d644a 100644 --- a/src/main/scala/play/api/cache/redis/RedisCacheComponents.scala +++ b/src/main/scala/play/api/cache/redis/RedisCacheComponents.scala @@ -50,17 +50,20 @@ package connector { case cluster: RedisCluster => new RedisCommandsCluster( cluster )( actorSystem, applicationLifecycle ).get } - private[ redis ] def redisConnectorFor( instance: RedisInstance ) = - new RedisConnectorImpl( akkaSerializer, instance, redisCommandsFor( instance ) )( actorSystem ) + private[ redis ] def redisConnectorFor( instance: RedisInstance )( implicit runtime: RedisRuntime ) = + new RedisConnectorImpl( akkaSerializer, redisCommandsFor( instance ) ) } } package impl { private[ redis ] trait RedisImplComponents { + import akka.actor.ActorSystem def environment: Environment + def actorSystem: ActorSystem + /** overwrite to provide custom recovery policy */ def redisRecoveryPolicy: PartialFunction[ String, RecoveryPolicy ] = { case "log-and-fail" => new LogAndFailPolicy @@ -69,15 +72,17 @@ package impl { case "log-condensed-and-default" => new LogCondensedAndDefaultPolicy } - private[ redis ] def redisConnectorFor( instance: RedisInstance ): RedisConnector + private[ redis ] def redisConnectorFor( instance: RedisInstance )( implicit runtime: RedisRuntime ): RedisConnector - private implicit def instance2connector( instance: RedisInstance ): RedisConnector = redisConnectorFor( instance ) + private implicit def instance2connector( instance: RedisInstance )( implicit runtime: RedisRuntime ): RedisConnector = redisConnectorFor( instance ) private implicit def instance2policy( instance: RedisInstance ): RecoveryPolicy = redisRecoveryPolicy( instance.recovery ) + implicit def runtime( implicit instance: RedisInstance ) = RedisRuntime( instance = instance, recovery = instance )( actorSystem ) + // play-redis APIs - private def asyncRedis( instance: RedisInstance ) = new AsyncRedis( instance.name, redis = instance, policy = instance ) + private def asyncRedis( implicit instance: RedisInstance ) = new AsyncRedis( redis = instance ) - def syncRedisCacheApi( instance: RedisInstance ): CacheApi = new SyncRedis( instance.name, redis = instance, policy = instance ) + def syncRedisCacheApi( implicit instance: RedisInstance ): CacheApi = new SyncRedis( redis = instance ) def asyncRedisCacheApi( instance: RedisInstance ): CacheAsyncApi = asyncRedis( instance ) // scala api defined by Play @@ -88,7 +93,7 @@ package impl { def syncCacheApi( instance: RedisInstance ): play.api.cache.SyncCacheApi = defaultSyncCache( instance ) // java api defined by Play - def javaAsyncCacheApi( instance: RedisInstance ): play.cache.AsyncCacheApi = new JavaRedis( instance.name, asyncRedis( instance ), environment = environment, connector = instance ) + def javaAsyncCacheApi( implicit instance: RedisInstance ): play.cache.AsyncCacheApi = new JavaRedis( asyncRedis( instance ), environment = environment ) private def javaDefaultSyncCache( instance: RedisInstance ) = new play.cache.DefaultSyncCacheApi( javaAsyncCacheApi( instance ) ) @deprecated( message = "Use javaSyncCacheApi or javaAsyncCacheApi.", since = "Play 2.6.0." ) def javaCacheApi( instance: RedisInstance ): play.cache.CacheApi = javaDefaultSyncCache( instance ) diff --git a/src/main/scala/play/api/cache/redis/RedisCacheModule.scala b/src/main/scala/play/api/cache/redis/RedisCacheModule.scala index 6d66ee0f..7202487f 100644 --- a/src/main/scala/play/api/cache/redis/RedisCacheModule.scala +++ b/src/main/scala/play/api/cache/redis/RedisCacheModule.scala @@ -2,6 +2,8 @@ package play.api.cache.redis import javax.inject.{Inject, Provider, Singleton} +import scala.language.implicitConversions + import play.api.inject._ import play.api.{Configuration, Environment} @@ -56,21 +58,20 @@ package impl { */ private[ redis ] trait ImplementationModule { import ImplementationModule._ - - def bindSharedCache: Seq[ Binding[ _ ] ] = RedisRecoveryPolicyResolver.bindings + import NamedCacheProvider._ def bindNamedCache( implicit name: String ) = Seq[ Binding[ _ ] ]( // play-redis APIs - bind[ CacheApi ].qualified.to( new NamedSyncRedisProvider( name ) ), - bind[ CacheAsyncApi ].qualified.to( new NamedAsyncRedisProvider( name ) ), + bind[ CacheApi ].qualified.to( NamedSyncRedisProvider( name ) ), + bind[ CacheAsyncApi ].qualified.to( NamedAsyncRedisProvider( name ) ), // scala api defined by Play - bind[ play.api.cache.CacheApi ].qualified.to( new NamedScalaSyncCacheProvider( name ) ), - bind[ play.api.cache.SyncCacheApi ].qualified.to( new NamedScalaSyncCacheProvider( name ) ), - bind[ play.api.cache.AsyncCacheApi ].qualified.to( new NamedAsyncRedisProvider( name ) ), + bind[ play.api.cache.CacheApi ].qualified.to( NamedScalaSyncCacheProvider( name ) ), + bind[ play.api.cache.SyncCacheApi ].qualified.to( NamedScalaSyncCacheProvider( name ) ), + bind[ play.api.cache.AsyncCacheApi ].qualified.to( NamedAsyncRedisProvider( name ) ), // java api defined by Play - bind[ play.cache.CacheApi ].qualified.to( new NamedJavaSyncCacheProvider( name ) ), - bind[ play.cache.SyncCacheApi ].qualified.to( new NamedJavaSyncCacheProvider( name ) ), - bind[ play.cache.AsyncCacheApi ].qualified.to( new NamedJavaRedisProvider( name ) ) + bind[ play.cache.CacheApi ].qualified.to( NamedJavaSyncCacheProvider( name ) ), + bind[ play.cache.SyncCacheApi ].qualified.to( NamedJavaSyncCacheProvider( name ) ), + bind[ play.cache.AsyncCacheApi ].qualified.to( NamedJavaRedisProvider( name ) ) ) def bindDefaultCache( implicit name: String ): Seq[ Binding[ _ ] ] = Seq( @@ -94,59 +95,66 @@ package impl { } } - abstract class NamedCacheProvider[ T ]( name: String )( f: Injector => T ) extends Provider[ T ] { + class NamedCacheProvider[ T ]( name: String )( f: Injector => T ) extends Provider[ T ] { @Inject var injector: Injector = _ lazy val get = f( injector ) } - class NamedAsyncRedisProvider( name: String ) extends NamedCacheProvider( name )( { injector: Injector => - def connector = bind[ RedisConnector ].qualifiedWith( name ) - def instance = injector instanceOf bind[ RedisInstance ].qualifiedWith( name ) - def policy = bind[ RecoveryPolicy ].qualifiedWith( instance.recovery ) + object NamedCacheProvider { - new AsyncRedis( name, injector instanceOf connector, injector instanceOf policy ) - } ) + @inline private implicit def implicitBinding[ T ]( bindingKey: BindingKey[ T ] )( implicit injector: Injector ): T = injector.instanceOf( bindingKey ) - class NamedSyncRedisProvider( name: String ) extends NamedCacheProvider( name )( { injector: Injector => - def connector = bind[ RedisConnector ].qualifiedWith( name ) - def instance = injector instanceOf bind[ RedisInstance ].qualifiedWith( name ) - def policy = bind[ RecoveryPolicy ].qualifiedWith( instance.recovery ) + @inline private implicit def implicitInjection[ T ]( implicit bindingKey: BindingKey[ T ], injector: Injector ): T = implicitBinding( bindingKey ) - new SyncRedis( name, injector instanceOf connector, injector instanceOf policy ) - } ) + def NamedAsyncRedisProvider( name: String ) = provider( name ) { implicit injector => + def connector = bind[ RedisConnector ].qualifiedWith( name ) + implicit def runtime = bind[ RedisRuntime ].qualifiedWith( name ) + + new AsyncRedis( connector ) + } - class NamedJavaRedisProvider( name: String ) extends NamedCacheProvider( name )( { injector => - def connector = injector instanceOf bind[ RedisConnector ].qualifiedWith( name ) - def internal = injector instanceOf bind[ CacheAsyncApi ].qualifiedWith( name ) - def environment = injector instanceOf bind[ Environment ] + def NamedSyncRedisProvider( name: String ) = provider( name ) { implicit injector => + def connector = bind[ RedisConnector ].qualifiedWith( name ) + implicit def runtime = bind[ RedisRuntime ].qualifiedWith( name ) - new JavaRedis( name, internal, environment, connector ) - } ) + new SyncRedis( connector ) + } - class NamedJavaSyncCacheProvider( name: String ) extends NamedCacheProvider( name )( { injector => - def internal = injector instanceOf bind[ play.cache.AsyncCacheApi ].qualifiedWith( name ) + def NamedJavaRedisProvider( name: String ) = provider( name ) { implicit injector => + def internal = bind[ CacheAsyncApi ].qualifiedWith( name ) + def environment = bind[ Environment ] + implicit def runtime = bind[ RedisRuntime ].qualifiedWith( name ) - new play.cache.DefaultSyncCacheApi( internal ) - } ) + new JavaRedis( internal, environment ) + } - class NamedScalaSyncCacheProvider( name: String ) extends NamedCacheProvider( name )( { injector => - def internal = injector instanceOf bind[ play.api.cache.AsyncCacheApi ].qualifiedWith( name ) + def NamedJavaSyncCacheProvider( name: String ) = provider( name ) { implicit injector => + def internal = bind[ play.cache.AsyncCacheApi ].qualifiedWith( name ) - new play.api.cache.DefaultSyncCacheApi( internal ) - } ) + new play.cache.DefaultSyncCacheApi( internal ) + } - private[ impl ] object RedisRecoveryPolicyResolver { + def NamedScalaSyncCacheProvider( name: String ) = provider( name ) { implicit injector => + def internal = bind[ play.api.cache.AsyncCacheApi ].qualifiedWith( name ) - def bindings = Seq( - bind[ RecoveryPolicy ].qualifiedWith( "log-and-fail" ).to[ LogAndFailPolicy ], - bind[ RecoveryPolicy ].qualifiedWith( "log-and-default" ).to[ LogAndDefaultPolicy ], - bind[ RecoveryPolicy ].qualifiedWith( "log-condensed-and-fail" ).to[ LogCondensedAndFailPolicy ], - bind[ RecoveryPolicy ].qualifiedWith( "log-condensed-and-default" ).to[ LogCondensedAndDefaultPolicy ] - ) + new play.api.cache.DefaultSyncCacheApi( internal ) + } + + @inline private def provider[ T ]( name: String )( f: Injector => T ): NamedCacheProvider[ T ] = new NamedCacheProvider( name )( f ) } } +private[ redis ] object RecoveryPolicyBinder { + + def bindings = Seq( + bind[ RecoveryPolicy ].qualifiedWith( "log-and-fail" ).to[ LogAndFailPolicy ], + bind[ RecoveryPolicy ].qualifiedWith( "log-and-default" ).to[ LogAndDefaultPolicy ], + bind[ RecoveryPolicy ].qualifiedWith( "log-condensed-and-fail" ).to[ LogCondensedAndFailPolicy ], + bind[ RecoveryPolicy ].qualifiedWith( "log-condensed-and-default" ).to[ LogCondensedAndDefaultPolicy ] + ) +} + /** Play framework module implementing play.api.cache.CacheApi for redis-server key/value storage. For more details * see README. * @@ -167,12 +175,16 @@ class RedisCacheModule extends Module { def bindDefault = config.get[ Boolean ]( "play.cache.redis.bind-default" ) def caches = module.caches - def bindShared = module.bindSharedConnector ++ module.bindSharedCache + def bindShared = module.bindSharedConnector ++ RecoveryPolicyBinder.bindings - def bindNamed( name: String ) = module.bindNamedConfiguration( name ) ++ module.bindNamedConnector( name ) ++ module.bindNamedCache( name ) + def bindNamed( name: String ) = module.bindNamedConfiguration( name ) ++ module.bindNamedConnector( name ) ++ module.bindNamedCache( name ) ++ Seq( + bind[ RedisRuntime ].qualifiedWith( name ).to( new NamedRedisRuntimeProvider( name ) ) + ) def bindCaches = caches.flatMap( bindNamed ) - def bindDefaults( name: String ) = module.bindDefaultConnector( name ) ++ module.bindDefaultCache( name ) + def bindDefaults( name: String ) = module.bindDefaultConnector( name ) ++ module.bindDefaultCache( name ) ++ Seq( + bind[ RedisRuntime ].to( new NamedRedisRuntimeProvider( name ) ) + ) def bindDefaultCache = if ( bindDefault ) bindDefaults( defaultCache ) else Seq.empty[ Binding[ _ ] ] bindShared ++ bindDefaultCache ++ bindCaches diff --git a/src/main/scala/play/api/cache/redis/RedisRuntime.scala b/src/main/scala/play/api/cache/redis/RedisRuntime.scala new file mode 100644 index 00000000..b2b3ae4d --- /dev/null +++ b/src/main/scala/play/api/cache/redis/RedisRuntime.scala @@ -0,0 +1,48 @@ +package play.api.cache.redis + +import javax.inject.{Inject, Provider} + +import scala.concurrent.ExecutionContext +import scala.concurrent.duration.FiniteDuration + +import play.api.inject._ + +import akka.actor.ActorSystem + +/** + * Runtime info about the current cache instance. It includes + * a configuration, recovery policy, and the execution context. + * + * @author Karel Cemus + */ +private[ redis ] trait RedisRuntime { + def name: String + implicit def context: ExecutionContext + implicit def policy: RecoveryPolicy + implicit def timeout: akka.util.Timeout +} + +private[ redis ] case class RedisRuntimeImpl( name: String, context: ExecutionContext, policy: RecoveryPolicy, timeout: akka.util.Timeout ) extends RedisRuntime + +private[ redis ] object RedisRuntime { + + def apply( instance: RedisInstance, recovery: RecoveryPolicy )( implicit system: ActorSystem ): RedisRuntime = + apply( instance.name, instance.timeout, system.dispatchers.lookup( instance.invocationContext ), recovery ) + + def apply( name: String, timeout: FiniteDuration, context: ExecutionContext, recovery: RecoveryPolicy ): RedisRuntime = + RedisRuntimeImpl( name, context, recovery, akka.util.Timeout( timeout ) ) +} + +class NamedRedisRuntimeProvider( cacheName: String ) extends Provider[ RedisRuntime ] { + @Inject() var injector: Injector = _ + @Inject() implicit var system: ActorSystem = _ + + def get( ) = { + val instance = injector instanceOf bind[ RedisInstance ].qualifiedWith( cacheName ) + + RedisRuntime( + instance = instance, + recovery = injector instanceOf bind[ RecoveryPolicy ].qualifiedWith( instance.recovery ) + ) + } +} diff --git a/src/main/scala/play/api/cache/redis/connector/RedisConnector.scala b/src/main/scala/play/api/cache/redis/connector/RedisConnector.scala index 458e8e50..24d5340e 100644 --- a/src/main/scala/play/api/cache/redis/connector/RedisConnector.scala +++ b/src/main/scala/play/api/cache/redis/connector/RedisConnector.scala @@ -454,10 +454,3 @@ trait RedisConnector extends AnyRef with ListCommands with SetCommands with HashCommands -{ - /** implicit execution context */ - implicit def context: ExecutionContext - - /** implicit ask timeout */ - implicit def timeout: Timeout -} diff --git a/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala b/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala index 9b15819e..9815425e 100644 --- a/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala +++ b/src/main/scala/play/api/cache/redis/connector/RedisConnectorImpl.scala @@ -7,7 +7,6 @@ import scala.reflect.ClassTag import play.api.Logger import play.api.cache.redis._ -import akka.actor.ActorSystem import redis._ /** @@ -15,18 +14,12 @@ import redis._ * and is supposed to by used internally by another wrappers. The connector does not * directly implement [[play.api.cache.redis.CacheApi]] but provides fundamental functionality. * - * @param serializer encodes/decodes objects into/from a string - * @param configuration connection settings - * @param redis: implementation of the commands + * @param serializer encodes/decodes objects into/from a string + * @param redis implementation of the commands * @author Karel Cemus */ -private[ connector ] class RedisConnectorImpl( serializer: AkkaSerializer, configuration: RedisInstance, redis: RedisCommands )( implicit system: ActorSystem ) extends RedisConnector { - - // implicit ask timeout - implicit val timeout = akka.util.Timeout( configuration.timeout ) - - /** implicit execution context */ - implicit val context = system.dispatchers.lookup( configuration.invocationContext ) +private[ connector ] class RedisConnectorImpl( serializer: AkkaSerializer, redis: RedisCommands )( implicit runtime: RedisRuntime ) extends RedisConnector { + import runtime._ /** logger instance */ protected val log = Logger( "play.api.cache.redis" ) @@ -331,5 +324,5 @@ private[ connector ] class RedisConnectorImpl( serializer: AkkaSerializer, confi case values => log.debug( s"The collection at '$key' contains ${ values.size } values." ); values.map( decode[ T ]( key, _ ) ).toSet } - override def toString = s"RedisConnector(name=${ configuration.name })" + override def toString = s"RedisConnector(name=$name)" } diff --git a/src/main/scala/play/api/cache/redis/connector/RedisConnectorProvider.scala b/src/main/scala/play/api/cache/redis/connector/RedisConnectorProvider.scala index c60d9ebc..4fc4c659 100644 --- a/src/main/scala/play/api/cache/redis/connector/RedisConnectorProvider.scala +++ b/src/main/scala/play/api/cache/redis/connector/RedisConnectorProvider.scala @@ -2,7 +2,7 @@ package play.api.cache.redis.connector import javax.inject.{Inject, Provider} -import play.api.cache.redis.configuration.RedisInstance +import play.api.cache.redis._ import play.api.inject.{Injector, bind} import akka.actor.ActorSystem @@ -19,8 +19,8 @@ class RedisConnectorProvider( name: String ) extends Provider[ RedisConnector ] @Inject private var serializer: AkkaSerializer = _ @Inject private implicit var system: ActorSystem = _ - private def instance = bind[ RedisInstance ].qualifiedWith( name ) + private def instance = bind[ RedisRuntime ].qualifiedWith( name ) private def commands = bind[ RedisCommands ].qualifiedWith( name ) - lazy val get = new RedisConnectorImpl( serializer, injector instanceOf instance, injector instanceOf commands ) + lazy val get = new RedisConnectorImpl( serializer, injector instanceOf commands )( injector instanceOf instance ) } diff --git a/src/main/scala/play/api/cache/redis/impl/AsyncRedis.scala b/src/main/scala/play/api/cache/redis/impl/AsyncRedis.scala index 90320b2d..7a98620e 100644 --- a/src/main/scala/play/api/cache/redis/impl/AsyncRedis.scala +++ b/src/main/scala/play/api/cache/redis/impl/AsyncRedis.scala @@ -11,8 +11,8 @@ import play.api.cache.redis._ * * @author Karel Cemus */ -private[ impl ] class AsyncRedis( name: String, redis: RedisConnector, policy: RecoveryPolicy ) - extends RedisCache( name, redis )( Builders.AsynchronousBuilder, policy ) +private[ impl ] class AsyncRedis( redis: RedisConnector )( implicit runtime: RedisRuntime ) + extends RedisCache( redis, Builders.AsynchronousBuilder ) with play.api.cache.AsyncCacheApi with CacheAsyncApi { diff --git a/src/main/scala/play/api/cache/redis/impl/Builders.scala b/src/main/scala/play/api/cache/redis/impl/Builders.scala index 7c2e545f..6e265cfa 100644 --- a/src/main/scala/play/api/cache/redis/impl/Builders.scala +++ b/src/main/scala/play/api/cache/redis/impl/Builders.scala @@ -1,6 +1,6 @@ package play.api.cache.redis.impl -import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.Future import scala.language.higherKinds /** @@ -9,16 +9,15 @@ import scala.language.higherKinds * @author Karel Cemus */ object Builders { - + import dsl._ import play.api.cache.redis._ - import akka.pattern.AskTimeoutException trait ResultBuilder[ Result[ X ] ] { /** name of the builder used for internal purposes */ def name: String = this.getClass.getSimpleName /** converts future result produced by Redis to the result of desired type */ - def toResult[ T ]( run: => Future[ T ], default: => Future[ T ] )( implicit policy: RecoveryPolicy, context: ExecutionContext, timeout: akka.util.Timeout ): Result[ T ] + def toResult[ T ]( run: => Future[ T ], default: => Future[ T ] )( implicit runtime: RedisRuntime ): Result[ T ] /** show the builder name */ override def toString = s"ResultBuilder($name)" } @@ -28,10 +27,10 @@ object Builders { override def name = "AsynchronousBuilder" - override def toResult[ T ]( run: => Future[ T ], default: => Future[ T ] )( implicit policy: RecoveryPolicy, context: ExecutionContext, timeout: akka.util.Timeout ): AsynchronousResult[ T ] = + override def toResult[ T ]( run: => Future[ T ], default: => Future[ T ] )( implicit runtime: RedisRuntime ): AsynchronousResult[ T ] = run recoverWith { // recover from known exceptions - case failure: RedisException => policy.recoverFrom( run, default, failure ) + case failure: RedisException => runtime.policy.recoverFrom( run, default, failure ) } } @@ -43,17 +42,16 @@ object Builders { override def name = "SynchronousBuilder" - override def toResult[ T ]( run: => Future[ T ], default: => Future[ T ] )( implicit policy: RecoveryPolicy, context: ExecutionContext, timeout: akka.util.Timeout ): SynchronousResult[ T ] = + override def toResult[ T ]( run: => Future[ T ], default: => Future[ T ] )( implicit runtime: RedisRuntime ): SynchronousResult[ T ] = Try { // wait for the result - Await.result( run, timeout.duration ) + Await.result( run, runtime.timeout.duration ) }.recover { // it timed out, produce an expected exception case cause: AskTimeoutException => timedOut( cause ) }.recover { // apply recovery policy to recover from expected exceptions - case failure: RedisException => Await.result( policy.recoverFrom( run, default, failure ), timeout.duration ) + case failure: RedisException => Await.result( runtime.policy.recoverFrom( run, default, failure ), runtime.timeout.duration ) }.get // finally, regardless the recovery status, get the synchronous result } - } 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 23ccd57d..4fb19f72 100644 --- a/src/main/scala/play/api/cache/redis/impl/JavaRedis.scala +++ b/src/main/scala/play/api/cache/redis/impl/JavaRedis.scala @@ -17,10 +17,9 @@ import play.api.cache.redis._ * * @author Karel Cemus */ -private[ impl ] class JavaRedis( name: String, internal: CacheAsyncApi, environment: Environment, connector: RedisConnector ) extends play.cache.AsyncCacheApi { - +private[ impl ] class JavaRedis( internal: CacheAsyncApi, environment: Environment )( implicit runtime: RedisRuntime ) extends play.cache.AsyncCacheApi { + import dsl._ import JavaRedis._ - import connector.context def set( key: String, value: scala.Any, expiration: Int ): CompletionStage[ Done ] = set( key, value, expiration.seconds ).toJava diff --git a/src/main/scala/play/api/cache/redis/impl/RedisCache.scala b/src/main/scala/play/api/cache/redis/impl/RedisCache.scala index 7dcd7209..892f0480 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisCache.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisCache.scala @@ -8,12 +8,13 @@ import scala.reflect.ClassTag import play.api.cache.redis._ /**

Implementation of plain API using redis-server cache and Brando connector implementation.

*/ -private[ impl ] class RedisCache[ Result[ _ ] ]( name: String, redis: RedisConnector )( implicit protected val implicitBuilder: Builders.ResultBuilder[ Result ], protected val implicitPolicy: RecoveryPolicy ) extends AbstractCacheApi[ Result ] { +private[ impl ] class RedisCache[ Result[ _ ] ]( redis: RedisConnector, builder: Builders.ResultBuilder[ Result ] )( implicit runtime: RedisRuntime ) extends AbstractCacheApi[ Result ] { // implicit ask timeout and execution context - import redis.{context, timeout} import dsl._ + @inline implicit protected def implicitBuilder: Builders.ResultBuilder[ Result ] = builder + def get[ T: ClassTag ]( key: String ) = redis.get[ T ]( key ).recoverWithDefault( None ) @@ -92,5 +93,5 @@ private[ impl ] class RedisCache[ Result[ _ ] ]( name: String, redis: RedisConne def map[ T: ClassTag ]( key: String ): RedisMap[ T, Result ] = new RedisMapImpl( key, redis ) - override def toString = s"RedisCache(name=$name)" + override def toString = s"RedisCache(name=${ runtime.name })" } diff --git a/src/main/scala/play/api/cache/redis/impl/RedisListImpl.scala b/src/main/scala/play/api/cache/redis/impl/RedisListImpl.scala index cecf8457..fc48c0f5 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisListImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisListImpl.scala @@ -6,10 +6,9 @@ import scala.reflect.ClassTag import play.api.cache.redis._ /**

Implementation of List API using redis-server cache implementation.

*/ -private[ impl ] class RedisListImpl[ Elem: ClassTag, Result[ _ ] ]( key: String, redis: RedisConnector )( implicit builder: Builders.ResultBuilder[ Result ], policy: RecoveryPolicy ) extends RedisList[ Elem, Result ] { +private[ impl ] class RedisListImpl[ Elem: ClassTag, Result[ _ ] ]( key: String, redis: RedisConnector )( implicit builder: Builders.ResultBuilder[ Result ], runtime: RedisRuntime ) extends RedisList[ Elem, Result ] { // implicit ask timeout and execution context - import redis.{context, timeout} import dsl._ @inline diff --git a/src/main/scala/play/api/cache/redis/impl/RedisMapImpl.scala b/src/main/scala/play/api/cache/redis/impl/RedisMapImpl.scala index 25fcd983..7429ee09 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisMapImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisMapImpl.scala @@ -6,10 +6,9 @@ import scala.reflect.ClassTag import play.api.cache.redis._ /**

Implementation of Set API using redis-server cache implementation.

*/ -private[ impl ] class RedisMapImpl[ Elem: ClassTag, Result[ _ ] ]( key: String, redis: RedisConnector )( implicit builder: Builders.ResultBuilder[ Result ], policy: RecoveryPolicy ) extends RedisMap[ Elem, Result ] { +private[ impl ] class RedisMapImpl[ Elem: ClassTag, Result[ _ ] ]( key: String, redis: RedisConnector )( implicit builder: Builders.ResultBuilder[ Result ], runtime: RedisRuntime ) extends RedisMap[ Elem, Result ] { // implicit ask timeout and execution context - import redis.{context, timeout} import dsl._ @inline diff --git a/src/main/scala/play/api/cache/redis/impl/RedisSetImpl.scala b/src/main/scala/play/api/cache/redis/impl/RedisSetImpl.scala index 1caf1f2d..c7df1634 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisSetImpl.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisSetImpl.scala @@ -6,10 +6,9 @@ import scala.reflect.ClassTag import play.api.cache.redis._ /**

Implementation of Set API using redis-server cache implementation.

*/ -private[ impl ] class RedisSetImpl[ Elem: ClassTag, Result[ _ ] ]( key: String, redis: RedisConnector )( implicit builder: Builders.ResultBuilder[ Result ], policy: RecoveryPolicy ) extends RedisSet[ Elem, Result ] { +private[ impl ] class RedisSetImpl[ Elem: ClassTag, Result[ _ ] ]( key: String, redis: RedisConnector )( implicit builder: Builders.ResultBuilder[ Result ], runtime: RedisRuntime ) extends RedisSet[ Elem, Result ] { // implicit ask timeout and execution context - import redis.{context, timeout} import dsl._ @inline diff --git a/src/main/scala/play/api/cache/redis/impl/SyncRedis.scala b/src/main/scala/play/api/cache/redis/impl/SyncRedis.scala index 602c269d..f5e441aa 100644 --- a/src/main/scala/play/api/cache/redis/impl/SyncRedis.scala +++ b/src/main/scala/play/api/cache/redis/impl/SyncRedis.scala @@ -10,13 +10,8 @@ import play.api.cache.redis._ * * @author Karel Cemus */ -private[ impl ] class SyncRedis( name: String, redis: RedisConnector, policy: RecoveryPolicy ) - extends RedisCache( name: String, redis )( Builders.SynchronousBuilder, policy ) - with CacheApi +private[ impl ] class SyncRedis( redis: RedisConnector )( implicit runtime: RedisRuntime ) extends RedisCache( redis, Builders.SynchronousBuilder ) with CacheApi { - - // implicit ask timeout and execution context - import redis.{context, timeout} // helpers for dsl import dsl._ diff --git a/src/main/scala/play/api/cache/redis/impl/dsl.scala b/src/main/scala/play/api/cache/redis/impl/dsl.scala index 1bfc210c..64f84acc 100644 --- a/src/main/scala/play/api/cache/redis/impl/dsl.scala +++ b/src/main/scala/play/api/cache/redis/impl/dsl.scala @@ -12,6 +12,8 @@ import play.api.cache.redis._ */ private[ impl ] object dsl { + @inline implicit def runtime2context( implicit runtime: RedisRuntime ): ExecutionContext = runtime.context + /** enriches any ref by toFuture converting a value to Future.successful */ implicit class RichFuture[ T ]( val any: T ) extends AnyVal { @inline def toFuture: Future[ T ] = Future.successful( any ) @@ -21,21 +23,21 @@ private[ impl ] object dsl { implicit class RecoveryFuture[ T ]( val future: Future[ T ] ) extends AnyVal { /** Transforms the promise into desired builder results, possibly recovers with provided default value */ - @inline def recoverWithDefault[ Result[ X ] ]( default: => T )( implicit builder: Builders.ResultBuilder[ Result ], policy: RecoveryPolicy, context: ExecutionContext, timeout: akka.util.Timeout ): Result[ T ] = + @inline def recoverWithDefault[ Result[ X ] ]( default: => T )( implicit builder: Builders.ResultBuilder[ Result ], runtime: RedisRuntime ): Result[ T ] = builder.toResult( future, Future.successful( default ) ) /** recovers from the execution but returns future, not Result */ - @inline def recoverWithFuture( default: => Future[ T ] )( implicit policy: RecoveryPolicy, context: ExecutionContext ): Future[ T ] = + @inline def recoverWithFuture( default: => Future[ T ] )( implicit runtime: RedisRuntime ): Future[ T ] = future recoverWith { // recover from known exceptions - case failure: RedisException => policy.recoverFrom( future, default, failure ) + case failure: RedisException => runtime.policy.recoverFrom( future, default, failure ) } } /** helper function enabling us to recover from command execution */ implicit class RecoveryUnitFuture( val future: Future[ Unit ] ) extends AnyVal { /** Transforms the promise into desired builder results, possibly recovers with provided default value */ - @inline def recoverWithDone[ Result[ X ] ]( implicit builder: Builders.ResultBuilder[ Result ], policy: RecoveryPolicy, context: ExecutionContext, timeout: akka.util.Timeout ): Result[ Done ] = + @inline def recoverWithDone[ Result[ X ] ]( implicit builder: Builders.ResultBuilder[ Result ], runtime: RedisRuntime ): Result[ Done ] = builder.toResult( future.map( unitAsDone ), Future.successful( Done ) ) } diff --git a/src/test/scala/play/api/cache/redis/impl/AsynchronousCacheSpec.scala b/src/test/scala/play/api/cache/redis/impl/AsynchronousCacheSpec.scala index d39bd82a..4b8082d3 100644 --- a/src/test/scala/play/api/cache/redis/impl/AsynchronousCacheSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/AsynchronousCacheSpec.scala @@ -3,7 +3,7 @@ package play.api.cache.redis.impl import java.util.Date import java.util.concurrent.atomic.AtomicInteger -import scala.concurrent.Future +import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global @@ -21,14 +21,16 @@ class AsynchronousCacheSpec extends Specification with Redis { private val workingConnector = injector.instanceOf[ RedisConnector ] + def runtime( policy: RecoveryPolicy ) = RedisRuntime( "play", 3.minutes, ExecutionContext.Implicits.global, policy ) + // test proper implementation, no fails - new RedisCacheSuite( "implement", "redis-cache-implements", new RedisCache( "play", workingConnector )( Builders.AsynchronousBuilder, FailThrough ), AlwaysSuccess ) + new RedisCacheSuite( "implement", "redis-cache-implements", new RedisCache( workingConnector, Builders.AsynchronousBuilder )( runtime( FailThrough ) ), AlwaysSuccess ) - new RedisCacheSuite( "recover from with working connector", "redis-cache-implements-and-recovery", new RedisCache( "play", workingConnector )( Builders.AsynchronousBuilder, RecoverWithDefault ), SuccessOrDefault ) + new RedisCacheSuite( "recover from with working connector", "redis-cache-implements-and-recovery", new RedisCache( workingConnector, Builders.AsynchronousBuilder )( runtime( RecoverWithDefault ) ), SuccessOrDefault ) - new RedisCacheSuite( "recover from", "redis-cache-recovery", new RedisCache( "play", FailingConnector )( Builders.AsynchronousBuilder, RecoverWithDefault ), AlwaysDefault ) + new RedisCacheSuite( "recover from", "redis-cache-recovery", new RedisCache( FailingConnector, Builders.AsynchronousBuilder )( runtime( RecoverWithDefault ) ), AlwaysDefault ) - new RedisCacheSuite( "fail on", "redis-cache-fail", new RedisCache( "play", FailingConnector )( Builders.AsynchronousBuilder, FailThrough ), AlwaysException ) + new RedisCacheSuite( "fail on", "redis-cache-fail", new RedisCache( FailingConnector, Builders.AsynchronousBuilder )( runtime( FailThrough ) ), AlwaysException ) class RedisCacheSuite( suiteName: String, prefix: String, cache: Cache, expectation: Expectation ) { diff --git a/src/test/scala/play/api/cache/redis/impl/RedisListSpec.scala b/src/test/scala/play/api/cache/redis/impl/RedisListSpec.scala index 51244f52..7855acbe 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisListSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisListSpec.scala @@ -1,5 +1,7 @@ package play.api.cache.redis.impl +import scala.concurrent.ExecutionContext +import scala.concurrent.duration._ import scala.reflect.ClassTag import play.api.cache.redis._ @@ -16,12 +18,14 @@ class RedisListSpec extends Specification with Redis { private val workingConnector = injector.instanceOf[ RedisConnector ] + def runtime( policy: RecoveryPolicy ) = RedisRuntime( "play", 3.minutes, ExecutionContext.Implicits.global, policy ) + // test proper implementation, no fails - new RedisListSuite( "implement", "redis-cache-implements", new RedisCache( "play", workingConnector )( Builders.SynchronousBuilder, FailThrough ), AlwaysSuccess ) + new RedisListSuite( "implement", "redis-cache-implements", new RedisCache( workingConnector, Builders.SynchronousBuilder )( runtime( FailThrough ) ), AlwaysSuccess ) - new RedisListSuite( "recover from", "redis-cache-recovery", new RedisCache( "play", FailingConnector )( Builders.SynchronousBuilder, RecoverWithDefault ), AlwaysDefault ) + new RedisListSuite( "recover from", "redis-cache-recovery", new RedisCache( FailingConnector, Builders.SynchronousBuilder )( runtime( RecoverWithDefault ) ), AlwaysDefault ) - new RedisListSuite( "fail on", "redis-cache-fail", new RedisCache( "play", FailingConnector )( Builders.SynchronousBuilder, FailThrough ), AlwaysException ) + new RedisListSuite( "fail on", "redis-cache-fail", new RedisCache( FailingConnector, Builders.SynchronousBuilder )( runtime( FailThrough ) ), AlwaysException ) class RedisListSuite( suiteName: String, prefix: String, cache: Cache, expectation: Expectation ) { diff --git a/src/test/scala/play/api/cache/redis/impl/RedisMapSpecs.scala b/src/test/scala/play/api/cache/redis/impl/RedisMapSpecs.scala index b5388cd2..4166fed8 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisMapSpecs.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisMapSpecs.scala @@ -1,5 +1,7 @@ package play.api.cache.redis.impl +import scala.concurrent.ExecutionContext +import scala.concurrent.duration._ import scala.reflect.ClassTag import play.api.cache.redis._ @@ -18,12 +20,14 @@ class RedisMapSpecs extends Specification with Redis { private val workingConnector = injector.instanceOf[ RedisConnector ] + implicit def runtime( policy: RecoveryPolicy ) = RedisRuntime( "play", 3.minutes, ExecutionContext.Implicits.global, policy ) + // test proper implementation, no fails - new RedisMapSuite( "implement", "redis-cache-implements", new RedisCache( "play", workingConnector )( Builders.SynchronousBuilder, FailThrough ), AlwaysSuccess ) + new RedisMapSuite( "implement", "redis-cache-implements", new RedisCache( workingConnector, Builders.SynchronousBuilder )( FailThrough ), AlwaysSuccess ) - new RedisMapSuite( "recover from", "redis-cache-recovery", new RedisCache( "play", FailingConnector )( Builders.SynchronousBuilder, RecoverWithDefault ), AlwaysDefault ) + new RedisMapSuite( "recover from", "redis-cache-recovery", new RedisCache( FailingConnector, Builders.SynchronousBuilder )( RecoverWithDefault ), AlwaysDefault ) - new RedisMapSuite( "fail on", "redis-cache-fail", new RedisCache( "play", FailingConnector )( Builders.SynchronousBuilder, FailThrough ), AlwaysException ) + new RedisMapSuite( "fail on", "redis-cache-fail", new RedisCache( FailingConnector, Builders.SynchronousBuilder )( FailThrough ), AlwaysException ) class RedisMapSuite( suiteName: String, prefix: String, cache: Cache, expectation: Expectation ) { diff --git a/src/test/scala/play/api/cache/redis/impl/RedisSetSpecs.scala b/src/test/scala/play/api/cache/redis/impl/RedisSetSpecs.scala index 14eb63f9..4e7accde 100644 --- a/src/test/scala/play/api/cache/redis/impl/RedisSetSpecs.scala +++ b/src/test/scala/play/api/cache/redis/impl/RedisSetSpecs.scala @@ -1,5 +1,7 @@ package play.api.cache.redis.impl +import scala.concurrent.ExecutionContext +import scala.concurrent.duration._ import scala.reflect.ClassTag import play.api.cache.redis._ @@ -18,12 +20,14 @@ class RedisSetSpecs extends Specification with Redis { private val workingConnector = injector.instanceOf[ RedisConnector ] + implicit def runtime( policy: RecoveryPolicy ) = RedisRuntime( "play", 3.minutes, ExecutionContext.Implicits.global, policy ) + // test proper implementation, no fails - new RedisSetSuite( "implement", "redis-cache-implements", new RedisCache( "play", workingConnector )( Builders.SynchronousBuilder, FailThrough ), AlwaysSuccess ) + new RedisSetSuite( "implement", "redis-cache-implements", new RedisCache( workingConnector, Builders.SynchronousBuilder )( FailThrough ), AlwaysSuccess ) - new RedisSetSuite( "recover from", "redis-cache-recovery", new RedisCache( "play", FailingConnector )( Builders.SynchronousBuilder, RecoverWithDefault ), AlwaysDefault ) + new RedisSetSuite( "recover from", "redis-cache-recovery", new RedisCache( FailingConnector, Builders.SynchronousBuilder )( RecoverWithDefault ), AlwaysDefault ) - new RedisSetSuite( "fail on", "redis-cache-fail", new RedisCache( "play", FailingConnector )( Builders.SynchronousBuilder, FailThrough ), AlwaysException ) + new RedisSetSuite( "fail on", "redis-cache-fail", new RedisCache( FailingConnector, Builders.SynchronousBuilder )( FailThrough ), AlwaysException ) class RedisSetSuite( suiteName: String, prefix: String, cache: Cache, expectation: Expectation ) { 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 8c7c3c15..322a0dbc 100644 --- a/src/test/scala/play/api/cache/redis/impl/package.scala +++ b/src/test/scala/play/api/cache/redis/impl/package.scala @@ -15,11 +15,11 @@ package object impl extends LowPriorityImplicits { val FailingConnector = connector.FailingConnector - val FailThrough = new impl.FailThrough { + val FailThrough = new FailThrough { override def name = "FailThrough" } - val RecoverWithDefault = new impl.RecoverWithDefault { + val RecoverWithDefault = new RecoverWithDefault { override def name = "RecoverWithDefault" } From e18ac43af406ab8fb089c02dcb778a2adca1f61e Mon Sep 17 00:00:00 2001 From: Karel Cemus Date: Mon, 25 Sep 2017 00:53:50 +0200 Subject: [PATCH 3/8] introduced RedisCaches and redesigned Components and Module --- .../play/api/cache/redis/RecoveryPolicy.scala | 28 ++++++++++++++ .../redis/connector/AkkaSerializer.scala | 7 +++- ...andsProvider.scala => RedisCommands.scala} | 10 +---- .../connector/RedisConnectorProvider.scala | 16 +++----- .../api/cache/redis/impl/RedisCaches.scala | 38 +++++++++++++++++++ 5 files changed, 79 insertions(+), 20 deletions(-) rename src/main/scala/play/api/cache/redis/connector/{RedisCommandsProvider.scala => RedisCommands.scala} (89%) create mode 100644 src/main/scala/play/api/cache/redis/impl/RedisCaches.scala diff --git a/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala b/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala index 765a06c3..703c65ee 100644 --- a/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala +++ b/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala @@ -5,6 +5,7 @@ import javax.inject.Inject import scala.concurrent.Future import play.api.Logger +import play.api.inject._ /** Recovery policy triggers when a request fails. Based on the implementation, * it may try it again, recover with a default value or just simply log the @@ -148,3 +149,30 @@ private[ redis ] class LogCondensedAndDefaultPolicy @Inject( )( ) extends Recove * @author Karel Cemus */ private[ redis ] class LogCondensedAndFailPolicy @Inject( )( ) extends FailThrough with CondensedReports + +/** + * This resolver represents an abstraction over translation + * of the policy name into the instance. It has two subclasses, + * one for guice and the other for compile-time injection. + */ +trait RecoveryPolicyResolver { + def resolve( name: String ): RecoveryPolicy +} + +object RecoveryPolicyResolver { + + def bindings = Seq( + bind[ RecoveryPolicy ].qualifiedWith( "log-and-fail" ).to[ LogAndFailPolicy ], + bind[ RecoveryPolicy ].qualifiedWith( "log-and-default" ).to[ LogAndDefaultPolicy ], + bind[ RecoveryPolicy ].qualifiedWith( "log-condensed-and-fail" ).to[ LogCondensedAndFailPolicy ], + bind[ RecoveryPolicy ].qualifiedWith( "log-condensed-and-default" ).to[ LogCondensedAndDefaultPolicy ], + // finally bind the resolver + bind[ RecoveryPolicyResolver ].to[ RecoveryPolicyResolverGuice ] + ) +} + +/** resolves a policies with guice enabled */ +class RecoveryPolicyResolverGuice @Inject( )( injector: Injector ) extends RecoveryPolicyResolver { + + def resolve( name: String ) = injector instanceOf bind[ RecoveryPolicy ].qualifiedWith( name ) +} diff --git a/src/main/scala/play/api/cache/redis/connector/AkkaSerializer.scala b/src/main/scala/play/api/cache/redis/connector/AkkaSerializer.scala index 8959f6d2..9a5d550c 100644 --- a/src/main/scala/play/api/cache/redis/connector/AkkaSerializer.scala +++ b/src/main/scala/play/api/cache/redis/connector/AkkaSerializer.scala @@ -1,6 +1,6 @@ package play.api.cache.redis.connector -import javax.inject.{Inject, Singleton} +import javax.inject._ import scala.language.implicitConversions import scala.reflect.ClassTag @@ -87,6 +87,7 @@ private[ connector ] class AkkaEncoder( serializer: Serialization ) { private[ connector ] class AkkaDecoder( serializer: Serialization ) { import scala.reflect.{ClassTag => Scala} + import play.api.cache.redis.connector.{JavaClassTag => Java} private val Nothing = ClassTag( classOf[ Nothing ] ) @@ -196,3 +197,7 @@ private[ connector ] object JavaClassTag { val Boolean = ClassTag( classOf[ java.lang.Boolean ] ) val String = ClassTag( classOf[ String ] ) } + +class AkkaSerializerProvider @Inject()( implicit system: ActorSystem ) extends Provider[ AkkaSerializer ] { + lazy val get = new AkkaSerializerImpl( system ) +} diff --git a/src/main/scala/play/api/cache/redis/connector/RedisCommandsProvider.scala b/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala similarity index 89% rename from src/main/scala/play/api/cache/redis/connector/RedisCommandsProvider.scala rename to src/main/scala/play/api/cache/redis/connector/RedisCommands.scala index 4905193a..64fc91fb 100644 --- a/src/main/scala/play/api/cache/redis/connector/RedisCommandsProvider.scala +++ b/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala @@ -17,15 +17,9 @@ import redis.{RedisClient => RedisStandaloneClient, RedisCluster => RedisCluster * * @author Karel Cemus */ -private[ connector ] class RedisCommandsProvider( name: String ) extends Provider[ RedisCommands ] { +private[ connector ] class RedisCommandsProvider( instance: RedisInstance )( implicit system: ActorSystem, lifecycle: ApplicationLifecycle ) extends Provider[ RedisCommands ] { - @Inject private var injector: Injector = _ - @Inject private implicit var system: ActorSystem = _ - @Inject private implicit var lifecycle: ApplicationLifecycle = _ - - private def instance = bind[ RedisInstance ].qualifiedWith( name ) - - lazy val get = injector instanceOf instance match { + lazy val get = instance match { case cluster: RedisCluster => new RedisCommandsCluster( cluster ).get case standalone: RedisStandalone => new RedisCommandsStandalone( standalone ).get } diff --git a/src/main/scala/play/api/cache/redis/connector/RedisConnectorProvider.scala b/src/main/scala/play/api/cache/redis/connector/RedisConnectorProvider.scala index 4fc4c659..3d45e256 100644 --- a/src/main/scala/play/api/cache/redis/connector/RedisConnectorProvider.scala +++ b/src/main/scala/play/api/cache/redis/connector/RedisConnectorProvider.scala @@ -1,26 +1,20 @@ package play.api.cache.redis.connector -import javax.inject.{Inject, Provider} +import javax.inject.Provider import play.api.cache.redis._ -import play.api.inject.{Injector, bind} +import play.api.inject.ApplicationLifecycle import akka.actor.ActorSystem -import redis.RedisCommands /** * Provides an instance of named redis connector * * @author Karel Cemus */ -class RedisConnectorProvider( name: String ) extends Provider[ RedisConnector ] { +private[ redis ] class RedisConnectorProvider( instance: RedisInstance, serializer: AkkaSerializer )( implicit system: ActorSystem, lifecycle: ApplicationLifecycle, runtime: RedisRuntime ) extends Provider[ RedisConnector ] { - @Inject private var injector: Injector = _ - @Inject private var serializer: AkkaSerializer = _ - @Inject private implicit var system: ActorSystem = _ + private lazy val commands = new RedisCommandsProvider( instance ).get - private def instance = bind[ RedisRuntime ].qualifiedWith( name ) - private def commands = bind[ RedisCommands ].qualifiedWith( name ) - - lazy val get = new RedisConnectorImpl( serializer, injector instanceOf commands )( injector instanceOf instance ) + lazy val get = new RedisConnectorImpl( serializer, commands ) } diff --git a/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala b/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala new file mode 100644 index 00000000..603bd39b --- /dev/null +++ b/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala @@ -0,0 +1,38 @@ +package play.api.cache.redis.impl + +import javax.inject.Provider + +import play.api.Environment +import play.api.cache.redis._ +import play.api.inject.ApplicationLifecycle + +import akka.actor.ActorSystem + +/** + * Aggregates all available redis APIs into a single handler. This simplifies + * binding, construction, and accessing all APIs. + * + * @author Karel Cemus + */ +trait RedisCaches { + def sync: CacheApi + def async: CacheAsyncApi + def scalaSync: play.api.cache.SyncCacheApi + def javaSync: play.cache.SyncCacheApi + def javaAsync: play.cache.AsyncCacheApi +} + +class RedisCachesProvider( instance: RedisInstance, serializer: connector.AkkaSerializer, environment: Environment, recovery: RecoveryPolicyResolver )( implicit system: ActorSystem, lifecycle: ApplicationLifecycle ) extends Provider[ RedisCaches ] { + implicit lazy val runtime: RedisRuntime = RedisRuntime( instance, recovery.resolve( instance.recovery ) )( system ) + + lazy val redisConnector = new connector.RedisConnectorProvider( instance, serializer ).get + + lazy val get = new RedisCaches { + lazy val async = new AsyncRedis( redisConnector ) + lazy val sync = new SyncRedis( redisConnector ) + lazy val scalaSync = new play.api.cache.DefaultSyncCacheApi( async ) + lazy val java = new JavaRedis( async, environment ) + lazy val javaAsync = new play.cache.DefaultAsyncCacheApi( async ) + lazy val javaSync = new play.cache.DefaultSyncCacheApi( java ) + } +} From 1f9cbd01c4a64ef82f13b8172488790a7642533c Mon Sep 17 00:00:00 2001 From: Karel Cemus Date: Mon, 25 Sep 2017 11:27:01 +0200 Subject: [PATCH 4/8] Removed binder, replaced by a provider --- .../configuration/RedisInstanceManager.scala | 18 ++++---- ...nder.scala => RedisInstanceProvider.scala} | 42 ++++++++----------- 2 files changed, 27 insertions(+), 33 deletions(-) rename src/main/scala/play/api/cache/redis/configuration/{RedisInstanceBinder.scala => RedisInstanceProvider.scala} (70%) diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisInstanceManager.scala b/src/main/scala/play/api/cache/redis/configuration/RedisInstanceManager.scala index 144b6134..6e282a8d 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisInstanceManager.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisInstanceManager.scala @@ -18,24 +18,24 @@ import com.typesafe.config.Config * * @author Karel Cemus */ -trait RedisInstanceManager extends Traversable[ RedisInstanceBinder ] { +trait RedisInstanceManager extends Traversable[ RedisInstanceProvider ] { /** names of all known redis caches */ def caches: Set[ String ] /** returns a configuration of a single named redis instance */ - def instanceOfOption( name: String ): Option[ RedisInstanceBinder ] + def instanceOfOption( name: String ): Option[ RedisInstanceProvider ] /** returns a configuration of a single named redis instance */ - def instanceOf( name: String ): RedisInstanceBinder = instanceOfOption( name ) getOrElse { + def instanceOf( name: String ): RedisInstanceProvider = instanceOfOption( name ) getOrElse { throw new IllegalArgumentException( s"There is no cache named '$name'." ) } /** traverse all binders */ - def foreach[ U ]( f: RedisInstanceBinder => U ) = caches.view.flatMap( instanceOfOption ).foreach( f ) + def foreach[ U ]( f: RedisInstanceProvider => U ) = caches.view.flatMap( instanceOfOption ).foreach( f ) } -private[ configuration ] object RedisInstanceManager extends ConfigLoader[ RedisInstanceManager ] { +private[ redis ] object RedisInstanceManager extends ConfigLoader[ RedisInstanceManager ] { import RedisConfigLoader._ def load( config: Config, path: String ) = { @@ -59,8 +59,8 @@ class RedisInstanceManagerImpl( config: Config, path: String )( implicit default def caches: Set[ String ] = config.getObject( path / "instances" ).keySet.asScala.toSet /** returns a configuration of a single named redis instance */ - def instanceOfOption( name: String ): Option[ RedisInstanceBinder ] = - if ( config hasPath ( path / "instances" / name ) ) Some( RedisInstanceBinder.load( config, path / "instances" / name, name ) ) else None + def instanceOfOption( name: String ): Option[ RedisInstanceProvider ] = + if ( config hasPath ( path / "instances" / name ) ) Some( RedisInstanceProvider.load( config, path / "instances" / name, name ) ) else None } /** @@ -75,6 +75,6 @@ class RedisInstanceManagerFallback( config: Config, path: String )( implicit def def caches: Set[ String ] = Set( name ) /** returns a configuration of a single named redis instance */ - def instanceOfOption( name: String ): Option[ RedisInstanceBinder ] = - if ( name == this.name ) Some( RedisInstanceBinder.load( config, path, name ) ) else None + def instanceOfOption( name: String ): Option[ RedisInstanceProvider ] = + if ( name == this.name ) Some( RedisInstanceProvider.load( config, path, name ) ) else None } diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisInstanceBinder.scala b/src/main/scala/play/api/cache/redis/configuration/RedisInstanceProvider.scala similarity index 70% rename from src/main/scala/play/api/cache/redis/configuration/RedisInstanceBinder.scala rename to src/main/scala/play/api/cache/redis/configuration/RedisInstanceProvider.scala index ec928700..508ebe50 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisInstanceBinder.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisInstanceProvider.scala @@ -2,34 +2,28 @@ package play.api.cache.redis.configuration import scala.collection.JavaConverters._ -import play.api.inject._ - import com.typesafe.config.{Config, ConfigOrigin} /** * @author Karel Cemus */ -private[ configuration ] trait RedisInstanceBinder { +trait RedisInstanceResolver extends PartialFunction[ String, RedisInstance ] + +sealed trait RedisInstanceProvider extends Any { def name: String - protected def self = asBindingKey - def toBinding: List[ Binding[ RedisInstance ] ] - def asBindingKey: BindingKey[ RedisInstance ] = bind[ RedisInstance ].qualifiedWith( name ) - def toDefaultBinding = List( bind[ RedisInstance ].to( self ) ) - def instanceOption: Option[ RedisInstance ] + def resolved( implicit resolver: RedisInstanceResolver ): RedisInstance } -private[ configuration ] class RedisInstanceSelfBinder( val instance: RedisInstance ) extends RedisInstanceBinder { - val name = instance.name - def instanceOption = Some( instance ) - def toBinding = List( self toInstance instance ) +class ResolvedRedisInstance( val instance: RedisInstance ) extends AnyVal with RedisInstanceProvider { + def name: String = instance.name + def resolved( implicit resolver: RedisInstanceResolver ) = instance } -private[ configuration ] class RedisInstanceCustomBinder( val name: String ) extends RedisInstanceBinder { - def toBinding = List.empty - def instanceOption = None +class UnresolvedRedisInstance( val name: String ) extends AnyVal with RedisInstanceProvider { + def resolved( implicit resolver: RedisInstanceResolver ) = resolver apply name } -private[ configuration ] object RedisInstanceBinder extends RedisConfigInstanceLoader[ RedisInstanceBinder ] { +private[ configuration ] object RedisInstanceProvider extends RedisConfigInstanceLoader[ RedisInstanceProvider ] { import RedisConfigLoader._ def load( config: Config, path: String, name: String )( implicit defaults: RedisSettings ) = { @@ -60,8 +54,8 @@ private[ configuration ] object RedisInstanceBinder extends RedisConfigInstanceL /** * Statically configured single standalone redis instance */ -private[ configuration ] object RedisInstanceStandalone extends RedisConfigInstanceLoader[ RedisInstanceBinder ] { - def load( config: Config, path: String, instanceName: String )( implicit defaults: RedisSettings ) = new RedisInstanceSelfBinder ( +private[ configuration ] object RedisInstanceStandalone extends RedisConfigInstanceLoader[ RedisInstanceProvider ] { + def load( config: Config, path: String, instanceName: String )( implicit defaults: RedisSettings ) = new ResolvedRedisInstance ( RedisStandalone.apply( name = instanceName, host = RedisHost.load( config, path ), @@ -73,10 +67,10 @@ private[ configuration ] object RedisInstanceStandalone extends RedisConfigInsta /** * Statically configured redis cluster */ -private[ configuration ] object RedisInstanceCluster extends RedisConfigInstanceLoader[ RedisInstanceBinder ] { +private[ configuration ] object RedisInstanceCluster extends RedisConfigInstanceLoader[ RedisInstanceProvider ] { import RedisConfigLoader._ - def load( config: Config, path: String, instanceName: String )( implicit defaults: RedisSettings ) = new RedisInstanceSelfBinder( + def load( config: Config, path: String, instanceName: String )( implicit defaults: RedisSettings ) = new ResolvedRedisInstance( RedisCluster.apply( name = instanceName, nodes = config.getConfigList( path / "cluster" ).asScala.map( config => RedisHost.load( config ) ).toList, @@ -89,10 +83,10 @@ private[ configuration ] object RedisInstanceCluster extends RedisConfigInstance * Reads a configuration from the connection string, possibly from an environmental variable. * This instance configuration is designed to work in PaaS environments such as Heroku. */ -private[ configuration ] object RedisInstanceEnvironmental extends RedisConfigInstanceLoader[ RedisInstanceBinder ] { +private[ configuration ] object RedisInstanceEnvironmental extends RedisConfigInstanceLoader[ RedisInstanceProvider ] { import RedisConfigLoader._ - def load( config: Config, path: String, instanceName: String )( implicit defaults: RedisSettings ) = new RedisInstanceSelfBinder( + def load( config: Config, path: String, instanceName: String )( implicit defaults: RedisSettings ) = new ResolvedRedisInstance( RedisStandalone.apply( name = instanceName, host = RedisHost.fromConnectionString( config getString path./( "connection-string" ) ), @@ -104,8 +98,8 @@ private[ configuration ] object RedisInstanceEnvironmental extends RedisConfigIn /** * This binder indicates that the user provides his own configuration of this named cache. */ -private[ configuration ] object RedisInstanceCustom extends RedisConfigInstanceLoader[ RedisInstanceBinder ] { - def load( config: Config, path: String, instanceName: String )( implicit defaults: RedisSettings ) = new RedisInstanceCustomBinder( +private[ configuration ] object RedisInstanceCustom extends RedisConfigInstanceLoader[ RedisInstanceProvider ] { + def load( config: Config, path: String, instanceName: String )( implicit defaults: RedisSettings ) = new UnresolvedRedisInstance( name = instanceName ) } From 1940c26432f20aa2e286276c3090260a86d5004c Mon Sep 17 00:00:00 2001 From: Karel Cemus Date: Mon, 25 Sep 2017 13:29:09 +0200 Subject: [PATCH 5/8] Redesigned RedisCacheModule to be more simple --- .../play/api/cache/redis/RecoveryPolicy.scala | 15 +- .../cache/redis/RedisCacheComponents.scala | 130 +++------- .../api/cache/redis/RedisCacheModule.scala | 239 ++++++------------ .../api/cache/redis/impl/RedisCaches.scala | 4 +- .../scala/play/api/cache/redis/package.scala | 2 + .../cache/redis/RedisComponentsSpecs.scala | 2 +- .../RedisInstanceManagerSpec.scala | 13 +- .../configuration/RedisInstanceSpec.scala | 24 +- 8 files changed, 151 insertions(+), 278 deletions(-) diff --git a/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala b/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala index 703c65ee..83e1ed4d 100644 --- a/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala +++ b/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala @@ -155,8 +155,17 @@ private[ redis ] class LogCondensedAndFailPolicy @Inject( )( ) extends FailThrou * of the policy name into the instance. It has two subclasses, * one for guice and the other for compile-time injection. */ -trait RecoveryPolicyResolver { - def resolve( name: String ): RecoveryPolicy +trait RecoveryPolicyResolver extends PartialFunction[ String, RecoveryPolicy ] { + def isDefinedAt( name: String ) = lift.apply( name ).nonEmpty +} + +class RecoveryPolicyResolverImpl extends RecoveryPolicyResolver { + def apply( name: String ) = name match { + case "log-and-fail" => new LogAndFailPolicy + case "log-and-default" => new LogAndDefaultPolicy + case "log-condensed-and-fail" => new LogCondensedAndFailPolicy + case "log-condensed-and-default" => new LogCondensedAndDefaultPolicy + } } object RecoveryPolicyResolver { @@ -174,5 +183,5 @@ object RecoveryPolicyResolver { /** resolves a policies with guice enabled */ class RecoveryPolicyResolverGuice @Inject( )( injector: Injector ) extends RecoveryPolicyResolver { - def resolve( name: String ) = injector instanceOf bind[ RecoveryPolicy ].qualifiedWith( name ) + def apply( name: String ) = injector instanceOf bind[ RecoveryPolicy ].qualifiedWith( name ) } diff --git a/src/main/scala/play/api/cache/redis/RedisCacheComponents.scala b/src/main/scala/play/api/cache/redis/RedisCacheComponents.scala index 344d644a..2918b655 100644 --- a/src/main/scala/play/api/cache/redis/RedisCacheComponents.scala +++ b/src/main/scala/play/api/cache/redis/RedisCacheComponents.scala @@ -5,110 +5,44 @@ import scala.language.implicitConversions import play.api.inject.ApplicationLifecycle import play.api.{Configuration, Environment} -package configuration { - - private[ redis ] trait RedisConfigurationComponents { - - def configuration: Configuration - - implicit lazy val redisDefaults = configuration.get( "play.cache.redis" )( RedisSettings ) - - /** override this method to provide custom configuration for some instances */ - def redisInstanceConfiguration: PartialFunction[ String, RedisInstance ] = PartialFunction.empty - - private def hasInstances = configuration.underlying.hasPath( "play.cache.redis.instances" ) - - private def defaultCache = configuration.underlying.getString( "play.cache.redis.default-cache" ) - - implicit def redisInstance( name: String ): RedisInstance = configuration.get { - if ( hasInstances ) s"play.cache.redis.instances.$name" - else if ( !hasInstances && name == defaultCache ) s"play.cache.redis" - else throw new IllegalArgumentException( s"Redis cache '$name' is not defined." ) - }( RedisInstanceBinder.loader( name ) ) match { - case self: RedisInstanceSelfBinder => self.instance - case _: RedisInstanceCustomBinder => redisInstanceConfiguration( name ) - } - } -} - -package connector { - - private[ redis ] trait RedisConnectorComponents { - import configuration._ - - import akka.actor.ActorSystem - import redis.RedisCommands - - def actorSystem: ActorSystem - - def applicationLifecycle: ApplicationLifecycle - - private lazy val akkaSerializer: AkkaSerializer = new AkkaSerializerImpl( actorSystem ) - - private def redisCommandsFor( instance: RedisInstance ): RedisCommands = instance match { - case standalone: RedisStandalone => new RedisCommandsStandalone( standalone )( actorSystem, applicationLifecycle ).get - case cluster: RedisCluster => new RedisCommandsCluster( cluster )( actorSystem, applicationLifecycle ).get - } - - private[ redis ] def redisConnectorFor( instance: RedisInstance )( implicit runtime: RedisRuntime ) = - new RedisConnectorImpl( akkaSerializer, redisCommandsFor( instance ) ) +/** + *

Components for compile-time dependency injection. + * It binds components from configuration package

+ * + * @author Karel Cemus + */ +trait RedisCacheComponents +{ + implicit def actorSystem: akka.actor.ActorSystem + implicit def applicationLifecycle: ApplicationLifecycle + def configuration: Configuration + def environment: Environment + + /** default implementation of the empty resolver */ + private lazy val emptyRecoveryResolver = new RecoveryPolicyResolverImpl + + /** override this for providing a custom policy resolver */ + implicit def recoveryPolicyResolver = emptyRecoveryResolver + + /** default implementation of the empty resolver */ + private lazy val emptyInstanceResolver = new play.api.cache.redis.configuration.RedisInstanceResolver { + def isDefinedAt( name: String ) = false + def apply( name: String ) = throw new IllegalArgumentException( s"Cannot resolve redis cache instance '$name'." ) } -} - -package impl { - - private[ redis ] trait RedisImplComponents { - import akka.actor.ActorSystem - - def environment: Environment - - def actorSystem: ActorSystem - - /** overwrite to provide custom recovery policy */ - def redisRecoveryPolicy: PartialFunction[ String, RecoveryPolicy ] = { - case "log-and-fail" => new LogAndFailPolicy - case "log-and-default" => new LogAndDefaultPolicy - case "log-condensed-and-fail" => new LogCondensedAndFailPolicy - case "log-condensed-and-default" => new LogCondensedAndDefaultPolicy - } - private[ redis ] def redisConnectorFor( instance: RedisInstance )( implicit runtime: RedisRuntime ): RedisConnector + /** override this for providing a custom redis instance resolver */ + implicit def redisInstanceResolver = emptyInstanceResolver - private implicit def instance2connector( instance: RedisInstance )( implicit runtime: RedisRuntime ): RedisConnector = redisConnectorFor( instance ) - private implicit def instance2policy( instance: RedisInstance ): RecoveryPolicy = redisRecoveryPolicy( instance.recovery ) + private lazy val akkaSerializer: connector.AkkaSerializer = new connector.AkkaSerializerProvider().get - implicit def runtime( implicit instance: RedisInstance ) = RedisRuntime( instance = instance, recovery = instance )( actorSystem ) + private def hasInstances = configuration.underlying.hasPath( "play.cache.redis.instances" ) - // play-redis APIs - private def asyncRedis( implicit instance: RedisInstance ) = new AsyncRedis( redis = instance ) + private def defaultCache = configuration.underlying.getString( "play.cache.redis.default-cache" ) - def syncRedisCacheApi( implicit instance: RedisInstance ): CacheApi = new SyncRedis( redis = instance ) - def asyncRedisCacheApi( instance: RedisInstance ): CacheAsyncApi = asyncRedis( instance ) + private lazy val manager = configuration.get( "play.cache.redis" )( play.api.cache.redis.configuration.RedisInstanceManager ) - // scala api defined by Play - def asyncCacheApi( instance: RedisInstance ): play.api.cache.AsyncCacheApi = asyncRedis( instance ) - private def defaultSyncCache( instance: RedisInstance ) = new play.api.cache.DefaultSyncCacheApi( asyncCacheApi( instance ) ) - @deprecated( message = "Use syncCacheApi or asyncCacheApi.", since = "Play 2.6.0." ) - def defaultCacheApi( instance: RedisInstance ): play.api.cache.CacheApi = defaultSyncCache( instance ) - def syncCacheApi( instance: RedisInstance ): play.api.cache.SyncCacheApi = defaultSyncCache( instance ) + /** translates the cache name into the configuration */ + implicit def redisInstance( name: String )( implicit resolver: play.api.cache.redis.configuration.RedisInstanceResolver ): RedisInstance = manager.instanceOf( name ).resolved - // java api defined by Play - def javaAsyncCacheApi( implicit instance: RedisInstance ): play.cache.AsyncCacheApi = new JavaRedis( asyncRedis( instance ), environment = environment ) - private def javaDefaultSyncCache( instance: RedisInstance ) = new play.cache.DefaultSyncCacheApi( javaAsyncCacheApi( instance ) ) - @deprecated( message = "Use javaSyncCacheApi or javaAsyncCacheApi.", since = "Play 2.6.0." ) - def javaCacheApi( instance: RedisInstance ): play.cache.CacheApi = javaDefaultSyncCache( instance ) - def javaSyncCacheApi( instance: RedisInstance ): play.cache.SyncCacheApi = javaDefaultSyncCache( instance ) - } + def cacheApi( instance: RedisInstance ): impl.RedisCaches = new impl.RedisCachesProvider( instance, akkaSerializer, environment, recoveryPolicyResolver ).get } - - -/** - *

Components for compile-time dependency injection. - * It binds components from configuration package

- * - * @author Karel Cemus - */ -trait RedisCacheComponents - extends configuration.RedisConfigurationComponents - with connector.RedisConnectorComponents - with impl.RedisImplComponents diff --git a/src/main/scala/play/api/cache/redis/RedisCacheModule.scala b/src/main/scala/play/api/cache/redis/RedisCacheModule.scala index 7202487f..df964fba 100644 --- a/src/main/scala/play/api/cache/redis/RedisCacheModule.scala +++ b/src/main/scala/play/api/cache/redis/RedisCacheModule.scala @@ -3,190 +3,107 @@ package play.api.cache.redis import javax.inject.{Inject, Provider, Singleton} import scala.language.implicitConversions +import scala.reflect.ClassTag +import play.api.Environment import play.api.inject._ -import play.api.{Configuration, Environment} -import redis.RedisCommands - -package configuration { - - /** - * Extracts the configuration and binds with DI for upper layers. - */ - private[ redis ] trait RedisConfigurationModule { - - def configuration: Configuration +/** Play framework module implementing play.api.cache.CacheApi for redis-server key/value storage. For more details + * see README. + * + * @author Karel Cemus + */ +@Singleton +class RedisCacheModule extends Module { - private lazy val manager = configuration.get( "play.cache.redis" )( RedisInstanceManager ) + override def bindings( environment: Environment, config: play.api.Configuration ) = { + def defaultCache = config.get[ String ]( "play.cache.redis.default-cache" ) + def bindDefault = config.get[ Boolean ]( "play.cache.redis.bind-default" ) - def caches = manager.caches + // read the config and get the configuration of the redis + val manager = config.get( "play.cache.redis" )( configuration.RedisInstanceManager ) - private def instanceOf( name: String ): RedisInstanceBinder = manager.instanceOf( name ) + // bind all caches + val caches = manager.flatMap( GuiceProvider.bindings ) + // common settings + val commons = Seq( + // bind serializer + bind[ connector.AkkaSerializer ].toProvider[ connector.AkkaSerializerProvider ], + bind[ configuration.RedisInstanceResolver ].to[ GuiceRedisInstanceResolver ] + ) + // bind recovery resolver + val recovery = RecoveryPolicyResolver.bindings + // default bindings + val defaults = if ( bindDefault ) GuiceProvider.defaults( manager.instanceOf( defaultCache ) ) else Seq.empty - def bindNamedConfiguration( name: String ): Seq[ Binding[ _ ] ] = instanceOf( name ).toBinding + // return all bindings + commons ++ caches ++ recovery ++ defaults } } -package connector { +trait GuiceProvider { + @Inject() var injector: Injector = _ + protected implicit def implicitInjection[ X ]( key: BindingKey[ X ] ): X = injector instanceOf key +} - /** - * Configures low-level classes communicating with the redis server. - */ - private[ redis ] trait RedisConnectorModule { +object GuiceProvider { - def bindSharedConnector: Seq[ Binding[ _ ] ] = Seq( - bind[ AkkaSerializer ].to[ AkkaSerializerImpl ] - ) + private def provider[ T ]( f: impl.RedisCaches => T )( implicit name: CacheName ): Provider[ T ] = new NamedCacheInstanceProvider( f ) - def bindNamedConnector( name: String ): Seq[ Binding[ _ ] ] = Seq( - bind[ RedisCommands ].qualifiedWith( name ).to( new RedisCommandsProvider( name ) ), - bind[ RedisConnector ].qualifiedWith( name ).to( new RedisConnectorProvider( name ) ) - ) + def bindings( instance: RedisInstanceProvider ) = { + implicit val name = new CacheName( instance.name ) - def bindDefaultConnector( name: String ): Seq[ Binding[ _ ] ] = Seq( - bind[ RedisCommands ].to( bind[ RedisCommands ].qualifiedWith( name ) ), - bind[ RedisConnector ].to( bind[ RedisConnector ].qualifiedWith( name ) ) + Seq( + // bind implementation of all caches + bind[ impl.RedisCaches ].qualifiedWith( name ).to( new GuiceRedisCacheProvider( instance ) ), + // expose a single-implementation providers + bind[ CacheApi ].qualifiedWith( name ).to( provider( _.sync ) ), + bind[ CacheAsyncApi ].qualifiedWith( name ).to( provider( _.async ) ), + bind[ play.api.cache.SyncCacheApi ].qualifiedWith( name ).to( provider( _.scalaSync ) ), + bind[ play.cache.SyncCacheApi ].qualifiedWith( name ).to( provider( _.javaSync ) ), + bind[ play.cache.AsyncCacheApi ].qualifiedWith( name ).to( provider( _.javaAsync ) ), ) } -} - -package impl { - - /** - * Configures low-level classes communicating with the redis server. - */ - private[ redis ] trait ImplementationModule { - import ImplementationModule._ - import NamedCacheProvider._ - - def bindNamedCache( implicit name: String ) = Seq[ Binding[ _ ] ]( - // play-redis APIs - bind[ CacheApi ].qualified.to( NamedSyncRedisProvider( name ) ), - bind[ CacheAsyncApi ].qualified.to( NamedAsyncRedisProvider( name ) ), - // scala api defined by Play - bind[ play.api.cache.CacheApi ].qualified.to( NamedScalaSyncCacheProvider( name ) ), - bind[ play.api.cache.SyncCacheApi ].qualified.to( NamedScalaSyncCacheProvider( name ) ), - bind[ play.api.cache.AsyncCacheApi ].qualified.to( NamedAsyncRedisProvider( name ) ), - // java api defined by Play - bind[ play.cache.CacheApi ].qualified.to( NamedJavaSyncCacheProvider( name ) ), - bind[ play.cache.SyncCacheApi ].qualified.to( NamedJavaSyncCacheProvider( name ) ), - bind[ play.cache.AsyncCacheApi ].qualified.to( NamedJavaRedisProvider( name ) ) - ) - def bindDefaultCache( implicit name: String ): Seq[ Binding[ _ ] ] = Seq( - // play-redis APIs - bind[ CacheApi ].to( bind[ CacheApi ].qualified ), - bind[ CacheAsyncApi ].to( bind[ CacheAsyncApi ].qualified ), - // scala api defined by Play - bind[ play.api.cache.CacheApi ].to( bind[ play.api.cache.CacheApi ].qualified ), - bind[ play.api.cache.SyncCacheApi ].to( bind[ play.api.cache.SyncCacheApi ].qualified ), - bind[ play.api.cache.AsyncCacheApi ].to( bind[ play.api.cache.AsyncCacheApi ].qualified ), - // java api defined by Play - bind[ play.cache.CacheApi ].to( bind[ play.cache.CacheApi ].qualified ), - bind[ play.cache.SyncCacheApi ].to( bind[ play.cache.SyncCacheApi ].qualified ), - bind[ play.cache.AsyncCacheApi ].to( bind[ play.cache.AsyncCacheApi ].qualified ) + def defaults( instance: RedisInstanceProvider ) = { + implicit val name = new CacheName( instance.name ) + def namedBinding[ T: ClassTag ]( implicit cacheName: CacheName ): Binding[ T ] = bind[ T ].to( bind[ T ].qualifiedWith( name ) ) + + Seq( + // bind implementation of all caches + namedBinding[ impl.RedisCaches ], + // expose a single-implementation providers + namedBinding[ CacheApi ], + namedBinding[ CacheAsyncApi ], + namedBinding[ play.api.cache.SyncCacheApi ], + namedBinding[ play.cache.SyncCacheApi ], + namedBinding[ play.cache.AsyncCacheApi ] ) } - - object ImplementationModule { - implicit class QualifiedBinding[ T ]( val binding: BindingKey[ T ] ) extends AnyVal { - def qualified( implicit name: String ) = binding.qualifiedWith( name ) - } - } - - class NamedCacheProvider[ T ]( name: String )( f: Injector => T ) extends Provider[ T ] { - @Inject var injector: Injector = _ - lazy val get = f( injector ) - } - - object NamedCacheProvider { - - @inline private implicit def implicitBinding[ T ]( bindingKey: BindingKey[ T ] )( implicit injector: Injector ): T = injector.instanceOf( bindingKey ) - - @inline private implicit def implicitInjection[ T ]( implicit bindingKey: BindingKey[ T ], injector: Injector ): T = implicitBinding( bindingKey ) - - def NamedAsyncRedisProvider( name: String ) = provider( name ) { implicit injector => - def connector = bind[ RedisConnector ].qualifiedWith( name ) - implicit def runtime = bind[ RedisRuntime ].qualifiedWith( name ) - - new AsyncRedis( connector ) - } - - def NamedSyncRedisProvider( name: String ) = provider( name ) { implicit injector => - def connector = bind[ RedisConnector ].qualifiedWith( name ) - implicit def runtime = bind[ RedisRuntime ].qualifiedWith( name ) - - new SyncRedis( connector ) - } - - def NamedJavaRedisProvider( name: String ) = provider( name ) { implicit injector => - def internal = bind[ CacheAsyncApi ].qualifiedWith( name ) - def environment = bind[ Environment ] - implicit def runtime = bind[ RedisRuntime ].qualifiedWith( name ) - - new JavaRedis( internal, environment ) - } - - def NamedJavaSyncCacheProvider( name: String ) = provider( name ) { implicit injector => - def internal = bind[ play.cache.AsyncCacheApi ].qualifiedWith( name ) - - new play.cache.DefaultSyncCacheApi( internal ) - } - - def NamedScalaSyncCacheProvider( name: String ) = provider( name ) { implicit injector => - def internal = bind[ play.api.cache.AsyncCacheApi ].qualifiedWith( name ) - - new play.api.cache.DefaultSyncCacheApi( internal ) - } - - @inline private def provider[ T ]( name: String )( f: Injector => T ): NamedCacheProvider[ T ] = new NamedCacheProvider( name )( f ) - } } - -private[ redis ] object RecoveryPolicyBinder { - - def bindings = Seq( - bind[ RecoveryPolicy ].qualifiedWith( "log-and-fail" ).to[ LogAndFailPolicy ], - bind[ RecoveryPolicy ].qualifiedWith( "log-and-default" ).to[ LogAndDefaultPolicy ], - bind[ RecoveryPolicy ].qualifiedWith( "log-condensed-and-fail" ).to[ LogCondensedAndFailPolicy ], - bind[ RecoveryPolicy ].qualifiedWith( "log-condensed-and-default" ).to[ LogCondensedAndDefaultPolicy ] - ) +class GuiceRedisCacheProvider( instance: RedisInstanceProvider ) extends Provider[ RedisCaches ] with GuiceProvider { + lazy val get = new impl.RedisCachesProvider( + instance = instance.resolved( bind[ configuration.RedisInstanceResolver ] ), + serializer = bind[ connector.AkkaSerializer ], + environment = bind[ Environment ], + recovery = bind[ RecoveryPolicyResolver ] + )( + system = bind[ akka.actor.ActorSystem ], + lifecycle = bind[ ApplicationLifecycle ] + ).get } -/** Play framework module implementing play.api.cache.CacheApi for redis-server key/value storage. For more details - * see README. - * - * @author Karel Cemus - */ -@Singleton -class RedisCacheModule extends Module { - - override def bindings( environment: Environment, config: play.api.Configuration ) = { - // play-redis consists of several layers and sub-modules, each defining it's own bindings - val module = new connector.RedisConnectorModule with configuration.RedisConfigurationModule with impl.ImplementationModule { - val configuration = config - def defaultCache = config.get[ String ]( "play.cache.redis.default-cache" ) - def bindDefault = config.get[ Boolean ]( "play.cache.redis.bind-default" ) - } - - def defaultCache = config.get[ String ]( "play.cache.redis.default-cache" ) - def bindDefault = config.get[ Boolean ]( "play.cache.redis.bind-default" ) - def caches = module.caches - - def bindShared = module.bindSharedConnector ++ RecoveryPolicyBinder.bindings - - def bindNamed( name: String ) = module.bindNamedConfiguration( name ) ++ module.bindNamedConnector( name ) ++ module.bindNamedCache( name ) ++ Seq( - bind[ RedisRuntime ].qualifiedWith( name ).to( new NamedRedisRuntimeProvider( name ) ) - ) - def bindCaches = caches.flatMap( bindNamed ) +class NamedCacheInstanceProvider[ T ]( f: RedisCaches => T )( implicit name: CacheName ) extends Provider[ T ] with GuiceProvider { + lazy val get = f( bind[ RedisCaches ].qualifiedWith( name ) ) +} - def bindDefaults( name: String ) = module.bindDefaultConnector( name ) ++ module.bindDefaultCache( name ) ++ Seq( - bind[ RedisRuntime ].to( new NamedRedisRuntimeProvider( name ) ) - ) - def bindDefaultCache = if ( bindDefault ) bindDefaults( defaultCache ) else Seq.empty[ Binding[ _ ] ] +class CacheName( val name: String ) extends AnyVal +object CacheName { + implicit def name2string( name: CacheName ): String = name.name +} - bindShared ++ bindDefaultCache ++ bindCaches - } +class GuiceRedisInstanceResolver @Inject()( injector: Injector ) extends configuration.RedisInstanceResolver with GuiceProvider { + def isDefinedAt( name: String ) = true + def apply( name: String ): RedisInstance = bind[ RedisInstance ].qualifiedWith( name ) } diff --git a/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala b/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala index 603bd39b..0e72efc2 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala @@ -23,9 +23,9 @@ trait RedisCaches { } class RedisCachesProvider( instance: RedisInstance, serializer: connector.AkkaSerializer, environment: Environment, recovery: RecoveryPolicyResolver )( implicit system: ActorSystem, lifecycle: ApplicationLifecycle ) extends Provider[ RedisCaches ] { - implicit lazy val runtime: RedisRuntime = RedisRuntime( instance, recovery.resolve( instance.recovery ) )( system ) + private implicit lazy val runtime: RedisRuntime = RedisRuntime( instance, recovery( instance.recovery ) )( system ) - lazy val redisConnector = new connector.RedisConnectorProvider( instance, serializer ).get + private lazy val redisConnector = new connector.RedisConnectorProvider( instance, serializer ).get lazy val get = new RedisCaches { lazy val async = new AsyncRedis( redisConnector ) diff --git a/src/main/scala/play/api/cache/redis/package.scala b/src/main/scala/play/api/cache/redis/package.scala index 97282a22..ddbc04be 100644 --- a/src/main/scala/play/api/cache/redis/package.scala +++ b/src/main/scala/play/api/cache/redis/package.scala @@ -13,4 +13,6 @@ package object redis extends AnyRef with ExpirationImplicits with ExceptionImpli private[ redis ] type RedisInstance = configuration.RedisInstance private[ redis ] type RedisConnector = connector.RedisConnector + private[ redis ] type RedisInstanceProvider = configuration.RedisInstanceProvider + private[ redis ] type RedisCaches = impl.RedisCaches } diff --git a/src/test/scala/play/api/cache/redis/RedisComponentsSpecs.scala b/src/test/scala/play/api/cache/redis/RedisComponentsSpecs.scala index 0da8e5d1..37c483c0 100644 --- a/src/test/scala/play/api/cache/redis/RedisComponentsSpecs.scala +++ b/src/test/scala/play/api/cache/redis/RedisComponentsSpecs.scala @@ -16,7 +16,7 @@ class RedisComponentsSpecs extends Specification with Redis { def environment = injector.instanceOf[ Environment ] def configuration = injector.instanceOf[ Configuration ] - val syncRedis = syncRedisCacheApi( "play" ) + val syncRedis = cacheApi( "play" ).sync } private type Cache = CacheApi diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala index 47063e13..7f8326b1 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala @@ -8,6 +8,11 @@ class RedisInstanceManagerSpec extends Specification { implicit val loader = RedisInstanceManager + implicit val resolver = new RedisInstanceResolver { + def isDefinedAt( name: String ) = true + def apply( name: String ) = ??? + } + "Advanced RedisInstanceManager" should "read" >> { "multiple caches" in new WithConfiguration( @@ -39,9 +44,9 @@ class RedisInstanceManagerSpec extends Specification { val manager = config.get[ RedisInstanceManager ]( "redis" ) manager.caches mustEqual Set( "play", "data", "users" ) - manager.instanceOf( "play" ).instanceOption must beSome( RedisStandalone( "play", RedisHost( "localhost", 6379 ), defaults ) ) - manager.instanceOf( "users" ).instanceOption must beSome( RedisStandalone( "users", RedisHost( "localhost", 6380 ), defaults ) ) - manager.instanceOf( "data" ).instanceOption must beSome( RedisStandalone( "data", RedisHost( "localhost", 6381 ), defaults ) ) + manager.instanceOf( "play" ).resolved must beEqualTo( RedisStandalone( "play", RedisHost( "localhost", 6379 ), defaults ) ) + manager.instanceOf( "users" ).resolved must beEqualTo( RedisStandalone( "users", RedisHost( "localhost", 6380 ), defaults ) ) + manager.instanceOf( "data" ).resolved must beEqualTo( RedisStandalone( "data", RedisHost( "localhost", 6381 ), defaults ) ) } } @@ -65,7 +70,7 @@ class RedisInstanceManagerSpec extends Specification { val manager = config.get[ RedisInstanceManager ]( "redis" ) manager.caches mustEqual Set( "play" ) - manager.instanceOf( "play" ).instanceOption must beSome( RedisStandalone( "play", RedisHost( "localhost", 6380 ), defaults ) ) + manager.instanceOf( "play" ).resolved must beEqualTo( RedisStandalone( "play", RedisHost( "localhost", 6380 ), defaults ) ) } } } diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceSpec.scala b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceSpec.scala index 51d38952..737e905f 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceSpec.scala @@ -2,9 +2,6 @@ package play.api.cache.redis.configuration import scala.concurrent.duration._ -import play.api.Configuration - -import com.typesafe.config.ConfigFactory import org.specs2.mutable.Specification class RedisInstanceSpec extends Specification { @@ -14,7 +11,16 @@ class RedisInstanceSpec extends Specification { // default settings implicit val defaults = settings( source = "standalone" ) // implicitly expose RedisHost config loader - implicit val loader = RedisInstanceBinder.loader( "play" ) + implicit val loader = RedisInstanceProvider.loader( "play" ) + // instance resolver + implicit def resolver = new RedisInstanceResolver { + def isDefinedAt( name: String ) = true + def apply( name: String ) = RedisStandalone( + name = s"resolved-$name", + host = RedisHost( "localhost", 6380 ), + settings = defaults + ) + } "RedisInstance" should "read" >> { @@ -26,7 +32,7 @@ class RedisInstanceSpec extends Specification { |} """ ) { - config.get[ RedisInstanceBinder ]( "redis" ).instanceOption must beSome( RedisStandalone( "play", RedisHost( host = "localhost", port = 6379 ), defaults ) ) + config.get[ RedisInstanceProvider ]( "redis" ).resolved must beEqualTo( RedisStandalone( "play", RedisHost( host = "localhost", port = 6379 ), defaults ) ) } "standalone overriding defaults" in new WithConfiguration( @@ -41,7 +47,7 @@ class RedisInstanceSpec extends Specification { |} """ ) { - config.get[ RedisInstanceBinder ]( "redis" ).instanceOption must beSome( RedisStandalone( "play", RedisHost( host = "localhost", port = 6379 ), RedisSettings( "custom-dispatcher", 2.seconds, "custom", "standalone" ) ) ) + config.get[ RedisInstanceProvider ]( "redis" ).resolved must beEqualTo( RedisStandalone( "play", RedisHost( host = "localhost", port = 6379 ), RedisSettings( "custom-dispatcher", 2.seconds, "custom", "standalone" ) ) ) } "cluster" in new WithConfiguration( @@ -55,7 +61,7 @@ class RedisInstanceSpec extends Specification { |} """ ) { - config.get[ RedisInstanceBinder ]( "redis" ).instanceOption must beSome( RedisCluster( "play", nodes = List( RedisHost( host = "localhost", port = 6379 ), RedisHost( host = "localhost", port = 6380 ) ), settings( source = "cluster" ) ) ) + config.get[ RedisInstanceProvider ]( "redis" ).resolved must beEqualTo( RedisCluster( "play", nodes = List( RedisHost( host = "localhost", port = 6379 ), RedisHost( host = "localhost", port = 6380 ) ), settings( source = "cluster" ) ) ) } "standalone with connection string" in new WithConfiguration( @@ -66,7 +72,7 @@ class RedisInstanceSpec extends Specification { |} """ ) { - config.get[ RedisInstanceBinder ]( "redis" ).instanceOption must beSome( RedisStandalone( "play", RedisHost( host = "localhost", port = 6379 ), settings( source = "connection-string" ) ) ) + config.get[ RedisInstanceProvider ]( "redis" ).resolved must beEqualTo( RedisStandalone( "play", RedisHost( host = "localhost", port = 6379 ), settings( source = "connection-string" ) ) ) } "custom" in new WithConfiguration( @@ -76,7 +82,7 @@ class RedisInstanceSpec extends Specification { |} """ ) { - config.get[ RedisInstanceBinder ]( "redis" ).instanceOption must beNone + config.get[ RedisInstanceProvider ]( "redis" ).resolved must beEqualTo( RedisStandalone( "resolved-redis", RedisHost( host = "localhost", port = 6380 ), defaults ) ) } } } From b56328024a73eb52c2a2ffae56cd801b496253b8 Mon Sep 17 00:00:00 2001 From: Karel Cemus Date: Mon, 25 Sep 2017 14:31:33 +0200 Subject: [PATCH 6/8] Refactored redis runtime into two traits and moved into impl package --- .../api/cache/redis/RedisCacheModule.scala | 3 +- .../redis/configuration/RedisInstance.scala | 5 ++- .../configuration/RedisInstanceManager.scala | 13 +++++++- .../cache/redis/connector/RedisRuntime.scala | 11 +++++++ .../cache/redis/{ => impl}/RedisRuntime.scala | 24 ++------------ src/test/resources/reference.conf | 2 ++ .../play/api/cache/redis/TestModule.scala | 31 +++++++++++++++++++ .../configuration/RedisInstanceSpec.scala | 2 +- .../api/cache/redis/impl/JavaCacheSpec.scala | 4 +-- .../api/cache/redis/impl/PlayCacheSpec.scala | 4 +-- .../play/api/cache/redis/impl/package.scala | 4 +-- 11 files changed, 71 insertions(+), 32 deletions(-) create mode 100644 src/main/scala/play/api/cache/redis/connector/RedisRuntime.scala rename src/main/scala/play/api/cache/redis/{ => impl}/RedisRuntime.scala (61%) create mode 100644 src/test/scala/play/api/cache/redis/TestModule.scala diff --git a/src/main/scala/play/api/cache/redis/RedisCacheModule.scala b/src/main/scala/play/api/cache/redis/RedisCacheModule.scala index df964fba..19b3ba81 100644 --- a/src/main/scala/play/api/cache/redis/RedisCacheModule.scala +++ b/src/main/scala/play/api/cache/redis/RedisCacheModule.scala @@ -17,7 +17,6 @@ import play.api.inject._ class RedisCacheModule extends Module { override def bindings( environment: Environment, config: play.api.Configuration ) = { - def defaultCache = config.get[ String ]( "play.cache.redis.default-cache" ) def bindDefault = config.get[ Boolean ]( "play.cache.redis.bind-default" ) // read the config and get the configuration of the redis @@ -34,7 +33,7 @@ class RedisCacheModule extends Module { // bind recovery resolver val recovery = RecoveryPolicyResolver.bindings // default bindings - val defaults = if ( bindDefault ) GuiceProvider.defaults( manager.instanceOf( defaultCache ) ) else Seq.empty + val defaults = if ( bindDefault ) GuiceProvider.defaults( manager.defaultInstance ) else Seq.empty // return all bindings commons ++ caches ++ recovery ++ defaults diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala b/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala index f693358d..b32d707f 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisInstance.scala @@ -63,7 +63,10 @@ trait RedisStandalone extends RedisInstance with RedisHost { case _ => false } /** to string */ - override def toString = s"Standalone($host:$port?db=${ database getOrElse "" }" + override def toString = database match { + case Some( database ) => s"Standalone($name@$host:$port?db=$database)" + case None => s"Standalone($name@$host:$port)" + } } object RedisStandalone { diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisInstanceManager.scala b/src/main/scala/play/api/cache/redis/configuration/RedisInstanceManager.scala index 6e282a8d..591b1d5b 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisInstanceManager.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisInstanceManager.scala @@ -31,6 +31,9 @@ trait RedisInstanceManager extends Traversable[ RedisInstanceProvider ] { throw new IllegalArgumentException( s"There is no cache named '$name'." ) } + /** returns the default instance */ + def defaultInstance: RedisInstanceProvider + /** traverse all binders */ def foreach[ U ]( f: RedisInstanceProvider => U ) = caches.view.flatMap( instanceOfOption ).foreach( f ) } @@ -58,6 +61,12 @@ class RedisInstanceManagerImpl( config: Config, path: String )( implicit default /** names of all known redis caches */ def caches: Set[ String ] = config.getObject( path / "instances" ).keySet.asScala.toSet + def defaultCacheName = config.getString( path / "default-cache" ) + + def defaultInstance = instanceOfOption( defaultCacheName ) getOrElse { + throw new IllegalArgumentException( s"Default cache '$defaultCacheName' is not defined." ) + } + /** returns a configuration of a single named redis instance */ def instanceOfOption( name: String ): Option[ RedisInstanceProvider ] = if ( config hasPath ( path / "instances" / name ) ) Some( RedisInstanceProvider.load( config, path / "instances" / name, name ) ) else None @@ -74,7 +83,9 @@ class RedisInstanceManagerFallback( config: Config, path: String )( implicit def /** names of all known redis caches */ def caches: Set[ String ] = Set( name ) + def defaultInstance = RedisInstanceProvider.load( config, path, name ) + /** returns a configuration of a single named redis instance */ def instanceOfOption( name: String ): Option[ RedisInstanceProvider ] = - if ( name == this.name ) Some( RedisInstanceProvider.load( config, path, name ) ) else None + if ( name == this.name ) Some( defaultInstance ) else None } diff --git a/src/main/scala/play/api/cache/redis/connector/RedisRuntime.scala b/src/main/scala/play/api/cache/redis/connector/RedisRuntime.scala new file mode 100644 index 00000000..223292a7 --- /dev/null +++ b/src/main/scala/play/api/cache/redis/connector/RedisRuntime.scala @@ -0,0 +1,11 @@ +package play.api.cache.redis.connector + +import scala.concurrent.ExecutionContext + +/** + * @author Karel Cemus + */ +private[ redis ] trait RedisRuntime { + def name: String + implicit def context: ExecutionContext +} diff --git a/src/main/scala/play/api/cache/redis/RedisRuntime.scala b/src/main/scala/play/api/cache/redis/impl/RedisRuntime.scala similarity index 61% rename from src/main/scala/play/api/cache/redis/RedisRuntime.scala rename to src/main/scala/play/api/cache/redis/impl/RedisRuntime.scala index b2b3ae4d..0600ca61 100644 --- a/src/main/scala/play/api/cache/redis/RedisRuntime.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisRuntime.scala @@ -1,11 +1,9 @@ -package play.api.cache.redis - -import javax.inject.{Inject, Provider} +package play.api.cache.redis.impl import scala.concurrent.ExecutionContext import scala.concurrent.duration.FiniteDuration -import play.api.inject._ +import play.api.cache.redis._ import akka.actor.ActorSystem @@ -15,9 +13,7 @@ import akka.actor.ActorSystem * * @author Karel Cemus */ -private[ redis ] trait RedisRuntime { - def name: String - implicit def context: ExecutionContext +private[ redis ] trait RedisRuntime extends connector.RedisRuntime { implicit def policy: RecoveryPolicy implicit def timeout: akka.util.Timeout } @@ -32,17 +28,3 @@ private[ redis ] object RedisRuntime { def apply( name: String, timeout: FiniteDuration, context: ExecutionContext, recovery: RecoveryPolicy ): RedisRuntime = RedisRuntimeImpl( name, context, recovery, akka.util.Timeout( timeout ) ) } - -class NamedRedisRuntimeProvider( cacheName: String ) extends Provider[ RedisRuntime ] { - @Inject() var injector: Injector = _ - @Inject() implicit var system: ActorSystem = _ - - def get( ) = { - val instance = injector instanceOf bind[ RedisInstance ].qualifiedWith( cacheName ) - - RedisRuntime( - instance = instance, - recovery = injector instanceOf bind[ RecoveryPolicy ].qualifiedWith( instance.recovery ) - ) - } -} diff --git a/src/test/resources/reference.conf b/src/test/resources/reference.conf index f7655b41..e17ae464 100644 --- a/src/test/resources/reference.conf +++ b/src/test/resources/reference.conf @@ -21,3 +21,5 @@ akka { } } } + +play.modules.enabled += "play.api.cache.redis.TestModule" diff --git a/src/test/scala/play/api/cache/redis/TestModule.scala b/src/test/scala/play/api/cache/redis/TestModule.scala new file mode 100644 index 00000000..0f3e60c0 --- /dev/null +++ b/src/test/scala/play/api/cache/redis/TestModule.scala @@ -0,0 +1,31 @@ +package play.api.cache.redis + +import javax.inject.{Inject, Provider} + +import play.api.cache.redis.configuration.{RedisInstanceManager, RedisInstanceResolver} +import play.api.cache.redis.connector.{AkkaSerializer, RedisConnectorProvider} +import play.api.inject._ +import play.api.{Configuration, Environment} + +import akka.actor.ActorSystem + +/** + * @author Karel Cemus + */ +class GuiceRedisConnectorProvider @Inject()( serializer: AkkaSerializer, configuration: Configuration )( implicit resolver: RedisInstanceResolver, system: ActorSystem, lifecycle: ApplicationLifecycle ) extends Provider[ RedisConnector ] { + + private val instance = configuration.get( "play.cache.redis" )( RedisInstanceManager ).defaultInstance.resolved + + private implicit def runtime = new connector.RedisRuntime { + def name = instance.name + implicit def context = system.dispatchers.lookup( instance.invocationContext ) + } + + lazy val get = new RedisConnectorProvider( instance, serializer ).get +} + +class TestModule extends Module { + def bindings( environment: Environment, configuration: Configuration ) = Seq( + bind[ connector.RedisConnector ].toProvider[ GuiceRedisConnectorProvider ] + ) +} diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceSpec.scala b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceSpec.scala index 737e905f..9e4293e1 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceSpec.scala @@ -82,7 +82,7 @@ class RedisInstanceSpec extends Specification { |} """ ) { - config.get[ RedisInstanceProvider ]( "redis" ).resolved must beEqualTo( RedisStandalone( "resolved-redis", RedisHost( host = "localhost", port = 6380 ), defaults ) ) + config.get[ RedisInstanceProvider ]( "redis" ).resolved must beEqualTo( RedisStandalone( "resolved-play", RedisHost( host = "localhost", port = 6380 ), defaults ) ) } } } 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..70638a21 100644 --- a/src/test/scala/play/api/cache/redis/impl/JavaCacheSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/JavaCacheSpec.scala @@ -13,9 +13,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" diff --git a/src/test/scala/play/api/cache/redis/impl/PlayCacheSpec.scala b/src/test/scala/play/api/cache/redis/impl/PlayCacheSpec.scala index 90fa422a..e81989df 100644 --- a/src/test/scala/play/api/cache/redis/impl/PlayCacheSpec.scala +++ b/src/test/scala/play/api/cache/redis/impl/PlayCacheSpec.scala @@ -11,7 +11,7 @@ import org.specs2.mutable.Specification */ class PlayCacheSpec extends Specification with Redis { - private type Cache = play.api.cache.CacheApi + private type Cache = play.api.cache.SyncCacheApi private val Cache = injector.instanceOf[ Cache ] @@ -60,7 +60,7 @@ class PlayCacheSpec extends Specification with Redis { 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( key ) { + def getOrElseCounting( key: String )( accumulator: Accumulator ) = cache.getOrElseUpdate( key ) { // increment miss counter accumulator.incrementAndGet() // return the value to store into the cache 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 322a0dbc..35d02560 100644 --- a/src/test/scala/play/api/cache/redis/impl/package.scala +++ b/src/test/scala/play/api/cache/redis/impl/package.scala @@ -65,11 +65,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 5a8d256abd6e7795765465d456a963f857b7d887 Mon Sep 17 00:00:00 2001 From: Karel Cemus Date: Mon, 25 Sep 2017 14:53:44 +0200 Subject: [PATCH 7/8] RecoveryPolicy and RedisInstance resolvers no longer extends PartialFunction - it caused undesired implicit conversions --- .../play/api/cache/redis/RecoveryPolicy.scala | 10 ++++++---- .../api/cache/redis/RedisCacheComponents.scala | 3 +-- .../api/cache/redis/RedisCacheModule.scala | 18 +++++++++++------- .../configuration/RedisInstanceProvider.scala | 6 ++++-- .../api/cache/redis/impl/RedisCaches.scala | 2 +- .../RedisInstanceManagerSpec.scala | 3 +-- .../configuration/RedisInstanceSpec.scala | 9 +++------ 7 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala b/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala index 83e1ed4d..d47636b3 100644 --- a/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala +++ b/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala @@ -155,12 +155,12 @@ private[ redis ] class LogCondensedAndFailPolicy @Inject( )( ) extends FailThrou * of the policy name into the instance. It has two subclasses, * one for guice and the other for compile-time injection. */ -trait RecoveryPolicyResolver extends PartialFunction[ String, RecoveryPolicy ] { - def isDefinedAt( name: String ) = lift.apply( name ).nonEmpty +trait RecoveryPolicyResolver { + def resolve: PartialFunction[ String, RecoveryPolicy ] } class RecoveryPolicyResolverImpl extends RecoveryPolicyResolver { - def apply( name: String ) = name match { + val resolve = { case "log-and-fail" => new LogAndFailPolicy case "log-and-default" => new LogAndDefaultPolicy case "log-condensed-and-fail" => new LogCondensedAndFailPolicy @@ -183,5 +183,7 @@ object RecoveryPolicyResolver { /** resolves a policies with guice enabled */ class RecoveryPolicyResolverGuice @Inject( )( injector: Injector ) extends RecoveryPolicyResolver { - def apply( name: String ) = injector instanceOf bind[ RecoveryPolicy ].qualifiedWith( name ) + def resolve = { + case name => injector instanceOf bind[ RecoveryPolicy ].qualifiedWith( name ) + } } diff --git a/src/main/scala/play/api/cache/redis/RedisCacheComponents.scala b/src/main/scala/play/api/cache/redis/RedisCacheComponents.scala index 2918b655..85f076aa 100644 --- a/src/main/scala/play/api/cache/redis/RedisCacheComponents.scala +++ b/src/main/scala/play/api/cache/redis/RedisCacheComponents.scala @@ -26,8 +26,7 @@ trait RedisCacheComponents /** default implementation of the empty resolver */ private lazy val emptyInstanceResolver = new play.api.cache.redis.configuration.RedisInstanceResolver { - def isDefinedAt( name: String ) = false - def apply( name: String ) = throw new IllegalArgumentException( s"Cannot resolve redis cache instance '$name'." ) + val resolve = PartialFunction.empty } /** override this for providing a custom redis instance resolver */ diff --git a/src/main/scala/play/api/cache/redis/RedisCacheModule.scala b/src/main/scala/play/api/cache/redis/RedisCacheModule.scala index 19b3ba81..fa8c68af 100644 --- a/src/main/scala/play/api/cache/redis/RedisCacheModule.scala +++ b/src/main/scala/play/api/cache/redis/RedisCacheModule.scala @@ -40,8 +40,8 @@ class RedisCacheModule extends Module { } } -trait GuiceProvider { - @Inject() var injector: Injector = _ +trait GuiceProviderImplicits { + def injector: Injector protected implicit def implicitInjection[ X ]( key: BindingKey[ X ] ): X = injector instanceOf key } @@ -81,7 +81,8 @@ object GuiceProvider { } } -class GuiceRedisCacheProvider( instance: RedisInstanceProvider ) extends Provider[ RedisCaches ] with GuiceProvider { +class GuiceRedisCacheProvider( instance: RedisInstanceProvider ) extends Provider[ RedisCaches ] with GuiceProviderImplicits { + @Inject() var injector: Injector = _ lazy val get = new impl.RedisCachesProvider( instance = instance.resolved( bind[ configuration.RedisInstanceResolver ] ), serializer = bind[ connector.AkkaSerializer ], @@ -93,7 +94,8 @@ class GuiceRedisCacheProvider( instance: RedisInstanceProvider ) extends Provide ).get } -class NamedCacheInstanceProvider[ T ]( f: RedisCaches => T )( implicit name: CacheName ) extends Provider[ T ] with GuiceProvider { +class NamedCacheInstanceProvider[ T ]( f: RedisCaches => T )( implicit name: CacheName ) extends Provider[ T ] with GuiceProviderImplicits { + @Inject() var injector: Injector = _ lazy val get = f( bind[ RedisCaches ].qualifiedWith( name ) ) } @@ -102,7 +104,9 @@ object CacheName { implicit def name2string( name: CacheName ): String = name.name } -class GuiceRedisInstanceResolver @Inject()( injector: Injector ) extends configuration.RedisInstanceResolver with GuiceProvider { - def isDefinedAt( name: String ) = true - def apply( name: String ): RedisInstance = bind[ RedisInstance ].qualifiedWith( name ) +@Singleton +class GuiceRedisInstanceResolver @Inject()( val injector: Injector ) extends configuration.RedisInstanceResolver with GuiceProviderImplicits { + def resolve = { + case name => bind[ RedisInstance ].qualifiedWith( name ) + } } diff --git a/src/main/scala/play/api/cache/redis/configuration/RedisInstanceProvider.scala b/src/main/scala/play/api/cache/redis/configuration/RedisInstanceProvider.scala index 508ebe50..e970f1da 100644 --- a/src/main/scala/play/api/cache/redis/configuration/RedisInstanceProvider.scala +++ b/src/main/scala/play/api/cache/redis/configuration/RedisInstanceProvider.scala @@ -7,7 +7,9 @@ import com.typesafe.config.{Config, ConfigOrigin} /** * @author Karel Cemus */ -trait RedisInstanceResolver extends PartialFunction[ String, RedisInstance ] +trait RedisInstanceResolver { + def resolve: PartialFunction[ String, RedisInstance ] +} sealed trait RedisInstanceProvider extends Any { def name: String @@ -20,7 +22,7 @@ class ResolvedRedisInstance( val instance: RedisInstance ) extends AnyVal with R } class UnresolvedRedisInstance( val name: String ) extends AnyVal with RedisInstanceProvider { - def resolved( implicit resolver: RedisInstanceResolver ) = resolver apply name + def resolved( implicit resolver: RedisInstanceResolver ) = resolver resolve name } private[ configuration ] object RedisInstanceProvider extends RedisConfigInstanceLoader[ RedisInstanceProvider ] { diff --git a/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala b/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala index 0e72efc2..d8c44632 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala @@ -23,7 +23,7 @@ trait RedisCaches { } class RedisCachesProvider( instance: RedisInstance, serializer: connector.AkkaSerializer, environment: Environment, recovery: RecoveryPolicyResolver )( implicit system: ActorSystem, lifecycle: ApplicationLifecycle ) extends Provider[ RedisCaches ] { - private implicit lazy val runtime: RedisRuntime = RedisRuntime( instance, recovery( instance.recovery ) )( system ) + private implicit lazy val runtime: RedisRuntime = RedisRuntime( instance, recovery resolve instance.recovery )( system ) private lazy val redisConnector = new connector.RedisConnectorProvider( instance, serializer ).get diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala index 7f8326b1..3ed05f1f 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceManagerSpec.scala @@ -9,8 +9,7 @@ class RedisInstanceManagerSpec extends Specification { implicit val loader = RedisInstanceManager implicit val resolver = new RedisInstanceResolver { - def isDefinedAt( name: String ) = true - def apply( name: String ) = ??? + val resolve = PartialFunction.empty } "Advanced RedisInstanceManager" should "read" >> { diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceSpec.scala b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceSpec.scala index 9e4293e1..709f5015 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceSpec.scala @@ -14,12 +14,9 @@ class RedisInstanceSpec extends Specification { implicit val loader = RedisInstanceProvider.loader( "play" ) // instance resolver implicit def resolver = new RedisInstanceResolver { - def isDefinedAt( name: String ) = true - def apply( name: String ) = RedisStandalone( - name = s"resolved-$name", - host = RedisHost( "localhost", 6380 ), - settings = defaults - ) + val resolve = { + case name => RedisStandalone( name = s"resolved-$name", host = RedisHost( "localhost", 6380 ), settings = defaults ) + } } "RedisInstance" should "read" >> { From 1c60ba7cee666a2a9564203ce3be7d557c6e2fe0 Mon Sep 17 00:00:00 2001 From: Karel Cemus Date: Mon, 25 Sep 2017 15:15:51 +0200 Subject: [PATCH 8/8] code clean up and optimization --- .../play/api/cache/redis/RecoveryPolicy.scala | 2 +- .../api/cache/redis/RedisCacheModule.scala | 29 ++++++++++--------- .../cache/redis/connector/RedisCommands.scala | 2 +- .../api/cache/redis/impl/RedisCaches.scala | 2 +- .../configuration/RedisInstanceSpec.scala | 2 +- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala b/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala index d47636b3..7332fe76 100644 --- a/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala +++ b/src/main/scala/play/api/cache/redis/RecoveryPolicy.scala @@ -160,7 +160,7 @@ trait RecoveryPolicyResolver { } class RecoveryPolicyResolverImpl extends RecoveryPolicyResolver { - val resolve = { + val resolve: PartialFunction[ String, RecoveryPolicy ] = { case "log-and-fail" => new LogAndFailPolicy case "log-and-default" => new LogAndDefaultPolicy case "log-condensed-and-fail" => new LogCondensedAndFailPolicy diff --git a/src/main/scala/play/api/cache/redis/RedisCacheModule.scala b/src/main/scala/play/api/cache/redis/RedisCacheModule.scala index fa8c68af..15db3149 100644 --- a/src/main/scala/play/api/cache/redis/RedisCacheModule.scala +++ b/src/main/scala/play/api/cache/redis/RedisCacheModule.scala @@ -47,7 +47,10 @@ trait GuiceProviderImplicits { object GuiceProvider { - private def provider[ T ]( f: impl.RedisCaches => T )( implicit name: CacheName ): Provider[ T ] = new NamedCacheInstanceProvider( f ) + @inline private def provider[ T ]( f: impl.RedisCaches => T )( implicit name: CacheName ): Provider[ T ] = new NamedCacheInstanceProvider( f ) + + @inline private def namedBinding[ T: ClassTag ]( f: impl.RedisCaches => T )( implicit name: CacheName ): Binding[ T ] = + bind[ T ].qualifiedWith( name ).to( provider( f ) ) def bindings( instance: RedisInstanceProvider ) = { implicit val name = new CacheName( instance.name ) @@ -56,27 +59,27 @@ object GuiceProvider { // bind implementation of all caches bind[ impl.RedisCaches ].qualifiedWith( name ).to( new GuiceRedisCacheProvider( instance ) ), // expose a single-implementation providers - bind[ CacheApi ].qualifiedWith( name ).to( provider( _.sync ) ), - bind[ CacheAsyncApi ].qualifiedWith( name ).to( provider( _.async ) ), - bind[ play.api.cache.SyncCacheApi ].qualifiedWith( name ).to( provider( _.scalaSync ) ), - bind[ play.cache.SyncCacheApi ].qualifiedWith( name ).to( provider( _.javaSync ) ), - bind[ play.cache.AsyncCacheApi ].qualifiedWith( name ).to( provider( _.javaAsync ) ), + namedBinding( _.sync ), + namedBinding( _.async ), + namedBinding( _.scalaSync ), + namedBinding( _.javaSync ), + namedBinding( _.javaAsync ) ) } def defaults( instance: RedisInstanceProvider ) = { implicit val name = new CacheName( instance.name ) - def namedBinding[ T: ClassTag ]( implicit cacheName: CacheName ): Binding[ T ] = bind[ T ].to( bind[ T ].qualifiedWith( name ) ) + @inline def defaultBinding[ T: ClassTag ]( implicit cacheName: CacheName ): Binding[ T ] = bind[ T ].to( bind[ T ].qualifiedWith( name ) ) Seq( // bind implementation of all caches - namedBinding[ impl.RedisCaches ], + defaultBinding[ impl.RedisCaches ], // expose a single-implementation providers - namedBinding[ CacheApi ], - namedBinding[ CacheAsyncApi ], - namedBinding[ play.api.cache.SyncCacheApi ], - namedBinding[ play.cache.SyncCacheApi ], - namedBinding[ play.cache.AsyncCacheApi ] + defaultBinding[ CacheApi ], + defaultBinding[ CacheAsyncApi ], + defaultBinding[ play.api.cache.SyncCacheApi ], + defaultBinding[ play.cache.SyncCacheApi ], + defaultBinding[ play.cache.AsyncCacheApi ] ) } } diff --git a/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala b/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala index 64fc91fb..d1d4dafe 100644 --- a/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala +++ b/src/main/scala/play/api/cache/redis/connector/RedisCommands.scala @@ -6,7 +6,7 @@ import scala.concurrent.Future import play.api.Logger import play.api.cache.redis.configuration._ -import play.api.inject._ +import play.api.inject.ApplicationLifecycle import akka.actor.ActorSystem import redis.{RedisClient => RedisStandaloneClient, RedisCluster => RedisClusterClient, _} diff --git a/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala b/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala index d8c44632..1023eaf6 100644 --- a/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala +++ b/src/main/scala/play/api/cache/redis/impl/RedisCaches.scala @@ -22,7 +22,7 @@ trait RedisCaches { def javaAsync: play.cache.AsyncCacheApi } -class RedisCachesProvider( instance: RedisInstance, serializer: connector.AkkaSerializer, environment: Environment, recovery: RecoveryPolicyResolver )( implicit system: ActorSystem, lifecycle: ApplicationLifecycle ) extends Provider[ RedisCaches ] { +private[ redis ] class RedisCachesProvider( instance: RedisInstance, serializer: connector.AkkaSerializer, environment: Environment, recovery: RecoveryPolicyResolver )( implicit system: ActorSystem, lifecycle: ApplicationLifecycle ) extends Provider[ RedisCaches ] { private implicit lazy val runtime: RedisRuntime = RedisRuntime( instance, recovery resolve instance.recovery )( system ) private lazy val redisConnector = new connector.RedisConnectorProvider( instance, serializer ).get diff --git a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceSpec.scala b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceSpec.scala index 709f5015..8c16120f 100644 --- a/src/test/scala/play/api/cache/redis/configuration/RedisInstanceSpec.scala +++ b/src/test/scala/play/api/cache/redis/configuration/RedisInstanceSpec.scala @@ -14,7 +14,7 @@ class RedisInstanceSpec extends Specification { implicit val loader = RedisInstanceProvider.loader( "play" ) // instance resolver implicit def resolver = new RedisInstanceResolver { - val resolve = { + val resolve: PartialFunction[ String, RedisInstance ] = { case name => RedisStandalone( name = s"resolved-$name", host = RedisHost( "localhost", 6380 ), settings = defaults ) } }