Skip to content

Commit

Permalink
Make CacheApi use futures
Browse files Browse the repository at this point in the history
  • Loading branch information
gmethvin committed Mar 21, 2016
1 parent 8165ede commit be668c5
Show file tree
Hide file tree
Showing 18 changed files with 623 additions and 478 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
import javaguide.testhelpers.MockJavaAction;
import javaguide.testhelpers.MockJavaActionHelper;

import java.lang.Throwable;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.CompletableFuture;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
Expand Down Expand Up @@ -43,28 +46,38 @@ public void simple() {
CacheApi cache = app.injector().instanceOf(CacheApi.class);

News frontPageNews = new News();
{
//#simple-set
cache.set("item.key", frontPageNews);
CompletionStage<Void> result = cache.set("item.key", frontPageNews);
//#simple-set
block(result);
//#time-set
}
{
// Cache for 15 minutes
cache.set("item.key", frontPageNews, 60 * 15);
CompletionStage<Void> result = cache.set("item.key", frontPageNews, 60 * 15);
//#time-set
block(result);
}
//#get
News news = cache.get("item.key");
CompletionStage<News> news = cache.get("item.key");
//#get
assertThat(news, equalTo(frontPageNews));
assertThat(block(news), equalTo(frontPageNews));
//#get-or-else
News maybeCached = cache.getOrElse("item.key", () -> lookUpFrontPageNews());
CompletionStage<News> maybeCached = cache.getOrElseUpdate("item.key", () -> lookUpFrontPageNews());
//#get-or-else
assertThat(block(maybeCached), equalTo(frontPageNews));
{
//#remove
cache.remove("item.key");
CompletionStage<Void> result = cache.remove("item.key");
//#remove
assertThat(cache.get("item.key"), nullValue());
block(result);
}
assertThat(cache.sync().get("item.key"), nullValue());
}

private News lookUpFrontPageNews() {
return new News();
private CompletionStage<News> lookUpFrontPageNews() {
return CompletableFuture.completedFuture(new News());
}

public static class Controller1 extends MockJavaAction {
Expand All @@ -81,8 +94,16 @@ public void http() {
CacheApi cache = app.injector().instanceOf(CacheApi.class);

assertThat(contentAsString(MockJavaActionHelper.call(new Controller1(), fakeRequest(), mat)), equalTo("Hello world"));
assertThat(cache.get("homePage"), notNullValue());
assertThat(cache.sync().get("homePage"), notNullValue());
cache.set("homePage", Results.ok("something else"));
assertThat(contentAsString(MockJavaActionHelper.call(new Controller1(), fakeRequest(), mat)), equalTo("something else"));
}

private static <T> T block(CompletionStage<T> stage) {
try {
return stage.toCompletableFuture().get();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Caching data is a typical optimization in modern applications, and so Play provi

> An important point about the cache is that it behaves just like a cache should: the data you just stored may just go missing.
For any data stored in the cache, a regeneration strategy needs to be put in place in case the data goes missing. This philosophy is one of the fundamentals behind Play, and is different from Java EE, where the session is expected to retain values throughout its lifetime.
For any data stored in the cache, a regeneration strategy needs to be put in place in case the data goes missing. This philosophy is one of the fundamentals behind Play, and is different from Java EE, where the session is expected to retain values throughout its lifetime.

The default implementation of the Cache API uses [EHCache](http://ehcache.org/).

Expand All @@ -28,7 +28,7 @@ The cache API is provided by the [CacheApi](api/scala/play/api/cache/CacheApi.ht

> **Note:** The API is intentionally minimal to allow several implementation to be plugged in. If you need a more specific API, use the one provided by your Cache plugin.
Using this simple API you can either store data in cache:
Using this simple API you can store data in cache:

@[set-value](code/ScalaCache.scala)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ package scalaguide.cache {
import akka.stream.ActorMaterializer
import org.junit.runner.RunWith
import org.specs2.runner.JUnitRunner
import org.specs2.execute.AsResult

import play.api.Play.current
import play.api.test._
import play.api.mvc._
import play.api.libs.json.Json
import scala.concurrent.Future
import org.specs2.execute.AsResult

import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext

@RunWith(classOf[JUnitRunner])
class ScalaCacheSpec extends PlaySpecification with Controller {
Expand All @@ -38,29 +39,34 @@ class ScalaCacheSpec extends PlaySpecification with Controller {
"a cache" in withCache { cache =>
val connectedUser = User("xf")
//#set-value
cache.set("item.key", connectedUser)
val result: Future[Unit] = cache.set("item.key", connectedUser)
//#set-value
Await.result(result, 1.second)

//#get-value
val maybeUser: Option[User] = cache.get[User]("item.key")
val futureMaybeUser: Future[Option[User]] = cache.get[User]("item.key")
//#get-value

val maybeUser = Await.result(futureMaybeUser, 1.second)
maybeUser must beSome(connectedUser)

//#remove-value
cache.remove("item.key")
val removeResult: Future[Unit] = cache.remove("item.key")
//#remove-value
cache.get[User]("item.key") must beNone
Await.result(removeResult, 1.second)

cache.sync.get[User]("item.key") must beNone
}


"a cache or get user" in withCache { cache =>
val connectedUser = "xf"
//#retrieve-missing
val user: User = cache.getOrElse[User]("item.key") {
val futureUser: Future[User] = cache.getOrElseUpdate[User]("item.key") {
User.findById(connectedUser)
}
//#retrieve-missing
val user = Await.result(futureUser, 1.second)
user must beEqualTo(User(connectedUser))
}

Expand All @@ -69,8 +75,9 @@ class ScalaCacheSpec extends PlaySpecification with Controller {
//#set-value-expiration
import scala.concurrent.duration._

cache.set("item.key", connectedUser, 5.minutes)
val result: Future[Unit] = cache.set("item.key", connectedUser, 5.minutes)
//#set-value-expiration
Await.result(result, 1.second)
ok
}

Expand Down Expand Up @@ -187,7 +194,7 @@ class Application @Inject() (cached: Cached) extends Controller {
}
//#cached-action-app

class Application1 @Inject() (cached: Cached) extends Controller {
class Application1 @Inject() (cached: Cached)(implicit ec: ExecutionContext) extends Controller {
//#cached-action
def index = cached("homePage") {
Action {
Expand All @@ -199,13 +206,14 @@ class Application1 @Inject() (cached: Cached) extends Controller {
import play.api.mvc.Security.Authenticated

//#composition-cached-action
def userProfile = Authenticated {
user =>
cached(req => "profile." + user) {
Action {
Ok(views.html.profile(User.find(user)))
def userProfile = Authenticated { userId =>
cached(req => "profile." + userId) {
Action.async {
User.find(userId).map { user =>
Ok(views.html.profile(user))
}
}
}
}
//#composition-cached-action
//#cached-action-control
Expand Down Expand Up @@ -245,9 +253,9 @@ class Application2 @Inject() (cached: Cached) extends Controller {
case class User(name: String)

object User {
def findById(userId: String) = User(userId)
def findById(userId: String) = Future.successful(User(userId))

def find(user: String) = User(user)
def find(user: String) = Future.successful(User(user))
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <https://www.lightbend.com>
*/
package play.cache;

import java.util.concurrent.Callable;

/**
* A CacheApi that makes blocking calls.
*/
public interface BlockingCacheApi {
/**
* Retrieves an object by key.
*
* @param <T> the type of the stored object
* @param key the key to look up
* @return the object or null
*/
<T> T get(String key);

/**
* Retrieve a value from the cache, or set it from a default Callable function.
*
* @param <T> the type of the value
* @param key Item key.
* @param block block returning value to set if key does not exist
* @param expiration expiration period in seconds.
* @return the value
*/
<T> T getOrElseUpdate(String key, Callable<T> block, int expiration);

/**
* Retrieve a value from the cache, or set it from a default Callable function.
*
* The value has no expiration.
*
* @param <T> the type of the value
* @param key Item key.
* @param block block returning value to set if key does not exist
* @return the value
*/
<T> T getOrElseUpdate(String key, Callable<T> block);

/**
* Sets a value with expiration.
*
* @param key Item key.
* @param value The value to set.
* @param expiration expiration in seconds
*/
void set(String key, Object value, int expiration);

/**
* Sets a value without expiration.
*
* @param key Item key.
* @param value The value to set.
*/
void set(String key, Object value);

/**
* Removes a value from the cache.
*
* @param key The key to remove the value for.
*/
void remove(String key);
}
69 changes: 0 additions & 69 deletions framework/src/play-cache/src/main/java/play/cache/Cache.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,83 +3,14 @@
*/
package play.cache;

import java.util.concurrent.Callable;

/**
* Provides an access point for Play's cache service.
*
* @deprecated Please use an dependency injected instance of CacheApi.
*/
@Deprecated
public class Cache {

static CacheApi cacheApi() {
return play.api.Play.current().injector().instanceOf(CacheApi.class);
}

/**
* Retrieves an object by key.
*
* @param key the cache key
* @return the object or null
* @deprecated Please use non-static cacheApi.get(key), deprecated since 2.5.0
*/
@Deprecated
public static Object get(String key) {
return cacheApi().get(key);
}

/**
* Retrieve a value from the cache, or set it from a default Callable function.
*
* @deprecated Please use non-static cacheApi.getOrElse, deprecated since 2.5.0
*
* @param <T> the type of object being queried
* @param key Item key.
* @param block block returning value to set if key does not exist
* @param expiration expiration period in seconds.
* @return value
*/
@Deprecated
@SuppressWarnings("unchecked")
public static <T> T getOrElse(String key, Callable<T> block, int expiration) {
return cacheApi().getOrElse(key, block, expiration);
}

/**
* Sets a value with expiration.
*
* @deprecated Please use non-static cacheApi.set(key,value,expiration), deprecated since 2.5.0
*
* @param key the key to set
* @param value the value to set
* @param expiration expiration in seconds
*/
@Deprecated
public static void set(String key, Object value, int expiration) {
cacheApi().set(key, value, expiration);
}

/**
* Sets a value without expiration.
*
* @param key the key to set
* @param value the value to set
*/
@Deprecated
public static void set(String key, Object value) {
cacheApi().set(key, value);
}

/**
* Removes the entry at a specific key
*
* @deprecated Please use non-static cacheApi.set(key,value,expiration), deprecated since 2.5.0
*
* @param key the key whose entry to remove
*/
@Deprecated
public static void remove(String key) {
cacheApi().remove(key);
}
}

0 comments on commit be668c5

Please sign in to comment.