Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Commit b66b821

Browse files
author
cesmec
committed
Added realtime updates
1 parent 130e2ab commit b66b821

File tree

6 files changed

+135
-1
lines changed

6 files changed

+135
-1
lines changed

src/main/scala/ScalatraBootstrap.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import javax.servlet.ServletContext
22
import org.codeoverflow.chatoverflow.ui.web.rest.config.ConfigController
33
import org.codeoverflow.chatoverflow.ui.web.rest.connector.ConnectorController
4+
import org.codeoverflow.chatoverflow.ui.web.rest.events.{EventsController, EventsDispatcher}
45
import org.codeoverflow.chatoverflow.ui.web.rest.plugin.PluginInstanceController
56
import org.codeoverflow.chatoverflow.ui.web.rest.types.TypeController
67
import org.codeoverflow.chatoverflow.ui.web.{CodeOverflowSwagger, OpenAPIServlet}
@@ -21,6 +22,9 @@ class ScalatraBootstrap extends LifeCycle {
2122
context.initParameters("org.scalatra.cors.allowedMethods") = "*"
2223

2324
// Add all servlets and controller
25+
val eventsController = new EventsController()
26+
EventsDispatcher.init(eventsController)
27+
context.mount(eventsController, "/events/*", "events")
2428
context.mount(new TypeController(), "/types/*", "types")
2529
context.mount(new ConfigController(), "/config/*", "config")
2630
context.mount(new PluginInstanceController(), "/instances/*", "instances")

src/main/scala/org/codeoverflow/chatoverflow/framework/manager/PluginManagerImpl.scala

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import java.util
44

55
import org.codeoverflow.chatoverflow.WithLogger
66
import org.codeoverflow.chatoverflow.api.plugin.{PluginLogMessage, PluginManager}
7+
import org.codeoverflow.chatoverflow.ui.web.rest.events.EventsDispatcher
8+
import org.json4s.DefaultFormats
9+
import org.json4s.jackson.Serialization
710

811
import scala.collection.JavaConverters._
912
import scala.collection.mutable.ListBuffer
@@ -23,11 +26,16 @@ class PluginManagerImpl(pluginInstanceName: String, logOutputOnConsole: Boolean)
2326
* @param message the message to show
2427
*/
2528
override def log(message: String): Unit = {
26-
logMessages += new PluginLogMessage(message)
29+
val logMessage = new PluginLogMessage(message)
30+
logMessages += logMessage
2731

2832
if (logOutputOnConsole) {
2933
logger info s"[$pluginInstanceName] $message"
3034
}
35+
36+
implicit val formats: DefaultFormats.type = DefaultFormats
37+
val data = Map(("message", message), ("timestamp", logMessage.getTimestamp.toString))
38+
EventsDispatcher.broadcast("instance", Serialization.write(Map(("name", pluginInstanceName), ("action", "log"), ("data", data))))
3139
}
3240

3341
/**

src/main/scala/org/codeoverflow/chatoverflow/instance/PluginInstance.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import org.codeoverflow.chatoverflow.api.plugin.{Plugin, PluginManager}
88
import org.codeoverflow.chatoverflow.framework.PluginCompatibilityState.PluginCompatibilityState
99
import org.codeoverflow.chatoverflow.framework.manager.{PluginManagerImpl, PluginManagerStub}
1010
import org.codeoverflow.chatoverflow.framework.{PluginCompatibilityState, PluginType}
11+
import org.codeoverflow.chatoverflow.ui.web.rest.events.EventsDispatcher
12+
import org.json4s.DefaultFormats
13+
import org.json4s.jackson.Serialization
1114

1215
/**
1316
* A plugin instance holds all the general information of the plugin type and specific information of
@@ -149,6 +152,9 @@ class PluginInstance(val instanceName: String, pluginType: PluginType) extends W
149152
logger info s"Starting plugin '$instanceName' in new thread!"
150153
try {
151154
instanceThread = new Thread(() => {
155+
implicit val formats: DefaultFormats.type = DefaultFormats
156+
EventsDispatcher.broadcast("instance", Serialization.write(Map(("name", instanceName), ("action", "start"))))
157+
152158
try {
153159

154160
// Execute plugin setup
@@ -191,6 +197,7 @@ class PluginInstance(val instanceName: String, pluginType: PluginType) extends W
191197
requirement.asInstanceOf[Requirement[Output]].get().shutdown()
192198
})
193199

200+
EventsDispatcher.broadcast("instance", Serialization.write(Map(("name", instanceName), ("action", "stop"))))
194201
}
195202
})
196203
instanceThread.start()
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package org.codeoverflow.chatoverflow.ui.web.rest.events
2+
3+
import java.io.PrintWriter
4+
import java.util.concurrent.ConcurrentHashMap
5+
6+
import javax.servlet.AsyncContext
7+
import javax.servlet.http.HttpServletRequest
8+
import org.codeoverflow.chatoverflow.ui.web.JsonServlet
9+
import org.scalatra.servlet.ScalatraAsyncSupport
10+
import org.scalatra.{BadRequest, Unauthorized}
11+
import org.scalatra.swagger.Swagger
12+
13+
class EventsController(implicit val swagger: Swagger) extends JsonServlet with ScalatraAsyncSupport with EventsControllerDefinition {
14+
private val connectionWriters = new ConcurrentHashMap[AsyncContext, PrintWriter]()
15+
16+
def broadcast(messageType: String, message: String = null): Unit = {
17+
connectionWriters.forEach((_, writer) => {
18+
try {
19+
sendMessage(writer, messageType, message)
20+
} catch {
21+
//probably lost or closed connection, remove from the list of connected clients
22+
case _: Throwable => connectionWriters.remove(writer)
23+
}
24+
})
25+
}
26+
27+
def closeConnections(): Unit = {
28+
connectionWriters.forEach((_, writer) => {
29+
try {
30+
sendMessage(writer, "close", null)
31+
writer.close()
32+
} finally {
33+
connectionWriters.remove(writer)
34+
}
35+
})
36+
}
37+
38+
private def sendMessage(writer: PrintWriter, messageType: String, message: String): Unit = {
39+
var msg = "event: " + messageType.replace("\n", "") + "\n"
40+
if (message != null)
41+
msg += "data: " + message.replace("\n", "\ndata: ") + "\n\n"
42+
writer.write(msg)
43+
writer.flush()
44+
}
45+
46+
get("/", operation(getEvents)) {
47+
val accept = request.getHeader("Accept")
48+
if (accept == null || !accept.replace(" ", "").split(",").contains("text/event-stream")) {
49+
status = 406
50+
} else {
51+
authParamRequired {
52+
contentType = "text/event-stream"
53+
54+
val asyncContext = request.startAsync()
55+
asyncContext.setTimeout(0)
56+
57+
val writer = asyncContext.getResponse.getWriter
58+
connectionWriters.put(asyncContext, writer)
59+
}
60+
}
61+
}
62+
63+
private def authParamRequired(func: => Any)(implicit request: HttpServletRequest): Any = {
64+
val authKeyKey = "authKey"
65+
66+
if (!request.parameters.contains(authKeyKey) || request.getParameter(authKeyKey).isEmpty) {
67+
BadRequest()
68+
} else if (request.getParameter(authKeyKey) != chatOverflow.credentialsService.generateAuthKey()) {
69+
Unauthorized()
70+
} else {
71+
func
72+
}
73+
}
74+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.codeoverflow.chatoverflow.ui.web.rest.events
2+
3+
import org.codeoverflow.chatoverflow.ui.web.rest.{AuthSupport, TagSupport}
4+
import org.scalatra.swagger.{SwaggerSupport, SwaggerSupportSyntax}
5+
import org.scalatra.swagger.SwaggerSupportSyntax.OperationBuilder
6+
7+
trait EventsControllerDefinition extends SwaggerSupport with TagSupport with AuthSupport {
8+
val getEvents: OperationBuilder =
9+
(apiOperation[Object]("getEvents")
10+
summary "Get events"
11+
description "Get events from chatoverflow using the EventSource API. Requires the authKey as a cookie and an Accept-header with the value text/event-stream."
12+
parameter authQuery
13+
tags controllerTag)
14+
15+
protected def authQuery: SwaggerSupportSyntax.ParameterBuilder[String] =
16+
queryParam[String]("authKey").description("connection auth key required")
17+
18+
override def controllerTag: String = "events"
19+
20+
override protected def applicationDescription: String = "Handles chatoverflow events."
21+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.codeoverflow.chatoverflow.ui.web.rest.events
2+
3+
object EventsDispatcher {
4+
private var controller: EventsController = _
5+
6+
def init(eventsController: EventsController): Unit = {
7+
if (controller == null)
8+
controller = eventsController
9+
}
10+
11+
def broadcast(messageType: String, message: String = null): Unit = {
12+
if (controller != null)
13+
controller.broadcast(messageType, message)
14+
}
15+
16+
def close(): Unit = {
17+
if (controller != null)
18+
controller.closeConnections()
19+
}
20+
}

0 commit comments

Comments
 (0)