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