diff --git a/src/main/java/net/ayyalasomayajula/net/client/ClientConnect.java b/src/main/java/net/ayyalasomayajula/net/client/ClientConnect.java
index b4f9aec..77539c0 100644
--- a/src/main/java/net/ayyalasomayajula/net/client/ClientConnect.java
+++ b/src/main/java/net/ayyalasomayajula/net/client/ClientConnect.java
@@ -6,6 +6,7 @@ import org.slf4j.LoggerFactory;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.io.IOException;
import java.net.Socket;
/**
@@ -42,6 +43,19 @@ public class ClientConnect extends JFrame {
public void actionPerformed(ActionEvent actionEvent) {
logger.info("Triggered a Connection request.");
// Connection logic would go here (e.g., retrieve values and initiate connection)
+ try {
+ Socket socket = new Socket(getSanitizedText(hostname), Integer.valueOf(getSanitizedText(portNumber)));
+ logger.info("socket connection has been successfully established by the client");
+ dispose();
+ if(socket.isConnected()){
+ Dashboard dashboard = new Dashboard(socket);
+ }
+ //pass a socket with a worker thread to a new variable so that the dashboard can get access to a consumer of messages
+ //make sure those messages are in turn written to the ServerDaemon socket. The Dashboard will make the messages
+
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
}
});
diff --git a/src/main/java/net/ayyalasomayajula/net/client/Dashboard.form b/src/main/java/net/ayyalasomayajula/net/client/Dashboard.form
new file mode 100644
index 0000000..79d9075
--- /dev/null
+++ b/src/main/java/net/ayyalasomayajula/net/client/Dashboard.form
@@ -0,0 +1,53 @@
+
+
diff --git a/src/main/java/net/ayyalasomayajula/net/client/Dashboard.java b/src/main/java/net/ayyalasomayajula/net/client/Dashboard.java
new file mode 100644
index 0000000..425e1f0
--- /dev/null
+++ b/src/main/java/net/ayyalasomayajula/net/client/Dashboard.java
@@ -0,0 +1,141 @@
+package net.ayyalasomayajula.net.client;
+
+import net.ayyalasomayajula.net.shared.Message;
+import net.ayyalasomayajula.net.shared.MessageVariant;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.*;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.Socket;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * The Dashboard class represents the user interface for interacting with the client after a successful connection.
+ * It provides buttons to trigger different actions (e.g., searching for Electronic Health Records (EHRs), finding X-ray files),
+ * and sends relevant messages to the server for processing. It also listens for incoming responses from the server.
+ */
+public class Dashboard extends JFrame {
+ private static final Logger logger = LoggerFactory.getLogger(Dashboard.class);
+
+ private JTextField textField1;
+ private JPanel main;
+ private JButton findEHRButton;
+ private JButton findXrayButton;
+ private JButton generateEHRButton;
+ private JTextArea messageDisplayArea;
+ private Socket connection;
+ private ObjectOutputStream outputStream;
+ private ObjectInputStream inputStream;
+ private List receivedMessages;
+
+ /**
+ * Constructs a new {@code Dashboard} frame, initializing the GUI components and setting up
+ * the button actions to send requests to the server. It also starts a background thread to listen
+ * for incoming messages from the server.
+ *
+ * @param connection The active socket connection to the server.
+ */
+ public Dashboard(Socket connection) {
+ this.connection = connection;
+ this.receivedMessages = new CopyOnWriteArrayList<>(); // Store messages
+
+ try {
+ this.outputStream = new ObjectOutputStream(connection.getOutputStream());
+ this.inputStream = new ObjectInputStream(connection.getInputStream());
+ } catch (IOException e) {
+ logger.error("Failed to create output/input streams: {}", e.getMessage());
+ }
+
+ // Setup GUI components (buttons, text fields, etc.)
+ setTitle("Client Dashboard");
+ setContentPane(main);
+ setSize(800, 600);
+ setVisible(true);
+
+ // Action listener for "Find EHR" button
+ findEHRButton.addActionListener(e -> sendMessage("EHR", textField1.getText()));
+
+ // Action listener for "Find X-ray" button
+ findXrayButton.addActionListener(e -> sendMessage("XRAY", textField1.getText()));
+
+ // Action listener for "Generate EHR" button
+ generateEHRButton.addActionListener(e -> generateEHR());
+
+ // Start the thread to listen for incoming messages
+ new Thread(this::listenForMessages).start();
+ }
+
+ /**
+ * Sends a message to the server with the specified asset type and asset ID.
+ * The message is wrapped in a {@link Message} object and sent to the server via the output stream.
+ *
+ * @param assetType The type of asset (either "EHR" or "XRAY").
+ * @param assetId The ID of the asset, typically the UUID or path.
+ */
+ private void sendMessage(String assetType, String assetId) {
+ try {
+ String query = assetType + " " + assetId;
+ logger.info("Query: {}", query);
+ Message message = new Message(MessageVariant.GET, query, 5, new byte[]{});
+
+ // Ensure the message is written to the output stream
+ outputStream.writeObject(message);
+ outputStream.flush(); // Flush to ensure the message is sent
+ logger.info("Sent {} request for asset ID: {}", assetType, assetId);
+
+ // Wait for a response from the server
+ Message response = (Message) inputStream.readObject();
+ updateMessageDisplay(response); // Update the display with the server's response
+ } catch (IOException | ClassNotFoundException e) {
+ logger.error("Failed to send message: {}", e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Sends a request to the server to generate an EHR. This simulates the generation of a new EHR.
+ */
+ private void generateEHR() {
+ try {
+ String query = "EHR generate";
+ Message message = new Message(MessageVariant.GET, query, 5, new byte[]{});
+ outputStream.writeObject(message);
+ logger.info("Sent request to generate new EHR.");
+ } catch (IOException e) {
+ logger.error("Failed to send EHR generation request: {}", e.getMessage());
+ }
+ }
+
+ /**
+ * Listens for incoming messages from the server and stores them in the list of received messages.
+ * It also updates the message display area in the GUI to show new messages.
+ */
+ private void listenForMessages() {
+ try {
+ while (true) {
+ // Read the next message from the server
+ Message receivedMessage = (Message) inputStream.readObject();
+ receivedMessages.add(receivedMessage);
+ updateMessageDisplay(receivedMessage); // Update the GUI with the new message
+ }
+ } catch (IOException | ClassNotFoundException e) {
+ logger.error("Error while listening for messages: {}", e.getMessage());
+ }
+ }
+
+ /**
+ * Updates the message display area in the GUI with the received message.
+ * This method is called whenever a new message is received from the server.
+ *
+ * @param receivedMessage The message received from the server.
+ */
+ private void updateMessageDisplay(Message receivedMessage) {
+ String displayText = "Received: " + receivedMessage.messageQuery() + "\n";
+ messageDisplayArea.append(displayText); // Append the message to the display area
+ messageDisplayArea.setCaretPosition(messageDisplayArea.getDocument().getLength()); // Auto-scroll to the bottom
+ }
+}
diff --git a/src/main/java/net/ayyalasomayajula/net/server/EHRUtils.java b/src/main/java/net/ayyalasomayajula/net/server/EHRUtils.java
new file mode 100644
index 0000000..f28f95c
--- /dev/null
+++ b/src/main/java/net/ayyalasomayajula/net/server/EHRUtils.java
@@ -0,0 +1,105 @@
+package net.ayyalasomayajula.net.server;
+
+import net.ayyalasomayajula.net.shared.EHR;
+
+import java.io.*;
+import java.nio.file.*;
+import java.util.UUID;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Utility class for handling EHR operations: serialization, deserialization, and searching.
+ */
+public class EHRUtils {
+
+ /**
+ * Constructs the file path for a given EHR based on its UUID.
+ *
+ * @param dirPath The base directory for storing EHRs.
+ * @param ehrUUID The UUID of the EHR.
+ * @return The full path to the EHR file.
+ */
+ public static Path getEHRFilePath(Path dirPath, UUID ehrUUID) {
+ return dirPath.resolve(ehrUUID.toString() + ".ehr");
+ }
+
+ /**
+ * Writes an EHR object to a file.
+ *
+ * @param ehr The EHR object to write.
+ * @param dirPath The directory where the file will be stored.
+ * @throws IOException If an I/O error occurs.
+ */
+ public static void writeEHRToFile(EHR ehr, Path dirPath) throws IOException {
+ if (!Files.exists(dirPath)) {
+ Files.createDirectories(dirPath);
+ }
+
+ Path filePath = getEHRFilePath(dirPath, ehr.getUuid());
+
+ try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath.toFile()))) {
+ oos.writeObject(ehr);
+ }
+ }
+
+ /**
+ * Reads and deserializes an EHR object from a file.
+ *
+ * @param dirPath The directory containing the EHR files.
+ * @param ehrUUID The UUID of the EHR to retrieve.
+ * @return The deserialized EHR object, or null if not found.
+ */
+ public static EHR readEHRFromFile(Path dirPath, UUID ehrUUID) {
+ Path filePath = getEHRFilePath(dirPath, ehrUUID);
+ if (!Files.exists(filePath)) return null;
+
+ try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath.toFile()))) {
+ return (EHR) ois.readObject();
+ } catch (IOException | ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Searches for the closest matching EHR based on an asset ID (UUID or patient name).
+ *
+ * @param assetId The UUID string or patient name to search for.
+ * @param dirPath The directory containing serialized EHR files.
+ * @return The closest matching EHR, or null if none are found.
+ */
+ public static EHR searchClosestEHR(String assetId, Path dirPath) {
+ File[] ehrFiles = dirPath.toFile().listFiles((dir, name) -> name.endsWith(".ehr"));
+ if (ehrFiles == null || ehrFiles.length == 0) return null;
+
+ return List.of(ehrFiles).stream()
+ .map(file -> readEHRFromFile(dirPath, UUID.fromString(file.getName().replace(".ehr", ""))))
+ .filter(ehr -> ehr != null)
+ .min(Comparator.comparingInt(ehr -> Math.min(
+ levenshteinDistance(ehr.getUuid().toString(), assetId),
+ levenshteinDistance(ehr.getPatientName(), assetId)
+ )))
+ .orElse(null);
+ }
+
+ /**
+ * Computes the Levenshtein distance between two strings.
+ *
+ * @param s1 First string.
+ * @param s2 Second string.
+ * @return The Levenshtein distance between s1 and s2.
+ */
+ private static int levenshteinDistance(String s1, String s2) {
+ int[][] dp = new int[s1.length() + 1][s2.length() + 1];
+
+ for (int i = 0; i <= s1.length(); i++)
+ for (int j = 0; j <= s2.length(); j++)
+ if (i == 0) dp[i][j] = j;
+ else if (j == 0) dp[i][j] = i;
+ else dp[i][j] = Math.min(dp[i - 1][j - 1] + (s1.charAt(i - 1) == s2.charAt(j - 1) ? 0 : 1),
+ Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));
+
+ return dp[s1.length()][s2.length()];
+ }
+}
+
diff --git a/src/main/java/net/ayyalasomayajula/net/server/ServerDaemon.java b/src/main/java/net/ayyalasomayajula/net/server/ServerDaemon.java
index 18a1120..d757e05 100644
--- a/src/main/java/net/ayyalasomayajula/net/server/ServerDaemon.java
+++ b/src/main/java/net/ayyalasomayajula/net/server/ServerDaemon.java
@@ -1,5 +1,9 @@
package net.ayyalasomayajula.net.server;
+import net.ayyalasomayajula.net.shared.EHR;
+import net.ayyalasomayajula.net.shared.Message;
+import net.ayyalasomayajula.net.shared.MessageVariant;
+import net.ayyalasomayajula.net.shared.SerializationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -12,6 +16,8 @@ import java.util.Scanner;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* The ServerDaemon class implements a server daemon that listens for incoming client connections.
@@ -19,8 +25,9 @@ import java.util.concurrent.TimeUnit;
* and handles incoming socket connections using a thread pool for concurrency.
*/
public class ServerDaemon implements Runnable {
+ private static final String patientEhrs = "patients/";
private static final Logger logger = LoggerFactory.getLogger(ServerDaemon.class);
-
+ private String basePath = "";
/**
* The entry point of the server daemon that sets up the server, initializes root paths,
* and listens for client connections.
@@ -40,7 +47,7 @@ public class ServerDaemon implements Runnable {
boolean success = initRootPath(path);
if (!success) throw new RuntimeException("Your Path is dysfunctional");
}
-
+ basePath=path.toString();
try {
ServerSocket serverSocket = new ServerSocket(8475);
logger.info("Server to handle connections has been made at: {} on 0.0.0.0", serverSocket.getLocalPort());
@@ -55,7 +62,7 @@ public class ServerDaemon implements Runnable {
public void run() {
try {
handleConnections(socketToPass);
- } catch (IOException e) {
+ } catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
@@ -109,7 +116,7 @@ public class ServerDaemon implements Runnable {
}
// Create the patient directory
- String patientEhrs = "patients/";
+
Path patientPath = path.resolve(patientEhrs);
File dir = patientPath.toFile();
@@ -147,10 +154,76 @@ public class ServerDaemon implements Runnable {
* @param socket The socket for the incoming connection.
* @throws IOException If an I/O error occurs while setting up the streams.
*/
- private void handleConnections(Socket socket) throws IOException {
+ private void handleConnections(Socket socket) throws IOException, ClassNotFoundException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
+ objectOutputStream.flush(); // Important to flush the stream to avoid issues on the first write
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
- // Further processing can be added here
+ logger.info("Got a handle running on the socket. Will await new message objects.");
+
+ Object readBuffer = objectInputStream.readObject();
+
+ while (socket.isConnected()) {
+ if (readBuffer instanceof Message intercepted) {
+ // Handle the received Message
+ Message toPush = handleMessage(intercepted);
+ objectOutputStream.writeObject(toPush);
+ objectOutputStream.flush(); // Ensure the response is sent
+ }
+ // Keep reading from the input stream
+ readBuffer = objectInputStream.readObject();
+ }
}
+
+ private Message handleMessage(Message in) throws IOException {
+ switch (in.messageVariant()) {
+ case GET: {
+ // Format of GET query:
+ // | asset types: EHR, XRAY | asset id: uuid field or the path
+
+ // Define regex pattern with capture groups
+ Pattern pattern = Pattern.compile("^(EHR|XRAY) (.+)$");
+
+ Matcher matcher = pattern.matcher(in.messageQuery());
+ if (matcher.matches()) {
+ String assetType = matcher.group(1); // Captures EHR or XRAY
+ String assetId = matcher.group(2); // Captures UUID or path
+ logger.debug("Asset Type: {}", assetType);
+ logger.debug("Asset ID: {}", assetId);
+
+ switch (assetType) {
+ case "EHR":
+ logger.info("EHR caught");
+ EHR found = EHRUtils.searchClosestEHR(assetId, Path.of(basePath));
+ return new Message(MessageVariant.SET, "", 5, SerializationUtils.toBytes(found));
+ case "XRAY":
+ logger.info("XRAY caught");
+ // Construct full file path for XRAY asset
+ Path xrayFilePath = Path.of(basePath, assetId);
+ File xrayFile = xrayFilePath.toFile();
+
+ // Check if the file exists and is a valid file
+ if (xrayFile.exists() && xrayFile.isFile()) {
+ // Read the file into a byte array and return it
+ try (FileInputStream fis = new FileInputStream(xrayFile)) {
+ byte[] fileBytes = fis.readAllBytes();
+ return new Message(MessageVariant.SET, "", 5, fileBytes);
+ } catch (IOException e) {
+ logger.error("Error reading XRAY file: {}", e.getMessage());
+ return new Message(MessageVariant.SET, "ERROR: Failed to read XRAY file.", 5, new byte[]{});
+ }
+ } else {
+ logger.error("XRAY file not found or invalid path: {}", xrayFilePath);
+ return new Message(MessageVariant.SET, "ERROR: XRAY file not found.", 5, new byte[]{});
+ }
+ }
+ } else {
+ logger.debug("No match found for: {}", in.messageQuery());
+ }
+ break;
+ }
+ }
+ return new Message(MessageVariant.SET, "THROW;", 5, new byte[]{});
+ }
+
}
diff --git a/src/main/java/net/ayyalasomayajula/net/shared/EHR.java b/src/main/java/net/ayyalasomayajula/net/shared/EHR.java
index 3c766be..6de2e5d 100644
--- a/src/main/java/net/ayyalasomayajula/net/shared/EHR.java
+++ b/src/main/java/net/ayyalasomayajula/net/shared/EHR.java
@@ -1,3 +1,5 @@
+package net.ayyalasomayajula.net.shared;
+
import net.ayyalasomayajula.net.shared.Appointment;
import net.ayyalasomayajula.net.shared.Message;
diff --git a/src/main/java/net/ayyalasomayajula/net/shared/SerializationUtils.java b/src/main/java/net/ayyalasomayajula/net/shared/SerializationUtils.java
new file mode 100644
index 0000000..a1f6789
--- /dev/null
+++ b/src/main/java/net/ayyalasomayajula/net/shared/SerializationUtils.java
@@ -0,0 +1,41 @@
+package net.ayyalasomayajula.net.shared;
+
+import java.io.*;
+
+/**
+ * Utility class for serializing and deserializing objects to and from byte arrays.
+ * This class provides methods for converting an object into a byte array and vice versa.
+ */
+public class SerializationUtils {
+
+ /**
+ * Serializes an object to a byte array.
+ *
+ * @param obj the object to serialize
+ * @return a byte array representing the serialized object
+ * @throws IOException if an I/O error occurs during serialization
+ */
+ public static byte[] toBytes(Object obj) throws IOException {
+ try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(bos)) {
+ oos.writeObject(obj);
+ oos.flush();
+ return bos.toByteArray();
+ }
+ }
+
+ /**
+ * Deserializes a byte array to an object.
+ *
+ * @param bytes the byte array to deserialize
+ * @return the deserialized object
+ * @throws IOException if an I/O error occurs during deserialization
+ * @throws ClassNotFoundException if the class of a serialized object cannot be found
+ */
+ public static Object fromBytes(byte[] bytes) throws IOException, ClassNotFoundException {
+ try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+ ObjectInputStream ois = new ObjectInputStream(bis)) {
+ return ois.readObject();
+ }
+ }
+}
diff --git a/target/classes/EHR.class b/target/classes/EHR.class
deleted file mode 100644
index f948084..0000000
Binary files a/target/classes/EHR.class and /dev/null differ
diff --git a/target/classes/net/ayyalasomayajula/net/client/ClientConnect$1.class b/target/classes/net/ayyalasomayajula/net/client/ClientConnect$1.class
index 34781ca..db8f068 100644
Binary files a/target/classes/net/ayyalasomayajula/net/client/ClientConnect$1.class and b/target/classes/net/ayyalasomayajula/net/client/ClientConnect$1.class differ
diff --git a/target/classes/net/ayyalasomayajula/net/client/ClientConnect.class b/target/classes/net/ayyalasomayajula/net/client/ClientConnect.class
index 5ce142b..d158b7d 100644
Binary files a/target/classes/net/ayyalasomayajula/net/client/ClientConnect.class and b/target/classes/net/ayyalasomayajula/net/client/ClientConnect.class differ
diff --git a/target/classes/net/ayyalasomayajula/net/client/Dashboard.class b/target/classes/net/ayyalasomayajula/net/client/Dashboard.class
new file mode 100644
index 0000000..83b2c48
Binary files /dev/null and b/target/classes/net/ayyalasomayajula/net/client/Dashboard.class differ
diff --git a/target/classes/net/ayyalasomayajula/net/server/ServerDaemon$1.class b/target/classes/net/ayyalasomayajula/net/server/ServerDaemon$1.class
index e5546d6..066e26f 100644
Binary files a/target/classes/net/ayyalasomayajula/net/server/ServerDaemon$1.class and b/target/classes/net/ayyalasomayajula/net/server/ServerDaemon$1.class differ
diff --git a/target/classes/net/ayyalasomayajula/net/server/ServerDaemon.class b/target/classes/net/ayyalasomayajula/net/server/ServerDaemon.class
index cb3931a..2d98ce3 100644
Binary files a/target/classes/net/ayyalasomayajula/net/server/ServerDaemon.class and b/target/classes/net/ayyalasomayajula/net/server/ServerDaemon.class differ