diff options
Diffstat (limited to 'src')
9 files changed, 242 insertions, 134 deletions
diff --git a/src/main/java/org/luxons/sevenwonders/actions/PrepareCardAction.java b/src/main/java/org/luxons/sevenwonders/actions/PrepareCardAction.java new file mode 100644 index 00000000..8fd809a0 --- /dev/null +++ b/src/main/java/org/luxons/sevenwonders/actions/PrepareCardAction.java @@ -0,0 +1,16 @@ +package org.luxons.sevenwonders.actions; + +import org.luxons.sevenwonders.game.Move; + +public class PrepareCardAction { + + private Move move; + + public Move getMove() { + return move; + } + + public void setMove(Move move) { + this.move = move; + } +} diff --git a/src/main/java/org/luxons/sevenwonders/controllers/LobbyController.java b/src/main/java/org/luxons/sevenwonders/controllers/LobbyController.java index e15935d6..22e994dd 100644 --- a/src/main/java/org/luxons/sevenwonders/controllers/LobbyController.java +++ b/src/main/java/org/luxons/sevenwonders/controllers/LobbyController.java @@ -1,25 +1,21 @@ package org.luxons.sevenwonders.controllers; import java.security.Principal; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.luxons.sevenwonders.actions.JoinOrCreateGameAction; import org.luxons.sevenwonders.actions.StartGameAction; -import org.luxons.sevenwonders.errors.ErrorFactory; -import org.luxons.sevenwonders.errors.UIError; -import org.luxons.sevenwonders.errors.UniqueIdAlreadyUsedException; +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.game.data.GameDefinitionLoader; +import org.luxons.sevenwonders.repositories.GameRepository; +import org.luxons.sevenwonders.repositories.LobbyRepository; import org.luxons.sevenwonders.session.SessionAttributes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.messaging.handler.annotation.MessageExceptionHandler; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.messaging.simp.SimpMessageHeaderAccessor; @@ -34,50 +30,32 @@ public class LobbyController { private static final Logger logger = LoggerFactory.getLogger(LobbyController.class); - private final GameDefinitionLoader gameDefinitionLoader; - private final SimpMessagingTemplate template; - private final ErrorFactory errorFactory; - - private long lastGameId = 0; - - private Map<String, Lobby> lobbies = new HashMap<>(); + private final LobbyRepository lobbyRepository; - private Map<String, Game> games = new HashMap<>(); + private final GameRepository gameRepository; @Autowired - public LobbyController(GameDefinitionLoader gameDefinitionLoader, SimpMessagingTemplate template, - ErrorFactory errorFactory) { - this.gameDefinitionLoader = gameDefinitionLoader; + public LobbyController(SimpMessagingTemplate template, LobbyRepository lobbyRepository, + GameRepository gameRepository) { this.template = template; - this.errorFactory = errorFactory; - } - - @MessageExceptionHandler - @SendToUser("/queue/errors") - public UIError handleException(Throwable exception) { - logger.error("An error occured during message handling", exception); - return errorFactory.createError(exception); + this.lobbyRepository = lobbyRepository; + this.gameRepository = gameRepository; } @MessageMapping("/create-game") @SendTo("/topic/games") public Lobby createGame(SimpMessageHeaderAccessor headerAccessor, @Validated JoinOrCreateGameAction action, Principal principal) { - Lobby lobby = (Lobby)headerAccessor.getSessionAttributes().get(SessionAttributes.ATTR_LOBBY); - if (lobby != null) { - logger.warn("Client already in game '{}', cannot create a new game", lobby.getName()); - return lobby; - } - - Player player = createPlayer(action.getPlayerName(), principal); - lobby = createGame(action.getGameName(), player); + checkThatUserIsNotInAGame(headerAccessor, "cannot create another game"); + Player gameOwner = new Player(action.getPlayerName(), principal.getName()); + Lobby lobby = lobbyRepository.create(action.getGameName(), gameOwner); headerAccessor.getSessionAttributes().put(SessionAttributes.ATTR_LOBBY, lobby); - logger.info("Game '{}' (id={}) created by {} ({})", lobby.getName(), lobby.getId(), player.getDisplayName(), - player.getUserName()); + logger.info("Game '{}' (id={}) created by {} ({})", lobby.getName(), lobby.getId(), gameOwner.getDisplayName(), + gameOwner.getUserName()); return lobby; } @@ -85,44 +63,32 @@ public class LobbyController { @SendToUser("/queue/join-game") public Lobby joinGame(SimpMessageHeaderAccessor headerAccessor, @Validated JoinOrCreateGameAction action, Principal principal) { - Lobby lobby = (Lobby)headerAccessor.getSessionAttributes().get(SessionAttributes.ATTR_LOBBY); - if (lobby != null) { - logger.warn("Client already in game '{}', cannot join a different game", lobby.getName()); - return lobby; - } - - lobby = lobbies.get(action.getGameName()); - if (lobby == null) { - throw new GameNotFoundException(action.getGameName()); - } + checkThatUserIsNotInAGame(headerAccessor, "cannot join another game"); - Player newPlayer = createPlayer(action.getPlayerName(), principal); + Lobby lobby = lobbyRepository.find(action.getGameName()); + Player newPlayer = new Player(action.getPlayerName(), principal.getName()); lobby.addPlayer(newPlayer); - headerAccessor.getSessionAttributes().put(SessionAttributes.ATTR_LOBBY, lobby); - logger.warn("Player {} joined game {}", action.getPlayerName(), action.getGameName()); - + logger.info("Player {} joined game {}", action.getPlayerName(), action.getGameName()); return lobby; } - @MessageMapping("/start-game") - public void startGame(SimpMessageHeaderAccessor headerAccessor, @Validated StartGameAction action, - Principal principal) { + private void checkThatUserIsNotInAGame(SimpMessageHeaderAccessor headerAccessor, + String impossibleActionDescription) { Lobby lobby = (Lobby)headerAccessor.getSessionAttributes().get(SessionAttributes.ATTR_LOBBY); - if (lobby == null) { - logger.error("User {} is not in a lobby", principal.getName()); - template.convertAndSendToUser(principal.getName(), "/queue/errors", "No game to start"); - return; - } - - if (!lobby.isOwner(principal.getName())) { - logger.error("User {} is not the owner of the game '{}'", principal.getName(), lobby.getName()); - template.convertAndSendToUser(principal.getName(), "/queue/errors", "Only the owner can start the game"); - return; + if (lobby != null) { + throw new UserAlreadyInGameException(lobby.getName(), impossibleActionDescription); } + } + @MessageMapping("/start-game") + public void startGame(SimpMessageHeaderAccessor headerAccessor, @Validated StartGameAction action, + Principal principal) { + Lobby lobby = getOwnedLobby(headerAccessor, principal); Game game = lobby.startGame(action.getSettings()); + gameRepository.add(game); + logger.info("Game {} successfully started", game.getId()); List<PlayerTurnInfo> playerTurnInfos = game.startTurn(); @@ -133,31 +99,26 @@ public class LobbyController { } } - private Player createPlayer(String name, Principal principal) { - return new Player(name, principal.getName()); - } - - private Lobby createGame(String name, Player owner) { - if (lobbies.containsKey(name)) { - throw new GameNameAlreadyUsedException(name); + private Lobby getOwnedLobby(SimpMessageHeaderAccessor headerAccessor, Principal principal) { + Lobby lobby = (Lobby)headerAccessor.getSessionAttributes().get(SessionAttributes.ATTR_LOBBY); + if (lobby == null) { + throw new UserOwnsNoLobbyException("User " + principal.getName() + " is not in a lobby"); + } + if (!lobby.isOwner(principal.getName())) { + throw new UserOwnsNoLobbyException("User " + principal.getName() + " does not own the lobby he's in"); } - long id = lastGameId++; - Lobby lobby = new Lobby(id, name, owner, gameDefinitionLoader.getGameDefinition()); - lobbies.put(name, lobby); return lobby; } - private class GameNotFoundException extends RuntimeException { - - public GameNotFoundException(String name) { - super(name); + private class UserOwnsNoLobbyException extends ApiMisuseException { + UserOwnsNoLobbyException(String message) { + super(message); } } - private class GameNameAlreadyUsedException extends UniqueIdAlreadyUsedException { - - public GameNameAlreadyUsedException(String name) { - super(name); + private 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/ApiMisuseException.java b/src/main/java/org/luxons/sevenwonders/errors/ApiMisuseException.java new file mode 100644 index 00000000..0d7d1a82 --- /dev/null +++ b/src/main/java/org/luxons/sevenwonders/errors/ApiMisuseException.java @@ -0,0 +1,8 @@ +package org.luxons.sevenwonders.errors; + +public class ApiMisuseException extends RuntimeException { + + public ApiMisuseException(String message) { + super(message); + } +} diff --git a/src/main/java/org/luxons/sevenwonders/errors/ErrorFactory.java b/src/main/java/org/luxons/sevenwonders/errors/ErrorFactory.java deleted file mode 100644 index 2d3dcd0b..00000000 --- a/src/main/java/org/luxons/sevenwonders/errors/ErrorFactory.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.luxons.sevenwonders.errors; - -import java.util.List; -import java.util.Locale; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.MessageSource; -import org.springframework.messaging.handler.annotation.support.MethodArgumentNotValidException; -import org.springframework.stereotype.Component; -import org.springframework.validation.ObjectError; - -@Component -public class ErrorFactory { - - private static final String ERROR_CODE_VALIDATION = "VALIDATION_ERROR"; - - private static final String ERROR_MSG_VALIDATION = "Input invalid"; - - private final MessageSource messageSource; - - @Autowired - public ErrorFactory(MessageSource messageSource) { - this.messageSource = messageSource; - } - - public UIError createError(Throwable exception) { - if (exception instanceof UserInputException) { - return createUserError((UserInputException)exception); - } else if (exception instanceof MethodArgumentNotValidException) { - return createValidationError((MethodArgumentNotValidException)exception); - } else { - return createInternalError(exception); - } - } - - private UIError createUserError(UserInputException exception) { - String messageKey = exception.getMessageResourceKey(); - String message = messageSource.getMessage(messageKey, null, messageKey, Locale.US); - return new UIError(messageKey, message, ErrorType.USER); - } - - private UIError createInternalError(Throwable exception) { - return new UIError(exception.getClass().getSimpleName(), exception.getMessage(), ErrorType.INTERNAL); - } - - private UIError createValidationError(MethodArgumentNotValidException exception) { - List<ObjectError> errors = exception.getBindingResult().getAllErrors(); - UIError uiError = new UIError(ERROR_CODE_VALIDATION, ERROR_MSG_VALIDATION, ErrorType.VALIDATION); - uiError.setValidationErrors(errors); - return uiError; - } -} diff --git a/src/main/java/org/luxons/sevenwonders/errors/ExceptionHandler.java b/src/main/java/org/luxons/sevenwonders/errors/ExceptionHandler.java new file mode 100644 index 00000000..77bda927 --- /dev/null +++ b/src/main/java/org/luxons/sevenwonders/errors/ExceptionHandler.java @@ -0,0 +1,65 @@ +package org.luxons.sevenwonders.errors; + +import java.util.List; +import java.util.Locale; + +import org.luxons.sevenwonders.controllers.GameController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.messaging.handler.annotation.MessageExceptionHandler; +import org.springframework.messaging.handler.annotation.support.MethodArgumentNotValidException; +import org.springframework.messaging.simp.annotation.SendToUser; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.annotation.ControllerAdvice; + +@ControllerAdvice +public class ExceptionHandler { + + private static final Logger logger = LoggerFactory.getLogger(GameController.class); + + private static final String ERROR_CODE_VALIDATION = "VALIDATION_ERROR"; + + private static final String ERROR_MSG_VALIDATION = "Input invalid"; + + private final MessageSource messageSource; + + @Autowired + public ExceptionHandler(MessageSource messageSource) { + this.messageSource = messageSource; + } + + @MessageExceptionHandler + @SendToUser("/queue/errors") + private UIError handleValidationError(MethodArgumentNotValidException exception) { + logger.error("Invalid input", exception); + List<ObjectError> errors = exception.getBindingResult().getAllErrors(); + UIError uiError = new UIError(ERROR_CODE_VALIDATION, ERROR_MSG_VALIDATION, ErrorType.VALIDATION); + uiError.setValidationErrors(errors); + return uiError; + } + + @MessageExceptionHandler + @SendToUser("/queue/errors") + private UIError handleGenericUserError(UserInputException exception) { + logger.error("Incorrect user input: " + exception.getMessage()); + String messageKey = exception.getMessageResourceKey(); + String message = messageSource.getMessage(messageKey, exception.getParams(), messageKey, Locale.US); + return new UIError(messageKey, message, ErrorType.USER); + } + + @MessageExceptionHandler + @SendToUser("/queue/errors") + private UIError handleApiError(ApiMisuseException exception) { + logger.error("Invalid API input", exception); + return new UIError(exception.getClass().getSimpleName(), exception.getMessage(), ErrorType.INTERNAL); + } + + @MessageExceptionHandler + @SendToUser("/queue/errors") + private UIError handleUnexpectedInternalError(Throwable exception) { + logger.error("Uncaught exception thrown during message handling", exception); + return new UIError(exception.getClass().getSimpleName(), exception.getMessage(), ErrorType.INTERNAL); + } +} diff --git a/src/main/java/org/luxons/sevenwonders/errors/UserInputException.java b/src/main/java/org/luxons/sevenwonders/errors/UserInputException.java index 11f7f8f2..a86448ae 100644 --- a/src/main/java/org/luxons/sevenwonders/errors/UserInputException.java +++ b/src/main/java/org/luxons/sevenwonders/errors/UserInputException.java @@ -4,11 +4,18 @@ public class UserInputException extends RuntimeException { private final String messageResourceKey; - public UserInputException(String messageResourceKey) { + private final Object[] params; + + public UserInputException(String messageResourceKey, Object... params) { this.messageResourceKey = messageResourceKey; + this.params = params; } public String getMessageResourceKey() { return messageResourceKey; } + + public 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 2a2fab27..6006ef47 100644 --- a/src/main/java/org/luxons/sevenwonders/game/Game.java +++ b/src/main/java/org/luxons/sevenwonders/game/Game.java @@ -44,6 +44,10 @@ public class Game { return id; } + public List<Player> getPlayers() { + return table.getPlayers(); + } + private void startNewAge() { currentAge++; hands = decks.deal(currentAge, table.getNbPlayers()); diff --git a/src/main/java/org/luxons/sevenwonders/repositories/GameRepository.java b/src/main/java/org/luxons/sevenwonders/repositories/GameRepository.java new file mode 100644 index 00000000..14aaf3ac --- /dev/null +++ b/src/main/java/org/luxons/sevenwonders/repositories/GameRepository.java @@ -0,0 +1,41 @@ +package org.luxons.sevenwonders.repositories; + +import java.util.HashMap; +import java.util.Map; + +import org.luxons.sevenwonders.errors.ApiMisuseException; +import org.luxons.sevenwonders.game.Game; +import org.springframework.stereotype.Repository; + +@Repository +public class GameRepository { + + private Map<Long, Game> games = new HashMap<>(); + + public void add(Game game) { + if (games.containsKey(game.getId())) { + throw new GameAlreadyExistsException(game.getId()); + } + games.put(game.getId(), game); + } + + public Game find(long gameId) { + Game game = games.get(gameId); + if (game == null) { + throw new GameNotFoundException(gameId); + } + return game; + } + + private class GameNotFoundException extends ApiMisuseException { + GameNotFoundException(long id) { + super("Game " + id + " doesn't exist"); + } + } + + private 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 new file mode 100644 index 00000000..e4f049f5 --- /dev/null +++ b/src/main/java/org/luxons/sevenwonders/repositories/LobbyRepository.java @@ -0,0 +1,58 @@ +package org.luxons.sevenwonders.repositories; + +import java.util.HashMap; +import java.util.Map; + +import org.luxons.sevenwonders.errors.UserInputException; +import org.luxons.sevenwonders.game.Lobby; +import org.luxons.sevenwonders.game.Player; +import org.luxons.sevenwonders.game.data.GameDefinitionLoader; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +@Repository +public class LobbyRepository { + + private final GameDefinitionLoader gameDefinitionLoader; + + private Map<String, Lobby> lobbies = new HashMap<>(); + + private long lastGameId = 0; + + @Autowired + public LobbyRepository(GameDefinitionLoader gameDefinitionLoader) { + this.gameDefinitionLoader = gameDefinitionLoader; + } + + public Lobby create(String gameName, Player owner) { + if (lobbies.containsKey(gameName)) { + throw new GameNameAlreadyUsedException(gameName); + } + long id = lastGameId++; + Lobby lobby = new Lobby(id, gameName, owner, gameDefinitionLoader.getGameDefinition()); + lobbies.put(gameName, lobby); + return lobby; + } + + public Lobby find(String gameName) { + Lobby lobby = lobbies.get(gameName); + if (lobby == null) { + throw new LobbyNotFoundException(gameName); + } + return lobby; + } + + private class LobbyNotFoundException extends RuntimeException { + + public LobbyNotFoundException(String name) { + super("Lobby not found for game '" + name + "'"); + } + } + + private class GameNameAlreadyUsedException extends UserInputException { + + public GameNameAlreadyUsedException(String name) { + super("Game name '" + name + "' already exists"); + } + } +} |