-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Make Example GdtfWrapper for Tests a Singleton Now, the example GDTF file only needs to be parsed once and the resulting GdtfWrapper can be shared between all tets. * Implement and Test Out Event for AddFixtureTypes * Make Queue Tests for AddFixtureTypes More Complex * Refactor Queue Notification for Patch into Generic Function * Implement Out Events for RemoveFixtureTypes * Implement Out Events for Fixture Operations in Patch * RemoveFixture OutEvent when Removing FixtureType * Make Putting Messages into Queue Blocking * Scaffolding for OutEventHandler Logging outgoing events is already working * First Unit-Tested Implementation of JsonSubscriptionHandler * Serialize JSON Message Only Once for All Subscribers * Move Example Fixture & Awaitility Config to Utilities * Move Integration Test Harness to Utilities * Move GDTF Integration Tests to Specific Class * Move Add Fixture Integration Tests to Specific Class * Move Update Fixture Integration Tests to Specific Class * Move Remove Fixture Integration Tests to Specific Class * Clean Up Test Names in Specific Classes Also clean up imports in whole project * Delete integration Test Package by moving specific classes to the specific packages * Delete Documentation of integration Test Package * Rename RemoveFixtureTest * Move JsonSubscriptionHandler to Generic SubscriptionHandler * Handle Subscribe in IncomingGlowRequestHandler required some changes for dependency injection * Implement Sync in SubscriptionHandler * Rename JsonTopic to GlowTopic * Integration Test for Subscription * Test Random Sync Messages for SubscriptionHandler * New Subscription API It is now generic over the topic, as it was before the rework. E.g. "patchSubscribe" -> "subscribe" with "data": "patch". I realized while implementing the subscription mechanism that this would be less work once we add a new topic. * Log Unexpected Message Events from Client * Remove Trivial TODOs * Unsubscribe Upon WebSocket Close * Add Some Docstrings * Test and Implement Sync After Unsubscribe * Add Docstrings and Refactors for Readability
- Loading branch information
Showing
37 changed files
with
1,215 additions
and
782 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
cueglow-server/src/main/kotlin/org/cueglow/server/OutEventHandler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package org.cueglow.server | ||
|
||
import org.apache.logging.log4j.kotlin.Logging | ||
import org.cueglow.server.objects.messages.GlowMessage | ||
import java.util.concurrent.Executors | ||
import java.util.concurrent.LinkedBlockingQueue | ||
|
||
/** | ||
* Starts a thread that takes GlowMessages from the [queue] of OutEvents and passes them to the registered receivers. | ||
*/ | ||
class OutEventHandler(receivers: Iterable<OutEventReceiver>): Logging { | ||
val queue = LinkedBlockingQueue<GlowMessage>() | ||
|
||
init { | ||
Executors.newSingleThreadExecutor().submit { | ||
while (true) { | ||
val glowMessage = queue.take() | ||
logger.info("Handling OutEvent: $glowMessage") | ||
receivers.forEach { it.receive(glowMessage) } | ||
} | ||
} | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
cueglow-server/src/main/kotlin/org/cueglow/server/OutEventReceiver.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package org.cueglow.server | ||
|
||
import org.cueglow.server.objects.messages.GlowMessage | ||
|
||
interface OutEventReceiver { | ||
fun receive(glowMessage: GlowMessage) | ||
} |
6 changes: 4 additions & 2 deletions
6
cueglow-server/src/main/kotlin/org/cueglow/server/StateProvider.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,15 @@ | ||
package org.cueglow.server | ||
|
||
import org.cueglow.server.json.JsonHandler | ||
import org.cueglow.server.objects.messages.GlowMessage | ||
import org.cueglow.server.patch.Patch | ||
import java.util.concurrent.BlockingQueue | ||
|
||
/** | ||
* Provides a collection of state objects | ||
* | ||
* The StateProvider is initialized by the main process and passed to e.g. a [JsonHandler] for mutation. | ||
*/ | ||
class StateProvider { | ||
val patch = Patch() | ||
class StateProvider(val outEventQueue: BlockingQueue<GlowMessage>) { | ||
val patch = Patch(outEventQueue) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
cueglow-server/src/main/kotlin/org/cueglow/server/json/JsonSubscriptionHandler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package org.cueglow.server.json | ||
|
||
import org.cueglow.server.objects.messages.GlowMessage | ||
import org.cueglow.server.objects.messages.SubscriptionHandler | ||
|
||
class JsonSubscriptionHandler: SubscriptionHandler() { | ||
override fun serializeMessage(glowMessage: GlowMessage): String = glowMessage.toJsonString() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
cueglow-server/src/main/kotlin/org/cueglow/server/objects/messages/GlowTopic.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package org.cueglow.server.objects.messages | ||
|
||
enum class GlowTopic(val string: String) { | ||
PATCH("patch"),; | ||
|
||
override fun toString() = string | ||
|
||
companion object { | ||
// lookup topic by topic string | ||
private val map = values().associateBy(GlowTopic::string) | ||
fun fromString(string: String) = map[string] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
97 changes: 97 additions & 0 deletions
97
cueglow-server/src/main/kotlin/org/cueglow/server/objects/messages/SubscriptionHandler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package org.cueglow.server.objects.messages | ||
|
||
import org.apache.logging.log4j.kotlin.Logging | ||
import org.cueglow.server.OutEventReceiver | ||
import org.cueglow.server.StateProvider | ||
import org.cueglow.server.json.AsyncClient | ||
import java.util.* | ||
|
||
/** | ||
* Handles Subscribe/Unsubscribe Events. Receives OutEvents from the OutEventHandler and sends them to the subscribers. | ||
**/ | ||
abstract class SubscriptionHandler: OutEventReceiver, Logging { | ||
private val activeSubscriptions = EnumMap<GlowTopic, MutableSet<AsyncClient>>(GlowTopic::class.java) // TODO synchronize (see JavaDoc for EnumMap) | ||
|
||
/** Keeps subscriptions that were sent the initial state but do not get updates yet because older updates | ||
* in the OutEventQueue first need to be handled. Subscriptions move from pending to active once the sync message | ||
* (identified by UUID) is received by the SubscriptionHandler. | ||
**/ | ||
private val pendingSubscriptions = mutableMapOf<UUID, Pair<GlowTopic, AsyncClient>>() | ||
|
||
init { | ||
// populate subscriptions with empty sets | ||
GlowTopic.values().forEach { activeSubscriptions[it] = mutableSetOf() } | ||
} | ||
|
||
abstract fun serializeMessage(glowMessage: GlowMessage): String | ||
|
||
/** Receive and handle messages from the OutEventQueue **/ | ||
override fun receive(glowMessage: GlowMessage) { | ||
logger.info("Receiving $glowMessage") | ||
|
||
when (glowMessage.event) { | ||
GlowEvent.ADD_FIXTURES, GlowEvent.UPDATE_FIXTURES, | ||
GlowEvent.REMOVE_FIXTURES, GlowEvent.ADD_FIXTURE_TYPES, | ||
GlowEvent.REMOVE_FIXTURE_TYPES -> publish(GlowTopic.PATCH, glowMessage) | ||
|
||
GlowEvent.SYNC -> activateSubscription((glowMessage as GlowMessage.Sync).data) | ||
|
||
else -> return | ||
} | ||
} | ||
|
||
private fun publish(topic: GlowTopic, glowMessage: GlowMessage) { | ||
val topicSubscribers = activeSubscriptions[topic] | ||
if (topicSubscribers!!.isNotEmpty()) { // null asserted because all possible keys are initialized in init block | ||
val messageString = serializeMessage(glowMessage) | ||
topicSubscribers.forEach {it.send(messageString)} | ||
} | ||
} | ||
|
||
/** Check if [syncUuid] is known. If yes, move subscription from pending to active **/ | ||
private fun activateSubscription(syncUuid: UUID) { | ||
val (topic, subscriber) = pendingSubscriptions.remove(syncUuid) ?: return | ||
activeSubscriptions[topic]!!.add(subscriber) // null asserted because all possible keys are initialized in init block | ||
} | ||
|
||
fun subscribe(subscriber: AsyncClient, topic: GlowTopic, state: StateProvider) { | ||
// unsubscribe before subscribing | ||
if (internalUnsubscribe(subscriber, topic)) {logger.warn("Client $subscriber subscribed to $topic but was already subscribed. Subscription was reset. ")} | ||
when (topic) { | ||
GlowTopic.PATCH -> { | ||
val syncUuid = UUID.randomUUID() | ||
val syncMessage = GlowMessage.Sync(syncUuid) | ||
pendingSubscriptions[syncUuid] = Pair(GlowTopic.PATCH, subscriber) | ||
// TODO acquire state lock here | ||
val initialPatchState = state.patch.getGlowPatch() | ||
state.outEventQueue.put(syncMessage) // TODO possible deadlock because SubscriptionHandler is locked and cannot work to reduce message count in queue | ||
// no deadlock problem if we don't have the SubscriptionHandler Lock here? | ||
// TODO release state lock here | ||
val initialMessage = GlowMessage.PatchInitialState(initialPatchState) | ||
subscriber.send(initialMessage) | ||
} | ||
} | ||
} | ||
|
||
fun unsubscribe(subscriber: AsyncClient, topic: GlowTopic) { | ||
if (!internalUnsubscribe(subscriber, topic)) {logger.warn("Client $subscriber unsubscribed from $topic but was not subscribed")} | ||
} | ||
|
||
/** Returns true if the subscriber was successfully unsubscribed and false if the subscriber wasn't subscribed */ | ||
private fun internalUnsubscribe(subscriber: AsyncClient, topic: GlowTopic): Boolean { | ||
val numberOfSubscriptionsRemovedFromPending = pendingSubscriptions | ||
.filter {it.value.first == topic && it.value.second == subscriber} | ||
.keys | ||
.map {pendingSubscriptions.remove(it)} | ||
.size | ||
val removedFromActive = activeSubscriptions[topic]!!.remove(subscriber) // null asserted because all possible keys are initialized in init block | ||
return removedFromActive || numberOfSubscriptionsRemovedFromPending > 0 | ||
} | ||
|
||
fun unsubscribeFromAllTopics(subscriber: AsyncClient) { | ||
pendingSubscriptions.filter {it.value.second == subscriber}.keys.map { pendingSubscriptions.remove(it) } | ||
activeSubscriptions.values.forEach { | ||
it.remove(subscriber) | ||
} | ||
} | ||
} |
Oops, something went wrong.