1 /*
2  *  Make.org Core API
3  *  Copyright (C) 2018 Make.org
4  *
5  * This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU Affero General Public License as
7  *  published by the Free Software Foundation, either version 3 of the
8  *  License, or (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU Affero General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Affero General Public License
16  *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  *
18  */
19 
20 package org.make.api.sessionhistory
21 
22 import akka.actor.typed.scaladsl.Behaviors
23 import akka.actor.typed.{ActorRef, Behavior}
24 import akka.persistence.typed.scaladsl._
25 import akka.persistence.typed.{PersistenceId, SnapshotAdapter}
26 import grizzled.slf4j.Logging
27 import org.make.api.extensions.MakeSettings
28 import org.make.api.technical.TimeSettings
29 import org.make.api.userhistory._
30 import org.make.core.proposal.{ProposalId, QualificationKey}
31 import org.make.core.session._
32 import org.make.core.technical.IdGenerator
33 
34 import scala.concurrent.duration.{Deadline, FiniteDuration}
35 
36 object SessionHistoryActor extends Logging {
37 
38   val JournalPluginId: String = "make-api.event-sourcing.sessions.journal"
39   val SnapshotPluginId: String = "make-api.event-sourcing.sessions.snapshot"
40   val QueryJournalPluginId: String = "make-api.event-sourcing.sessions.query"
41 
42   sealed trait LockVoteAction
43 
44   case object Vote extends LockVoteAction
45   final case class ChangeQualifications(key: Seq[QualificationKey]) extends LockVoteAction
46 
47   class LockHandler(lockDuration: FiniteDuration) {
48     private var locks: Map[ProposalId, LockVoteAction] = Map.empty
49     private var deadline = Deadline.now
50 
51     def add(lock: (ProposalId, LockVoteAction)): Unit = {
52       locks += lock
53       deadline = Deadline.now + lockDuration
54     }
55 
56     def getOrExpire(id: ProposalId): Option[LockVoteAction] = {
57       if (deadline.isOverdue()) {
58         locks = Map.empty
59       }
60       locks.get(id)
61     }
62 
63     def release(id: ProposalId): Unit = {
64       locks -= id
65     }
66   }
67 
68   def apply(
69     userHistoryCoordinator: ActorRef[UserHistoryCommand],
70     lockDuration: FiniteDuration,
71     idGenerator: IdGenerator,
72     settings: MakeSettings
73   ): Behavior[SessionHistoryCommand] = {
74     Behaviors.setup { implicit context =>
75       val timeout = TimeSettings.defaultTimeout
76       val id: SessionId = SessionId(context.self.path.name)
77       val persistenceId: PersistenceId = PersistenceId.ofUniqueId(id.value)
78       val lockHandler = new LockHandler(lockDuration)
79       EventSourcedBehavior[SessionHistoryCommand, SessionHistoryEvent[_], State](
80         persistenceId,
81         emptyState = Active(SessionHistory(Nil), None),
82         _.handleCommand(userHistoryCoordinator, lockHandler, settings, idGenerator)(context, timeout)(_),
83         _.handleEvent(settings)(_)
84       ).withJournalPluginId(JournalPluginId)
85         .withSnapshotPluginId(SnapshotPluginId)
86         .withRetention(
87           RetentionCriteria.snapshotEvery(numberOfEvents = 10, keepNSnapshots = 2).withDeleteEventsOnSnapshot
88         )
89         .snapshotAdapter(snapshotAdapter)
90     }
91   }
92 
93   val snapshotAdapter: SnapshotAdapter[State] = new SnapshotAdapter[State] {
94     override def toJournal(state: State): Any = state
95 
96     @SuppressWarnings(Array("org.wartremover.warts.Throw"))
97     override def fromJournal(from: Any): State = {
98       from match {
99         case sessionHistory: SessionHistory =>
100           val allEvents: Seq[SessionHistoryEvent[_]] =
101             sessionHistory.events.sortWith((e1, e2) => e2.action.date.isBefore(e1.action.date))
102 
103           allEvents.collectFirst {
104             case event: SessionExpired     => Expired(event.action.arguments, Some(event.action.date))
105             case event: SessionTransformed => Closed(event.action.arguments, Some(event.action.date))
106             case event: SessionTransforming =>
107               Transforming(sessionHistory, event.requestContext, Some(event.action.date))
108           }.getOrElse(Active(sessionHistory, allEvents.headOption.map(_.action.date)))
109         case state: State => state
110         case other        => throw new IllegalStateException(s"$other with class ${other.getClass} is not a recoverable state")
111       }
112     }
113   }
114 }
Line Stmt Id Pos Tree Symbol Tests Code
38 15633 1400 - 1442 Literal <nosymbol> "make-api.event-sourcing.sessions.journal"
39 12245 1476 - 1519 Literal <nosymbol> "make-api.event-sourcing.sessions.snapshot"
40 18188 1557 - 1597 Literal <nosymbol> "make-api.event-sourcing.sessions.query"
48 14469 1873 - 1882 TypeApply scala.collection.immutable.Map.empty scala.Predef.Map.empty[org.make.core.proposal.ProposalId, Nothing]
49 12664 1910 - 1922 Select scala.concurrent.duration.Deadline.now scala.concurrent.duration.Deadline.now
52 9436 1988 - 2001 Apply scala.collection.immutable.MapOps.+ LockHandler.this.locks.+[org.make.api.sessionhistory.SessionHistoryActor.LockVoteAction](lock)
52 15249 1988 - 2001 Apply org.make.api.sessionhistory.SessionHistoryActor.LockHandler.locks_= LockHandler.this.locks_=(LockHandler.this.locks.+[org.make.api.sessionhistory.SessionHistoryActor.LockVoteAction](lock))
53 11516 2034 - 2046 Select org.make.api.sessionhistory.SessionHistoryActor.LockHandler.lockDuration LockHandler.this.lockDuration
53 16019 2008 - 2046 Apply org.make.api.sessionhistory.SessionHistoryActor.LockHandler.deadline_= LockHandler.this.deadline_=(scala.concurrent.duration.Deadline.now.+(LockHandler.this.lockDuration))
53 9577 2019 - 2046 Apply scala.concurrent.duration.Deadline.+ scala.concurrent.duration.Deadline.now.+(LockHandler.this.lockDuration)
57 15425 2124 - 2124 Block <nosymbol> ()
57 12442 2128 - 2148 Apply scala.concurrent.duration.Deadline.isOverdue LockHandler.this.deadline.isOverdue()
57 9451 2124 - 2124 Literal <nosymbol> ()
58 12568 2160 - 2177 Block org.make.api.sessionhistory.SessionHistoryActor.LockHandler.locks_= LockHandler.this.locks_=(scala.Predef.Map.empty[org.make.core.proposal.ProposalId, Nothing])
58 14488 2160 - 2177 Apply org.make.api.sessionhistory.SessionHistoryActor.LockHandler.locks_= LockHandler.this.locks_=(scala.Predef.Map.empty[org.make.core.proposal.ProposalId, Nothing])
58 17947 2168 - 2177 TypeApply scala.collection.immutable.Map.empty scala.Predef.Map.empty[org.make.core.proposal.ProposalId, Nothing]
60 11400 2192 - 2205 Apply scala.collection.MapOps.get LockHandler.this.locks.get(id)
64 17422 2261 - 2272 Apply scala.collection.immutable.MapOps.- LockHandler.this.locks.-(id)
64 15910 2261 - 2272 Apply org.make.api.sessionhistory.SessionHistoryActor.LockHandler.locks_= LockHandler.this.locks_=(LockHandler.this.locks.-(id))
74 18197 2491 - 3353 Apply akka.actor.typed.scaladsl.Behaviors.setup akka.actor.typed.scaladsl.Behaviors.setup[org.make.api.sessionhistory.SessionHistoryCommand](((implicit context: akka.actor.typed.scaladsl.ActorContext[org.make.api.sessionhistory.SessionHistoryCommand]) => { val timeout: akka.util.Timeout = org.make.api.technical.TimeSettings.defaultTimeout; val id: org.make.core.session.SessionId = org.make.core.session.SessionId.apply(context.self.path.name); val persistenceId: akka.persistence.typed.PersistenceId = akka.persistence.typed.PersistenceId.ofUniqueId(id.value); val lockHandler: org.make.api.sessionhistory.SessionHistoryActor.LockHandler = new SessionHistoryActor.this.LockHandler(lockDuration); akka.persistence.typed.scaladsl.EventSourcedBehavior.apply[org.make.api.sessionhistory.SessionHistoryCommand, org.make.api.sessionhistory.SessionHistoryEvent[_], org.make.api.sessionhistory.State](persistenceId, Active.apply(SessionHistory.apply(scala.`package`.Nil), scala.None), ((x$1: org.make.api.sessionhistory.State, x$2: org.make.api.sessionhistory.SessionHistoryCommand) => x$1.handleCommand(userHistoryCoordinator, lockHandler, settings, idGenerator)(context, timeout).apply(x$2)), ((x$3: org.make.api.sessionhistory.State, x$4: org.make.api.sessionhistory.SessionHistoryEvent[_]) => x$3.handleEvent(settings).apply(x$4))).withJournalPluginId(SessionHistoryActor.this.JournalPluginId).withSnapshotPluginId(SessionHistoryActor.this.SnapshotPluginId).withRetention(akka.persistence.typed.scaladsl.RetentionCriteria.snapshotEvery(10, 2).withDeleteEventsOnSnapshot).snapshotAdapter(SessionHistoryActor.this.snapshotAdapter) }))
75 12452 2549 - 2576 Select org.make.api.technical.TimeSettings.defaultTimeout org.make.api.technical.TimeSettings.defaultTimeout
76 14678 2603 - 2636 Apply org.make.core.session.SessionId.apply org.make.core.session.SessionId.apply(context.self.path.name)
76 18276 2613 - 2635 Select akka.actor.ActorPath.name context.self.path.name
77 9352 2678 - 2712 Apply akka.persistence.typed.PersistenceId.ofUniqueId akka.persistence.typed.PersistenceId.ofUniqueId(id.value)
77 12928 2703 - 2711 Select org.make.core.session.SessionId.value id.value
78 15435 2737 - 2766 Apply org.make.api.sessionhistory.SessionHistoryActor.LockHandler.<init> new SessionHistoryActor.this.LockHandler(lockDuration)
81 11713 2915 - 2918 Select scala.Nil scala.`package`.Nil
81 17634 2900 - 2919 Apply org.make.api.sessionhistory.SessionHistory.apply SessionHistory.apply(scala.`package`.Nil)
81 15923 2921 - 2925 Select scala.None scala.None
81 12462 2893 - 2926 Apply org.make.api.sessionhistory.Active.apply Active.apply(SessionHistory.apply(scala.`package`.Nil), scala.None)
82 18184 2936 - 3032 Apply scala.Function1.apply x$1.handleCommand(userHistoryCoordinator, lockHandler, settings, idGenerator)(context, timeout).apply(x$2)
83 14696 3042 - 3068 Apply scala.Function1.apply x$3.handleEvent(settings).apply(x$4)
84 10820 3097 - 3112 Select org.make.api.sessionhistory.SessionHistoryActor.JournalPluginId SessionHistoryActor.this.JournalPluginId
85 9368 3144 - 3160 Select org.make.api.sessionhistory.SessionHistoryActor.SnapshotPluginId SessionHistoryActor.this.SnapshotPluginId
87 17401 3196 - 3295 Select akka.persistence.typed.scaladsl.SnapshotCountRetentionCriteria.withDeleteEventsOnSnapshot akka.persistence.typed.scaladsl.RetentionCriteria.snapshotEvery(10, 2).withDeleteEventsOnSnapshot
87 11620 3266 - 3267 Literal <nosymbol> 2
87 15243 3245 - 3247 Literal <nosymbol> 10
89 12377 2773 - 3347 Apply akka.persistence.typed.scaladsl.EventSourcedBehavior.snapshotAdapter akka.persistence.typed.scaladsl.EventSourcedBehavior.apply[org.make.api.sessionhistory.SessionHistoryCommand, org.make.api.sessionhistory.SessionHistoryEvent[_], org.make.api.sessionhistory.State](persistenceId, Active.apply(SessionHistory.apply(scala.`package`.Nil), scala.None), ((x$1: org.make.api.sessionhistory.State, x$2: org.make.api.sessionhistory.SessionHistoryCommand) => x$1.handleCommand(userHistoryCoordinator, lockHandler, settings, idGenerator)(context, timeout).apply(x$2)), ((x$3: org.make.api.sessionhistory.State, x$4: org.make.api.sessionhistory.SessionHistoryEvent[_]) => x$3.handleEvent(settings).apply(x$4))).withJournalPluginId(SessionHistoryActor.this.JournalPluginId).withSnapshotPluginId(SessionHistoryActor.this.SnapshotPluginId).withRetention(akka.persistence.typed.scaladsl.RetentionCriteria.snapshotEvery(10, 2).withDeleteEventsOnSnapshot).snapshotAdapter(SessionHistoryActor.this.snapshotAdapter)
89 15529 3331 - 3346 Select org.make.api.sessionhistory.SessionHistoryActor.snapshotAdapter SessionHistoryActor.this.snapshotAdapter
93 15341 3407 - 3410 Apply org.make.api.sessionhistory.SessionHistoryActor.$anon.<init> new $anon()
101 14590 3802 - 3816 Select org.make.api.sessionhistory.SessionAction.date org.make.api.sessionhistory.sessionhistoryactortest e1.action.date
101 10834 3778 - 3817 Apply java.time.chrono.ChronoZonedDateTime.isBefore org.make.api.sessionhistory.sessionhistoryactortest e2.action.date.isBefore(e1.action.date)
101 9075 3735 - 3818 Apply scala.collection.SeqOps.sortWith org.make.api.sessionhistory.sessionhistoryactortest sessionHistory.events.sortWith(((e1: org.make.api.sessionhistory.SessionHistoryEvent[_], e2: org.make.api.sessionhistory.SessionHistoryEvent[_]) => e2.action.date.isBefore(e1.action.date)))
103 13865 3853 - 3853 Apply org.make.api.sessionhistory.SessionHistoryActor.$anon.$anonfun.<init> org.make.api.sessionhistory.sessionhistoryactortest new $anonfun()
104 15129 3909 - 3931 Select org.make.api.sessionhistory.SessionAction.arguments org.make.api.sessionhistory.sessionhistoryactortest event.action.arguments
104 17312 3933 - 3956 Apply scala.Some.apply org.make.api.sessionhistory.sessionhistoryactortest scala.Some.apply[java.time.ZonedDateTime](event.action.date)
104 11635 3938 - 3955 Select org.make.api.sessionhistory.SessionAction.date org.make.api.sessionhistory.sessionhistoryactortest event.action.date
104 15543 3901 - 3957 Apply org.make.api.sessionhistory.Expired.apply org.make.api.sessionhistory.sessionhistoryactortest Expired.apply(event.action.arguments, scala.Some.apply[java.time.ZonedDateTime](event.action.date))
105 12449 4011 - 4033 Select org.make.api.sessionhistory.SessionAction.arguments org.make.api.sessionhistory.sessionhistoryactortest event.action.arguments
105 14378 4035 - 4058 Apply scala.Some.apply org.make.api.sessionhistory.sessionhistoryactortest scala.Some.apply[java.time.ZonedDateTime](event.action.date)
105 10741 4004 - 4059 Apply org.make.api.sessionhistory.Closed.apply org.make.api.sessionhistory.sessionhistoryactortest Closed.apply(event.action.arguments, scala.Some.apply[java.time.ZonedDateTime](event.action.date))
105 18101 4040 - 4057 Select org.make.api.sessionhistory.SessionAction.date org.make.api.sessionhistory.sessionhistoryactortest event.action.date
107 8958 4150 - 4170 Select org.make.api.sessionhistory.SessionTransforming.requestContext org.make.api.sessionhistory.sessionhistoryactortest event.requestContext
107 11542 4172 - 4195 Apply scala.Some.apply org.make.api.sessionhistory.sessionhistoryactortest scala.Some.apply[java.time.ZonedDateTime](event.action.date)
107 17325 4121 - 4196 Apply org.make.api.sessionhistory.Transforming.apply org.make.api.sessionhistory.sessionhistoryactortest Transforming.apply(sessionHistory, event.requestContext, scala.Some.apply[java.time.ZonedDateTime](event.action.date))
107 15431 4177 - 4194 Select org.make.api.sessionhistory.SessionAction.date org.make.api.sessionhistory.sessionhistoryactortest event.action.date
108 14856 4219 - 4282 Apply org.make.api.sessionhistory.Active.apply org.make.api.sessionhistory.sessionhistoryactortest Active.apply(sessionHistory, allEvents.headOption.map[java.time.ZonedDateTime](((x$5: org.make.api.sessionhistory.SessionHistoryEvent[_]) => x$5.action.date)))
108 10756 3830 - 4283 Apply scala.Option.getOrElse org.make.api.sessionhistory.sessionhistoryactortest allEvents.collectFirst[org.make.api.sessionhistory.State](({ @SerialVersionUID(value = 0) final <synthetic> class $anonfun extends scala.runtime.AbstractPartialFunction[org.make.api.sessionhistory.SessionHistoryEvent[_],org.make.api.sessionhistory.State] with java.io.Serializable { def <init>(): <$anon: org.make.api.sessionhistory.SessionHistoryEvent[_] => org.make.api.sessionhistory.State> = { $anonfun.super.<init>(); () }; final override def applyOrElse[A1 <: org.make.api.sessionhistory.SessionHistoryEvent[_], B1 >: org.make.api.sessionhistory.State](x1: A1, default: A1 => B1): B1 = ((x1.asInstanceOf[org.make.api.sessionhistory.SessionHistoryEvent[_]]: org.make.api.sessionhistory.SessionHistoryEvent[_]): org.make.api.sessionhistory.SessionHistoryEvent[_$2] @unchecked) match { case (event @ (_: org.make.api.sessionhistory.SessionExpired)) => Expired.apply(event.action.arguments, scala.Some.apply[java.time.ZonedDateTime](event.action.date)) case (event @ (_: org.make.api.sessionhistory.SessionTransformed)) => Closed.apply(event.action.arguments, scala.Some.apply[java.time.ZonedDateTime](event.action.date)) case (event @ (_: org.make.api.sessionhistory.SessionTransforming)) => Transforming.apply(sessionHistory, event.requestContext, scala.Some.apply[java.time.ZonedDateTime](event.action.date)) case (defaultCase$ @ _) => default.apply(x1) }; final def isDefinedAt(x1: org.make.api.sessionhistory.SessionHistoryEvent[_]): Boolean = ((x1.asInstanceOf[org.make.api.sessionhistory.SessionHistoryEvent[_]]: org.make.api.sessionhistory.SessionHistoryEvent[_]): org.make.api.sessionhistory.SessionHistoryEvent[_$2] @unchecked) match { case (event @ (_: org.make.api.sessionhistory.SessionExpired)) => true case (event @ (_: org.make.api.sessionhistory.SessionTransformed)) => true case (event @ (_: org.make.api.sessionhistory.SessionTransforming)) => true case (defaultCase$ @ _) => false } }; new $anonfun() }: PartialFunction[org.make.api.sessionhistory.SessionHistoryEvent[_],org.make.api.sessionhistory.State])).getOrElse[org.make.api.sessionhistory.State](Active.apply(sessionHistory, allEvents.headOption.map[java.time.ZonedDateTime](((x$5: org.make.api.sessionhistory.SessionHistoryEvent[_]) => x$5.action.date))))
108 18400 4242 - 4281 Apply scala.Option.map org.make.api.sessionhistory.sessionhistoryactortest allEvents.headOption.map[java.time.ZonedDateTime](((x$5: org.make.api.sessionhistory.SessionHistoryEvent[_]) => x$5.action.date))
108 12354 4267 - 4280 Select org.make.api.sessionhistory.SessionAction.date org.make.api.sessionhistory.sessionhistoryactortest x$5.action.date
110 9363 4348 - 4446 Throw <nosymbol> throw new java.lang.IllegalStateException(("".+(other).+(" with class ").+(other.getClass()).+(" is not a recoverable state"): String))