ââIn July 2016, our team of 4 UTM students, both graduate and undergraduate began our internship program at ISD. After we received a warm welcome from our mentors Dumitru and Dan, and afterwards from the whole company, we were presented with the general plan and the requirements of our project.
ââThe program was meant to simulate the process of development of a product, we were to work in the Agile Scrum environment, and our mentors were meant to play the role of PO’s (besides giving us the necessary technical guidance, of course). Afterwards we were also presented with a POC. Right from the beginning we have understood the fineness of the experience we were yet to receive in the following 4 weeks. This immersion into the actual process of development is especially important for the students yet familiar only with the theoretical aspects of programming. Eagerly we started with analyzing what exactly should we develop.
ââThe application we were to build was a Warehouse Visualization Control Tool (WVCT for short), which as the name implies, has the primary goal of displaying the warehouse layout in 2D and 3D. The general requirements we had received at the beginning were the following:
- The data needs to be displayed in real time and it should be loaded from a database;
- The client side of the application should be implemented in JavaScript;
- The server side should be written in Java;
- The main milestone, which proved to be a very interesting challenge, was that this application should be able to handle 20.000 locations displayed at an average rate above 30 frames per second.
ââA lot of the technologies required for this project were new to us, which meant that we needed to plan and divide efficiently the learning of unknown areas. Since we were a team now, instead of just intern colleagues, we were able to easily maintain a strong spirit of cooperation, which in the end proved to be one of the key factors in our whole experience.
ââAt first we divided ourselves into pairs which worked on either client or server side. Since the client one presented to be the part which required a lot of additional learning, as nobody had experience in JS, we expected it to develop slower than the other side. That is why it was decided that the colleagues which were more experienced in Java to work on back-end, and once itâs up and running all 4 of us would concentrate on the issues left with client side (or the app in general).
Client Side
ââThe technologies we have used for the client side are the following:
- ThreeJS – a JavaScript 3D library which uses WebGL renderer, was used for the warehouse graphics;
- JQuery/Jquery UI – were used for navigation and control elements in the app such as the search menu and the dialog box;
- Dtree – which we used to display the child transport units treeview in one of our dialogs;
- Dat.GUI – a very handy library used to easily manipulate JavaScript variables, we used it to display our main menu.
ââThe structure of our client module is quite simple, below are listed the directories and their description:
- scripts/ – contains the JS files that implement the app logic – the communication with the server, fetching the data and rendering the output;
- libs/ – contains the libraries mentioned above;
- images/ – various textures and images that are used for the UI purposes;
- css/ – some style sheets like jQuery UI stuff, Dat.GUI theme and our custom styles.
ââSome additional description of one of the more important .js files used in the app:
- webSocket.js – this script ensures the communication with the server module through the WebSocket protocol, fetches the data like the configuration file, the location list, the subarea list, the movement list. Here, we also receive the notifications concerning the new movements and the stock changes on locations;
- main.js – this is one of the most important files, other than webSocket.js. This file initializes the Three.js library, creates the scene, the renderer, sets up the Dat.GUI library. It also contains the camera switching logic, the navigation logic in the perspective mode, fps counter, location drawing logic and window resizing listeners;
- settingsInteractors.js – when the webSocket.js script receives the configuration file from the server, it calls a callback from this script. Here we parse the json configuration file and apply most settings. Few settings are applied in the main.js file;
- subAreaPainter.js – this script contains all the subarea drawing logic;
- locationSelectionHandler.js – this script contains the location-selection logic. Also it creates the popup dialog box and creates its content;
- SearchBox.js – a large file which implements a lot of functionality. It contains all the search box logic, like movement searching and highlighting, location searching and highlighting,
goToLocation
button logic, which moves the camera smoothly to a location; - preloader.js – this file contains some logic used by the splash-screen, specifically, the actions that are taken when the user clicks on the button on the splash screen;
- movementArrowPainter.js – this script contains the arrow-drawing logic for movements between two locations.
Server Side
ââFor this part of the application we had an even larger specter of technologies to chose from, as the only requirement was for Java language to be used. As in the case of client part, we took the opportunity to learn new technologies, so we chose the ones in which we were interested to use for a while.
ââWe agreed to use JMS, Apache ActiveMQ, WebSocket API, Jackson, Apache Commons DBCP.
JMS
ââJava Message Service (JMS) is an application program interface that supports the formal communication known as messaging between computers in a network. In general JMS supports two different ways of using messaging between a producer and consumer. These ways are PTP (Point-to-Point) and Pub/Sub (Publish/Subscribe), difference between them are that in the Pub/Sub way a message can be delivered to multiple recipients.
ââWe used JMS in order to receive messages from IntherLC regarding the new movements which appeared in the system, or if a location of a stock was changed.
ââWe considered that choosing the model Pub/Sub was the best option, since in this case we will be able to increase the number of subscribers to IntherLC in future.
[code language=”java”]
public class WarehouseChangeMessageNotifier {
private TopicConnection connection;
private TopicSession session;
private WarehouseListener warehouseListener;
public WarehouseChangeMessageNotifier(WarehouseListener warehouseListener) {
this.warehouseListener = warehouseListener;
try {
Context ctx = new InitialContext();
TopicConnectionFactory cf = (TopicConnectionFactory) ctx.lookup("java:/comp/env/jms/ConnectionFactory");
connection = cf.createTopicConnection();
session = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
connection.start();
setUpMessageNotifiers();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void setUpMessageNotifiers() {
new MovementMessageNotifier(warehouseListener, session);
new LocationChangeMessageNotifier(warehouseListener, session);
}
public void dispose() {
try {
connection.close();
} catch (JMSException e) {
throw new RuntimeException(e);
}
}
}
public class LocationChangeMessageNotifier implements MessageListener {
private Topic locationTopic;
private TopicSubscriber locationSubscriber;
private WarehouseListener warehouseListener;
public LocationChangeMessageNotifier(WarehouseListener warehouseListener, TopicSession session) {
this.warehouseListener = warehouseListener;
try {
Context ctx = new InitialContext();
locationTopic = (Topic) ctx.lookup("java:/comp/env/jms/locationsTopic");
locationSubscriber = session.createSubscriber(locationTopic);
locationSubscriber.setMessageListener(this);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void onMessage(Message message) {
TextMessage locationMessage = (TextMessage) message;
try {
warehouseListener.onLocationChange(locationMessage.getText());
} catch (JMSException e) {
throw new RuntimeException(e);
}
}
}
public class MovementMessageNotifier implements MessageListener {
private Topic movementTopic;
private TopicSubscriber movementSubscriber;
private WarehouseListener warehouseListener;
public MovementMessageNotifier(WarehouseListener warehouseListener, TopicSession session) {
this.warehouseListener = warehouseListener;
try {
Context ctx = new InitialContext();
movementTopic = (Topic) ctx.lookup("java:/comp/env/jms/movementsTopic");
movementSubscriber = session.createSubscriber(movementTopic);
movementSubscriber.setMessageListener(this);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void onMessage(Message message) {
TextMessage movementMessage = (TextMessage) message;
try {
warehouseListener.onMovement(movementMessage.getText());
} catch (JMSException e) {
throw new RuntimeException(e);
}
}
}
[/code]
ââAbove is presented our Receiver. At the beginning we create a start connection and a topic, after this we call the setUpMessageNotifiers()
method, where we create a topicSubscriber
. Here we also have the listeners and in the method onMessage()
we process our messages.
Apache ActiveMQ
ââSince we canât receive messages directly from IntherLC, we needed to find a provider which would send us this data. We chose to use Apache ActiveMQ JMS provider, because it is the most popular provider used at the moment.
WebSocket API
ââWebSockets is an advanced technology that makes possible to open an interactive communication session between the user’s browser and a server. In our project it played a crucial part, because through it we could send messages to the server and receive event-driven responses without having to pool the server for a reply.
ââFirst, we created the class WebSockerServerEndPoint where we process the next events: onOpen()
, onClose()
, onMessage()
and onError()
.
[code language=”java”]
@OnOpen
public void onOpen(Session session) {
peers.add(session);
logger.info("Opening the connection: " + session.getId());
}
@OnClose
public void onClose(Session session) {
peers.remove(session);
movementNotifier.dispose();
logger.info("Closing the connection: " + session.getId());
}
@OnError
public void onError(Session session, Throwable error) {
logger.error(session.getId(), error);
}
@OnMessage
public void onMessage(Session session, ClientRequest request) {
if (request.isGetAllWarehouseLocations())
sendResponse(session, warehouseService.getAllWarehouseLocations());
else if (request.isGetAllSubareas())
sendResponse(session, warehouseService.getAllSubareas());
else if (request.isGetWarehouseSettings())
sendResponse(session, warehouseService.getWarehouseSettings());
else if (request.isGetAllMovements())
sendResponse(session, warehouseService.getAllMovements());
}
[/code]
ââOn our Client Side we created a JavaScript file where we make connections with our EndPoint and we also process the messages: onOpen()
, onClose()
, onMessage()
and onError()
.
[code language=”java”]
$(document).ready(function() {
var webSocket = new WebSocket(WEBSOCKET_SERVER_ENDPOINT);
checkWebSocketConnection(webSocket);
var serverResponseHandlerMap = setUpServerResponseHandlerMap();
webSocket.onopen = function(event) {
if (webSocket.readyState == WebSocket.OPEN) {
webSocket.send(JSON.stringify({
getWarehouseSettings: true
}));
}
};
webSocket.onmessage = function(event) {
var serverResponse = JSON.parse(event.data);
var responseHandlerKey = getServerResponseHandlerKey(serverResponse);
var reponseHandler = serverResponseHandlerMap.get(responseHandlerKey);
if (reponseHandler)
reponseHandler.apply(this, [serverResponse, webSocket]);
};
});
[/code]
ââThis function will be called everytime the page is loaded, which means at every refresh we will have in our application, we also get the actual database situation.
Jackson
ââJackson is a library which is mostly used to convert Java Object to JSON format. We used it to convert our data, which is fetched from database to a JSON structure used by our client.
[code language=”java”]
public <T> String convertObjectToJson(T object) {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(object);
} catch (Exception e) {
logger.error("Can’t convert object to JSON", e);
throw new RuntimeException(e);
}
}
public String buildResponseObject(List<?> list, String propertyName) {
try {
ObjectMapper mapper = new ObjectMapper();
ObjectNode objectNode = mapper.createObjectNode();
ArrayNode arrayNode = mapper.createArrayNode();
list.stream().forEach(e -> arrayNode.addPOJO(e));
objectNode.set(propertyName, arrayNode);
return mapper.writeValueAsString(objectNode);
} catch (JsonProcessingException e) {
logger.error("Can’t convert list to JSON", e);
throw new RuntimeException(e);
}
}
@Override
public String serializeMovement(Movement movement) {
return converter.convertObjectToJson(movement);
}
@Override
public String serializeMovementList(List<Movement> movementList) {
return buildAllMovementListResponse(movementList);
}
private String buildAllMovementListResponse(List<?> movementList) {
return converter.buildResponseObject(movementList, "movementList");
}
[/code]
ââBasically we have two methods which we use to convert our object in JSON files, after our object was serialized we call the method buildResponseObject()
and create our file.
Apache Commons DBCP
ââOur reason to use DBCP was to improve the performance of executing commands on the database. So instead of one connection to the database we create a pool of connections. Another positive aspect is that the connections can be reused when future requests to the database are required.
Final result
ââWith our teamâs effort and our mentorâs guidance we managed to complete the requirements set in the beginning. The whole process and experience gained during this month of internship, in the end, was also rewarded with a sense of achievement. And what gave this sensation even more meaning is that we could see every bit of progress and the steps we made to get closer to a new level. All of us became significantly closer to being the programmers we wanted.