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

Support of plain arrays in JavaRedis #177

Merged
merged 1 commit into from
Jul 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@

## Changelog

### [:link: 2.2.0](https://github.com/KarelCemus/play-redis/tree/2.2.0)

Support of plain arrays in JavaRedis [#176](https://github.com/KarelCemus/play-redis/pull/176).

### [:link: 2.1.2](https://github.com/KarelCemus/play-redis/tree/2.1.2)

JDK 10 compatibility. Replace deprecated com.sun.misc.BASE64* usages with jdk8 java.util.Base64 [#170](https://github.com/KarelCemus/play-redis/pull/170).
Expand Down
29 changes: 26 additions & 3 deletions src/main/scala/play/api/cache/redis/impl/JavaRedis.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ private[ impl ] class JavaRedis( internal: CacheAsyncApi, environment: Environme
// set the value
internal.set( key, value, duration ),
// and set its type to be able to read it
internal.set( s"classTag::$key", if ( value == null ) "" else value.getClass.getCanonicalName, duration )
internal.set( s"classTag::$key", classTagOf( value ), duration )
)
).map {
case Seq( done, _ ) => done
Expand All @@ -57,8 +57,7 @@ private[ impl ] class JavaRedis( internal: CacheAsyncApi, environment: Environme
implicit val context = HttpExecutionContext.fromThread( runtime.context )
// get the tag and decode it
def getClassTag = internal.get[ String ]( s"classTag::$key" )
def decodeClassTag( name: String ): ClassTag[ T ] = if ( name == "" ) ClassTag.Null.asInstanceOf[ ClassTag[ T ] ] else ClassTag( environment.classLoader.loadClass( name ) )
def decodedClassTag( tag: Option[ String ] ) = tag.map( decodeClassTag )
def decodedClassTag( tag: Option[ String ] ) = tag.map( classTagFrom[ T ] )
// if tag is defined, get Option[ value ] otherwise None
def getValue = getClassTag.map( decodedClassTag ).flatMap {
case Some( ClassTag.Null ) => Future.successful( Some( null.asInstanceOf[ T ] ) )
Expand All @@ -81,6 +80,15 @@ private[ impl ] class JavaRedis( internal: CacheAsyncApi, environment: Environme
}

def removeAll( ) = internal.invalidate().toJava

protected def classTagOf( value: Any ): String = {
if ( value == null ) "" else value.getClass.getCanonicalName
}

protected def classTagFrom[ T ]( tag: String ): ClassTag[ T ] = {
if ( tag == "" ) ClassTag.Null.asInstanceOf[ ClassTag[ T ] ]
else ClassTag( classTagNameToClass( tag, environment ) )
}
}

private[ impl ] object JavaRedis {
Expand All @@ -93,4 +101,19 @@ private[ impl ] object JavaRedis {
private[ impl ] implicit class ScalaCompatibility[ T ]( val future: CompletionStage[ T ] ) extends AnyVal {
@inline def toScala: Future[ T ] = FutureConverters.toScala( future )
}

// $COVERAGE-OFF$
/** java primitives are serialized into their type names instead of classes */
private[ impl ] def classTagNameToClass( name: String, environment: Environment ): Class[ _ ] = name match {
case "boolean[]" => classOf[ Array[ java.lang.Boolean ] ]
case "byte[]" => classOf[ Array[ java.lang.Byte ] ]
case "char[]" => classOf[ Array[ java.lang.Character ] ]
case "short[]" => classOf[ Array[ java.lang.Short ] ]
case "int[]" => classOf[ Array[ java.lang.Integer ] ]
case "long[]" => classOf[ Array[ java.lang.Long ] ]
case "float[]" => classOf[ Array[ java.lang.Float ] ]
case "double[]" => classOf[ Array[ java.lang.Double ] ]
case clazz => environment.classLoader.loadClass( clazz )
}
// $COVERAGE-ON$
}
10 changes: 10 additions & 0 deletions src/test/java/play/api/cache/redis/JavaTypes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package play.api.cache.redis;

/**
* @author Karel Cemus
*/
public class JavaTypes {

public static final byte byteValue = 5;
public static final byte[] bytesValue = new byte[]{1, 2, 3};
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import java.util.Date
import scala.reflect.ClassTag

import play.api.inject.guice.GuiceApplicationBuilder
import play.api.cache.redis._

import org.joda.time.{DateTime, DateTimeZone}
import org.specs2.mock.Mockito
Expand All @@ -24,6 +25,11 @@ class SerializerSpecs extends Specification with Mockito {

"byte" in {
0xAB.toByte.encoded mustEqual "-85"
JavaTypes.byteValue.encoded mustEqual "5"
}

"byte[]" in {
JavaTypes.bytesValue.encoded mustEqual "AQID"
}

"char" in {
Expand Down Expand Up @@ -93,7 +99,6 @@ class SerializerSpecs extends Specification with Mockito {
|TGlzdFNlcmlhbGl6ZUVuZCSKXGNb91MLbQIAAHhweA==
""".stripMargin.lines.map(_.trim).mkString
}

}

"AkkaDecoder" should "decode" >> {
Expand All @@ -102,6 +107,10 @@ class SerializerSpecs extends Specification with Mockito {
"-85".decoded[ Byte ] mustEqual 0xAB.toByte
}

"byte[]" in {
"YWJj".decoded[ Array[ Byte ] ] mustEqual Array( "a".head.toByte, "b".head.toByte, "c".head.toByte )
}

"char" in {
"a".decoded[ Char ] mustEqual 'a'
"b".decoded[ Char ] mustEqual 'b'
Expand Down
32 changes: 30 additions & 2 deletions src/test/scala/play/api/cache/redis/impl/JavaRedisSpecs.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package play.api.cache.redis.impl

import java.util.concurrent.Callable

import scala.concurrent.duration.Duration

import play.api.cache.redis._
Expand Down Expand Up @@ -105,5 +103,35 @@ class JavaRedisSpecs( implicit ee: ExecutionEnv ) extends Specification with Red
async.invalidate() returns execDone
cache.removeAll().toScala must beDone.await
}

"get and set 'byte'" in new MockedJavaRedis {
val byte = JavaTypes.byteValue

// set a value
// note: there should be hit on "byte" but the value is wrapped instead
async.set( anyString, beEq( byte ), any[ Duration ] ) returns execDone
async.set( anyString, beEq( "byte" ), any[ Duration ] ) returns execDone
async.set( anyString, beEq( "java.lang.Byte" ), any[ Duration ] ) returns execDone
cache.set( key, byte ).toScala must beDone.await

// hit on GET
async.get[ Byte ]( beEq( key ) )( anyClassTag ) returns Some( byte )
async.get[ String ]( beEq( s"classTag::$key" ) )( anyClassTag ) returns Some( "java.lang.Byte" )
cache.get[ Byte ]( key ).toScala must beEqualTo( byte ).await
}

"get and set 'byte[]'" in new MockedJavaRedis {
val bytes = JavaTypes.bytesValue

// set a value
async.set( anyString, beEq( bytes ), any[ Duration ] ) returns execDone
async.set( anyString, beEq( "byte[]" ), any[ Duration ] ) returns execDone
cache.set( key, bytes ).toScala must beDone.await

// hit on GET
async.get[ Array[ Byte ] ]( beEq( key ) )( anyClassTag ) returns Some( bytes )
async.get[ String ]( beEq( s"classTag::$key" ) )( anyClassTag ) returns Some( "byte[]" )
cache.get[ Array[ Byte ] ]( key ).toScala must beEqualTo( bytes ).await
}
}
}