Commit 5a3e58c4 authored by Alexandre Ducarne's avatar Alexandre Ducarne
Browse files

Merge branch 'feature/com/websockets' into 'int/v2'

Feature/com/websockets

See merge request !17
parents 9a7f115a bd9f9bbe
<p align="center">
<h3 align="center">AI20 Projet othello</h3>
</p>
# AI12 Projet othello
## Getting Started
......
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ai12</groupId>
<artifactId>othello</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>othello</name>
<description>ai12 othello game</description>
<packaging>jar</packaging>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.ai12</groupId>
<artifactId>othello</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>othello</name>
<description>ai12 othello game</description>
<packaging>jar</packaging>
<properties>
<java.version>1.8</java.version>
</properties>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>0.10.2</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<testSourceDirectory>src/test</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>baleine.client.ClientApp</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
......@@ -2,38 +2,43 @@ package baleine.client;
import baleine.client.game.controller.TransApplicationController;
import baleine.client.game.view.TransApplicationView;
import baleine.client.network.WebSocketStompSessionInitializer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
/**
* Main application
*
*
* @author thomas
*/
public class ClientApp extends Application {
private TransApplicationView view;
private TransApplicationController controller;
@Override
public void start(Stage stage) throws Exception {
private TransApplicationView view;
private TransApplicationController controller;
view = new TransApplicationView();
controller = new TransApplicationController(view);
/**
* Run the client app (JavaFX)
*
* @param args additional args
* @throws Exception TODO: remove once the Websocket is initialized in the proper class
*/
public static void main(String[] args) throws Exception {
// TODO: call this method right when the user connects to a server instead of here
WebSocketStompSessionInitializer.init("localhost", 8080);
launch(args);
}
// Application content display
stage.setTitle("Translator");
stage.setScene(new Scene(view, 600, 400));
stage.setResizable(false);
stage.show();
}
@Override
public void start(Stage stage) {
/**
* Run
*
* @param args
*/
public static void main(String[] args) {
launch(args);
}
view = new TransApplicationView();
controller = new TransApplicationController(view);
// Application content display
stage.setTitle("Translator");
stage.setScene(new Scene(view, 600, 400));
stage.setResizable(false);
stage.show();
}
}
package baleine.client.network;
import baleine.client.network.messages.clientToServer.LoginUserToServerMessage;
import java.util.Optional;
/**
* Class handling message sending & receiving
*/
public class MessageController {
private static MessageController instance = new MessageController();
private WebSocketStompSessionHandler wsHandler;
private MessageController() {
}
/**
* Get the unique instance of this class
*
* @return the instance of MessageController
*/
public static MessageController getInstance() {
return Optional.of(instance).orElseGet(MessageController::new);
}
/**
* Set the new WebSocket handler to use for messaging
*
* @param wsHandler the WebSocket handler to use
*/
void setWsHandler(WebSocketStompSessionHandler wsHandler) {
this.wsHandler = wsHandler;
}
/**
* Send a LoginUserToServerMessage to the server
*
* @param message the message to send
*/
public void sendLoginUserToServer(LoginUserToServerMessage message) {
wsHandler.sendMessage(message);
}
}
package baleine.client.network;
import baleine.common.message.MessageMeta;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaders;
import org.springframework.messaging.simp.stomp.StompSession;
import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
import java.lang.reflect.Type;
/**
* WebSocket message handler
*/
public class WebSocketStompSessionHandler extends StompSessionHandlerAdapter {
public static final String RECEIVE_MESSAGE_TOPIC = "/topic/clientMessages";
public static final String SEND_MESSAGE_TOPIC = "/topic/serverMessages";
private static final Logger LOGGER = LogManager.getLogger(WebSocketStompSessionHandler.class);
private StompSession stompSession;
/**
* Send a message to the server
*
* @param message the message to be sent
*/
public void sendMessage(MessageMeta message) {
stompSession.send(SEND_MESSAGE_TOPIC, message);
LOGGER.info("Message sent to server : " + message.toString());
}
/**
* Set the session and subscribe to a topic after handshake
*
* @param session the session opened between server and client
* @param connectedHeaders the StomHeaders of the session
*/
@Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
stompSession = session;
MessageController.getInstance().setWsHandler(this);
LOGGER.info("New session established : " + session.getSessionId());
stompSession.subscribe(RECEIVE_MESSAGE_TOPIC, this);
LOGGER.info("Subscribed to " + RECEIVE_MESSAGE_TOPIC);
}
/**
* Log an error when an exception is raised
*
* @param session the current session
* @param command the current StompCommand
* @param headers the current StompHeaders
* @param payload the current payload
* @param exception the raised exception
*/
@Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
LOGGER.error("Got an exception", exception);
}
/**
* Get the payload for serialization/deserialization with Jackson API
*
* @param headers the StompHeaders
* @return the type of the class to serialize/deserialize
*/
@Override
public Type getPayloadType(StompHeaders headers) {
return MessageMeta.class;
}
/**
* Process the message received from the server
* Triggered when the client app receives a message from the server
*
* @param headers the current StompHeaders
* @param payload the message sent by the server
*/
@Override
public void handleFrame(StompHeaders headers, Object payload) {
MessageMeta message = (MessageMeta) payload;
LOGGER.info("New server message received : " + message.toString());
message.processData();
}
}
package baleine.client.network;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.simp.stomp.StompSession;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.socket.WebSocketHttpHeaders;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.messaging.WebSocketStompClient;
import org.springframework.web.socket.sockjs.client.SockJsClient;
import org.springframework.web.socket.sockjs.client.Transport;
import org.springframework.web.socket.sockjs.client.WebSocketTransport;
import org.springframework.web.socket.sockjs.frame.Jackson2SockJsMessageCodec;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
/**
* Class responsible for the first connection and handshake between the server and client
*/
public class WebSocketStompSessionInitializer {
private final static WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
/**
* Open a new WebSocket connection over Stomp
*
* @param hostname the hostname of the server
* @param port the port of the server
* @throws ExecutionException Exception thrown when attempting to retrieve the result of a task that aborted by throwing an exception
* @throws InterruptedException Exception thrown when the server closes the connection
*/
public static void init(String hostname, Integer port) throws ExecutionException, InterruptedException {
WebSocketStompSessionInitializer webSocketStompSessionInitializer = new WebSocketStompSessionInitializer();
ListenableFuture<StompSession> session = webSocketStompSessionInitializer.connect(hostname, port);
session.get();
}
/**
* Set the configuration to be used for messaging
*
* @param hostname the hostname of the server
* @param port the port of the server
* @return a listenable future list containing the newly opened stomp session
*/
private ListenableFuture<StompSession> connect(String hostname, Integer port) {
List<Transport> transports = new ArrayList<>(1);
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
SockJsClient sockJsClient = new SockJsClient(transports);
sockJsClient.setMessageCodec(new Jackson2SockJsMessageCodec());
WebSocketStompClient stompClient = new WebSocketStompClient(sockJsClient);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
String url = "ws://{host}:{port}/messages";
return stompClient.connect(url, headers, new WebSocketStompSessionHandler(), hostname, port);
}
}
package baleine.client.network.messages.clientToServer;
import baleine.common.message.MessageMeta;
import java.sql.Timestamp;
import java.util.UUID;
/**
* Message containing a chat message to send to the server.
*/
public class ChatMessageToServerMessage extends MessageMeta {
private String message;
private Timestamp time;
private UUID playerID;
private UUID gameID;
public ChatMessageToServerMessage(String message, Timestamp time, UUID playerID, UUID gameID) {
this.message = message;
this.time = time;
this.playerID = playerID;
this.gameID = gameID;
}
public ChatMessageToServerMessage() {
}
@Override
public void processData() {
// TODO: Implement
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Timestamp getTime() {
return time;
}
public void setTime(Timestamp time) {
this.time = time;
}
public UUID getPlayerID() {
return playerID;
}
public void setPlayerID(UUID playerID) {
this.playerID = playerID;
}
public UUID getGameID() {
return gameID;
}
public void setGameID(UUID gameID) {
this.gameID = gameID;
}
}
package baleine.client.network.messages.clientToServer;
import baleine.common.message.MessageMeta;
import java.util.UUID;
/**
* Message containing a disconnection request to send to the server.
*/
public class DisconnectionRequestMessage extends MessageMeta {
private UUID playerAskingID;
public DisconnectionRequestMessage(UUID playerAskingID) {
this.playerAskingID = playerAskingID;
}
public DisconnectionRequestMessage() {
}
@Override
public void processData() {
}
public UUID getPlayerAskingID() {
return playerAskingID;
}
public void setPlayerAskingID(UUID playerAskingID) {
this.playerAskingID = playerAskingID;
}
}
package baleine.client.network.messages.clientToServer;
import baleine.common.message.MessageMeta;
import java.util.UUID;
/**
* Message containing a game save request.
*/
public class GameSaveRequestMessage extends MessageMeta {
private UUID gameID;
private UUID playerID;
public GameSaveRequestMessage(UUID gameID, UUID playerID) {
this.gameID = gameID;
this.playerID = playerID;
}
public GameSaveRequestMessage() {
}
@Override
public void processData() {
// TODO: Implement
}
public UUID getGameID() {
return gameID;
}
public void setGameID(UUID gameID) {
this.gameID = gameID;
}
public UUID getPlayerID() {
return playerID;
}
public void setPlayerID(UUID playerID) {
this.playerID = playerID;
}
}
package baleine.client.network.messages.clientToServer;
import baleine.common.message.MessageMeta;
import java.util.UUID;
/**
* Message containing the answer to a join request.
*/
public class JoinRequestAnswerMessage extends MessageMeta {
private UUID playerAskingID;
private UUID gameAskedID;
private Boolean answer;
public JoinRequestAnswerMessage(UUID playerAskingID, UUID gameAskedID, Boolean answer) {
this.playerAskingID = playerAskingID;
this.gameAskedID = gameAskedID;
this.answer = answer;
}
public JoinRequestAnswerMessage() {
}
@Override
public void processData() {
// TODO: Implement
}
public UUID getPlayerAskingID() {
return playerAskingID;
}
public void setPlayerAskingID(UUID playerAskingID) {
this.playerAskingID = playerAskingID;
}
public UUID getGameAskedID() {
return gameAskedID;
}
public void setGameAskedID(UUID gameAskedID) {
this.gameAskedID = gameAskedID;
}
public Boolean getAnswer() {
return answer;
}
public void setAnswer(Boolean answer) {
this.answer = answer;
}
}
package baleine.client.network.messages.clientToServer;
import baleine.common.dataModel.UserLight;
import baleine.common.interfaces.server.ComToDataImpl;
import baleine.common.message.MessageMeta;
import java.net.InetAddress;
/**
* Message containing a login request to send to the server.
*/
public class LoginUserToServerMessage extends MessageMeta {
private UserLight userToAdd;
private InetAddress userAddress;
public LoginUserToServerMessage(UserLight userToAdd) {
this.userToAdd = userToAdd;
}
public LoginUserToServerMessage(UserLight userToConnect, InetAddress address) {
this.userToAdd = userToConnect;
this.userAddress = address;