Blog
Implementing efficient monitoring of long running operations with OSGi R7 Push Stream and Server Sent Events
Providing users of your application with feedback regarding long running operations used to require utilizing pull based solutions such as polling. With Server Sent Events and OSGi R7 Push Stream1, a far more efficient approach is possible, where its the server which pushes such status updates onto GUI, instead of having a background JavaScript worker slamming your server with requests for status every few seconds.
Supplemented with OSGi R7 Promises, which I talked about in my most recent article “Implementing asynchronous processing with OSGi R7 Promises”, your users will not only be able to use your application in a non-blocking way, but they will be informed of each step that happens in real-time, without the need to abuse your infrastructure.
As with many other OSGi R7 specifications, it is very difficult or impossible to find in public repositories actual applications utilizing these technologies. Therefore, in this article I will be sharing more code and implementation tips, based on a complete application available for cloning and deploying yourself–all steps are documented https://github.com/ideas-into-software/automated-linguistic-analysis.
1. The service-status-impl module https://github.com/ideas-into-software/automated-linguistic-analysis/tree/master/service-status-impl, rest-status module httphttps://github.com/ideas-into-software/automated-linguistic-analysis/tree/master/rest-status and rest-common module https://github.com/ideas-into-software/automated-linguistic-analysis/tree/master/rest-common contain all the code referred to in this article.
2. Starting with the service layer–having the DAO layer and other mundane parts obviously working already–in the service-status-impl module POM we pull in the camel-core dependency required for accessing Camel context and using consumer templates, i.e.:
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>${camel.version}</version>
</dependency>
3. In the same service-status-impl module https://github.com/ideas-into-software/automated-linguistic-analysis/tree/master/service-status-impl you will find the software.into.ala.service.status.impl.StatusUpdatesServiceImpl
class responsible for pushing events received onto Push Stream instance.
4. Here, we have the StatusUpdatesConsumer
private class implemented as Runnable
, which uses Camel’s consumer template to receive messages from topic pertaining to particular file being processed and pushes these onto event source, i.e.:
try {
ConsumerTemplate consumerTemplate = getCamelContext().createConsumerTemplate();
Endpoint endpoint = messagingService.registerStatusUpdatesEndpoint(fileId);
FileMessageDTO fmDTO = consumerTemplate.receiveBody(endpoint, FileMessageDTO.class);
while (fmDTO != null) {
simplePushEventSource.publish(fmDTO);
if (isProcessingFinished(fmDTO)) {
break;
}
fmDTO = consumerTemplate.receiveBody(endpoint, FileMessageDTO.class);
}
consumerTemplate.cleanUp();
simplePushEventSource.endOfStream();
} catch (Exception e) {
simplePushEventSource.error(e);
}
5. In the same software.into.ala.service.status.impl.StatusUpdatesServiceImpl
class we have the software.into.ala.service.status.impl.StatusUpdatesServiceImpl.getStatusUpdates(String)
method which starts a new instance of StatusUpdatesConsumer
to receive status updates for particular file being processed, then creates and returns a push based stream of processing status updates obtained from push event source, i.e.:
public PushStream<String> getStatusUpdates(String fileId) {
Objects.requireNonNull(fileId, "File ID must be specified!");
fileProcessingStatusConsumerExec.execute(new StatusUpdatesConsumer(fileId));
return pushStreamProvider.createStream(simplePushEventSource).map(fmDTO -> fmDTO.status);
}
6. Now, we move to the rest-status module https://github.com/ideas-into-software/automated-linguistic-analysis/tree/master/rest-status which contains software.into.ala.rest.status.StatusUpdatesRestController
7. Here, in software.into.ala.rest.status.StatusUpdatesRestController.getStatusUpdates(SseEventSink, String)
method, which produces Server Sent Events, we call the software.into.ala.service.status.StatusUpdatesService.getStatusUpdates(String)
method in service layer we just discussed in detail to receive status update push notifications, registering method to execute for each event received as well as two callback methods for failure and resolved scenarios, i.e.:
PushStream<String> fPushStream = statusUpdateService.getStatusUpdates(fileId);
fPushStream.forEach(this::deliverEvent).onFailure(this::failure).onResolve(this::resolved);
8. The action being executed for each event received is handled by the software.into.ala.rest.status.StatusUpdatesRestController.deliverEvent(String)
method, which simply wraps each event in OutboundSseEvent
and sends it via the javax.ws.rs.sse.SseEventSink.send(OutboundSseEvent)
method
9. The two callback methods – software.into.ala.rest.status.StatusUpdatesRestController.failure(Throwable)
for handling failures and software.into.ala.rest.status.StatusUpdatesRestController.resolved()
called when Promise is resolved–are very similar in that they also wrap these different types of events in OutboundSseEvent
and send them via javax.ws.rs.sse.SseEventSink.send(OutboundSseEvent)
method
10. Finally, our GUI resides in rest-common module https://github.com/ideas-into-software/automated-linguistic-analysis/tree/master/rest-common and it’s here that we use JavaScript’s EventSource2, wrapped in the jQuery Plugin for Server-Sent Events (SSE)3. Therefore, we include the jQuery and jQuery SSE dependencies as well as our custom JavaScript contained in ala.js
, where we have the monitorStatus(fileId)
method responsible for receiving each of these events and passing them to displayStatusMessage
method to display each event in GUI for user’s edification and amusement.
-
“OSGi Enterprise R7 Push Stream Specification“ https://osgi.org/specification/osgi.enterprise/7.0.0/util.pushstream.html ↩
-
“EventSource“ https://developer.mozilla.org/en-US/docs/Web/API/EventSource ↩
-
“jQuery SSE - jQuery Plugin for Server-Sent Events (SSE) EventSource Polyfill“ https://github.com/byjg/jquery-sse ↩