Skip to content

Commit

Permalink
Make CacheApi use futures (#5923)
Browse files Browse the repository at this point in the history
  • Loading branch information
gmethvin committed Jun 18, 2016
1 parent 9e596b7 commit 936de27
Show file tree
Hide file tree
Showing 23 changed files with 850 additions and 726 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@ While Play 2.4 won't force you to use the dependency injected versions of compon
| [`Lang`](api/scala/play/api/i18n/Lang$.html) | [`Langs`](api/scala/play/api/i18n/Langs.html) | |
| [`Messages`](api/scala/play/api/i18n/Messages$.html) | [`MessagesApi`](api/scala/play/api/i18n/MessagesApi.html) | Using one of the `preferred` methods, you can get a [`Messages`](api/scala/play/api/i18n/Messages.html) instance. |
| [`DB`](api/scala/play/api/db/DB$.html) | [`DBApi`](api/scala/play/api/db/DBApi.html) or better, [`Database`](api/scala/play/api/db/Database.html) | You can get a particular database using the `@NamedDatabase` annotation. |
| [`Cache`](api/scala/play/api/cache/Cache$.html) | [`CacheApi`](api/scala/play/api/cache/CacheApi.html) or better | You can get a particular cache using the `@NamedCache` annotation. |
| [`Cached` object](api/scala/play/api/cache/Cached$.html) | [`Cached` instance](api/scala/play/api/cache/Cached.html) | Use an injected instance instead of the companion object. You can use the `@NamedCache` annotation. |
| `Cache` | [`CacheApi`](api/scala/play/api/cache/CacheApi.html) or better | You can get a particular cache using the `@NamedCache` annotation. |
| `Cached` object | [`Cached` instance](api/scala/play/api/cache/Cached.html) | Use an injected instance instead of the companion object. You can use the `@NamedCache` annotation. |
| [`Akka`](api/scala/play/api/libs/concurrent/Akka$.html) | N/A | No longer needed, just declare a dependency on `ActorSystem` |
| [`WS`](api/scala/play/api/libs/ws/WS$.html) | [`WSClient`](api/scala/play/api/libs/ws/WSClient.html) | |
| [`Crypto`](api/scala/play/api/libs/Crypto$.html) | [`Crypto`](api/scala/play/api/libs/Crypto.html) | |
Expand All @@ -207,7 +207,7 @@ While Play 2.4 won't force you to use the dependency injected versions of compon
| [`Messages`](api/java/play/i18n/Messages.html) | [`MessagesApi`](api/java/play/i18n/MessagesApi.html) | Using one of the `preferred` methods, you can get a `Messages` instance, and you can then use `at` to get messages for that lang. |
| [`DB`](api/java/play/db/DB.html) | [`DBApi`](api/java/play/db/DBApi.html) or better, [`Database`](api/java/play/db/Database.html) | You can get a particular database using the [`@NamedDatabase`](api/java/play/db/NamedDatabase.html) annotation. |
| [`JPA`](api/java/play/db/jpa/JPA.html) | [`JPAApi`](api/java/play/db/jpa/JPAApi.html) | |
| [`Cache`](api/java/play/cache/Cache.html) | [`CacheApi`](api/java/play/cache/CacheApi.html) | You can get a particular cache using the [`@NamedCache`](api/java/play/cache/NamedCache.html) annotation. |
| `Cache` | [`CacheApi`](api/java/play/cache/CacheApi.html) | You can get a particular cache using the [`@NamedCache`](api/java/play/cache/NamedCache.html) annotation. |
| [`Akka`](api/java/play/libs/Akka.html) | N/A | No longer needed, just declare a dependency on `ActorSystem` |
| [`WS`](api/java/play/libs/ws/WS.html) | [`WSClient`](api/java/play/libs/ws/WSClient.html) | |
| [`Crypto`](api/java/play/libs/Crypto.html) | [`Crypto`](api/java/play/libs/Crypto.html) | The old static methods have been removed, an instance can statically be accessed using `play.Play.application().injector().instanceOf(Crypto.class)` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Add `cache` into your dependencies list. For example, in `build.sbt`:

## Accessing the Cache API

The cache API is provided by the [CacheApi](api/java/play/cache/CacheApi.html) object, and can be injected into your component like any other dependency. For example:
The cache API is defined by the [AsyncCacheApi](api/java/play/cache/AsyncCacheApi.html) and [SyncCacheApi](api/java/play/cache/SyncCacheApi.html) interfaces, depending on whether you want an asynchronous or synchronous implementation, and can be injected into your component like any other dependency. For example:

@[inject](code/javaguide/cache/inject/Application.java)

Expand All @@ -41,6 +41,8 @@ To remove an item from the cache use the `remove` method:

@[remove](code/javaguide/cache/JavaCache.java)

Note that the [SyncCacheApi](api/java/play/cache/SyncCacheApi.html) has the same API, except it returns the values directly instead of using futures.

## Accessing different caches

It is possible to access different caches. The default cache is called `play`, and can be configured by creating a file called `ehcache.xml`. Additional caches may be configured with different configurations, or even implementations.
Expand Down Expand Up @@ -69,14 +71,14 @@ Play provides a default built-in helper for the standard case:

## Custom implementations

It is possible to provide a custom implementation of the [CacheApi](api/java/play/cache/CacheApi.html) that either replaces, or sits along side the default implementation.
It is possible to provide a custom implementation of the cache API that either replaces or sits alongside the default implementation.

To replace the default implementation, you'll need to disable the default implementation by setting the following in `application.conf`:

```
play.modules.disabled += "play.api.cache.EhCacheModule"
```

Then simply implement [CacheApi](api/java/play/cache/CacheApi.html) and bind it in the DI container.
You can then implement [AsyncCacheApi](api/java/play/cache/AsyncCacheApi.html) and bind it in the DI container. You can also bind [SyncCacheApi](api/java/play/cache/SyncCacheApi.html) to [DefaultSyncCacheApi](api/java/play/cache/DefaultSyncCacheApi.html), which simply wraps the async implementation.

To provide an implementation of the cache API in addition to the default implementation, you can either create a custom qualifier, or reuse the `NamedCache` qualifier to bind the implementation.
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@
*/
package javaguide.cache;

import akka.Done;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import play.Application;
import play.cache.CacheApi;
import play.cache.AsyncCacheApi;
import play.cache.Cached;
import play.mvc.*;
import play.test.WithApplication;

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 All @@ -40,31 +44,41 @@ public void inject() {

@Test
public void simple() {
CacheApi cache = app.injector().instanceOf(CacheApi.class);
AsyncCacheApi cache = app.injector().instanceOf(AsyncCacheApi.class);

News frontPageNews = new News();
{
//#simple-set
cache.set("item.key", frontPageNews);
CompletionStage<Done> result = cache.set("item.key", frontPageNews);
//#simple-set
block(result);
//#time-set
}
{
// Cache for 15 minutes
cache.set("item.key", frontPageNews, 60 * 15);
CompletionStage<Done> 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<Done> 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 @@ -78,11 +92,19 @@ public Result index() {

@Test
public void http() {
CacheApi cache = app.injector().instanceOf(CacheApi.class);
AsyncCacheApi cache = app.injector().instanceOf(AsyncCacheApi.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 @@ -10,10 +10,10 @@

public class Application extends Controller {

private CacheApi cache;
private AsyncCacheApi cache;

@Inject
public Application(CacheApi cache) {
public Application(AsyncCacheApi cache) {
this.cache = cache;
}

Expand Down
10 changes: 6 additions & 4 deletions documentation/manual/working/scalaGuide/main/cache/ScalaCache.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ libraryDependencies ++= Seq(

## Accessing the Cache API

The cache API is provided by the [CacheApi](api/scala/play/api/cache/CacheApi.html) object, and can be injected into your component like any other dependency. For example:
The cache API is defined by the [AsyncCacheApi](api/scala/play/api/cache/AsyncCacheApi.html) and [SyncCacheApi](api/scala/play/api/cache/SyncCacheApi.html) traits, depending on whether you want an asynchronous or synchronous implementation, and can be injected into your component like any other dependency. For example:

@[inject](code/ScalaCache.scala)

> **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 All @@ -48,6 +48,8 @@ To remove an item from the cache use the `remove` method:

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

Note that the [SyncCacheApi](api/scala/play/api/cache/SyncCacheApi.html) has the same API, except it returns the values directly instead of using futures.

## Accessing different caches

It is possible to access different caches. The default cache is called `play`, and can be configured by creating a file called `ehcache.xml`. Additional caches may be configured with different configurations, or even implementations.
Expand Down Expand Up @@ -96,14 +98,14 @@ Or cache 404 Not Found only for a couple of minutes

## Custom implementations

It is possible to provide a custom implementation of the [CacheApi](api/scala/play/api/cache/CacheApi.html) that either replaces, or sits along side the default implementation.
It is possible to provide a custom implementation of the cache API that either replaces, or sits along side the default implementation.

To replace the default implementation, you'll need to disable the default implementation by setting the following in `application.conf`:

```
play.modules.disabled += "play.api.cache.EhCacheModule"
```

Then simply implement `CacheApi` and bind it in the [[DI container|ScalaDependencyInjection]].
You can then implement [AsyncCacheApi](api/java/play/cache/AsyncCacheApi.html) and bind it in the DI container. You can also bind [SyncCacheApi](api/java/play/cache/SyncCacheApi.html) to [DefaultSyncCacheApi](api/java/play/cache/DefaultSyncCacheApi.html), which simply wraps the async implementation.

To provide an implementation of the cache API in addition to the default implementation, you can either create a custom qualifier, or reuse the `NamedCache` qualifier to bind the implementation.
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,28 @@
*/
package scalaguide.cache {

import akka.Done
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 {

import play.api.cache.CacheApi
import play.api.cache.AsyncCacheApi
import play.api.cache.Cached

def withCache[T](block: CacheApi => T) = {
running()(app => block(app.injector.instanceOf[CacheApi]))
def withCache[T](block: AsyncCacheApi => T) = {
running()(app => block(app.injector.instanceOf[AsyncCacheApi]))
}

"A scala Cache" should {
Expand All @@ -38,29 +40,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[Done] = 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[Done] = 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 +76,9 @@ class ScalaCacheSpec extends PlaySpecification with Controller {
//#set-value-expiration
import scala.concurrent.duration._

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

Expand Down Expand Up @@ -157,7 +165,7 @@ import play.api.cache._
import play.api.mvc._
import javax.inject.Inject

class Application @Inject() (cache: CacheApi) extends Controller {
class Application @Inject() (cache: AsyncCacheApi) extends Controller {

}
//#inject
Expand All @@ -170,7 +178,7 @@ import play.api.mvc._
import javax.inject.Inject

class Application @Inject()(
@NamedCache("session-cache") sessionCache: CacheApi
@NamedCache("session-cache") sessionCache: AsyncCacheApi
) extends Controller {

}
Expand All @@ -187,7 +195,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 +207,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 +254,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
Loading

0 comments on commit 936de27

Please sign in to comment.