From 28bbdaaeeb63765acab1895bcd9d57975332d17d Mon Sep 17 00:00:00 2001 From: Sergei Dudzin Date: Fri, 3 May 2024 17:20:56 +0200 Subject: [PATCH] Bump ehcache to version 3 --- build.sbt | 3 +- .../play/api/cache}/ExpirableCacheValue.scala | 4 +- .../caffeine/DefaultCaffeineExpiry.scala | 1 + .../play/cache/ehcache/EhCacheComponents.java | 2 +- .../src/main/resources/ehcache-default.xml | 29 ++--- .../play/api/cache/ehcache/EhCacheApi.scala | 106 ++++++++++++------ .../scala/play/api/cache/CachedSpec.scala | 72 +++++++++--- .../api/cache/ehcache/EhCacheApiSpec.scala | 28 +++-- .../play/api/http/HttpErrorHandlerSpec.scala | 9 +- .../ServerIntegrationSpecificationSpec.scala | 4 +- .../scala/play/it/auth/SecuritySpec.scala | 8 +- .../play/it/http/BadClientHandlingSpec.scala | 2 +- .../play/it/http/HttpErrorHandlingSpec.scala | 4 +- .../it/http/JavaHttpErrorHandlingSpec.scala | 4 +- .../it/http/JavaResultsHandlingSpec.scala | 2 +- .../PekkoHttpCustomServerProviderSpec.scala | 5 +- .../it/http/RequestBodyHandlingSpec.scala | 2 +- .../it/http/websocket/WebSocketClient.scala | 2 +- .../it/http/websocket/WebSocketSpec.scala | 9 +- .../test/scala/play/it/mvc/FiltersSpec.scala | 3 +- .../test/scala/play/it/tools/HttpBin.scala | 2 +- .../scala/play/mvc/StatusHeaderSpec.scala | 2 +- documentation/build.sbt | 4 +- .../manual/hacking/BuildingFromSource.md | 2 +- .../manual/releases/release27/Highlights27.md | 2 +- .../release28/migration28/Migration28.md | 4 +- .../release29/migration29/Migration29.md | 4 +- .../commonGuide/build/sbtDependencies.md | 2 +- .../commonGuide/configuration/WsCache.md | 27 +++-- .../working/javaGuide/main/cache/JavaCache.md | 2 +- .../scalaGuide/main/cache/ScalaCache.md | 4 +- .../api/db/evolutions/EvolutionsSpec.scala | 2 +- project/BuildSettings.scala | 11 ++ project/Dependencies.scala | 8 +- project/PlayBuildBase.scala | 8 -- project/Versions.scala | 2 +- .../test/resources/ehcache-play-ws-cache.xml | 25 +++-- .../OptionalAhcHttpCacheProviderSpec.scala | 8 +- .../core/server/ProdServerStartSpec.scala | 4 +- .../play/filters/cors/CORSFilterSpec.scala | 3 +- .../play/filters/cors/CORSWithCSRFSpec.scala | 7 +- .../play/filters/csp/CSPFilterSpec.scala | 2 +- .../play/filters/csrf/CSRFFilterSpec.scala | 5 +- .../filters/csrf/JavaCSRFActionSpec.scala | 9 +- .../play/filters/gzip/GzipFilterSpec.scala | 3 +- .../headers/SecurityHeadersFilterSpec.scala | 3 +- .../hosts/AllowedHostsFilterSpec.scala | 4 +- 47 files changed, 291 insertions(+), 167 deletions(-) rename cache/{play-caffeine-cache/src/main/scala/play/api/cache/caffeine => play-cache/src/main/scala/play/api/cache}/ExpirableCacheValue.scala (66%) diff --git a/build.sbt b/build.sbt index 168f1f97471..a20e76d2a0b 100644 --- a/build.sbt +++ b/build.sbt @@ -294,7 +294,8 @@ lazy val PlayAhcWsProject = PlayCrossBuiltProject("Play-AHC-WS", "transport/clie // quieten deprecation warnings in tests (Test / scalacOptions) := (Test / scalacOptions).value.diff(Seq("-deprecation")) ) - .dependsOn(PlayWsProject, PlayCaffeineCacheProject % "test") + .dependsOn(PlayWsProject) + .dependsOn(PlayCaffeineCacheProject % "test") .dependsOn(PlaySpecs2Project % "test") .dependsOn(PlayTestProject % "test->test") .dependsOn(PlayPekkoHttpServerProject % "test") // Because we need a server provider when running the tests diff --git a/cache/play-caffeine-cache/src/main/scala/play/api/cache/caffeine/ExpirableCacheValue.scala b/cache/play-cache/src/main/scala/play/api/cache/ExpirableCacheValue.scala similarity index 66% rename from cache/play-caffeine-cache/src/main/scala/play/api/cache/caffeine/ExpirableCacheValue.scala rename to cache/play-cache/src/main/scala/play/api/cache/ExpirableCacheValue.scala index d1902cba002..353219cc07d 100644 --- a/cache/play-caffeine-cache/src/main/scala/play/api/cache/caffeine/ExpirableCacheValue.scala +++ b/cache/play-cache/src/main/scala/play/api/cache/ExpirableCacheValue.scala @@ -2,11 +2,11 @@ * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. */ -package play.api.cache.caffeine +package play.api.cache import scala.concurrent.duration.Duration import org.apache.pekko.annotation.InternalApi @InternalApi -private[caffeine] case class ExpirableCacheValue[V](value: V, durationMaybe: Option[Duration] = None) +case class ExpirableCacheValue[V](value: V, durationMaybe: Option[Duration] = None) diff --git a/cache/play-caffeine-cache/src/main/scala/play/api/cache/caffeine/DefaultCaffeineExpiry.scala b/cache/play-caffeine-cache/src/main/scala/play/api/cache/caffeine/DefaultCaffeineExpiry.scala index b77c5632316..627c80c03bc 100644 --- a/cache/play-caffeine-cache/src/main/scala/play/api/cache/caffeine/DefaultCaffeineExpiry.scala +++ b/cache/play-caffeine-cache/src/main/scala/play/api/cache/caffeine/DefaultCaffeineExpiry.scala @@ -9,6 +9,7 @@ import scala.concurrent.duration.Duration import com.github.benmanes.caffeine.cache.Expiry import org.apache.pekko.annotation.InternalApi +import play.api.cache.ExpirableCacheValue @InternalApi private[caffeine] class DefaultCaffeineExpiry extends Expiry[String, ExpirableCacheValue[Any]] { diff --git a/cache/play-ehcache/src/main/java/play/cache/ehcache/EhCacheComponents.java b/cache/play-ehcache/src/main/java/play/cache/ehcache/EhCacheComponents.java index 27808d434a0..103649d6bd7 100644 --- a/cache/play-ehcache/src/main/java/play/cache/ehcache/EhCacheComponents.java +++ b/cache/play-ehcache/src/main/java/play/cache/ehcache/EhCacheComponents.java @@ -4,7 +4,7 @@ package play.cache.ehcache; -import net.sf.ehcache.CacheManager; +import org.ehcache.CacheManager; import play.Environment; import play.api.cache.ehcache.CacheManagerProvider; import play.api.cache.ehcache.EhCacheApi; diff --git a/cache/play-ehcache/src/main/resources/ehcache-default.xml b/cache/play-ehcache/src/main/resources/ehcache-default.xml index 65544019189..33eb7d2699f 100644 --- a/cache/play-ehcache/src/main/resources/ehcache-default.xml +++ b/cache/play-ehcache/src/main/resources/ehcache-default.xml @@ -2,18 +2,19 @@ Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. --> - + - - - + + java.lang.String + play.api.cache.ehcache.EhCacheApi.CacheValue + + 120 + + + 10000 + + + diff --git a/cache/play-ehcache/src/main/scala/play/api/cache/ehcache/EhCacheApi.scala b/cache/play-ehcache/src/main/scala/play/api/cache/ehcache/EhCacheApi.scala index 86cf33a566e..c70c9a88dc7 100644 --- a/cache/play-ehcache/src/main/scala/play/api/cache/ehcache/EhCacheApi.scala +++ b/cache/play-ehcache/src/main/scala/play/api/cache/ehcache/EhCacheApi.scala @@ -4,10 +4,13 @@ package play.api.cache.ehcache +import java.time +import java.util.function.Supplier import javax.inject.Inject import javax.inject.Provider import javax.inject.Singleton +import scala.concurrent.duration import scala.concurrent.duration.Duration import scala.concurrent.duration.FiniteDuration import scala.concurrent.ExecutionContext @@ -15,14 +18,21 @@ import scala.concurrent.Future import scala.reflect.ClassTag import com.google.common.primitives.Primitives -import net.sf.ehcache.CacheManager -import net.sf.ehcache.Ehcache -import net.sf.ehcache.Element -import net.sf.ehcache.ObjectExistsException import org.apache.pekko.actor.ActorSystem +import org.apache.pekko.annotation.InternalApi import org.apache.pekko.stream.Materializer import org.apache.pekko.Done +import org.ehcache.config.builders.CacheConfigurationBuilder +import org.ehcache.config.builders.CacheManagerBuilder +import org.ehcache.config.builders.ResourcePoolsBuilder +import org.ehcache.expiry.ExpiryPolicy +import org.ehcache.xml.XmlConfiguration +import org.ehcache.Cache +import org.ehcache.CacheManager import play.api.cache._ +import play.api.cache.ehcache.EhCacheApi.EhExpirableCacheValue +import play.api.cache.ehcache.EhCacheApi.PlayEhCache +import play.api.cache.ExpirableCacheValue import play.api.inject._ import play.api.Configuration import play.api.Environment @@ -85,7 +95,7 @@ class EhCacheModule // bind a cache with the given name def bindCache(name: String) = { val namedCache = named(name) - val ehcacheKey = bind[Ehcache].qualifiedWith(namedCache) + val ehcacheKey = bind[PlayEhCache].qualifiedWith(namedCache) val cacheApiKey = bind[AsyncCacheApi].qualifiedWith(namedCache) Seq( ehcacheKey.to(new NamedEhCacheProvider(name, createBoundCaches)), @@ -113,26 +123,64 @@ class CacheManagerProvider @Inject() (env: Environment, config: Configuration, l lazy val get: CacheManager = { val resourceName = config.underlying.getString("play.cache.configResource") val configResource = env.resource(resourceName).getOrElse(env.classLoader.getResource("ehcache-default.xml")) - val manager = CacheManager.create(configResource) - lifecycle.addStopHook(() => Future.successful(manager.shutdown())) + val configuration = new XmlConfiguration(configResource) + val manager = CacheManagerBuilder.newCacheManager(configuration) + manager.init() + lifecycle.addStopHook(() => Future.successful(manager.close())) manager } } -private[play] class NamedEhCacheProvider(name: String, create: Boolean) extends Provider[Ehcache] { +private[play] class NamedEhCacheProvider(name: String, create: Boolean) extends Provider[PlayEhCache] { @Inject private var manager: CacheManager = _ - lazy val get: Ehcache = NamedEhCacheProvider.getNamedCache(name, manager, create) + lazy val get: PlayEhCache = NamedEhCacheProvider.getNamedCache(name, manager, create) } private[play] object NamedEhCacheProvider { - def getNamedCache(name: String, manager: CacheManager, create: Boolean): Ehcache = + + private val expiryPolicy = new ExpiryPolicy[String, EhExpirableCacheValue]() { + def getExpiryForCreation(key: String, value: EhExpirableCacheValue): time.Duration = value.durationMaybe match { + case Some(finite: FiniteDuration) => + val seconds = finite.toSeconds + if (seconds <= 0) { + time.Duration.ZERO + } else if (seconds > Int.MaxValue) { + ExpiryPolicy.INFINITE + } else { + time.Duration.ofSeconds(seconds.toInt) + } + case _ => ExpiryPolicy.INFINITE + } + + def getExpiryForAccess(key: String, value: Supplier[? <: EhExpirableCacheValue]): time.Duration = null + + def getExpiryForUpdate( + key: String, + oldValue: Supplier[? <: EhExpirableCacheValue], + newValue: EhExpirableCacheValue + ): time.Duration = null + } + + private def cacheConfigurationBuilder(manager: CacheManager) = { + val builder = manager.getRuntimeConfiguration match { + case configuration: XmlConfiguration => + configuration + .newCacheConfigurationBuilderFromTemplate("default", classOf[String], classOf[EhExpirableCacheValue]) + case _ => + CacheConfigurationBuilder + .newCacheConfigurationBuilder(classOf[String], classOf[EhExpirableCacheValue], ResourcePoolsBuilder.heap(100)) + } + builder.withExpiry(expiryPolicy) + } + + def getNamedCache(name: String, manager: CacheManager, create: Boolean): PlayEhCache = try { if (create) { - manager.addCache(name) + manager.createCache(name, cacheConfigurationBuilder(manager)) } - manager.getEhcache(name) + manager.getCache(name, classOf[String], classOf[EhExpirableCacheValue]) } catch { - case e: ObjectExistsException => + case e: IllegalArgumentException => throw EhCacheExistsException( s"""An EhCache instance with name '$name' already exists. | @@ -143,7 +191,7 @@ private[play] object NamedEhCacheProvider { } } -private[play] class NamedAsyncCacheApiProvider(key: BindingKey[Ehcache]) extends Provider[AsyncCacheApi] { +private[play] class NamedAsyncCacheApiProvider(key: BindingKey[PlayEhCache]) extends Provider[AsyncCacheApi] { @Inject private var injector: Injector = _ @Inject private var defaultEc: ExecutionContext = _ @Inject private var config: Configuration = _ @@ -185,23 +233,10 @@ private[play] class NamedCachedProvider(key: BindingKey[AsyncCacheApi]) extends private[play] case class EhCacheExistsException(msg: String, cause: Throwable) extends RuntimeException(msg, cause) -class SyncEhCacheApi @Inject() (private[ehcache] val cache: Ehcache) extends SyncCacheApi { +class SyncEhCacheApi @Inject() (private[ehcache] val cache: PlayEhCache) extends SyncCacheApi { override def set(key: String, value: Any, expiration: Duration): Unit = { - val element = new Element(key, value) - expiration match { - case infinite: Duration.Infinite => element.setEternal(true) - case finite: FiniteDuration => - val seconds = finite.toSeconds - if (seconds <= 0) { - element.setTimeToLive(1) - } else if (seconds > Int.MaxValue) { - element.setTimeToLive(Int.MaxValue) - } else { - element.setTimeToLive(seconds.toInt) - } - } - cache.put(element) - Done + cache.put(key, ExpirableCacheValue[Any](value, Some(expiration))) + () } override def remove(key: String): Unit = cache.remove(key) @@ -218,7 +253,7 @@ class SyncEhCacheApi @Inject() (private[ehcache] val cache: Ehcache) extends Syn override def get[T](key: String)(implicit ct: ClassTag[T]): Option[T] = { Option(cache.get(key)) - .map(_.getObjectValue) + .map(_.value) .filter { v => Primitives.wrap(ct.runtimeClass).isInstance(v) || ct == ClassTag.Nothing || (ct == ClassTag.Unit && v == ((): Unit).asInstanceOf[Any]) @@ -230,7 +265,7 @@ class SyncEhCacheApi @Inject() (private[ehcache] val cache: Ehcache) extends Syn /** * Ehcache implementation of [[AsyncCacheApi]]. Since Ehcache is synchronous by default, this uses [[SyncEhCacheApi]]. */ -class EhCacheApi @Inject() (private[ehcache] val cache: Ehcache)(implicit context: ExecutionContext) +class EhCacheApi @Inject() (private[ehcache] val cache: PlayEhCache)(implicit context: ExecutionContext) extends AsyncCacheApi { override lazy val sync: SyncEhCacheApi = new SyncEhCacheApi(cache) @@ -256,7 +291,12 @@ class EhCacheApi @Inject() (private[ehcache] val cache: Ehcache)(implicit contex } def removeAll(): Future[Done] = Future { - cache.removeAll() + cache.clear() Done } } + +object EhCacheApi { + type EhExpirableCacheValue = ExpirableCacheValue[Any] + type PlayEhCache = Cache[String, EhExpirableCacheValue] +} diff --git a/cache/play-ehcache/src/test/scala/play/api/cache/CachedSpec.scala b/cache/play-ehcache/src/test/scala/play/api/cache/CachedSpec.scala index 9342c78cc29..935924d0856 100644 --- a/cache/play-ehcache/src/test/scala/play/api/cache/CachedSpec.scala +++ b/cache/play-ehcache/src/test/scala/play/api/cache/CachedSpec.scala @@ -4,18 +4,35 @@ package play.api.cache +import java.nio.file.Files +import java.time.{ Duration => JDurarion } import java.time.Instant import java.util.concurrent.atomic.AtomicInteger import javax.inject._ import scala.concurrent.duration._ +import scala.concurrent.Future import scala.util.Random +import org.ehcache.config.builders.CacheConfigurationBuilder +import org.ehcache.config.builders.CacheManagerBuilder +import org.ehcache.config.builders.ConfigurationBuilder +import org.ehcache.config.builders.ExpiryPolicyBuilder +import org.ehcache.config.builders.ResourcePoolsBuilder +import org.ehcache.config.units.MemoryUnit +import org.ehcache.impl.config.persistence.DefaultPersistenceConfiguration +import org.ehcache.CacheManager import play.api.cache.ehcache.EhCacheApi +import play.api.cache.ehcache.EhCacheApi.EhExpirableCacheValue +import play.api.cache.ehcache.EhCacheApi.PlayEhCache import play.api.http +import play.api.inject +import play.api.inject.ApplicationLifecycle import play.api.mvc._ import play.api.test._ import play.api.Application +import play.api.Configuration +import play.api.Environment class CachedSpec extends PlaySpecification { sequential @@ -66,24 +83,31 @@ class CachedSpec extends PlaySpecification { } } - "cache values to disk using injected CachedApi" in new WithApplication() { + "cache values to disk using injected CachedApi" in new WithApplication( + // default implementation does not do disk caching, so inject a custom one + _.overrides(inject.bind[CacheManager].toProvider[PersistentCacheManagerProvider]) + ) { override def running() = { - import net.sf.ehcache._ - import net.sf.ehcache.config._ - import net.sf.ehcache.store.MemoryStoreEvictionPolicy + import org.ehcache._ // FIXME: Do this properly val cacheManager = app.injector.instanceOf[CacheManager] - val diskEhcache = new Cache( - new CacheConfiguration("disk", 30) - .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU) - .eternal(false) - .timeToLiveSeconds(60) - .timeToIdleSeconds(30) - .diskExpiryThreadIntervalSeconds(0) - .persistence(new PersistenceConfiguration().strategy(PersistenceConfiguration.Strategy.LOCALTEMPSWAP)) + + cacheManager.createCache( + "disk", + CacheConfigurationBuilder + .newCacheConfigurationBuilder( + classOf[String], + classOf[EhExpirableCacheValue], + ResourcePoolsBuilder + .newResourcePoolsBuilder() + .disk(1, MemoryUnit.MB) + ) + .withExpiry( + ExpiryPolicyBuilder.timeToIdleExpiration(JDurarion.ofSeconds(30)) + ) ) - cacheManager.addCache(diskEhcache) - val diskEhcache2 = cacheManager.getCache("disk") + + val diskEhcache2: PlayEhCache = cacheManager.getCache("disk", classOf[String], classOf[EhExpirableCacheValue]) assert(diskEhcache2 != null) val diskCache = new EhCacheApi(diskEhcache2)(app.materializer.executionContext) val diskCached = new Cached(diskCache) @@ -399,3 +423,23 @@ class NamedCachedController @Inject() ( val action = cached(_ => "foo")(Action(Results.Ok("" + invoked.incrementAndGet()))) def isCached(key: String): Boolean = cache.sync.get[String](key).isDefined } + +class PersistentCacheManagerProvider @Inject() ( + env: Environment, + config: Configuration, + lifecycle: ApplicationLifecycle +) extends Provider[CacheManager] { + + lazy val tempDir = Files.createTempDirectory("cache").toFile() + + lazy val get: CacheManager = { + val configuration = ConfigurationBuilder + .newConfigurationBuilder() + .withService(new DefaultPersistenceConfiguration(tempDir)) + .build() + val manager = CacheManagerBuilder.newCacheManager(configuration) + manager.init() + lifecycle.addStopHook(() => Future.successful(manager.close())) + manager + } +} diff --git a/cache/play-ehcache/src/test/scala/play/api/cache/ehcache/EhCacheApiSpec.scala b/cache/play-ehcache/src/test/scala/play/api/cache/ehcache/EhCacheApiSpec.scala index 6cf087b785f..2c2f274933e 100644 --- a/cache/play-ehcache/src/test/scala/play/api/cache/ehcache/EhCacheApiSpec.scala +++ b/cache/play-ehcache/src/test/scala/play/api/cache/ehcache/EhCacheApiSpec.scala @@ -13,7 +13,10 @@ import scala.concurrent.Await import scala.concurrent.ExecutionContext import scala.concurrent.Future -import net.sf.ehcache.CacheManager +import org.ehcache.config.builders.CacheConfigurationBuilder +import org.ehcache.config.builders.ResourcePoolsBuilder +import org.ehcache.CacheManager +import play.api.cache.ehcache.EhCacheApi.EhExpirableCacheValue import play.api.cache.AsyncCacheApi import play.api.cache.SyncCacheApi import play.api.inject._ @@ -33,12 +36,13 @@ class EhCacheApiSpec extends PlaySpecification { override def running() = { val controller = app.injector.instanceOf[NamedCacheController] val syncCacheName = - controller.cache.asInstanceOf[SyncEhCacheApi].cache.getName + controller.cache.asInstanceOf[SyncEhCacheApi].cache val asyncCacheName = - controller.asyncCache.asInstanceOf[EhCacheApi].cache.getName + controller.asyncCache.asInstanceOf[EhCacheApi].cache - syncCacheName must_== "custom" - asyncCacheName must_== "custom" + // no way to get cache name anymore, so doing most basic test + syncCacheName.getClass.getSimpleName must_== "Ehcache" + asyncCacheName.getClass.getSimpleName must_== "Ehcache" } } "bind already created named caches" in new WithApplication( @@ -102,8 +106,18 @@ class EhCacheApiSpec extends PlaySpecification { class CustomCacheManagerProvider @Inject() (cacheManagerProvider: CacheManagerProvider) extends Provider[CacheManager] { lazy val get = { val mgr = cacheManagerProvider.get - mgr.removeAllCaches() - mgr.addCache("custom") + mgr.close() + mgr.init() + mgr.removeCache("custom") // cache config is not cleared upon `close()` and causes auto-creation of cache on init() + mgr.createCache( + "custom", + CacheConfigurationBuilder.newCacheConfigurationBuilder( + classOf[String], + classOf[EhExpirableCacheValue], + ResourcePoolsBuilder.heap(100) + ) + ) + mgr.getCache("custom", classOf[String], classOf[EhExpirableCacheValue]) mgr } } diff --git a/core/play-guice/src/test/scala/play/api/http/HttpErrorHandlerSpec.scala b/core/play-guice/src/test/scala/play/api/http/HttpErrorHandlerSpec.scala index 7e8d5ff58a3..735df33901e 100644 --- a/core/play-guice/src/test/scala/play/api/http/HttpErrorHandlerSpec.scala +++ b/core/play-guice/src/test/scala/play/api/http/HttpErrorHandlerSpec.scala @@ -5,6 +5,7 @@ package play.api.http import java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletionStage import scala.concurrent.duration.Duration import scala.concurrent.Await @@ -254,15 +255,15 @@ object HttpErrorHandlerSpec { } class CustomScalaErrorHandler extends HttpErrorHandler { - def onClientError(request: RequestHeader, statusCode: Int, message: String) = + def onClientError(request: RequestHeader, statusCode: Int, message: String): Future[Result] = Future.successful(Results.Ok) - def onServerError(request: RequestHeader, exception: Throwable) = + def onServerError(request: RequestHeader, exception: Throwable): Future[Result] = Future.successful(Results.Ok) } class CustomJavaErrorHandler extends play.http.HttpErrorHandler { - def onClientError(req: play.mvc.Http.RequestHeader, status: Int, msg: String) = + def onClientError(req: play.mvc.Http.RequestHeader, status: Int, msg: String): CompletionStage[play.mvc.Result] = CompletableFuture.completedFuture(play.mvc.Results.ok()) - def onServerError(req: play.mvc.Http.RequestHeader, exception: Throwable) = + def onServerError(req: play.mvc.Http.RequestHeader, exception: Throwable): CompletionStage[play.mvc.Result] = CompletableFuture.completedFuture(play.mvc.Results.ok()) } diff --git a/core/play-integration-test/src/test/scala/play/it/ServerIntegrationSpecificationSpec.scala b/core/play-integration-test/src/test/scala/play/it/ServerIntegrationSpecificationSpec.scala index 9f611545781..f0561738af3 100644 --- a/core/play-integration-test/src/test/scala/play/it/ServerIntegrationSpecificationSpec.scala +++ b/core/play-integration-test/src/test/scala/play/it/ServerIntegrationSpecificationSpec.scala @@ -14,12 +14,12 @@ import play.api.test._ class NettyServerIntegrationSpecificationSpec extends ServerIntegrationSpecificationSpec with NettyIntegrationSpecification { - override def expectedServerTag = Some("netty") + override def expectedServerTag: Option[String] = Some("netty") } class PekkoHttpServerIntegrationSpecificationSpec extends ServerIntegrationSpecificationSpec with PekkoHttpIntegrationSpecification { - override def expectedServerTag = None + override def expectedServerTag: Option[String] = None } /** diff --git a/core/play-integration-test/src/test/scala/play/it/auth/SecuritySpec.scala b/core/play-integration-test/src/test/scala/play/it/auth/SecuritySpec.scala index cdc64f82341..72bb72143d0 100644 --- a/core/play-integration-test/src/test/scala/play/it/auth/SecuritySpec.scala +++ b/core/play-integration-test/src/test/scala/play/it/auth/SecuritySpec.scala @@ -60,14 +60,14 @@ class SecuritySpec extends PlaySpecification { def getUserInfoFromRequest(req: RequestHeader) = req.session.get("username") - def getUserFromRequest(req: RequestHeader) = req.session.get("user").map(User) + def getUserFromRequest(req: RequestHeader) = req.session.get("user").map(User.apply) class AuthenticatedDbRequest[A](val user: User, val conn: Connection, request: Request[A]) extends WrappedRequest[A](request) def Authenticated(implicit app: Application) = new ActionBuilder[AuthenticatedDbRequest, AnyContent] { - lazy val executionContext = app.materializer.executionContext - lazy val parser = app.injector.instanceOf[PlayBodyParsers].default + lazy val executionContext: ExecutionContext = app.materializer.executionContext + lazy val parser = app.injector.instanceOf[PlayBodyParsers].default def invokeBlock[A](request: Request[A], block: (AuthenticatedDbRequest[A]) => Future[Result]) = { val builder = AuthenticatedBuilder(req => getUserFromRequest(req), parser)(executionContext) builder.authenticate( @@ -100,7 +100,7 @@ class AuthMessagesRequest[A](val user: User, messagesApi: MessagesApi, request: extends MessagesRequest[A](request, messagesApi) class UserAuthenticatedBuilder(parser: BodyParser[AnyContent])(implicit ec: ExecutionContext) - extends AuthenticatedBuilder[User]({ (req: RequestHeader) => req.session.get("user").map(User) }, parser) { + extends AuthenticatedBuilder[User]({ (req: RequestHeader) => req.session.get("user").map(User.apply) }, parser) { @Inject() def this(parser: BodyParsers.Default)(implicit ec: ExecutionContext) = { this(parser: BodyParser[AnyContent]) diff --git a/core/play-integration-test/src/test/scala/play/it/http/BadClientHandlingSpec.scala b/core/play-integration-test/src/test/scala/play/it/http/BadClientHandlingSpec.scala index cbba8db8f9c..6a0cffb4d08 100644 --- a/core/play-integration-test/src/test/scala/play/it/http/BadClientHandlingSpec.scala +++ b/core/play-integration-test/src/test/scala/play/it/http/BadClientHandlingSpec.scala @@ -74,7 +74,7 @@ trait BadClientHandlingSpec extends PlaySpecification with ServerIntegrationSpec "allow accessing the raw unparsed path from an error handler" in withServer(new HttpErrorHandler() { def onClientError(request: RequestHeader, statusCode: Int, message: String) = Future.successful(Results.BadRequest("Bad path: " + request.path + " message: " + message)) - def onServerError(request: RequestHeader, exception: Throwable) = Future.successful(Results.Ok) + def onServerError(request: RequestHeader, exception: Throwable): Future[Result] = Future.successful(Results.Ok) }) { port => val response = BasicHttpClient.makeRequests(port)( BasicRequest("GET", "/[", "HTTP/1.1", Map(), "") diff --git a/core/play-integration-test/src/test/scala/play/it/http/HttpErrorHandlingSpec.scala b/core/play-integration-test/src/test/scala/play/it/http/HttpErrorHandlingSpec.scala index ae5e8fb0c42..363ff403990 100644 --- a/core/play-integration-test/src/test/scala/play/it/http/HttpErrorHandlingSpec.scala +++ b/core/play-integration-test/src/test/scala/play/it/http/HttpErrorHandlingSpec.scala @@ -73,7 +73,7 @@ class HttpErrorHandlingSpec webCommandHandler = None, filters = Seq( new EssentialFilter { - def apply(next: EssentialAction) = { + def apply(next: EssentialAction): EssentialAction = { throw new RuntimeException("filter exception!") } } @@ -129,7 +129,7 @@ class HttpErrorHandlingSpec webCommandHandler = None, filters = Seq( new EssentialFilter { - def apply(next: EssentialAction) = { + def apply(next: EssentialAction): EssentialAction = { throw new RuntimeException("filter exception!") } } diff --git a/core/play-integration-test/src/test/scala/play/it/http/JavaHttpErrorHandlingSpec.scala b/core/play-integration-test/src/test/scala/play/it/http/JavaHttpErrorHandlingSpec.scala index 37431219c14..7eb907dfaae 100644 --- a/core/play-integration-test/src/test/scala/play/it/http/JavaHttpErrorHandlingSpec.scala +++ b/core/play-integration-test/src/test/scala/play/it/http/JavaHttpErrorHandlingSpec.scala @@ -112,7 +112,7 @@ class JavaHttpErrorHandlingSpec webCommandHandler = None, filters = Seq( new EssentialFilter { - def apply(next: EssentialAction) = { + def apply(next: EssentialAction): EssentialAction = { throw new RuntimeException("filter exception!") } } @@ -176,7 +176,7 @@ class JavaHttpErrorHandlingSpec webCommandHandler = None, filters = Seq( new EssentialFilter { - def apply(next: EssentialAction) = { + def apply(next: EssentialAction): EssentialAction = { throw new RuntimeException("filter exception!") } } diff --git a/core/play-integration-test/src/test/scala/play/it/http/JavaResultsHandlingSpec.scala b/core/play-integration-test/src/test/scala/play/it/http/JavaResultsHandlingSpec.scala index 9083b4843c0..1b8a015c07a 100644 --- a/core/play-integration-test/src/test/scala/play/it/http/JavaResultsHandlingSpec.scala +++ b/core/play-integration-test/src/test/scala/play/it/http/JavaResultsHandlingSpec.scala @@ -85,7 +85,7 @@ trait JavaResultsHandlingSpec }) { response => response.header(DATE) must beSome } "work with non-standard HTTP response codes" in makeRequest(new MockController { - def action(request: Http.Request) = { + def action(request: Http.Request): Result = { Results.status(498) } }) { response => response.status must beEqualTo(498) } diff --git a/core/play-integration-test/src/test/scala/play/it/http/PekkoHttpCustomServerProviderSpec.scala b/core/play-integration-test/src/test/scala/play/it/http/PekkoHttpCustomServerProviderSpec.scala index 60897d97c1c..2d82a296270 100644 --- a/core/play-integration-test/src/test/scala/play/it/http/PekkoHttpCustomServerProviderSpec.scala +++ b/core/play-integration-test/src/test/scala/play/it/http/PekkoHttpCustomServerProviderSpec.scala @@ -18,6 +18,7 @@ import play.api.test.ApplicationFactory import play.api.test.PlaySpecification import play.api.test.ServerEndpointRecipe import play.core.server.PekkoHttpServer +import play.core.server.Server import play.core.server.ServerProvider import play.it.test._ @@ -73,7 +74,7 @@ class PekkoHttpCustomServerProviderSpec val customPekkoHttpEndpoint: ServerEndpointRecipe = PekkoHttp11Plaintext .withDescription("Pekko HTTP HTTP/1.1 (plaintext, supports FOO)") .withServerProvider(new ServerProvider { - def createServer(context: ServerProvider.Context) = + def createServer(context: ServerProvider.Context): Server = new PekkoHttpServer(PekkoHttpServer.Context.fromServerProviderContext(context)) { protected override def createParserSettings(): ParserSettings = { super.createParserSettings().withCustomMethods(HttpMethod.custom("FOO")) @@ -95,7 +96,7 @@ class PekkoHttpCustomServerProviderSpec val customPekkoHttpEndpoint: ServerEndpointRecipe = PekkoHttp11Plaintext .withDescription("Pekko HTTP HTTP/1.1 (plaintext, long headers)") .withServerProvider(new ServerProvider { - def createServer(context: ServerProvider.Context) = + def createServer(context: ServerProvider.Context): Server = new PekkoHttpServer(PekkoHttpServer.Context.fromServerProviderContext(context)) { protected override def createParserSettings(): ParserSettings = { super.createParserSettings().withMaxHeaderNameLength(100) diff --git a/core/play-integration-test/src/test/scala/play/it/http/RequestBodyHandlingSpec.scala b/core/play-integration-test/src/test/scala/play/it/http/RequestBodyHandlingSpec.scala index 8399a838447..20561c9eb74 100644 --- a/core/play-integration-test/src/test/scala/play/it/http/RequestBodyHandlingSpec.scala +++ b/core/play-integration-test/src/test/scala/play/it/http/RequestBodyHandlingSpec.scala @@ -164,6 +164,6 @@ class CustomErrorHandler extends HttpErrorHandler { .getOrElse("") + " / " + message ) ) - def onServerError(request: RequestHeader, exception: Throwable) = + def onServerError(request: RequestHeader, exception: Throwable): Future[Result] = Future.successful(Results.BadRequest) } diff --git a/core/play-integration-test/src/test/scala/play/it/http/websocket/WebSocketClient.scala b/core/play-integration-test/src/test/scala/play/it/http/websocket/WebSocketClient.scala index 9b49c18b787..fd4c3f7b558 100644 --- a/core/play-integration-test/src/test/scala/play/it/http/websocket/WebSocketClient.scala +++ b/core/play-integration-test/src/test/scala/play/it/http/websocket/WebSocketClient.scala @@ -118,7 +118,7 @@ object WebSocketClient { */ def connect(url: URI, version: WebSocketVersion, subprotocol: Option[String])( onConnected: (immutable.Seq[(String, String)], Flow[ExtendedMessage, ExtendedMessage, ?]) => Unit - ) = { + ): Future[?] = { val normalized = url.normalize() val tgt = if (normalized.getPath == null || normalized.getPath.trim().isEmpty) { new URI(normalized.getScheme, normalized.getAuthority, "/", normalized.getQuery, normalized.getFragment) diff --git a/core/play-integration-test/src/test/scala/play/it/http/websocket/WebSocketSpec.scala b/core/play-integration-test/src/test/scala/play/it/http/websocket/WebSocketSpec.scala index e64106d30fb..38957d28d28 100644 --- a/core/play-integration-test/src/test/scala/play/it/http/websocket/WebSocketSpec.scala +++ b/core/play-integration-test/src/test/scala/play/it/http/websocket/WebSocketSpec.scala @@ -20,6 +20,7 @@ import org.apache.pekko.actor.Props import org.apache.pekko.actor.Status import org.apache.pekko.stream.scaladsl._ import org.apache.pekko.util.ByteString +import org.apache.pekko.util.Timeout import org.specs2.execute.AsResult import org.specs2.execute.EventuallyResults import org.specs2.matcher.Matcher @@ -343,7 +344,7 @@ trait WebSocketSpec Props(new Actor() { messages.foreach { msg => out ! msg } out ! Status.Success(()) - def receive = PartialFunction.empty + def receive: Actor.Receive = PartialFunction.empty }) } } @@ -356,7 +357,7 @@ trait WebSocketSpec ActorFlow.actorRef { out => Props(new Actor() { system.scheduler.scheduleOnce(10.millis, out, Status.Success(())) - def receive = PartialFunction.empty + def receive: Actor.Receive = PartialFunction.empty }) } } @@ -382,7 +383,7 @@ trait WebSocketSpec WebSocket.accept[String, String] { req => ActorFlow.actorRef { out => Props(new Actor() { - def receive = PartialFunction.empty + def receive: Actor.Receive = PartialFunction.empty override def postStop() = { cleanedUp.success(true) } @@ -441,7 +442,7 @@ trait WebSocketSpecMethods extends PlaySpecification with WsTestClient with Serv import scala.jdk.CollectionConverters._ // Extend the default spec timeout for CI. - implicit override def defaultAwaitTimeout = 10.seconds + implicit override def defaultAwaitTimeout: Timeout = 10.seconds protected override def shouldRunSequentially(app: Application): Boolean = false diff --git a/core/play-integration-test/src/test/scala/play/it/mvc/FiltersSpec.scala b/core/play-integration-test/src/test/scala/play/it/mvc/FiltersSpec.scala index 253624dd2a9..fae7b74cac7 100644 --- a/core/play-integration-test/src/test/scala/play/it/mvc/FiltersSpec.scala +++ b/core/play-integration-test/src/test/scala/play/it/mvc/FiltersSpec.scala @@ -276,7 +276,8 @@ trait FiltersSpec extends Specification with ServerIntegrationSpecification { def onClientError(request: RequestHeader, statusCode: Int, message: String) = { Future.successful(Results.NotFound(request.headers.get(filterAddedHeaderKey).getOrElse("undefined header"))) } - def onServerError(request: RequestHeader, exception: Throwable) = Future.successful(Results.InternalServerError) + def onServerError(request: RequestHeader, exception: Throwable): Future[Result] = + Future.successful(Results.InternalServerError) } "requests not matching a route should receive a RequestHeader modified by upstream filters" in withServer( diff --git a/core/play-integration-test/src/test/scala/play/it/tools/HttpBin.scala b/core/play-integration-test/src/test/scala/play/it/tools/HttpBin.scala index de67cd0569c..281ac52e451 100644 --- a/core/play-integration-test/src/test/scala/play/it/tools/HttpBin.scala +++ b/core/play-integration-test/src/test/scala/play/it/tools/HttpBin.scala @@ -325,7 +325,7 @@ object HttpBinApplication { new BuiltInComponentsFromContext(ApplicationLoader.Context.create(Environment.simple())) with AhcWSComponents with NoHttpFiltersComponents { - implicit override lazy val Action = defaultActionBuilder + implicit override lazy val Action: DefaultActionBuilder = defaultActionBuilder override def router = SimpleRouter( PartialFunction.empty .orElse(getIp) diff --git a/core/play/src/test/scala/play/mvc/StatusHeaderSpec.scala b/core/play/src/test/scala/play/mvc/StatusHeaderSpec.scala index dc1f2a7eb6a..2aaa6e643a8 100644 --- a/core/play/src/test/scala/play/mvc/StatusHeaderSpec.scala +++ b/core/play/src/test/scala/play/mvc/StatusHeaderSpec.scala @@ -36,7 +36,7 @@ class StatusHeaderSpec extends TestKit(ActorSystem("StatusHeaderSpec")) with Spe val materializer = Materializer.matFromSystem Json.mapper.getFactory.setCharacterEscapes(new CharacterEscapes { - override def getEscapeSequence(ch: Int) = new SerializedString(f"\\u$ch%04x") + override def getEscapeSequence(ch: Int): SerializedString = new SerializedString(f"\\u$ch%04x") override def getEscapeCodesForAscii: Array[Int] = CharacterEscapes.standardAsciiEscapesForJSON.zipWithIndex.map { diff --git a/documentation/build.sbt b/documentation/build.sbt index d7de4bc738a..ce9bc76b02c 100644 --- a/documentation/build.sbt +++ b/documentation/build.sbt @@ -77,8 +77,8 @@ lazy val main = Project("Play-Documentation", file(".")) Test / unmanagedResourceDirectories ++= (baseDirectory.value / "manual" / "detailedTopics" ** "code").get, // Don't include sbt files in the resources Test / unmanagedResources / excludeFilter := (Test / unmanagedResources / excludeFilter).value || "*.sbt", - crossScalaVersions := Seq("2.13.13", "3.3.3"), - scalaVersion := "2.13.13", + crossScalaVersions := Seq("2.13.14", "3.3.3"), + scalaVersion := "2.13.14", Test / fork := true, Test / javaOptions ++= Seq("-Xmx512m", "-Xms128m"), headerLicense := Some( diff --git a/documentation/manual/hacking/BuildingFromSource.md b/documentation/manual/hacking/BuildingFromSource.md index cd17d02b9fc..260714c7f46 100644 --- a/documentation/manual/hacking/BuildingFromSource.md +++ b/documentation/manual/hacking/BuildingFromSource.md @@ -39,7 +39,7 @@ This will build and publish Play for the default Scala version. If you want to p Or to publish for a specific Scala version: ```bash -> ++ 2.13.13 publishLocal +> ++ 2.13.14 publishLocal ``` ## Build the documentation diff --git a/documentation/manual/releases/release27/Highlights27.md b/documentation/manual/releases/release27/Highlights27.md index 064ba8d2539..f209ed28c88 100644 --- a/documentation/manual/releases/release27/Highlights27.md +++ b/documentation/manual/releases/release27/Highlights27.md @@ -25,7 +25,7 @@ scalaVersion := "2.11.12" For Scala 2.13: ```scala -scalaVersion := "2.13.13" +scalaVersion := "2.13.14" ``` ## Lifecycle managed by Akka's Coordinated Shutdown diff --git a/documentation/manual/releases/release28/migration28/Migration28.md b/documentation/manual/releases/release28/migration28/Migration28.md index e56ea09808c..752ab04db29 100644 --- a/documentation/manual/releases/release28/migration28/Migration28.md +++ b/documentation/manual/releases/release28/migration28/Migration28.md @@ -47,14 +47,14 @@ Play 2.8 support Scala 2.12 and 2.13, but not 2.11, which has reached its end of To set the Scala version in sbt, simply set the `scalaVersion` key, for example: ```scala -scalaVersion := "2.13.13" +scalaVersion := "2.13.14" ``` If you have a single project build, then this setting can just be placed on its own line in `build.sbt`. However, if you have a multi-project build, then the scala version setting must be set on each project. Typically, in a multi-project build, you will have some common settings shared by every project, this is the best place to put the setting, for example: ```scala def commonSettings = Seq( - scalaVersion := "2.13.13" + scalaVersion := "2.13.14" ) val projectA = (project in file("projectA")) diff --git a/documentation/manual/releases/release29/migration29/Migration29.md b/documentation/manual/releases/release29/migration29/Migration29.md index 165fcc4bfbc..4c016a3d42f 100644 --- a/documentation/manual/releases/release29/migration29/Migration29.md +++ b/documentation/manual/releases/release29/migration29/Migration29.md @@ -62,7 +62,7 @@ Play 2.9 supports Scala 2.13 and Scala 3.3, but not 2.12 anymore. Scala 3 requir To set the Scala version in sbt, simply set the `scalaVersion` key, for example: ```scala -scalaVersion := "2.13.13" +scalaVersion := "2.13.14" ``` Play 2.9 also supports Scala 3: @@ -77,7 +77,7 @@ If you have a single project build, then this setting can just be placed on its ```scala def commonSettings = Seq( - scalaVersion := "2.13.13" + scalaVersion := "2.13.14" ) val projectA = (project in file("projectA")) diff --git a/documentation/manual/working/commonGuide/build/sbtDependencies.md b/documentation/manual/working/commonGuide/build/sbtDependencies.md index dc1e52adeff..5060c15042a 100644 --- a/documentation/manual/working/commonGuide/build/sbtDependencies.md +++ b/documentation/manual/working/commonGuide/build/sbtDependencies.md @@ -38,7 +38,7 @@ If you use `groupID %% artifactID % revision` rather than `groupID % artifactID @[explicit-scala-version-dep](code/dependencies.sbt) -Assuming the `scalaVersion` for your build is `2.13.13`, the following is identical (note the double `%%` after `"org.scala-tools"`): +Assuming the `scalaVersion` for your build is `2.13.14`, the following is identical (note the double `%%` after `"org.scala-tools"`): @[auto-scala-version-dep](code/dependencies.sbt) diff --git a/documentation/manual/working/commonGuide/configuration/WsCache.md b/documentation/manual/working/commonGuide/configuration/WsCache.md index 8d81931b363..61416bc3084 100644 --- a/documentation/manual/working/commonGuide/configuration/WsCache.md +++ b/documentation/manual/working/commonGuide/configuration/WsCache.md @@ -45,17 +45,22 @@ play.ws.cache.cacheManagerResource="ehcache-play-ws-cache.xml" and then adding a cache such as following into the `conf` directory: ```xml - - - - - + + + + java.lang.String + play.api.cache.ExpirableCacheValue + + 360 + + + 10000 + + + ``` > **NOTE**: `play.ws.cache.cacheManagerURI` is deprecated, use `play.ws.cache.cacheManagerResource` with a path on the classpath instead. diff --git a/documentation/manual/working/javaGuide/main/cache/JavaCache.md b/documentation/manual/working/javaGuide/main/cache/JavaCache.md index c55e5af14e5..5b5a4cf3e53 100644 --- a/documentation/manual/working/javaGuide/main/cache/JavaCache.md +++ b/documentation/manual/working/javaGuide/main/cache/JavaCache.md @@ -8,7 +8,7 @@ Caching data is a typical optimization in modern applications, and so Play provi 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. -Play provides a CacheApi implementation based on [Caffeine](https://github.com/ben-manes/caffeine/) and a legacy implementation based on [Ehcache 2.x](http://www.ehcache.org). For in-process caching Caffeine is typically the best choice. If you need distributed caching, there are third-party plugins for [memcached](https://github.com/mumoshu/play2-memcached) and [redis](https://github.com/KarelCemus/play-redis). +Play provides a CacheApi implementation based on [Caffeine](https://github.com/ben-manes/caffeine/) and a legacy implementation based on [Ehcache 3.x](http://www.ehcache.org). For in-process caching Caffeine is typically the best choice. If you need distributed caching, there are third-party plugins for [memcached](https://github.com/mumoshu/play2-memcached) and [redis](https://github.com/KarelCemus/play-redis). ## Importing the Cache API diff --git a/documentation/manual/working/scalaGuide/main/cache/ScalaCache.md b/documentation/manual/working/scalaGuide/main/cache/ScalaCache.md index 721f9c653f3..26ba33f1b76 100644 --- a/documentation/manual/working/scalaGuide/main/cache/ScalaCache.md +++ b/documentation/manual/working/scalaGuide/main/cache/ScalaCache.md @@ -8,7 +8,7 @@ Caching data is a typical optimization in modern applications, and so Play provi 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. -Play provides a CacheApi implementation based on [Caffeine](https://github.com/ben-manes/caffeine/) and a legacy implementation based on [Ehcache 2.x](http://www.ehcache.org). For in-process caching Caffeine is typically the best choice. If you need distributed caching, there are third-party plugins for [memcached](https://github.com/mumoshu/play2-memcached) and [redis](https://github.com/KarelCemus/play-redis). +Play provides a CacheApi implementation based on [Caffeine](https://github.com/ben-manes/caffeine/) and a legacy implementation based on [Ehcache 3.x](http://www.ehcache.org). For in-process caching Caffeine is typically the best choice. If you need distributed caching, there are third-party plugins for [memcached](https://github.com/mumoshu/play2-memcached) and [redis](https://github.com/KarelCemus/play-redis). ## Importing the Cache API @@ -121,7 +121,7 @@ By default, Play will try to create caches with names from `play.cache.bindCache ## Setting the execution context By default, Caffeine and EhCache store elements in memory. Therefore reads from and writes to the cache should be very fast, because there is hardly any blocking I/O. -However, depending on how a cache was configured (e.g. by using [EhCache's `DiskStore`](http://www.ehcache.org/generated/2.10.4/html/ehc-all/#page/Ehcache_Documentation_Set%2Fco-store_storage_tiers.html)), there might be blocking I/O which can become too costly, because even the async implementations will block threads in the default execution context. +However, depending on how a cache was configured (e.g. by using [EhCache's `DiskStore`](https://www.ehcache.org/documentation/3.10/tiering.html#disk-tier)), there might be blocking I/O which can become too costly, because even the async implementations will block threads in the default execution context. For such a case you can configure a different [Pekko dispatcher](https://pekko.apache.org/docs/pekko/1.0/dispatchers.html?language=scala#looking-up-a-dispatcher) and set it via `play.cache.dispatcher` so the cache plugin makes use of it: diff --git a/persistence/play-jdbc-evolutions/src/test/scala/play/api/db/evolutions/EvolutionsSpec.scala b/persistence/play-jdbc-evolutions/src/test/scala/play/api/db/evolutions/EvolutionsSpec.scala index 5d40bae919c..8a7feaff671 100644 --- a/persistence/play-jdbc-evolutions/src/test/scala/play/api/db/evolutions/EvolutionsSpec.scala +++ b/persistence/play-jdbc-evolutions/src/test/scala/play/api/db/evolutions/EvolutionsSpec.scala @@ -194,7 +194,7 @@ class EvolutionsSpec extends Specification { connection.createStatement.execute(sql) }.get - def after = { + def after: Any = { database.shutdown() } } diff --git a/project/BuildSettings.scala b/project/BuildSettings.scala index a1acbdf743a..51f2cd1c7cc 100644 --- a/project/BuildSettings.scala +++ b/project/BuildSettings.scala @@ -318,6 +318,17 @@ object BuildSettings { ProblemFilters.exclude[MissingTypesProblem]("views.html.helper.style$"), ProblemFilters.exclude[MissingTypesProblem]("views.html.helper.textarea$"), ProblemFilters.exclude[MissingTypesProblem]("views.html.play20.manual$"), + // EhCache3 upgrade + ProblemFilters.exclude[IncompatibleResultTypeProblem]("play.api.cache.ehcache.CacheManagerProvider.get"), + ProblemFilters.exclude[IncompatibleMethTypeProblem]("play.api.cache.ehcache.EhCacheApi."), + ProblemFilters.exclude[DirectMissingMethodProblem]("play.api.cache.ehcache.EhCacheComponents.ehCacheManager"), + ProblemFilters.exclude[IncompatibleMethTypeProblem]("play.api.cache.ehcache.EhCacheApi.this"), + ProblemFilters.exclude[IncompatibleMethTypeProblem]("play.api.cache.ehcache.SyncEhCacheApi.this"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("play.cache.ehcache.EhCacheComponents.ehCacheManager"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("play.api.cache.ehcache.EhCacheApi.cache"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("play.api.cache.ehcache.SyncEhCacheApi.cache"), + ProblemFilters.exclude[MissingClassProblem]("play.api.cache.caffeine.ExpirableCacheValue"), + ProblemFilters.exclude[MissingClassProblem]("play.api.cache.caffeine.ExpirableCacheValue$") ), (Compile / unmanagedSourceDirectories) += { val suffix = CrossVersion.partialVersion(scalaVersion.value) match { diff --git a/project/Dependencies.scala b/project/Dependencies.scala index f9f0a9c3cb2..f781edc1374 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -279,10 +279,9 @@ object Dependencies { "javax.cache" % "cache-api" % "1.1.1" ) - val ehcacheVersion = "2.10.9.2" + val ehcacheVersion = "3.10.8" val playEhcacheDeps = Seq( - "net.sf.ehcache" % "ehcache" % ehcacheVersion, - "org.ehcache" % "jcache" % "1.0.1" + ("org.ehcache" % "ehcache" % ehcacheVersion).classifier("jakarta") ) ++ jcacheApi val caffeineVersion = "3.1.8" @@ -306,8 +305,7 @@ object Dependencies { "org.playframework" % "shaded-asynchttpclient" % playWsStandaloneVersion, "org.playframework" % "shaded-oauth" % playWsStandaloneVersion, "com.github.ben-manes.caffeine" % "jcache" % caffeineVersion % Test, - "net.sf.ehcache" % "ehcache" % ehcacheVersion % Test, - "org.ehcache" % "jcache" % "1.0.1" % Test + ("org.ehcache" % "ehcache" % ehcacheVersion % Test).classifier("jakarta") ) ++ jcacheApi val playDocsSbtPluginDependencies = Seq( diff --git a/project/PlayBuildBase.scala b/project/PlayBuildBase.scala index a8b6eb63a77..bd30d13b1dc 100644 --- a/project/PlayBuildBase.scala +++ b/project/PlayBuildBase.scala @@ -59,14 +59,6 @@ object PlayBuildBase extends AutoPlugin { case Some((2, 13)) => Seq("-Xsource:3") case _ => Seq.empty }), - Test / scalacOptions ++= { - CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, 13)) => - Seq("-Xmigration:2.13") - case _ => - Seq.empty - } - }, javacOptions ++= Seq("-encoding", "UTF-8", "-Xlint:-options"), resolvers ++= { if (isSnapshot.value) { diff --git a/project/Versions.scala b/project/Versions.scala index 805025968f3..efccf665e4f 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -4,7 +4,7 @@ object ScalaVersions { val scala212 = "2.12.19" - val scala213 = "2.13.13" + val scala213 = "2.13.14" val scala3 = "3.3.3" } diff --git a/transport/client/play-ahc-ws/src/test/resources/ehcache-play-ws-cache.xml b/transport/client/play-ahc-ws/src/test/resources/ehcache-play-ws-cache.xml index ac000e92c94..5a13d383eca 100644 --- a/transport/client/play-ahc-ws/src/test/resources/ehcache-play-ws-cache.xml +++ b/transport/client/play-ahc-ws/src/test/resources/ehcache-play-ws-cache.xml @@ -2,14 +2,19 @@ Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. --> - + - - - \ No newline at end of file + + java.lang.String + play.api.cache.ExpirableCacheValue + + 360 + + + 10000 + + + \ No newline at end of file diff --git a/transport/client/play-ahc-ws/src/test/scala/play/api/libs/ws/ahc/OptionalAhcHttpCacheProviderSpec.scala b/transport/client/play-ahc-ws/src/test/scala/play/api/libs/ws/ahc/OptionalAhcHttpCacheProviderSpec.scala index 47454668c82..031021957e8 100644 --- a/transport/client/play-ahc-ws/src/test/scala/play/api/libs/ws/ahc/OptionalAhcHttpCacheProviderSpec.scala +++ b/transport/client/play-ahc-ws/src/test/scala/play/api/libs/ws/ahc/OptionalAhcHttpCacheProviderSpec.scala @@ -5,7 +5,7 @@ package play.api.libs.ws.ahc import com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider -import org.ehcache.jcache.JCacheCachingProvider +import org.ehcache.jsr107.EhcacheCachingProvider import org.specs2.concurrent.ExecutionEnv import play.api.inject.guice.GuiceApplicationBuilder import play.api.inject.DefaultApplicationLifecycle @@ -31,8 +31,9 @@ class OptionalAhcHttpCacheProviderSpec(implicit ee: ExecutionEnv) extends PlaySp "work with a cache defined using ehcache through jcache" in new WithApplication( GuiceApplicationBuilder(loadConfiguration = { (env: Environment) => val settings = Map( + "play.http.secret.key" -> "ad31779d4ee49d5ad5162bf1429c32e2e9933f3b", "play.ws.cache.enabled" -> "true", - "play.ws.cache.cachingProviderName" -> classOf[JCacheCachingProvider].getName, + "play.ws.cache.cachingProviderName" -> classOf[EhcacheCachingProvider].getName, "play.ws.cache.cacheManagerResource" -> "ehcache-play-ws-cache.xml" ) Configuration.load(env, settings) @@ -47,6 +48,7 @@ class OptionalAhcHttpCacheProviderSpec(implicit ee: ExecutionEnv) extends PlaySp "work with a cache defined using caffeine through jcache" in new WithApplication( GuiceApplicationBuilder(loadConfiguration = { (env: Environment) => val settings = Map( + "play.http.secret.key" -> "ad31779d4ee49d5ad5162bf1429c32e2e9933f3b", "play.ws.cache.enabled" -> "true", "play.ws.cache.cachingProviderName" -> classOf[CaffeineCachingProvider].getName, "play.ws.cache.cacheManagerResource" -> "caffeine.conf" @@ -54,7 +56,7 @@ class OptionalAhcHttpCacheProviderSpec(implicit ee: ExecutionEnv) extends PlaySp Configuration.load(env, settings) }).build() ) { - override def running() = { + override def running(): Unit = { val provider = app.injector.instanceOf[OptionalAhcHttpCacheProvider] provider.get must beSome[AhcHttpCache].which { cache => cache.isShared must beFalse } } diff --git a/transport/server/play-server/src/test/scala/play/core/server/ProdServerStartSpec.scala b/transport/server/play-server/src/test/scala/play/core/server/ProdServerStartSpec.scala index cc685820db6..c97252fa1e5 100644 --- a/transport/server/play-server/src/test/scala/play/core/server/ProdServerStartSpec.scala +++ b/transport/server/play-server/src/test/scala/play/core/server/ProdServerStartSpec.scala @@ -78,11 +78,11 @@ class FakeServer(context: ServerProvider.Context) extends Server with Reloadable } class FakeServerProvider extends ServerProvider { - override def createServer(context: ServerProvider.Context) = new FakeServer(context) + override def createServer(context: ServerProvider.Context): Server = new FakeServer(context) } class StartupErrorServerProvider extends ServerProvider { - override def createServer(context: ServerProvider.Context) = throw new Exception("server fails to start") + override def createServer(context: ServerProvider.Context): Server = throw new Exception("server fails to start") } class ProdServerStartSpec extends Specification { diff --git a/web/play-filters-helpers/src/test/scala/play/filters/cors/CORSFilterSpec.scala b/web/play-filters-helpers/src/test/scala/play/filters/cors/CORSFilterSpec.scala index 86de86680a0..bf1c3591342 100644 --- a/web/play-filters-helpers/src/test/scala/play/filters/cors/CORSFilterSpec.scala +++ b/web/play-filters-helpers/src/test/scala/play/filters/cors/CORSFilterSpec.scala @@ -9,6 +9,7 @@ import javax.inject.Inject import play.api.http.HttpFilters import play.api.inject.bind import play.api.mvc.DefaultActionBuilder +import play.api.mvc.EssentialFilter import play.api.mvc.Results import play.api.routing.sird._ import play.api.routing.Router @@ -19,7 +20,7 @@ import play.mvc.Http.HeaderNames._ object CORSFilterSpec { class Filters @Inject() (corsFilter: CORSFilter) extends HttpFilters { - def filters = Seq(corsFilter) + def filters: Seq[EssentialFilter] = Seq(corsFilter) } class CorsApplicationRouter @Inject() (action: DefaultActionBuilder) diff --git a/web/play-filters-helpers/src/test/scala/play/filters/cors/CORSWithCSRFSpec.scala b/web/play-filters-helpers/src/test/scala/play/filters/cors/CORSWithCSRFSpec.scala index 790c91086ee..d53bbd2d7c9 100644 --- a/web/play-filters-helpers/src/test/scala/play/filters/cors/CORSWithCSRFSpec.scala +++ b/web/play-filters-helpers/src/test/scala/play/filters/cors/CORSWithCSRFSpec.scala @@ -17,6 +17,7 @@ import play.api.inject.bind import play.api.libs.crypto.DefaultCSRFTokenSigner import play.api.libs.crypto.DefaultCookieSigner import play.api.mvc.DefaultActionBuilder +import play.api.mvc.EssentialFilter import play.api.mvc.Results import play.api.routing.sird._ import play.api.routing.Router @@ -30,7 +31,7 @@ object CORSWithCSRFSpec { } class FiltersWithoutCors @Inject() (csrfFilter: CSRFFilter) extends HttpFilters { - def filters = Seq(csrfFilter) + def filters: Seq[EssentialFilter] = Seq(csrfFilter) } class CORSWithCSRFRouter @Inject() (action: DefaultActionBuilder) extends Router { @@ -50,8 +51,8 @@ object CORSWithCSRFSpec { val csrfCheck = CSRFCheck(play.filters.csrf.CSRFConfig(), signer, sessionConfiguration) csrfCheck(action(Results.Ok), CSRF.DefaultErrorHandler) } - override def withPrefix(prefix: String) = this - override def documentation = Seq.empty + override def withPrefix(prefix: String): Router = this + override def documentation: Seq[(String, String, String)] = Seq.empty } } diff --git a/web/play-filters-helpers/src/test/scala/play/filters/csp/CSPFilterSpec.scala b/web/play-filters-helpers/src/test/scala/play/filters/csp/CSPFilterSpec.scala index 23dd20c7785..375ee3d2f8b 100644 --- a/web/play-filters-helpers/src/test/scala/play/filters/csp/CSPFilterSpec.scala +++ b/web/play-filters-helpers/src/test/scala/play/filters/csp/CSPFilterSpec.scala @@ -25,7 +25,7 @@ import play.api.Configuration import play.api.Environment class Filters @Inject() (cspFilter: CSPFilter) extends HttpFilters { - def filters = Seq(cspFilter) + def filters: Seq[EssentialFilter] = Seq(cspFilter) } class CSPFilterSpec extends PlaySpecification { diff --git a/web/play-filters-helpers/src/test/scala/play/filters/csrf/CSRFFilterSpec.scala b/web/play-filters-helpers/src/test/scala/play/filters/csrf/CSRFFilterSpec.scala index 7012683a443..6efe058234d 100644 --- a/web/play-filters-helpers/src/test/scala/play/filters/csrf/CSRFFilterSpec.scala +++ b/web/play-filters-helpers/src/test/scala/play/filters/csrf/CSRFFilterSpec.scala @@ -5,6 +5,7 @@ package play.filters.csrf import java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletionStage import javax.inject.Inject import scala.concurrent.Future @@ -543,7 +544,7 @@ class CustomErrorHandler extends CSRF.ErrorHandler { } class JavaErrorHandler extends CSRFErrorHandler { - def handle(req: Http.RequestHeader, msg: String) = + def handle(req: Http.RequestHeader, msg: String): CompletionStage[play.mvc.Result] = CompletableFuture.completedFuture( play.mvc.Results.unauthorized( "Origin: " + req.attrs @@ -556,5 +557,5 @@ class JavaErrorHandler extends CSRFErrorHandler { } class CsrfFilters @Inject() (filter: CSRFFilter) extends HttpFilters { - def filters = Seq(filter) + def filters: Seq[EssentialFilter] = Seq(filter) } diff --git a/web/play-filters-helpers/src/test/scala/play/filters/csrf/JavaCSRFActionSpec.scala b/web/play-filters-helpers/src/test/scala/play/filters/csrf/JavaCSRFActionSpec.scala index 379b2a878ed..8cf3d8b5c31 100644 --- a/web/play-filters-helpers/src/test/scala/play/filters/csrf/JavaCSRFActionSpec.scala +++ b/web/play-filters-helpers/src/test/scala/play/filters/csrf/JavaCSRFActionSpec.scala @@ -5,6 +5,7 @@ package play.filters.csrf import java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletionStage import scala.concurrent.Future import scala.jdk.OptionConverters._ @@ -32,9 +33,9 @@ class JavaCSRFActionSpec extends CSRFCommonSpecs { def javaAction[T: ClassTag](method: String, inv: JRequest => Result)(implicit app: Application) = new JavaAction(javaHandlerComponents) { - val clazz = implicitly[ClassTag[T]].runtimeClass - def parser = HandlerInvokerFactory.javaBodyParserToScala(javaHandlerComponents.getBodyParser(annotations.parser)) - def invocation(req: JRequest) = CompletableFuture.completedFuture(inv(req)) + val clazz = implicitly[ClassTag[T]].runtimeClass + def parser = HandlerInvokerFactory.javaBodyParserToScala(javaHandlerComponents.getBodyParser(annotations.parser)) + def invocation(req: JRequest): CompletionStage[Result] = CompletableFuture.completedFuture(inv(req)) val annotations = new JavaActionAnnotations( clazz, @@ -139,7 +140,7 @@ object JavaCSRFActionSpec { } class CustomErrorHandler extends CSRFErrorHandler { - def handle(req: RequestHeader, msg: String) = { + def handle(req: RequestHeader, msg: String): CompletionStage[Result] = { CompletableFuture.completedFuture( Results.unauthorized( "Origin: " + req diff --git a/web/play-filters-helpers/src/test/scala/play/filters/gzip/GzipFilterSpec.scala b/web/play-filters-helpers/src/test/scala/play/filters/gzip/GzipFilterSpec.scala index d291dae7d03..1edad58ed8d 100644 --- a/web/play-filters-helpers/src/test/scala/play/filters/gzip/GzipFilterSpec.scala +++ b/web/play-filters-helpers/src/test/scala/play/filters/gzip/GzipFilterSpec.scala @@ -28,6 +28,7 @@ import play.api.inject.guice.GuiceApplicationBuilder import play.api.mvc.AnyContentAsEmpty import play.api.mvc.Cookie import play.api.mvc.DefaultActionBuilder +import play.api.mvc.EssentialFilter import play.api.mvc.Result import play.api.mvc.Results._ import play.api.routing.Router @@ -40,7 +41,7 @@ object GzipFilterSpec { extends SimpleRouterImpl({ case _ => action(result) }) class Filters @Inject() (gzipFilter: GzipFilter) extends HttpFilters { - def filters = Seq(gzipFilter) + def filters: Seq[EssentialFilter] = Seq(gzipFilter) } } diff --git a/web/play-filters-helpers/src/test/scala/play/filters/headers/SecurityHeadersFilterSpec.scala b/web/play-filters-helpers/src/test/scala/play/filters/headers/SecurityHeadersFilterSpec.scala index e924e1d1dfb..b1306992eb6 100644 --- a/web/play-filters-helpers/src/test/scala/play/filters/headers/SecurityHeadersFilterSpec.scala +++ b/web/play-filters-helpers/src/test/scala/play/filters/headers/SecurityHeadersFilterSpec.scala @@ -11,6 +11,7 @@ import play.api.http.HttpFilters import play.api.inject.bind import play.api.inject.guice.GuiceApplicationBuilder import play.api.mvc.DefaultActionBuilder +import play.api.mvc.EssentialFilter import play.api.mvc.Result import play.api.mvc.Results._ import play.api.routing.Router @@ -22,7 +23,7 @@ import play.api.Application import play.api.Configuration class Filters @Inject() (securityHeadersFilter: SecurityHeadersFilter) extends HttpFilters { - def filters = Seq(securityHeadersFilter) + def filters: Seq[EssentialFilter] = Seq(securityHeadersFilter) } object SecurityHeadersFilterSpec { diff --git a/web/play-filters-helpers/src/test/scala/play/filters/hosts/AllowedHostsFilterSpec.scala b/web/play-filters-helpers/src/test/scala/play/filters/hosts/AllowedHostsFilterSpec.scala index deb38b96788..03d7ba69315 100644 --- a/web/play-filters-helpers/src/test/scala/play/filters/hosts/AllowedHostsFilterSpec.scala +++ b/web/play-filters-helpers/src/test/scala/play/filters/hosts/AllowedHostsFilterSpec.scala @@ -35,7 +35,7 @@ import play.api.Environment object AllowedHostsFilterSpec { class Filters @Inject() (allowedHostsFilter: AllowedHostsFilter) extends HttpFilters { - def filters = Seq(allowedHostsFilter) + def filters: Seq[EssentialFilter] = Seq(allowedHostsFilter) } case class ActionHandler(result: RequestHeader => Result) extends (RequestHeader => Result) { @@ -369,6 +369,6 @@ class CustomErrorHandler extends HttpErrorHandler { .getOrElse("") + " / " + message ) ) - def onServerError(request: RequestHeader, exception: Throwable) = + def onServerError(request: RequestHeader, exception: Throwable): Future[Result] = Future.successful(Results.BadRequest) }