This commit is contained in:
parent
0a426a7ab0
commit
46c662965f
16 changed files with 918 additions and 144 deletions
|
|
@ -5,11 +5,13 @@
|
|||
package akka.persistence.query
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
import akka.actor._
|
||||
import akka.event.Logging
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.Failure
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.{ Config, ConfigFactory }
|
||||
|
||||
/**
|
||||
* Persistence extension for queries.
|
||||
|
|
@ -39,21 +41,33 @@ class PersistenceQuery(system: ExtendedActorSystem) extends Extension {
|
|||
/** Discovered query plugins. */
|
||||
private val readJournalPluginExtensionIds = new AtomicReference[Map[String, ExtensionId[PluginHolder]]](Map.empty)
|
||||
|
||||
/**
|
||||
* Scala API: Returns the [[akka.persistence.query.scaladsl.ReadJournal]] specified by the given
|
||||
* read journal configuration entry.
|
||||
*
|
||||
* The provided readJournalPluginConfig will be used to configure the journal plugin instead of the actor system
|
||||
* config.
|
||||
*/
|
||||
final def readJournalFor[T <: scaladsl.ReadJournal](readJournalPluginId: String, readJournalPluginConfig: Config): T =
|
||||
readJournalPluginFor(readJournalPluginId, readJournalPluginConfig).scaladslPlugin.asInstanceOf[T]
|
||||
|
||||
/**
|
||||
* Scala API: Returns the [[akka.persistence.query.scaladsl.ReadJournal]] specified by the given
|
||||
* read journal configuration entry.
|
||||
*/
|
||||
final def readJournalFor[T <: scaladsl.ReadJournal](readJournalPluginId: String): T =
|
||||
readJournalPluginFor(readJournalPluginId).scaladslPlugin.asInstanceOf[T]
|
||||
readJournalFor(readJournalPluginId, ConfigFactory.empty)
|
||||
|
||||
/**
|
||||
* Java API: Returns the [[akka.persistence.query.javadsl.ReadJournal]] specified by the given
|
||||
* read journal configuration entry.
|
||||
*/
|
||||
final def getReadJournalFor[T <: javadsl.ReadJournal](clazz: Class[T], readJournalPluginId: String): T =
|
||||
readJournalPluginFor(readJournalPluginId).javadslPlugin.asInstanceOf[T]
|
||||
final def getReadJournalFor[T <: javadsl.ReadJournal](clazz: Class[T], readJournalPluginId: String, readJournalPluginConfig: Config): T =
|
||||
readJournalPluginFor(readJournalPluginId, readJournalPluginConfig).javadslPlugin.asInstanceOf[T]
|
||||
|
||||
@tailrec private def readJournalPluginFor(readJournalPluginId: String): PluginHolder = {
|
||||
final def getReadJournalFor[T <: javadsl.ReadJournal](clazz: Class[T], readJournalPluginId: String): T = getReadJournalFor[T](clazz, readJournalPluginId, ConfigFactory.empty())
|
||||
|
||||
@tailrec private def readJournalPluginFor(readJournalPluginId: String, readJournalPluginConfig: Config): PluginHolder = {
|
||||
val configPath = readJournalPluginId
|
||||
val extensionIdMap = readJournalPluginExtensionIds.get
|
||||
extensionIdMap.get(configPath) match {
|
||||
|
|
@ -62,20 +76,21 @@ class PersistenceQuery(system: ExtendedActorSystem) extends Extension {
|
|||
case None ⇒
|
||||
val extensionId = new ExtensionId[PluginHolder] {
|
||||
override def createExtension(system: ExtendedActorSystem): PluginHolder = {
|
||||
val provider = createPlugin(configPath)
|
||||
val provider = createPlugin(configPath, readJournalPluginConfig)
|
||||
PluginHolder(provider.scaladslReadJournal(), provider.javadslReadJournal())
|
||||
}
|
||||
}
|
||||
readJournalPluginExtensionIds.compareAndSet(extensionIdMap, extensionIdMap.updated(configPath, extensionId))
|
||||
readJournalPluginFor(readJournalPluginId) // Recursive invocation.
|
||||
readJournalPluginFor(readJournalPluginId, readJournalPluginConfig) // Recursive invocation.
|
||||
}
|
||||
}
|
||||
|
||||
private def createPlugin(configPath: String): ReadJournalProvider = {
|
||||
private def createPlugin(configPath: String, readJournalPluginConfig: Config): ReadJournalProvider = {
|
||||
val mergedConfig = readJournalPluginConfig.withFallback(system.settings.config)
|
||||
require(
|
||||
!isEmpty(configPath) && system.settings.config.hasPath(configPath),
|
||||
!isEmpty(configPath) && mergedConfig.hasPath(configPath),
|
||||
s"'reference.conf' is missing persistence read journal plugin config path: '${configPath}'")
|
||||
val pluginConfig = system.settings.config.getConfig(configPath)
|
||||
val pluginConfig = mergedConfig.getConfig(configPath)
|
||||
val pluginClassName = pluginConfig.getString("class")
|
||||
log.debug(s"Create plugin: ${configPath} ${pluginClassName}")
|
||||
val pluginClass = system.dynamicAccess.getClassFor[AnyRef](pluginClassName).get
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ package akka.persistence.query;
|
|||
import akka.NotUsed;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.testkit.AkkaJUnitActorSystemResource;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import org.junit.ClassRule;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import akka.actor.ExtendedActorSystem
|
|||
* Use for tests only!
|
||||
* Emits infinite stream of strings (representing queried for events).
|
||||
*/
|
||||
class DummyReadJournal extends scaladsl.ReadJournal with scaladsl.PersistenceIdsQuery {
|
||||
class DummyReadJournal(val dummyValue: String) extends scaladsl.ReadJournal with scaladsl.PersistenceIdsQuery {
|
||||
override def persistenceIds(): Source[String, NotUsed] =
|
||||
Source.fromIterator(() ⇒ Iterator.from(0)).map(_.toString)
|
||||
}
|
||||
|
|
@ -42,13 +42,19 @@ object DummyReadJournalProvider {
|
|||
${DummyReadJournal.Identifier}4 {
|
||||
class = "${classOf[DummyReadJournalProvider4].getCanonicalName}"
|
||||
}
|
||||
${DummyReadJournal.Identifier}5 {
|
||||
class = "${classOf[DummyReadJournalProvider5].getCanonicalName}"
|
||||
}
|
||||
""")
|
||||
}
|
||||
|
||||
class DummyReadJournalProvider extends ReadJournalProvider {
|
||||
class DummyReadJournalProvider(dummyValue: String) extends ReadJournalProvider {
|
||||
|
||||
// mandatory zero-arg constructor
|
||||
def this() = this("dummy")
|
||||
|
||||
override val scaladslReadJournal: DummyReadJournal =
|
||||
new DummyReadJournal
|
||||
new DummyReadJournal(dummyValue)
|
||||
|
||||
override val javadslReadJournal: DummyReadJournalForJava =
|
||||
new DummyReadJournalForJava(scaladslReadJournal)
|
||||
|
|
@ -60,3 +66,7 @@ class DummyReadJournalProvider3(sys: ExtendedActorSystem, conf: Config) extends
|
|||
|
||||
class DummyReadJournalProvider4(sys: ExtendedActorSystem, conf: Config, confPath: String) extends DummyReadJournalProvider
|
||||
|
||||
class DummyReadJournalProvider5(sys: ExtendedActorSystem) extends DummyReadJournalProvider
|
||||
|
||||
class CustomDummyReadJournalProvider5(sys: ExtendedActorSystem) extends DummyReadJournalProvider("custom")
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@
|
|||
package akka.persistence.query
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.persistence.journal.EventSeq
|
||||
import akka.persistence.journal.ReadEventAdapter
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import akka.persistence.journal.{ EventSeq, ReadEventAdapter }
|
||||
import com.typesafe.config.{ Config, ConfigFactory }
|
||||
import org.scalatest.{ BeforeAndAfterAll, Matchers, WordSpecLike }
|
||||
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.duration._
|
||||
|
||||
|
|
@ -17,21 +18,45 @@ class PersistenceQuerySpec extends WordSpecLike with Matchers with BeforeAndAfte
|
|||
|
||||
val eventAdaptersConfig =
|
||||
s"""
|
||||
|akka.persistence.query.journal.dummy {
|
||||
| event-adapters {
|
||||
| adapt = ${classOf[PrefixStringWithPAdapter].getCanonicalName}
|
||||
| }
|
||||
|}
|
||||
|akka.persistence.query.journal.dummy {
|
||||
| event-adapters {
|
||||
| adapt = ${classOf[PrefixStringWithPAdapter].getCanonicalName}
|
||||
| }
|
||||
|}
|
||||
""".stripMargin
|
||||
|
||||
val customReadJournalPluginConfig =
|
||||
s"""
|
||||
|${DummyReadJournal.Identifier}5 {
|
||||
| class = "${classOf[CustomDummyReadJournalProvider5].getCanonicalName}"
|
||||
|}
|
||||
|${DummyReadJournal.Identifier}6 {
|
||||
| class = "${classOf[DummyReadJournalProvider].getCanonicalName}"
|
||||
|}
|
||||
""".stripMargin
|
||||
|
||||
"ReadJournal" must {
|
||||
"be found by full config key" in {
|
||||
withActorSystem() { system ⇒
|
||||
PersistenceQuery.get(system).readJournalFor[DummyReadJournal](DummyReadJournal.Identifier)
|
||||
val readJournalPluginConfig: Config = ConfigFactory.parseString(customReadJournalPluginConfig)
|
||||
PersistenceQuery.get(system).readJournalFor[DummyReadJournal](
|
||||
DummyReadJournal.Identifier, readJournalPluginConfig)
|
||||
// other combinations of constructor parameters
|
||||
PersistenceQuery.get(system).readJournalFor[DummyReadJournal](DummyReadJournal.Identifier + "2")
|
||||
PersistenceQuery.get(system).readJournalFor[DummyReadJournal](DummyReadJournal.Identifier + "3")
|
||||
PersistenceQuery.get(system).readJournalFor[DummyReadJournal](DummyReadJournal.Identifier + "4")
|
||||
PersistenceQuery.get(system).readJournalFor[DummyReadJournal](
|
||||
DummyReadJournal.Identifier + "2", readJournalPluginConfig)
|
||||
PersistenceQuery.get(system).readJournalFor[DummyReadJournal](
|
||||
DummyReadJournal.Identifier + "3", readJournalPluginConfig)
|
||||
PersistenceQuery.get(system).readJournalFor[DummyReadJournal](
|
||||
DummyReadJournal.Identifier + "4", readJournalPluginConfig)
|
||||
// config key existing within both the provided readJournalPluginConfig
|
||||
// and the actorSystem config. The journal must be created from the provided config then.
|
||||
val dummyReadJournal5 = PersistenceQuery.get(system).readJournalFor[DummyReadJournal](
|
||||
DummyReadJournal.Identifier + "5", readJournalPluginConfig)
|
||||
dummyReadJournal5.dummyValue should equal("custom")
|
||||
// config key directly coming from the provided readJournalPluginConfig,
|
||||
// and does not exist within the actorSystem config
|
||||
PersistenceQuery.get(system).readJournalFor[DummyReadJournal](
|
||||
DummyReadJournal.Identifier + "6", readJournalPluginConfig)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -46,6 +71,7 @@ class PersistenceQuerySpec extends WordSpecLike with Matchers with BeforeAndAfte
|
|||
}
|
||||
|
||||
private val systemCounter = new AtomicInteger()
|
||||
|
||||
private def withActorSystem(conf: String = "")(block: ActorSystem ⇒ Unit): Unit = {
|
||||
val config =
|
||||
DummyReadJournalProvider.config
|
||||
|
|
@ -60,8 +86,13 @@ class PersistenceQuerySpec extends WordSpecLike with Matchers with BeforeAndAfte
|
|||
}
|
||||
|
||||
object ExampleQueryModels {
|
||||
case class OldModel(value: String) { def promote = NewModel(value) }
|
||||
|
||||
case class OldModel(value: String) {
|
||||
def promote = NewModel(value)
|
||||
}
|
||||
|
||||
case class NewModel(value: String)
|
||||
|
||||
}
|
||||
|
||||
class PrefixStringWithPAdapter extends ReadEventAdapter {
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class EventsByPersistenceIdSpec extends AkkaSpec(EventsByPersistenceIdSpec.confi
|
|||
src.map(_.event).runWith(TestSink.probe[Any])
|
||||
.request(2)
|
||||
.expectNext("a-1", "a-2")
|
||||
.expectNoMsg(500.millis)
|
||||
.expectNoMessage(500.millis)
|
||||
.request(2)
|
||||
.expectNext("a-3")
|
||||
.expectComplete()
|
||||
|
|
@ -81,13 +81,13 @@ class EventsByPersistenceIdSpec extends AkkaSpec(EventsByPersistenceIdSpec.confi
|
|||
val probe = src.map(_.event).runWith(TestSink.probe[Any])
|
||||
.request(2)
|
||||
.expectNext("f-1", "f-2")
|
||||
.expectNoMsg(100.millis)
|
||||
.expectNoMessage(100.millis)
|
||||
|
||||
ref ! "f-4"
|
||||
expectMsg("f-4-done")
|
||||
|
||||
probe
|
||||
.expectNoMsg(100.millis)
|
||||
.expectNoMessage(100.millis)
|
||||
.request(5)
|
||||
.expectNext("f-3")
|
||||
.expectComplete() // f-4 not seen
|
||||
|
|
@ -186,13 +186,13 @@ class EventsByPersistenceIdSpec extends AkkaSpec(EventsByPersistenceIdSpec.confi
|
|||
val probe = src.map(_.event).runWith(TestSink.probe[Any])
|
||||
.request(2)
|
||||
.expectNext("e-1", "e-2")
|
||||
.expectNoMsg(100.millis)
|
||||
.expectNoMessage(100.millis)
|
||||
|
||||
ref ! "e-4"
|
||||
expectMsg("e-4-done")
|
||||
|
||||
probe
|
||||
.expectNoMsg(100.millis)
|
||||
.expectNoMessage(100.millis)
|
||||
.request(5)
|
||||
.expectNext("e-3")
|
||||
.expectNext("e-4")
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ class EventsByTagSpec extends AkkaSpec(EventsByTagSpec.config)
|
|||
.request(2)
|
||||
.expectNext(EventEnvelope(Sequence(1L), "a", 2L, "a green apple"))
|
||||
.expectNext(EventEnvelope(Sequence(2L), "a", 3L, "a green banana"))
|
||||
.expectNoMsg(500.millis)
|
||||
.expectNoMessage(500.millis)
|
||||
.request(2)
|
||||
.expectNext(EventEnvelope(Sequence(3L), "b", 2L, "a green leaf"))
|
||||
.expectComplete()
|
||||
|
|
@ -99,13 +99,13 @@ class EventsByTagSpec extends AkkaSpec(EventsByTagSpec.config)
|
|||
.request(2)
|
||||
.expectNext(EventEnvelope(Sequence(1L), "a", 2L, "a green apple"))
|
||||
.expectNext(EventEnvelope(Sequence(2L), "a", 3L, "a green banana"))
|
||||
.expectNoMsg(100.millis)
|
||||
.expectNoMessage(100.millis)
|
||||
|
||||
c ! "a green cucumber"
|
||||
expectMsg(s"a green cucumber-done")
|
||||
|
||||
probe
|
||||
.expectNoMsg(100.millis)
|
||||
.expectNoMessage(100.millis)
|
||||
.request(5)
|
||||
.expectNext(EventEnvelope(Sequence(3L), "b", 2L, "a green leaf"))
|
||||
.expectComplete() // green cucumber not seen
|
||||
|
|
@ -130,7 +130,7 @@ class EventsByTagSpec extends AkkaSpec(EventsByTagSpec.config)
|
|||
val probe = blackSrc.runWith(TestSink.probe[Any])
|
||||
.request(2)
|
||||
.expectNext(EventEnvelope(Sequence(1L), "b", 1L, "a black car"))
|
||||
.expectNoMsg(100.millis)
|
||||
.expectNoMessage(100.millis)
|
||||
|
||||
d ! "a black dog"
|
||||
expectMsg(s"a black dog-done")
|
||||
|
|
@ -139,7 +139,7 @@ class EventsByTagSpec extends AkkaSpec(EventsByTagSpec.config)
|
|||
|
||||
probe
|
||||
.expectNext(EventEnvelope(Sequence(2L), "d", 1L, "a black dog"))
|
||||
.expectNoMsg(100.millis)
|
||||
.expectNoMessage(100.millis)
|
||||
.request(10)
|
||||
.expectNext(EventEnvelope(Sequence(3L), "d", 2L, "a black night"))
|
||||
}
|
||||
|
|
@ -151,7 +151,7 @@ class EventsByTagSpec extends AkkaSpec(EventsByTagSpec.config)
|
|||
// note that banana is not included, since exclusive offset
|
||||
.expectNext(EventEnvelope(Sequence(3L), "b", 2L, "a green leaf"))
|
||||
.expectNext(EventEnvelope(Sequence(4L), "c", 1L, "a green cucumber"))
|
||||
.expectNoMsg(100.millis)
|
||||
.expectNoMessage(100.millis)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue