Skip to content

LCO Probe : Probes the LANSA LCO listener to establish if the listener is active or not

Notifications You must be signed in to change notification settings

OsvaldoMartini/LCO_Probe

Repository files navigation

Software

Before proceeding, please ensure you have the following software installed on your computer.

  • Node

  • Yarn (optional but recommended)

  • Git command line tools

Installation

Please fork a copy of this repository. Forking a repository allows you to freely experiment with changes without affecting the original project. Alternatively download or clone the master branch.

  1. Clone the repo to your machine

  git clone <CloneURL>
  1. Within terminal or cmd ensure you have changed directory (into the new folder that has been cloned) and install the dependencies

  cd <new-dir>
  yarn install OR npm install
  1. Before we can build, run or deploy our app it is important to ensure that the 'webConfig.json' is configured for our environment. Please change 'siteURL' to point to either your local or live url.

    {
    "siteURL": "http://localhost:12000", ...

    OR

    {
    "siteURL": "http://mydomain.com", ...
  1. You must build the app before you can run it

  yarn run build OR npm run build
  1. Run your build

  yarn build OR npm run build
  1. Run your Java with React embeded # Obs: # Verify the LCOProbe.pptx to execute the Java LCO Probe

  1) Single Page App Monitoring Mode
  java -jar target/LCOProbe-RealTime.jar

  2) One Time Call mode
  java -jar target/LCOProbe-RealTime.jar <host> <user> <password> <timeout>

  3) Specific Profile mode
  java -jar target/LCOProbe-RealTime.jar <host> <user> <password> <timeout> <profile>

This should launch the application and start running on: http://localhost:12000/

Notes

For specific versions of dependencies being used please reference the 'package.json' file within the main project directory.

Configuring WebSockets with Spring

Spring comes with powerful WebSocket support. One thing to recognize is that a WebSocket is a very low-level protocol. It does little more than offer the means to transmit data between client and server. The recommendation is to use a sub-protocol (STOMP for this section) to actually encode data and routes.

The following code configures WebSocket support on the server side:

link:src/main/java/com/greglturnquist/payroll/WebSocketConfiguration.java[role=include]
  1. @EnableWebSocketMessageBroker turns on WebSocket support.

  2. WebSocketMessageBrokerConfigurer provides a convenient base class to configure basic features.

  3. MESSAGE_PREFIX is the prefix you will prepend to every message’s route.

  4. registerStompEndpoints() is used to configure the endpoint on the backend for clients and server to link (/payroll).

  5. configureMessageBroker() is used to configure the broker used to relay messages between server and client.

With this configuration, you can now tap into Spring Data REST events and publish them over a WebSocket.

Subscribing to Spring Data REST Events

Spring Data REST generates several application events based on actions occurring on the repositories. The following code shows how to subscribe to some of these events:

link:src/main/java/com/greglturnquist/probe/EventHandler.java[role=include]
  1. @RepositoryEventHandler(Employee.class) flags this class to trap events based on employees.

  2. SimpMessagingTemplate and EntityLinks are autowired from the application context.

  3. The @HandleXYZ annotations flag the methods that need to listen to events. These methods must be public.

Each of these handler methods invokes SimpMessagingTemplate.convertAndSend() to transmit a message over the WebSocket. This is a pub-sub approach so that one message is relayed to every attached consumer.

The route of each message is different, allowing multiple messages to be sent to distinct receivers on the client while needing only one open WebSocket — a resource-efficient approach.

getPath() uses Spring Data REST’s EntityLinks to look up the path for a given class type and id. To serve the client’s needs, this Link object is converted to a Java URI with its path extracted.

Note
EntityLinks comes with several utility methods to programmatically find the paths of various resources, whether single or for collections.

In essence, you are listening for create, update, and delete events, and, after they are completed, sending notice of them to all clients. You can also intercept such operations BEFORE they happen, and perhaps log them, block them for some reason, or decorate the domain objects with extra information. (In the next section, we will see a handy use for this.)

Configuring a JavaScript WebSocket

The next step is to write some client-side code to consume WebSocket events. The following chunk in the main application pulls in a module:

var stompClient = require('./websocket-listener')

That module is shown below:

link:src/main/js/websocket-listener.js[role=include]
  1. Pull in the SockJS JavaScript library for talking over WebSockets.

  2. Pull in the stomp-websocket JavaScript library to use the STOMP sub-protocol.

  3. Point the WebSocket at the application’s /probe endpoint.

  4. Iterate over the array of registrations supplied so that each can subscribe for callback as messages arrive.

Each registration entry has a route and a callback. In the next section, you can see how to register event handlers.

Registering for WebSocket Events

In React, a component’s componentDidMount() function gets called after it has been rendered in the DOM. That is also the right time to register for WebSocket events, because the component is now online and ready for business. The following code does so:

link:src/main/js/app.js[role=include]

The first line is the same as before, where all the employees are fetched from the server using page size. The second line shows an array of JavaScript objects being registered for WebSocket events, each with a route and a callback.

When a new employee is created, the behavior is to refresh the data set and then use the paging links to navigate to the last page. Why refresh the data before navigating to the end? It is possible that adding a new record causes a new page to get created. While it is possible to calculate if this will happen, it subverts the point of hypermedia. Instead of cobbling together customized page counts, it is better to use existing links and only go down that road if there is a performance-driving reason to do so.

When an employee is updated or deleted, the behavior is to refresh the current page. When you update a record, it impacts the page your are viewing. When you delete a record on the current page, a record from the next page will get pulled into the current one — hence the need to also refresh the current page.

Note
There is no requirement for these WebSocket messages to start with /topic. It is a common convention that indicates pub-sub semantics.

In the next section, you can see the actual operations to perform these operations.

Reacting to WebSocket Events and Updating the UI State

The following chunk of code contains the two callbacks used to update UI state when a WebSocket event is received:

link:src/main/js/app.js[role=include]

refreshAndGoToLastPage() uses the familiar follow() function to navigate to the employees link with the size parameter applied, plugging in this.state.pageSize. When the response is received, you then invoke the same onNavigate() function from the last section and jump to the last page, the one where the new record will be found.

refreshCurrentPage() also uses the follow() function but applies this.state.pageSize to size and this.state.page.number to page. This fetches the same page you are currently looking at and updates the state accordingly.

Note
This behavior tells every client to refresh their current page when an update or delete message is sent. It is possible that their current page may have nothing to do with the current event. However, it can be tricky to figure that out. What if the record that was deleted was on page two and you are looking at page three? Every entry would change. But is this desired behavior at all? Maybe. Maybe not.

Moving State Management Out of the Local Updates

Before you finish this section, there is something to recognize. You just added a new way for the state in the UI to get updated: when a WebSocket message arrives. But the old way to update the state is still there.

To simplify your code’s management of state, remove the old way. In other words, submit your POST, PUT, and DELETE calls, but do not use their results to update the UI’s state. Instead, wait for the WebSocket event to circle back and then do the update.

The follow chunk of code shows the same onCreate() function as the previous section, only simplified:

link:src/main/js/app.js[role=include]

Here, the follow() function is used to get to the employees link, and then the POST operation is applied. Notice how client({method: 'GET' …​}) has no then() or done(), as before? The event handler to listen for updates is now found in refreshAndGoToLastPage(), which you just looked at.

Putting It All Together

With all these modifications in place, fire up the application (./mvnw spring-boot:run) and poke around with it. Open up two browser tabs and resize so you can see them both. Start making updates in one and see how they instantly update the other tab. Open up your phone and visit the same page. Find a friend and ask that person to do the same thing. You might find this type of dynamic updating more keen.

Want a challenge? Try the exercise from the previous section where you open the same record in two different browser tabs. Try to update it in one and NOT see it update in the other. If it is possible, the conditional PUT code should still protect you. But it may be trickier to pull that off!

Review

In this section, you:

  • Configured Spring’s WebSocket support with a SockJS fallback.

  • Subscribed for create, update, and delete events from Spring Data REST to dynamically update the UI.

  • Published the URI of affected REST resources along with a contextual message ("/topic/newEmployee", "/topic/updateEmployee", and so on).

  • Registered WebSocket listeners in the UI to listen for these events.

  • Wired the listeners to handlers to update the UI state.

With all these features, it is easy to run two browsers, side-by-side, and see how updating one ripples to the other.

Issues?

While multiple displays nicely update, polishing the precise behavior is warranted. For example, creating a new user will cause ALL users to jump to the end. Any thoughts on how this should be handled?

Paging is useful, but it offers a tricky state to manage. The costs are low on this sample application, and React is very efficient at updating the DOM without causing lots of flickering in the UI. But with a more complex application, not all of these approaches will fit.

When designing with paging in mind, you have to decide what is the expected behavior between clients and if there needs to be updates or not. Depending on your requirements and performance of the system, the existing navigational hypermedia may be sufficient.

About

LCO Probe : Probes the LANSA LCO listener to establish if the listener is active or not

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published