Reusablecode for db operations with dependency injection - scala

I have multiple actors managing data models that are written to a mongo db.
object LevelManager {
val collectionName = "levels"
}
#Singleton
class LevelManager #Inject()(
val reactiveMongoApi: ReactiveMongoApi) extends Actor with ActorLogging with InjectedActorSupport {
def collection: Future[JSONCollection] = reactiveMongoApi.database.map(_.collection[JSONCollection](LevelManager.collectionName))
override def receive: Receive = {
case msg:GetById =>
var level = collection.flatMap(c => c.find(Json.obj("_id" -> msg.id), Option.empty[JsObject]).one[LevelModel].map {
result =>
logger.info( result )
}
}
}
This works fine, but this db code is used in every actor and i did not manage to have it only once. I'm not sure if this is even a clever way, too. It derived from older scala times without dependency injection, where everything was put in an object trait.
So i'm looking for a trait or something, with basic db io handling
Edit: Before dependency injection i was able to use a trait like this:
trait BaseModel[T] {
val collectionName: String
val db = ReactiveMongoPlugin.db
def load(id: Long)(implicit fmt: Format[T]) = {
val coll = db.collection[JSONCollection](collectionName)
coll.find(Json.obj("_id" -> id)).one[T]
}
def loadAll()(implicit fmt: Format[T]) = {
val coll = db.collection[JSONCollection](collectionName)
coll.find(Json.obj()).cursor[T].collect[Vector]()
}
def save(id: Long, model: T)(implicit fmt: Format[T]) = {
val coll = db.collection[JSONCollection](collectionName)
val doc = Json.toJson(model).as[JsObject] + ("_id" -> Json.toJson(id))
coll.save(doc).map { lastError =>
if (!lastError.ok) Logger.error(lastError.message)
lastError.ok
}
}

I ended in creating a trait with def collection: Future[JSONCollection] and i'm now able to access the db my favorite db functions. This was my goal and makes life so much better. But i'm unsettled from the recent feedback here, if this has any disadvantages.
trait DBStuff[T] {
def collection: Future[JSONCollection]
def log: LoggingAdapter
def save(id: String, model: T)(implicit fmt: Format[T]) = {
val doc:JsObject = Json.toJson(model).as[JsObject] + ("_id" -> Json.toJson(id))
collection.flatMap(_.update.one(Json.obj("_id" -> id), doc, true)).map(lastError => if (!lastError.ok) log.warning(s"Mongo LastError: %s".format(lastError)))
}
def loadOne(id: String)(implicit fmt: Format[T]): Future[Option[T]] = loadOne( Json.obj("_id" -> id) )
def loadOne(obj: JsObject, projection:Option[JsObject] = None )(implicit fmt: Format[T]): Future[Option[T]] = {
collection.flatMap(_.find( obj, projection).one[T].map {
result =>
result
}.recover {
case err => log.error(s"DB Loading Error: $err")
None
})
}
def loadAll()(implicit fmt: Format[T]):Future[Vector[T]] = {
loadAll(Json.obj(), None )
}
def loadAll( obj: JsObject, projection:Option[JsObject] = None)(implicit fmt: Format[T]):Future[Vector[T]] = {
collection.flatMap(_.find(obj, projection ).cursor[T]().collect[Vector](Int.MaxValue, Cursor.FailOnError()).map {
result => result
}.recover {
case err =>
log.error(s"DB Loading Error: $err")
Vector.empty
})
}
...
}
#Singleton
class TaskManager #Inject()(
val reactiveMongoApi: ReactiveMongoApi
) extends Actor with ActorLogging with InjectedActorSupport with DBStuff[TaskModel] {
def collection: Future[JSONCollection] = reactiveMongoApi.database.map(_.collection[JSONCollection](TaskManager.collectionName))
override def preStart() = {
loadAll() map {
result =>
//What ever
}
}

Related

FS2 passing resource (or effect) as a state

I'm trying to implement an application that controls a camera. Camera commands are represented as a stream of CameraAction objects:
sealed trait CameraMessage
case object Record(recordId: String) extends CameraMessage
case object Stop extends CameraMessage
...
val s = Stream[F, CameraMessage]
Let's say I have a test stream that emits "Record" and emits "Stop" 20 seconds later, after another 20 seconds another "Record" message is emitted and so on, the input stream is infinite.
Then the app consumes "Record" it should create an instance of GStreamer pipeline (i.e. it is an effect) and "run" it, on "Stop" it 'stops' the pipeline and closes it. Then on subsequent "Record" the pattern is repeated with new GStreamer pipeline.
The problem is that I need to pass an instance of impure, mutable object between handles of stream events.
FS2 documentation suggest to use chunks to make a stream stateful, so I tried
def record(gStreamerPipeline: String, fileName: String)
(implicit sync: Sync[F]): F[Pipeline] =
{
... create and open pipeline ...
}
def stopRecording(pipe: Pipeline)(implicit sync: Sync[F]): F[Unit] = {
... stop pipeline, release resources ...
}
def effectPipe(pipelineDef: String)(implicit L: Logger[F]):
Pipe[F, CameraMessage, F[Unit]] = {
type CameraSessionHandle = Pipeline
type CameraStream = Stream[F, CameraSessionHandle]
s: Stream[F, CameraMessage] =>
s.scanChunks(Stream[F, CameraSessionHandle]()) {
case (s: CameraStream, c: Chunk[CameraMessage]) =>
c.last match {
case Some(Record(fileName)) =>
(Stream.bracket(record(pipelineDef, fileName))(p => stopRecording(p)), Chunk.empty)
case Some(StopRecording) =>
(Stream.empty, Chunk(s.compile.drain))
case _ =>
(s, Chunk.empty)
}
}
}
The problem with this code that actual recording does not happen on 'Record' event but rather then the effect of the whole chunk is evaluated, i.e. when 'StopRecording' message arrives the camera is turned on and then immediately turned off again.
How can I pass a "state" without chunking? Or is there any other way to achieve the result I need?
This may be similar to
FS2 Stream with StateT[IO, _, _], periodically dumping state
but the difference is that the state in my case is not a pure data structure but a resource.
I eventually was able so solve it using Mutable Reference pattern as described in https://typelevel.org/blog/2018/06/07/shared-state-in-fp.html
Here is the code:
import cats.effect._
import cats.syntax.all._
import fs2.Stream
import scala.concurrent.{ExecutionContext, ExecutionContextExecutor}
import scala.language.higherKinds
class FRef[F[_], T](implicit sync: Sync[F]) {
private var state: T = _
def set(n: T): F[Unit] = sync.delay(this.state = n)
def get: F[T] = sync.pure(state)
}
object FRef {
def apply[F[_], T](implicit sync: Sync[F]): F[FRef[F, T]] = sync.delay { new FRef() }
}
class CameraImpl(id: String) extends Camera {
override def record(): Unit = {
println(s"Recording $id")
}
override def stop(): Unit = {
println(s"Stopping $id")
}
override def free(): Unit = {
Thread.sleep(500)
println(s"Freeing $id")
}
}
object Camera {
def apply(id: String) = new CameraImpl(id)
}
trait Camera {
def record(): Unit
def stop(): Unit
def free(): Unit
}
sealed trait CameraMessage
case class Record(recordId: String) extends CameraMessage
case object StopRecording extends CameraMessage
class Streamer[F[_]](implicit sync: Sync[F]) {
def record(id: String): F[Camera] = {
sync.delay {
val r = Camera(id)
r.record()
r
}
}
def stopRecording(pipe: Camera): F[Unit] = {
sync.delay {
pipe.stop()
pipe.free()
}
}
def effectPipe(state: FRef[F, Option[Camera]])(
implicit sync: Sync[F]): Stream[F, CameraMessage] => Stream[F, Unit] = {
type CameraStream = Stream[F, Camera]
s: Stream[F, CameraMessage] =>
s.evalMap {
case Record(fileName) =>
for {
r <- record(fileName)
_ <- state.set(Some(r))
} yield ()
case StopRecording =>
for {
s <- state.get
_ <- stopRecording(s.get)
_ <- state.set(None)
} yield ()
}
}
}
object FS2Problem extends IOApp {
import scala.concurrent.duration._
override def run(args: List[String]): IO[ExitCode] = {
implicit val ec: ExecutionContextExecutor = ExecutionContext.global
val streamer = new Streamer[IO]
val s = Stream.awakeEvery[IO](5.seconds).take(10).zipWithIndex.map {
case (_, idx) =>
idx % 2 match {
case 0 =>
Record(s"Record $idx")
case _ =>
StopRecording
}
}
val ss = for {
streamerState <- Stream.eval(FRef[IO, Option[Camera]])
s <- s.through(streamer.effectPipe(streamerState))
} yield ()
ss.compile.drain.map(_ => ExitCode.Success)
}
}

How to properly use IO and OptionT in service layer in for-comprehension?

I have a simple repository interface with CRUD operations (probably, it is a bad idea to pass implicit session as parameter in general trait):
trait Repository[Entity, PK] {
def find(pk: PK)(implicit session: DBSession): OptionT[IO, Entity]
def insert(e: Entity)(implicit session: DBSession): IO[Entity]
def update(e: Entity)(implicit session: DBSession): IO[Entity]
def delete(pk: PK)(implicit session: DBSession): IO[Int]
def findAll()(implicit session: DBSession): IO[List[Entity]]
}
And i want to use it like this:
for {
_ <- repository.insert(???)
_ <- repository.delete(???)
v <- repository.find(???).value
_ <- someFunctionReliesOnReturnedValue(v)
} yield (???)
Also, i want to stop execution if v is None and rollback transaction if there is any error (i use scalikejdbc). So, as i think, i have to do it in my service layer like this (+ wrap it into Try or something like this to cacth business exception):
def logic(???) = {
DB localTx {
implicit session => {
(for {
_ <- repository.insert(???)
_ <- repository.delete(???)
v <- repository.find(???).value
_ <- someFunctionReliesOnReturnedValue(v)
} yield (???)).unsafeRunSync() // to rollback transaction if there is any error
}
}
}
The problem is here: someFunctionReliesOnReturnedValue(v). It can be an arbitrary function which accepts Entity not Option[Entity]. How can i convert result of OptionT[IO, Entity] to IO[Entity] and save semantic of Option[]?
Is it correct approach or i've mistaken somewhere?
import java.nio.file.{Files, Paths}
import cats.data.OptionT
import cats.effect.IO
import scalikejdbc._
import scala.util.Try
case class Entity(id: Long, value: String)
object Entity extends SQLSyntaxSupport[Entity] {
override def tableName: String = "entity"
override def columnNames: Seq[String] = Seq("id", "value")
def apply(g: SyntaxProvider[Entity])(rs: WrappedResultSet): Entity = apply(g.resultName)(rs)
def apply(r: ResultName[Entity])(rs: WrappedResultSet): Entity =
Entity(rs.long(r.id), rs.string(r.value))
}
trait Repository[Entity, PK] {
def find(pk: PK)(implicit session: DBSession): OptionT[IO, Entity]
def insert(e: Entity)(implicit session: DBSession): IO[Entity]
}
class EntityRepository extends Repository[Entity, Long] {
private val alias = Entity.syntax("entity")
override def find(pk: Long)(implicit session: DBSession): OptionT[IO, Entity] = OptionT{
IO{
withSQL {
select(alias.resultAll).from(Entity as alias).where.eq(Entity.column.id, pk)
}.map(Entity(alias.resultName)(_)).single().apply()
}
}
override def insert(e: Entity)(implicit session: DBSession): IO[Entity] = IO{
withSQL {
insertInto(Entity).namedValues(
Entity.column.id -> e.id,
Entity.column.value -> e.value,
)
}.update().apply()
e
}
}
object EntityRepository {
def apply(): EntityRepository = new EntityRepository()
}
object Util {
def createFile(value: String): IO[Unit] = IO(Files.createDirectory(Paths.get("path", value)))
}
class Service {
val repository = EntityRepository()
def logic(): Either[Throwable, Unit] = Try {
DB localTx {
implicit session => {
val result: IO[Unit] = for {
_ <- repository.insert(Entity(1, "1"))
_ <- repository.insert(Entity(2, "2"))
e <- repository.find(3)
_ <- Util.createFile(e.value) // error
//after this step there is possible more steps (another insert or find)
} yield ()
result.unsafeRunSync()
}
}
}.toEither
}
object Test extends App {
ConnectionPool.singleton("jdbc:postgresql://localhost:5433/postgres", "postgres", "")
val service = new Service()
service.logic()
}
Table:
create table entity (id numeric(38), value varchar(255));
And i got compile error:
Error:(69, 13) type mismatch; found : cats.effect.IO[Unit]
required: cats.data.OptionT[cats.effect.IO,?]
_ <- Util.createFile(e.value)
In general, you should convert all of your different results to your "most general" type that has a monad. In this case, that means you should use OptionT[IO, A] throughout your for-comprehension by converting all of those IO[Entity] to OptionT[IO, Entity] with OptionT.liftF:
for {
_ <- OptionT.liftF(repository.insert(???))
_ <- OptionT.liftF(repository.delete(???))
v <- repository.find(???)
_ <- someFunctionReliesOnReturnedValue(v)
} yield (???)
If you had an Option[A] you could use OptionT.fromOption[IO]. The issues come from trying to mix monads within the same for-comprehension.
This will already stop execution if any of these result in a None. As for rolling back the transaction, that depends on how your DB interaction library works, but if it handles exceptions by rolling back, then yes, unsafeRunSync will work. If you also want it to roll back by throwing an exception when the result is None, you could do something like:
val result: OptionT[IO, ...] = ...
result.value.unsafeRunSync().getOrElse(throw new FooException(...))

How to create slick projection for list of nested case class?

I am using play 2.6.6 , scala 2.12.3 and slick 3.0.0.
I had following case class structure initially where there was a nested case class:
case class Device(id: Int, deviceUser: Option[DeviceUser] =None)
case class DeviceUser(name: Option[String] = None)
So, I had created following projection for Device class:
class DevicesTable(tag: Tag) extends Table[Device](tag, "DEVICES") {
def id = column[Int]("ID", O.PrimaryKey)
def name = column[Option[String]]("NAME")
def deviceUser = name.<>[Option[DeviceUser]](
{
(param: Option[String]) => {
param match {
case Some(name) => Some(DeviceUser(Some(name)))
case None => None
}
}
},
{
(t: Option[DeviceUser]) =>
{
t match {
case Some(user) => Some(user.name)
case None => None
}
}
}
)
def * = (id, deviceUser).<>(Device.tupled, Device.unapply)
}
The above setup was working fine. I could easily store and retrieve data using the above projection. But now, my requirement has changed and I need to store list of nested case class. So, the class structure is now as follow :
case class Device(id: Int, deviceUser: Option[List[DeviceUser]] =None)
case class DeviceUser(name: Option[String] = None)
Is there some way where I could define projection for the field deviceUser: Option[List[DeviceUser]] ?
Update : I am looking for more of a non-relational approach here.
Since, no body has suggested a solution so far, I am sharing the approach that I am using right now. It works but of course is not the best solution. Specially, I want to avoid using Await here and would like to develop a generic implicit parser.
ALso, I had to create a separate DeviceUsersTable.
case class DeviceUser(id: Int,name: Option[String] = None)
class DeviceUserRepo #Inject()(protected val dbConfigProvider: DatabaseConfigProvider) {
val dbConfig = dbConfigProvider.get[JdbcProfile]
val db = dbConfig.db
import dbConfig.profile.api._
val DeviceUsers = TableQuery[DeviceUserTable]
private def _findById(id: Int): DBIO[Option[DeviceUser]] =
DeviceUsers.filter(_.id === id).result.headOption
def findById(id: Int): Future[Option[DeviceUser]] =
db.run(_findById(id))
def all: Future[List[DeviceUser]] =
db.run(DeviceUsers.to[List].result)
def create(deviceUser: DeviceUser): Future[Int] = {
db.run(DeviceUsers returning DeviceUsers.map(_.id) += deviceUser)
}
class DeviceUserTable(tag: Tag) extends Table[DeviceUser](tag, "DEVICE_USERS") {
def id = column[Int]("ID", O.PrimaryKey)
def name = column[Option[String]]("NAME")
def * = (id, name).<>(DeviceUser.tupled, DeviceUser.unapply)
}
}
And the original DevicesTable now looks like this :
class DevicesTable(tag: Tag) extends Table[Device](tag, "DEVICES") {
implicit val deviceUserConverter = MappedColumnType.base[Option[List[DeviceUser]], String](
deviceUsersOpt => {
deviceUsersOpt match {
case Some(users:List[DeviceUser]) =>val listOfId = users.map{
k => val res = deviceUserRepo.create(k)
Await.result(res, 10 seconds)
}
listOfId.mkString(",")
case None => ""
}
},
str =>{
val listOfIds = (str split "," map Integer.parseInt).toList.filterNot(k => k.equals(""))
if(listOfIds.nonEmpty){
val users = listOfIds.map{ k =>
val res = deviceUserRepo.findById(k)
Await.result(res, 10 seconds)
}
Some(users.flatten)
} else {
None
}
}
)
def id = column[Int]("ID", O.PrimaryKey)
def deviceUser = column[Option[List[DeviceUser]]]("DEVICE_USERS")
def * = (id, deviceUser).<>(Device.tupled, Device.unapply)
}

Run code if no results were found in a MongoDB query in Scala

I've got this Scala code:
Bot.dbUserCollection.find(/*filter*/).first().subscribe(new Observer[Document] {
override def onError(e: Throwable): Unit = Utils.handleError(msg, e)
override def onComplete(): Unit = {}
override def onNext(result: Document): Unit =
/*here im doing operations with the result, if found*/
})
What is the best way to execute some code only if no result was found? Can I somehow easily check if anything was found in onComplete() maybe? I know this has been asked and anwsered many times for other languages in many places all over the Internet but unfortunately I've found no Scala answer
Keeping in mind that onNext will be called when a new document is retrieved and onComplete will be called when the operation is complete regardless of whether or not the query returned any results, you can count the number of results in onNext and check that count in onComplete.
An MVCE follows:
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicLong
import org.mongodb.scala.bson.collection.immutable.Document
import org.mongodb.scala.{
MongoClient,
MongoCollection,
MongoDatabase,
Observable,
Observer
}
import scala.concurrent.Await
import scala.concurrent.duration.Duration
object SimpleInsertionAndRetrieval extends App {
import Helpers._
// To directly connect to the default server localhost on port 27017
val mongoClient: MongoClient = MongoClient()
val database: MongoDatabase = mongoClient.getDatabase("mydb")
val collection: MongoCollection[Document] = database.getCollection("test")
val doc: Document =
Document("_id" -> 0, "name" -> "MongoDB", "type" -> "database")
collection.insertOne(doc).results()
private def observer = new Observer[Document] {
private val counter = new AtomicLong(0)
override def onError(e: Throwable): Unit = {
println(s"Error: $e")
}
override def onComplete(): Unit = {
val numberOfElements = counter.get()
if (numberOfElements == 0) {
println("Nothing was found!")
} else {
println(s"There were $numberOfElements")
}
}
override def onNext(result: Document): Unit = {
counter.incrementAndGet()
// do other things
}
}
collection
.find()
.subscribe(observer)
collection
.find(Document("{ _id: 1 }")) // There is no document with _id = 0
.subscribe(observer)
Thread.sleep(5000)
/**
* From the documentation
*
* #see https://github.com/mongodb/mongo-scala-driver/blob/master/examples/src/test/scala/tour/Helpers.scala
*/
object Helpers {
implicit class DocumentObservable[C](val observable: Observable[Document])
extends ImplicitObservable[Document] {
override val converter: (Document) => String = (doc) => doc.toJson
}
implicit class GenericObservable[C](val observable: Observable[C])
extends ImplicitObservable[C] {
override val converter: (C) => String = (doc) => doc.toString
}
trait ImplicitObservable[C] {
val observable: Observable[C]
val converter: (C) => String
def results(): Seq[C] =
Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS))
def headResult() =
Await.result(observable.head(), Duration(10, TimeUnit.SECONDS))
def printResults(initial: String = ""): Unit = {
if (initial.length > 0) print(initial)
results().foreach(res => println(converter(res)))
}
def printHeadResult(initial: String = ""): Unit =
println(s"${initial}${converter(headResult())}")
}
}
}
This prints
There were 1
Nothing was found!

Neo4j streaming in user-defined procedures

When using Neo4j unmanaged extensions, one can stream results to the client while traversing the graph like this (in Scala):
import javax.ws.rs.core.{MediaType, Response, StreamingOutput}
val stream: StreamingOutput = ???
Response.ok().entity(stream).`type`(MediaType.APPLICATION_JSON).build()
I can't find a similar possibility when using Neo4j 3 used-defined stored procedures. They return Java 8 Streams but I can't see how I could add elements to such streams while they already being consumed, in parallel.
Is it possible?
I have an example of that in one of the APOC procedures.
https://github.com/neo4j-contrib/neo4j-apoc-procedures/blob/master/src/main/java/apoc/cypher/Cypher.java#L77
I want to add more / a more general example of that in the future.
Here is what I came up with based on Michael Hunger code (in Scala).
QueueBasedSpliterator:
import java.util.Spliterator
import java.util.concurrent.{BlockingQueue, TimeUnit}
import java.util.function.Consumer
import org.neo4j.kernel.api.KernelTransaction
private class QueueBasedSpliterator[T](queue: BlockingQueue[T],
tombstone: T,
tx: KernelTransaction) extends Spliterator[T] {
override def tryAdvance(action: Consumer[_ >: T]): Boolean =
try {
if (tx.shouldBeTerminated()) false
else {
val entry = queue.poll(100, TimeUnit.MILLISECONDS)
if (entry == null || entry == tombstone) false
else {
action.accept(entry)
true
}
}
} catch {
case e: InterruptedException => false
}
override def trySplit(): Spliterator[T] = null
override def estimateSize(): Long = Long.MaxValue
override def characteristics(): Int = Spliterator.ORDERED | Spliterator.NONNULL
}
Notice the 100 ms timeout value. Might require tuning.
ResultsStream (wrapper around blocking queue):
import java.util.concurrent.BlockingQueue
class ResultsStream[T](tombstone: T, queue: BlockingQueue[T]) extends AutoCloseable {
def put(t: T): Unit = {
queue.put(t)
}
override def close(): Unit = {
put(tombstone)
}
}
CommonUtil helper methods:
import java.util.concurrent.ArrayBlockingQueue
import java.util.stream.{Stream, StreamSupport}
import org.neo4j.kernel.api.KernelTransaction
import org.neo4j.kernel.internal.GraphDatabaseAPI
import scala.concurrent.{ExecutionContext, Future}
object CommonUtil {
def inTx(db: GraphDatabaseAPI)(f: => Unit): Unit =
Managed(db.beginTx()) { tx => f; tx.success() }
def inTxFuture(db: GraphDatabaseAPI)(f: => Unit)(implicit ec: ExecutionContext): Future[Unit] =
Future(inTx(db)(f))
def streamResults[T](tombstone: T, tx: KernelTransaction)
(f: ResultsStream[T] => Any): Stream[T] = {
val queue = new ArrayBlockingQueue[T](100)
f(new ResultsStream(tombstone, queue))
StreamSupport.stream(new QueueBasedSpliterator[T](queue, tombstone, tx), false)
}
}
Some more helpers:
object Managed {
type AutoCloseableView[T] = T => AutoCloseable
def apply[T : AutoCloseableView, V](resource: T)(op: T => V): V =
try {
op(resource)
} finally {
resource.close()
}
}
Pool:
import java.util.concurrent.{ArrayBlockingQueue, ThreadPoolExecutor, TimeUnit}
import scala.concurrent.{ExecutionContext, ExecutionContextExecutor}
object Pool {
lazy val DefaultExecutionContent: ExecutionContextExecutor =
ExecutionContext.fromExecutor(createDefaultExecutor())
// values might be tuned in production
def createDefaultExecutor(corePoolSize: Int = Runtime.getRuntime.availableProcessors() * 2,
keepAliveSeconds: Int = 30) = {
val queueSize = corePoolSize * 25
new ThreadPoolExecutor(
corePoolSize / 2,
corePoolSize,
keepAliveSeconds.toLong,
TimeUnit.SECONDS,
new ArrayBlockingQueue[Runnable](queueSize),
new ThreadPoolExecutor.CallerRunsPolicy()
)
}
}
Usage in a procedure:
#Procedure("example.readStream")
def readStream(#Name("nodeId") nodeId: NodeId): Stream[StreamingItem] =
CommonUtil.streamResults(StreamingItem.Tombstone, kernelTx) { results =>
CommonUtil.inTxFuture(db) { // uses Pool.DefaultExecutionContent
Managed(results) { _ =>
graphUtil.findTreeNode(nodeId).foreach { node =>
// add elements to the stream here
results.put(???)
}
}
}
}
StreamingItem.Tombstone is just a static StreamingItem instance with special meaning to close the stream. db and kernelTx are just context variable set by Neo4j:
#Context
public GraphDatabaseAPI db;
#Context
public KernelTransaction kernelTx;