end websocket feature to send public messages

This commit is contained in:
PEDSF 2020-11-07 11:34:03 +01:00
parent 811f0852df
commit e88fbdb0d0
19 changed files with 219 additions and 206 deletions

View file

@ -0,0 +1,70 @@
package org.springframework.samples.petclinic.common;
/**
* Class for WebSocket messages
*
* @author Paul-Emmanuel DOS SANTOS FACAO
*/
public class CommonWebSocket {
public static final String OWNER_FIND_ERROR = "No Owner found !";
public static final String OWNER_CREATED = "Owner created";
public static final String OWNER_CREATION_ERROR = "Error creating Owner !";
public static final String OWNER_UPDATED = "Owner updated";
public static final String OWNER_UPDATED_ERROR = "Error updating Owner !";
public static final String OWNER_DELETED = "Owner deleted";
public static final String OWNER_DELETED_ERROR = "Error deleting Owner !";
public static final String PET_FIND_ERROR = "No Pet found !";
public static final String PET_CREATED = "Pet created";
public static final String PET_CREATION_ERROR = "Error creating Pet !";
public static final String PET_UPDATED = "Pet updated";
public static final String PET_UPDATED_ERROR = "Error updating Pet !";
public static final String PET_DELETED = "Pet deleted";
public static final String PET_DELETED_ERROR = "Error deleting Pet !";
public static final String VET_FIND_ERROR = "No Vet found !";
public static final String VET_CREATED = "Vet created";
public static final String VET_CREATION_ERROR = "Error creating Vet !";
public static final String VET_UPDATED = "Vet updated";
public static final String VET_UPDATED_ERROR = "Error updating Vet !";
public static final String VET_DELETED = "Vet deleted";
public static final String VET_DELETED_ERROR = "Error deleting Vet !";
public static final String VISIT_FIND_ERROR = "No Visit found !";
public static final String VISIT_CREATED = "Visit created";
public static final String VISIT_CREATION_ERROR = "Error creating Visit !";
public static final String VISIT_UPDATED = "Visit updated";
public static final String VISIT_UPDATED_ERROR = "Error updating Visit !";
public static final String VISIT_DELETED = "Visit deleted";
public static final String VISIT_DELETED_ERROR = "Eror deleting Visit !";
private CommonWebSocket() {
throw new IllegalStateException("Utility class");
}
}

View file

@ -1,23 +0,0 @@
package org.springframework.samples.petclinic.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.samples.petclinic.model.common.WebSocketMessage;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
@Configuration
@EnableScheduling
public class WebSocketSchedulerConfiguration {
@Autowired
SimpMessagingTemplate simpMessagingTemplate;
//@Scheduled(fixedDelay = 3000)
public void sendMessages() {
simpMessagingTemplate.convertAndSend("/topic/public", new WebSocketMessage("Fixed Delay Scheduler"));
}
}

View file

@ -15,10 +15,7 @@
*/
package org.springframework.samples.petclinic.controller;
import org.springframework.samples.petclinic.common.CommonAttribute;
import org.springframework.samples.petclinic.common.CommonEndPoint;
import org.springframework.samples.petclinic.common.CommonError;
import org.springframework.samples.petclinic.common.CommonView;
import org.springframework.samples.petclinic.common.*;
import org.springframework.samples.petclinic.controller.common.WebSocketSender;
import org.springframework.samples.petclinic.dto.*;
import org.springframework.samples.petclinic.service.OwnerService;
@ -69,12 +66,12 @@ class OwnerController extends WebSocketSender {
public String processCreationForm(@ModelAttribute(CommonAttribute.OWNER) @Valid OwnerDTO owner,
BindingResult result) {
if (result.hasErrors()) {
sendMessages(CommonView.OWNER_CREATE_OR_UPDATE);
sendErrorMessage(CommonWebSocket.OWNER_CREATION_ERROR);
return CommonView.OWNER_CREATE_OR_UPDATE;
}
else {
owner = this.ownerService.save(owner);
sendMessages(OWNER_CREATED);
sendSuccessMessage(CommonWebSocket.OWNER_CREATED);
return CommonView.OWNER_OWNERS_R + owner.getId();
}
}
@ -101,6 +98,7 @@ class OwnerController extends WebSocketSender {
// no owners found
result.rejectValue(CommonAttribute.OWNER_LAST_NAME, CommonError.NOT_FOUND_ARGS,
CommonError.NOT_FOUND_MESSAGE);
sendInfoMessage(CommonWebSocket.OWNER_FIND_ERROR);
return CommonView.OWNER_FIND_OWNERS;
}
else if (results.size() == 1) {
@ -112,7 +110,7 @@ class OwnerController extends WebSocketSender {
// multiple owners found
model.put(CommonAttribute.SELECTIONS, results);
return CommonView.OWNER_OWNERS_LIST;
return CommonView.OWNER_OWNERS_LIST;
}
}
@ -127,12 +125,14 @@ class OwnerController extends WebSocketSender {
public String processUpdateOwnerForm(@ModelAttribute(CommonAttribute.OWNER) @Valid OwnerDTO owner,
BindingResult result, @PathVariable("ownerId") int ownerId) {
if (result.hasErrors()) {
sendErrorMessage(CommonWebSocket.OWNER_UPDATED_ERROR);
return CommonView.OWNER_CREATE_OR_UPDATE;
}
else {
owner.setId(ownerId);
this.ownerService.save(owner);
sendMessages(OWNER_UPDATED);
sendSuccessMessage(CommonWebSocket.OWNER_UPDATED);
return CommonView.OWNER_OWNERS_ID_R;
}
}

View file

@ -16,12 +16,10 @@
package org.springframework.samples.petclinic.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.samples.petclinic.common.CommonAttribute;
import org.springframework.samples.petclinic.common.CommonEndPoint;
import org.springframework.samples.petclinic.common.CommonError;
import org.springframework.samples.petclinic.common.CommonView;
import org.springframework.samples.petclinic.common.*;
import org.springframework.samples.petclinic.controller.common.WebSocketSender;
import org.springframework.samples.petclinic.dto.*;
import org.springframework.samples.petclinic.model.common.WebSocketMessage;
import org.springframework.samples.petclinic.validator.PetDTOValidator;
import org.springframework.samples.petclinic.service.*;
import org.springframework.stereotype.Controller;
@ -86,22 +84,25 @@ class PetController extends WebSocketSender {
public String processCreationForm(@ModelAttribute(CommonAttribute.OWNER) OwnerDTO owner,
@ModelAttribute(CommonAttribute.PET) @Valid PetDTO pet, BindingResult result, ModelMap model) {
if (owner == null) {
sendErrorMessage(CommonWebSocket.PET_CREATION_ERROR);
result.rejectValue(CommonAttribute.OWNER, CommonError.NOT_FOUND_ARGS, CommonError.NOT_FOUND_MESSAGE);
}
else {
if (StringUtils.hasLength(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null) {
sendErrorMessage(CommonWebSocket.PET_CREATION_ERROR);
result.rejectValue(CommonAttribute.NAME, CommonError.DUPLICATE_ARGS, CommonError.DUPLICATE_MESSAGE);
}
owner.addPet(pet);
}
if (result.hasErrors()) {
sendErrorMessage(CommonWebSocket.PET_CREATION_ERROR);
model.put(CommonAttribute.PET, pet);
return CommonView.PET_CREATE_OR_UPDATE;
}
else {
this.petService.save(pet);
sendMessages(PET_CREATED);
sendSuccessMessage(CommonWebSocket.PET_CREATED);
return CommonView.OWNER_OWNERS_ID_R;
}
}
@ -119,12 +120,13 @@ class PetController extends WebSocketSender {
if (result.hasErrors()) {
pet.setOwner(owner);
model.put(CommonAttribute.PET, pet);
sendErrorMessage(CommonWebSocket.PET_UPDATED_ERROR);
return CommonView.PET_CREATE_OR_UPDATE;
}
else {
owner.addPet(pet);
this.petService.save(pet);
sendMessages(PET_UPDATED);
sendSuccessMessage(CommonWebSocket.PET_UPDATED);
return CommonView.OWNER_OWNERS_ID_R;
}
}

View file

@ -18,6 +18,7 @@ package org.springframework.samples.petclinic.controller;
import org.springframework.samples.petclinic.common.CommonAttribute;
import org.springframework.samples.petclinic.common.CommonEndPoint;
import org.springframework.samples.petclinic.common.CommonView;
import org.springframework.samples.petclinic.common.CommonWebSocket;
import org.springframework.samples.petclinic.controller.common.WebSocketSender;
import org.springframework.samples.petclinic.dto.VetsDTO;
import org.springframework.samples.petclinic.service.VetService;
@ -35,7 +36,7 @@ import java.util.Map;
* @author Paul-Emmanuel DOS SANTOS FACAO
*/
@Controller
class VetController {
class VetController extends WebSocketSender {
private final VetService vetService;
@ -49,6 +50,10 @@ class VetController {
// objects so it is simpler for Object-Xml mapping
VetsDTO vets = new VetsDTO();
vets.getVetList().addAll(this.vetService.findAll());
if (vets.getVetList().isEmpty()) {
sendInfoMessage(CommonWebSocket.VET_FIND_ERROR);
}
model.put(CommonAttribute.VETS, vets);
return CommonView.VET_VETS_LIST;
}
@ -59,6 +64,9 @@ class VetController {
// objects so it is simpler for JSon/Object mapping
VetsDTO vets = new VetsDTO();
vets.getVetList().addAll(this.vetService.findAll());
if (vets.getVetList().isEmpty()) {
sendInfoMessage(CommonWebSocket.VET_FIND_ERROR);
}
return vets;
}

View file

@ -22,12 +22,13 @@ import javax.validation.Valid;
import org.springframework.samples.petclinic.common.CommonAttribute;
import org.springframework.samples.petclinic.common.CommonEndPoint;
import org.springframework.samples.petclinic.common.CommonView;
import org.springframework.samples.petclinic.common.CommonWebSocket;
import org.springframework.samples.petclinic.controller.common.WebSocketSender;
import org.springframework.samples.petclinic.dto.PetDTO;
import org.springframework.samples.petclinic.dto.VisitDTO;
import org.springframework.samples.petclinic.model.common.WebSocketMessage;
import org.springframework.samples.petclinic.service.PetService;
import org.springframework.samples.petclinic.service.VisitService;
import org.springframework.samples.petclinic.validator.PetDTOValidator;
import org.springframework.samples.petclinic.validator.VisitDTOValidator;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
@ -96,11 +97,12 @@ class VisitController extends WebSocketSender {
public String processNewVisitForm(@ModelAttribute(CommonAttribute.VISIT) @Valid VisitDTO visit,
BindingResult result) {
if (result.hasErrors()) {
sendErrorMessage(CommonWebSocket.VISIT_CREATION_ERROR);
return CommonView.VISIT_CREATE_OR_UPDATE;
}
else {
this.visitService.save(visit);
sendMessages(VISIT_CREATED);
sendSuccessMessage(CommonWebSocket.VISIT_CREATED);
return CommonView.OWNER_OWNERS_ID_R;
}
}

View file

@ -1,25 +0,0 @@
package org.springframework.samples.petclinic.controller.common;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.samples.petclinic.model.common.WebSocketMessage;
import org.springframework.stereotype.Controller;
public class WebSocketController {
@MessageMapping("/user")
@SendTo("/topic/user")
public WebSocketMessage sendMessage(@Payload final WebSocketMessage message) {
return message;
}
@MessageMapping("/websocket.newUser")
@SendTo("/topic/public")
public WebSocketMessage newUser(@Payload final WebSocketMessage message, SimpMessageHeaderAccessor headerAccessor) {
headerAccessor.getSessionAttributes().put("username", message.getSender());
return message;
}
}

View file

@ -1,35 +0,0 @@
package org.springframework.samples.petclinic.controller.common;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.samples.petclinic.model.common.WebSocketMessage;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionConnectedEvent;
@Slf4j
@Component
public class WebSocketEventListener {
@Autowired
private SimpMessageSendingOperations sendingOperations;
@EventListener
public void handlewebSocketConnectListener(final SessionConnectedEvent event) {
log.info("Ding dong. We have a new connection!");
}
@EventListener
public void handlewebSocketDisconnectListener(final SessionConnectedEvent event) {
final StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());
// final String username = (String)
// headerAccessor.getSessionAttributes().get("username");
sendingOperations.convertAndSend("/topic/user", new WebSocketMessage("Hello in event lmistener"));
}
}

View file

@ -4,28 +4,51 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.samples.petclinic.model.common.WebSocketMessage;
/**
* Class to extend for calling websocket sending messages
*
* @author Paul-Emmanuel DOS SANTOS FACAO
*/
public class WebSocketSender {
public static final String OWNER_CREATED = "Owner created";
public static final String OWNER_UPDATED = "Owner updated";
public static final String OWNER_DELETED = "Owner deleted";
public static final String PET_CREATED = "Pet created";
public static final String PET_UPDATED = "Pet updated";
public static final String PET_DELETED = "Pet deleted";
public static final String VET_CREATED = "Vet created";
public static final String VET_UPDATED = "Vet updated";
public static final String VET_DELETED = "Vet deleted";
public static final String VISIT_CREATED = "Visit created";
public static final String VISIT_UPDATED = "Visit updated";
public static final String VISIT_DELETED = "Visit deleted";
private static final int WAITING_TIME = 200; // delay of frontend page changing
@Autowired
SimpMessagingTemplate simpMessagingTemplate;
public void sendMessages(String message) {
simpMessagingTemplate.convertAndSend("/topic/public", new WebSocketMessage(message));
public void sendMessage(String message, String type) {
// Send message asynchronously
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(WAITING_TIME);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
simpMessagingTemplate.convertAndSend("/topic/public", new WebSocketMessage(message, type));
}
}).start();
}
public void sendAlertMessage(String message) {
sendMessage(message, WebSocketMessage.ALERT);
}
public void sendErrorMessage(String message) {
sendMessage(message, WebSocketMessage.ERROR);
}
public void sendInfoMessage(String message) {
sendMessage(message, WebSocketMessage.INFO);
}
public void sendSuccessMessage(String message) {
sendMessage(message, WebSocketMessage.SUCCESS);
}
public void sendWarningMessage(String message) {
sendMessage(message, WebSocketMessage.WARNING);
}
}

View file

@ -2,17 +2,41 @@ package org.springframework.samples.petclinic.model.common;
import java.time.LocalDate;
/**
* WebSocket message type.
*
* @author Paul-Emmanuel DOS SANTOS FACAO
*/
public class WebSocketMessage {
public static final String ALERT = "alert";
public static final String SUCCESS = "success";
public static final String ERROR = "error";
public static final String WARNING = "warning";
public static final String INFO = "info";
private String sender;
private String time;
private String content;
private String type;
public WebSocketMessage(String content) {
this.time = LocalDate.now().toString();
this.content = content;
this.type = INFO;
}
public WebSocketMessage(String content, String type) {
this.time = LocalDate.now().toString();
this.content = content;
this.type = type;
}
public String getSender() {
@ -39,4 +63,12 @@ public class WebSocketMessage {
this.content = content;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

View file

@ -1,7 +0,0 @@
package org.springframework.samples.petclinic.model.common;
public enum WebSocketMessageType {
MESSAGE, CONNECT, DISCONNECT
}

View file

@ -0,0 +1,24 @@
function displayMessage() {
var socket = new SockJS('/websocket');
var stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
stompClient.subscribe('/topic/public', function (socketMessage) {
noty({
text: JSON.parse(socketMessage.body).content,
type: JSON.parse(socketMessage.body).type,
layout: "topRight",
timeout: 4000,
animation: {
open: 'animated bounceInRight',
close: 'animated bounceOutRight',
easing: 'swing',
speed: 500
}
});
});
});
}

View file

@ -1,62 +0,0 @@
var stompClient = null;
function setConnected(connected) {
$("#connect").prop("disabled", connected);
$("#disconnect").prop("disabled", !connected);
if (connected) {
$("#conversation").show();
}
else {
$("#conversation").hide();
}
$("#userinfo").html("");
}
function displayMessage() {
var socket = new SockJS('/websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
stompClient.subscribe('/topic/public', function (greeting) {
showGreeting(JSON.parse(greeting.body).content);
});
});
}
function connect() {
var socket = new SockJS('/websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/public', function (greeting) {
$("#userinfo").text(JSON.parse(greeting.body).content);
});
});
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendName() {
stompClient.send("/app/user", {}, JSON.stringify({'name': $("#name").val()}));
}
function showGreeting(message) {
$("#userinfo").text(message);
}
$(function () {
$("form").on('submit', function (e) {
e.preventDefault();
});
$( "#connect" ).click(function() { connect(); });
$( "#disconnect" ).click(function() { disconnect(); });
$( "#send" ).click(function() { sendName(); });
});

View file

@ -15,7 +15,7 @@
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<![endif]-->
<link rel="stylesheet" th:href="@{/resources/css/petclinic.css}" />
<link rel="stylesheet" th:href="@{/resources/static/css/style.css}" />
@ -98,9 +98,6 @@
<div id="main-content" class="container">
<div class="row">
<form class="form-inline">
<div class="form-group pull-left" id="conversation">
<span id="messageOutput" class="form-control" style="width:200px; "></span>
</div>
<div class="form-group pull-right" >
<img src="../static/resources/images/spring-pivotal-logo.png"
th:src="@{/resources/images/spring-pivotal-logo.png}" alt="Sponsored by Pivotal" />
@ -117,21 +114,12 @@
<script th:src="@{/webjars/sockjs-client/sockjs.min.js}"></script>
<script th:src="@{/webjars/stomp-websocket/stomp.min.js}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-noty/2.3.7/packaged/jquery.noty.packaged.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.4.0/animate.min.css">
<script th:src="@{/js/notification.js}"></script>
<script type="text/javascript">
$(document).ready( function(){
var socket = new SockJS('/websocket');
var stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
stompClient.subscribe('/topic/public', function (socketMessage) {
var message = JSON.parse(socketMessage.body).content;
alert(message);
$('#messageOutput').text(message);
});
});
});
displayMessage();
</script>
</body>

View file

@ -16,7 +16,6 @@ import org.springframework.samples.petclinic.service.OwnerService;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.ui.Model;
import java.time.LocalDate;
import java.util.Collection;

View file

@ -30,10 +30,12 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.samples.petclinic.common.CommonAttribute;
import org.springframework.samples.petclinic.common.CommonEndPoint;
import org.springframework.samples.petclinic.common.CommonError;
import org.springframework.samples.petclinic.common.CommonView;
import org.springframework.samples.petclinic.controller.common.WebSocketSender;
import org.springframework.samples.petclinic.dto.OwnerDTO;
import org.springframework.samples.petclinic.dto.PetDTO;
import org.springframework.samples.petclinic.dto.PetTypeDTO;
@ -61,7 +63,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Paul-Emmanuel DOS SANTOS FACAO
*/
@WebMvcTest(OwnerController.class)
class OwnerControllerTest {
class OwnerControllerTest extends WebSocketSender {
private static final int OWNER_ID = 15;
@ -88,6 +90,9 @@ class OwnerControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
SimpMessagingTemplate simpMessagingTemplate;
@MockBean
private OwnerService owners;

View file

@ -33,6 +33,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.samples.petclinic.common.CommonAttribute;
import org.springframework.samples.petclinic.common.CommonEndPoint;
import org.springframework.samples.petclinic.common.CommonError;
@ -63,6 +64,9 @@ class PetControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
SimpMessagingTemplate simpMessagingTemplate;
@MockBean
private PetService petService;

View file

@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.samples.petclinic.common.CommonAttribute;
import org.springframework.samples.petclinic.common.CommonEndPoint;
import org.springframework.samples.petclinic.common.CommonView;
@ -59,6 +60,9 @@ class VetControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
SimpMessagingTemplate simpMessagingTemplate;
@MockBean
private VetService vetService;

View file

@ -32,6 +32,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.samples.petclinic.common.CommonAttribute;
import org.springframework.samples.petclinic.common.CommonEndPoint;
import org.springframework.samples.petclinic.common.CommonView;
@ -74,6 +75,9 @@ class VisitControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
SimpMessagingTemplate simpMessagingTemplate;
@MockBean
private VisitService visitService;