I'm trying to setup a basic websocket based notification system using Scala, Play and Akka. The websocket works fine, I'm able to send stuff, echo it back, respond with newly created case classes etc. I've defined a sealed trait with case classes to represent the various inputs and outputs, with custom writes and reads in order to manage input/output JSON objects and add/remove a "type" field.
However, when I try to send data through the websocket via an external action, I get this weird run time error: java.lang.ClassCastException: v1.socket.SocketEvent$ cannot be cast to v1.socket.SocketEvent
Here is the invocation from an action:
WebSocketActor.send(ConnectionRequest("test", "test", "test"), "test")
Here is my WebSocketActor:
object WebSocketActor {
def props(out: ActorRef) = Props(new WebSocketActor(out))
var sessions: Map[String, Map[String, ActorRef]] = Map()
/** Send data to a specified user.
* Response with false if the user does not have a session
*/
def send(socketEvent: SocketEvent, userId: String): Boolean = {
if (sessions contains userId) {
val userSessions = sessions(userId)
userSessions.foreach(user => user._2 ! SocketEvent)
return true
}
false
}
}
class WebSocketActor(out: ActorRef) extends Actor {
/** Authenticate the user and add device to session
* (note that authentication is stripped out for brevity)
*/
def auth: Receive = {
case init: Init => {
var session: Map[String, ActorRef] = Map(init.deviceId -> out)
if (sessions contains init.userId) {
session = session ++ sessions(init.userId)
}
sessions = sessions + (init.userId -> session)
context become await
}
}
def await: Receive = {
case conReq: ConnectionRequest => {
out ! conReq
}
}
override def receive: Receive = auth
}
Here is the SocketEvent seale trait, companion object and case classes:
sealed trait SocketEvent
object SocketEvent {
implicit val socketEventFmt: Format[SocketEvent] = new Format[SocketEvent] {
override def writes(event: SocketEvent): JsValue = event match {
case e: ConnectionRequest => Json.toJson(e)(Json.format[ConnectionRequest])
case e: Init => Json.toJson(e)(Json.format[Init])
}
override def reads(json: JsValue): JsResult[SocketEvent] = (json \ "type").as[String] match {
case "connectionRequest" =>
Json.fromJson[ConnectionRequest](json.as[JsObject] - "type")(Json.format[ConnectionRequest])
case "init" =>
Json.fromJson[Init](json.as[JsObject] - "type")(Json.format[Init])
}
}
}
case class ConnectionRequest(userId: String, publicKey: String, signature: String) extends SocketEvent
object ConnectionRequest {
implicit val format: OWrites[ConnectionRequest] = Json.format[ConnectionRequest]
.withConstant("type", "connectionRequest")
}
case class Init(userId: String, deviceId: String) extends SocketEvent
object Init{
implicit val initFmt: OWrites[Init] = Json.format[Init]
.withConstant("type", "Init")
}
Finally, an OWritesExtra class that is used to add fields to the outputed Json:
object OWritesExtra {
implicit class OWritesExt[A](owrites: OWrites[A]) {
/** Add a (key, value) pair to the JSON object,
* where the value is constant.
*/
def withConstant[B: Writes](key: String, value: B): OWrites[A] =
withValue(key, _ => value)
/** Add a key, value pair to the JSON object that is written, where
* the value depends on the original object.
*/
def withValue[B: Writes](key: String, value: A => B): OWrites[A] =
new OWrites[A] {
def writes(a: A): JsObject = owrites.writes(a) ++ Json.obj(key -> value(a))
}
}
}