diff --git a/.gitignore b/.gitignore
index 8dfbde68..0fc6d408 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,8 @@ users
*.key
*.config
*.fagi
+*.log
+*.log.lck
.gradle/*
**/build/*
buildSrc/.gradle
\ No newline at end of file
diff --git a/fagiClient/build.gradle.kts b/fagiClient/build.gradle.kts
index fc5a93e9..2d3851fb 100644
--- a/fagiClient/build.gradle.kts
+++ b/fagiClient/build.gradle.kts
@@ -18,6 +18,8 @@ javafx {
dependencies {
implementation(project(":shared"))
+ testImplementation(testFixtures(project(":shared")))
+
testImplementation(libs.bundles.junit.base)
testImplementation(libs.bundles.mockito)
testImplementation(libs.hamcrest)
diff --git a/fagiClient/src/main/java/com/fagi/action/items/LoadFXML.java b/fagiClient/src/main/java/com/fagi/action/items/LoadFXML.java
index f8b94c0c..bde3ecb7 100644
--- a/fagiClient/src/main/java/com/fagi/action/items/LoadFXML.java
+++ b/fagiClient/src/main/java/com/fagi/action/items/LoadFXML.java
@@ -5,7 +5,8 @@
package com.fagi.action.items;
import com.fagi.action.Action;
-import com.fagi.utility.Logger;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
@@ -18,6 +19,7 @@
* @author miniwolf
*/
public record LoadFXML(String resourcePath) implements Action {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(LoadFXML.class);
@Override
public void execute(Parent parent) {
@@ -27,8 +29,10 @@ public void execute(Parent parent) {
try {
loader.load();
} catch (IOException ioe) {
- ioe.printStackTrace();
- Logger.logStackTrace(ioe);
+ LOGGER.error(
+ ioe,
+ () -> "Failed to load the FXML file: " + resourcePath
+ );
}
}
}
diff --git a/fagiClient/src/main/java/com/fagi/action/items/OpenConversationFromID.java b/fagiClient/src/main/java/com/fagi/action/items/OpenConversationFromID.java
index 4bbfcfe3..a4236bb9 100644
--- a/fagiClient/src/main/java/com/fagi/action/items/OpenConversationFromID.java
+++ b/fagiClient/src/main/java/com/fagi/action/items/OpenConversationFromID.java
@@ -6,6 +6,8 @@
import com.fagi.conversation.Conversation;
import com.fagi.conversation.ConversationType;
import com.fagi.conversation.GetAllConversationDataRequest;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import com.fagi.network.Communication;
import java.util.Optional;
@@ -14,6 +16,7 @@
* @author miniwolf
*/
public record OpenConversationFromID(MainScreen mainScreen) implements Action {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(OpenConversationFromID.class);
@Override
public void execute(Long id) {
Optional optional = mainScreen
@@ -58,7 +61,7 @@ private void addConversationToMain(
}
private void errorHandling(long id) {
- System.err.println("OpenConversationFromID: Couldn't find conversation on ID <" + id + ">");
+ LOGGER.error(() -> "OpenConversationFromID: Couldn't find conversation on ID <" + id + ">");
throw new RuntimeException();
}
}
diff --git a/fagiClient/src/main/java/com/fagi/controller/MainScreen.java b/fagiClient/src/main/java/com/fagi/controller/MainScreen.java
index c9de5f4a..d8962ed8 100644
--- a/fagiClient/src/main/java/com/fagi/controller/MainScreen.java
+++ b/fagiClient/src/main/java/com/fagi/controller/MainScreen.java
@@ -16,6 +16,8 @@
import com.fagi.conversation.Conversation;
import com.fagi.conversation.ConversationFilter;
import com.fagi.handler.Search;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import com.fagi.model.GetFriendListRequest;
import com.fagi.model.Logout;
import com.fagi.model.conversation.GetConversationsRequest;
@@ -30,7 +32,6 @@
import com.fagi.threads.ThreadPool;
import com.fagi.uimodel.FriendMapWrapper;
import com.fagi.utility.JsonFileOperations;
-import com.fagi.utility.Logger;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
@@ -60,6 +61,7 @@
* TODO: Write description.
*/
public class MainScreen extends Pane {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(MainScreen.class);
@FXML private Pane messages;
@FXML private Pane contacts;
@FXML private ScrollPane listContent;
@@ -139,11 +141,24 @@ public void initCommunication(ThreadPool threadPool) {
setupFriendList();
setupContactList();
- messageHandler = new TextMessageHandler(this, communication.getInputDistributor());
- threadPool.startThread(messageHandler.getRunnable(), "MessageHandler");
+ messageHandler = new TextMessageHandler(
+ this,
+ communication.getInputDistributor()
+ );
+ threadPool.startThread(
+ messageHandler.getRunnable(),
+ "MessageHandler"
+ );
- generalHandler = new GeneralHandlerFactory().construct(this, communication.getInputDistributor(), threadPool);
- threadPool.startThread(generalHandler.getRunnable(), "GeneralHandler");
+ generalHandler = new GeneralHandlerFactory().construct(
+ this,
+ communication.getInputDistributor(),
+ threadPool
+ );
+ threadPool.startThread(
+ generalHandler.getRunnable(),
+ "GeneralHandler"
+ );
updateConversationListFromServer(conversations);
}
@@ -161,15 +176,31 @@ private void initialize() {
emptyFocusElement = messages;
username.setText(usernameString);
char cUpper = Character.toUpperCase(usernameString.toCharArray()[0]);
- Image tiny = new Image("/style/material-icons/" + cUpper + ".png", 40, 40, true, true);
+ Image tiny = new Image(
+ "/style/material-icons/" + cUpper + ".png",
+ 40,
+ 40,
+ true,
+ true
+ );
this.tinyIcon.setImage(tiny);
- Image large = new Image("/style/material-icons/" + cUpper + ".png", 96, 96, true, true);
+ Image large = new Image(
+ "/style/material-icons/" + cUpper + ".png",
+ 96,
+ 96,
+ true,
+ true
+ );
this.largeIcon.setImage(large);
this.requestFocus();
Scene scene = primaryStage.getScene();
final MainScreen mainScreen = this;
- Platform.runLater(() -> search = new Search(searchBox, searchHeader, mainScreen));
+ Platform.runLater(() -> search = new Search(
+ searchBox,
+ searchHeader,
+ mainScreen
+ ));
scene
.widthProperty()
.addListener(new ChangeListener<>() {
@@ -183,14 +214,20 @@ public void changed(
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
- e.printStackTrace();
- Logger.logStackTrace(e);
+ LOGGER.info(() -> "Search thread was interrupted.");
} finally {
- Platform.runLater(() -> search = new Search(searchBox, searchHeader, mainScreen));
+ Platform.runLater(() -> search = new Search(
+ searchBox,
+ searchHeader,
+ mainScreen
+ ));
}
};
- threadPool.startThread(run, "Search thread");
+ threadPool.startThread(
+ run,
+ "Search thread"
+ );
scene
.widthProperty()
@@ -281,7 +318,10 @@ public void setScrollPaneContent(
Platform.runLater(() -> listContent.setContent(parent));
}
- listContentMap.put(content, parent);
+ listContentMap.put(
+ content,
+ parent
+ );
}
public void setFriendList(FriendList friendList) {
@@ -318,7 +358,7 @@ public void changeMenuStyle(String menu) {
currentPane = messages;
}
default -> {
- System.err.println("Mainscreen, changeMenuStyle: " + menu);
+ LOGGER.error(() -> "Failed to change MainScreen menu into unsupported menu type: " + menu);
throw new UnsupportedOperationException();
}
}
@@ -364,10 +404,16 @@ public Parent getListContent(PaneContent content) {
private void updateConversationListFromServer(List conversations) {
List filters = conversations
.stream()
- .map(x -> new ConversationFilter(x.getId(), x.getLastMessageDate()))
+ .map(x -> new ConversationFilter(
+ x.getId(),
+ x.getLastMessageDate()
+ ))
.collect(Collectors.toList());
- communication.sendObject(new GetConversationsRequest(usernameString, filters));
+ communication.sendObject(new GetConversationsRequest(
+ usernameString,
+ filters
+ ));
}
private void setupFriendList() {
@@ -376,12 +422,18 @@ private void setupFriendList() {
private void setupContactList() {
ContentController contactContentController = new ContentController("/view/content/ContentList.fxml");
- setScrollPaneContent(PaneContent.Contacts, contactContentController);
+ setScrollPaneContent(
+ PaneContent.Contacts,
+ contactContentController
+ );
}
private synchronized void setupConversationList() {
conversationContentController = new ContentController("/view/content/ContentList.fxml");
- setScrollPaneContent(PaneContent.Messages, conversationContentController);
+ setScrollPaneContent(
+ PaneContent.Messages,
+ conversationContentController
+ );
messageItems.forEach(MessageItemController::stopTimer);
messageItems.clear();
@@ -391,10 +443,11 @@ private synchronized void setupConversationList() {
}
public Pane createMessageItem(Conversation conversation) {
- MessageItemController messageItemController = new MessageItemController(usernameString,
- conversation,
- new OpenConversationFromID(this),
- conversation.getLastMessageDate()
+ MessageItemController messageItemController = new MessageItemController(
+ usernameString,
+ conversation,
+ new OpenConversationFromID(this),
+ conversation.getLastMessageDate()
);
messageItems.add(messageItemController);
return messageItemController;
diff --git a/fagiClient/src/main/java/com/fagi/controller/login/MasterLogin.java b/fagiClient/src/main/java/com/fagi/controller/login/MasterLogin.java
index e4ff756b..0aed6de4 100644
--- a/fagiClient/src/main/java/com/fagi/controller/login/MasterLogin.java
+++ b/fagiClient/src/main/java/com/fagi/controller/login/MasterLogin.java
@@ -6,6 +6,8 @@
import com.fagi.controller.utility.Draggable;
import com.fagi.enums.LoginState;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import com.fagi.main.FagiApp;
import com.fagi.network.ChatManager;
import com.fagi.network.Communication;
@@ -21,6 +23,7 @@
* class.
*/
public class MasterLogin {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(MasterLogin.class);
private final FagiApp fagiApp;
private final Communication communication;
private final Draggable draggable;
@@ -63,7 +66,10 @@ public void handleQuit() {
ChatManager.closeCommunication();
fagiApp.stop();
} catch (Exception ex) {
- System.err.println(ex.toString());
+ LOGGER.error(
+ ex,
+ () -> "Client encountered unexpected exception whilst attempting to shutdown."
+ );
}
}
@@ -90,7 +96,7 @@ public void next() {
case PASSWORD -> state = LoginState.INVITE_CODE;
case INVITE_CODE -> state = LoginState.LOGIN;
default -> {
- System.out.println(state + " is not known");
+ LOGGER.error(() -> state + " is not known");
throw new UnsupportedOperationException();
}
}
@@ -115,7 +121,7 @@ public void back() {
state = LoginState.PASSWORD;
break;
default:
- System.out.println(state + " is not known");
+ LOGGER.error(() -> state + " is not known");
throw new UnsupportedOperationException();
}
showScreen(state);
@@ -140,7 +146,10 @@ private boolean setupController(LoginState screen) {
LoginController controller;
switch (screen) {
case LOGIN:
- controller = new LoginScreenController(this, communication);
+ controller = new LoginScreenController(
+ this,
+ communication
+ );
break;
case USERNAME:
controller = new CreateUserNameController(this);
diff --git a/fagiClient/src/main/java/com/fagi/main/FagiApp.java b/fagiClient/src/main/java/com/fagi/main/FagiApp.java
index c2ae7481..ea9489ee 100644
--- a/fagiClient/src/main/java/com/fagi/main/FagiApp.java
+++ b/fagiClient/src/main/java/com/fagi/main/FagiApp.java
@@ -8,10 +8,11 @@
import com.fagi.controller.login.MasterLogin;
import com.fagi.controller.utility.Draggable;
import com.fagi.encryption.AES;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import com.fagi.network.ChatManager;
import com.fagi.network.Communication;
import com.fagi.threads.ThreadPool;
-import com.fagi.utility.Logger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
@@ -20,12 +21,14 @@
import javafx.stage.StageStyle;
import java.io.IOException;
+import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* JavaFX application class for handling GUI.
*/
public class FagiApp extends Application {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(FagiApp.class);
private Stage primaryStage;
private Scene scene;
private ThreadPool threadPool;
@@ -36,8 +39,12 @@ public class FagiApp extends Application {
* @param args the command line arguments
*/
public static void main(String[] args) {
+ if (!FagiLoggerFactory.isCustomConfigurationAvailable()) {
+ FagiLoggerFactory.setupDefaultConfiguration(Path.of("client.log"));
+ LOGGER.info(() -> "No log config file specified. Using default log config instead.");
+ }
if (args.length != 0) {
- System.out.println("Usage: java LoginScreen");
+ LOGGER.info(() -> "Usage: java LoginScreen");
}
launch(args);
}
@@ -80,8 +87,10 @@ private void startCommunication(
successfulConnection.set(true);
} catch (IOException e) {
Platform.runLater(() -> masterLogin.setMessageLabel("Connection refused"));
- e.printStackTrace();
- Logger.logStackTrace(e);
+ LOGGER.error(
+ e,
+ () -> "Failed to connect to the server."
+ );
}
});
diff --git a/fagiClient/src/main/java/com/fagi/network/ChatManager.java b/fagiClient/src/main/java/com/fagi/network/ChatManager.java
index c8401d37..473fbea0 100644
--- a/fagiClient/src/main/java/com/fagi/network/ChatManager.java
+++ b/fagiClient/src/main/java/com/fagi/network/ChatManager.java
@@ -4,6 +4,8 @@
* ChatManager.java
*/
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import com.fagi.main.FagiApp;
import com.fagi.model.CreateUser;
import com.fagi.model.InviteCode;
@@ -24,6 +26,7 @@
* Handles login requests and responds to and from server.
*/
public class ChatManager {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(ChatManager.class);
private static Communication communication = null;
private static FagiApp application;
private ServiceLoader communicationLoader;
@@ -45,7 +48,7 @@ public static void handleLogout(Logout logout) {
communication.sendObject(logout);
Response response = communication.getNextResponse();
if (!(response instanceof AllIsWell)) {
- System.err.println("Could not log out properly. " + "Shut down and let server handle the response");
+ LOGGER.error(() -> "Could not log out properly. Shut down and let server handle the response.");
}
communication.close();
application.showLoginScreen();
@@ -79,12 +82,16 @@ public static boolean handleCreateUser(
}
if (!isValidUserName(username)) {
- System.out.println(username);
+ LOGGER.info(() -> "The username " + username + " is not valid.");
labelMessage.setText("Username may not contain special symbols");
return false;
}
- CreateUser createUser = new CreateUser(username, password, new InviteCode(inviteCode));
+ CreateUser createUser = new CreateUser(
+ username,
+ password,
+ new InviteCode(inviteCode)
+ );
communication.sendObject(createUser);
Response response = communication.getNextResponse();
@@ -128,7 +135,10 @@ public static void setApplication(FagiApp application) {
}
public static boolean isValidUserName(String string) {
- return Pattern.matches("\\w*", string);
+ return Pattern.matches(
+ "\\w*",
+ string
+ );
}
public static FagiApp getApplication() {
diff --git a/fagiClient/src/main/java/com/fagi/network/Communication.java b/fagiClient/src/main/java/com/fagi/network/Communication.java
index b39156e2..28b4dbf0 100644
--- a/fagiClient/src/main/java/com/fagi/network/Communication.java
+++ b/fagiClient/src/main/java/com/fagi/network/Communication.java
@@ -13,12 +13,13 @@
import com.fagi.encryption.EncryptionAlgorithm;
import com.fagi.encryption.RSA;
import com.fagi.encryption.RSAKey;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import com.fagi.model.HistoryUpdates;
import com.fagi.model.Session;
import com.fagi.responses.AllIsWell;
import com.fagi.responses.Response;
import com.fagi.threads.ThreadPool;
-import com.fagi.utility.Logger;
import java.io.IOException;
import java.io.ObjectInputStream;
@@ -32,6 +33,7 @@
* TODO: Add description
*/
public class Communication {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(Communication.class);
private ObjectOutputStream out;
private InputHandler inputHandler;
private Socket socket;
@@ -76,12 +78,11 @@ public void connect(
encryption,
serverKey
);
- } catch (UnknownHostException Uhe) {
- System.err.println("c Uhe: " + Uhe);
- Logger.logStackTrace(Uhe);
- } catch (IOException ioe) {
- Logger.logStackTrace(ioe);
- throw new IOException("c ioe: " + ioe.toString());
+ } catch (UnknownHostException uhe) {
+ LOGGER.error(
+ uhe,
+ () -> "Failed to connect to the following host: " + host
+ );
}
}
@@ -123,9 +124,10 @@ public void sendObject(Object obj) {
out.writeObject(encryption.encrypt(Conversion.convertToBytes(obj)));
out.flush();
} catch (IOException e) {
- System.err.println("cso ioe: " + e.toString());
- e.printStackTrace();
- Logger.logStackTrace(e);
+ LOGGER.error(
+ e,
+ () -> "Lost connection to server. Crashing now."
+ );
System.exit(1);
}
}
@@ -136,7 +138,10 @@ public void close() {
try {
socket.close();
} catch (IOException ioe) {
- System.err.println("cc ioe: " + ioe.toString());
+ LOGGER.error(
+ ioe,
+ () -> "Failed to close Communication gracefully."
+ );
}
}
diff --git a/fagiClient/src/main/java/com/fagi/network/InputHandler.java b/fagiClient/src/main/java/com/fagi/network/InputHandler.java
index 234602d6..b30d4bd1 100644
--- a/fagiClient/src/main/java/com/fagi/network/InputHandler.java
+++ b/fagiClient/src/main/java/com/fagi/network/InputHandler.java
@@ -8,11 +8,12 @@
import com.fagi.conversation.Conversation;
import com.fagi.encryption.Conversion;
import com.fagi.encryption.EncryptionAlgorithm;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import com.fagi.model.HistoryUpdates;
import com.fagi.model.messages.InGoingMessages;
import com.fagi.responses.Response;
import com.fagi.threads.ThreadPool;
-import com.fagi.utility.Logger;
import javafx.application.Platform;
import java.io.IOException;
@@ -25,6 +26,7 @@
* TODO: Write description
*/
public class InputHandler implements Runnable {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(InputHandler.class);
private final Queue inputs = new LinkedBlockingQueue<>();
private final ObjectInputStream in;
private final EncryptionAlgorithm encryption;
@@ -53,9 +55,10 @@ public void run() {
handleInput(Conversion.convertFromBytes(encryption.decrypt(input)));
} catch (IOException ioe) {
if (running) {
- System.err.println("inputhandler ioe: " + ioe.toString());
- ioe.printStackTrace(); // DEBUG need to terminate before closing socket.
- Logger.logStackTrace(ioe);
+ LOGGER.error(
+ ioe,
+ () -> "Failed to receive data from server. Logging user out."
+ );
}
running = false;
ChatManager.closeCommunication();
@@ -63,9 +66,11 @@ public void run() {
.getApplication()
.showLoginScreen());
} catch (ClassNotFoundException cnfe) {
+ LOGGER.error(
+ cnfe,
+ () -> "Failed to parse response data from server."
+ );
// Shared files are not the same on both side of the server
- System.err.println(cnfe.getMessage());
- Logger.logStackTrace(cnfe);
// TODO: This will be a bitch when having to update the server
// Fix: could be to implement JSON
}
diff --git a/fagiClient/src/main/java/com/fagi/network/handlers/DefaultThreadHandler.java b/fagiClient/src/main/java/com/fagi/network/handlers/DefaultThreadHandler.java
index 81539fff..06984e86 100644
--- a/fagiClient/src/main/java/com/fagi/network/handlers/DefaultThreadHandler.java
+++ b/fagiClient/src/main/java/com/fagi/network/handlers/DefaultThreadHandler.java
@@ -7,11 +7,13 @@
import com.fagi.network.handlers.container.Container;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Logger;
/**
* @author miniwolf
*/
public class DefaultThreadHandler implements Runnable {
+ private static final Logger LOGGER = Logger.getLogger(DefaultThreadHandler.class.getName());
private Container container;
private Handler handler;
private AtomicBoolean running = new AtomicBoolean(true);
@@ -39,7 +41,7 @@ public void run() {
}
} catch (InterruptedException e) {
running.set(false);
- System.out.println("Stopped the thread handler");
+ LOGGER.info(() -> "Stopped the thread handler");
}
}
}
diff --git a/fagiClient/src/main/java/com/fagi/network/handlers/GeneralHandler.java b/fagiClient/src/main/java/com/fagi/network/handlers/GeneralHandler.java
index d0113198..129ea653 100644
--- a/fagiClient/src/main/java/com/fagi/network/handlers/GeneralHandler.java
+++ b/fagiClient/src/main/java/com/fagi/network/handlers/GeneralHandler.java
@@ -9,17 +9,22 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Logger;
/**
* Created by Marcus on 08-07-2016.
*/
public class GeneralHandler implements Handler {
+ private static final Logger LOGGER = Logger.getLogger(GeneralHandler.class.getName());
private final Map> handlers = new ConcurrentHashMap<>();
private final Container container = new DefaultContainer<>();
private final InputDistributor inputDistributor;
private final List unhandledObjects = new ArrayList<>();
private final ThreadPool threadPool;
- private DefaultThreadHandler runnable = new DefaultThreadHandler<>(container, this);
+ private DefaultThreadHandler runnable = new DefaultThreadHandler<>(
+ container,
+ this
+ );
public GeneralHandler(
InputDistributor inputDistributor,
@@ -34,19 +39,28 @@ public void handle(T object) {
Handler handler = handlers.get(object.getClass());
if (handler == null) {
- System.err.println("Missing handler: " + object.getClass());
+ LOGGER.severe(() -> "Missing handler: " + object.getClass());
unhandledObjects.add(object);
return;
}
- threadPool.startThread(() -> handler.handle(object), "GeneralHandler: " + object.getClass());
+ threadPool.startThread(
+ () -> handler.handle(object),
+ "GeneralHandler: " + object.getClass()
+ );
}
public void registerHandler(
Class clazz,
Handler handler) {
- handlers.put(clazz, handler);
- inputDistributor.register(clazz, container);
+ handlers.put(
+ clazz,
+ handler
+ );
+ inputDistributor.register(
+ clazz,
+ container
+ );
}
@Override
diff --git a/fagiClient/src/main/java/com/fagi/network/handlers/TextMessageHandler.java b/fagiClient/src/main/java/com/fagi/network/handlers/TextMessageHandler.java
index d8a573dc..4bd48830 100644
--- a/fagiClient/src/main/java/com/fagi/network/handlers/TextMessageHandler.java
+++ b/fagiClient/src/main/java/com/fagi/network/handlers/TextMessageHandler.java
@@ -18,20 +18,28 @@
import javafx.application.Platform;
import java.util.Optional;
+import java.util.logging.Logger;
/**
* @author miniwolf
*/
public class TextMessageHandler implements Handler {
+ private static final Logger LOGGER = Logger.getLogger(TextMessageHandler.class.getName());
private Container container = new DefaultContainer<>();
- private DefaultThreadHandler runnable = new DefaultThreadHandler<>(container, this);
+ private DefaultThreadHandler runnable = new DefaultThreadHandler<>(
+ container,
+ this
+ );
private final MainScreen mainScreen;
public TextMessageHandler(
MainScreen mainScreen,
InputDistributor inputDistributor) {
container.setThread(runnable);
- inputDistributor.register(TextMessage.class, container);
+ inputDistributor.register(
+ TextMessage.class,
+ container
+ );
this.mainScreen = mainScreen;
}
@@ -45,7 +53,7 @@ public void handle(TextMessage message) {
.getConversationID())
.findFirst();
if (first.isEmpty()) {
- System.err.println("Server sent a message before it sent the conversation of ID '" + message
+ LOGGER.severe(() -> "Server sent a message before it sent the conversation of ID '" + message
.getMessageInfo()
.getConversationID() + "'to the profile.");
return;
@@ -60,7 +68,10 @@ public void handle(TextMessage message) {
conversation.setType(type);
mainScreen
.getCommunication()
- .sendObject(new GetAllConversationDataRequest(mainScreen.getUsername(), conversation.getId()));
+ .sendObject(new GetAllConversationDataRequest(
+ mainScreen.getUsername(),
+ conversation.getId()
+ ));
}
if (mainScreen.hasCurrentOpenConversation(conversation)) {
@@ -71,7 +82,10 @@ public void handle(TextMessage message) {
conversation
.data()
.addMessage(message);
- JsonFileOperations.storeClientConversation(conversation, mainScreen.getUsername());
+ JsonFileOperations.storeClientConversation(
+ conversation,
+ mainScreen.getUsername()
+ );
MessageItemController messageItemController = mainScreen
.getMessageItems()
diff --git a/fagiClient/src/main/java/com/fagi/threads/ThreadPool.java b/fagiClient/src/main/java/com/fagi/threads/ThreadPool.java
index 75927256..f504a139 100644
--- a/fagiClient/src/main/java/com/fagi/threads/ThreadPool.java
+++ b/fagiClient/src/main/java/com/fagi/threads/ThreadPool.java
@@ -42,8 +42,4 @@ public void stopThreads() {
threads.forEach(Thread::interrupt);
threads.clear();
}
-
- public void printThreads() {
- threads.forEach(thread -> System.out.println("Thread: " + thread.getName() + " alive: " + thread.isAlive()));
- }
}
diff --git a/fagiClient/src/test/java/com/fagi/action/items/LoadFXMLTest.java b/fagiClient/src/test/java/com/fagi/action/items/LoadFXMLTest.java
index b483160c..6fb82922 100644
--- a/fagiClient/src/test/java/com/fagi/action/items/LoadFXMLTest.java
+++ b/fagiClient/src/test/java/com/fagi/action/items/LoadFXMLTest.java
@@ -1,5 +1,6 @@
package com.fagi.action.items;
+import com.fagi.BaseFagiTest;
import com.fagi.controller.MainScreen;
import com.fagi.controller.conversation.ConversationController;
import com.fagi.conversation.Conversation;
@@ -14,7 +15,7 @@
* Created by miniwolf on 01-04-2017.
*/
@ExtendWith(JavaFXThreadingExtension.class)
-public class LoadFXMLTest {
+public class LoadFXMLTest extends BaseFagiTest {
private LoadFXML loadFXML;
private ConversationController mock;
diff --git a/fagiClient/src/test/java/com/fagi/guitests/ConversationTests.java b/fagiClient/src/test/java/com/fagi/guitests/ConversationTests.java
index 27003622..19093e57 100644
--- a/fagiClient/src/test/java/com/fagi/guitests/ConversationTests.java
+++ b/fagiClient/src/test/java/com/fagi/guitests/ConversationTests.java
@@ -1,5 +1,6 @@
package com.fagi.guitests;
+import com.fagi.BaseFagiTest;
import com.fagi.controller.MainScreen;
import com.fagi.controller.login.MasterLogin;
import com.fagi.controller.utility.Draggable;
@@ -44,7 +45,7 @@
import java.util.List;
@ExtendWith(ApplicationExtension.class)
-public class ConversationTests {
+public class ConversationTests extends BaseFagiTest {
private InputHandler inputHandler;
private Communication communication;
diff --git a/fagiClient/src/test/java/com/fagi/guitests/CreatePasswordTests.java b/fagiClient/src/test/java/com/fagi/guitests/CreatePasswordTests.java
index 220c1d1f..51a0239a 100644
--- a/fagiClient/src/test/java/com/fagi/guitests/CreatePasswordTests.java
+++ b/fagiClient/src/test/java/com/fagi/guitests/CreatePasswordTests.java
@@ -1,5 +1,6 @@
package com.fagi.guitests;
+import com.fagi.BaseFagiTest;
import com.fagi.controller.login.MasterLogin;
import com.fagi.controller.utility.Draggable;
import com.fagi.enums.LoginState;
@@ -25,7 +26,7 @@
import org.testfx.matcher.base.NodeMatchers;
@ExtendWith(ApplicationExtension.class)
-public class CreatePasswordTests {
+public class CreatePasswordTests extends BaseFagiTest {
private MasterLogin masterLogin;
@BeforeAll
diff --git a/fagiClient/src/test/java/com/fagi/guitests/CreateUserNameTests.java b/fagiClient/src/test/java/com/fagi/guitests/CreateUserNameTests.java
index 604364d8..fc971c1e 100644
--- a/fagiClient/src/test/java/com/fagi/guitests/CreateUserNameTests.java
+++ b/fagiClient/src/test/java/com/fagi/guitests/CreateUserNameTests.java
@@ -1,5 +1,6 @@
package com.fagi.guitests;
+import com.fagi.BaseFagiTest;
import com.fagi.controller.login.MasterLogin;
import com.fagi.controller.utility.Draggable;
import com.fagi.enums.LoginState;
@@ -28,7 +29,7 @@
import org.testfx.matcher.base.NodeMatchers;
@ExtendWith(ApplicationExtension.class)
-public class CreateUserNameTests {
+public class CreateUserNameTests extends BaseFagiTest {
private Communication communication;
private MasterLogin masterLogin;
diff --git a/fagiClient/src/test/java/com/fagi/guitests/FriendsTests.java b/fagiClient/src/test/java/com/fagi/guitests/FriendsTests.java
index c1b7615c..aca3a1a8 100644
--- a/fagiClient/src/test/java/com/fagi/guitests/FriendsTests.java
+++ b/fagiClient/src/test/java/com/fagi/guitests/FriendsTests.java
@@ -1,5 +1,6 @@
package com.fagi.guitests;
+import com.fagi.BaseFagiTest;
import com.fagi.controller.MainScreen;
import com.fagi.controller.login.MasterLogin;
import com.fagi.controller.utility.Draggable;
@@ -44,7 +45,7 @@
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
@ExtendWith(ApplicationExtension.class)
-public class FriendsTests {
+public class FriendsTests extends BaseFagiTest {
private InputHandler inputHandler;
private final ThreadPool threadPool = new ThreadPool();
diff --git a/fagiClient/src/test/java/com/fagi/guitests/InviteCodeTests.java b/fagiClient/src/test/java/com/fagi/guitests/InviteCodeTests.java
index 1057dbbf..b550d407 100644
--- a/fagiClient/src/test/java/com/fagi/guitests/InviteCodeTests.java
+++ b/fagiClient/src/test/java/com/fagi/guitests/InviteCodeTests.java
@@ -1,5 +1,6 @@
package com.fagi.guitests;
+import com.fagi.BaseFagiTest;
import com.fagi.controller.login.MasterLogin;
import com.fagi.controller.utility.Draggable;
import com.fagi.enums.LoginState;
@@ -26,7 +27,7 @@
import org.testfx.framework.junit5.Start;
@ExtendWith(ApplicationExtension.class)
-public class InviteCodeTests {
+public class InviteCodeTests extends BaseFagiTest {
private Communication communication;
private MasterLogin masterLogin;
diff --git a/fagiClient/src/test/java/com/fagi/guitests/LoginTests.java b/fagiClient/src/test/java/com/fagi/guitests/LoginTests.java
index 2dc54ce6..5bae185a 100644
--- a/fagiClient/src/test/java/com/fagi/guitests/LoginTests.java
+++ b/fagiClient/src/test/java/com/fagi/guitests/LoginTests.java
@@ -1,5 +1,6 @@
package com.fagi.guitests;
+import com.fagi.BaseFagiTest;
import com.fagi.controller.login.MasterLogin;
import com.fagi.controller.utility.Draggable;
import com.fagi.enums.LoginState;
@@ -30,7 +31,7 @@
import org.testfx.util.WaitForAsyncUtils;
@ExtendWith(ApplicationExtension.class)
-public class LoginTests {
+public class LoginTests extends BaseFagiTest {
private MasterLogin masterLogin;
private Communication communication;
diff --git a/fagiClient/src/test/java/com/fagi/guitests/ReceiveFriendRequestTests.java b/fagiClient/src/test/java/com/fagi/guitests/ReceiveFriendRequestTests.java
index 5322acfb..aad32805 100644
--- a/fagiClient/src/test/java/com/fagi/guitests/ReceiveFriendRequestTests.java
+++ b/fagiClient/src/test/java/com/fagi/guitests/ReceiveFriendRequestTests.java
@@ -1,5 +1,6 @@
package com.fagi.guitests;
+import com.fagi.BaseFagiTest;
import com.fagi.controller.MainScreen;
import com.fagi.controller.login.MasterLogin;
import com.fagi.controller.utility.Draggable;
@@ -34,7 +35,7 @@
import static com.fagi.helpers.WaitForFXEventsTestHelper.addIngoingMessageToInputHandler;
@ExtendWith(ApplicationExtension.class)
-public class ReceiveFriendRequestTests {
+public class ReceiveFriendRequestTests extends BaseFagiTest {
private static final String myUsername = "Test";
private Communication communication;
private InputHandler inputHandler;
diff --git a/fagiClient/src/test/java/com/fagi/guitests/SearchUserTests.java b/fagiClient/src/test/java/com/fagi/guitests/SearchUserTests.java
index 00352239..e72237b1 100644
--- a/fagiClient/src/test/java/com/fagi/guitests/SearchUserTests.java
+++ b/fagiClient/src/test/java/com/fagi/guitests/SearchUserTests.java
@@ -1,5 +1,6 @@
package com.fagi.guitests;
+import com.fagi.BaseFagiTest;
import com.fagi.controller.MainScreen;
import com.fagi.controller.login.MasterLogin;
import com.fagi.controller.utility.Draggable;
@@ -46,7 +47,7 @@
import static com.fagi.helpers.WaitForFXEventsTestHelper.addIngoingMessageToInputHandler;
@ExtendWith(ApplicationExtension.class)
-public class SearchUserTests {
+public class SearchUserTests extends BaseFagiTest {
private Communication communication;
private InputHandler inputHandler;
private final ThreadPool threadPool = new ThreadPool();
diff --git a/fagiClient/src/test/java/com/fagi/guitests/SendFriendRequestTests.java b/fagiClient/src/test/java/com/fagi/guitests/SendFriendRequestTests.java
index 5184cb3a..7a77b46d 100644
--- a/fagiClient/src/test/java/com/fagi/guitests/SendFriendRequestTests.java
+++ b/fagiClient/src/test/java/com/fagi/guitests/SendFriendRequestTests.java
@@ -1,5 +1,6 @@
package com.fagi.guitests;
+import com.fagi.BaseFagiTest;
import com.fagi.controller.MainScreen;
import com.fagi.controller.login.MasterLogin;
import com.fagi.controller.utility.Draggable;
@@ -35,7 +36,7 @@
import static com.fagi.helpers.WaitForFXEventsTestHelper.addIngoingMessageToInputHandler;
@ExtendWith(ApplicationExtension.class)
-public class SendFriendRequestTests {
+public class SendFriendRequestTests extends BaseFagiTest {
private Communication communication;
private InputHandler inputHandler;
private final ThreadPool threadPool = new ThreadPool();
diff --git a/fagiClient/src/test/java/com/fagi/guitests/SignOutTests.java b/fagiClient/src/test/java/com/fagi/guitests/SignOutTests.java
index 6b23d73e..09874402 100644
--- a/fagiClient/src/test/java/com/fagi/guitests/SignOutTests.java
+++ b/fagiClient/src/test/java/com/fagi/guitests/SignOutTests.java
@@ -1,5 +1,6 @@
package com.fagi.guitests;
+import com.fagi.BaseFagiTest;
import com.fagi.controller.MainScreen;
import com.fagi.controller.login.MasterLogin;
import com.fagi.controller.utility.Draggable;
@@ -26,7 +27,7 @@
import org.testfx.matcher.base.NodeMatchers;
@ExtendWith(ApplicationExtension.class)
-public class SignOutTests {
+public class SignOutTests extends BaseFagiTest {
private final ThreadPool threadPool = new ThreadPool();
@BeforeAll
diff --git a/fagiClient/src/test/java/com/fagi/network/CommunicationTest.java b/fagiClient/src/test/java/com/fagi/network/CommunicationTest.java
index 9fe8838b..56bb45f5 100644
--- a/fagiClient/src/test/java/com/fagi/network/CommunicationTest.java
+++ b/fagiClient/src/test/java/com/fagi/network/CommunicationTest.java
@@ -1,5 +1,6 @@
package com.fagi.network;
+import com.fagi.BaseFagiTest;
import com.fagi.encryption.Conversion;
import com.fagi.encryption.EncryptionAlgorithm;
import org.junit.jupiter.api.BeforeEach;
@@ -13,7 +14,7 @@
*
* @author miniwolf
*/
-public class CommunicationTest {
+public class CommunicationTest extends BaseFagiTest {
private Communication communication;
@BeforeEach
diff --git a/fagiClient/src/test/java/com/fagi/util/FontUtilsTest.java b/fagiClient/src/test/java/com/fagi/util/FontUtilsTest.java
index ead62759..b1a74e0e 100644
--- a/fagiClient/src/test/java/com/fagi/util/FontUtilsTest.java
+++ b/fagiClient/src/test/java/com/fagi/util/FontUtilsTest.java
@@ -1,5 +1,6 @@
package com.fagi.util;
+import com.fagi.BaseFagiTest;
import javafx.scene.text.Font;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -34,7 +35,7 @@ private boolean isWindows() {
* @author miniwolf
*/
@ExtendWith({JavaFXThreadingExtension.class, DisableOnLinuxAndMacCondition.class})
-public class FontUtilsTest {
+public class FontUtilsTest extends BaseFagiTest {
private static final Font ROBOTO = new Font("Roboto-Regular", 13);
@Test
diff --git a/fagiServer/build.gradle.kts b/fagiServer/build.gradle.kts
index 22655d3c..d5224630 100644
--- a/fagiServer/build.gradle.kts
+++ b/fagiServer/build.gradle.kts
@@ -12,6 +12,8 @@ tasks.test {
dependencies {
implementation(project(":shared"))
+
+ testImplementation(testFixtures(project(":shared")))
testImplementation(libs.bundles.junit.base)
testImplementation(libs.bundles.mockito)
}
diff --git a/fagiServer/src/main/java/com/fagi/encryption/Encryption.java b/fagiServer/src/main/java/com/fagi/encryption/Encryption.java
index 91bea5e5..fabe5db7 100644
--- a/fagiServer/src/main/java/com/fagi/encryption/Encryption.java
+++ b/fagiServer/src/main/java/com/fagi/encryption/Encryption.java
@@ -1,5 +1,8 @@
package com.fagi.encryption;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
+
import java.io.File;
import java.io.IOException;
import java.security.KeyPair;
@@ -10,6 +13,7 @@
* Created by Marcus on 04-06-2016.
*/
public class Encryption {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(Encryption.class);
private static Encryption instance;
private RSA rsa;
@@ -24,14 +28,20 @@ public class Encryption {
try {
KeyStorage.SaveKeyPair(key);
} catch (IOException e) {
- e.printStackTrace();
+ LOGGER.error(
+ e,
+ () -> "Could not save RSA key pair."
+ );
}
} else {
try {
KeyPair key = KeyStorage.LoadKeyPair("RSA");
this.rsa = new RSA(key);
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
- e.printStackTrace();
+ LOGGER.error(
+ e,
+ () -> "Could not load RSA key pair."
+ );
}
}
diff --git a/fagiServer/src/main/java/com/fagi/handler/ConversationHandler.java b/fagiServer/src/main/java/com/fagi/handler/ConversationHandler.java
index a818a73f..4374d4bb 100644
--- a/fagiServer/src/main/java/com/fagi/handler/ConversationHandler.java
+++ b/fagiServer/src/main/java/com/fagi/handler/ConversationHandler.java
@@ -1,6 +1,8 @@
package com.fagi.handler;
import com.fagi.conversation.Conversation;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import com.fagi.model.Data;
import com.fagi.model.messages.message.TextMessage;
@@ -10,6 +12,7 @@
* Created by Marcus on 04-07-2016.
*/
public class ConversationHandler implements Runnable {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(ConversationHandler.class);
private final Data data;
private final LinkedBlockingQueue queue = new LinkedBlockingQueue<>();
@@ -44,7 +47,10 @@ public void tick() {
data.storeConversation(conversation);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
- ie.printStackTrace();
+ LOGGER.debug(
+ ie,
+ () -> "Interrupted ConversationHandler thread."
+ );
}
}
diff --git a/fagiServer/src/main/java/com/fagi/handler/InputHandler.java b/fagiServer/src/main/java/com/fagi/handler/InputHandler.java
index 3d09fa62..1b8abfe4 100644
--- a/fagiServer/src/main/java/com/fagi/handler/InputHandler.java
+++ b/fagiServer/src/main/java/com/fagi/handler/InputHandler.java
@@ -4,6 +4,8 @@
import com.fagi.conversation.ConversationDataUpdate;
import com.fagi.conversation.GetAllConversationDataRequest;
import com.fagi.encryption.AES;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import com.fagi.model.CreateUser;
import com.fagi.model.Data;
import com.fagi.model.DeleteFriend;
@@ -50,9 +52,10 @@
import java.util.stream.Collectors;
public record InputHandler(InputAgent inputAgent, OutputAgent out, ConversationHandler conversationHandler, Data data) {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(InputHandler.class);
public void handleInput(Object input) {
if (input == null) {
- System.out.println("Input is null. Doing nothing.");
+ LOGGER.info(() -> "Input is null. Doing nothing.");
} else if (input instanceof TextMessage arg) {
MessageInfo messageInfo = arg.getMessageInfo();
messageInfo.setTimestamp(new Timestamp(System.currentTimeMillis()));
@@ -102,7 +105,7 @@ public void handleInput(Object input) {
} else if (input instanceof UserNameAvailableRequest request) {
out.addResponse(handleUserNameAvailableRequest(request));
} else {
- System.out.println("Unknown handle: " + input.getClass());
+ LOGGER.info(() -> "Unknown handle: " + input.getClass());
}
}
diff --git a/fagiServer/src/main/java/com/fagi/main/Main.java b/fagiServer/src/main/java/com/fagi/main/Main.java
index 5021a448..ca402ee5 100644
--- a/fagiServer/src/main/java/com/fagi/main/Main.java
+++ b/fagiServer/src/main/java/com/fagi/main/Main.java
@@ -4,30 +4,46 @@
* Main.java
*/
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import com.fagi.model.Data;
import com.fagi.server.Server;
import java.io.IOException;
import java.net.ServerSocket;
+import java.nio.file.Path;
/**
* Handling server start.
*/
class Main {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(Main.class);
+
public static void main(String[] args) {
Data data = new Data();
data.loadUsers();
int port = args.length > 0 ? Integer.parseInt(args[0]) : 4242;
- Server server = new Server(port, data);
+ Server server = new Server(
+ port,
+ data
+ );
+
+ if (!FagiLoggerFactory.isCustomConfigurationAvailable()) {
+ FagiLoggerFactory.setupDefaultConfiguration(Path.of("server.log"));
+ LOGGER.info(() -> "No log config file specified. Using default log config instead.");
+ }
try {
var serverSocket = new ServerSocket(port);
server.start(serverSocket);
} catch (IOException e) {
- System.out.println("Error while creating socket, are you sure you can use port " + port + " on you system?");
+ LOGGER.error(
+ e,
+ () -> "Error while creating socket, are you sure you can use port " + port + " on you system?"
+ );
}
}
}
\ No newline at end of file
diff --git a/fagiServer/src/main/java/com/fagi/model/Data.java b/fagiServer/src/main/java/com/fagi/model/Data.java
index 1b5ff75e..4737f195 100644
--- a/fagiServer/src/main/java/com/fagi/model/Data.java
+++ b/fagiServer/src/main/java/com/fagi/model/Data.java
@@ -6,6 +6,8 @@
import com.fagi.conversation.Conversation;
import com.fagi.conversation.ConversationType;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import com.fagi.responses.AllIsWell;
import com.fagi.responses.NoSuchUser;
import com.fagi.responses.PasswordError;
@@ -31,6 +33,7 @@
* Contains and update information on users.
*/
public class Data {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(Data.class);
private final Map OUTPUT_AGENT_MAP = new ConcurrentHashMap<>();
private final Map INPUT_AGENT_MAP = new ConcurrentHashMap<>();
private final Map registeredUsers = new ConcurrentHashMap<>();
@@ -164,7 +167,7 @@ public void userLogout(String userName) {
OUTPUT_AGENT_MAP.remove(userName);
INPUT_AGENT_MAP.remove(userName);
} else {
- System.out.println("Couldn't log " + userName + " out");
+ LOGGER.info(() -> "Couldn't log " + userName + " out");
}
}
diff --git a/fagiServer/src/main/java/com/fagi/server/Server.java b/fagiServer/src/main/java/com/fagi/server/Server.java
index 9beda6d7..82a96a74 100644
--- a/fagiServer/src/main/java/com/fagi/server/Server.java
+++ b/fagiServer/src/main/java/com/fagi/server/Server.java
@@ -10,6 +10,8 @@
import com.fagi.encryption.Encryption;
import com.fagi.encryption.RSAKey;
import com.fagi.handler.ConversationHandler;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import com.fagi.model.Data;
import com.fagi.model.InviteCodeContainer;
import com.fagi.running.CheckFieldRunningStrategy;
@@ -30,6 +32,7 @@
import java.util.List;
public class Server {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(Server.class);
static final String CONFIG_FILE = "config/serverinfo.config";
private final Data data;
private IsRunningStrategy isRunningStrategy = new CheckFieldRunningStrategy();
@@ -64,12 +67,15 @@ public Server(
data.storeInviteCodes(new InviteCodeContainer(new ArrayList<>()));
}
} catch (IOException e) {
- e.printStackTrace();
+ LOGGER.error(
+ e,
+ () -> "Could not create server."
+ );
}
}
public void start(ServerSocket serverSocket) {
- System.out.println("Starting Server");
+ LOGGER.info(() -> "Starting Server");
conversationHandlerThread = new Thread(handler);
conversationHandlerThread.setDaemon(true);
@@ -80,20 +86,26 @@ public void start(ServerSocket serverSocket) {
try {
workerCreation(serverSocket);
} catch (IOException e) {
- System.out.println("Error in server loop exception = " + e);
+ LOGGER.error(
+ e,
+ () -> "Error in server loop exception"
+ );
isRunningStrategy.stop();
}
}
conversationHandlerThread.interrupt();
- System.out.println("Stopping Server");
+ LOGGER.info(() -> "Stopping Server");
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
- e.printStackTrace();
+ LOGGER.error(
+ e,
+ () -> "Failed to close server socket gracefully."
+ );
}
}
}
diff --git a/fagiServer/src/main/java/com/fagi/worker/InputWorker.java b/fagiServer/src/main/java/com/fagi/worker/InputWorker.java
index e0eab39b..b45b2b4c 100644
--- a/fagiServer/src/main/java/com/fagi/worker/InputWorker.java
+++ b/fagiServer/src/main/java/com/fagi/worker/InputWorker.java
@@ -9,6 +9,8 @@
import com.fagi.encryption.EncryptionAlgorithm;
import com.fagi.handler.ConversationHandler;
import com.fagi.handler.InputHandler;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import com.fagi.model.Data;
import java.io.EOFException;
@@ -20,6 +22,7 @@
* @author miniwolf
*/
public class InputWorker extends Worker implements InputAgent {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(InputWorker.class);
private final InputHandler inputHandler;
private final Data data;
private final ObjectInputStream objIn;
@@ -35,9 +38,6 @@ public InputWorker(
ConversationHandler handler,
Data data) {
this.data = data;
- // TODO: This sysout does not make sense. Should be in the run method or where the thread is started.
- // https://trello.com/c/SVazRIgj/58-inputworker-should-not-print-starting-an-input-thread-in-its-constructor
- System.out.println("Starting an input thread");
this.objIn = objIn;
this.out = out;
this.inputHandler = new InputHandler(
@@ -50,8 +50,9 @@ public InputWorker(
@Override
public void run() {
+ LOGGER.info(() -> "Starting an input thread");
while (isRunningStrategy.isRunning()) {
- System.out.println("Running");
+ LOGGER.info(() -> "Running");
try {
Object input = objIn.readObject();
@@ -64,19 +65,21 @@ public void run() {
inputHandler.handleInput(input);
} catch (EOFException | SocketException eof) {
stop();
- System.out.println("Logging out user " + myUserName);
+ LOGGER.info(() -> "Logging out user " + myUserName);
out.stop();
data.userLogout(myUserName);
} catch (Exception e) {
stop();
out.stop();
- System.out.println("Something went wrong in a input worker while loop " + e);
- e.printStackTrace();
- System.out.println("Logging out user " + myUserName);
+ LOGGER.error(
+ e,
+ () -> "Something went wrong in a input worker while loop."
+ );
+ LOGGER.info(() -> "Logging out user " + myUserName);
data.userLogout(myUserName);
}
}
- System.out.println("Closing input");
+ LOGGER.info(() -> "Closing input.");
}
private Object decryptAndConvertToObject(byte[] input) {
@@ -89,7 +92,10 @@ private Object decryptAndConvertToObject(byte[] input) {
try {
return Conversion.convertFromBytes(input);
} catch (IOException | ClassNotFoundException e) {
- e.printStackTrace();
+ LOGGER.error(
+ e,
+ () -> "Failed to decrypt or deserialize object."
+ );
}
return null;
}
diff --git a/fagiServer/src/main/java/com/fagi/worker/OutputWorker.java b/fagiServer/src/main/java/com/fagi/worker/OutputWorker.java
index 5203f6e4..c30bbbcf 100644
--- a/fagiServer/src/main/java/com/fagi/worker/OutputWorker.java
+++ b/fagiServer/src/main/java/com/fagi/worker/OutputWorker.java
@@ -6,6 +6,8 @@
import com.fagi.encryption.AESKey;
import com.fagi.encryption.Conversion;
import com.fagi.encryption.EncryptionAlgorithm;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import com.fagi.model.Data;
import com.fagi.model.FriendRequest;
import com.fagi.model.User;
@@ -26,6 +28,7 @@
* @author miniwolf
*/
public class OutputWorker extends Worker implements OutputAgent {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(OutputWorker.class);
private final Queue messages = new ConcurrentLinkedQueue<>();
private final Queue respondObjects = new ConcurrentLinkedQueue<>();
private final Data data;
@@ -45,7 +48,7 @@ public OutputWorker(
@Override
public void run() {
while (isRunningStrategy.isRunning()) {
- System.out.println("Running");
+ LOGGER.info(() -> "Running");
try {
sendIncMessages();
sendResponses();
@@ -58,8 +61,10 @@ public void run() {
}
} catch (IOException | InterruptedException ioe) {
stop();
- System.out.println(ioe.toString());
- System.out.println("Logging out user " + myUserName);
+ LOGGER.error(
+ ioe,
+ () -> "Logging out user " + myUserName
+ );
data.userLogout(myUserName);
}
}
@@ -69,7 +74,7 @@ public void run() {
} catch (IOException ignored) { // User logged off we didn't manage to send response
}
}
- System.out.println("Closing output");
+ LOGGER.info(() -> "Closing output");
}
private void checkForLists() throws IOException {
@@ -103,7 +108,7 @@ private void sendIncMessages() throws IOException {
}
private void send(Object object) throws IOException {
- System.out.println("so: " + object.toString());
+ LOGGER.debug(() -> "Sending the following object to user " + myUserName + ": " + object.toString());
objOut.writeObject(aes.encrypt(Conversion.convertToBytes(object)));
objOut.flush();
}
diff --git a/fagiServer/src/test/java/TextMessageIntegrationTests.java b/fagiServer/src/test/java/TextMessageIntegrationTests.java
index 9ace37d9..cd3b4665 100644
--- a/fagiServer/src/test/java/TextMessageIntegrationTests.java
+++ b/fagiServer/src/test/java/TextMessageIntegrationTests.java
@@ -1,3 +1,4 @@
+import com.fagi.BaseFagiTest;
import com.fagi.conversation.Conversation;
import com.fagi.conversation.ConversationType;
import com.fagi.handler.ConversationHandler;
@@ -17,7 +18,7 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
-public class TextMessageIntegrationTests {
+public class TextMessageIntegrationTests extends BaseFagiTest {
private OutputAgent outputAgent;
private ConversationHandler conversationHandler;
private Data data;
diff --git a/fagiServer/src/test/java/com/fagi/encryption/EncryptionTests.java b/fagiServer/src/test/java/com/fagi/encryption/EncryptionTests.java
index e8e2b97a..3228cd10 100644
--- a/fagiServer/src/test/java/com/fagi/encryption/EncryptionTests.java
+++ b/fagiServer/src/test/java/com/fagi/encryption/EncryptionTests.java
@@ -1,16 +1,19 @@
package com.fagi.encryption;
+import com.fagi.BaseFagiTest;
+import com.fagi.logging.TestLogLevel;
+import com.fagi.logging.TestLogRecord;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
-import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
-import java.io.PrintStream;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
+import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
@@ -18,12 +21,11 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
-class EncryptionTests {
+class EncryptionTests extends BaseFagiTest {
private static final String PUBLIC_KEY_PATH = "build/test/data/encryption_test/public.key";
@AfterEach
void tearDown() {
- System.setErr(System.err);
deleteFileAndFolder(PUBLIC_KEY_PATH);
deleteFileAndFolder(KeyStorage.PUBLICKEYFILE);
deleteFileAndFolder(KeyStorage.PRIVATEKEYFILE);
@@ -39,13 +41,33 @@ public void whenLoadKeyPairThrowsIOException_ThenConstructorPrintsStacktrace() t
createPublicKeyFile();
- var outContent = new ByteArrayOutputStream();
- System.setErr(new PrintStream(outContent));
-
new Encryption(PUBLIC_KEY_PATH);
- String consoleOutput = outContent.toString();
- assertTrue(consoleOutput.contains("java.io.IOException"));
+ List> logRecords = lookupLogRecordsForClass(Encryption.class);
+ Assertions.assertAll(
+ () -> Assertions.assertEquals(
+ 1,
+ logRecords.size()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.ERROR,
+ logRecords
+ .getFirst()
+ .logLevel()
+ ),
+ () -> Assertions.assertEquals(
+ "Could not load RSA key pair.",
+ logRecords
+ .getFirst()
+ .message()
+ ),
+ () -> Assertions.assertInstanceOf(
+ IOException.class,
+ logRecords
+ .getFirst()
+ .throwable()
+ )
+ );
}
}
@@ -58,13 +80,33 @@ public void whenLoadKeyPairThrowsNoSuchAlgorithmException_ThenConstructorPrintsS
createPublicKeyFile();
- var outContent = new ByteArrayOutputStream();
- System.setErr(new PrintStream(outContent));
-
new Encryption(PUBLIC_KEY_PATH);
- String consoleOutput = outContent.toString();
- assertTrue(consoleOutput.contains("java.security.NoSuchAlgorithmException"));
+ List> logRecords = lookupLogRecordsForClass(Encryption.class);
+ Assertions.assertAll(
+ () -> Assertions.assertEquals(
+ 1,
+ logRecords.size()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.ERROR,
+ logRecords
+ .getFirst()
+ .logLevel()
+ ),
+ () -> Assertions.assertEquals(
+ "Could not load RSA key pair.",
+ logRecords
+ .getFirst()
+ .message()
+ ),
+ () -> Assertions.assertInstanceOf(
+ NoSuchAlgorithmException.class,
+ logRecords
+ .getFirst()
+ .throwable()
+ )
+ );
}
}
@@ -77,13 +119,33 @@ public void whenLoadKeyPairThrowsInvalidKeySpecException_ThenConstructorPrintsSt
createPublicKeyFile();
- var outContent = new ByteArrayOutputStream();
- System.setErr(new PrintStream(outContent));
-
new Encryption(PUBLIC_KEY_PATH);
- String consoleOutput = outContent.toString();
- assertTrue(consoleOutput.contains("java.security.spec.InvalidKeySpecException"));
+ List> logRecords = lookupLogRecordsForClass(Encryption.class);
+ Assertions.assertAll(
+ () -> Assertions.assertEquals(
+ 1,
+ logRecords.size()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.ERROR,
+ logRecords
+ .getFirst()
+ .logLevel()
+ ),
+ () -> Assertions.assertEquals(
+ "Could not load RSA key pair.",
+ logRecords
+ .getFirst()
+ .message()
+ ),
+ () -> Assertions.assertInstanceOf(
+ InvalidKeySpecException.class,
+ logRecords
+ .getFirst()
+ .throwable()
+ )
+ );
}
}
@@ -94,13 +156,33 @@ void whenSaveKeyPairThrowsIOException_ThenConstructorPrintsStacktrace() {
.when(() -> KeyStorage.SaveKeyPair(any()))
.thenThrow(new IOException());
- var outContent = new ByteArrayOutputStream();
- System.setErr(new PrintStream(outContent));
-
new Encryption(PUBLIC_KEY_PATH);
- String consoleOutput = outContent.toString();
- assertTrue(consoleOutput.contains("java.io.IOException"));
+ List> logRecords = lookupLogRecordsForClass(Encryption.class);
+ Assertions.assertAll(
+ () -> Assertions.assertEquals(
+ 1,
+ logRecords.size()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.ERROR,
+ logRecords
+ .getFirst()
+ .logLevel()
+ ),
+ () -> Assertions.assertEquals(
+ "Could not save RSA key pair.",
+ logRecords
+ .getFirst()
+ .message()
+ ),
+ () -> Assertions.assertInstanceOf(
+ IOException.class,
+ logRecords
+ .getFirst()
+ .throwable()
+ )
+ );
}
}
diff --git a/fagiServer/src/test/java/com/fagi/handler/ConversationHandlerTest.java b/fagiServer/src/test/java/com/fagi/handler/ConversationHandlerTest.java
index b17150f7..6e1bd77b 100644
--- a/fagiServer/src/test/java/com/fagi/handler/ConversationHandlerTest.java
+++ b/fagiServer/src/test/java/com/fagi/handler/ConversationHandlerTest.java
@@ -1,7 +1,10 @@
package com.fagi.handler;
+import com.fagi.BaseFagiTest;
import com.fagi.conversation.Conversation;
import com.fagi.conversation.ConversationType;
+import com.fagi.logging.TestLogLevel;
+import com.fagi.logging.TestLogRecord;
import com.fagi.model.Data;
import com.fagi.model.User;
import com.fagi.model.messages.message.TextMessage;
@@ -10,13 +13,14 @@
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
-class ConversationHandlerTest {
+class ConversationHandlerTest extends BaseFagiTest {
private Data data;
private ConversationHandler conversationHandler;
@@ -195,12 +199,8 @@ void givenMessageInQueue_WhenTwoOfThreeParticipantsAreOnline_ThenOnlyTheOnlinePa
}
@Test
- void dummy() throws InterruptedException {
- var outContent = new ByteArrayOutputStream();
- var errorContent = new ByteArrayOutputStream();
- System.setErr(new PrintStream(errorContent));
- System.setOut(new PrintStream(outContent));
-
+ @Timeout(value = 2, unit = TimeUnit.SECONDS)
+ void testConversationHandlerLogsMessageWhenInterrupted() throws InterruptedException {
var thread = new Thread(conversationHandler);
thread.setDaemon(true);
@@ -210,13 +210,34 @@ void dummy() throws InterruptedException {
thread.interrupt();
+ List> logRecords = lookupLogRecordsForClass(ConversationHandler.class);
+
+ while (logRecords.isEmpty()) {
+ Thread.sleep(100);
+ logRecords = lookupLogRecordsForClass(ConversationHandler.class);
+ }
+
+ Assertions.assertEquals(
+ 1,
+ logRecords.size()
+ );
+
+ TestLogRecord> logRecord = logRecords.getFirst();
+
Assertions.assertAll(
- () -> Assertions.assertTrue(errorContent.toString().contains("java.lang.InterruptedException")),
- () -> Assertions.assertFalse(outContent.toString().contains("java.lang.InterruptedException")),
+ () -> Assertions.assertEquals(
+ "Interrupted ConversationHandler thread.",
+ logRecord.message()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.DEBUG,
+ logRecord.logLevel()
+ ),
+ () -> Assertions.assertInstanceOf(
+ InterruptedException.class,
+ logRecord.throwable()
+ ),
() -> Assertions.assertTrue(thread.isInterrupted())
);
-
- System.setErr(System.err);
- System.setOut(System.out);
}
}
\ No newline at end of file
diff --git a/fagiServer/src/test/java/com/fagi/handler/inputhandler/BaseInputHandlerTest.java b/fagiServer/src/test/java/com/fagi/handler/inputhandler/BaseInputHandlerTest.java
index ce56a491..e519c722 100644
--- a/fagiServer/src/test/java/com/fagi/handler/inputhandler/BaseInputHandlerTest.java
+++ b/fagiServer/src/test/java/com/fagi/handler/inputhandler/BaseInputHandlerTest.java
@@ -1,5 +1,6 @@
package com.fagi.handler.inputhandler;
+import com.fagi.BaseFagiTest;
import com.fagi.conversation.Conversation;
import com.fagi.conversation.ConversationType;
import com.fagi.handler.ConversationHandler;
@@ -23,7 +24,7 @@
* A base class to make it easy for all {@link InputHandler} tests to have the base set up ready for each test.
* Also contains helpful methods to simplify st ups and mocks that are used multiple times.
*/
-public abstract class BaseInputHandlerTest {
+public abstract class BaseInputHandlerTest extends BaseFagiTest {
protected OutputAgent outputAgent;
protected Data data;
protected InputAgent inputAgent;
diff --git a/fagiServer/src/test/java/com/fagi/handler/inputhandler/NotValidRequestTests.java b/fagiServer/src/test/java/com/fagi/handler/inputhandler/NotValidRequestTests.java
index 3dfc5be8..c3feb210 100644
--- a/fagiServer/src/test/java/com/fagi/handler/inputhandler/NotValidRequestTests.java
+++ b/fagiServer/src/test/java/com/fagi/handler/inputhandler/NotValidRequestTests.java
@@ -1,28 +1,20 @@
package com.fagi.handler.inputhandler;
+import com.fagi.handler.InputHandler;
+import com.fagi.logging.TestLogLevel;
+import com.fagi.logging.TestLogRecord;
import com.fagi.util.OutputAgentTestUtil;
-import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
+import java.util.List;
import static org.mockito.Mockito.when;
public class NotValidRequestTests extends BaseInputHandlerTest {
- private final PrintStream standardOut = System.out;
- private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream();
-
void beforeEach() {
when(data.getOutputAgent(Mockito.anyString())).thenReturn(outputAgent);
- System.setOut(new PrintStream(outputStreamCaptor));
- }
-
- @AfterEach
- public void tearDown() {
- System.setOut(standardOut);
}
@Test
@@ -36,14 +28,27 @@ void whenSendingUnknownRequestObject_ShouldGiveNoResponse() {
}
@Test
- void whenSendingUnknownRequestObject_ShouldPrintMessageInSysOut() {
+ void whenSendingUnknownRequestObject_ShouldLogMessage() {
inputHandler.handleInput(new UnknownRequest());
+ List> testLogRecords = lookupLogRecordsForClass(InputHandler.class);
+
Assertions.assertEquals(
- "Unknown handle: " + UnknownRequest.class,
- outputStreamCaptor
- .toString()
- .trim()
+ 1,
+ testLogRecords.size()
+ );
+
+ TestLogRecord> logRecord = testLogRecords.getFirst();
+
+ Assertions.assertAll(
+ () -> Assertions.assertEquals(
+ "Unknown handle: " + UnknownRequest.class,
+ logRecord.message()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.INFO,
+ logRecord.logLevel()
+ )
);
}
@@ -58,14 +63,27 @@ void givenInputIsNull_ShouldGiveNoResponse() {
}
@Test
- void givenInputIsNull_ShouldPrintMessageInSysOut() {
+ void givenInputIsNull_ShouldLogMessage() {
inputHandler.handleInput(null);
+ List> testLogRecords = lookupLogRecordsForClass(InputHandler.class);
+
Assertions.assertEquals(
- "Input is null. Doing nothing.",
- outputStreamCaptor
- .toString()
- .trim()
+ 1,
+ testLogRecords.size()
+ );
+
+ TestLogRecord> logRecord = testLogRecords.getFirst();
+
+ Assertions.assertAll(
+ () -> Assertions.assertEquals(
+ "Input is null. Doing nothing.",
+ logRecord.message()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.INFO,
+ logRecord.logLevel()
+ )
);
}
diff --git a/fagiServer/src/test/java/com/fagi/model/DataTests.java b/fagiServer/src/test/java/com/fagi/model/DataTests.java
index 39189da7..c899619f 100644
--- a/fagiServer/src/test/java/com/fagi/model/DataTests.java
+++ b/fagiServer/src/test/java/com/fagi/model/DataTests.java
@@ -1,7 +1,10 @@
package com.fagi.model;
+import com.fagi.BaseFagiTest;
import com.fagi.conversation.Conversation;
import com.fagi.conversation.ConversationType;
+import com.fagi.logging.TestLogLevel;
+import com.fagi.logging.TestLogRecord;
import com.fagi.responses.AllIsWell;
import com.fagi.responses.NoSuchUser;
import com.fagi.responses.PasswordError;
@@ -11,21 +14,18 @@
import com.fagi.utility.JsonFileOperations;
import com.fagi.worker.InputAgent;
import com.fagi.worker.OutputAgent;
-import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
import java.util.List;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
-class DataTests {
+class DataTests extends BaseFagiTest {
private Data data;
private User user;
private InputAgent inputAgent;
@@ -42,11 +42,6 @@ void setup() {
);
}
- @AfterEach
- void tearDown() {
- System.setOut(System.out);
- }
-
@Test
void userAlreadyOnline_ShouldResultInUserOnlineResponse() {
doReturn(true)
@@ -529,10 +524,7 @@ void storeInviteCodes_ShouldAttemptToStoreInviteCodesToDisk() {
}
@Test
- void logoutUserWithNotLoggedInUser_ShouldResultInPrintToSysOut() {
- var outputStreamCaptor = new ByteArrayOutputStream();
- System.setOut(new PrintStream(outputStreamCaptor));
-
+ void logoutUserWithNotLoggedInUser_ShouldLogMessage() {
var user = new User(
"bob",
"password"
@@ -540,11 +532,24 @@ void logoutUserWithNotLoggedInUser_ShouldResultInPrintToSysOut() {
data.userLogout(user.getUserName());
+ List> testLogRecords = lookupLogRecordsForClass(Data.class);
+
Assertions.assertEquals(
- "Couldn't log " + user.getUserName() + " out",
- outputStreamCaptor
- .toString()
- .trim()
+ 1,
+ testLogRecords.size()
+ );
+
+ TestLogRecord> logRecord = testLogRecords.getFirst();
+
+ Assertions.assertAll(
+ () -> Assertions.assertEquals(
+ "Couldn't log " + user.getUserName() + " out",
+ logRecord.message()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.INFO,
+ logRecord.logLevel()
+ )
);
}
diff --git a/fagiServer/src/test/java/com/fagi/model/UserTests.java b/fagiServer/src/test/java/com/fagi/model/UserTests.java
index 303e70b4..c0bcdc25 100644
--- a/fagiServer/src/test/java/com/fagi/model/UserTests.java
+++ b/fagiServer/src/test/java/com/fagi/model/UserTests.java
@@ -1,5 +1,6 @@
package com.fagi.model;
+import com.fagi.BaseFagiTest;
import com.fagi.model.messages.message.TextMessage;
import com.fagi.responses.AllIsWell;
import com.fagi.responses.NoSuchUser;
@@ -23,7 +24,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
-public class UserTests {
+public class UserTests extends BaseFagiTest {
private Data data;
private User user;
private User secondUser;
diff --git a/fagiServer/src/test/java/com/fagi/server/ServerConversationHandlerTests.java b/fagiServer/src/test/java/com/fagi/server/ServerConversationHandlerTests.java
index 69cd97af..4ecbba91 100644
--- a/fagiServer/src/test/java/com/fagi/server/ServerConversationHandlerTests.java
+++ b/fagiServer/src/test/java/com/fagi/server/ServerConversationHandlerTests.java
@@ -6,7 +6,7 @@
import com.fagi.model.messages.message.TextMessage;
import com.fagi.running.IsRunningStrategy;
import com.fagi.util.DataTestUtil;
-import com.fagi.util.NeverRunStrategy;
+import com.fagi.util.running.NeverRunStrategy;
import com.fagi.utility.JsonFileOperations;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
diff --git a/fagiServer/src/test/java/com/fagi/server/ServerLoggingTests.java b/fagiServer/src/test/java/com/fagi/server/ServerLoggingTests.java
index 89733789..c582d21a 100644
--- a/fagiServer/src/test/java/com/fagi/server/ServerLoggingTests.java
+++ b/fagiServer/src/test/java/com/fagi/server/ServerLoggingTests.java
@@ -1,30 +1,21 @@
package com.fagi.server;
-import com.fagi.util.NeverRunStrategy;
-import org.junit.jupiter.api.AfterEach;
+import com.fagi.logging.TestLogLevel;
+import com.fagi.logging.TestLogRecord;
+import com.fagi.util.running.NeverRunStrategy;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.PrintStream;
import java.net.ServerSocket;
import java.nio.file.Files;
+import java.util.List;
class ServerLoggingTests extends ServerTests {
- @AfterEach
- void tearDown() {
- System.setOut(System.out);
- System.setErr(System.err);
- }
-
@Test
- void givenFileWriteGivesIOException_ThenShouldPrintErrorToErrorConsole() {
+ void givenFileWriteGivesIOException_ThenShouldLogError() {
try (var mockedFiles = Mockito.mockStatic(Files.class)) {
- var outContent = new ByteArrayOutputStream();
- System.setErr(new PrintStream(outContent));
-
mockedFiles
.when(() -> Files.write(
Mockito.any(),
@@ -38,18 +29,38 @@ void givenFileWriteGivesIOException_ThenShouldPrintErrorToErrorConsole() {
data
);
- String consoleOutput = outContent.toString();
- Assertions.assertTrue(consoleOutput.contains("java.io.IOException"));
+ List> testLogRecords = lookupLogRecordsForClass(
+ Server.class,
+ TestLogLevel.ERROR
+ );
+
+ Assertions.assertEquals(
+ 1,
+ testLogRecords.size()
+ );
+
+ TestLogRecord> logRecord = testLogRecords.getFirst();
+
+ Assertions.assertAll(
+ () -> Assertions.assertEquals(
+ "Could not create server.",
+ logRecord.message()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.ERROR,
+ logRecord.logLevel()
+ ),
+ () -> Assertions.assertInstanceOf(
+ IOException.class,
+ logRecord.throwable()
+ )
+ );
}
}
@Test
- void givenServerSocketAcceptThrowsIOException_ThenShouldPrintErrorToSysOutButNotSysError() throws IOException {
+ void givenServerSocketAcceptThrowsIOException_ThenShouldLogError() throws IOException {
var serverSocket = Mockito.mock(ServerSocket.class);
- var outContent = new ByteArrayOutputStream();
- var outErrorContent = new ByteArrayOutputStream();
- System.setOut(new PrintStream(outContent));
- System.setErr(new PrintStream(outErrorContent));
Mockito
.when(serverSocket.accept())
@@ -61,30 +72,38 @@ void givenServerSocketAcceptThrowsIOException_ThenShouldPrintErrorToSysOutButNot
);
server.start(serverSocket);
+ List> testLogRecords = lookupLogRecordsForClass(
+ Server.class,
+ TestLogLevel.ERROR
+ );
+
+ Assertions.assertEquals(
+ 1,
+ testLogRecords.size()
+ );
+
+ TestLogRecord> logRecord = testLogRecords.getFirst();
+
Assertions.assertAll(
- () -> Assertions.assertTrue(outContent
- .toString()
- .contains("Error in server loop exception = ")),
- () -> Assertions.assertTrue(outContent
- .toString()
- .contains("java.io.IOException")),
- () -> Assertions.assertFalse(outErrorContent
- .toString()
- .contains("Error in server loop exception = ")),
- () -> Assertions.assertFalse(outErrorContent
- .toString()
- .contains("java.io.IOException")),
+ () -> Assertions.assertEquals(
+ "Error in server loop exception",
+ logRecord.message()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.ERROR,
+ logRecord.logLevel()
+ ),
+ () -> Assertions.assertInstanceOf(
+ IOException.class,
+ logRecord.throwable()
+ ),
() -> Assertions.assertFalse(server.isRunning())
);
}
@Test
- void givenServerSocketCloseThrowsIOException_ThenShouldPrintErrorToSysErrorButNotSysOut() throws IOException {
+ void givenServerSocketCloseThrowsIOException_ThenShouldBeLogged() throws IOException {
var serverSocket = Mockito.mock(ServerSocket.class);
- var outContent = new ByteArrayOutputStream();
- var outErrorContent = new ByteArrayOutputStream();
- System.setOut(new PrintStream(outContent));
- System.setErr(new PrintStream(outErrorContent));
Mockito
.doThrow(new IOException())
@@ -98,22 +117,36 @@ void givenServerSocketCloseThrowsIOException_ThenShouldPrintErrorToSysErrorButNo
server.setIsRunningStrategy(new NeverRunStrategy());
server.start(serverSocket);
+ List> testLogRecords = lookupLogRecordsForClass(
+ Server.class,
+ TestLogLevel.ERROR
+ );
+
+ Assertions.assertEquals(
+ 1,
+ testLogRecords.size()
+ );
+
+ TestLogRecord> logRecord = testLogRecords.getFirst();
+
Assertions.assertAll(
- () -> Assertions.assertFalse(outContent
- .toString()
- .contains("java.io.IOException")),
- () -> Assertions.assertTrue(outErrorContent
- .toString()
- .contains("java.io.IOException")),
- () -> Assertions.assertFalse(server.isRunning())
+ () -> Assertions.assertEquals(
+ "Failed to close server socket gracefully.",
+ logRecord.message()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.ERROR,
+ logRecord.logLevel()
+ ),
+ () -> Assertions.assertInstanceOf(
+ IOException.class,
+ logRecord.throwable()
+ )
);
}
@Test
- void whenServerStarts_ThenStartingServerIsPrintedInSysOut() {
- var outContent = new ByteArrayOutputStream();
- System.setOut(new PrintStream(outContent));
-
+ void whenServerStartsAndThenStops_ThenShouldLogThatServerStartsAndStops() {
var server = new Server(
serverPort,
data
@@ -121,25 +154,42 @@ void whenServerStarts_ThenStartingServerIsPrintedInSysOut() {
server.setIsRunningStrategy(new NeverRunStrategy());
server.start(null);
- Assertions.assertTrue(outContent
- .toString()
- .contains("Starting Server"));
- }
-
- @Test
- void whenServerStops_ThenStoppingServerIsPrintedInSysOut() {
- var outContent = new ByteArrayOutputStream();
- System.setOut(new PrintStream(outContent));
+ List> testLogRecords = lookupLogRecordsForClass(
+ Server.class,
+ TestLogLevel.INFO
+ );
- var server = new Server(
- serverPort,
- data
+ Assertions.assertEquals(
+ 2,
+ testLogRecords.size()
);
- server.setIsRunningStrategy(new NeverRunStrategy());
- server.start(null);
- Assertions.assertTrue(outContent
- .toString()
- .contains("Stopping Server"));
+ Assertions.assertAll(
+ () -> Assertions.assertEquals(
+ "Starting Server",
+ testLogRecords
+ .getFirst()
+ .message()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.INFO,
+ testLogRecords
+ .getFirst()
+ .logLevel()
+ ),
+
+ () -> Assertions.assertEquals(
+ "Stopping Server",
+ testLogRecords
+ .getLast()
+ .message()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.INFO,
+ testLogRecords
+ .getLast()
+ .logLevel()
+ )
+ );
}
}
diff --git a/fagiServer/src/test/java/com/fagi/server/ServerTests.java b/fagiServer/src/test/java/com/fagi/server/ServerTests.java
index 264f5210..9fdb8df9 100644
--- a/fagiServer/src/test/java/com/fagi/server/ServerTests.java
+++ b/fagiServer/src/test/java/com/fagi/server/ServerTests.java
@@ -1,5 +1,6 @@
package com.fagi.server;
+import com.fagi.BaseFagiTest;
import com.fagi.model.Data;
import com.fagi.utility.JsonFileOperations;
import org.junit.jupiter.api.AfterEach;
@@ -8,7 +9,7 @@
import java.io.File;
-abstract class ServerTests {
+abstract class ServerTests extends BaseFagiTest {
protected final int serverPort = 4242;
protected Data data;
diff --git a/fagiServer/src/test/java/com/fagi/server/ServerWorkerTests.java b/fagiServer/src/test/java/com/fagi/server/ServerWorkerTests.java
index ced9ef77..be24bd2c 100644
--- a/fagiServer/src/test/java/com/fagi/server/ServerWorkerTests.java
+++ b/fagiServer/src/test/java/com/fagi/server/ServerWorkerTests.java
@@ -12,7 +12,7 @@
import com.fagi.responses.AllIsWell;
import com.fagi.running.IsRunningStrategy;
import com.fagi.util.DataTestUtil;
-import com.fagi.util.RunOnceStrategy;
+import com.fagi.util.running.RunOnceStrategy;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
diff --git a/fagiServer/src/test/java/com/fagi/util/NeverRunStrategy.java b/fagiServer/src/test/java/com/fagi/util/running/NeverRunStrategy.java
similarity index 90%
rename from fagiServer/src/test/java/com/fagi/util/NeverRunStrategy.java
rename to fagiServer/src/test/java/com/fagi/util/running/NeverRunStrategy.java
index d1ca6019..f31748b1 100644
--- a/fagiServer/src/test/java/com/fagi/util/NeverRunStrategy.java
+++ b/fagiServer/src/test/java/com/fagi/util/running/NeverRunStrategy.java
@@ -1,4 +1,4 @@
-package com.fagi.util;
+package com.fagi.util.running;
import com.fagi.running.IsRunningStrategy;
diff --git a/fagiServer/src/test/java/com/fagi/util/RunOnceStrategy.java b/fagiServer/src/test/java/com/fagi/util/running/RunOnceStrategy.java
similarity index 94%
rename from fagiServer/src/test/java/com/fagi/util/RunOnceStrategy.java
rename to fagiServer/src/test/java/com/fagi/util/running/RunOnceStrategy.java
index 0640b3c0..2cd7b0c5 100644
--- a/fagiServer/src/test/java/com/fagi/util/RunOnceStrategy.java
+++ b/fagiServer/src/test/java/com/fagi/util/running/RunOnceStrategy.java
@@ -1,4 +1,4 @@
-package com.fagi.util;
+package com.fagi.util.running;
import com.fagi.running.IsRunningStrategy;
diff --git a/fagiServer/src/test/java/com/fagi/worker/InputWorkerTests.java b/fagiServer/src/test/java/com/fagi/worker/InputWorkerTests.java
index c5b3d1b0..37678c37 100644
--- a/fagiServer/src/test/java/com/fagi/worker/InputWorkerTests.java
+++ b/fagiServer/src/test/java/com/fagi/worker/InputWorkerTests.java
@@ -1,5 +1,6 @@
package com.fagi.worker;
+import com.fagi.BaseFagiTest;
import com.fagi.encryption.AES;
import com.fagi.encryption.AESKey;
import com.fagi.encryption.Conversion;
@@ -7,36 +8,34 @@
import com.fagi.encryption.RSA;
import com.fagi.handler.ConversationHandler;
import com.fagi.handler.InputHandler;
+import com.fagi.logging.TestLogLevel;
+import com.fagi.logging.TestLogRecord;
import com.fagi.model.Data;
import com.fagi.model.Login;
import com.fagi.model.Session;
import com.fagi.model.UserNameAvailableRequest;
import com.fagi.responses.AllIsWell;
-import com.fagi.util.NeverRunStrategy;
import com.fagi.util.OutputAgentTestUtil;
-import com.fagi.util.RunOnceStrategy;
-import org.junit.jupiter.api.AfterEach;
+import com.fagi.util.running.NeverRunStrategy;
+import com.fagi.util.running.RunOnceStrategy;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
-import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
-import java.io.PrintStream;
import java.net.SocketException;
import java.net.SocketTimeoutException;
+import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
-public class InputWorkerTests {
+public class InputWorkerTests extends BaseFagiTest {
private static final byte[] ENCRYPTED_DATA = "encryptedData".getBytes();
private OutputWorker outputWorker;
private ConversationHandler conversationHandler;
@@ -61,26 +60,19 @@ void setup() throws IOException, ClassNotFoundException {
);
}
- @AfterEach
- void tearDown() {
- System.setErr(System.err);
- System.setOut(System.out);
- }
-
@Test
- void whenInputWorkerIsCreated_ThenStartingThreadMessagesIsPrintedToConsole() {
- var outContent = new ByteArrayOutputStream();
- System.setOut(new PrintStream(outContent));
+ void whenInputWorkerIsStarted_ThenStartingThreadMessageIsLogged() {
+ inputWorker.setIsRunningStrategy(new NeverRunStrategy());
- new InputWorker(
- mockObjectInputStream,
- outputWorker,
- conversationHandler,
- data
- );
+ inputWorker.run();
+
+ List> testLogRecords = lookupLogRecordsForClass(InputWorker.class);
- String consoleOutput = outContent.toString();
- assertTrue(consoleOutput.contains("Starting an input thread"));
+ Assertions.assertTrue(testLogRecords
+ .stream()
+ .anyMatch(logRecord -> logRecord
+ .message()
+ .equals("Starting an input thread")));
}
@Test
@@ -109,33 +101,37 @@ void givenUsernameSetToCharles_ThenUsernameShouldBeCharles() {
}
@Test
- void givenRunningIsSetToFalse_WhenWorkerIsRunning_ThenShouldNotPrintRunningToConsole() {
- var outContent = new ByteArrayOutputStream();
- System.setOut(new PrintStream(outContent));
-
+ void givenRunningIsSetToFalse_WhenWorkerIsRunning_ThenShouldNotLogRunning() {
inputWorker.setIsRunningStrategy(new NeverRunStrategy());
inputWorker.run();
- String consoleOutput = outContent.toString();
- assertFalse(consoleOutput.contains("Running"));
+ List> testLogRecords = lookupLogRecordsForClass(InputWorker.class);
+
+ Assertions.assertFalse(testLogRecords
+ .stream()
+ .anyMatch(logRecord -> logRecord
+ .message()
+ .equals("Running")));
}
@Test
- void givenRunningIsSetToFalse_WhenWorkerIsRunning_ThenShouldPrintClosingInputToConsole() {
- var outContent = new ByteArrayOutputStream();
- System.setOut(new PrintStream(outContent));
-
+ void givenRunningIsSetToFalse_WhenWorkerIsRunning_ThenShouldLogClosingInput() {
inputWorker.setIsRunningStrategy(new NeverRunStrategy());
inputWorker.run();
- String consoleOutput = outContent.toString();
- assertTrue(consoleOutput.contains("Closing input"));
+ List> testLogRecords = lookupLogRecordsForClass(InputWorker.class);
+
+ Assertions.assertTrue(testLogRecords
+ .stream()
+ .anyMatch(logRecord -> logRecord
+ .message()
+ .equals("Closing input.")));
}
@Test
- void givenRunningIsSetToTrue_WhenWorkerIsRunning_ThenShouldPrintRunningToConsole() {
+ void givenRunningIsSetToTrue_WhenWorkerIsRunning_ThenShouldLogRunning() {
try (var mockedConversion = Mockito.mockStatic(Conversion.class)) {
mockedConversion
.when(() -> Conversion.convertFromBytes(any()))
@@ -144,17 +140,20 @@ void givenRunningIsSetToTrue_WhenWorkerIsRunning_ThenShouldPrintRunningToConsole
var mockAes = Mockito.mock(AES.class);
when(mockAes.decrypt(any())).thenReturn("decrypted".getBytes());
- var outContent = new ByteArrayOutputStream();
- System.setOut(new PrintStream(outContent));
-
inputWorker.setIsRunningStrategy(new RunOnceStrategy());
inputWorker.setSessionCreated(true);
inputWorker.setAes(mockAes);
inputWorker.run();
- String consoleOutput = outContent.toString();
- assertTrue(consoleOutput.contains("Running"));
+
+ List> testLogRecords = lookupLogRecordsForClass(InputWorker.class);
+
+ Assertions.assertTrue(testLogRecords
+ .stream()
+ .anyMatch(logRecord -> logRecord
+ .message()
+ .equals("Running")));
}
}
@@ -235,7 +234,7 @@ void givenSessionCreated_WhenWorkerReceivesEncryptedObject_TheAESDecryptionIsCal
}
@Test
- void givenConversionFailsToConvertByteArrayToObject_WhenWorkerReceivesEncryptedObject_ThenSystemErrorShouldContainClassNotFoundException() {
+ void givenConversionFailsToConvertByteArrayToObject_WhenWorkerReceivesEncryptedObject_ThenShouldLogError() {
try (var mockedConversion = Mockito.mockStatic(Conversion.class)) {
byte[] decryptedInput = "decryptedData".getBytes();
var mockedAES = Mockito.mock(AES.class);
@@ -245,23 +244,43 @@ void givenConversionFailsToConvertByteArrayToObject_WhenWorkerReceivesEncryptedO
.when(() -> Conversion.convertFromBytes(any()))
.thenThrow(new ClassNotFoundException());
-
- var outContent = new ByteArrayOutputStream();
- System.setErr(new PrintStream(outContent));
-
inputWorker.setIsRunningStrategy(new RunOnceStrategy());
inputWorker.setSessionCreated(true);
inputWorker.setAes(mockedAES);
inputWorker.run();
- String consoleOutput = outContent.toString();
- assertTrue(consoleOutput.contains("java.lang.ClassNotFoundException"));
+ List> testLogRecords = lookupLogRecordsForClass(
+ InputWorker.class,
+ TestLogLevel.ERROR
+ );
+
+ Assertions.assertEquals(
+ 1,
+ testLogRecords.size()
+ );
+
+ TestLogRecord> logRecord = testLogRecords.getFirst();
+
+ Assertions.assertAll(
+ () -> Assertions.assertEquals(
+ "Failed to decrypt or deserialize object.",
+ logRecord.message()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.ERROR,
+ logRecord.logLevel()
+ ),
+ () -> Assertions.assertInstanceOf(
+ ClassNotFoundException.class,
+ logRecord.throwable()
+ )
+ );
}
}
@Test
- void givenConversionFailsWithIo_WhenWorkerReceivesEncryptedObject_ThenSystemErrorShouldContainIOException() {
+ void givenConversionFailsWithIo_WhenWorkerReceivesEncryptedObject_ThenShouldLogError() {
try (var mockedConversion = Mockito.mockStatic(Conversion.class)) {
byte[] decryptedInput = "decryptedData".getBytes();
var mockedAES = Mockito.mock(AES.class);
@@ -271,18 +290,38 @@ void givenConversionFailsWithIo_WhenWorkerReceivesEncryptedObject_ThenSystemErro
.when(() -> Conversion.convertFromBytes(any()))
.thenThrow(new IOException());
-
- var outContent = new ByteArrayOutputStream();
- System.setErr(new PrintStream(outContent));
-
inputWorker.setIsRunningStrategy(new RunOnceStrategy());
inputWorker.setSessionCreated(true);
inputWorker.setAes(mockedAES);
inputWorker.run();
- String consoleOutput = outContent.toString();
- assertTrue(consoleOutput.contains("java.io.IOException"));
+ List> testLogRecords = lookupLogRecordsForClass(
+ InputWorker.class,
+ TestLogLevel.ERROR
+ );
+
+ Assertions.assertEquals(
+ 1,
+ testLogRecords.size()
+ );
+
+ TestLogRecord> logRecord = testLogRecords.getFirst();
+
+ Assertions.assertAll(
+ () -> Assertions.assertEquals(
+ "Failed to decrypt or deserialize object.",
+ logRecord.message()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.ERROR,
+ logRecord.logLevel()
+ ),
+ () -> Assertions.assertInstanceOf(
+ IOException.class,
+ logRecord.throwable()
+ )
+ );
}
}
@@ -291,11 +330,6 @@ void givenSocketException_WhenRunning_ThenShouldHandleUserLogoutGracefully() thr
var username = "my username";
when(mockObjectInputStream.readObject()).thenThrow(new SocketException());
- var outContent = new ByteArrayOutputStream();
- var errorContent = new ByteArrayOutputStream();
- System.setErr(new PrintStream(errorContent));
- System.setOut(new PrintStream(outContent));
-
inputWorker.setSessionCreated(true);
inputWorker.setUsername(username);
@@ -315,15 +349,13 @@ void givenSocketException_WhenRunning_ThenShouldHandleUserLogoutGracefully() thr
)
.stop();
- Assertions.assertAll(
- () -> Assertions.assertFalse(inputWorker.isRunning()),
- () -> Assertions.assertTrue(outContent
- .toString()
- .contains("Logging out user " + username)),
- () -> Assertions.assertFalse(errorContent
- .toString()
- .contains("java.net.SocketException"))
- );
+ List> testLogRecords = lookupLogRecordsForClass(InputWorker.class);
+
+ Assertions.assertTrue(testLogRecords
+ .stream()
+ .anyMatch(logRecord -> logRecord
+ .message()
+ .equals("Logging out user " + username)));
}
@Test
@@ -331,11 +363,6 @@ void givenEOFException_WhenRunning_ThenShouldHandleUserLogoutGracefully() throws
var username = "my username";
when(mockObjectInputStream.readObject()).thenThrow(new EOFException());
- var outContent = new ByteArrayOutputStream();
- var errorContent = new ByteArrayOutputStream();
- System.setErr(new PrintStream(errorContent));
- System.setOut(new PrintStream(outContent));
-
inputWorker.setSessionCreated(true);
inputWorker.setUsername(username);
@@ -355,27 +382,20 @@ void givenEOFException_WhenRunning_ThenShouldHandleUserLogoutGracefully() throws
)
.stop();
- Assertions.assertAll(
- () -> Assertions.assertFalse(inputWorker.isRunning()),
- () -> Assertions.assertTrue(outContent
- .toString()
- .contains("Logging out user " + username)),
- () -> Assertions.assertFalse(errorContent
- .toString()
- .contains("java.io.EOFException"))
- );
+ List> testLogRecords = lookupLogRecordsForClass(InputWorker.class);
+
+ Assertions.assertTrue(testLogRecords
+ .stream()
+ .anyMatch(logRecord -> logRecord
+ .message()
+ .equals("Logging out user " + username)));
}
@Test
- void givenUnexpectedException_WhenRunning_ThenShouldHandleUserLogoutGracefullyButWithStacktrace() throws IOException, ClassNotFoundException {
+ void givenUnexpectedException_WhenRunning_ThenShouldHandleUserLogoutGracefullyButWithErrorLog() throws IOException, ClassNotFoundException {
var username = "my username";
when(mockObjectInputStream.readObject()).thenThrow(new SocketTimeoutException());
- var outContent = new ByteArrayOutputStream();
- var errorContent = new ByteArrayOutputStream();
- System.setErr(new PrintStream(errorContent));
- System.setOut(new PrintStream(outContent));
-
inputWorker.setSessionCreated(true);
inputWorker.setUsername(username);
@@ -395,20 +415,38 @@ void givenUnexpectedException_WhenRunning_ThenShouldHandleUserLogoutGracefullyBu
)
.stop();
+ Assertions.assertTrue(lookupLogRecordsForClass(InputWorker.class)
+ .stream()
+ .anyMatch(logRecord -> logRecord
+ .message()
+ .equals("Logging out user " + username)));
+
+
+ List> testErrorLogRecords = lookupLogRecordsForClass(
+ InputWorker.class,
+ TestLogLevel.ERROR
+ );
+
+ Assertions.assertEquals(
+ 1,
+ testErrorLogRecords.size()
+ );
+
+ TestLogRecord> errorLogRecord = testErrorLogRecords.getFirst();
+
Assertions.assertAll(
- () -> Assertions.assertFalse(inputWorker.isRunning()),
- () -> Assertions.assertTrue(outContent
- .toString()
- .contains("Logging out user " + username)),
- () -> Assertions.assertTrue(outContent
- .toString()
- .contains("Something went wrong in a input worker while loop ")),
- () -> Assertions.assertTrue(outContent
- .toString()
- .contains("java.net.SocketTimeoutException")),
- () -> Assertions.assertTrue(errorContent
- .toString()
- .contains("java.net.SocketTimeoutException"))
+ () -> Assertions.assertEquals(
+ "Something went wrong in a input worker while loop.",
+ errorLogRecord.message()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.ERROR,
+ errorLogRecord.logLevel()
+ ),
+ () -> Assertions.assertInstanceOf(
+ SocketTimeoutException.class,
+ errorLogRecord.throwable()
+ )
);
}
diff --git a/fagiServer/src/test/java/com/fagi/worker/OutputWorkerTest.java b/fagiServer/src/test/java/com/fagi/worker/OutputWorkerTest.java
index d9e34c8b..66dffd3f 100644
--- a/fagiServer/src/test/java/com/fagi/worker/OutputWorkerTest.java
+++ b/fagiServer/src/test/java/com/fagi/worker/OutputWorkerTest.java
@@ -1,7 +1,10 @@
package com.fagi.worker;
+import com.fagi.BaseFagiTest;
import com.fagi.encryption.AES;
import com.fagi.encryption.Conversion;
+import com.fagi.logging.TestLogLevel;
+import com.fagi.logging.TestLogRecord;
import com.fagi.model.Data;
import com.fagi.model.FriendRequest;
import com.fagi.model.User;
@@ -12,9 +15,8 @@
import com.fagi.model.messages.message.TextMessage;
import com.fagi.responses.AllIsWell;
import com.fagi.responses.UserOnline;
-import com.fagi.util.NeverRunStrategy;
-import com.fagi.util.RunOnceStrategy;
-import org.junit.jupiter.api.AfterEach;
+import com.fagi.util.running.NeverRunStrategy;
+import com.fagi.util.running.RunOnceStrategy;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
@@ -36,8 +38,9 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
-class OutputWorkerTest {
+class OutputWorkerTest extends BaseFagiTest {
private final ArgumentCaptor captor = ArgumentCaptor.forClass(byte[].class);
+ private final String username = "Charles";
private ObjectOutputStream objOut;
private Data data;
private OutputWorker outputWorker;
@@ -62,15 +65,10 @@ void givenOutputWorkerCreated_ThenShouldBeRunning() {
}
@Nested
- class OutputWorkerErrorHandlingTests {
- @AfterEach
- void tearDown() {
- System.setErr(System.err);
- System.setOut(System.out);
- }
+ class OutputWorkerErrorHandlingTests extends BaseFagiTest {
@Test
- void givenWritingObjectGivesIOException_WhenRunningIsFalse_ThenShouldNotWriteToConsole() throws IOException {
+ void givenWritingObjectGivesIOException_WhenRunningIsFalse_ThenShouldNotLogError() throws IOException {
var outContent = new ByteArrayOutputStream();
var errorContent = new ByteArrayOutputStream();
System.setErr(new PrintStream(errorContent));
@@ -84,14 +82,12 @@ void givenWritingObjectGivesIOException_WhenRunningIsFalse_ThenShouldNotWriteToC
outputWorker.run();
- Assertions.assertAll(
- () -> Assertions.assertFalse(outContent
- .toString()
- .contains("java.io.IOException")),
- () -> Assertions.assertFalse(errorContent
- .toString()
- .contains("java.io.IOException"))
+ List> testLogRecords = lookupLogRecordsForClass(
+ OutputWorker.class,
+ TestLogLevel.ERROR
);
+
+ Assertions.assertTrue(testLogRecords.isEmpty());
}
@Test
@@ -107,50 +103,91 @@ void givenWritingObjectsGivesException_WhenRunningIsTrue_ThenRunningShouldBeSetT
}
@Test
- void givenWritingObjectsGivesIOException_WhenRunningIsTrue_ThenShouldWriteToConsoleAsInfo() throws IOException {
- var outContent = new ByteArrayOutputStream();
- var errorContent = new ByteArrayOutputStream();
- System.setErr(new PrintStream(errorContent));
- System.setOut(new PrintStream(outContent));
-
+ void givenWritingObjectsGivesIOException_WhenRunningIsTrue_ThenShouldLogError() throws IOException {
doThrow(new IOException())
.when(objOut)
.writeObject(any());
outputWorker.addResponse("dummy");
+ outputWorker.setUserName(username);
outputWorker.run();
+ List> testLogRecords = lookupLogRecordsForClass(
+ OutputWorker.class,
+ TestLogLevel.ERROR
+ );
+
+ Assertions.assertEquals(
+ 1,
+ testLogRecords.size()
+ );
+
+ TestLogRecord> logRecord = testLogRecords.getFirst();
+
Assertions.assertAll(
- () -> Assertions.assertTrue(outContent
- .toString()
- .contains("java.io.IOException")),
- () -> Assertions.assertFalse(errorContent
- .toString()
- .contains("java.io.IOException"))
+ () -> Assertions.assertEquals(
+ "Logging out user " + username,
+ logRecord.message()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.ERROR,
+ logRecord.logLevel()
+ ),
+ () -> Assertions.assertInstanceOf(
+ IOException.class,
+ logRecord.throwable()
+ )
);
}
@Test
void givenWritingObjectsGivesException_WhenRunningIsTrue_ThenShouldLogoutUser() throws IOException {
- var outContent = new ByteArrayOutputStream();
- System.setOut(new PrintStream(outContent));
-
doThrow(new IOException())
.when(objOut)
.writeObject(any());
outputWorker.addResponse("dummy");
- outputWorker.setUserName("bob");
+ outputWorker.setUserName(username);
outputWorker.run();
- Mockito.verify(data, times(1)).userLogout("bob");
+ Mockito
+ .verify(
+ data,
+ times(1)
+ )
+ .userLogout(username);
+
+ List> testLogRecords = lookupLogRecordsForClass(
+ OutputWorker.class,
+ TestLogLevel.ERROR
+ );
+
+ Assertions.assertEquals(
+ 1,
+ testLogRecords.size()
+ );
+
+ TestLogRecord> logRecord = testLogRecords.getFirst();
- Assertions.assertTrue(outContent.toString().contains("Logging out user bob"));
+ Assertions.assertAll(
+ () -> Assertions.assertEquals(
+ "Logging out user " + username,
+ logRecord.message()
+ ),
+ () -> Assertions.assertEquals(
+ TestLogLevel.ERROR,
+ logRecord.logLevel()
+ ),
+ () -> Assertions.assertInstanceOf(
+ IOException.class,
+ logRecord.throwable()
+ )
+ );
}
}
@Nested
- class OutputWorkerEqualListsTests {
+ class OutputWorkerEqualListsTests extends BaseFagiTest {
@Test
void givenBothListsAreNull_WhenCallingEqualLists_ThenShouldReturnTrue() {
Assertions.assertTrue(outputWorker.equalLists(
@@ -252,7 +289,7 @@ void givenTwoListsHaveDifferentContent_WhenCallingEqualLists_ThenShouldReturnTru
}
@Nested
- class OutputWorkerSendFriendRequestListTests {
+ class OutputWorkerSendFriendRequestListTests extends BaseFagiTest {
@Test
void givenNoUsernameInOutputWorker_WhenCheckingFriendRequestList_ThenShouldNotSendFriendRequestList() throws IOException {
outputWorker.setIsRunningStrategy(new WorkerRunCalledNTimesStrategy(2));
@@ -463,16 +500,14 @@ void whenCheckingFriendRequestListTwentyTimes_ThenRunShouldTakeAtLeastTwoSeconds
}
@Nested
- class OutputWorkerSendMessagesAndResponsesTests {
+ class OutputWorkerSendMessagesAndResponsesTests extends BaseFagiTest {
@Test
void givenMessagesQueueHasTwoMessages_WhenRunningIsTrue_ThenHaveSentTwoObject() throws IOException {
- var outContent = new ByteArrayOutputStream();
- System.setOut(new PrintStream(outContent));
-
var user1LoggedInMessage = new UserLoggedIn("bob");
var user2LoggedInMessage = new UserLoggedIn("eve");
outputWorker.setIsRunningStrategy(new RunOnceStrategy());
+ outputWorker.setUserName(username);
outputWorker.addMessage(user1LoggedInMessage);
outputWorker.addMessage(user2LoggedInMessage);
@@ -490,6 +525,8 @@ void givenMessagesQueueHasTwoMessages_WhenRunningIsTrue_ThenHaveSentTwoObject()
List messageObjects = captor.getAllValues();
+ List> testLogRecords = lookupLogRecordsForClass(OutputWorker.class);
+
Assertions.assertAll(
() -> Assertions.assertEquals(
2,
@@ -499,16 +536,20 @@ void givenMessagesQueueHasTwoMessages_WhenRunningIsTrue_ThenHaveSentTwoObject()
Conversion.convertToBytes(user1LoggedInMessage),
messageObjects.getFirst()
),
- () -> Assertions.assertTrue(outContent
- .toString()
- .contains(user1LoggedInMessage.toString())),
+ () -> Assertions.assertTrue(testLogRecords
+ .stream()
+ .anyMatch(logRecord -> logRecord
+ .message()
+ .equals("Sending the following object to user " + username + ": " + user1LoggedInMessage))),
() -> Assertions.assertArrayEquals(
Conversion.convertToBytes(user2LoggedInMessage),
messageObjects.getLast()
),
- () -> Assertions.assertTrue(outContent
- .toString()
- .contains(user2LoggedInMessage.toString()))
+ () -> Assertions.assertTrue(testLogRecords
+ .stream()
+ .anyMatch(logRecord -> logRecord
+ .message()
+ .equals("Sending the following object to user " + username + ": " + user2LoggedInMessage)))
);
}
@@ -581,45 +622,39 @@ void whenRunningIsFalse_ThenShouldSendAllRespondObjects() throws IOException {
}
@Nested
- class OutputWorkerLoggingTests {
- @AfterEach
- void tearDown() {
- System.setOut(System.out);
- }
-
+ class OutputWorkerLoggingTests extends BaseFagiTest {
@Test
- void whenRunningIsFalse_ThenClosingOutputShouldBePrintedToConsole() {
- var outContent = new ByteArrayOutputStream();
- System.setOut(new PrintStream(outContent));
-
+ void whenRunningIsFalse_ThenClosingOutputShouldBeLogged() {
outputWorker.setIsRunningStrategy(new NeverRunStrategy());
outputWorker.run();
- Assertions.assertTrue(outContent
- .toString()
- .contains("Closing output"));
+ List> testLogRecords = lookupLogRecordsForClass(OutputWorker.class);
+
+ Assertions.assertTrue(testLogRecords
+ .stream()
+ .anyMatch(logRecord -> logRecord
+ .message()
+ .equals("Closing output")));
}
@Test
- void whenRunningIsTrue_ThenRunningShouldBePrintedToConsole() {
- var outContent = new ByteArrayOutputStream();
- System.setOut(new PrintStream(outContent));
-
+ void whenRunningIsTrue_ThenRunningShouldBeLogged() {
outputWorker.setIsRunningStrategy(new RunOnceStrategy());
outputWorker.run();
- Assertions.assertTrue(outContent
- .toString()
- .contains("Running"));
+ List> testLogRecords = lookupLogRecordsForClass(OutputWorker.class);
+
+ Assertions.assertTrue(testLogRecords
+ .stream()
+ .anyMatch(logRecord -> logRecord
+ .message()
+ .equals("Running")));
}
@Test
void givenMessagesQueueHasOneMessage_WhenRunningIsTrue_ThenHaveSentOneObject() throws IOException {
- var outContent = new ByteArrayOutputStream();
- System.setOut(new PrintStream(outContent));
-
var userLoggedInMessage = new UserLoggedIn("bob");
outputWorker.setIsRunningStrategy(new RunOnceStrategy());
@@ -639,15 +674,18 @@ void givenMessagesQueueHasOneMessage_WhenRunningIsTrue_ThenHaveSentOneObject() t
byte[] messageObject = captor.getValue();
- Assertions.assertAll(
- () -> Assertions.assertArrayEquals(
- Conversion.convertToBytes(userLoggedInMessage),
- messageObject
- ),
- () -> Assertions.assertTrue(outContent
- .toString()
- .contains(userLoggedInMessage.toString()))
+ Assertions.assertArrayEquals(
+ Conversion.convertToBytes(userLoggedInMessage),
+ messageObject
);
+
+ List> testLogRecords = lookupLogRecordsForClass(OutputWorker.class);
+
+ Assertions.assertFalse(testLogRecords
+ .stream()
+ .anyMatch(logRecord -> logRecord
+ .message()
+ .equals(userLoggedInMessage.toString())));
}
}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index a5cbd95a..d0449477 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,5 +1,5 @@
rootProject.name = "fagi"
-include("fagiClient")
-include("shared")
-include("fagiServer")
\ No newline at end of file
+include(":fagiClient")
+include(":fagiServer")
+include(":shared")
\ No newline at end of file
diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts
index 95e30793..baf9f80f 100644
--- a/shared/build.gradle.kts
+++ b/shared/build.gradle.kts
@@ -1,9 +1,17 @@
+plugins {
+ id("java-test-fixtures")
+}
+
tasks.test {
useJUnitPlatform()
}
dependencies {
implementation(libs.gson)
+
+ testFixturesImplementation(libs.junit.jupiter.api)
+
+ testImplementation(testFixtures(project(":shared")))
testImplementation(libs.bundles.junit.base)
testImplementation(libs.junit.platform)
testImplementation(libs.bundles.mockito)
diff --git a/shared/src/main/java/com/fagi/compression/Compression.java b/shared/src/main/java/com/fagi/compression/Compression.java
deleted file mode 100644
index 1a6fad94..00000000
--- a/shared/src/main/java/com/fagi/compression/Compression.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.fagi.compression;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.zip.DataFormatException;
-import java.util.zip.Deflater;
-import java.util.zip.Inflater;
-
-/**
- * Created by Marcus on 05-06-2016.
- */
-public class Compression {
- public static byte[] compress(byte[] data) throws IOException {
- Deflater deflater = new Deflater();
- deflater.setInput(data);
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length);
- deflater.finish();
- byte[] buffer = new byte[1024];
- while (!deflater.finished()) {
- int count = deflater.deflate(buffer); // returns the generated code... index
- outputStream.write(buffer, 0, count);
- }
- outputStream.close();
- byte[] output = outputStream.toByteArray();
- System.out.println("Original: " + data.length);
- System.out.println("Compressed: " + output.length);
- return output;
- }
-
- public static byte[] decompress(byte[] data) throws IOException, DataFormatException {
- Inflater inflater = new Inflater();
- inflater.setInput(data);
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length);
- byte[] buffer = new byte[1024];
- while (!inflater.finished()) {
- int count = inflater.inflate(buffer);
- outputStream.write(buffer, 0, count);
- }
- outputStream.close();
- byte[] output = outputStream.toByteArray();
- System.out.println("Original: " + output.length);
- System.out.println("Compressed: " + data.length);
- return output;
- }
-}
diff --git a/shared/src/main/java/com/fagi/db/LogBasedDatabase.java b/shared/src/main/java/com/fagi/db/LogBasedDatabase.java
index 9c369048..b4aeda95 100644
--- a/shared/src/main/java/com/fagi/db/LogBasedDatabase.java
+++ b/shared/src/main/java/com/fagi/db/LogBasedDatabase.java
@@ -1,7 +1,16 @@
package com.fagi.db;
-import java.io.*;
-import java.util.*;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -70,8 +79,8 @@
* Error Handling:
* The database handles various error conditions gracefully:
*
- * Corrupted Log entries are skipped with warnings to stderr
- * Malformed entries are ignored during startup
+ * Corrupted Log entries are skipped with warnings logged to WARNING
+ * Malformed entries are ignored during startup and logged to WARNING
* I/O errors during writes throw RuntimeException
*
*
@@ -87,6 +96,7 @@
* @see com.fagi.utility.Checksum
*/
public class LogBasedDatabase implements AutoCloseable {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(LogBasedDatabase.class);
enum DBOperation {
PUT,
@@ -125,11 +135,11 @@ enum DBOperation {
*
* @param filePath the path to the database Log file, must not be null
* @throws DatabaseInitializeException if the database cannot be initialized due to:
- *
- * I/O errors when creating/opening the Log file
- * Permission issue with the file or directory
- * Corruption in the existing Log file that prevents loading
- *
+ *
+ * I/O errors when creating/opening the Log file
+ * Permission issue with the file or directory
+ * Corruption in the existing Log file that prevents loading
+ *
*/
public LogBasedDatabase(String filePath) throws DatabaseInitializeException {
this.logFilePath = filePath;
@@ -144,7 +154,10 @@ public LogBasedDatabase(String filePath) throws DatabaseInitializeException {
loadFromLog();
} catch (IOException e) {
close();
- throw new DatabaseInitializeException("Failed to initialize database", e);
+ throw new DatabaseInitializeException(
+ "Failed to initialize database",
+ e
+ );
} catch (DatabaseInitializeException e) {
close();
throw e;
@@ -158,14 +171,14 @@ public LogBasedDatabase(String filePath) throws DatabaseInitializeException {
* It bypasses normal file initialization and error handling. The provided
* FileWriter must be properly configured for append mode.
*
- * @param filePath the path to the database log file, used for identification
+ * @param filePath the path to the database log file, used for identification
* @param logWriter the FileWriter to use for logging operations, must not be null
* @throws DatabaseInitializeException if the database cannot be initialized due to:
- *
- * I/O errors when creating/opening the Log file
- * Permission issue with the file or directory
- * Corruption in the existing Log file that prevents loading
- *
+ *
+ * I/O errors when creating/opening the Log file
+ * Permission issue with the file or directory
+ * Corruption in the existing Log file that prevents loading
+ *
*/
public LogBasedDatabase(
String filePath,
@@ -190,14 +203,12 @@ public LogBasedDatabase(
* Performance: O(1) time complexity. Constant time regardless
* of database size.
*
- * @param id the record identifier, must not be null
- * @param key the field key, must not be null
+ * @param id the record identifier, must not be null
+ * @param key the field key, must not be null
* @param value the field value, must not be null
* @throws IllegalArgumentException if any parameter is null
- * @throws DatabaseUpdateException if the write operation fails due to I/O errors
- *
- * @snippet
- * {@code
+ * @throws DatabaseUpdateException if the write operation fails due to I/O errors
+ * @snippet {@code
* db.put("user123", "name", "Alice");
* db.put("user123", "email", "alice@example.com");
* db.put("user123", "status", "active");
@@ -245,13 +256,11 @@ public void put(
*
* Performance: O(1) time complexity. Direct hash map lookup.
*
- * @param id the record identifier to look up, must not be null
+ * @param id the record identifier to look up, must not be null
* @param key the field key to retrieve, must not be null
* @return the field value, or {@code null} if the record or field doesn't exist
* @throws IllegalArgumentException if the id or key is null
- *
- * @snippet
- * {@code
+ * @snippet {@code
* String email = db.get("user123", "email"); // Returns "alice@example.com" or null
* String phone = db.get("user123", "phone"); // Returns null if field doesn't exist
* String name = db.get("nonexistent", "name"); // Returns null if record doesn't exist
@@ -288,8 +297,7 @@ public String get(
* @param id the record identifier to retrieve, must not be null
* @return a copy of all fields for the record, or {@code null} if the record doesn't exist
* @throws IllegalArgumentException if id is null
- * @snippet
- * {@code
+ * @snippet {@code
* Map user = db.get("user123");
* if (user != null) {
* String name = user.get("name"); // "Alice Johnson"
@@ -344,7 +352,8 @@ private void loadFromLog() throws DatabaseInitializeException {
String[] parts = line.split("\\|");
if (parts.length < 5) {
- System.err.println("Malformed entry at line " + lineNumber);
+ int malformedLine = lineNumber;
+ LOGGER.warning(() -> "Malformed entry at line " + malformedLine);
continue;
}
@@ -358,7 +367,8 @@ private void loadFromLog() throws DatabaseInitializeException {
);
if (!calculateChecksum(originalEntry).equals(storedChecksum)) {
- System.err.println("Corruption detected at line " + lineNumber);
+ int corruptLine = lineNumber;
+ LOGGER.warning(() -> "Corruption detected at line " + corruptLine);
continue;
}
@@ -405,9 +415,9 @@ private void processLogEntry(String[] parts) {
* Appends an operation to the Log file with checksum verification.
*
* @param operation the operator type (e.g. "PUT")
- * @param id the record identifier
- * @param key the field key
- * @param value the field value
+ * @param id the record identifier
+ * @param key the field key
+ * @param value the field value
* @throws DatabaseUpdateException if the write operations fails
*/
private void appendToLog(
@@ -450,14 +460,12 @@ private void appendToLog(
* Performance: O(n) where n is the total number of records.
* Performance degrades linearly with database size.
*
- * @param fieldName the name of the field to match against, must not be null
+ * @param fieldName the name of the field to match against, must not be null
* @param fieldValue the value to search for, must not be null
* @return a List or records (as maps) that contain the specified field-value pair,
- * empty List if no matches found, never null
+ * empty List if no matches found, never null
* @throws IllegalArgumentException if fieldName or fieldValue is null
- *
- * @snippet
- * {@code
+ * @snippet {@code
* // Find all active users
* List> activeUsers = db.queryByField("status", "active");
*
@@ -466,7 +474,7 @@ private void appendToLog(
*
* // Process results
* for (Map user : activeUsers) {
- * System.out.println("Active user: " + user.get("name"));
+ * LOGGER.info(() -> "Active user: " + user.get("name"));
* }
* }
*/
@@ -499,15 +507,13 @@ public List> queryByField(
* Performance: O(1) time complexity. Direct size lookup.
*
* @return the number of records currently stored in the database, never negative
- *
- * @snippet
- * {@code
+ * @snippet {@code
* int totalUsers = db.count();
- * System.out.println("Database contains " + totalUsers + " records");
+ * LOGGER.info(() -> "Database contains " + totalUsers + " records");
*
* // Check if database is empty
* if (db.count() == 0) {
- * System.out.println("Database is empty");
+ * LOGGER.info(() -> "Database is empty");
* }
* }
*/
@@ -535,12 +541,11 @@ public int count() {
* only be called when no other operations are in progress.
*
* Error Handling: I/O errors during close are logged
- * to stderr but do not throw exceptions.
+ * to ERROR but do not throw exceptions.
*
* This method is idempotent - calling it multiple times has no additional effect.
*
- * @snippet
- * {@code
+ * @snippet {@code
* LogBasedDatabase db = new LogBasedDatabase("myapp.db");
* try {
* // Use database...
@@ -563,7 +568,10 @@ public void close() {
logWriter.close();
}
} catch (IOException e) {
- System.err.println("Error closing database: " + e.getMessage());
+ LOGGER.error(
+ e,
+ () -> "Error closing database."
+ );
}
}
}
diff --git a/shared/src/main/java/com/fagi/encryption/AES.java b/shared/src/main/java/com/fagi/encryption/AES.java
index 666198f2..5da51d6b 100644
--- a/shared/src/main/java/com/fagi/encryption/AES.java
+++ b/shared/src/main/java/com/fagi/encryption/AES.java
@@ -1,6 +1,7 @@
package com.fagi.encryption;
-import com.fagi.utility.Logger;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@@ -17,6 +18,7 @@
* Created by Marcus on 04-06-2016.
*/
public class AES implements EncryptionAlgorithm {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(AES.class);
byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
IvParameterSpec ivspec = new IvParameterSpec(iv);
private AESKey key;
@@ -44,8 +46,10 @@ public byte[] encrypt(byte[] msg) {
cipher.init(Cipher.ENCRYPT_MODE, key.key(), ivspec);
return cipher.doFinal(msg);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | InvalidKeyException | IllegalBlockSizeException | InvalidAlgorithmParameterException e) {
- e.printStackTrace();
- Logger.logStackTrace(e);
+ LOGGER.error(
+ e,
+ () -> "Failed to encrypt byte array."
+ );
}
return null;
}
@@ -57,8 +61,10 @@ public byte[] decrypt(byte[] cipherText) {
cipher.init(Cipher.DECRYPT_MODE, key.key(), ivspec);
return cipher.doFinal(cipherText);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException e) {
- e.printStackTrace();
- Logger.logStackTrace(e);
+ LOGGER.error(
+ e,
+ () -> "Failed to decrypt byte array."
+ );
}
return null;
}
@@ -70,8 +76,10 @@ public void generateKey(int keyLength) {
keygen.init(keyLength);
this.key = new AESKey(keygen.generateKey());
} catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- Logger.logStackTrace(e);
+ LOGGER.error(
+ e,
+ () -> "Failed to generate AES key."
+ );
}
}
diff --git a/shared/src/main/java/com/fagi/encryption/KeyStorage.java b/shared/src/main/java/com/fagi/encryption/KeyStorage.java
index f51f345d..50c27fcb 100644
--- a/shared/src/main/java/com/fagi/encryption/KeyStorage.java
+++ b/shared/src/main/java/com/fagi/encryption/KeyStorage.java
@@ -1,6 +1,7 @@
package com.fagi.encryption;
-import com.fagi.utility.Logger;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import java.io.File;
import java.io.FileInputStream;
@@ -22,6 +23,7 @@
* Created by Marcus on 04-06-2016.
*/
public class KeyStorage {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(KeyStorage.class);
private static final String KEYSFOLDER = "config/keys";
public static final String PUBLICKEYFILE = KEYSFOLDER + "/public.key";
public static final String PRIVATEKEYFILE = KEYSFOLDER + "/private.key";
@@ -71,8 +73,10 @@ public static KeyPair LoadKeyPair(String algorithm) throws IOException, NoSuchAl
try {
fis.read(encodedPrivateKey);
} catch (IOException e) {
- e.printStackTrace();
- Logger.logStackTrace(e);
+ LOGGER.error(
+ e,
+ () -> "Failed to read encoded private key."
+ );
}
fis.close();
diff --git a/shared/src/main/java/com/fagi/encryption/RSA.java b/shared/src/main/java/com/fagi/encryption/RSA.java
index 655e7480..031f8a59 100644
--- a/shared/src/main/java/com/fagi/encryption/RSA.java
+++ b/shared/src/main/java/com/fagi/encryption/RSA.java
@@ -1,6 +1,7 @@
package com.fagi.encryption;
-import com.fagi.utility.Logger;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@@ -20,6 +21,7 @@
* Created by Marcus on 30-05-2016.
*/
public class RSA implements EncryptionAlgorithm {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(RSA.class);
private RSAKey key;
// TODO: Remove this as it is redundant. Trello task: https://trello.com/c/JZynEGUQ
private PublicKey encryptionKey;
@@ -30,8 +32,10 @@ public RSA() {
try {
key = new RSAKey(KeyStorage.LoadKeyPair("RSA"));
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
- e.printStackTrace();
- Logger.logStackTrace(e);
+ LOGGER.error(
+ e,
+ () -> "Failed to load existing RSA key pair."
+ );
}
} else {
generateKey(4096);
@@ -61,8 +65,10 @@ public byte[] encrypt(byte[] msg) {
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
return cipher.doFinal(msg);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
- e.printStackTrace();
- Logger.logStackTrace(e);
+ LOGGER.error(
+ e,
+ () -> "Failed to encrypt byte array."
+ );
}
return null;
}
@@ -78,8 +84,10 @@ public byte[] decrypt(byte[] cipherText) {
);
return cipher.doFinal(cipherText);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException e) {
- e.printStackTrace();
- Logger.logStackTrace(e);
+ LOGGER.error(
+ e,
+ () -> "Failed to decrypt byte array."
+ );
}
return null;
}
@@ -91,8 +99,10 @@ public void generateKey(int keyLength) {
keygen.initialize(keyLength);
key = new RSAKey(keygen.generateKeyPair());
} catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- Logger.logStackTrace(e);
+ LOGGER.error(
+ e,
+ () -> "Failed to generate RSA key."
+ );
}
}
diff --git a/shared/src/main/java/com/fagi/logging/FagiLogger.java b/shared/src/main/java/com/fagi/logging/FagiLogger.java
new file mode 100644
index 00000000..fd6d1aad
--- /dev/null
+++ b/shared/src/main/java/com/fagi/logging/FagiLogger.java
@@ -0,0 +1,83 @@
+package com.fagi.logging;
+
+import java.util.function.Supplier;
+
+/**
+ *
+ * A Logger interface used to log messages for the Fagi server and client.
+ *
+ *
+ * This interface helps hide what logging framework is used, and thus makes it simpler to change framework without having to change a lot of code.
+ *
+ *
+ * @author Marcus Haagh
+ */
+public interface FagiLogger {
+ /**
+ * Logs message in DEBUG level
+ *
+ * @param messageSupplier a supplier that returns the message to be logged
+ */
+ void debug(Supplier messageSupplier);
+
+ /**
+ * Logs message in DEBUG level along with the associated throwable
+ *
+ * @param throwable the throwable to be logged
+ * @param messageSupplier a supplier that returns the message to be logged
+ */
+ void debug(
+ Throwable throwable,
+ Supplier messageSupplier);
+
+ /**
+ * Logs message in INFO level
+ *
+ * @param messageSupplier a supplier that returns the message to be logged
+ */
+ void info(Supplier messageSupplier);
+
+ /**
+ * Logs message in INFO level along with the associated throwable
+ *
+ * @param throwable the throwable to be logged
+ * @param messageSupplier a supplier that returns the message to be logged
+ */
+ void info(
+ Throwable throwable,
+ Supplier messageSupplier);
+
+ /**
+ * Logs message in WARNING level
+ *
+ * @param messageSupplier a supplier that returns the message to be logged
+ */
+ void warning(Supplier messageSupplier);
+
+ /**
+ * Logs message in WARNING level along with the associated throwable
+ *
+ * @param throwable the throwable to be logged
+ * @param messageSupplier a supplier that returns the message to be logged
+ */
+ void warning(
+ Throwable throwable,
+ Supplier messageSupplier);
+
+ /**
+ * Logs message in ERROR level
+ *
+ * @param messageSupplier a supplier that returns the message to be logged
+ */
+ void error(Supplier messageSupplier);
+
+ /**
+ * Logs message in ERROR level along with the associated throwable
+ *
+ * @param throwable the throwable to be logged
+ * @param messageSupplier a supplier that returns the message to be logged
+ */
+ void error(
+ Throwable throwable,
+ Supplier messageSupplier);
+}
diff --git a/shared/src/main/java/com/fagi/logging/FagiLoggerConfigStrategy.java b/shared/src/main/java/com/fagi/logging/FagiLoggerConfigStrategy.java
new file mode 100644
index 00000000..6edd46b9
--- /dev/null
+++ b/shared/src/main/java/com/fagi/logging/FagiLoggerConfigStrategy.java
@@ -0,0 +1,31 @@
+package com.fagi.logging;
+
+import java.nio.file.Path;
+
+/**
+ *
+ * This strategy can check if a default configuration should be used and create the default logging configuration.
+ *
+ *
+ * The strategy must be set in the {@link FagiLoggerFactory} in order to be used.
+ *
+ *
+ * @author Marcus Haagh
+ * @see FagiLoggerFactory
+ */
+public interface FagiLoggerConfigStrategy {
+ /**
+ * Checks if custom configuration has been provided.
+ *
+ * @return true if logging configuration has been provided by the user
+ */
+ boolean isCustomConfigurationAvailable();
+
+ /**
+ * Sets up the logging in a default manner. This ensures that proper logging is configured, even if the user hasn't
+ * provided a log config file.
+ *
+ * @param logFile the path of the resulting log file.
+ */
+ void setupDefaultConfiguration(Path logFile);
+}
diff --git a/shared/src/main/java/com/fagi/logging/FagiLoggerCreationStrategy.java b/shared/src/main/java/com/fagi/logging/FagiLoggerCreationStrategy.java
new file mode 100644
index 00000000..cd40ae31
--- /dev/null
+++ b/shared/src/main/java/com/fagi/logging/FagiLoggerCreationStrategy.java
@@ -0,0 +1,22 @@
+package com.fagi.logging;
+
+/**
+ *
+ * A strategy to help create a {@link FagiLogger}.
+ *
+ *
+ * The strategy should hold the details on how to instantiate a new instance of a {@link FagiLogger}. The strategy must be set in the {@link FagiLoggerFactory} in order to be used.
+ *
+ *
+ * @author Marcus Haagh
+ * @see FagiLogger
+ */
+public interface FagiLoggerCreationStrategy {
+ /**
+ * Creates a logger for the given class.
+ *
+ * @param tClass the class the new logger should be associated with.
+ * @return a {@link FagiLogger}
+ */
+ FagiLogger createLogger(Class tClass);
+}
diff --git a/shared/src/main/java/com/fagi/logging/FagiLoggerFactory.java b/shared/src/main/java/com/fagi/logging/FagiLoggerFactory.java
new file mode 100644
index 00000000..ad7ab982
--- /dev/null
+++ b/shared/src/main/java/com/fagi/logging/FagiLoggerFactory.java
@@ -0,0 +1,88 @@
+package com.fagi.logging;
+
+import com.fagi.logging.java.JavaLoggerConfigStrategy;
+import com.fagi.logging.java.JavaLoggerCreationStrategy;
+
+import java.nio.file.Path;
+
+/**
+ * This class is used to aid in creating and configuring {@link FagiLogger}s. This is to make it simpler to change
+ * logging frameworks, both during testing and in cases where we want to change the entire framework.
+ *
+ * @author Marcus Haagh
+ * @see FagiLogger
+ * @see FagiLoggerCreationStrategy
+ * @see FagiLoggerConfigStrategy
+ */
+public class FagiLoggerFactory {
+ /**
+ * This contains the creation strategy to be used. Should this change, then make sure that the config strategy is
+ * changed as well.
+ */
+ private static FagiLoggerCreationStrategy loggerCreationStrategy = new JavaLoggerCreationStrategy();
+ /**
+ * This contains the config strategy to be used.Should this change, then make sure that the creation strategy is
+ * changed as well.
+ */
+ private static FagiLoggerConfigStrategy loggerConfigStrategy = new JavaLoggerConfigStrategy();
+ private static FagiLogger LOGGER = createLogger(FagiLoggerFactory.class);
+
+ private FagiLoggerFactory() {
+ throw new UnsupportedOperationException("Factory class - do not instantiate");
+ }
+
+ /**
+ * Creates a FagiLogger for the given class
+ *
+ * @param tClass the class for the logger to be created for
+ * @return a FagiLogger
+ */
+ public static FagiLogger createLogger(Class tClass) {
+ return loggerCreationStrategy.createLogger(tClass);
+ }
+
+ /**
+ * Determines if custom configuration has been provided by the user. If that's the case,
+ * then configuration should not be overridden with the default configuration.
+ *
+ * @return true if logging configuration has been provided by the user
+ */
+ public static boolean isCustomConfigurationAvailable() {
+ return loggerConfigStrategy.isCustomConfigurationAvailable();
+ }
+
+ /**
+ *
+ * Sets up the default logging configuration.
+ *
+ *
+ * Does nothing if {@link FagiLoggerFactory#isCustomConfigurationAvailable()} is true
+ *
+ *
+ * @param logFile the file where logs will be stored
+ */
+ public static void setupDefaultConfiguration(Path logFile) {
+ if (!isCustomConfigurationAvailable()) {
+ loggerConfigStrategy.setupDefaultConfiguration(logFile);
+ } else {
+ LOGGER.debug(() -> "Custom configuration has been provided. No further configuration is made.");
+ }
+ }
+
+ public static void setLoggerCreationStrategy(FagiLoggerCreationStrategy strategy) {
+ loggerCreationStrategy = strategy;
+ }
+
+ public static void setLoggerConfigStrategy(FagiLoggerConfigStrategy loggerConfigStrategy) {
+ FagiLoggerFactory.loggerConfigStrategy = loggerConfigStrategy;
+ }
+
+ /**
+ * Used for testing as the logger on the class is created before any test class can override the strategies.
+ *
+ * @param logger the logger to be used by the factory
+ */
+ static void setLogger(FagiLogger logger) {
+ LOGGER = logger;
+ }
+}
diff --git a/shared/src/main/java/com/fagi/logging/java/JavaLogger.java b/shared/src/main/java/com/fagi/logging/java/JavaLogger.java
new file mode 100644
index 00000000..939975bd
--- /dev/null
+++ b/shared/src/main/java/com/fagi/logging/java/JavaLogger.java
@@ -0,0 +1,98 @@
+package com.fagi.logging.java;
+
+import com.fagi.logging.FagiLogger;
+
+import java.util.function.Supplier;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A {@link FagiLogger} implementation that uses {@link Logger} from the Java Standard Library.
+ *
+ * @author Marcus Haagh
+ * @see FagiLogger
+ * @see Logger
+ */
+public class JavaLogger implements FagiLogger {
+ private final Logger logger;
+
+ public JavaLogger(Class tClass) {
+ this.logger = Logger.getLogger(tClass.getName());
+ }
+
+ @Override
+ public void debug(Supplier messageSupplier) {
+ logger.log(
+ Level.FINE,
+ messageSupplier
+ );
+ }
+
+ @Override
+ public void debug(
+ Throwable throwable,
+ Supplier messageSupplier) {
+ logger.log(
+ Level.FINE,
+ throwable,
+ messageSupplier
+ );
+ }
+
+ @Override
+ public void info(Supplier messageSupplier) {
+ logger.log(
+ Level.INFO,
+ messageSupplier
+ );
+ }
+
+ @Override
+ public void info(
+ Throwable throwable,
+ Supplier messageSupplier) {
+ logger.log(
+ Level.INFO,
+ throwable,
+ messageSupplier
+ );
+ }
+
+ @Override
+ public void warning(Supplier messageSupplier) {
+ logger.log(
+ Level.WARNING,
+ messageSupplier
+ );
+ }
+
+ @Override
+ public void warning(
+ Throwable throwable,
+ Supplier messageSupplier) {
+ logger.log(
+ Level.WARNING,
+ throwable,
+ messageSupplier
+ );
+ }
+
+ @Override
+ public void error(Supplier messageSupplier) {
+ logger.log(
+ Level.SEVERE,
+ messageSupplier
+ );
+ }
+
+ @Override
+ public void error(
+ Throwable throwable,
+ Supplier messageSupplier) {
+ logger.log(
+ Level.SEVERE,
+ throwable,
+ messageSupplier
+ );
+ }
+}
diff --git a/shared/src/main/java/com/fagi/logging/java/JavaLoggerConfigStrategy.java b/shared/src/main/java/com/fagi/logging/java/JavaLoggerConfigStrategy.java
new file mode 100644
index 00000000..b47cc8f0
--- /dev/null
+++ b/shared/src/main/java/com/fagi/logging/java/JavaLoggerConfigStrategy.java
@@ -0,0 +1,56 @@
+package com.fagi.logging.java;
+
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerConfigStrategy;
+import com.fagi.logging.FagiLoggerFactory;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.FileHandler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This strategy configures {@link Logger} from the Java Standard Library.
+ *
+ * @author Marcus Haagh
+ */
+public class JavaLoggerConfigStrategy implements FagiLoggerConfigStrategy {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(JavaLoggerConfigStrategy.class);
+ static final String LOGGING_CONFIG_FILE_SYSTEM_PROPERTY_NAME = "java.util.logging.config.file";
+
+ @Override
+ public boolean isCustomConfigurationAvailable() {
+ return System.getProperty(LOGGING_CONFIG_FILE_SYSTEM_PROPERTY_NAME) != null;
+ }
+
+ @Override
+ public void setupDefaultConfiguration(Path logFile) {
+ var logLevel = Level.INFO;
+ var rootLogger = Logger.getLogger("");
+ rootLogger.setLevel(logLevel);
+
+ for (var handler : rootLogger.getHandlers()) {
+ rootLogger.removeHandler(handler);
+ }
+
+ var consoleHandler = new ConsoleHandler();
+ consoleHandler.setLevel(logLevel);
+ consoleHandler.setFormatter(new JavaLoggerFormatter());
+
+ rootLogger.addHandler(consoleHandler);
+
+ try {
+ var fileHandler = new FileHandler(logFile.toString());
+ fileHandler.setLevel(logLevel);
+ fileHandler.setFormatter(new JavaLoggerFormatter());
+ rootLogger.addHandler(fileHandler);
+ } catch (IOException e) {
+ LOGGER.error(
+ e,
+ () -> "Failed to setup file handler for logger. Log statements will not be saved in files."
+ );
+ }
+ }
+}
diff --git a/shared/src/main/java/com/fagi/logging/java/JavaLoggerCreationStrategy.java b/shared/src/main/java/com/fagi/logging/java/JavaLoggerCreationStrategy.java
new file mode 100644
index 00000000..07952da6
--- /dev/null
+++ b/shared/src/main/java/com/fagi/logging/java/JavaLoggerCreationStrategy.java
@@ -0,0 +1,16 @@
+package com.fagi.logging.java;
+
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerCreationStrategy;
+
+/**
+ * Creates a {@link java.util.logging.Logger} from the Java Standard Library
+ *
+ * @author Marcus Haagh
+ */
+public class JavaLoggerCreationStrategy implements FagiLoggerCreationStrategy {
+ @Override
+ public FagiLogger createLogger(Class tClass) {
+ return new JavaLogger(tClass);
+ }
+}
diff --git a/shared/src/main/java/com/fagi/logging/java/JavaLoggerFormatter.java b/shared/src/main/java/com/fagi/logging/java/JavaLoggerFormatter.java
new file mode 100644
index 00000000..5fbfb5ee
--- /dev/null
+++ b/shared/src/main/java/com/fagi/logging/java/JavaLoggerFormatter.java
@@ -0,0 +1,72 @@
+package com.fagi.logging.java;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.logging.Formatter;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+/**
+ *
+ * This class formats logs in the default Fagi format. The format looks as follows:
+ *
+ *
+ * yyyy-MM-dd HH:mm:ss [LOG LEVEL] full.classpath.for.class - LOG MESSAGE
+ *
+ *
+ * The following is an example of a log statement:
+ *
+ *
+ * 2025-07-19 09:42:37 [INFO] com.fagi.server.Server - Starting Server
+ * 2025-07-19 09:42:40 [DEBUG] com.fagi.server.Server - Starting Server took 3 ms
+ *
+ *
+ * If there is an exception in the log, the stacktrace will start on the next line.
+ *
+ *
+ * This should only be used if the user hasn't specified their own logging configuration file.
+ *
+ *
+ * @author Marcus Haagh
+ */
+public class JavaLoggerFormatter extends Formatter {
+ final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ @Override
+ public String format(LogRecord record) {
+ String loggerName = record.getLoggerName();
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(dateFormat.format(new Date(record.getMillis())));
+
+ sb.append(String.format(
+ " [%s] %s - %s%n",
+ transformLogLevel(record.getLevel()),
+ loggerName,
+ formatMessage(record)
+ ));
+
+ if (record.getThrown() != null) {
+ StringWriter sw = new StringWriter();
+ record
+ .getThrown()
+ .printStackTrace(new PrintWriter(sw));
+ sb.append(sw);
+ }
+
+ return sb.toString();
+ }
+
+ private String transformLogLevel(Level level) {
+ return switch (level.getName()) {
+ case "SEVERE" -> "ERROR";
+ case "WARNING" -> "WARNING";
+ case "INFO" -> "INFO";
+ case "FINE" -> "DEBUG";
+ default -> level.getName();
+ };
+ }
+}
diff --git a/shared/src/main/java/com/fagi/utility/Checksum.java b/shared/src/main/java/com/fagi/utility/Checksum.java
index a1a35b75..acb64f7e 100644
--- a/shared/src/main/java/com/fagi/utility/Checksum.java
+++ b/shared/src/main/java/com/fagi/utility/Checksum.java
@@ -40,7 +40,7 @@
* // Calculate checksum for data integrity
* String data = "PUT|user1|name|John Doe";
* String checksum = Checksum.calculateChecksum(data);
- * System.out.println("Checksum: " + checksum); // e.g., "a1b2c3d4"
+ * LOGGER.info(() -> "Checksum: " + checksum); // e.g., "a1b2c3d4"
*
* // Verify data integrity
* String receivedData = "PUT|user1|name|John Doe";
@@ -48,9 +48,9 @@
* String calculatedChecksum = Checksum.calculateChecksum(receivedData);
*
* if (calculatedChecksum.equals(receivedChecksum)) {
- * System.out.println("Data integrity verified");
+ * LOGGER.info(() -> "Data integrity verified");
* } else {
- * System.out.println("Data corruption detected!");
+ * LOGGER.warning(() -> "Data corruption detected!");
* }
* }
*
@@ -112,7 +112,7 @@ private Checksum() {
*
* // Empty string handling
* String emptyChecksum = Checksum.calculateChecksum("");
- * System.out.println("Empty string checksum: " + emptyChecksum);
+ * LOGGER.info(() -> "Empty string checksum: " + emptyChecksum);
*
* // Database log entry checksumming
* String logEntry = "PUT|user123|email|john@example.com";
@@ -203,9 +203,9 @@ public static String calculateChecksum(String data, String encoding)
* // Later, verify the data hasn't been corrupted
* String retrievedData = "Important data";
* if (Checksum.verifyChecksum(retrievedData, storedChecksum)) {
- * System.out.println("Data integrity verified");
+ * LOGGER.info(() -> "Data integrity verified");
* } else {
- * System.out.println("Data corruption detected!");
+ * LOGGER.warning(() -> "Data corruption detected!");
* }
* }
*/
diff --git a/shared/src/main/java/com/fagi/utility/JsonFileOperations.java b/shared/src/main/java/com/fagi/utility/JsonFileOperations.java
index 4036e18a..e7902643 100644
--- a/shared/src/main/java/com/fagi/utility/JsonFileOperations.java
+++ b/shared/src/main/java/com/fagi/utility/JsonFileOperations.java
@@ -1,6 +1,8 @@
package com.fagi.utility;
import com.fagi.conversation.Conversation;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
import com.google.gson.Gson;
import java.io.BufferedReader;
@@ -17,6 +19,7 @@
* Created by costa on 09-11-2016.
*/
public class JsonFileOperations {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(JsonFileOperations.class);
public static final String FAGI_EXTENSION = ".fagi";
public static final String CONFIG_FOLDER_PATH = "config/";
public static final String CONVERSATION_FOLDER_PATH = "conversations/";
@@ -40,13 +43,18 @@ public static void storeObjectToFile(
Gson gson = new Gson();
- PrintWriter out = new PrintWriter(new FileWriter(folderPath + fileName + FAGI_EXTENSION, false));
+ PrintWriter out = new PrintWriter(new FileWriter(
+ folderPath + fileName + FAGI_EXTENSION,
+ false
+ ));
out.println(gson.toJson(object));
out.flush();
out.close();
} catch (IOException e) {
- e.printStackTrace();
- Logger.logStackTrace(e);
+ LOGGER.error(
+ e,
+ () -> "Failed to write object to file."
+ );
}
}
@@ -68,10 +76,15 @@ public static T loadObjectFromFile(
Gson gson = new Gson();
- res = gson.fromJson(json.toString(), clazz);
+ res = gson.fromJson(
+ json.toString(),
+ clazz
+ );
} catch (IOException e) {
- e.printStackTrace();
- Logger.logStackTrace(e);
+ LOGGER.error(
+ e,
+ () -> "Failed to load object from file."
+ );
}
return res;
}
@@ -92,9 +105,12 @@ public static List loadAllObjectsInFolder(
}
for (File file : files) {
- var loadedObj = loadObjectFromFile(file.getAbsolutePath(), clazz);
+ var loadedObj = loadObjectFromFile(
+ file.getAbsolutePath(),
+ clazz
+ );
if (loadedObj == null) {
- System.err.println("Warning: We were about to add null due to this file path: " + file.getAbsolutePath());
+ LOGGER.warning(() -> "We were about to add null due to this file path: " + file.getAbsolutePath());
continue;
}
res.add(loadedObj);
@@ -104,7 +120,11 @@ public static List loadAllObjectsInFolder(
}
public static void storeConversation(Conversation c) {
- storeObjectToFile(c, CONVERSATION_FOLDER_PATH, c.getId() + "");
+ storeObjectToFile(
+ c,
+ CONVERSATION_FOLDER_PATH,
+ c.getId() + ""
+ );
}
public static void storeClientConversation(
@@ -114,14 +134,24 @@ public static void storeClientConversation(
if (!clientFolder.exists()) {
clientFolder.mkdir();
}
- storeObjectToFile(c, username + "/" + CONVERSATION_FOLDER_PATH, c.getId() + "");
+ storeObjectToFile(
+ c,
+ username + "/" + CONVERSATION_FOLDER_PATH,
+ c.getId() + ""
+ );
}
public static List loadAllConversations() {
- return loadAllObjectsInFolder(CONVERSATION_FOLDER_PATH, Conversation.class);
+ return loadAllObjectsInFolder(
+ CONVERSATION_FOLDER_PATH,
+ Conversation.class
+ );
}
public static List loadAllClientConversations(String username) {
- return loadAllObjectsInFolder(username + "/" + CONVERSATION_FOLDER_PATH, Conversation.class);
+ return loadAllObjectsInFolder(
+ username + "/" + CONVERSATION_FOLDER_PATH,
+ Conversation.class
+ );
}
}
diff --git a/shared/src/main/java/com/fagi/utility/Logger.java b/shared/src/main/java/com/fagi/utility/Logger.java
deleted file mode 100644
index 59db7300..00000000
--- a/shared/src/main/java/com/fagi/utility/Logger.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.fagi.utility;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.PrintWriter;
-
-/**
- * Created by costa on 02-04-2017.
- */
-public class Logger {
- public static final String LOGS_FOLDER_PATH = "logs/";
-
- public static void logStackTrace(Exception e) {
- long time = System.nanoTime();
- String filePath = LOGS_FOLDER_PATH + "log-error-" + time + JsonFileOperations.FAGI_EXTENSION;
- File folder = new File(LOGS_FOLDER_PATH);
- File logFile = new File(filePath);
- if (!folder.exists()) {
- folder.mkdir();
- }
- try {
- logFile.createNewFile();
-
- PrintWriter out = new PrintWriter(new FileWriter(logFile), false);
- e.printStackTrace(out);
- out.flush();
- out.close();
- } catch (IOException ioe) {
- e.printStackTrace();
- }
- }
-}
diff --git a/shared/src/main/java/com/fagi/utility/NetworkUtility.java b/shared/src/main/java/com/fagi/utility/NetworkUtility.java
index 80e9daa8..8f17ca1c 100644
--- a/shared/src/main/java/com/fagi/utility/NetworkUtility.java
+++ b/shared/src/main/java/com/fagi/utility/NetworkUtility.java
@@ -1,5 +1,8 @@
package com.fagi.utility;
+import com.fagi.logging.FagiLogger;
+import com.fagi.logging.FagiLoggerFactory;
+
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -10,6 +13,8 @@
* Used for utility functions related to network
*/
public class NetworkUtility {
+ private static final FagiLogger LOGGER = FagiLoggerFactory.createLogger(NetworkUtility.class);
+
/**
* Calls external service to find the public IP of the machine the server is running on
*
@@ -17,16 +22,21 @@ public class NetworkUtility {
*/
public static String getExternalIP() {
String ip = "";
+ String checkIPServiceUrl = "http://checkip.amazonaws.com";
try {
URL whatismyip = URI
- .create("http://checkip.amazonaws.com")
+ .create(checkIPServiceUrl)
.toURL();
BufferedReader in = new BufferedReader(new InputStreamReader(whatismyip.openStream()));
ip = in.readLine();
} catch (IOException e) {
- System.err.println("Could not get public it. Cause: " + e);
+ LOGGER.error(
+ e,
+ () -> "Could not get public ip from " + checkIPServiceUrl
+ );
}
+
return ip;
}
}
diff --git a/shared/src/test/java/com/fagi/db/LogBasedDatabaseConcurrencyTests.java b/shared/src/test/java/com/fagi/db/LogBasedDatabaseConcurrencyTests.java
index efaa3658..a9cf0857 100644
--- a/shared/src/test/java/com/fagi/db/LogBasedDatabaseConcurrencyTests.java
+++ b/shared/src/test/java/com/fagi/db/LogBasedDatabaseConcurrencyTests.java
@@ -1,14 +1,23 @@
package com.fagi.db;
-import org.junit.jupiter.api.*;
+import com.fagi.BaseFagiTest;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;
-import java.util.*;
-import java.util.concurrent.*;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-public class LogBasedDatabaseConcurrencyTests {
+public class LogBasedDatabaseConcurrencyTests extends BaseFagiTest {
private LogBasedDatabase db;
@BeforeEach
diff --git a/shared/src/test/java/com/fagi/db/LogBasedDatabaseCorruptionTests.java b/shared/src/test/java/com/fagi/db/LogBasedDatabaseCorruptionTests.java
index 4744a98a..4b1ba985 100644
--- a/shared/src/test/java/com/fagi/db/LogBasedDatabaseCorruptionTests.java
+++ b/shared/src/test/java/com/fagi/db/LogBasedDatabaseCorruptionTests.java
@@ -1,15 +1,25 @@
package com.fagi.db;
-import org.junit.jupiter.api.*;
+import com.fagi.BaseFagiTest;
+import com.fagi.logging.TestLogLevel;
+import com.fagi.logging.TestLogRecord;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
-import java.io.*;
-import java.nio.file.*;
-import java.util.*;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.Arrays;
+import java.util.List;
import static com.fagi.utility.Checksum.calculateChecksum;
-public class LogBasedDatabaseCorruptionTests {
+public class LogBasedDatabaseCorruptionTests extends BaseFagiTest {
private LogBasedDatabase db;
private String dbPath;
@@ -53,14 +63,27 @@ void testCorruptedChecksum() throws IOException, DatabaseInitializeException, Da
corruptedContent
);
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- System.setErr(new PrintStream(out));
-
db = new LogBasedDatabase(dbPath);
+ List> logRecords = lookupLogRecordsForClass(LogBasedDatabase.class);
+
+ Assertions.assertEquals(
+ 1,
+ logRecords.size()
+ );
+
+ Assertions.assertEquals(
+ "Corruption detected at line 2",
+ logRecords
+ .getFirst()
+ .message()
+ );
+
Assertions.assertEquals(
- "Corruption detected at line 2" + System.lineSeparator(),
- out.toString()
+ TestLogLevel.WARNING,
+ logRecords
+ .getFirst()
+ .logLevel()
);
Assertions.assertEquals(
@@ -133,15 +156,28 @@ void testOperationsAfterRecovery() throws IOException, DatabaseInitializeExcepti
StandardOpenOption.APPEND
);
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- System.setErr(new PrintStream(out));
-
// Reload and continue operations
db = new LogBasedDatabase(dbPath);
+ List> logRecords = lookupLogRecordsForClass(LogBasedDatabase.class);
+
+ Assertions.assertEquals(
+ 1,
+ logRecords.size()
+ );
+
+ Assertions.assertEquals(
+ "Malformed entry at line 3",
+ logRecords
+ .getFirst()
+ .message()
+ );
+
Assertions.assertEquals(
- "Malformed entry at line 3" + System.lineSeparator(),
- out.toString()
+ TestLogLevel.WARNING,
+ logRecords
+ .getFirst()
+ .logLevel()
);
// Should be able to continue normal operations
diff --git a/shared/src/test/java/com/fagi/db/LogBasedDatabaseIntegrationTests.java b/shared/src/test/java/com/fagi/db/LogBasedDatabaseIntegrationTests.java
index 11a8fee3..8efc2502 100644
--- a/shared/src/test/java/com/fagi/db/LogBasedDatabaseIntegrationTests.java
+++ b/shared/src/test/java/com/fagi/db/LogBasedDatabaseIntegrationTests.java
@@ -1,19 +1,25 @@
package com.fagi.db;
-import org.junit.jupiter.api.*;
+import com.fagi.BaseFagiTest;
+import com.fagi.logging.TestLogRecord;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mockito;
-import java.io.*;
-import java.nio.file.*;
-import java.util.*;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import java.util.Map;
import static com.fagi.utility.Checksum.calculateChecksum;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
-class LogBasedDatabaseIntegrationTests {
-
+class LogBasedDatabaseIntegrationTests extends BaseFagiTest {
@Test
void testCloseIOException(@TempDir Path tempDir) throws IOException, DatabaseInitializeException {
FileWriter mockWriter = Mockito.mock(FileWriter.class);
@@ -21,26 +27,44 @@ void testCloseIOException(@TempDir Path tempDir) throws IOException, DatabaseIni
.when(mockWriter)
.close();
Path dbPath = tempDir.resolve("test.db");
- var ignored = dbPath.toFile().createNewFile();
+ var ignored = dbPath
+ .toFile()
+ .createNewFile();
LogBasedDatabase db = new LogBasedDatabase(
dbPath.toString(),
mockWriter
);
- // Capture System.err output to verify error message
- ByteArrayOutputStream errOutput = new ByteArrayOutputStream();
- PrintStream originalErr = System.err;
- System.setErr(new PrintStream(errOutput));
-
db.close();
- String errorOutput = errOutput.toString();
- Assertions.assertTrue(errorOutput.contains("Error closing database"));
- Assertions.assertTrue(errorOutput.contains("Disk full"));
+ List> logRecords = lookupLogRecordsForClass(LogBasedDatabase.class);
+
+ Assertions.assertEquals(
+ 1,
+ logRecords.size()
+ );
+ Assertions.assertEquals(
+ "Error closing database.",
+ logRecords
+ .getFirst()
+ .message()
+ );
+ Assertions.assertInstanceOf(
+ IOException.class,
+ logRecords
+ .getFirst()
+ .throwable()
+ );
+ Assertions.assertEquals(
+ "Disk full",
+ logRecords
+ .getFirst()
+ .throwable()
+ .getMessage()
+ );
verify(mockWriter).close();
- System.setErr(originalErr);
}
@Test
diff --git a/shared/src/test/java/com/fagi/db/LogBasedDatabasePerformanceTests.java b/shared/src/test/java/com/fagi/db/LogBasedDatabasePerformanceTests.java
index 8a1ac3dc..a8488bcb 100644
--- a/shared/src/test/java/com/fagi/db/LogBasedDatabasePerformanceTests.java
+++ b/shared/src/test/java/com/fagi/db/LogBasedDatabasePerformanceTests.java
@@ -1,17 +1,23 @@
package com.fagi.db;
-import org.junit.jupiter.api.*;
+import com.fagi.BaseFagiTest;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;
-import java.util.*;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-public class LogBasedDatabasePerformanceTests {
+public class LogBasedDatabasePerformanceTests extends BaseFagiTest {
private LogBasedDatabase db;
private String dbPath;
diff --git a/shared/src/test/java/com/fagi/db/LogBasedDatabaseResourceTests.java b/shared/src/test/java/com/fagi/db/LogBasedDatabaseResourceTests.java
index 3be62eaa..c9075a4b 100644
--- a/shared/src/test/java/com/fagi/db/LogBasedDatabaseResourceTests.java
+++ b/shared/src/test/java/com/fagi/db/LogBasedDatabaseResourceTests.java
@@ -1,12 +1,13 @@
package com.fagi.db;
+import com.fagi.BaseFagiTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;
-public class LogBasedDatabaseResourceTests {
+public class LogBasedDatabaseResourceTests extends BaseFagiTest {
@Test
void testMemoryUsage(@TempDir Path tempDir) throws DatabaseInitializeException, DatabaseUpdateException {
String dbPath = tempDir
diff --git a/shared/src/test/java/com/fagi/db/LogBasedDatabaseTests.java b/shared/src/test/java/com/fagi/db/LogBasedDatabaseTests.java
index da31133e..3d843486 100644
--- a/shared/src/test/java/com/fagi/db/LogBasedDatabaseTests.java
+++ b/shared/src/test/java/com/fagi/db/LogBasedDatabaseTests.java
@@ -1,5 +1,6 @@
package com.fagi.db;
+import com.fagi.BaseFagiTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -9,7 +10,7 @@
import java.nio.file.Paths;
import java.util.Map;
-public class LogBasedDatabaseTests {
+public class LogBasedDatabaseTests extends BaseFagiTest {
private String dbPath;
@BeforeEach
diff --git a/shared/src/test/java/com/fagi/logging/FagiLoggerFactoryTest.java b/shared/src/test/java/com/fagi/logging/FagiLoggerFactoryTest.java
new file mode 100644
index 00000000..f7d0d6e4
--- /dev/null
+++ b/shared/src/test/java/com/fagi/logging/FagiLoggerFactoryTest.java
@@ -0,0 +1,99 @@
+package com.fagi.logging;
+
+import com.fagi.BaseFagiTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.nio.file.Path;
+import java.util.List;
+
+public class FagiLoggerFactoryTest extends BaseFagiTest {
+ @Test
+ void testCanCreateLogger() {
+ FagiLogger logger = FagiLoggerFactory.createLogger(FagiLoggerFactoryTest.class);
+
+ Assertions.assertNotNull(logger);
+ Assertions.assertInstanceOf(
+ TestLogger.class,
+ logger
+ );
+ Assertions.assertEquals(
+ FagiLoggerFactoryTest.class,
+ ((TestLogger) logger).getLoggerClass()
+ );
+ }
+
+ @Test
+ void testConfigurationAvailableWhenStrategyReturnsTrue() {
+ FagiLoggerFactory.setLoggerConfigStrategy(new TestLoggerConfigurationStrategy(true));
+
+ Assertions.assertTrue(FagiLoggerFactory.isCustomConfigurationAvailable());
+ }
+
+ @Test
+ void testConfigurationNotAvailableWhenStrategyReturnsFalse() {
+ FagiLoggerFactory.setLoggerConfigStrategy(new TestLoggerConfigurationStrategy(false));
+
+ Assertions.assertFalse(FagiLoggerFactory.isCustomConfigurationAvailable());
+ }
+
+ @Test
+ void testFactoryUsesConfigurationStrategyWhenNoCustomConfigurationIsProvided() {
+ // Creates logger to be used by the factory. This allows us to check the log records later.
+ FagiLoggerFactory.setLogger(FagiLoggerFactory.createLogger(FagiLoggerFactory.class));
+
+ TestLoggerConfigurationStrategy loggerConfigStrategy = new TestLoggerConfigurationStrategy(false);
+ FagiLoggerFactory.setLoggerConfigStrategy(loggerConfigStrategy);
+
+ Path logFile = Path.of("/some/path");
+ FagiLoggerFactory.setupDefaultConfiguration(logFile);
+
+ List> testLogRecords = lookupLogRecordsForClass(FagiLoggerFactory.class);
+
+ Assertions.assertAll(
+ () -> Assertions.assertTrue(testLogRecords.isEmpty()),
+ () -> Assertions.assertTrue(loggerConfigStrategy.isConfigured()),
+ () -> Assertions.assertEquals(
+ logFile,
+ loggerConfigStrategy.getLogFile()
+ )
+ );
+ }
+
+ @Test
+ void testFactoryDebugLogsWhenItShouldntUseDefaultConfiguration() {
+ // Creates logger to be used by the factory. This allows us to check the log records later.
+ FagiLoggerFactory.setLogger(FagiLoggerFactory.createLogger(FagiLoggerFactory.class));
+
+ TestLoggerConfigurationStrategy loggerConfigStrategy = new TestLoggerConfigurationStrategy(true);
+ FagiLoggerFactory.setLoggerConfigStrategy(loggerConfigStrategy);
+
+ FagiLoggerFactory.setupDefaultConfiguration(Path.of("/some/path"));
+
+ List> testLogRecords = lookupLogRecordsForClass(FagiLoggerFactory.class);
+
+ Assertions.assertEquals(
+ 1,
+ testLogRecords.size()
+ );
+
+ TestLogRecord> logRecord = testLogRecords.getFirst();
+
+ Assertions.assertAll(
+ () -> Assertions.assertEquals(
+ TestLogLevel.DEBUG,
+ logRecord.logLevel()
+ ),
+ () -> Assertions.assertEquals(
+ FagiLoggerFactory.class,
+ logRecord.loggerClass()
+ ),
+ () -> Assertions.assertEquals(
+ "Custom configuration has been provided. No further configuration is made.",
+ logRecord.message()
+ ),
+ () -> Assertions.assertNull(logRecord.throwable()),
+ () -> Assertions.assertFalse(loggerConfigStrategy.isConfigured())
+ );
+ }
+}
diff --git a/shared/src/test/java/com/fagi/logging/java/JavaLoggerConfigStrategyTest.java b/shared/src/test/java/com/fagi/logging/java/JavaLoggerConfigStrategyTest.java
new file mode 100644
index 00000000..303c20c2
--- /dev/null
+++ b/shared/src/test/java/com/fagi/logging/java/JavaLoggerConfigStrategyTest.java
@@ -0,0 +1,292 @@
+package com.fagi.logging.java;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.FileHandler;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import java.util.stream.Stream;
+
+/**
+ * Doesn't utilise {@link com.fagi.BaseFagiTest} as we want to test the real logging framework to check if the
+ * logs work as expected.
+ */
+class JavaLoggerConfigStrategyTest {
+ private static Path tempDir;
+ private final Logger LOGGER = Logger.getLogger(JavaLoggerConfigStrategy.class.getName());
+ private final TestJavaLoggingHandler testJavaLoggingHandler = new TestJavaLoggingHandler();
+ private final JavaLoggerConfigStrategy strategy = new JavaLoggerConfigStrategy();
+
+ @BeforeAll
+ static void setupClass() throws IOException {
+ tempDir = Path.of("build/test_logs");
+ // Deleting the "build/test_logs" folder before executing the test.
+ // This is done to mitigate Windows locking the log files in such a way that they cannot be
+ // deleted after running the tests.
+ if (Files.exists(tempDir)) {
+ try (Stream walk = Files.walk(tempDir)) {
+ walk
+ .sorted(Comparator.reverseOrder())
+ .forEach(path -> {
+ try {
+ Files.delete(path);
+ } catch (IOException e) {
+ throw new RuntimeException(
+ "Failed to delete: " + path,
+ e
+ );
+ }
+ });
+ }
+ }
+
+ if (!Files.exists(tempDir)) {
+ // Create the "build/test_logs" folder
+ Files.createDirectory(tempDir);
+ }
+ }
+
+ @BeforeEach
+ void setup() {
+ LOGGER.addHandler(testJavaLoggingHandler);
+ }
+
+ @Test
+ void testRootLoggerHasConsoleAndFileHandlersAndBothHasCorrectFormatter() {
+ strategy.setupDefaultConfiguration(createLogFile());
+
+ var logger = Logger.getLogger("");
+
+ Assertions.assertAll(
+ () -> Assertions.assertEquals(
+ 2,
+ logger.getHandlers().length
+ ),
+ () -> Assertions.assertTrue(Arrays
+ .stream(logger.getHandlers())
+ .anyMatch(h -> h instanceof ConsoleHandler)),
+ () -> Assertions.assertTrue(Arrays
+ .stream(logger.getHandlers())
+ .anyMatch(h -> h instanceof FileHandler)),
+ () -> Assertions.assertTrue(Arrays
+ .stream(logger.getHandlers())
+ .allMatch(h -> h.getFormatter() instanceof JavaLoggerFormatter))
+ );
+
+ cleanHandlers();
+ }
+
+ @Test
+ void testConfigLogsIfFailsToMakeFileHandler() throws IOException {
+ Path restrictedFile = createLogFile();
+ Files.createFile(restrictedFile);
+
+ // Remove read/write permissions on file to force IOException.
+ restrictedFile
+ .toFile()
+ .setReadable(
+ false,
+ false
+ );
+ restrictedFile
+ .toFile()
+ .setWritable(
+ false,
+ false
+ );
+
+ strategy.setupDefaultConfiguration(restrictedFile.toAbsolutePath());
+
+ List logRecords = testJavaLoggingHandler.getLogRecords();
+ Assertions.assertAll(
+ () -> Assertions.assertEquals(
+ 1,
+ logRecords.size()
+ ),
+ () -> Assertions.assertEquals(
+ Level.SEVERE,
+ logRecords
+ .getFirst()
+ .getLevel()
+ ),
+ () -> Assertions.assertEquals(
+ "Failed to setup file handler for logger. Log statements will not be saved in files.",
+ logRecords
+ .getFirst()
+ .getMessage()
+ ),
+ () -> Assertions.assertInstanceOf(
+ IOException.class,
+ logRecords
+ .getFirst()
+ .getThrown()
+ )
+ );
+
+ cleanHandlers();
+ }
+
+ @Test
+ void testShouldNotLogFineRecords() {
+ strategy.setupDefaultConfiguration(createLogFile());
+
+ LOGGER.fine(() -> "This record should not be recorded.");
+
+ Assertions.assertEquals(
+ 0,
+ testJavaLoggingHandler
+ .getLogRecords()
+ .size()
+ );
+
+ cleanHandlers();
+ }
+
+ @Test
+ void testShouldNotLogFinerRecords() {
+ strategy.setupDefaultConfiguration(createLogFile());
+
+ LOGGER.finer(() -> "This record should not be recorded.");
+
+ Assertions.assertEquals(
+ 0,
+ testJavaLoggingHandler
+ .getLogRecords()
+ .size()
+ );
+
+ cleanHandlers();
+ }
+
+ @Test
+ void testShouldNotLogFinestRecords() {
+ strategy.setupDefaultConfiguration(createLogFile());
+
+ LOGGER.finest(() -> "This record should not be recorded.");
+
+ Assertions.assertEquals(
+ 0,
+ testJavaLoggingHandler
+ .getLogRecords()
+ .size()
+ );
+
+ cleanHandlers();
+ }
+
+ @Test
+ void testShouldLogInfoRecords() {
+ strategy.setupDefaultConfiguration(createLogFile());
+
+ LOGGER.info(() -> "This record should be recorded.");
+
+ Assertions.assertAll(() -> Assertions.assertEquals(
+ 1,
+ testJavaLoggingHandler
+ .getLogRecords()
+ .size()
+ ));
+
+ cleanHandlers();
+ }
+
+ @Test
+ void testShouldLogWarningRecords() {
+ strategy.setupDefaultConfiguration(createLogFile());
+
+ LOGGER.warning(() -> "This record should be recorded.");
+
+ Assertions.assertAll(() -> Assertions.assertEquals(
+ 1,
+ testJavaLoggingHandler
+ .getLogRecords()
+ .size()
+ ));
+
+ cleanHandlers();
+ }
+
+ @Test
+ void testShouldLogSevereRecords() {
+ strategy.setupDefaultConfiguration(createLogFile());
+
+ LOGGER.severe(() -> "This record should be recorded.");
+
+ Assertions.assertAll(() -> Assertions.assertEquals(
+ 1,
+ testJavaLoggingHandler
+ .getLogRecords()
+ .size()
+ ));
+
+ cleanHandlers();
+ }
+
+ @Test
+ void testCustomConfigCheckIsTrueWhenPropertyIsNonNull() {
+ String sysPropertyOldValue = System.getProperty(JavaLoggerConfigStrategy.LOGGING_CONFIG_FILE_SYSTEM_PROPERTY_NAME);
+ System.setProperty(
+ JavaLoggerConfigStrategy.LOGGING_CONFIG_FILE_SYSTEM_PROPERTY_NAME,
+ "Some non-null value"
+ );
+
+ boolean isCustomConfigAvailable = strategy.isCustomConfigurationAvailable();
+
+ Assertions.assertTrue(isCustomConfigAvailable);
+
+ if (sysPropertyOldValue != null) {
+ System.setProperty(
+ JavaLoggerConfigStrategy.LOGGING_CONFIG_FILE_SYSTEM_PROPERTY_NAME,
+ sysPropertyOldValue
+ );
+ } else {
+ System.clearProperty(JavaLoggerConfigStrategy.LOGGING_CONFIG_FILE_SYSTEM_PROPERTY_NAME);
+ }
+
+ cleanHandlers();
+ }
+
+ @Test
+ void testCustomConfigCheckIsFalseWhenPropertyIsNull() {
+ String sysPropertyOldValue = System.getProperty(JavaLoggerConfigStrategy.LOGGING_CONFIG_FILE_SYSTEM_PROPERTY_NAME);
+ System.clearProperty(JavaLoggerConfigStrategy.LOGGING_CONFIG_FILE_SYSTEM_PROPERTY_NAME);
+
+ boolean isCustomConfigAvailable = strategy.isCustomConfigurationAvailable();
+
+ Assertions.assertFalse(isCustomConfigAvailable);
+
+ if (sysPropertyOldValue != null) {
+ System.setProperty(
+ JavaLoggerConfigStrategy.LOGGING_CONFIG_FILE_SYSTEM_PROPERTY_NAME,
+ sysPropertyOldValue
+ );
+ }
+
+ cleanHandlers();
+ }
+
+ private static Path createLogFile() {
+ return tempDir.resolve("test_" + System.currentTimeMillis() + ".log");
+ }
+
+ private void cleanHandlers() {
+ Handler[] handlers = LOGGER.getHandlers();
+ for (Handler handler : handlers) {
+ handler.flush();
+ handler.close();
+ LOGGER.removeHandler(handler);
+ }
+ }
+}
diff --git a/shared/src/test/java/com/fagi/logging/java/JavaLoggerCreationStrategyTest.java b/shared/src/test/java/com/fagi/logging/java/JavaLoggerCreationStrategyTest.java
new file mode 100644
index 00000000..afb71b8f
--- /dev/null
+++ b/shared/src/test/java/com/fagi/logging/java/JavaLoggerCreationStrategyTest.java
@@ -0,0 +1,22 @@
+package com.fagi.logging.java;
+
+import com.fagi.logging.FagiLogger;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class JavaLoggerCreationStrategyTest {
+ @Test
+ void verifyStrategyCreatesJavaLogger() {
+ var strategy = new JavaLoggerCreationStrategy();
+
+ FagiLogger logger = strategy.createLogger(JavaLoggerCreationStrategyTest.class);
+
+ Assertions.assertAll(
+ () -> Assertions.assertNotNull(logger),
+ () -> Assertions.assertInstanceOf(
+ JavaLogger.class,
+ logger
+ )
+ );
+ }
+}
diff --git a/shared/src/test/java/com/fagi/logging/java/JavaLoggerFormatterTest.java b/shared/src/test/java/com/fagi/logging/java/JavaLoggerFormatterTest.java
new file mode 100644
index 00000000..65a73c74
--- /dev/null
+++ b/shared/src/test/java/com/fagi/logging/java/JavaLoggerFormatterTest.java
@@ -0,0 +1,37 @@
+package com.fagi.logging.java;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+class JavaLoggerFormatterTest {
+ private final JavaLoggerFormatter formatter = new JavaLoggerFormatter();
+
+ @Test
+ void testFormatterShouldGiveCorrectFormattedStrings() {
+ var logRecord = new LogRecord(
+ Level.FINE,
+ "This is the log message"
+ );
+ logRecord.setLoggerName(JavaLoggerFormatterTest.class.getName());
+ logRecord.setThrown(new IOException());
+
+ String dateTimeString = formatter.dateFormat.format(new Date(logRecord.getMillis()));
+
+ var expectedFormattedLogEntry = dateTimeString + " [DEBUG] " + logRecord.getLoggerName() + " - " + logRecord.getMessage() + System.lineSeparator() + IOException.class.getName();
+
+ // Normalize line endings such that the test works on different operating systems
+ String formattedLogRecord = formatter
+ .format(logRecord)
+ .replace(
+ "\n\r",
+ System.lineSeparator()
+ );
+
+ Assertions.assertTrue(formattedLogRecord.startsWith(expectedFormattedLogEntry));
+ }
+}
\ No newline at end of file
diff --git a/shared/src/test/java/com/fagi/logging/java/TestJavaLoggingHandler.java b/shared/src/test/java/com/fagi/logging/java/TestJavaLoggingHandler.java
new file mode 100644
index 00000000..da094395
--- /dev/null
+++ b/shared/src/test/java/com/fagi/logging/java/TestJavaLoggingHandler.java
@@ -0,0 +1,36 @@
+package com.fagi.logging.java;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+
+/**
+ * This class is used to record all logs sent to a logger
+ */
+public class TestJavaLoggingHandler extends Handler {
+ private final List logRecords = new ArrayList<>();
+
+ @Override
+ public void publish(LogRecord record) {
+ logRecords.add(record);
+ }
+
+ /**
+ * Does nothing
+ */
+ @Override
+ public void flush() {
+ }
+
+ /**
+ * Does nothing
+ */
+ @Override
+ public void close() throws SecurityException {
+ }
+
+ public List getLogRecords() {
+ return logRecords;
+ }
+}
diff --git a/shared/src/testFixtures/java/com/fagi/BaseFagiTest.java b/shared/src/testFixtures/java/com/fagi/BaseFagiTest.java
new file mode 100644
index 00000000..2a71407d
--- /dev/null
+++ b/shared/src/testFixtures/java/com/fagi/BaseFagiTest.java
@@ -0,0 +1,96 @@
+package com.fagi;
+
+import com.fagi.logging.FagiLoggerFactory;
+import com.fagi.logging.TestLogLevel;
+import com.fagi.logging.TestLogRecord;
+import com.fagi.logging.TestLogger;
+import com.fagi.logging.TestLoggerConfigurationStrategy;
+import com.fagi.logging.TestLoggerCreationStrategy;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ * This abstract class offers utility to make unit tests easier to write.
+ *
+ *
+ * All unit tests should inherit from this class.
+ *
+ */
+public abstract class BaseFagiTest {
+ /**
+ * All loggers registered during a test class will be stored here. This allows for reading log records
+ * and clearing log records between tests.
+ */
+ private static final Map, TestLogger>> LOGGERS = new HashMap<>();
+
+ @BeforeAll
+ static void baseTestClassSetup() {
+ FagiLoggerFactory.setLoggerCreationStrategy(new TestLoggerCreationStrategy(LOGGERS));
+ FagiLoggerFactory.setLoggerConfigStrategy(new TestLoggerConfigurationStrategy(false));
+ }
+
+ /**
+ *
+ * Performs tear down after every test.
+ *
+ *
+ * This currently does the following:
+ *
+ * Clears log records from all registered loggers.
+ *
+ *
+ */
+ @AfterEach
+ void baseTestTearDown() {
+ for (TestLogger> logger : LOGGERS.values()) {
+ logger.clearRecords();
+ }
+ }
+
+ /**
+ *
+ * Allows tests to lookup log records from a logger registered on the given Class.
+ *
+ *
+ * To be used when testing if logs have been made correctly.
+ *
+ *
+ * @param tClass the class the logger was registered on.
+ * @return a list of records made with a logger registered on the given Class.
+ * @throws AssertionError if no logger has been registered on the given Class.
+ */
+ public List> lookupLogRecordsForClass(Class> tClass) {
+ var logger = LOGGERS.get(tClass);
+ if (logger != null) {
+ return logger.getLogRecords();
+ }
+ throw new AssertionError("No logger has been registered for class: " + tClass);
+ }
+
+ /**
+ *
+ * Allows tests to lookup log records from a logger registered on the given Class with the given log level.
+ *
+ *
+ * To be used when testing if logs have been made correctly.
+ *
+ *
+ * @param tClass the class the logger was registered on.
+ * @param logLevel the log level of records to be found.
+ * @return a list of records made with a logger registered on the given Class at the given level.
+ * @throws AssertionError if no logger has been registered on the given Class.
+ */
+ public List> lookupLogRecordsForClass(
+ Class> tClass,
+ TestLogLevel logLevel) {
+ return lookupLogRecordsForClass(tClass)
+ .stream()
+ .filter(logRecord -> logLevel.equals(logRecord.logLevel()))
+ .toList();
+ }
+}
diff --git a/shared/src/testFixtures/java/com/fagi/logging/TestLogLevel.java b/shared/src/testFixtures/java/com/fagi/logging/TestLogLevel.java
new file mode 100644
index 00000000..7e7720b9
--- /dev/null
+++ b/shared/src/testFixtures/java/com/fagi/logging/TestLogLevel.java
@@ -0,0 +1,11 @@
+package com.fagi.logging;
+
+/**
+ * A test representation of log levels.
+ */
+public enum TestLogLevel {
+ DEBUG,
+ INFO,
+ WARNING,
+ ERROR
+}
diff --git a/shared/src/testFixtures/java/com/fagi/logging/TestLogRecord.java b/shared/src/testFixtures/java/com/fagi/logging/TestLogRecord.java
new file mode 100644
index 00000000..5eb7c4fe
--- /dev/null
+++ b/shared/src/testFixtures/java/com/fagi/logging/TestLogRecord.java
@@ -0,0 +1,12 @@
+package com.fagi.logging;
+
+/**
+ * A test representation of a log record. Made to be agnostic about the logging framework used by the production code.
+ *
+ * @param loggerClass the Class the logger that made the record was registered on.
+ * @param logLevel the log level the log record was made to.
+ * @param message the logged message.
+ * @param throwable the throwable associated with the message.
+ */
+public record TestLogRecord(Class loggerClass, TestLogLevel logLevel, String message, Throwable throwable) {
+}
diff --git a/shared/src/testFixtures/java/com/fagi/logging/TestLogger.java b/shared/src/testFixtures/java/com/fagi/logging/TestLogger.java
new file mode 100644
index 00000000..497c2bd3
--- /dev/null
+++ b/shared/src/testFixtures/java/com/fagi/logging/TestLogger.java
@@ -0,0 +1,130 @@
+package com.fagi.logging;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ *
+ * This class is used to simulate a logger during unit tests, making it easier to test that the correct
+ * logs were made.
+ *
+ *
+ * All log records are stored in a list, ready to be read by tests.
+ *
+ *
+ * Log records should be cleared after every unit test as loggers are stored statically,
+ * making it possible for records to carry over to other tests.
+ *
+ */
+public class TestLogger implements FagiLogger {
+ private final List> logRecords = new ArrayList<>();
+ private final Class loggerClass;
+
+ public TestLogger(Class tClass) {
+ this.loggerClass = tClass;
+ }
+
+ @Override
+ public void debug(Supplier messageSupplier) {
+ logRecords.add(new TestLogRecord<>(
+ loggerClass,
+ TestLogLevel.DEBUG,
+ messageSupplier.get(),
+ null
+ ));
+ }
+
+ @Override
+ public void debug(
+ Throwable throwable,
+ Supplier messageSupplier) {
+ logRecords.add(new TestLogRecord<>(
+ loggerClass,
+ TestLogLevel.DEBUG,
+ messageSupplier.get(),
+ throwable
+ ));
+ }
+
+ @Override
+ public void info(Supplier messageSupplier) {
+ logRecords.add(new TestLogRecord<>(
+ loggerClass,
+ TestLogLevel.INFO,
+ messageSupplier.get(),
+ null
+ ));
+ }
+
+ @Override
+ public void info(
+ Throwable throwable,
+ Supplier messageSupplier) {
+ logRecords.add(new TestLogRecord<>(
+ loggerClass,
+ TestLogLevel.INFO,
+ messageSupplier.get(),
+ throwable
+ ));
+ }
+
+ @Override
+ public void warning(Supplier messageSupplier) {
+ logRecords.add(new TestLogRecord<>(
+ loggerClass,
+ TestLogLevel.WARNING,
+ messageSupplier.get(),
+ null
+ ));
+ }
+
+ @Override
+ public void warning(
+ Throwable throwable,
+ Supplier messageSupplier) {
+ logRecords.add(new TestLogRecord<>(
+ loggerClass,
+ TestLogLevel.WARNING,
+ messageSupplier.get(),
+ throwable
+ ));
+ }
+
+ @Override
+ public void error(Supplier messageSupplier) {
+ logRecords.add(new TestLogRecord<>(
+ loggerClass,
+ TestLogLevel.ERROR,
+ messageSupplier.get(),
+ null
+ ));
+ }
+
+ @Override
+ public void error(
+ Throwable throwable,
+ Supplier messageSupplier) {
+ logRecords.add(new TestLogRecord<>(
+ loggerClass,
+ TestLogLevel.ERROR,
+ messageSupplier.get(),
+ throwable
+ ));
+ }
+
+ public List> getLogRecords() {
+ return logRecords;
+ }
+
+ public Class getLoggerClass() {
+ return loggerClass;
+ }
+
+ /**
+ * Clears the stored log records. Should be done after every unit test.
+ */
+ public void clearRecords() {
+ logRecords.clear();
+ }
+}
diff --git a/shared/src/testFixtures/java/com/fagi/logging/TestLoggerConfigurationStrategy.java b/shared/src/testFixtures/java/com/fagi/logging/TestLoggerConfigurationStrategy.java
new file mode 100644
index 00000000..451231a2
--- /dev/null
+++ b/shared/src/testFixtures/java/com/fagi/logging/TestLoggerConfigurationStrategy.java
@@ -0,0 +1,35 @@
+package com.fagi.logging;
+
+import java.nio.file.Path;
+
+/**
+ * Used to help test logging
+ */
+public class TestLoggerConfigurationStrategy implements FagiLoggerConfigStrategy {
+ private final boolean customConfigurationAvailable;
+ private boolean configured = false;
+ private Path logFile;
+
+ public TestLoggerConfigurationStrategy(boolean customConfigurationAvailable) {
+ this.customConfigurationAvailable = customConfigurationAvailable;
+ }
+
+ @Override
+ public boolean isCustomConfigurationAvailable() {
+ return customConfigurationAvailable;
+ }
+
+ @Override
+ public void setupDefaultConfiguration(Path logFile) {
+ configured = true;
+ this.logFile = logFile;
+ }
+
+ public boolean isConfigured() {
+ return configured;
+ }
+
+ public Path getLogFile() {
+ return logFile;
+ }
+}
diff --git a/shared/src/testFixtures/java/com/fagi/logging/TestLoggerCreationStrategy.java b/shared/src/testFixtures/java/com/fagi/logging/TestLoggerCreationStrategy.java
new file mode 100644
index 00000000..7eea4ab1
--- /dev/null
+++ b/shared/src/testFixtures/java/com/fagi/logging/TestLoggerCreationStrategy.java
@@ -0,0 +1,22 @@
+package com.fagi.logging;
+
+import java.util.Map;
+
+/**
+ * Test creation strategy that stores created loggers to simulate the same behaviour as normal logger creation
+ */
+public class TestLoggerCreationStrategy implements FagiLoggerCreationStrategy {
+ private final Map, TestLogger>> loggers;
+
+ public TestLoggerCreationStrategy(Map, TestLogger>> loggers) {
+ this.loggers = loggers;
+ }
+
+ @Override
+ public FagiLogger createLogger(Class tClass) {
+ return loggers.computeIfAbsent(
+ tClass,
+ (k) -> new TestLogger<>(tClass)
+ );
+ }
+}