diff options
11 files changed, 81 insertions, 62 deletions
diff --git a/src/main/java/org/luxons/sevenwonders/config/WebSocketConfig.java b/src/main/java/org/luxons/sevenwonders/config/WebSocketConfig.java index 83629d6a..3b588894 100644 --- a/src/main/java/org/luxons/sevenwonders/config/WebSocketConfig.java +++ b/src/main/java/org/luxons/sevenwonders/config/WebSocketConfig.java @@ -19,7 +19,7 @@ public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { config.setUserDestinationPrefix("/user"); // prefix for all calls from clients - config.setApplicationDestinationPrefixes("/app"); + config.setApplicationDestinationPrefixes("/app", "/topic"); } @Override diff --git a/src/main/java/org/luxons/sevenwonders/controllers/LobbyController.java b/src/main/java/org/luxons/sevenwonders/controllers/LobbyController.java index 22e994dd..3b98d72f 100644 --- a/src/main/java/org/luxons/sevenwonders/controllers/LobbyController.java +++ b/src/main/java/org/luxons/sevenwonders/controllers/LobbyController.java @@ -1,7 +1,7 @@ package org.luxons.sevenwonders.controllers; import java.security.Principal; -import java.util.List; +import java.util.Collection; import org.luxons.sevenwonders.actions.JoinOrCreateGameAction; import org.luxons.sevenwonders.actions.StartGameAction; @@ -9,7 +9,6 @@ import org.luxons.sevenwonders.errors.ApiMisuseException; import org.luxons.sevenwonders.game.Game; import org.luxons.sevenwonders.game.Lobby; import org.luxons.sevenwonders.game.Player; -import org.luxons.sevenwonders.game.api.PlayerTurnInfo; import org.luxons.sevenwonders.repositories.GameRepository; import org.luxons.sevenwonders.repositories.LobbyRepository; import org.luxons.sevenwonders.session.SessionAttributes; @@ -21,11 +20,11 @@ import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.messaging.simp.annotation.SendToUser; +import org.springframework.messaging.simp.annotation.SubscribeMapping; import org.springframework.stereotype.Controller; import org.springframework.validation.annotation.Validated; @Controller -@MessageMapping("/lobby") public class LobbyController { private static final Logger logger = LoggerFactory.getLogger(LobbyController.class); @@ -44,7 +43,13 @@ public class LobbyController { this.gameRepository = gameRepository; } - @MessageMapping("/create-game") + @SubscribeMapping("/games") // prefix /topic not shown + public Collection<Lobby> listGames() { + logger.info("Subscribed to /games"); + return lobbyRepository.list(); + } + + @MessageMapping("/lobby/create-game") @SendTo("/topic/games") public Lobby createGame(SimpMessageHeaderAccessor headerAccessor, @Validated JoinOrCreateGameAction action, Principal principal) { @@ -59,7 +64,7 @@ public class LobbyController { return lobby; } - @MessageMapping("/join-game") + @MessageMapping("/lobby/join-game") @SendToUser("/queue/join-game") public Lobby joinGame(SimpMessageHeaderAccessor headerAccessor, @Validated JoinOrCreateGameAction action, Principal principal) { @@ -82,7 +87,7 @@ public class LobbyController { } } - @MessageMapping("/start-game") + @MessageMapping("/lobby/start-game") public void startGame(SimpMessageHeaderAccessor headerAccessor, @Validated StartGameAction action, Principal principal) { Lobby lobby = getOwnedLobby(headerAccessor, principal); @@ -90,13 +95,6 @@ public class LobbyController { gameRepository.add(game); logger.info("Game {} successfully started", game.getId()); - - List<PlayerTurnInfo> playerTurnInfos = game.startTurn(); - for (PlayerTurnInfo playerTurnInfo : playerTurnInfos) { - Player player = playerTurnInfo.getTable().getPlayers().get(playerTurnInfo.getPlayerIndex()); - String userName = player.getUserName(); - template.convertAndSendToUser(userName, "/queue/game/turn", playerTurnInfo); - } } private Lobby getOwnedLobby(SimpMessageHeaderAccessor headerAccessor, Principal principal) { @@ -110,13 +108,13 @@ public class LobbyController { return lobby; } - private class UserOwnsNoLobbyException extends ApiMisuseException { + private static class UserOwnsNoLobbyException extends ApiMisuseException { UserOwnsNoLobbyException(String message) { super(message); } } - private class UserAlreadyInGameException extends ApiMisuseException { + private static class UserAlreadyInGameException extends ApiMisuseException { UserAlreadyInGameException(String gameName, String impossibleActionDescription) { super("Client already in game '" + gameName + "', " + impossibleActionDescription); } diff --git a/src/main/java/org/luxons/sevenwonders/errors/ExceptionHandler.java b/src/main/java/org/luxons/sevenwonders/errors/ExceptionHandler.java index ca2e4062..9e26cbe3 100644 --- a/src/main/java/org/luxons/sevenwonders/errors/ExceptionHandler.java +++ b/src/main/java/org/luxons/sevenwonders/errors/ExceptionHandler.java @@ -7,6 +7,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; +import org.springframework.messaging.converter.MessageConversionException; import org.springframework.messaging.handler.annotation.MessageExceptionHandler; import org.springframework.messaging.handler.annotation.support.MethodArgumentNotValidException; import org.springframework.messaging.simp.annotation.SendToUser; @@ -20,7 +21,11 @@ public class ExceptionHandler { private static final String ERROR_CODE_VALIDATION = "VALIDATION_ERROR"; - private static final String ERROR_MSG_VALIDATION = "Input invalid"; + private static final String ERROR_CODE_CONVERSION = "MESSAGE_FORMAT_ERROR"; + + private static final String ERROR_MSG_VALIDATION = "Invalid input data"; + + private static final String ERROR_MSG_CONVERSION = "Invalid input format"; private final MessageSource messageSource; @@ -41,6 +46,13 @@ public class ExceptionHandler { @MessageExceptionHandler @SendToUser("/queue/errors") + private UIError handleConversionError(MessageConversionException exception) { + logger.error("Error interpreting the message", exception); + return new UIError(ERROR_CODE_CONVERSION, ERROR_MSG_CONVERSION, ErrorType.VALIDATION); + } + + @MessageExceptionHandler + @SendToUser("/queue/errors") private UIError handleGenericUserError(UserInputException exception) { logger.error("Incorrect user input: " + exception.getMessage()); String messageKey = exception.getMessageResourceKey(); diff --git a/src/main/java/org/luxons/sevenwonders/errors/UserInputException.java b/src/main/java/org/luxons/sevenwonders/errors/UserInputException.java index a86448ae..4033a696 100644 --- a/src/main/java/org/luxons/sevenwonders/errors/UserInputException.java +++ b/src/main/java/org/luxons/sevenwonders/errors/UserInputException.java @@ -11,11 +11,11 @@ public class UserInputException extends RuntimeException { this.params = params; } - public String getMessageResourceKey() { + String getMessageResourceKey() { return messageResourceKey; } - public Object[] getParams() { + Object[] getParams() { return params; } } diff --git a/src/main/java/org/luxons/sevenwonders/game/Game.java b/src/main/java/org/luxons/sevenwonders/game/Game.java index 6006ef47..70a5b615 100644 --- a/src/main/java/org/luxons/sevenwonders/game/Game.java +++ b/src/main/java/org/luxons/sevenwonders/game/Game.java @@ -131,8 +131,7 @@ public class Game { } private static class MissingPreparedMoveException extends RuntimeException { - - public MissingPreparedMoveException(int playerIndex) { + MissingPreparedMoveException(int playerIndex) { super("Player " + playerIndex + " is not ready to play"); } } diff --git a/src/main/java/org/luxons/sevenwonders/game/Lobby.java b/src/main/java/org/luxons/sevenwonders/game/Lobby.java index 16d4e8a3..241c5530 100644 --- a/src/main/java/org/luxons/sevenwonders/game/Lobby.java +++ b/src/main/java/org/luxons/sevenwonders/game/Lobby.java @@ -86,18 +86,17 @@ public class Lobby { return owner.getUserName().equals(userName); } - public class GameAlreadyStartedException extends IllegalStateException { + private static class GameAlreadyStartedException extends IllegalStateException { } - public class PlayerOverflowException extends IllegalStateException { + private static class PlayerOverflowException extends IllegalStateException { } - public class PlayerUnderflowException extends IllegalStateException { + private static class PlayerUnderflowException extends IllegalStateException { } - public class PlayerNameAlreadyUsedException extends UniqueIdAlreadyUsedException { - - public PlayerNameAlreadyUsedException(String name) { + private static class PlayerNameAlreadyUsedException extends UniqueIdAlreadyUsedException { + PlayerNameAlreadyUsedException(String name) { super(name); } } diff --git a/src/main/java/org/luxons/sevenwonders/repositories/GameRepository.java b/src/main/java/org/luxons/sevenwonders/repositories/GameRepository.java index 14aaf3ac..0e0f6db8 100644 --- a/src/main/java/org/luxons/sevenwonders/repositories/GameRepository.java +++ b/src/main/java/org/luxons/sevenwonders/repositories/GameRepository.java @@ -27,13 +27,13 @@ public class GameRepository { return game; } - private class GameNotFoundException extends ApiMisuseException { + private static class GameNotFoundException extends ApiMisuseException { GameNotFoundException(long id) { super("Game " + id + " doesn't exist"); } } - private class GameAlreadyExistsException extends ApiMisuseException { + private static class GameAlreadyExistsException extends ApiMisuseException { GameAlreadyExistsException(long id) { super("Game " + id + " already exists"); } diff --git a/src/main/java/org/luxons/sevenwonders/repositories/LobbyRepository.java b/src/main/java/org/luxons/sevenwonders/repositories/LobbyRepository.java index e4f049f5..bede34af 100644 --- a/src/main/java/org/luxons/sevenwonders/repositories/LobbyRepository.java +++ b/src/main/java/org/luxons/sevenwonders/repositories/LobbyRepository.java @@ -1,5 +1,6 @@ package org.luxons.sevenwonders.repositories; +import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -24,6 +25,10 @@ public class LobbyRepository { this.gameDefinitionLoader = gameDefinitionLoader; } + public Collection<Lobby> list() { + return lobbies.values(); + } + public Lobby create(String gameName, Player owner) { if (lobbies.containsKey(gameName)) { throw new GameNameAlreadyUsedException(gameName); @@ -42,16 +47,14 @@ public class LobbyRepository { return lobby; } - private class LobbyNotFoundException extends RuntimeException { - - public LobbyNotFoundException(String name) { + private static class LobbyNotFoundException extends RuntimeException { + LobbyNotFoundException(String name) { super("Lobby not found for game '" + name + "'"); } } - private class GameNameAlreadyUsedException extends UserInputException { - - public GameNameAlreadyUsedException(String name) { + private static class GameNameAlreadyUsedException extends UserInputException { + GameNameAlreadyUsedException(String name) { super("Game name '" + name + "' already exists"); } } diff --git a/src/main/resources/static/app.js b/src/main/resources/static/app.js index ace23fdb..2b6b160b 100644 --- a/src/main/resources/static/app.js +++ b/src/main/resources/static/app.js @@ -25,8 +25,15 @@ function connect() { stompClient.subscribe('/topic/games', function (msg) { var game = JSON.parse(msg.body); - console.log("Received new game: " + game); - addNewGame(game); + if (Array.isArray(game)) { + console.log("Received new games: " + game); + for (var i = 0; i < game.length; i++) { + addNewGame(game[i]); + } + } else { + console.log("Received new game: " + game); + addNewGame(game); + } }); stompClient.subscribe('/user/queue/join-game', function (msg) { diff --git a/src/main/resources/static/test-ws.js b/src/main/resources/static/test-ws.js index 48104537..662cf811 100644 --- a/src/main/resources/static/test-ws.js +++ b/src/main/resources/static/test-ws.js @@ -6,31 +6,17 @@ function connect() { stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { console.log('Connected: ' + frame); - - for (var i = 1; i < 10; i++) { - subscribeTest(stompClient, '/test' + i); - subscribeTest(stompClient, '/topic/test' + i); - subscribeTest(stompClient, '/broadcast/test' + i); - subscribeTest(stompClient, '/queue/test' + i); - subscribeTest(stompClient, '/user/queue/test' + i); - subscribeTest(stompClient, '/user/queue/topic/test' + i); - subscribeTest(stompClient, '/user/queue/broadcast/test' + i); - } }); } -function sendTest(indexes) { - for (var i = 0; i < indexes.length; i++) { - stompClient.send("/app/test" + indexes[i], {}, "test payload " + indexes[i]); - } +function send(endpoint, payload) { + stompClient.send(endpoint, {}, payload); } -function subscribeTest(stompClient, endpoint) { - var id = endpoint.replace(new RegExp('/', 'g'), '') + '-data'; - $("#test-feeds").append('<tr><td>' + endpoint + '</td><td id="' + id + '">no data received yet</td></tr>'); +function subscribeTo(endpoint) { + $("#test-feeds").prepend('<tr><td>' + endpoint + '</td><td>Subscribed</td></tr>'); stompClient.subscribe(endpoint, function (data) { - console.log("Received event on " + endpoint + ": data.body=" + data.body); - $("#" + id).html('<strong>received "' + data.body + '"</strong>'); + $("#test-feeds").prepend('<tr><td>' + endpoint + '</td><td>Received: <pre>' + data.body + '</pre></td></tr>'); }); } @@ -38,9 +24,14 @@ $(function () { $("form").on('submit', function (e) { e.preventDefault(); }); - $("#send-test").click(function () { - var indexesToSend = $("#test-index-field").val().split(','); - sendTest(indexesToSend); + $("#send-btn").click(function () { + var endpoint = $("#path-field").val(); + var payload = $("#payload-field").val(); + send(endpoint, payload); + }); + $("#subscribe-btn").click(function () { + var endpoint = $("#subscribe-path-field").val(); + subscribeTo(endpoint); }); }); diff --git a/src/main/resources/static/test.html b/src/main/resources/static/test.html index 93c5d928..e19f9eb3 100644 --- a/src/main/resources/static/test.html +++ b/src/main/resources/static/test.html @@ -21,9 +21,19 @@ <form class="form-inline"> <div class="form-group"> - <label for="test-index-field">Send to /app/testX, with X in </label> - <input id="test-index-field"> - <button id="send-test" class="btn btn-default" type="submit">Send</button> + <label for="subscribe-path-field">Path:</label> + <input id="subscribe-path-field" placeholder="path"> + <button id="subscribe-btn" class="btn btn-default" type="submit">Subscribe</button> + </div> +</form> + +<form class="form-inline"> + <div class="form-group"> + <label for="path-field">Path:</label> + <input id="path-field" placeholder="path"> + <label for="payload-field">Payload:</label> + <input id="payload-field" placeholder="JSON payload"> + <button id="send-btn" class="btn btn-default" type="submit">Send</button> </div> </form> |