Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code simplification #122

Merged
merged 8 commits into from
Sep 25, 2017
4 changes: 1 addition & 3 deletions src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -202,8 +202,6 @@ play.cache.redis {
# Akka configuration
# ==================
akka {
log-dead-letters = off
log-dead-letters-during-shutdown = off

actor {
serialization-bindings {
Expand Down
41 changes: 41 additions & 0 deletions src/main/scala/play/api/cache/redis/Expiration.scala
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
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.exception._
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
Expand Down Expand Up @@ -118,15 +118,15 @@ 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
* operation and it should behave like there is no cache
*
* @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
Expand All @@ -138,7 +138,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.
Expand All @@ -148,4 +148,42 @@ 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

/**
* 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: PartialFunction[ String, RecoveryPolicy ]
}

class RecoveryPolicyResolverImpl extends RecoveryPolicyResolver {
val resolve: 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
}
}

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 = {
case name => injector instanceOf bind[ RecoveryPolicy ].qualifiedWith( name )
}
}
118 changes: 28 additions & 90 deletions src/main/scala/play/api/cache/redis/RedisCacheComponents.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,105 +5,43 @@ 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
/**
* <p>Components for compile-time dependency injection.
* It binds components from configuration package</p>
*
* @author Karel Cemus
*/
trait RedisCacheComponents
{
implicit def actorSystem: akka.actor.ActorSystem
implicit def applicationLifecycle: ApplicationLifecycle
def configuration: Configuration
def environment: Environment

private lazy val akkaSerializer: AkkaSerializer = new AkkaSerializerImpl( actorSystem )
/** default implementation of the empty resolver */
private lazy val emptyRecoveryResolver = new RecoveryPolicyResolverImpl

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
}
/** override this for providing a custom policy resolver */
implicit def recoveryPolicyResolver = emptyRecoveryResolver

private[ redis ] def redisConnectorFor( instance: RedisInstance ) =
new RedisConnectorImpl( akkaSerializer, instance, redisCommandsFor( instance ) )( actorSystem )
/** default implementation of the empty resolver */
private lazy val emptyInstanceResolver = new play.api.cache.redis.configuration.RedisInstanceResolver {
val resolve = PartialFunction.empty
}
}

package impl {

private[ redis ] trait RedisImplComponents {
/** override this for providing a custom redis instance resolver */
implicit def redisInstanceResolver = emptyInstanceResolver

def environment: Environment
private lazy val akkaSerializer: connector.AkkaSerializer = new connector.AkkaSerializerProvider().get

/** 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 def hasInstances = configuration.underlying.hasPath( "play.cache.redis.instances" )

private[ redis ] def redisConnectorFor( instance: RedisInstance ): RedisConnector
private def defaultCache = configuration.underlying.getString( "play.cache.redis.default-cache" )

private implicit def instance2connector( instance: RedisInstance ): RedisConnector = redisConnectorFor( instance )
private implicit def instance2policy( instance: RedisInstance ): RecoveryPolicy = redisRecoveryPolicy( instance.recovery )
private lazy val manager = configuration.get( "play.cache.redis" )( play.api.cache.redis.configuration.RedisInstanceManager )

// play-redis APIs
private def asyncRedis( instance: RedisInstance ) = new AsyncRedis( instance.name, redis = instance, policy = 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

def syncRedisCacheApi( instance: RedisInstance ): CacheApi = new SyncRedis( instance.name, redis = instance, policy = instance )
def asyncRedisCacheApi( instance: RedisInstance ): CacheAsyncApi = asyncRedis( instance )

// 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 )

// java api defined by Play
def javaAsyncCacheApi( instance: RedisInstance ): play.cache.AsyncCacheApi = new JavaRedis( instance.name, asyncRedis( instance ), environment = environment, connector = instance )
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
}


/**
* <p>Components for compile-time dependency injection.
* It binds components from configuration package</p>
*
* @author Karel Cemus
*/
trait RedisCacheComponents
extends configuration.RedisConfigurationComponents
with connector.RedisConnectorComponents
with impl.RedisImplComponents
Loading