Real-time Applications with AngularJS and Java – Part 2
1. Introduction
As the title of this article implies, this is the second part of how to create a real-time application using AngularJS and Java. The first part showed how to automatically refresh a page content using the periodic refresh AJAX design pattern. Here, I will show and explain the concept behind long polling.
If you have not, I would suggest that you read and try the example of part 1. I will use the same skeleton application and modify it, it’s important that you know the basic concepts of the part 1 as I will not explain them again here.
Moreover, a basic knowledge of AngularJS and Spring is important as I will not explain how to set your workspace up, nor will I explain how they interact with each other.
AngularJS Programming Cookbook
In this ebook, we provide a compilation of AngularJS based examples that will help you kick-start your own web projects. We cover a wide range of topics, from Single Page Apps and Routing, to Data Binding and JSON Fetching. With our straightforward tutorials, you will be able to get your own projects up and running in minimum time. Download the cookbook by joining the Web Code Geeks Newsletter.
2. Long Polling
Long polling is a concept used to emulate the server push (CometD, Bayeux, Atmosphere, WebSocket, etc.). Basically, the client starts an XMLHttpRequest
with the server using Ajax. The server then accepts the request and check for updated information to send to the client. If the server does not find any new data, it loops until it finds or until a fixed amount of time to avoid infinite loops or client connection timeout.
At the time of writing this article, Facebook uses Long Polling to update the UI with new information. Using Google Chrome or any new browser’s network analyzer, you can see it in action. Go to your Facebook home page and hit F12
. Go to the network tab and filter to show only XHR. You’ll see that a request is sent to the server through a specific pull channel and stays in the Pending
state for a little while, then the request is completed, a new one is started and so on.
The main advantage of this method vs the periodic refresh pattern is that we reduce quite a lot the number of requests sent to the server. On the other hand, this uses and holds a thread from the server’s thread pool which could potentially run out of free threads. That means a user would get locked out of the system until a thread is freed, but this is not a show stopper if the server is properly configured or if you have load balancing on different instances.
3. The RESTful JSON Java Back-end
3.1. The new Task object status
As I said in the introduction, I will modify the example of part 1 in which the Task
object had a duration that was decremented by a thread every second or so. That meant the data was actually changing quite often, so the periodic refresh was a good solution to display those changes to the client. We simply set the refresh rate at 1 second and it appeared to be real-time. Regarding the Long Polling, it would not make much sense to have the data updated that often. What we want to emulate is the server telling the client: “Hold on, I will send you data once I got something new for you.“. The data has to be unpredictably updated to see the long polling in action. To implement that, I will add a new TaskStatus
that a Task
can be in that is CREATED
.
TaskStatus.java
public enum TaskStatus { CREATED, IDLE, RUNNING, SUCCESS; }
3.2. The Task object
The new version of the Task
object need to be instantiated with the new status by default, meaning that all new Task
s will be created with the CREATED
status.
Task.java
public class Task { private TaskStatus status = TaskStatus.CREATED; private long duration; public TaskStatus getStatus() { return status; } public void setStatus(TaskStatus status) { this.status = status; } public long getDuration() { return duration; } public void setDuration(long duration) { this.duration = duration; } public void decrementDuration() { this.duration--; } public boolean isRunning() { return this.status.equals(TaskStatus.RUNNING); } public String getName() { return this.toString(); } public void start() { this.status = TaskStatus.RUNNING; } }
3.3. The TaskCreator
To emulate users creating new Task
s, I created a TaskCreator
object that randomly creates a new Task
with the status CREATED
. The point is that, unlike the previous example of part 1, I will query only for new information instead of the whole thing. That obviously will reduce the amount of data transferred over the network.
TaskCreator.java
@Component @Scope("singleton") public class TaskCreator { private static final int MAX_TASK_DURATION = 5000; private static final int MAX_TASK_CREATION_INTERVAL = 10000; private static final Random RANDOMIZER = new Random(); @Autowired private TaskExecutor executor; public void start() { Runnable taskPoolConsumer = () -> { synchronized (executor) { while (true) { try { Task newTask = new Task(); newTask.setStatus(TaskStatus.CREATED); newTask.setDuration(RANDOMIZER.nextInt(MAX_TASK_DURATION)); this.executor.addTask(newTask); this.executor.wait(RANDOMIZER.nextInt(MAX_TASK_CREATION_INTERVAL)); } catch (Exception e) { e.printStackTrace(); } } } }; new Thread(taskPoolConsumer).start(); } }
3.4. The TaskExecutor
As I said above, we want to improve the application so it only returns Task
objects that have changed. A Task
will be considered as changed if it’s either new or if its status has changed since the last time it was queried. For simplicity’s sake, this example will work only for one user. You could, like Facebook does, have a channel opened for each user and compute the delta between what is in the UI and what is in the back-end. To compute the delta in this example, I will simply keep a second list of Task in which will be added Task that were started or completed. This deals pretty badly with concurrency, but again, for simplicity’s sake, I decided that this was enough to show the concept.
TaskExecutor.java
@Component @Scope("singleton") public class TaskExecutor { private List pool = new LinkedList<>(); private Set updatedTaskPool = new HashSet<>(); @PostConstruct public void initialize() { Runnable taskPoolConsumer = () -> { synchronized(this) { while (true) { try { this.pool.stream() .filter(task -> task.isRunning() && task.getDuration() > 0) .forEach(task -> { task.decrementDuration(); }); this.pool.stream() .filter(task -> task.isRunning() && task.getDuration() == 0) .forEach(task -> { task.setStatus(TaskStatus.SUCCESS); this.updatedTaskPool.add(task); }); this.wait(1000); } catch (Exception e) { e.printStackTrace(); } } } }; new Thread(taskPoolConsumer).start(); } public synchronized List getUpdatedTasks() { List updatedTasks = new LinkedList<>(); updatedTasks.addAll(this.pool.stream() .filter(task -> task.getStatus().equals(TaskStatus.CREATED)) .collect(Collectors.toList())); updatedTasks.addAll(this.updatedTaskPool); this.changeCreatedStatusToIdle(); this.updatedTaskPool.clear(); return updatedTasks; } private void changeCreatedStatusToIdle() { this.pool.stream() .filter(task -> task.getStatus().equals(TaskStatus.CREATED)) .forEach(task -> task.setStatus(TaskStatus.IDLE)); } public synchronized void startAllTasks() throws InterruptedException { this.pool.stream() .filter(task -> task.getStatus().equals(TaskStatus.IDLE)) .forEach(task -> { task.start(); this.updatedTaskPool.add(task); }); } public List getPool() { this.changeCreatedStatusToIdle(); return this.pool; } public void addTask(Task taskToAdd) { this.pool.add(taskToAdd); } }
3.5. TaskService
In our TaskService
, we want to inject the new TaskCreator
singleton and start it at the initialization. Then, we want to create a new mapping for our RestController
that is to make the distinction between the function that returns all Task
and the one that returns only updated information. That last one will implement the loop necessary for long polling.
TaskService.java
@RestController @RequestMapping("/api/task") public class TaskService { @Autowired private TaskExecutor taskExecutor; @Autowired private TaskCreator taskCreator; @PostConstruct public void initialize() { this.taskCreator.start(); } @RequestMapping(path = "/all", method = RequestMethod.GET) public List getTasks() { return this.taskExecutor.getPool(); } @RequestMapping(method = RequestMethod.GET) public List getUpdatedTasks() { List updatedTasks = null; // Fetch updated task until there is one or more do { updatedTasks = this.taskExecutor.getUpdatedTasks(); } while (updatedTasks.size() == 0); return updatedTasks; } @RequestMapping(method = RequestMethod.POST) public void addTask(@RequestBody Task taskToAdd) { this.taskExecutor.addTask(taskToAdd); } public void startIdleTasks() throws InterruptedException { this.taskExecutor.startAllTasks(); } }
As you can see, I did not implement the loop break condition on a maximum waiting time. You could also add a Thread.sleep()
to reduce the number of calls to getUpdatedTasks()
of the TaskExecutor
if necessary.
4. Front-end implementation with AngularJS
The front-end part also changes a little bit. First, we want to separate the function that returns all Task
s and the function that returns only the updated Task
s. That last one will be a recursive function calling itself when data has arrived through the channel or if the server replies with an error message. Then we either push the Task
received in the Array
of Task
s if the status is IDLE
as the TaskExecutor
changes status from CREATED
to IDLE
before sending them to the client or we try and find the existing Task
to update its status if the status is different from IDLE
(either RUNNING
or SUCCESS
).
index.xhtml
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"> <h:head> <title>Real-time applications - Part 1 - Java Code Geeks</title> <link rel="stylesheet" href="https://examples.javacodegeeks.com/wp-content/litespeed/localres/aHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS8=bootstrap/3.3.5/css/bootstrap.min.css"/> <script src="https://examples.javacodegeeks.com/wp-content/litespeed/localres/aHR0cHM6Ly9jZG5qcy5jbG91ZGZsYXJlLmNvbS8=ajax/libs/angular.js/1.4.5/angular.min.js"></script> <script> var part1 = angular.module("part1", []); part1.controller("RealtimeCtrl", function($scope, $http, $timeout) { $scope.addTask = function() { $http.post("api/task", $scope.task); } $scope.getTasks = function() { $http.get("api/task/all") .success(function(data) { $scope.tasks = data; }); } $scope.getUpdatedTasks = function() { $http.get("api/task") .success(function(data) { data.forEach(function(currentTask) { if (currentTask.status === 'IDLE') { $scope.tasks.push(currentTask); } else { $scope.tasks.forEach(function(taskToBeUpdated) { if (taskToBeUpdated.name === currentTask.name) { taskToBeUpdated.status = currentTask.status; taskToBeUpdated.running = currentTask.status === 'RUNNING'; } }); } }); // Recursive of Long Polling on success. $scope.getUpdatedTasks(); }).error(function() { // Recursive of Long Polling on error. $scope.getUpdatedTasks(); }); } $scope.activateRealtime = function() { $scope.getUpdatedTasks(); } $scope.getTasks(); }); </script> </h:head> <h:body> <div ng-app="part1" ng-controller="RealtimeCtrl" class="container"> <h1>Real-time application <SMALL>part 2</SMALL></h1> <h2>Add task</h2> <h:form> <label for="durationField">Duration (in seconds):</label> <input type="number" id="durationField" class="form-control" ng-model="task.duration"/> <button type="button" ng-click="addTask()" class="btn btn-success">Add task</button> <button type="button" ng-click="getTasks()" class="btn btn-default">Refresh Tasks</button> <button type="button" ng-click="activateRealtime()" class="btn btn-default">Activate Auto Refresh</button> <h:commandButton actionListener="#{taskController.startTasks}" styleClass="btn btn-default" value="Start Idle Tasks"> <f:ajax execute="@form"/> </h:commandButton> </h:form> <h2>Listing</h2> <ul class="list-group"> <li ng-repeat="curTask in tasks" class="list-group-item {{curTask.running ? 'active' : ''}}"> {{curTask.name}} ({{curTask.status}})<span class="badge">{{curTask.duration}}</span> </li> </ul> </div> </h:body> </html>
5. What’s next?
As you can see, it’s a little bit more complex to implement in comparison with the periodic refresh AJAX pattern, but we get a better feel of real-time. As the back-end loops and hangs the thread for a couple of seconds until it has found new data, the notification of the update seems to come from the server in real-time. Now, the above example is not the perfect implementation of long polling. It has many flaws compared to Facebook’s implementation, but for demonstration purposes, I think it does the job.
In the next part of this article, I will show you the new HTML 5 WebSocket and how this same application here can be improved to get the Task
through a socket opened with the server.
6. Download the Eclipse project
This was an example of how to integrate AngularJS and Spring MVC to create an application that is updated automatically using Long Polling.
You can download the full source code of this example here: Long Polling
Simply encouraging, though I have not run this appli, but I want to go further