From 885d5c95a416cd7e0eba04625f755deb2b65f7c3 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Thu, 13 Nov 2025 03:25:33 -0300 Subject: [PATCH 01/39] Replace `application.properties` with `config.properties`: - Renamed configuration file to `config.properties` for consistency. - Updated `MediaManagerApplication` to load the new configuration file. - Adjusted references in `README.md` and `.gitignore`. --- .gitignore | 2 +- README.md | 10 +++++----- .../java/com/mediamanager/MediaManagerApplication.java | 4 ++-- .../{application.properties => config.properties} | 0 4 files changed, 8 insertions(+), 8 deletions(-) rename src/main/resources/{application.properties => config.properties} (100%) diff --git a/.gitignore b/.gitignore index 5074d96..51b319c 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,7 @@ build/ .DS_Store ### Application Specific ### -application-local.properties +config-local.properties *.db pipes/ *.log diff --git a/README.md b/README.md index baf75eb..56207a1 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ MediaManager-Core/ │ │ ├── util/ # Utility classes │ │ └── MediaManagerApplication.java │ └── resources/ -│ ├── application.properties # App configuration +│ ├── config.properties # App configuration │ ├── log4j2.xml # Logging configuration │ └── META-INF/ │ └── persistence.xml # JPA configuration @@ -55,7 +55,7 @@ GRANT ALL PRIVILEGES ON DATABASE mediamanager TO mediamanager; ### 2. Configuration -Edit `src/main/resources/application.properties` and update the database credentials: +Edit `src/main/resources/config.properties` and update the database credentials: ```properties db.url=jdbc:postgresql://localhost:5432/mediamanager @@ -90,7 +90,7 @@ The application creates named pipes for inter-process communication. Default con - Pipe name: `mediamanager-pipe` - Buffer size: 8192 bytes -You can modify these settings in `application.properties`. +You can modify these settings in `config.properties`. ## Logging @@ -116,8 +116,8 @@ mvn test ## Dependencies -- PostgreSQL Driver: 42.7.3 -- Hibernate ORM: 6.4.4.Final +- PostgreSQL Driver: 42.7.5 +- Hibernate ORM: 7.1.7.Final - HikariCP: 5.1.0 - Log4j 2: 2.23.1 - Jackson: 2.16.1 diff --git a/src/main/java/com/mediamanager/MediaManagerApplication.java b/src/main/java/com/mediamanager/MediaManagerApplication.java index d3030da..fcaaa65 100644 --- a/src/main/java/com/mediamanager/MediaManagerApplication.java +++ b/src/main/java/com/mediamanager/MediaManagerApplication.java @@ -40,9 +40,9 @@ public class MediaManagerApplication { private static void loadConfiguration() throws IOException { config = new Properties(); try (InputStream input = MediaManagerApplication.class.getClassLoader() - .getResourceAsStream("application.properties")) { + .getResourceAsStream("config.properties")) { if (input == null) { - throw new IOException("Unable to find application.properties"); + throw new IOException("Unable to find config.properties"); } config.load(input); logger.info("Configuration loaded successfully"); diff --git a/src/main/resources/application.properties b/src/main/resources/config.properties similarity index 100% rename from src/main/resources/application.properties rename to src/main/resources/config.properties From 22476abb27d95e88ae0439d97ef97467b6a4f985 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Thu, 13 Nov 2025 03:59:31 -0300 Subject: [PATCH 02/39] Add `DatabaseManager` class and initialize database connection - Introduced `DatabaseManager` class to manage database connections. - Updated `MediaManagerApplication` to initialize `DatabaseManager` using configuration properties. --- .../mediamanager/MediaManagerApplication.java | 5 +++ .../service/database/DatabaseManager.java | 32 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/main/java/com/mediamanager/service/database/DatabaseManager.java diff --git a/src/main/java/com/mediamanager/MediaManagerApplication.java b/src/main/java/com/mediamanager/MediaManagerApplication.java index fcaaa65..b3dbada 100644 --- a/src/main/java/com/mediamanager/MediaManagerApplication.java +++ b/src/main/java/com/mediamanager/MediaManagerApplication.java @@ -7,9 +7,12 @@ import java.io.IOException; import java.io.InputStream; import java.util.Properties; +import com.mediamanager.service.database.DatabaseManager; + public class MediaManagerApplication { private static final Logger logger = LogManager.getLogger(MediaManagerApplication.class); private static Properties config; + private static DatabaseManager databaseManager; public static void main(String[] args) { logger.info("Starting MediaManager Core Application..."); @@ -17,6 +20,8 @@ public class MediaManagerApplication { try { // Load configuration loadConfiguration(); + databaseManager = new DatabaseManager(config); + databaseManager.init(); // TODO: Initialize database connection // TODO: Initialize IPC server with named pipes diff --git a/src/main/java/com/mediamanager/service/database/DatabaseManager.java b/src/main/java/com/mediamanager/service/database/DatabaseManager.java new file mode 100644 index 0000000..e71a18e --- /dev/null +++ b/src/main/java/com/mediamanager/service/database/DatabaseManager.java @@ -0,0 +1,32 @@ +package com.mediamanager.service.database; +import java.sql.Connection; +import java.sql.DriverManager; +import java.util.Properties; + + +public class DatabaseManager { + private final Properties configuration; + private Connection connection; + public DatabaseManager(Properties config) { + this.configuration = config; + + } + + public void init() throws Exception { + String databaseType = configuration.getProperty("database.type"); + String databaseUrl = configuration.getProperty("database.url"); + String databaseUsername = configuration.getProperty("database.username"); + String databasePassword = configuration.getProperty("database.password"); + String databasePort = configuration.getProperty("database.port"); + String databaseName = configuration.getProperty("database.name"); + + + String connectionString = String.format("jdbc:postgresql://%s:%s/%s", databaseUrl, databasePort, databaseName); + connection = DriverManager.getConnection(connectionString, databaseUsername, databasePassword); + + } + public Connection getConnection() { + return connection; + } + +} From 3bb0423167a79c80de5b24023a39cc5d4924d991 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Thu, 13 Nov 2025 04:43:05 -0300 Subject: [PATCH 03/39] Enhance `DatabaseManager` with logging, validation, and cleanup - Added detailed logging for database initialization and connection handling. - Implemented configuration validation and sanity checks during initialization. - Introduced `close()` method to properly release database resources. - Updated `MediaManagerApplication` to invoke `DatabaseManager.close()` on shutdown. - Updated `.gitignore` to exclude sensitive configuration files while allowing example files. --- .gitignore | 8 +++ .../mediamanager/MediaManagerApplication.java | 4 +- .../service/database/DatabaseManager.java | 68 ++++++++++++++++++- ...g.properties => config.properties.example} | 1 + 4 files changed, 79 insertions(+), 2 deletions(-) rename src/main/resources/{config.properties => config.properties.example} (99%) diff --git a/.gitignore b/.gitignore index 51b319c..30116b9 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,11 @@ config-local.properties pipes/ *.log /.idea/ + + Configuration files with sensitive credentials +# These files contain database passwords and API keys and should never be committed +src/main/resources/config.properties +src/main/resources/application.properties + +# Allow example configuration files to be committed +!src/main/resources/*.properties.example \ No newline at end of file diff --git a/src/main/java/com/mediamanager/MediaManagerApplication.java b/src/main/java/com/mediamanager/MediaManagerApplication.java index b3dbada..e09b9fa 100644 --- a/src/main/java/com/mediamanager/MediaManagerApplication.java +++ b/src/main/java/com/mediamanager/MediaManagerApplication.java @@ -33,7 +33,9 @@ public class MediaManagerApplication { // Keep application running Runtime.getRuntime().addShutdownHook(new Thread(() -> { logger.info("Shutting down MediaManager Core..."); - // TODO: Cleanup resources + if (databaseManager != null) { + databaseManager.close(); + } })); } catch (Exception e) { diff --git a/src/main/java/com/mediamanager/service/database/DatabaseManager.java b/src/main/java/com/mediamanager/service/database/DatabaseManager.java index e71a18e..0f43fa9 100644 --- a/src/main/java/com/mediamanager/service/database/DatabaseManager.java +++ b/src/main/java/com/mediamanager/service/database/DatabaseManager.java @@ -1,18 +1,28 @@ package com.mediamanager.service.database; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.sql.Connection; import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; import java.util.Properties; public class DatabaseManager { private final Properties configuration; private Connection connection; + private static final Logger logger = LogManager.getLogger(DatabaseManager.class); public DatabaseManager(Properties config) { this.configuration = config; + logger.debug("DatabaseManager created with configuration:"); } public void init() throws Exception { + logger.info("Initializing database connection..."); + validateConfiguration(); + String databaseType = configuration.getProperty("database.type"); String databaseUrl = configuration.getProperty("database.url"); String databaseUsername = configuration.getProperty("database.username"); @@ -22,11 +32,67 @@ public class DatabaseManager { String connectionString = String.format("jdbc:postgresql://%s:%s/%s", databaseUrl, databasePort, databaseName); - connection = DriverManager.getConnection(connectionString, databaseUsername, databasePassword); + logger.debug("Attempting to connect to: {}", connectionString); + try { + connection = DriverManager.getConnection(connectionString, databaseUsername, databasePassword); + logger.info("Database connection established successfully"); + + performSanityChecks(); + logger.info("Database sanity checks passed successfully"); + + + } catch (SQLException e) { + logger.error("Failed to connect to database", e); + throw new Exception("Database connection failed: " + e.getMessage(), e); + } + + } + private void performSanityChecks() throws SQLException { + logger.debug("Performing sanity checks..."); + try (Statement stmt = connection.createStatement()) { + stmt.execute("SELECT 1"); + + } + String databaseProductName = connection.getMetaData().getDatabaseProductName(); + String databaseProductVersion = connection.getMetaData().getDatabaseProductVersion(); + logger.info("Connected to database: {} v{}", databaseProductName, databaseProductVersion); + } + private void validateConfiguration() throws Exception { + String[] requiredProperties = { + "database.url", + "database.username", + "database.password", + "database.port", + "database.name" + }; + + for (String property : requiredProperties) { + if (configuration.getProperty(property) == null || + configuration.getProperty(property).trim().isEmpty()) { + throw new Exception("Required database configuration missing: " + property); + } + } + + logger.debug("Database configuration validated successfully"); + + } + + + public Connection getConnection() { return connection; } + public void close() { + if (connection != null) { + try { + connection.close(); + logger.info("Database connection closed successfully"); + } catch (SQLException e) { + logger.error("Failed to close database connection", e); + } + } + } } diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties.example similarity index 99% rename from src/main/resources/config.properties rename to src/main/resources/config.properties.example index fbef11a..8840823 100644 --- a/src/main/resources/config.properties +++ b/src/main/resources/config.properties.example @@ -21,3 +21,4 @@ hibernate.format_sql=true ipc.pipe.name=mediamanager-pipe ipc.pipe.path=/tmp/mediamanager ipc.buffer.size=8192 + From 65e14930076db71a312feae373d6c5daee3c4d29 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Thu, 13 Nov 2025 05:40:01 -0300 Subject: [PATCH 04/39] Improve shutdown hook and application lifecycle logging: - Added graceful shutdown messages. - Handled `InterruptedException` to ensure proper shutdown behavior. - Included clear prompts for application status. --- .../mediamanager/MediaManagerApplication.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/com/mediamanager/MediaManagerApplication.java b/src/main/java/com/mediamanager/MediaManagerApplication.java index fcaaa65..6566968 100644 --- a/src/main/java/com/mediamanager/MediaManagerApplication.java +++ b/src/main/java/com/mediamanager/MediaManagerApplication.java @@ -29,7 +29,23 @@ public class MediaManagerApplication { Runtime.getRuntime().addShutdownHook(new Thread(() -> { logger.info("Shutting down MediaManager Core..."); // TODO: Cleanup resources + + logger.info("MediaManager Core shutdown successfully"); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } })); + logger.info("Application is running"); + logger.info("Press Ctrl+C to exit"); + Thread.currentThread().join(); + + } catch (InterruptedException e) { + + logger.info("Application interrupted, initiating shutdown..."); + + Thread.currentThread().interrupt(); } catch (Exception e) { logger.error("Failed to start MediaManager Core", e); From b0c45bf657a8f86b79f7a8aa96e59cf439233c95 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Thu, 13 Nov 2025 05:58:57 -0300 Subject: [PATCH 05/39] Refactor code for clarity and consistent formatting: - Added missing braces in the shutdown hook. - Removed unnecessary empty lines. - Streamlined `try` block indentation in `loadConfiguration()`. --- src/main/java/com/mediamanager/MediaManagerApplication.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/mediamanager/MediaManagerApplication.java b/src/main/java/com/mediamanager/MediaManagerApplication.java index 653c354..7150608 100644 --- a/src/main/java/com/mediamanager/MediaManagerApplication.java +++ b/src/main/java/com/mediamanager/MediaManagerApplication.java @@ -35,6 +35,7 @@ public class MediaManagerApplication { logger.info("Shutting down MediaManager Core..."); if (databaseManager != null) { databaseManager.close(); + } // TODO: Cleanup resources @@ -43,7 +44,6 @@ public class MediaManagerApplication { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - } })); logger.info("Application is running"); @@ -64,8 +64,7 @@ public class MediaManagerApplication { private static void loadConfiguration() throws IOException { config = new Properties(); - try (InputStream input = MediaManagerApplication.class.getClassLoader() - .getResourceAsStream("config.properties")) { + try (InputStream input = MediaManagerApplication.class.getClassLoader().getResourceAsStream("config.properties")) { if (input == null) { throw new IOException("Unable to find config.properties"); } From b7ff9d1e16c85bf986a770676827bee390f0d53c Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Thu, 13 Nov 2025 17:10:23 -0300 Subject: [PATCH 06/39] Improve logging, shutdown behavior, and build configuration: - Enabled `immediateFlush` for log appenders and disabled `shutdownHook` in Log4j2 configuration. - Enhanced shutdown hook with graceful Log4j2 termination and extended delay for log flushing. - Improved database connection closure logging in `DatabaseManager`. - Integrated Maven Shade Plugin to create executable JARs with all dependencies. --- pom.xml | 22 +++++++++++++++++++ .../mediamanager/MediaManagerApplication.java | 13 +++++++++-- .../service/database/DatabaseManager.java | 5 ++++- src/main/resources/log4j2.xml | 7 +++--- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 9a87f4d..3ea8e09 100644 --- a/pom.xml +++ b/pom.xml @@ -96,6 +96,28 @@ maven-surefire-plugin 3.2.5 + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.1 + + + package + + shade + + + + + com.mediamanager.MediaManagerApplication + + + + + + diff --git a/src/main/java/com/mediamanager/MediaManagerApplication.java b/src/main/java/com/mediamanager/MediaManagerApplication.java index 7150608..f4acc3c 100644 --- a/src/main/java/com/mediamanager/MediaManagerApplication.java +++ b/src/main/java/com/mediamanager/MediaManagerApplication.java @@ -32,19 +32,28 @@ public class MediaManagerApplication { // Keep application running Runtime.getRuntime().addShutdownHook(new Thread(() -> { + logger.info("Shutting down MediaManager Core..."); + + if (databaseManager != null) { databaseManager.close(); } - // TODO: Cleanup resources logger.info("MediaManager Core shutdown successfully"); + logger.info("Goodbye!"); + + + // Give Log4j2 time to write all pending messages before shutting down try { - Thread.sleep(500); + Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } + + // Now shutdown Log4j2 + org.apache.logging.log4j.LogManager.shutdown(); })); logger.info("Application is running"); logger.info("Press Ctrl+C to exit"); diff --git a/src/main/java/com/mediamanager/service/database/DatabaseManager.java b/src/main/java/com/mediamanager/service/database/DatabaseManager.java index 0f43fa9..eeabc69 100644 --- a/src/main/java/com/mediamanager/service/database/DatabaseManager.java +++ b/src/main/java/com/mediamanager/service/database/DatabaseManager.java @@ -88,11 +88,14 @@ public class DatabaseManager { public void close() { if (connection != null) { try { + logger.info("Closing database connection..."); connection.close(); logger.info("Database connection closed successfully"); } catch (SQLException e) { - logger.error("Failed to close database connection", e); + logger.error("Error closing database connection: {}", e.getMessage()); } + } else { + logger.debug("No database connection to close"); } } } diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 29d32dc..39be1a8 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -1,12 +1,13 @@ - + - + + filePattern="logs/mediamanager-%d{yyyy-MM-dd}-%i.log" + immediateFlush="true"> From 73d1753935f4fbf9b8ffa0e4b51a96a17156e155 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Thu, 13 Nov 2025 20:45:37 -0300 Subject: [PATCH 07/39] Add `IPCManager` for UNIX domain socket communication: - Introduced `IPCManager` class to handle IPC using UNIX domain sockets. - Updated `MediaManagerApplication` to initialize and manage `IPCManager` during startup and shutdown. - Enhanced logging for IPC initialization and cleanup. - Updated `config.properties.example` with `ipc.socket.path` property. --- .../mediamanager/MediaManagerApplication.java | 17 ++- .../mediamanager/service/ipc/IPCManager.java | 101 ++++++++++++++++++ src/main/resources/config.properties.example | 2 + 3 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/mediamanager/service/ipc/IPCManager.java diff --git a/src/main/java/com/mediamanager/MediaManagerApplication.java b/src/main/java/com/mediamanager/MediaManagerApplication.java index f4acc3c..d483da3 100644 --- a/src/main/java/com/mediamanager/MediaManagerApplication.java +++ b/src/main/java/com/mediamanager/MediaManagerApplication.java @@ -1,5 +1,6 @@ package com.mediamanager; +import com.mediamanager.service.ipc.IPCManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -13,6 +14,7 @@ public class MediaManagerApplication { private static final Logger logger = LogManager.getLogger(MediaManagerApplication.class); private static Properties config; private static DatabaseManager databaseManager; + private static IPCManager ipcManager; public static void main(String[] args) { logger.info("Starting MediaManager Core Application..."); @@ -23,12 +25,13 @@ public class MediaManagerApplication { databaseManager = new DatabaseManager(config); databaseManager.init(); - - // TODO: Initialize IPC server with named pipes + ipcManager = new IPCManager(config); + ipcManager.init(); + // TODO: Start application services logger.info("MediaManager Core started successfully"); - logger.info("IPC Pipe: {}", config.getProperty("ipc.pipe.path") + "/" + config.getProperty("ipc.pipe.name")); + logger.info("IPC Socket: {}", ipcManager.getSocketPath().toAbsolutePath().toString()); // Keep application running Runtime.getRuntime().addShutdownHook(new Thread(() -> { @@ -40,6 +43,14 @@ public class MediaManagerApplication { databaseManager.close(); } + if (ipcManager != null) { + try { + ipcManager.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + logger.info("MediaManager Core shutdown successfully"); logger.info("Goodbye!"); diff --git a/src/main/java/com/mediamanager/service/ipc/IPCManager.java b/src/main/java/com/mediamanager/service/ipc/IPCManager.java new file mode 100644 index 0000000..e1cc0ae --- /dev/null +++ b/src/main/java/com/mediamanager/service/ipc/IPCManager.java @@ -0,0 +1,101 @@ +package com.mediamanager.service.ipc; + + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.net.StandardProtocolFamily; +import java.net.UnixDomainSocketAddress; +import java.nio.channels.ServerSocketChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Properties; + +public class IPCManager { + private static Properties configuration; + private static final Logger logger = LogManager.getLogger(IPCManager.class); + private Path socketPath; + private UnixDomainSocketAddress socketAddress; + private ServerSocketChannel serverChannel; + + public IPCManager(Properties config){ + configuration = config; + logger.debug("IPCManager created with configuration:"); + + + } + public void init() throws Exception { + logger.info("Initializing IPC connection..."); + validateConfiguration(); + socketPath = Path.of(configuration.getProperty("ipc.socket.path")).resolve("mediamanager.sock"); + + if (checkUnixSocketExists()){ + logger.warn("IPC socket already exists"); + logger.info("Deleting existing socket..."); + Files.deleteIfExists(socketPath); + + + } + + try{ + socketAddress = UnixDomainSocketAddress.of(socketPath); + logger.info("IPC socket created successfully"); + } catch (Exception e){ + logger.error("Failed to create socket: {}", e.getMessage()); + throw new Exception("Failed to create socket: " + e.getMessage(), e); + } + if (!Files.isDirectory(socketPath.getParent())) { + logger.info("Creating parent directory for socket..."); + Files.createDirectories(socketPath.getParent()); + } + + serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); + logger.info("IPC server channel opened successfully"); + serverChannel.bind(socketAddress); + logger.info("IPC server channel bound successfully"); + logger.info("IPC server listening on {}", socketPath.toAbsolutePath().toString()); + + + } + private void validateConfiguration() throws Exception { + String[] requiredProperties = { + "ipc.socket.path" + }; + for (String property : requiredProperties) { + if (configuration.getProperty(property) == null) { + throw new Exception("Missing required configuration property: " + property); + } + } + logger.debug("IPC configuration validated successfully"); + } + private boolean checkUnixSocketExists() { + File socketFile = new File(String.valueOf(socketPath)); + return socketFile.exists(); + } + + public Path getSocketPath(){ + return socketPath; + } + + public void close() throws Exception { + logger.info("Closing IPC connection..."); + if (serverChannel != null) { + serverChannel.close(); + } + File socketFile = new File(String.valueOf(socketPath)); + boolean delete = false; + if (socketFile.exists()) { + delete = socketFile.delete(); + } + if (!delete){ + logger.warn("Failed to delete socket file"); + }else { + logger.info("IPC socket deleted successfully"); + Files.deleteIfExists(socketPath.getParent()); + logger.info("IPC socket parent directory deleted successfully"); + + } + + } +} diff --git a/src/main/resources/config.properties.example b/src/main/resources/config.properties.example index 8840823..095d75c 100644 --- a/src/main/resources/config.properties.example +++ b/src/main/resources/config.properties.example @@ -22,3 +22,5 @@ ipc.pipe.name=mediamanager-pipe ipc.pipe.path=/tmp/mediamanager ipc.buffer.size=8192 +ipc.socket.path=/tmp/mediamanager + From f1071dca03da33f7355a6927497509b5aedaa4a2 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Fri, 14 Nov 2025 06:22:35 -0300 Subject: [PATCH 08/39] Enhance `IPCManager` with non-blocking server support and improved connection handling: - Configured UNIX domain socket for non-blocking mode to prevent indefinite blocking on `accept()`. - Introduced connection loop with thread pool for client handling. - Added `ClientHandler` to process client communications. - Implemented proper resource cleanup during shutdown. - Improved logging for socket initialization, client handling, and shutdown. --- .../mediamanager/service/ipc/IPCManager.java | 227 +++++++++++++++--- 1 file changed, 191 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/mediamanager/service/ipc/IPCManager.java b/src/main/java/com/mediamanager/service/ipc/IPCManager.java index e1cc0ae..3666c19 100644 --- a/src/main/java/com/mediamanager/service/ipc/IPCManager.java +++ b/src/main/java/com/mediamanager/service/ipc/IPCManager.java @@ -5,19 +5,35 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.File; +import java.io.IOException; import java.net.StandardProtocolFamily; import java.net.UnixDomainSocketAddress; import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; public class IPCManager { - private static Properties configuration; + private final Properties configuration; private static final Logger logger = LogManager.getLogger(IPCManager.class); private Path socketPath; private UnixDomainSocketAddress socketAddress; private ServerSocketChannel serverChannel; + private ExecutorService clientThreadPool; + private final AtomicBoolean running = new AtomicBoolean(false); + private final ConcurrentHashMap activeClients = new ConcurrentHashMap<>(); + private final AtomicInteger clientIdCounter = new AtomicInteger(0); + public IPCManager(Properties config){ configuration = config; @@ -30,33 +46,58 @@ public class IPCManager { validateConfiguration(); socketPath = Path.of(configuration.getProperty("ipc.socket.path")).resolve("mediamanager.sock"); - if (checkUnixSocketExists()){ - logger.warn("IPC socket already exists"); + if (Files.exists(socketPath)) { + logger.warn("Socket file already exists at: {}", socketPath); logger.info("Deleting existing socket..."); Files.deleteIfExists(socketPath); - - } - try{ + Path parentDir = socketPath.getParent(); + if (parentDir != null && !Files.exists(parentDir)) { + logger.info("Creating parent directory for socket: {}", parentDir); + Files.createDirectories(parentDir); + } + + try { socketAddress = UnixDomainSocketAddress.of(socketPath); - logger.info("IPC socket created successfully"); - } catch (Exception e){ - logger.error("Failed to create socket: {}", e.getMessage()); - throw new Exception("Failed to create socket: " + e.getMessage(), e); + logger.debug("Socket address created"); + + serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); + serverChannel.bind(socketAddress); + logger.info("Server bound to socket - file created at: {}", socketPath); + + // ESTA É A MUDANÇA CRÍTICA + // Configura o canal para modo não-bloqueante + // Isso faz accept() retornar null imediatamente se não houver cliente + // ao invés de bloquear esperando indefinidamente + serverChannel.configureBlocking(false); + logger.debug("Server channel configured for non-blocking mode"); + + Set perms = PosixFilePermissions.fromString("rw-------"); + Files.setPosixFilePermissions(socketPath, perms); + logger.debug("Socket permissions set to: rw-------"); + + clientThreadPool = Executors.newCachedThreadPool(runnable -> { + Thread thread = new Thread(runnable); + thread.setName("IPC-Client-Handler-" + thread.getId()); + thread.setDaemon(true); + return thread; + }); + logger.debug("Client thread pool created"); + + running.set(true); + + Thread serverThread = new Thread(this::acceptConnectionsLoop, "IPC-Server-Accept-Thread"); + serverThread.setDaemon(true); + serverThread.start(); + logger.info("Server thread started - accepting connections"); + + logger.info("IPC server initialized successfully on {}", socketPath.toAbsolutePath()); + + } catch (IOException e) { + logger.error("Failed to initialize IPC server: {}", e.getMessage()); + throw new Exception("Failed to initialize IPC server: " + e.getMessage(), e); } - if (!Files.isDirectory(socketPath.getParent())) { - logger.info("Creating parent directory for socket..."); - Files.createDirectories(socketPath.getParent()); - } - - serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); - logger.info("IPC server channel opened successfully"); - serverChannel.bind(socketAddress); - logger.info("IPC server channel bound successfully"); - logger.info("IPC server listening on {}", socketPath.toAbsolutePath().toString()); - - } private void validateConfiguration() throws Exception { String[] requiredProperties = { @@ -79,23 +120,137 @@ public class IPCManager { } public void close() throws Exception { + if(!running.get()){ + logger.warn("IPC connection is already closed"); + } + + logger.info("Closing IPC connection..."); - if (serverChannel != null) { + + running.set(false); + if (serverChannel != null && serverChannel.isOpen()) { serverChannel.close(); - } - File socketFile = new File(String.valueOf(socketPath)); - boolean delete = false; - if (socketFile.exists()) { - delete = socketFile.delete(); - } - if (!delete){ - logger.warn("Failed to delete socket file"); - }else { - logger.info("IPC socket deleted successfully"); - Files.deleteIfExists(socketPath.getParent()); - logger.info("IPC socket parent directory deleted successfully"); - + logger.debug("Server channel closed"); } + if (clientThreadPool != null) { + clientThreadPool.shutdown(); + try { + if (!clientThreadPool.awaitTermination(30, TimeUnit.SECONDS)) { + logger.warn("Some client handlers did not finish in time, forcing shutdown"); + clientThreadPool.shutdownNow(); + } + } catch (InterruptedException e) { + logger.error("Interrupted while waiting for client handlers", e); + clientThreadPool.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + + if (socketPath != null && Files.exists(socketPath)) { + Files.deleteIfExists(socketPath); + logger.info("Socket file deleted successfully"); + } + + logger.info("IPC server closed successfully"); + } + + private void acceptConnectionsLoop() { + logger.info("Preparing to accept connections..."); + + while (running.get()) { + try { + // Em modo não-bloqueante, accept() retorna imediatamente + // Se há um cliente esperando, retorna o SocketChannel + // Se não há cliente, retorna null + SocketChannel clientChannel = serverChannel.accept(); + + if (clientChannel != null) { + // Um cliente realmente se conectou! + int clientId = clientIdCounter.incrementAndGet(); + logger.info("Client {} connected", clientId); + + ClientHandler handler = new ClientHandler(clientId, clientChannel); + activeClients.put(clientId, handler); + + clientThreadPool.submit(() -> { + try { + handler.handle(); + } finally { + activeClients.remove(clientId); + logger.info("Client {} disconnected", clientId); + } + }); + } else { + // Nenhum cliente conectado no momento + // Dorme por um curto período antes de verificar novamente + // Isso evita consumir CPU desnecessariamente em um loop vazio + Thread.sleep(100); // 100 milissegundos + } + + } catch (InterruptedException e) { + // Thread foi interrompida, provavelmente durante shutdown + logger.debug("Accept loop interrupted"); + break; + + } catch (IOException e) { + // Erros de I/O reais devem ser logados + if (running.get()) { + logger.error("Error accepting client connection", e); + } + break; + } + } + + logger.info("Connection loop stopped gracefully"); + } + + + private class ClientHandler { + private final int clientId; + private final SocketChannel channel; + + public ClientHandler(int clientId, SocketChannel channel) { + this.clientId = clientId; + this.channel = channel; + } + + /** + * Método principal que processa a comunicação com o cliente. + * Aqui é onde vamos ler mensagens JSON, processá-las, e enviar respostas. + */ + public void handle() { + logger.debug("Client {} handler thread started", clientId); + + try { + // TODO: No próximo passo, vamos implementar: + // 1. Ler mensagens JSON do SocketChannel + // 2. Parsear o JSON para objetos Java + // 3. Processar a requisição + // 4. Criar uma resposta JSON + // 5. Escrever a resposta de volta no SocketChannel + + // Por enquanto, apenas mantém a conexão aberta brevemente + // para testar que o sistema de aceitação e threads está funcionando + Thread.sleep(100); + + logger.debug("Client {} processing complete", clientId); + + } catch (Exception e) { + logger.error("Error handling client {}", clientId, e); + } finally { + // SEMPRE fecha o canal quando terminar + // O finally garante que isso acontece mesmo se houver exceção + try { + channel.close(); + logger.debug("Client {} channel closed", clientId); + } catch (IOException e) { + logger.error("Error closing client {} channel", clientId, e); + } + } + } } } + + + From 38546351851eb92e7af99579aaeb3a390fdb3112 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Fri, 14 Nov 2025 19:52:53 -0300 Subject: [PATCH 09/39] Remove the unused ` checkUnixSocketExists ` method from `IPCManager`. --- src/main/java/com/mediamanager/service/ipc/IPCManager.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/mediamanager/service/ipc/IPCManager.java b/src/main/java/com/mediamanager/service/ipc/IPCManager.java index 3666c19..19537c5 100644 --- a/src/main/java/com/mediamanager/service/ipc/IPCManager.java +++ b/src/main/java/com/mediamanager/service/ipc/IPCManager.java @@ -110,10 +110,6 @@ public class IPCManager { } logger.debug("IPC configuration validated successfully"); } - private boolean checkUnixSocketExists() { - File socketFile = new File(String.valueOf(socketPath)); - return socketFile.exists(); - } public Path getSocketPath(){ return socketPath; From 23b6d5467403129dccd73e059594ce05bd32870f Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Sat, 15 Nov 2025 01:54:31 -0300 Subject: [PATCH 10/39] Add Protocol Buffers support to IPCManager and Maven build: - Created `messages.proto` to define `TextMessage` schema. - Integrated Protocol Buffers Java library into Maven dependencies. - Configured Maven plugins for Protocol Buffers compilation and generated source management. - Implemented message serialization and deserialization in `IPCManager`. - Enhanced client handling to process and respond with Protocol Buffers messages. --- pom.xml | 55 ++++++++++++ .../mediamanager/service/ipc/IPCManager.java | 88 +++++++++++++++++-- src/main/proto/messages.proto | 9 ++ 3 files changed, 144 insertions(+), 8 deletions(-) create mode 100644 src/main/proto/messages.proto diff --git a/pom.xml b/pom.xml index 3ea8e09..b0ed9b5 100644 --- a/pom.xml +++ b/pom.xml @@ -78,9 +78,22 @@ ${junit.version} test + + com.google.protobuf + protobuf-java + 4.32.0 + + + + kr.motd.maven + os-maven-plugin + 1.7.1 + + + org.apache.maven.plugins @@ -91,12 +104,54 @@ 17 + org.apache.maven.plugins maven-surefire-plugin 3.2.5 + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + /usr/bin/protoc + ${project.basedir}/src/main/proto + ${project.build.directory}/generated-sources/protobuf/java + false + + + + + compile + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.5.0 + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/protobuf/java + + + + + + org.apache.maven.plugins diff --git a/src/main/java/com/mediamanager/service/ipc/IPCManager.java b/src/main/java/com/mediamanager/service/ipc/IPCManager.java index 19537c5..50d2e50 100644 --- a/src/main/java/com/mediamanager/service/ipc/IPCManager.java +++ b/src/main/java/com/mediamanager/service/ipc/IPCManager.java @@ -1,9 +1,11 @@ package com.mediamanager.service.ipc; +import com.mediamanager.protocol.SimpleProtocol; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.nio.ByteBuffer; import java.io.File; import java.io.IOException; import java.net.StandardProtocolFamily; @@ -219,15 +221,20 @@ public class IPCManager { logger.debug("Client {} handler thread started", clientId); try { - // TODO: No próximo passo, vamos implementar: - // 1. Ler mensagens JSON do SocketChannel - // 2. Parsear o JSON para objetos Java - // 3. Processar a requisição - // 4. Criar uma resposta JSON - // 5. Escrever a resposta de volta no SocketChannel + SimpleProtocol.TextMessage request = readMessage(channel); + if (request != null) { + logger.info("Client {} sent: '{}'", clientId, request.getContent()); + String response = "Server Received: " + request.getContent(); - // Por enquanto, apenas mantém a conexão aberta brevemente - // para testar que o sistema de aceitação e threads está funcionando + SimpleProtocol.TextMessage responseMsg = SimpleProtocol.TextMessage.newBuilder() + .setContent(response).build(); + + writeMessage(channel,responseMsg); + logger.info("Client {} response sent: '{}'", clientId, response); + + }else { + logger.warn("Client {} sent null message", clientId); + } Thread.sleep(100); logger.debug("Client {} processing complete", clientId); @@ -245,6 +252,71 @@ public class IPCManager { } } } + + private SimpleProtocol.TextMessage readMessage(SocketChannel channel) throws IOException { + // Primeiro, lê o tamanho da mensagem (4 bytes = int32) + java.nio.ByteBuffer sizeBuffer = java.nio.ByteBuffer.allocate(4); + int bytesRead = 0; + + while (bytesRead < 4) { + int read = channel.read(sizeBuffer); + if (read == -1) { + logger.debug("Client disconnected before sending size"); + return null; + } + bytesRead += read; + } + + sizeBuffer.flip(); + int messageSize = sizeBuffer.getInt(); + logger.debug("Expecting message of {} bytes", messageSize); + + // Validação básica de segurança + if (messageSize <= 0 || messageSize > 1024 * 1024) { // Max 1MB + throw new IOException("Invalid message size: " + messageSize); + } + + // Agora lê a mensagem completa + java.nio.ByteBuffer messageBuffer = java.nio.ByteBuffer.allocate(messageSize); + bytesRead = 0; + + while (bytesRead < messageSize) { + int read = channel.read(messageBuffer); + if (read == -1) { + throw new IOException("Client disconnected while reading message"); + } + bytesRead += read; + } + + messageBuffer.flip(); + + // Deserializa o Protocol Buffers + byte[] messageBytes = new byte[messageSize]; + messageBuffer.get(messageBytes); + + return SimpleProtocol.TextMessage.parseFrom(messageBytes); + } + + private void writeMessage(SocketChannel channel, SimpleProtocol.TextMessage message) throws IOException { + // Serializa a mensagem + byte[] messageBytes = message.toByteArray(); + int messageSize = messageBytes.length; + + logger.debug("Writing message of {} bytes", messageSize); + + // Cria buffer com tamanho + mensagem + java.nio.ByteBuffer buffer = java.nio.ByteBuffer.allocate(4 + messageSize); + buffer.putInt(messageSize); // 4 bytes de tamanho + buffer.put(messageBytes); // N bytes da mensagem + buffer.flip(); + + // Escreve tudo no canal + while (buffer.hasRemaining()) { + channel.write(buffer); + } + + logger.debug("Message written successfully"); + } } } diff --git a/src/main/proto/messages.proto b/src/main/proto/messages.proto new file mode 100644 index 0000000..23b6925 --- /dev/null +++ b/src/main/proto/messages.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "com.mediamanager.protocol"; +option java_outer_classname = "SimpleProtocol"; +package mediamanager; + +message TextMessage { + string content = 1; // O texto da mensagem +} \ No newline at end of file From a0ad10b1bce99ae9168a942a9f31a50caa47905c Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Mon, 17 Nov 2025 07:21:52 -0300 Subject: [PATCH 11/39] Integrate `DelegateActionManager` for request handling and refactor IPC: - Replaced `SimpleProtocol` with `TransportProtocol` in `IPCManager`. - Added `DelegateActionManager` to route requests to handlers, starting with `EchoHandler`. - Updated `MediaManagerApplication` to initialize and manage `DelegateActionManager`. - Extended Protocol Buffers schema with `Request` and `Response` definitions. - Enhanced logging for request processing and redesigned response flow. --- .../mediamanager/MediaManagerApplication.java | 10 +++- .../service/delegate/ActionHandler.java | 11 ++++ .../delegate/DelegateActionManager.java | 60 +++++++++++++++++++ .../service/delegate/handler/EchoHandler.java | 17 ++++++ .../mediamanager/service/ipc/IPCManager.java | 54 ++++++++--------- src/main/proto/messages.proto | 16 ++++- 6 files changed, 133 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/mediamanager/service/delegate/ActionHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/EchoHandler.java diff --git a/src/main/java/com/mediamanager/MediaManagerApplication.java b/src/main/java/com/mediamanager/MediaManagerApplication.java index d483da3..bc04be1 100644 --- a/src/main/java/com/mediamanager/MediaManagerApplication.java +++ b/src/main/java/com/mediamanager/MediaManagerApplication.java @@ -1,5 +1,6 @@ package com.mediamanager; +import com.mediamanager.service.delegate.DelegateActionManager; import com.mediamanager.service.ipc.IPCManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -14,6 +15,7 @@ public class MediaManagerApplication { private static final Logger logger = LogManager.getLogger(MediaManagerApplication.class); private static Properties config; private static DatabaseManager databaseManager; + private static DelegateActionManager actionManager; private static IPCManager ipcManager; public static void main(String[] args) { @@ -24,8 +26,9 @@ public class MediaManagerApplication { loadConfiguration(); databaseManager = new DatabaseManager(config); databaseManager.init(); - - ipcManager = new IPCManager(config); + actionManager = new DelegateActionManager(); + actionManager.start(); + ipcManager = new IPCManager(config,actionManager); ipcManager.init(); // TODO: Start application services @@ -51,6 +54,9 @@ public class MediaManagerApplication { } } + if (actionManager != null) { + actionManager.stop(); + } logger.info("MediaManager Core shutdown successfully"); logger.info("Goodbye!"); diff --git a/src/main/java/com/mediamanager/service/delegate/ActionHandler.java b/src/main/java/com/mediamanager/service/delegate/ActionHandler.java new file mode 100644 index 0000000..27228fc --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/ActionHandler.java @@ -0,0 +1,11 @@ +package com.mediamanager.service.delegate; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.protocol.TransportProtocol; + +@FunctionalInterface +public interface ActionHandler { + TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException; +} + diff --git a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java new file mode 100644 index 0000000..6293947 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java @@ -0,0 +1,60 @@ +package com.mediamanager.service.delegate; + +import com.google.protobuf.ByteString; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.service.delegate.handler.EchoHandler; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.HashMap; +import java.util.Map; + +public class DelegateActionManager { + private static final Logger logger = LogManager.getLogger(DelegateActionManager.class); + + private final Map handlerRegistry; + + public DelegateActionManager() { + + logger.debug("DelegateActionManager created"); + this.handlerRegistry = new HashMap<>(); + registerHandlers(); + } + + private void registerHandlers() { + handlerRegistry.put("echo",new EchoHandler()); + } + + public void start(){ + logger.info("DelegateActionManager started"); + } + + public void stop(){ + logger.info("DelegateActionManager stopped"); + } + + public TransportProtocol.Response ProcessedRequest(TransportProtocol.Request request){ + String requestId = request.getRequestId(); + logger.info("Processing request: {}", requestId); + String action = request.getHeadersMap().getOrDefault("action", "unknown"); + ActionHandler handler = handlerRegistry.get(action); + TransportProtocol.Response.Builder responseBuilder; + if (handler == null) { + logger.warn("No handler found for action: {}", action); + responseBuilder = TransportProtocol.Response.newBuilder() + .setStatusCode(404) // 404 Not Found + .setPayload(ByteString.copyFromUtf8("Error: Action '" + action + "' not found.")); + } else{ + try { + logger.debug("Delegating action '{}' to handler...", action); + responseBuilder = handler.handle(request.getPayload()); + }catch (Exception e) { + logger.error("Handler for action '{}' threw an exception:", action, e); + responseBuilder = TransportProtocol.Response.newBuilder() + .setStatusCode(500) // 500 Internal Server Error + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + } + return responseBuilder.setRequestId(requestId).build(); + } +} diff --git a/src/main/java/com/mediamanager/service/delegate/handler/EchoHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/EchoHandler.java new file mode 100644 index 0000000..23463e6 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/EchoHandler.java @@ -0,0 +1,17 @@ +package com.mediamanager.service.delegate.handler; + +import com.google.protobuf.ByteString; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.service.delegate.ActionHandler; + +public class EchoHandler implements ActionHandler { + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) { + String payloadText = requestPayload.toStringUtf8(); + String responseText = "Server received: " + payloadText; + + return TransportProtocol.Response.newBuilder() + .setPayload(ByteString.copyFromUtf8(responseText)) + .setStatusCode(200); + } +} diff --git a/src/main/java/com/mediamanager/service/ipc/IPCManager.java b/src/main/java/com/mediamanager/service/ipc/IPCManager.java index 50d2e50..17ff8b1 100644 --- a/src/main/java/com/mediamanager/service/ipc/IPCManager.java +++ b/src/main/java/com/mediamanager/service/ipc/IPCManager.java @@ -1,12 +1,11 @@ package com.mediamanager.service.ipc; - -import com.mediamanager.protocol.SimpleProtocol; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.service.delegate.DelegateActionManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.nio.ByteBuffer; -import java.io.File; + import java.io.IOException; import java.net.StandardProtocolFamily; import java.net.UnixDomainSocketAddress; @@ -28,6 +27,8 @@ import java.util.concurrent.atomic.AtomicInteger; public class IPCManager { private final Properties configuration; private static final Logger logger = LogManager.getLogger(IPCManager.class); + private final DelegateActionManager actionManager; + private Path socketPath; private UnixDomainSocketAddress socketAddress; private ServerSocketChannel serverChannel; @@ -37,8 +38,9 @@ public class IPCManager { private final AtomicInteger clientIdCounter = new AtomicInteger(0); - public IPCManager(Properties config){ + public IPCManager(Properties config, DelegateActionManager actionManager){ configuration = config; + this.actionManager = actionManager; logger.debug("IPCManager created with configuration:"); @@ -221,29 +223,25 @@ public class IPCManager { logger.debug("Client {} handler thread started", clientId); try { - SimpleProtocol.TextMessage request = readMessage(channel); + TransportProtocol.Request request = readRequest(channel); + if (request != null) { - logger.info("Client {} sent: '{}'", clientId, request.getContent()); - String response = "Server Received: " + request.getContent(); + logger.info("Client {} sent request {}", clientId, request.getRequestId()); - SimpleProtocol.TextMessage responseMsg = SimpleProtocol.TextMessage.newBuilder() - .setContent(response).build(); + // Processa usando o DelegateActionManager + TransportProtocol.Response response = actionManager.ProcessedRequest(request); - writeMessage(channel,responseMsg); - logger.info("Client {} response sent: '{}'", clientId, response); + // Envia resposta de volta + writeResponse(channel, response); - }else { + logger.info("Client {} response sent", clientId); + } else { logger.warn("Client {} sent null message", clientId); } - Thread.sleep(100); - - logger.debug("Client {} processing complete", clientId); } catch (Exception e) { logger.error("Error handling client {}", clientId, e); } finally { - // SEMPRE fecha o canal quando terminar - // O finally garante que isso acontece mesmo se houver exceção try { channel.close(); logger.debug("Client {} channel closed", clientId); @@ -252,8 +250,7 @@ public class IPCManager { } } } - - private SimpleProtocol.TextMessage readMessage(SocketChannel channel) throws IOException { + private TransportProtocol.Request readRequest(SocketChannel channel) throws IOException { // Primeiro, lê o tamanho da mensagem (4 bytes = int32) java.nio.ByteBuffer sizeBuffer = java.nio.ByteBuffer.allocate(4); int bytesRead = 0; @@ -294,28 +291,25 @@ public class IPCManager { byte[] messageBytes = new byte[messageSize]; messageBuffer.get(messageBytes); - return SimpleProtocol.TextMessage.parseFrom(messageBytes); + return TransportProtocol.Request.parseFrom(messageBytes); } - private void writeMessage(SocketChannel channel, SimpleProtocol.TextMessage message) throws IOException { - // Serializa a mensagem - byte[] messageBytes = message.toByteArray(); + private void writeResponse(SocketChannel channel, TransportProtocol.Response response) throws IOException { + byte[] messageBytes = response.toByteArray(); int messageSize = messageBytes.length; - logger.debug("Writing message of {} bytes", messageSize); + logger.debug("Writing response of {} bytes", messageSize); - // Cria buffer com tamanho + mensagem java.nio.ByteBuffer buffer = java.nio.ByteBuffer.allocate(4 + messageSize); - buffer.putInt(messageSize); // 4 bytes de tamanho - buffer.put(messageBytes); // N bytes da mensagem + buffer.putInt(messageSize); + buffer.put(messageBytes); buffer.flip(); - // Escreve tudo no canal while (buffer.hasRemaining()) { channel.write(buffer); } - logger.debug("Message written successfully"); + logger.debug("Response written successfully"); } } } diff --git a/src/main/proto/messages.proto b/src/main/proto/messages.proto index 23b6925..106e59e 100644 --- a/src/main/proto/messages.proto +++ b/src/main/proto/messages.proto @@ -1,9 +1,19 @@ syntax = "proto3"; option java_package = "com.mediamanager.protocol"; -option java_outer_classname = "SimpleProtocol"; +option java_outer_classname = "TransportProtocol"; + package mediamanager; -message TextMessage { - string content = 1; // O texto da mensagem +message Request { + string request_id = 1; + bytes payload = 2; + map headers = 3; +} + +message Response { + string request_id = 1; + int32 status_code = 2; + bytes payload = 3; + map headers = 4; } \ No newline at end of file From dc4b6cbdfa78b89425cfdf5cbeae3bfc96aafec3 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Fri, 21 Nov 2025 18:03:20 -0300 Subject: [PATCH 12/39] Add SQLite support with `SqliteDatabaseManager` and application mode handling: - Created `SqliteDatabaseManager` for SQLite database initialization and management. - Refactored `DatabaseManager` into an abstract class to support different database implementations. - Introduced `ApplicationMode` enum to switch between `local` and `server` modes. - Updated `MediaManagerApplication` to handle `runtype` configuration and initialize SQLite in `local` mode. - Updated `config.properties.example` with new `runtype` property. - Added SQLite JDBC dependency to `pom.xml`. --- pom.xml | 5 + .../mediamanager/MediaManagerApplication.java | 40 +++++++- .../service/database/DatabaseManager.java | 75 ++++---------- .../database/SqliteDatabaseManager.java | 97 +++++++++++++++++++ src/main/resources/config.properties.example | 4 +- 5 files changed, 160 insertions(+), 61 deletions(-) create mode 100644 src/main/java/com/mediamanager/service/database/SqliteDatabaseManager.java diff --git a/pom.xml b/pom.xml index b0ed9b5..bce0f0f 100644 --- a/pom.xml +++ b/pom.xml @@ -83,6 +83,11 @@ protobuf-java 4.32.0 + + org.xerial + sqlite-jdbc + 3.44.1.0 + diff --git a/src/main/java/com/mediamanager/MediaManagerApplication.java b/src/main/java/com/mediamanager/MediaManagerApplication.java index bc04be1..b9579dc 100644 --- a/src/main/java/com/mediamanager/MediaManagerApplication.java +++ b/src/main/java/com/mediamanager/MediaManagerApplication.java @@ -1,5 +1,6 @@ package com.mediamanager; +import com.mediamanager.service.database.DatabaseManager; import com.mediamanager.service.delegate.DelegateActionManager; import com.mediamanager.service.ipc.IPCManager; import org.apache.logging.log4j.LogManager; @@ -9,7 +10,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.Properties; -import com.mediamanager.service.database.DatabaseManager; +import com.mediamanager.service.database.SqliteDatabaseManager; public class MediaManagerApplication { private static final Logger logger = LogManager.getLogger(MediaManagerApplication.class); @@ -18,13 +19,46 @@ public class MediaManagerApplication { private static DelegateActionManager actionManager; private static IPCManager ipcManager; + public enum ApplicationMode { + LOCAL("local"), + SERVER("server"); + + private final String value; + + ApplicationMode(String value) { + this.value = value; + } + } + public static void main(String[] args) { logger.info("Starting MediaManager Core Application..."); try { // Load configuration loadConfiguration(); - databaseManager = new DatabaseManager(config); + String runTypeString = config.getProperty("runtype","local"); + ApplicationMode mode = null; + + for (ApplicationMode am : ApplicationMode.values()) { + if (am.value.equalsIgnoreCase(runTypeString)) { + mode = am; + break; + } + } + if (mode == null) { + logger.error("Invalid run type: {}", runTypeString); + throw new Exception("Invalid run type: " + runTypeString); + } + logger.info("Run type: {}", mode); + switch (mode) { + case LOCAL: + logger.info("Starting local database..."); + databaseManager = new SqliteDatabaseManager(config); + break; + case SERVER: + throw new Exception("Server mode not yet implemented"); + default: + } databaseManager.init(); actionManager = new DelegateActionManager(); actionManager.start(); @@ -102,4 +136,4 @@ public class MediaManagerApplication { public static Properties getConfig() { return config; } -} +} \ No newline at end of file diff --git a/src/main/java/com/mediamanager/service/database/DatabaseManager.java b/src/main/java/com/mediamanager/service/database/DatabaseManager.java index eeabc69..34f09d5 100644 --- a/src/main/java/com/mediamanager/service/database/DatabaseManager.java +++ b/src/main/java/com/mediamanager/service/database/DatabaseManager.java @@ -1,55 +1,28 @@ package com.mediamanager.service.database; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; +public abstract class DatabaseManager { + protected final Properties configuration; + protected Connection connection; + protected static final Logger logger = LogManager.getLogger(DatabaseManager.class); -public class DatabaseManager { - private final Properties configuration; - private Connection connection; - private static final Logger logger = LogManager.getLogger(DatabaseManager.class); public DatabaseManager(Properties config) { this.configuration = config; logger.debug("DatabaseManager created with configuration:"); - } - public void init() throws Exception { - logger.info("Initializing database connection..."); - validateConfiguration(); + public abstract void init() throws Exception; - String databaseType = configuration.getProperty("database.type"); - String databaseUrl = configuration.getProperty("database.url"); - String databaseUsername = configuration.getProperty("database.username"); - String databasePassword = configuration.getProperty("database.password"); - String databasePort = configuration.getProperty("database.port"); - String databaseName = configuration.getProperty("database.name"); + protected abstract Connection createConnection() throws Exception; - - String connectionString = String.format("jdbc:postgresql://%s:%s/%s", databaseUrl, databasePort, databaseName); - logger.debug("Attempting to connect to: {}", connectionString); - try { - connection = DriverManager.getConnection(connectionString, databaseUsername, databasePassword); - logger.info("Database connection established successfully"); - - performSanityChecks(); - logger.info("Database sanity checks passed successfully"); - - - } catch (SQLException e) { - logger.error("Failed to connect to database", e); - throw new Exception("Database connection failed: " + e.getMessage(), e); - } - - - - } - private void performSanityChecks() throws SQLException { + protected void performSanityChecks() throws SQLException { logger.debug("Performing sanity checks..."); try (Statement stmt = connection.createStatement()) { stmt.execute("SELECT 1"); @@ -59,27 +32,6 @@ public class DatabaseManager { String databaseProductVersion = connection.getMetaData().getDatabaseProductVersion(); logger.info("Connected to database: {} v{}", databaseProductName, databaseProductVersion); } - private void validateConfiguration() throws Exception { - String[] requiredProperties = { - "database.url", - "database.username", - "database.password", - "database.port", - "database.name" - }; - - for (String property : requiredProperties) { - if (configuration.getProperty(property) == null || - configuration.getProperty(property).trim().isEmpty()) { - throw new Exception("Required database configuration missing: " + property); - } - } - - logger.debug("Database configuration validated successfully"); - - } - - public Connection getConnection() { return connection; @@ -98,4 +50,13 @@ public class DatabaseManager { logger.debug("No database connection to close"); } } -} + protected boolean testConnection() { + try (Statement stmt = connection.createStatement()) { + stmt.execute("SELECT 1"); + return true; + } catch (SQLException e) { + logger.error("Connection test failed", e); + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/mediamanager/service/database/SqliteDatabaseManager.java b/src/main/java/com/mediamanager/service/database/SqliteDatabaseManager.java new file mode 100644 index 0000000..02d3ecc --- /dev/null +++ b/src/main/java/com/mediamanager/service/database/SqliteDatabaseManager.java @@ -0,0 +1,97 @@ +package com.mediamanager.service.database; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; + +public class SqliteDatabaseManager extends DatabaseManager { + private static final Logger logger = LogManager.getLogger(SqliteDatabaseManager.class); + + + private String connectionUrl; + + public SqliteDatabaseManager(Properties config) { + super(config); + } + + @Override + public void init() throws Exception { + + logger.info("Initializing SQLite database..."); + String dataDir = configuration.getProperty("database.dir", + System.getProperty("user.home") + "/.mediamanager/db"); + String dbFilename = configuration.getProperty("database.filename", "mediamanager.db"); + String driverClassName = configuration.getProperty("database.driver", "org.sqlite.JDBC"); + try { + Class.forName(driverClassName); + }catch(ClassNotFoundException e) { + logger.error("Failed to load SQLite driver", e); + throw e; + } + Path dataPath = Paths.get(dataDir); + if (!Files.exists(dataPath)) { + Files.createDirectories(dataPath); + logger.debug("Created database directory: {}", dataDir); + } + Path dbFile = dataPath.resolve(dbFilename); + this.connectionUrl = "jdbc:sqlite:" + dbFile.toAbsolutePath().toString(); + logger.info("Database file path: {}", dbFile); + logger.info("Connection URL: {}", this.connectionUrl); + this.connection = createConnection(); + configurePerformancePragmas(); + performSanityChecks(); + ensureSchemaExists(); + logger.info("SQLite database initialized successfully"); + + } + + @Override + protected Connection createConnection() throws Exception { + try { + // O driver org.xerial.sqlite-jdbc é carregado automaticamente aqui + Connection conn = DriverManager.getConnection(this.connectionUrl); + logger.debug("Got connection to SQLite file"); + return conn; + } catch (SQLException e) { + logger.error("Failed to create SQLite connection", e); + throw new Exception("SQLite connection failed: " + e.getMessage(), e); + } + } + + + private void configurePerformancePragmas() throws SQLException { + try (Statement stmt = connection.createStatement()) { + + stmt.execute("PRAGMA journal_mode=WAL;"); + + + stmt.execute("PRAGMA foreign_keys=ON;"); + + + stmt.execute("PRAGMA synchronous=NORMAL;"); + + stmt.execute("PRAGMA busy_timeout=5000;"); + + logger.debug("SQLite performance PRAGMAs applied (WAL, Synchronous, ForeignKeys)."); + } + } + + private void ensureSchemaExists() throws SQLException { + + } + + @Override + public void close() { + + super.close(); + logger.info("SQLite resources released."); + } +} \ No newline at end of file diff --git a/src/main/resources/config.properties.example b/src/main/resources/config.properties.example index 095d75c..0991905 100644 --- a/src/main/resources/config.properties.example +++ b/src/main/resources/config.properties.example @@ -21,6 +21,8 @@ hibernate.format_sql=true ipc.pipe.name=mediamanager-pipe ipc.pipe.path=/tmp/mediamanager ipc.buffer.size=8192 - +runtype=local ipc.socket.path=/tmp/mediamanager + + From 2704be31bca6833b09d0fee8b0170ce37ec80960 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Fri, 21 Nov 2025 18:19:38 -0300 Subject: [PATCH 13/39] Fix logging library dependency in `pom.xml`: replace `log4j-slf4j2-impl` with `log4j-slf4j-impl`. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bce0f0f..261df9e 100644 --- a/pom.xml +++ b/pom.xml @@ -55,7 +55,7 @@ org.apache.logging.log4j - log4j-slf4j2-impl + log4j-slf4j-impl ${log4j.version} From 6576b5405746b6589e50e4ca0afe3df869496c33 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Sat, 22 Nov 2025 02:29:28 -0300 Subject: [PATCH 14/39] - Add `HeartbeatHandler` and enhance `EchoHandler` for Protocol Buffers support. - Extend `test.proto` schema with heartbeat and echo message definitions. - Register `HeartbeatHandler` in `DelegateActionManager`. - Refactor Log4j2 configuration to standardize log levels. - Optimize client wait time in `IPCManager` for reduced latency. --- .../delegate/DelegateActionManager.java | 2 ++ .../service/delegate/handler/EchoHandler.java | 34 ++++++++++++++---- .../delegate/handler/HeartbeatHandler.java | 36 +++++++++++++++++++ .../mediamanager/service/ipc/IPCManager.java | 2 +- src/main/proto/test.proto | 24 +++++++++++++ src/main/resources/log4j2.xml | 6 ++-- 6 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/HeartbeatHandler.java create mode 100644 src/main/proto/test.proto diff --git a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java index 6293947..1d2c0a6 100644 --- a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java +++ b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java @@ -3,6 +3,7 @@ package com.mediamanager.service.delegate; import com.google.protobuf.ByteString; import com.mediamanager.protocol.TransportProtocol; import com.mediamanager.service.delegate.handler.EchoHandler; +import com.mediamanager.service.delegate.handler.HeartbeatHandler; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -23,6 +24,7 @@ public class DelegateActionManager { private void registerHandlers() { handlerRegistry.put("echo",new EchoHandler()); + handlerRegistry.put("heartbeat",new HeartbeatHandler()); } public void start(){ diff --git a/src/main/java/com/mediamanager/service/delegate/handler/EchoHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/EchoHandler.java index 23463e6..4945efc 100644 --- a/src/main/java/com/mediamanager/service/delegate/handler/EchoHandler.java +++ b/src/main/java/com/mediamanager/service/delegate/handler/EchoHandler.java @@ -1,17 +1,39 @@ package com.mediamanager.service.delegate.handler; import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.protocol.TestProtocol.EchoCommand; // ← Import +import com.mediamanager.protocol.TestProtocol.EchoResponse; // ← Import import com.mediamanager.protocol.TransportProtocol; import com.mediamanager.service.delegate.ActionHandler; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class EchoHandler implements ActionHandler { - @Override - public TransportProtocol.Response.Builder handle(ByteString requestPayload) { - String payloadText = requestPayload.toStringUtf8(); - String responseText = "Server received: " + payloadText; + private static final Logger logger = LogManager.getLogger(EchoHandler.class); + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException { // ← Pode lançar exceção + + // 1. Parse Protobuf bytes → EchoCommand + EchoCommand command = EchoCommand.parseFrom(requestPayload); + + logger.debug("Echo received: {}", command.getMessage()); + + // 2. Cria EchoResponse (Protobuf) + EchoResponse echoResponse = EchoResponse.newBuilder() + .setMessage(command.getMessage()) + .setServerTimestamp(System.currentTimeMillis()) + .build(); + + // 3. Serializa EchoResponse → bytes + ByteString responsePayload = ByteString.copyFrom(echoResponse.toByteArray()); + + // 4. Retorna Response return TransportProtocol.Response.newBuilder() - .setPayload(ByteString.copyFromUtf8(responseText)) - .setStatusCode(200); + .setPayload(responsePayload) + .setStatusCode(200) + .putHeaders("Content-Type", "application/x-protobuf"); } } diff --git a/src/main/java/com/mediamanager/service/delegate/handler/HeartbeatHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/HeartbeatHandler.java new file mode 100644 index 0000000..c385433 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/HeartbeatHandler.java @@ -0,0 +1,36 @@ +package com.mediamanager.service.delegate.handler; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.protocol.TestProtocol.HeartbeatCommand; +import com.mediamanager.protocol.TestProtocol.HeartbeatResponse; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.service.delegate.ActionHandler; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class HeartbeatHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(HeartbeatHandler.class); + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException { + + HeartbeatCommand command = HeartbeatCommand.parseFrom(requestPayload); + + long serverTime = System.currentTimeMillis(); + + logger.debug("Heartbeat received. Client T1={}, Server T2={}", + command.getClientTimestamp(), serverTime); + + HeartbeatResponse response = HeartbeatResponse.newBuilder() + .setClientTimestamp(command.getClientTimestamp()) // Echo T1 + .setServerTimestamp(serverTime) // T2 + .build(); + + return TransportProtocol.Response.newBuilder() + .setPayload(ByteString.copyFrom(response.toByteArray())) + .setStatusCode(200) + .putHeaders("Content-Type", "application/x-protobuf"); + } +} \ No newline at end of file diff --git a/src/main/java/com/mediamanager/service/ipc/IPCManager.java b/src/main/java/com/mediamanager/service/ipc/IPCManager.java index 17ff8b1..00ed070 100644 --- a/src/main/java/com/mediamanager/service/ipc/IPCManager.java +++ b/src/main/java/com/mediamanager/service/ipc/IPCManager.java @@ -185,7 +185,7 @@ public class IPCManager { // Nenhum cliente conectado no momento // Dorme por um curto período antes de verificar novamente // Isso evita consumir CPU desnecessariamente em um loop vazio - Thread.sleep(100); // 100 milissegundos + Thread.sleep(1); // 1 milissegundos } } catch (InterruptedException e) { diff --git a/src/main/proto/test.proto b/src/main/proto/test.proto new file mode 100644 index 0000000..ea4bf62 --- /dev/null +++ b/src/main/proto/test.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +option java_package = "com.mediamanager.protocol"; +option java_outer_classname = "TestProtocol"; + +package mediamanager.test; + +message EchoCommand { + string message = 1; +} + +message EchoResponse { + string message = 1; + int64 server_timestamp = 2; +} + +message HeartbeatCommand { + int64 client_timestamp = 1; +} + +message HeartbeatResponse { + int64 client_timestamp = 1; + int64 server_timestamp = 2; +} \ No newline at end of file diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 39be1a8..a9f3f84 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -18,17 +18,17 @@ - + - + - + From 269780d8cfa12f83dc000ba2b56dbb99b5296938 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Sat, 22 Nov 2025 23:27:04 -0300 Subject: [PATCH 15/39] Add `CloseHandler` to support graceful connection termination. - Extend `test.proto` with `CloseCommand` and `CloseResponse` messages. - Introduce `CloseHandler` to process "close" actions and respond with connection termination notice. - Update `DelegateActionManager` to register `CloseHandler`. - Refactor `IPCManager` to handle "close" response headers and terminate client connections gracefully. --- .../delegate/DelegateActionManager.java | 2 ++ .../delegate/handler/CloseHandler.java | 32 ++++++++++++++++++ .../service/delegate/handler/EchoHandler.java | 4 +-- .../delegate/handler/HeartbeatHandler.java | 4 +-- .../mediamanager/service/ipc/IPCManager.java | 33 +++++++++++++++---- src/main/proto/test.proto | 6 ++++ 6 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/CloseHandler.java diff --git a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java index 1d2c0a6..5909c9c 100644 --- a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java +++ b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java @@ -2,6 +2,7 @@ package com.mediamanager.service.delegate; import com.google.protobuf.ByteString; import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.service.delegate.handler.CloseHandler; import com.mediamanager.service.delegate.handler.EchoHandler; import com.mediamanager.service.delegate.handler.HeartbeatHandler; import org.apache.logging.log4j.LogManager; @@ -25,6 +26,7 @@ public class DelegateActionManager { private void registerHandlers() { handlerRegistry.put("echo",new EchoHandler()); handlerRegistry.put("heartbeat",new HeartbeatHandler()); + handlerRegistry.put("close", new CloseHandler()); } public void start(){ diff --git a/src/main/java/com/mediamanager/service/delegate/handler/CloseHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/CloseHandler.java new file mode 100644 index 0000000..de59bf3 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/CloseHandler.java @@ -0,0 +1,32 @@ +package com.mediamanager.service.delegate.handler; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.protocol.TestProtocol.CloseCommand; +import com.mediamanager.protocol.TestProtocol.CloseResponse; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.service.delegate.ActionHandler; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class CloseHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(CloseHandler.class); + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException { + + CloseCommand.parseFrom(requestPayload); // Valida + + logger.info("Close command received - connection will close"); + + CloseResponse response = CloseResponse.newBuilder() + .setMessage("Connection closing. Goodbye!") + .build(); + + return TransportProtocol.Response.newBuilder() + .setPayload(ByteString.copyFrom(response.toByteArray())) + .setStatusCode(200) + .putHeaders("Connection", "close"); // ← Marca para fechar + } +} \ No newline at end of file diff --git a/src/main/java/com/mediamanager/service/delegate/handler/EchoHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/EchoHandler.java index 4945efc..1e7ece9 100644 --- a/src/main/java/com/mediamanager/service/delegate/handler/EchoHandler.java +++ b/src/main/java/com/mediamanager/service/delegate/handler/EchoHandler.java @@ -33,7 +33,7 @@ public class EchoHandler implements ActionHandler { // 4. Retorna Response return TransportProtocol.Response.newBuilder() .setPayload(responsePayload) - .setStatusCode(200) - .putHeaders("Content-Type", "application/x-protobuf"); + .setStatusCode(200); + } } diff --git a/src/main/java/com/mediamanager/service/delegate/handler/HeartbeatHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/HeartbeatHandler.java index c385433..20e3d6d 100644 --- a/src/main/java/com/mediamanager/service/delegate/handler/HeartbeatHandler.java +++ b/src/main/java/com/mediamanager/service/delegate/handler/HeartbeatHandler.java @@ -30,7 +30,7 @@ public class HeartbeatHandler implements ActionHandler { return TransportProtocol.Response.newBuilder() .setPayload(ByteString.copyFrom(response.toByteArray())) - .setStatusCode(200) - .putHeaders("Content-Type", "application/x-protobuf"); + .setStatusCode(200); + } } \ No newline at end of file diff --git a/src/main/java/com/mediamanager/service/ipc/IPCManager.java b/src/main/java/com/mediamanager/service/ipc/IPCManager.java index 00ed070..2115066 100644 --- a/src/main/java/com/mediamanager/service/ipc/IPCManager.java +++ b/src/main/java/com/mediamanager/service/ipc/IPCManager.java @@ -223,9 +223,16 @@ public class IPCManager { logger.debug("Client {} handler thread started", clientId); try { - TransportProtocol.Request request = readRequest(channel); + // LOOP: processa múltiplas requests na mesma conexão + while (channel.isOpen()) { + TransportProtocol.Request request = readRequest(channel); + + if (request == null) { + // Cliente desconectou gracefully + logger.info("Client {} disconnected (end of stream)", clientId); + break; + } - if (request != null) { logger.info("Client {} sent request {}", clientId, request.getRequestId()); // Processa usando o DelegateActionManager @@ -233,17 +240,29 @@ public class IPCManager { // Envia resposta de volta writeResponse(channel, response); - logger.info("Client {} response sent", clientId); - } else { - logger.warn("Client {} sent null message", clientId); + + // Verifica se é comando CLOSE + String connectionHeader = response.getHeadersOrDefault("Connection", ""); + if ("close".equals(connectionHeader)) { + logger.info("Client {} requested connection close", clientId); + break; // Sai do loop e fecha + } } + } catch (IOException e) { + if (channel.isOpen()) { + logger.error("IO error handling client {}", clientId, e); + } else { + logger.debug("Client {} connection closed by peer", clientId); + } } catch (Exception e) { - logger.error("Error handling client {}", clientId, e); + logger.error("Unexpected error handling client {}", clientId, e); } finally { try { - channel.close(); + if (channel.isOpen()) { + channel.close(); + } logger.debug("Client {} channel closed", clientId); } catch (IOException e) { logger.error("Error closing client {} channel", clientId, e); diff --git a/src/main/proto/test.proto b/src/main/proto/test.proto index ea4bf62..00330cd 100644 --- a/src/main/proto/test.proto +++ b/src/main/proto/test.proto @@ -21,4 +21,10 @@ message HeartbeatCommand { message HeartbeatResponse { int64 client_timestamp = 1; int64 server_timestamp = 2; +} +message CloseCommand { + // Vazio - apenas sinaliza fechamento +} +message CloseResponse { + string message = 1; } \ No newline at end of file From 53004a3730fa77d4fa45145883d02acfb26d4833 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Mon, 24 Nov 2025 21:59:35 -0300 Subject: [PATCH 16/39] Fix the pom.xml to alter the postgres dependency to a safer version due a CVE in the 42.7.5 then it was bumped t0 42.7.7 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b0ed9b5..c266201 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ 17 UTF-8 7.1.7.Final - 42.7.5 + 42.7.7 5.1.0 2.23.1 5.10.2 From 4ae1e3075df13bae2cf965199ad146a836773cc1 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Thu, 27 Nov 2025 18:40:08 -0300 Subject: [PATCH 17/39] Remove `Media` entity and integrate Hibernate ORM into `DatabaseManager`: - Deleted `Media` class and its annotations. - Added Hibernate ORM initialization in `DatabaseManager`, including dynamic entity scanning and configuration. - Updated `pom.xml` to include dependencies for Hibernate community dialects and Reflections. --- pom.xml | 11 ++ .../java/com/mediamanager/model/Media.java | 128 ------------------ .../service/database/DatabaseManager.java | 54 +++++++- .../database/SqliteDatabaseManager.java | 4 +- 4 files changed, 67 insertions(+), 130 deletions(-) delete mode 100644 src/main/java/com/mediamanager/model/Media.java diff --git a/pom.xml b/pom.xml index 6b2ee1e..8846994 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,12 @@ ${hibernate.version} + + org.hibernate.orm + hibernate-community-dialects + ${hibernate.version} + + com.zaxxer @@ -88,6 +94,11 @@ sqlite-jdbc 3.44.1.0 + + org.reflections + reflections + 0.10.2 + diff --git a/src/main/java/com/mediamanager/model/Media.java b/src/main/java/com/mediamanager/model/Media.java deleted file mode 100644 index 99a455d..0000000 --- a/src/main/java/com/mediamanager/model/Media.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.mediamanager.model; - -import jakarta.persistence.*; -import java.time.LocalDateTime; - -@Entity -@Table(name = "media") -public class Media { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(nullable = false) - private String title; - - @Column(nullable = false, unique = true) - private String filePath; - - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private MediaType type; - - @Column - private Long fileSize; - - @Column - private String mimeType; - - @Column(name = "created_at", nullable = false, updatable = false) - private LocalDateTime createdAt; - - @Column(name = "updated_at") - private LocalDateTime updatedAt; - - @PrePersist - protected void onCreate() { - createdAt = LocalDateTime.now(); - updatedAt = LocalDateTime.now(); - } - - @PreUpdate - protected void onUpdate() { - updatedAt = LocalDateTime.now(); - } - - // Constructors - public Media() {} - - public Media(String title, String filePath, MediaType type) { - this.title = title; - this.filePath = filePath; - this.type = type; - } - - // Getters and Setters - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getFilePath() { - return filePath; - } - - public void setFilePath(String filePath) { - this.filePath = filePath; - } - - public MediaType getType() { - return type; - } - - public void setType(MediaType type) { - this.type = type; - } - - public Long getFileSize() { - return fileSize; - } - - public void setFileSize(Long fileSize) { - this.fileSize = fileSize; - } - - public String getMimeType() { - return mimeType; - } - - public void setMimeType(String mimeType) { - this.mimeType = mimeType; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public LocalDateTime getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } - - public enum MediaType { - VIDEO, - AUDIO, - IMAGE, - DOCUMENT, - OTHER - } -} diff --git a/src/main/java/com/mediamanager/service/database/DatabaseManager.java b/src/main/java/com/mediamanager/service/database/DatabaseManager.java index 34f09d5..58ba7e4 100644 --- a/src/main/java/com/mediamanager/service/database/DatabaseManager.java +++ b/src/main/java/com/mediamanager/service/database/DatabaseManager.java @@ -1,16 +1,26 @@ package com.mediamanager.service.database; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; - +import org.hibernate.cfg.Configuration; +import org.reflections.Reflections; +import org.reflections.scanners.Scanners; +import jakarta.persistence.Entity; +import java.util.Set; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; + public abstract class DatabaseManager { protected final Properties configuration; protected Connection connection; + protected String connectionUrl; + protected EntityManagerFactory entityManagerFactory; + protected EntityManager entityManager; protected static final Logger logger = LogManager.getLogger(DatabaseManager.class); public DatabaseManager(Properties config) { @@ -50,6 +60,7 @@ public abstract class DatabaseManager { logger.debug("No database connection to close"); } } + protected boolean testConnection() { try (Statement stmt = connection.createStatement()) { stmt.execute("SELECT 1"); @@ -59,4 +70,45 @@ public abstract class DatabaseManager { return false; } } + protected void initializeHibernate() { + logger.info("Initializing Hibernate ORM..."); + + Configuration hibernateConfig = new Configuration(); + + // DEBUG PRIMEIRO - antes de usar as propriedades + String dialect = configuration.getProperty("hibernate.dialect"); + String hbm2ddl = configuration.getProperty("hibernate.hbm2ddl.auto"); + String driver = configuration.getProperty("database.driver"); + + logger.info("DEBUG - dialect: {}", dialect); + logger.info("DEBUG - hbm2ddl: {}", hbm2ddl); + logger.info("DEBUG - driver: {}", driver); + logger.info("DEBUG - connectionUrl: {}", connectionUrl); + + // Agora usa as propriedades + hibernateConfig.setProperty("hibernate.connection.url", connectionUrl); + hibernateConfig.setProperty("hibernate.connection.driver_class", driver); + hibernateConfig.setProperty("hibernate.dialect", dialect); + hibernateConfig.setProperty("hibernate.hbm2ddl.auto", hbm2ddl); + hibernateConfig.setProperty("hibernate.show_sql", + configuration.getProperty("hibernate.show_sql", "false")); + hibernateConfig.setProperty("hibernate.format_sql", + configuration.getProperty("hibernate.format_sql", "true")); + + logger.info("Scanning for entities in package: com.mediamanager.model"); + Reflections reflections = new Reflections("com.mediamanager.model", Scanners.TypesAnnotated); + Set> entityClasses = reflections.getTypesAnnotatedWith(Entity.class); + + logger.info("Found {} entities", entityClasses.size()); + for (Class entityClass : entityClasses) { + logger.debug("Registering entity: {}", entityClass.getSimpleName()); + hibernateConfig.addAnnotatedClass(entityClass); + } + + // Criar EntityManagerFactory + entityManagerFactory = hibernateConfig.buildSessionFactory().unwrap(EntityManagerFactory.class); + entityManager = entityManagerFactory.createEntityManager(); + + logger.info("Hibernate ORM initialized successfully"); + } } \ No newline at end of file diff --git a/src/main/java/com/mediamanager/service/database/SqliteDatabaseManager.java b/src/main/java/com/mediamanager/service/database/SqliteDatabaseManager.java index 02d3ecc..dc98c91 100644 --- a/src/main/java/com/mediamanager/service/database/SqliteDatabaseManager.java +++ b/src/main/java/com/mediamanager/service/database/SqliteDatabaseManager.java @@ -16,7 +16,7 @@ public class SqliteDatabaseManager extends DatabaseManager { private static final Logger logger = LogManager.getLogger(SqliteDatabaseManager.class); - private String connectionUrl; + public SqliteDatabaseManager(Properties config) { super(config); @@ -45,12 +45,14 @@ public class SqliteDatabaseManager extends DatabaseManager { this.connectionUrl = "jdbc:sqlite:" + dbFile.toAbsolutePath().toString(); logger.info("Database file path: {}", dbFile); logger.info("Connection URL: {}", this.connectionUrl); + initializeHibernate(); this.connection = createConnection(); configurePerformancePragmas(); performSanityChecks(); ensureSchemaExists(); logger.info("SQLite database initialized successfully"); + } @Override From bc0199312f825e9e51bc9623cffaf03603e6424a Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Thu, 27 Nov 2025 18:55:43 -0300 Subject: [PATCH 18/39] Add null and open-state checks for EntityManager and EntityManagerFactory closure - Ensure safe closure of `EntityManager` and `EntityManagerFactory`. - Add logging for successful closure and error handling during exceptions. --- .../service/database/DatabaseManager.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/com/mediamanager/service/database/DatabaseManager.java b/src/main/java/com/mediamanager/service/database/DatabaseManager.java index 58ba7e4..ae9e934 100644 --- a/src/main/java/com/mediamanager/service/database/DatabaseManager.java +++ b/src/main/java/com/mediamanager/service/database/DatabaseManager.java @@ -48,6 +48,22 @@ public abstract class DatabaseManager { } public void close() { + if (entityManager != null && entityManager.isOpen()) { + try { + entityManager.close(); + logger.info("EntityManager closed"); + } catch (Exception e) { + logger.error("Error closing EntityManager: {}", e.getMessage()); + } + } + if (entityManagerFactory != null && entityManagerFactory.isOpen()) { + try { + entityManagerFactory.close(); + logger.info("EntityManagerFactory closed"); + } catch (Exception e) { + logger.error("Error closing EntityManagerFactory: {}", e.getMessage()); + } + } if (connection != null) { try { logger.info("Closing database connection..."); From 412a590a98e682d199d99c6f8b15a13d271fe671 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Thu, 27 Nov 2025 19:04:44 -0300 Subject: [PATCH 19/39] Add exception handling for Hibernate initialization in DatabaseManager - Wrap Hibernate setup in a try-catch block. - Log errors during initialization failure with descriptive messages. - Throw a runtime exception if setup fails to ensure proper error propagation. --- .../mediamanager/service/database/DatabaseManager.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/mediamanager/service/database/DatabaseManager.java b/src/main/java/com/mediamanager/service/database/DatabaseManager.java index ae9e934..808b9e8 100644 --- a/src/main/java/com/mediamanager/service/database/DatabaseManager.java +++ b/src/main/java/com/mediamanager/service/database/DatabaseManager.java @@ -121,9 +121,13 @@ public abstract class DatabaseManager { hibernateConfig.addAnnotatedClass(entityClass); } - // Criar EntityManagerFactory - entityManagerFactory = hibernateConfig.buildSessionFactory().unwrap(EntityManagerFactory.class); - entityManager = entityManagerFactory.createEntityManager(); + try { + entityManagerFactory = hibernateConfig.buildSessionFactory().unwrap(EntityManagerFactory.class); + entityManager = entityManagerFactory.createEntityManager(); + } catch (Exception e) { + logger.error("Failed to initialize Hibernate: {}", e.getMessage()); + throw new RuntimeException("Hibernate initialization failed", e); + } logger.info("Hibernate ORM initialized successfully"); } From e395e21e8cef583b0f8449788fa2508513f0a095 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Thu, 27 Nov 2025 19:08:11 -0300 Subject: [PATCH 20/39] Add validation for required Hibernate configuration properties in `DatabaseManager` - Ensure non-null and non-empty values for `hibernate.dialect`, `database.driver`, and `connectionUrl`. - Throw `IllegalStateException` when required properties are missing. --- .../service/database/DatabaseManager.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/mediamanager/service/database/DatabaseManager.java b/src/main/java/com/mediamanager/service/database/DatabaseManager.java index 808b9e8..7c2c102 100644 --- a/src/main/java/com/mediamanager/service/database/DatabaseManager.java +++ b/src/main/java/com/mediamanager/service/database/DatabaseManager.java @@ -91,17 +91,22 @@ public abstract class DatabaseManager { Configuration hibernateConfig = new Configuration(); - // DEBUG PRIMEIRO - antes de usar as propriedades + String dialect = configuration.getProperty("hibernate.dialect"); String hbm2ddl = configuration.getProperty("hibernate.hbm2ddl.auto"); String driver = configuration.getProperty("database.driver"); - logger.info("DEBUG - dialect: {}", dialect); - logger.info("DEBUG - hbm2ddl: {}", hbm2ddl); - logger.info("DEBUG - driver: {}", driver); - logger.info("DEBUG - connectionUrl: {}", connectionUrl); + if (dialect == null || dialect.isEmpty()) { + throw new IllegalStateException("hibernate.dialect property is required but not configured"); + } + if (driver == null || driver.isEmpty()) { + throw new IllegalStateException("database.driver property is required but not configured"); + } + if (connectionUrl == null || connectionUrl.isEmpty()) { + throw new IllegalStateException("connectionUrl must be set before initializing Hibernate"); + } + - // Agora usa as propriedades hibernateConfig.setProperty("hibernate.connection.url", connectionUrl); hibernateConfig.setProperty("hibernate.connection.driver_class", driver); hibernateConfig.setProperty("hibernate.dialect", dialect); From 081a1f1ed47fb26f2b7f575f4a67bcde3cbaac82 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Thu, 27 Nov 2025 19:20:22 -0300 Subject: [PATCH 21/39] Add exception handling for entity scanning in `DatabaseManager` - Wrap entity scanning in a try-catch block to handle potential exceptions. - Log descriptive error messages and throw a runtime exception for failure cases. - Warn if no `@Entity` classes are found during the scan. --- .../service/database/DatabaseManager.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/mediamanager/service/database/DatabaseManager.java b/src/main/java/com/mediamanager/service/database/DatabaseManager.java index 7c2c102..4c0375b 100644 --- a/src/main/java/com/mediamanager/service/database/DatabaseManager.java +++ b/src/main/java/com/mediamanager/service/database/DatabaseManager.java @@ -117,10 +117,20 @@ public abstract class DatabaseManager { configuration.getProperty("hibernate.format_sql", "true")); logger.info("Scanning for entities in package: com.mediamanager.model"); - Reflections reflections = new Reflections("com.mediamanager.model", Scanners.TypesAnnotated); - Set> entityClasses = reflections.getTypesAnnotatedWith(Entity.class); + + Set> entityClasses; + try { + Reflections reflections = new Reflections("com.mediamanager.model", Scanners.TypesAnnotated); + entityClasses = reflections.getTypesAnnotatedWith(Entity.class); + } catch (Exception e) { + logger.error("Failed to scan for entities: {}", e.getMessage()); + throw new RuntimeException("Entity scanning failed", e); + } logger.info("Found {} entities", entityClasses.size()); + if (entityClasses.isEmpty()) { + logger.warn("No @Entity classes found in package com.mediamanager.model - is this expected?"); + } for (Class entityClass : entityClasses) { logger.debug("Registering entity: {}", entityClass.getSimpleName()); hibernateConfig.addAnnotatedClass(entityClass); From 7b61b2071e0714b8b292afae8ec964ace5b62cc4 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Fri, 28 Nov 2025 23:33:27 -0300 Subject: [PATCH 22/39] Implement Genre Management with CRUD Operations - Add `Genre` entity with JPA annotations and database mapping. - Create repository, service, and delegate handlers for Genre CRUD operations. - Register handlers (`create_genre`, `get_genres`, `get_genre_by_id`, `update_genre`, `delete_genre`) in `DelegateActionManager`. - Introduce Protobuf definitions for Genre messages. - Update `DatabaseManager` to expose `EntityManager`. - Enhance `DelegateActionManager` constructor to use `EntityManager` for handler initialization. --- .../mediamanager/MediaManagerApplication.java | 2 +- .../com/mediamanager/mapper/GenreMapper.java | 34 ++++++ .../java/com/mediamanager/model/Genre.java | 30 ++++++ .../repository/GenreRepository.java | 101 ++++++++++++++++++ .../service/database/DatabaseManager.java | 4 + .../delegate/DelegateActionManager.java | 20 +++- .../handler/genre/CreateGenreHandler.java | 60 +++++++++++ .../handler/genre/DeleteGenreHandler.java | 70 ++++++++++++ .../handler/genre/GetGenreByIdHandler.java | 64 +++++++++++ .../handler/genre/GetGenreHandler.java | 54 ++++++++++ .../handler/genre/UpdateGenreHandler.java | 71 ++++++++++++ .../service/genre/GenreService.java | 88 +++++++++++++++ src/main/proto/genre.proto | 53 +++++++++ 13 files changed, 647 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/mediamanager/mapper/GenreMapper.java create mode 100644 src/main/java/com/mediamanager/model/Genre.java create mode 100644 src/main/java/com/mediamanager/repository/GenreRepository.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/genre/CreateGenreHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/genre/DeleteGenreHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreByIdHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/genre/UpdateGenreHandler.java create mode 100644 src/main/java/com/mediamanager/service/genre/GenreService.java create mode 100644 src/main/proto/genre.proto diff --git a/src/main/java/com/mediamanager/MediaManagerApplication.java b/src/main/java/com/mediamanager/MediaManagerApplication.java index b9579dc..3a85f6f 100644 --- a/src/main/java/com/mediamanager/MediaManagerApplication.java +++ b/src/main/java/com/mediamanager/MediaManagerApplication.java @@ -60,7 +60,7 @@ public class MediaManagerApplication { default: } databaseManager.init(); - actionManager = new DelegateActionManager(); + actionManager = new DelegateActionManager(databaseManager.getEntityManager()); actionManager.start(); ipcManager = new IPCManager(config,actionManager); ipcManager.init(); diff --git a/src/main/java/com/mediamanager/mapper/GenreMapper.java b/src/main/java/com/mediamanager/mapper/GenreMapper.java new file mode 100644 index 0000000..ceae850 --- /dev/null +++ b/src/main/java/com/mediamanager/mapper/GenreMapper.java @@ -0,0 +1,34 @@ +package com.mediamanager.mapper; + +import com.mediamanager.model.Genre; +import com.mediamanager.protocol.messages.GenreMessages; + +public class GenreMapper { + public static GenreMessages.Genre toProtobuf(Genre entity) { + if (entity == null) { + return null; + } + + return GenreMessages.Genre.newBuilder() + .setId(entity.getId()) + .setName(entity.getName()) + .build(); + } + public static Genre toEntity(GenreMessages.Genre protobuf) { + if (protobuf == null) { + return null; + } + + Genre entity = new Genre(); + + // Só seta ID se for > 0 (protobuf default é 0) + if (protobuf.getId() > 0) { + entity.setId(protobuf.getId()); + } + + entity.setName(protobuf.getName()); + + return entity; + } +} + diff --git a/src/main/java/com/mediamanager/model/Genre.java b/src/main/java/com/mediamanager/model/Genre.java new file mode 100644 index 0000000..b2a736b --- /dev/null +++ b/src/main/java/com/mediamanager/model/Genre.java @@ -0,0 +1,30 @@ +package com.mediamanager.model; + +import jakarta.persistence.*; + +@Entity +@Table(name = "genre") +public class Genre { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Column(nullable = false) + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/com/mediamanager/repository/GenreRepository.java b/src/main/java/com/mediamanager/repository/GenreRepository.java new file mode 100644 index 0000000..6279caa --- /dev/null +++ b/src/main/java/com/mediamanager/repository/GenreRepository.java @@ -0,0 +1,101 @@ +package com.mediamanager.repository; + +import com.mediamanager.model.Genre; +import jakarta.persistence.EntityManager; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.Optional; + +/** + * Repository para acesso a dados de Genre + * Encapsula todas as operações de banco de dados + */ +public class GenreRepository { + private static final Logger logger = LogManager.getLogger(GenreRepository.class); + + private final EntityManager entityManager; + + public GenreRepository(EntityManager entityManager) { + this.entityManager = entityManager; + } + + /** + * Salva um novo genre + */ + public Genre save(Genre genre) { + logger.debug("Saving genre: {}", genre.getName()); + entityManager.getTransaction().begin(); + try { + entityManager.persist(genre); + entityManager.getTransaction().commit(); + logger.debug("Genre saved with ID: {}", genre.getId()); + return genre; + } catch (Exception e) { + entityManager.getTransaction().rollback(); + logger.error("Error saving genre", e); + throw e; + } + } + + /** + * Busca todos os genres + */ + public List findAll() { + logger.debug("Finding all genres"); + return entityManager + .createQuery("SELECT g FROM Genre g ORDER BY g.name", Genre.class) + .getResultList(); + } + + /** + * Busca genre por ID + */ + public Optional findById(Integer id) { + logger.debug("Finding genre by ID: {}", id); + Genre genre = entityManager.find(Genre.class, id); + return Optional.ofNullable(genre); + } + + /** + * Atualiza um genre existente + */ + public Genre update(Genre genre) { + logger.debug("Updating genre ID: {}", genre.getId()); + entityManager.getTransaction().begin(); + try { + Genre updated = entityManager.merge(genre); + entityManager.getTransaction().commit(); + logger.debug("Genre updated successfully"); + return updated; + } catch (Exception e) { + entityManager.getTransaction().rollback(); + logger.error("Error updating genre", e); + throw e; + } + } + + /** + * Deleta um genre por ID + */ + public boolean deleteById(Integer id) { + logger.debug("Deleting genre by ID: {}", id); + entityManager.getTransaction().begin(); + try { + Genre genre = entityManager.find(Genre.class, id); + if (genre == null) { + entityManager.getTransaction().rollback(); + return false; + } + entityManager.remove(genre); + entityManager.getTransaction().commit(); + logger.debug("Genre deleted successfully"); + return true; + } catch (Exception e) { + entityManager.getTransaction().rollback(); + logger.error("Error deleting genre", e); + throw e; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/mediamanager/service/database/DatabaseManager.java b/src/main/java/com/mediamanager/service/database/DatabaseManager.java index 4c0375b..2d9c7eb 100644 --- a/src/main/java/com/mediamanager/service/database/DatabaseManager.java +++ b/src/main/java/com/mediamanager/service/database/DatabaseManager.java @@ -146,4 +146,8 @@ public abstract class DatabaseManager { logger.info("Hibernate ORM initialized successfully"); } + + public EntityManager getEntityManager() { + return entityManager; + } } \ No newline at end of file diff --git a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java index 5909c9c..d05de4e 100644 --- a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java +++ b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java @@ -2,9 +2,13 @@ package com.mediamanager.service.delegate; import com.google.protobuf.ByteString; import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.repository.GenreRepository; import com.mediamanager.service.delegate.handler.CloseHandler; import com.mediamanager.service.delegate.handler.EchoHandler; import com.mediamanager.service.delegate.handler.HeartbeatHandler; +import com.mediamanager.service.delegate.handler.genre.*; +import com.mediamanager.service.genre.GenreService; +import jakarta.persistence.EntityManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -15,9 +19,14 @@ public class DelegateActionManager { private static final Logger logger = LogManager.getLogger(DelegateActionManager.class); private final Map handlerRegistry; - - public DelegateActionManager() { - + private final EntityManager entityManager; + private final GenreService genreService; + + public DelegateActionManager(EntityManager entityManager) { + this.entityManager = entityManager; + + GenreRepository genreRepository = new GenreRepository(entityManager); + this.genreService = new GenreService(genreRepository); logger.debug("DelegateActionManager created"); this.handlerRegistry = new HashMap<>(); registerHandlers(); @@ -27,6 +36,11 @@ public class DelegateActionManager { handlerRegistry.put("echo",new EchoHandler()); handlerRegistry.put("heartbeat",new HeartbeatHandler()); handlerRegistry.put("close", new CloseHandler()); + handlerRegistry.put("create_genre", new CreateGenreHandler(genreService)); + handlerRegistry.put("get_genres", new GetGenreHandler(genreService)); + handlerRegistry.put("get_genre_by_id", new GetGenreByIdHandler(genreService)); + handlerRegistry.put("update_genre", new UpdateGenreHandler(genreService)); + handlerRegistry.put("delete_genre", new DeleteGenreHandler(genreService)); } public void start(){ diff --git a/src/main/java/com/mediamanager/service/delegate/handler/genre/CreateGenreHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/genre/CreateGenreHandler.java new file mode 100644 index 0000000..93b530a --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/genre/CreateGenreHandler.java @@ -0,0 +1,60 @@ +package com.mediamanager.service.delegate.handler.genre; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.GenreMapper; +import com.mediamanager.model.Genre; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.GenreMessages; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.genre.GenreService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class CreateGenreHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(CreateGenreHandler.class); + + private final GenreService genreService; + + public CreateGenreHandler(GenreService genreService) { + this.genreService = genreService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException { + try { + // 1. Parse protobuf request + GenreMessages.CreateGenreRequest createRequest = + GenreMessages.CreateGenreRequest.parseFrom(requestPayload); + + // 2. Chama service (lógica de negócio) + Genre genre = genreService.createGenre(createRequest.getName()); + + // 3. Converte entity para protobuf + GenreMessages.Genre genreProto = GenreMapper.toProtobuf(genre); + + // 4. Cria response protobuf + GenreMessages.CreateGenreResponse createResponse = + GenreMessages.CreateGenreResponse.newBuilder() + .setGenre(genreProto) + .build(); + + // 5. Retorna + return TransportProtocol.Response.newBuilder() + .setPayload(createResponse.toByteString()); + + } catch (IllegalArgumentException e) { + logger.error("Validation error", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(400) + .setPayload(ByteString.copyFromUtf8("Validation error: " + e.getMessage())); + + } catch (Exception e) { + logger.error("Error creating genre", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/mediamanager/service/delegate/handler/genre/DeleteGenreHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/genre/DeleteGenreHandler.java new file mode 100644 index 0000000..61f90df --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/genre/DeleteGenreHandler.java @@ -0,0 +1,70 @@ +package com.mediamanager.service.delegate.handler.genre; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.GenreMessages; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.genre.GenreService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class DeleteGenreHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(DeleteGenreHandler.class); + + private final GenreService genreService; + + public DeleteGenreHandler(GenreService genreService) { + this.genreService = genreService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException { + try { + // 1. Parse protobuf request + GenreMessages.DeleteGenreRequest deleteRequest = + GenreMessages.DeleteGenreRequest.parseFrom(requestPayload); + + int id = deleteRequest.getId(); + + // 2. Deleta via service + boolean success = genreService.deleteGenre(id); + + // 3. Cria response + GenreMessages.DeleteGenreResponse deleteResponse; + + if (success) { + deleteResponse = GenreMessages.DeleteGenreResponse.newBuilder() + .setSuccess(true) + .setMessage("Genre deleted successfully") + .build(); + + return TransportProtocol.Response.newBuilder() + .setPayload(deleteResponse.toByteString()); + } else { + deleteResponse = GenreMessages.DeleteGenreResponse.newBuilder() + .setSuccess(false) + .setMessage("Genre not found") + .build(); + + return TransportProtocol.Response.newBuilder() + .setStatusCode(404) + .setPayload(deleteResponse.toByteString()); + } + + } catch (Exception e) { + logger.error("Error deleting genre", e); + + GenreMessages.DeleteGenreResponse deleteResponse = + GenreMessages.DeleteGenreResponse.newBuilder() + .setSuccess(false) + .setMessage("Error: " + e.getMessage()) + .build(); + + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(deleteResponse.toByteString()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreByIdHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreByIdHandler.java new file mode 100644 index 0000000..77ce9e3 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreByIdHandler.java @@ -0,0 +1,64 @@ +package com.mediamanager.service.delegate.handler.genre; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.GenreMapper; +import com.mediamanager.model.Genre; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.GenreMessages; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.genre.GenreService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +public class GetGenreByIdHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(GetGenreByIdHandler.class); + + private final GenreService genreService; + + public GetGenreByIdHandler(GenreService genreService) { + this.genreService = genreService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException { + try { + // 1. Parse protobuf request + GenreMessages.GetGenreByIdRequest getByIdRequest = + GenreMessages.GetGenreByIdRequest.parseFrom(requestPayload); + + int id = getByIdRequest.getId(); + + // 2. Busca via service + Optional genreOpt = genreService.getGenreById(id); + + if (genreOpt.isEmpty()) { + logger.warn("Genre not found with ID: {}", id); + return TransportProtocol.Response.newBuilder() + .setStatusCode(404) + .setPayload(ByteString.copyFromUtf8("Genre not found")); + } + + // 3. Converte para protobuf + GenreMessages.Genre genreProto = GenreMapper.toProtobuf(genreOpt.get()); + + GenreMessages.GetGenreByIdResponse getByIdResponse = + GenreMessages.GetGenreByIdResponse.newBuilder() + .setGenre(genreProto) + .build(); + + // 4. Retorna + return TransportProtocol.Response.newBuilder() + .setPayload(getByIdResponse.toByteString()); + + } catch (Exception e) { + logger.error("Error getting genre by ID", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreHandler.java new file mode 100644 index 0000000..1cb3c94 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreHandler.java @@ -0,0 +1,54 @@ +package com.mediamanager.service.delegate.handler.genre; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.GenreMapper; +import com.mediamanager.model.Genre; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.GenreMessages; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.genre.GenreService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; + +public class GetGenreHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(GetGenreHandler.class); + + private final GenreService genreService; + + public GetGenreHandler(GenreService genreService) { + this.genreService = genreService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException { + try { + // 1. Busca todos os genres via service + List genres = genreService.getAllGenres(); + + // 2. Converte cada Genre para protobuf + GenreMessages.GetGenresResponse.Builder responseBuilder = + GenreMessages.GetGenresResponse.newBuilder(); + + for (Genre genre : genres) { + GenreMessages.Genre genreProto = GenreMapper.toProtobuf(genre); + responseBuilder.addGenres(genreProto); + } + + GenreMessages.GetGenresResponse getGenresResponse = responseBuilder.build(); + + // 3. Retorna + return TransportProtocol.Response.newBuilder() + .setPayload(getGenresResponse.toByteString()); + + } catch (Exception e) { + logger.error("Error getting genres", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/mediamanager/service/delegate/handler/genre/UpdateGenreHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/genre/UpdateGenreHandler.java new file mode 100644 index 0000000..919f50c --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/genre/UpdateGenreHandler.java @@ -0,0 +1,71 @@ +package com.mediamanager.service.delegate.handler.genre; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.GenreMapper; +import com.mediamanager.model.Genre; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.GenreMessages; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.genre.GenreService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +public class UpdateGenreHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(UpdateGenreHandler.class); + + private final GenreService genreService; + + public UpdateGenreHandler(GenreService genreService) { + this.genreService = genreService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException { + try { + // 1. Parse protobuf request + GenreMessages.UpdateGenreRequest updateRequest = + GenreMessages.UpdateGenreRequest.parseFrom(requestPayload); + + int id = updateRequest.getId(); + String newName = updateRequest.getName(); + + // 2. Atualiza via service + Optional genreOpt = genreService.updateGenre(id, newName); + + if (genreOpt.isEmpty()) { + logger.warn("Genre not found with ID: {}", id); + return TransportProtocol.Response.newBuilder() + .setStatusCode(404) + .setPayload(ByteString.copyFromUtf8("Genre not found")); + } + + // 3. Converte para protobuf + GenreMessages.Genre genreProto = GenreMapper.toProtobuf(genreOpt.get()); + + GenreMessages.UpdateGenreResponse updateResponse = + GenreMessages.UpdateGenreResponse.newBuilder() + .setGenre(genreProto) + .build(); + + // 4. Retorna + return TransportProtocol.Response.newBuilder() + .setPayload(updateResponse.toByteString()); + + } catch (IllegalArgumentException e) { + logger.error("Validation error", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(400) + .setPayload(ByteString.copyFromUtf8("Validation error: " + e.getMessage())); + + } catch (Exception e) { + logger.error("Error updating genre", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/mediamanager/service/genre/GenreService.java b/src/main/java/com/mediamanager/service/genre/GenreService.java new file mode 100644 index 0000000..f0acf70 --- /dev/null +++ b/src/main/java/com/mediamanager/service/genre/GenreService.java @@ -0,0 +1,88 @@ +package com.mediamanager.service.genre; + +import com.mediamanager.model.Genre; +import com.mediamanager.repository.GenreRepository; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.Optional; + +/** + * Service para lógica de negócio relacionada a Genre + */ +public class GenreService { + private static final Logger logger = LogManager.getLogger(GenreService.class); + + private final GenreRepository genreRepository; + + public GenreService(GenreRepository genreRepository) { + this.genreRepository = genreRepository; + } + + /** + * Cria um novo genre + */ + public Genre createGenre(String name) { + logger.info("Creating genre: {}", name); + + // Aqui poderia ter validações de negócio + if (name == null || name.trim().isEmpty()) { + throw new IllegalArgumentException("Genre name cannot be empty"); + } + + Genre genre = new Genre(); + genre.setName(name.trim()); + + return genreRepository.save(genre); + } + + /** + * Busca todos os genres + */ + public List getAllGenres() { + logger.info("Getting all genres"); + return genreRepository.findAll(); + } + + /** + * Busca genre por ID + */ + public Optional getGenreById(Integer id) { + logger.info("Getting genre by ID: {}", id); + return genreRepository.findById(id); + } + + /** + * Atualiza um genre existente + */ + public Optional updateGenre(Integer id, String name) { + logger.info("Updating genre ID {}: {}", id, name); + + // Validação + if (name == null || name.trim().isEmpty()) { + throw new IllegalArgumentException("Genre name cannot be empty"); + } + + Optional existingGenre = genreRepository.findById(id); + + if (existingGenre.isEmpty()) { + logger.warn("Genre not found with ID: {}", id); + return Optional.empty(); + } + + Genre genre = existingGenre.get(); + genre.setName(name.trim()); + + Genre updated = genreRepository.update(genre); + return Optional.of(updated); + } + + /** + * Deleta um genre por ID + */ + public boolean deleteGenre(Integer id) { + logger.info("Deleting genre ID: {}", id); + return genreRepository.deleteById(id); + } +} \ No newline at end of file diff --git a/src/main/proto/genre.proto b/src/main/proto/genre.proto new file mode 100644 index 0000000..b059b89 --- /dev/null +++ b/src/main/proto/genre.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; + +option java_package = "com.mediamanager.protocol.messages"; +option java_outer_classname = "GenreMessages"; + +package mediamanager.messages; + +message Genre { + int32 id = 1; + string name = 2; +} + +message CreateGenreRequest { + string name = 1; +} + +message CreateGenreResponse { + Genre genre = 1; +} + +message GetGenresRequest { + +} + +message GetGenresResponse { + repeated Genre genres = 1; +} + +message GetGenreByIdRequest { + int32 id = 1; +} + +message GetGenreByIdResponse { + Genre genre = 1; +} + +message UpdateGenreRequest { + int32 id = 1; + string name = 2; // Novo nome +} + +message UpdateGenreResponse { + Genre genre = 1; // Genre atualizado +} + +message DeleteGenreRequest { + int32 id = 1; +} + +message DeleteGenreResponse { + bool success = 1; + string message = 2; +} From 32fff9c725beff5ed933f1af47e89bb7d40330ac Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Sat, 29 Nov 2025 02:50:07 -0300 Subject: [PATCH 23/39] Add null-safe ID handling in `GenreMapper` and fix minor logging format in `IPCManager` - Ensure `GenreMapper` sets ID only if it's non-null and valid (> 0) to prevent potential NPEs. - Adjust logging format in `IPCManager` for consistent indentation. --- .../java/com/mediamanager/mapper/GenreMapper.java | 14 ++++++++++---- .../com/mediamanager/service/ipc/IPCManager.java | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/mediamanager/mapper/GenreMapper.java b/src/main/java/com/mediamanager/mapper/GenreMapper.java index ceae850..331e67c 100644 --- a/src/main/java/com/mediamanager/mapper/GenreMapper.java +++ b/src/main/java/com/mediamanager/mapper/GenreMapper.java @@ -9,10 +9,16 @@ public class GenreMapper { return null; } - return GenreMessages.Genre.newBuilder() - .setId(entity.getId()) - .setName(entity.getName()) - .build(); + GenreMessages.Genre.Builder builder = GenreMessages.Genre.newBuilder() + .setName(entity.getName()); + + // Only set ID when it's present and valid (> 0). Avoids NPE for null IDs. + Integer id = entity.getId(); + if (id != null && id > 0) { + builder.setId(id); + } + + return builder.build(); } public static Genre toEntity(GenreMessages.Genre protobuf) { if (protobuf == null) { diff --git a/src/main/java/com/mediamanager/service/ipc/IPCManager.java b/src/main/java/com/mediamanager/service/ipc/IPCManager.java index 2115066..7fe7541 100644 --- a/src/main/java/com/mediamanager/service/ipc/IPCManager.java +++ b/src/main/java/com/mediamanager/service/ipc/IPCManager.java @@ -125,7 +125,7 @@ public class IPCManager { } - logger.info("Closing IPC connection..."); + logger.info("Closing IPC connection..."); running.set(false); if (serverChannel != null && serverChannel.isOpen()) { From f70c1e1d947f3bd3ed5863018312cd7650e22373 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Sat, 29 Nov 2025 03:02:49 -0300 Subject: [PATCH 24/39] Switch from `EntityManager` to `EntityManagerFactory` across repositories, managers, and services for improved resource management and thread safety. --- .../mediamanager/MediaManagerApplication.java | 2 +- .../repository/GenreRepository.java | 64 ++++++++++++------- .../service/database/DatabaseManager.java | 15 +---- .../delegate/DelegateActionManager.java | 10 +-- 4 files changed, 50 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/mediamanager/MediaManagerApplication.java b/src/main/java/com/mediamanager/MediaManagerApplication.java index 3a85f6f..22834fb 100644 --- a/src/main/java/com/mediamanager/MediaManagerApplication.java +++ b/src/main/java/com/mediamanager/MediaManagerApplication.java @@ -60,7 +60,7 @@ public class MediaManagerApplication { default: } databaseManager.init(); - actionManager = new DelegateActionManager(databaseManager.getEntityManager()); + actionManager = new DelegateActionManager(databaseManager.getEntityManagerFactory()); actionManager.start(); ipcManager = new IPCManager(config,actionManager); ipcManager.init(); diff --git a/src/main/java/com/mediamanager/repository/GenreRepository.java b/src/main/java/com/mediamanager/repository/GenreRepository.java index 6279caa..73e9be9 100644 --- a/src/main/java/com/mediamanager/repository/GenreRepository.java +++ b/src/main/java/com/mediamanager/repository/GenreRepository.java @@ -2,6 +2,7 @@ package com.mediamanager.repository; import com.mediamanager.model.Genre; import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -15,10 +16,10 @@ import java.util.Optional; public class GenreRepository { private static final Logger logger = LogManager.getLogger(GenreRepository.class); - private final EntityManager entityManager; + private final EntityManagerFactory entityManagerFactory; - public GenreRepository(EntityManager entityManager) { - this.entityManager = entityManager; + public GenreRepository(EntityManagerFactory entityManagerFactory) { + this.entityManagerFactory = entityManagerFactory; } /** @@ -26,16 +27,19 @@ public class GenreRepository { */ public Genre save(Genre genre) { logger.debug("Saving genre: {}", genre.getName()); - entityManager.getTransaction().begin(); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); try { - entityManager.persist(genre); - entityManager.getTransaction().commit(); + em.persist(genre); + em.getTransaction().commit(); logger.debug("Genre saved with ID: {}", genre.getId()); return genre; } catch (Exception e) { - entityManager.getTransaction().rollback(); + em.getTransaction().rollback(); logger.error("Error saving genre", e); throw e; + } finally { + if (em.isOpen()) em.close(); } } @@ -44,9 +48,14 @@ public class GenreRepository { */ public List findAll() { logger.debug("Finding all genres"); - return entityManager - .createQuery("SELECT g FROM Genre g ORDER BY g.name", Genre.class) - .getResultList(); + EntityManager em = entityManagerFactory.createEntityManager(); + try { + return em + .createQuery("SELECT g FROM Genre g ORDER BY g.name", Genre.class) + .getResultList(); + } finally { + if (em.isOpen()) em.close(); + } } /** @@ -54,8 +63,13 @@ public class GenreRepository { */ public Optional findById(Integer id) { logger.debug("Finding genre by ID: {}", id); - Genre genre = entityManager.find(Genre.class, id); - return Optional.ofNullable(genre); + EntityManager em = entityManagerFactory.createEntityManager(); + try { + Genre genre = em.find(Genre.class, id); + return Optional.ofNullable(genre); + } finally { + if (em.isOpen()) em.close(); + } } /** @@ -63,16 +77,19 @@ public class GenreRepository { */ public Genre update(Genre genre) { logger.debug("Updating genre ID: {}", genre.getId()); - entityManager.getTransaction().begin(); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); try { - Genre updated = entityManager.merge(genre); - entityManager.getTransaction().commit(); + Genre updated = em.merge(genre); + em.getTransaction().commit(); logger.debug("Genre updated successfully"); return updated; } catch (Exception e) { - entityManager.getTransaction().rollback(); + em.getTransaction().rollback(); logger.error("Error updating genre", e); throw e; + } finally { + if (em.isOpen()) em.close(); } } @@ -81,21 +98,24 @@ public class GenreRepository { */ public boolean deleteById(Integer id) { logger.debug("Deleting genre by ID: {}", id); - entityManager.getTransaction().begin(); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); try { - Genre genre = entityManager.find(Genre.class, id); + Genre genre = em.find(Genre.class, id); if (genre == null) { - entityManager.getTransaction().rollback(); + em.getTransaction().rollback(); return false; } - entityManager.remove(genre); - entityManager.getTransaction().commit(); + em.remove(genre); + em.getTransaction().commit(); logger.debug("Genre deleted successfully"); return true; } catch (Exception e) { - entityManager.getTransaction().rollback(); + em.getTransaction().rollback(); logger.error("Error deleting genre", e); throw e; + } finally { + if (em.isOpen()) em.close(); } } } \ No newline at end of file diff --git a/src/main/java/com/mediamanager/service/database/DatabaseManager.java b/src/main/java/com/mediamanager/service/database/DatabaseManager.java index 2d9c7eb..13188a0 100644 --- a/src/main/java/com/mediamanager/service/database/DatabaseManager.java +++ b/src/main/java/com/mediamanager/service/database/DatabaseManager.java @@ -1,6 +1,5 @@ package com.mediamanager.service.database; -import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -20,7 +19,6 @@ public abstract class DatabaseManager { protected Connection connection; protected String connectionUrl; protected EntityManagerFactory entityManagerFactory; - protected EntityManager entityManager; protected static final Logger logger = LogManager.getLogger(DatabaseManager.class); public DatabaseManager(Properties config) { @@ -48,14 +46,6 @@ public abstract class DatabaseManager { } public void close() { - if (entityManager != null && entityManager.isOpen()) { - try { - entityManager.close(); - logger.info("EntityManager closed"); - } catch (Exception e) { - logger.error("Error closing EntityManager: {}", e.getMessage()); - } - } if (entityManagerFactory != null && entityManagerFactory.isOpen()) { try { entityManagerFactory.close(); @@ -138,7 +128,6 @@ public abstract class DatabaseManager { try { entityManagerFactory = hibernateConfig.buildSessionFactory().unwrap(EntityManagerFactory.class); - entityManager = entityManagerFactory.createEntityManager(); } catch (Exception e) { logger.error("Failed to initialize Hibernate: {}", e.getMessage()); throw new RuntimeException("Hibernate initialization failed", e); @@ -147,7 +136,7 @@ public abstract class DatabaseManager { logger.info("Hibernate ORM initialized successfully"); } - public EntityManager getEntityManager() { - return entityManager; + public EntityManagerFactory getEntityManagerFactory() { + return entityManagerFactory; } } \ No newline at end of file diff --git a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java index d05de4e..2221236 100644 --- a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java +++ b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java @@ -8,7 +8,7 @@ import com.mediamanager.service.delegate.handler.EchoHandler; import com.mediamanager.service.delegate.handler.HeartbeatHandler; import com.mediamanager.service.delegate.handler.genre.*; import com.mediamanager.service.genre.GenreService; -import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -19,13 +19,13 @@ public class DelegateActionManager { private static final Logger logger = LogManager.getLogger(DelegateActionManager.class); private final Map handlerRegistry; - private final EntityManager entityManager; + private final EntityManagerFactory entityManagerFactory; private final GenreService genreService; - public DelegateActionManager(EntityManager entityManager) { - this.entityManager = entityManager; + public DelegateActionManager(EntityManagerFactory entityManagerFactory) { + this.entityManagerFactory = entityManagerFactory; - GenreRepository genreRepository = new GenreRepository(entityManager); + GenreRepository genreRepository = new GenreRepository(entityManagerFactory); this.genreService = new GenreService(genreRepository); logger.debug("DelegateActionManager created"); this.handlerRegistry = new HashMap<>(); From b715bb1bd342eb8a4885429adedc85fd8b92ff22 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Sat, 29 Nov 2025 07:54:46 -0300 Subject: [PATCH 25/39] Add `@Action` annotation and `ServiceLocator` for improved handler registration - Introduce `@Action` annotation to simplify action handler identification and tagging. - Add `ServiceLocator` for dynamic service registration and retrieval. - Annotate handlers (`CreateGenreHandler`, `GetGenreHandler`, `UpdateGenreHandler`, `DeleteGenreHandler`, etc.) with corresponding actions. --- .../service/delegate/ServiceLocator.java | 45 +++++++++++++++++++ .../service/delegate/annotation/Action.java | 13 ++++++ .../delegate/handler/CloseHandler.java | 2 + .../service/delegate/handler/EchoHandler.java | 2 + .../delegate/handler/HeartbeatHandler.java | 2 + .../handler/genre/CreateGenreHandler.java | 2 + .../handler/genre/DeleteGenreHandler.java | 2 + .../handler/genre/GetGenreByIdHandler.java | 2 + .../handler/genre/GetGenreHandler.java | 2 + .../handler/genre/UpdateGenreHandler.java | 2 + 10 files changed, 74 insertions(+) create mode 100644 src/main/java/com/mediamanager/service/delegate/ServiceLocator.java create mode 100644 src/main/java/com/mediamanager/service/delegate/annotation/Action.java diff --git a/src/main/java/com/mediamanager/service/delegate/ServiceLocator.java b/src/main/java/com/mediamanager/service/delegate/ServiceLocator.java new file mode 100644 index 0000000..69886f4 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/ServiceLocator.java @@ -0,0 +1,45 @@ +package com.mediamanager.service.delegate; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.HashMap; +import java.util.Map; + +public class ServiceLocator { + private static final Logger logger = LogManager.getLogger(ServiceLocator.class); + private final Map, Object> services = new HashMap<>(); + + public void register(Class serviceClass, T serviceInstance) { + if (serviceInstance == null) { + throw new IllegalArgumentException("Service instance cannot be null"); + } + services.put(serviceClass, serviceInstance); + logger.debug("Registered service: {} -> {}", + serviceClass.getSimpleName(), + serviceInstance.getClass().getSimpleName()); + } + + @SuppressWarnings("unchecked") + public T get(Class serviceClass) { + return (T) services.get(serviceClass); + } + + public boolean has(Class serviceClass) { + return services.containsKey(serviceClass); + } + public int size() { + return services.size(); + } + public void logRegisteredServices() { + logger.info("Registered services: {}", services.size()); + + + services.forEach((clazz, instance) -> + logger.info(" - {} -> {}", + clazz.getSimpleName(), + instance.getClass().getSimpleName()) + ); + } +} + diff --git a/src/main/java/com/mediamanager/service/delegate/annotation/Action.java b/src/main/java/com/mediamanager/service/delegate/annotation/Action.java new file mode 100644 index 0000000..cac0db7 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/annotation/Action.java @@ -0,0 +1,13 @@ +package com.mediamanager.service.delegate.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Action { + + String value(); +} diff --git a/src/main/java/com/mediamanager/service/delegate/handler/CloseHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/CloseHandler.java index de59bf3..9e9c658 100644 --- a/src/main/java/com/mediamanager/service/delegate/handler/CloseHandler.java +++ b/src/main/java/com/mediamanager/service/delegate/handler/CloseHandler.java @@ -6,9 +6,11 @@ import com.mediamanager.protocol.TestProtocol.CloseCommand; import com.mediamanager.protocol.TestProtocol.CloseResponse; import com.mediamanager.protocol.TransportProtocol; import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +@Action("close") public class CloseHandler implements ActionHandler { private static final Logger logger = LogManager.getLogger(CloseHandler.class); diff --git a/src/main/java/com/mediamanager/service/delegate/handler/EchoHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/EchoHandler.java index 1e7ece9..657c2e7 100644 --- a/src/main/java/com/mediamanager/service/delegate/handler/EchoHandler.java +++ b/src/main/java/com/mediamanager/service/delegate/handler/EchoHandler.java @@ -6,9 +6,11 @@ import com.mediamanager.protocol.TestProtocol.EchoCommand; // ← Import import com.mediamanager.protocol.TestProtocol.EchoResponse; // ← Import import com.mediamanager.protocol.TransportProtocol; import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +@Action("echo") public class EchoHandler implements ActionHandler { private static final Logger logger = LogManager.getLogger(EchoHandler.class); diff --git a/src/main/java/com/mediamanager/service/delegate/handler/HeartbeatHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/HeartbeatHandler.java index 20e3d6d..d79270c 100644 --- a/src/main/java/com/mediamanager/service/delegate/handler/HeartbeatHandler.java +++ b/src/main/java/com/mediamanager/service/delegate/handler/HeartbeatHandler.java @@ -6,9 +6,11 @@ import com.mediamanager.protocol.TestProtocol.HeartbeatCommand; import com.mediamanager.protocol.TestProtocol.HeartbeatResponse; import com.mediamanager.protocol.TransportProtocol; import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +@Action("heartbeat") public class HeartbeatHandler implements ActionHandler { private static final Logger logger = LogManager.getLogger(HeartbeatHandler.class); diff --git a/src/main/java/com/mediamanager/service/delegate/handler/genre/CreateGenreHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/genre/CreateGenreHandler.java index 93b530a..569b119 100644 --- a/src/main/java/com/mediamanager/service/delegate/handler/genre/CreateGenreHandler.java +++ b/src/main/java/com/mediamanager/service/delegate/handler/genre/CreateGenreHandler.java @@ -7,10 +7,12 @@ import com.mediamanager.model.Genre; import com.mediamanager.protocol.TransportProtocol; import com.mediamanager.protocol.messages.GenreMessages; import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; import com.mediamanager.service.genre.GenreService; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +@Action("genre.create") public class CreateGenreHandler implements ActionHandler { private static final Logger logger = LogManager.getLogger(CreateGenreHandler.class); diff --git a/src/main/java/com/mediamanager/service/delegate/handler/genre/DeleteGenreHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/genre/DeleteGenreHandler.java index 61f90df..bffdb1e 100644 --- a/src/main/java/com/mediamanager/service/delegate/handler/genre/DeleteGenreHandler.java +++ b/src/main/java/com/mediamanager/service/delegate/handler/genre/DeleteGenreHandler.java @@ -5,10 +5,12 @@ import com.google.protobuf.InvalidProtocolBufferException; import com.mediamanager.protocol.TransportProtocol; import com.mediamanager.protocol.messages.GenreMessages; import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; import com.mediamanager.service.genre.GenreService; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +@Action("genre.delete") public class DeleteGenreHandler implements ActionHandler { private static final Logger logger = LogManager.getLogger(DeleteGenreHandler.class); diff --git a/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreByIdHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreByIdHandler.java index 77ce9e3..9dfb2db 100644 --- a/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreByIdHandler.java +++ b/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreByIdHandler.java @@ -7,12 +7,14 @@ import com.mediamanager.model.Genre; import com.mediamanager.protocol.TransportProtocol; import com.mediamanager.protocol.messages.GenreMessages; import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; import com.mediamanager.service.genre.GenreService; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.Optional; +@Action( "genre.getById") public class GetGenreByIdHandler implements ActionHandler { private static final Logger logger = LogManager.getLogger(GetGenreByIdHandler.class); diff --git a/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreHandler.java index 1cb3c94..068b44c 100644 --- a/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreHandler.java +++ b/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreHandler.java @@ -7,12 +7,14 @@ import com.mediamanager.model.Genre; import com.mediamanager.protocol.TransportProtocol; import com.mediamanager.protocol.messages.GenreMessages; import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; import com.mediamanager.service.genre.GenreService; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.List; +@Action("genre.getAll") public class GetGenreHandler implements ActionHandler { private static final Logger logger = LogManager.getLogger(GetGenreHandler.class); diff --git a/src/main/java/com/mediamanager/service/delegate/handler/genre/UpdateGenreHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/genre/UpdateGenreHandler.java index 919f50c..d0b54d1 100644 --- a/src/main/java/com/mediamanager/service/delegate/handler/genre/UpdateGenreHandler.java +++ b/src/main/java/com/mediamanager/service/delegate/handler/genre/UpdateGenreHandler.java @@ -7,12 +7,14 @@ import com.mediamanager.model.Genre; import com.mediamanager.protocol.TransportProtocol; import com.mediamanager.protocol.messages.GenreMessages; import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; import com.mediamanager.service.genre.GenreService; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.Optional; +@Action("genre.update") public class UpdateGenreHandler implements ActionHandler { private static final Logger logger = LogManager.getLogger(UpdateGenreHandler.class); From f87d6b1b53fc9bc86fb8889c5bac00c4346ca2a4 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Sat, 29 Nov 2025 08:26:58 -0300 Subject: [PATCH 26/39] Refactor `DelegateActionManager` for dynamic handler registration - Replace manual handler registration with automatic scanning and registration of handlers annotated with `@Action`. - Introduce `ServiceLocator` to enable dependency injection for dynamically instantiated handlers. - Add `initializeServices` to centralize service initialization and registration. - Enhance error handling and logging for handler instantiation and registration processes. --- .../delegate/DelegateActionManager.java | 156 ++++++++++++++++-- 1 file changed, 139 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java index 2221236..3e35e4b 100644 --- a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java +++ b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java @@ -3,46 +3,59 @@ package com.mediamanager.service.delegate; import com.google.protobuf.ByteString; import com.mediamanager.protocol.TransportProtocol; import com.mediamanager.repository.GenreRepository; -import com.mediamanager.service.delegate.handler.CloseHandler; -import com.mediamanager.service.delegate.handler.EchoHandler; -import com.mediamanager.service.delegate.handler.HeartbeatHandler; -import com.mediamanager.service.delegate.handler.genre.*; +import com.mediamanager.service.delegate.annotation.Action; + import com.mediamanager.service.genre.GenreService; import jakarta.persistence.EntityManagerFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.reflections.Reflections; +import org.reflections.scanners.Scanners; +import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; +import java.util.Set; public class DelegateActionManager { private static final Logger logger = LogManager.getLogger(DelegateActionManager.class); private final Map handlerRegistry; + private final ServiceLocator serviceLocator; private final EntityManagerFactory entityManagerFactory; - private final GenreService genreService; + public DelegateActionManager(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; + this.serviceLocator = new ServiceLocator(); + + initializeServices(); + - GenreRepository genreRepository = new GenreRepository(entityManagerFactory); - this.genreService = new GenreService(genreRepository); logger.debug("DelegateActionManager created"); this.handlerRegistry = new HashMap<>(); - registerHandlers(); + autoRegisterHandlers(); + } - private void registerHandlers() { - handlerRegistry.put("echo",new EchoHandler()); - handlerRegistry.put("heartbeat",new HeartbeatHandler()); - handlerRegistry.put("close", new CloseHandler()); - handlerRegistry.put("create_genre", new CreateGenreHandler(genreService)); - handlerRegistry.put("get_genres", new GetGenreHandler(genreService)); - handlerRegistry.put("get_genre_by_id", new GetGenreByIdHandler(genreService)); - handlerRegistry.put("update_genre", new UpdateGenreHandler(genreService)); - handlerRegistry.put("delete_genre", new DeleteGenreHandler(genreService)); + private void initializeServices() { + logger.info("Initializing services..."); + + + GenreRepository genreRepository = new GenreRepository(entityManagerFactory); + GenreService genreService = new GenreService(genreRepository); + + + serviceLocator.register(GenreService.class, genreService); + + + serviceLocator.logRegisteredServices(); + + logger.info("Services initialized successfully"); } + + public void start(){ logger.info("DelegateActionManager started"); } @@ -51,6 +64,115 @@ public class DelegateActionManager { logger.info("DelegateActionManager stopped"); } + @SuppressWarnings("unchecked") + private ActionHandler instantiateHandler(Class clazz) throws Exception { + logger.debug("Attempting to instantiate handler: {}", clazz.getSimpleName()); + + + Constructor[] constructors = clazz.getDeclaredConstructors(); + + + for (Constructor constructor : constructors) { + + Class[] paramTypes = constructor.getParameterTypes(); + + + if (paramTypes.length == 0) { + logger.debug("Using no-arg constructor for {}", clazz.getSimpleName()); + return (ActionHandler) constructor.newInstance(); + } + + + Object[] params = new Object[paramTypes.length]; + boolean allDependenciesResolved = true; + + for (int i = 0; i < paramTypes.length; i++) { + + Object service = serviceLocator.get(paramTypes[i]); + + if (service == null) { + + allDependenciesResolved = false; + logger.debug("Cannot resolve dependency {} for {}", + paramTypes[i].getSimpleName(), + clazz.getSimpleName()); + break; // Para de tentar esse construtor + } + + + params[i] = service; + } + + + if (allDependenciesResolved) { + logger.debug("Successfully resolved {} dependencies for {}", + paramTypes.length, + clazz.getSimpleName()); + return (ActionHandler) constructor.newInstance(params); + } + } + + + throw new IllegalStateException( + String.format( + "Cannot instantiate handler %s. No suitable constructor found. " + + "Make sure all required services are registered in ServiceLocator.", + clazz.getName() + ) + ); + } + + private void autoRegisterHandlers() { + logger.info("Starting auto-registration of handlers..."); + Reflections reflections = new Reflections( + "com.mediamanager.service.delegate.handler", + Scanners.TypesAnnotated + ); + Set> annotatedClasses = reflections.getTypesAnnotatedWith(Action.class); + logger.info("Found {} handler classes with @Action annotation", annotatedClasses.size()); + int successCount = 0; + int failureCount = 0; + for (Class handlerClass : annotatedClasses) { + try { + + Action actionAnnotation = handlerClass.getAnnotation(Action.class); + String actionName = actionAnnotation.value(); + + logger.debug("Processing handler: {} for action '{}'", + handlerClass.getSimpleName(), + actionName); + + + ActionHandler handler = instantiateHandler(handlerClass); + + + handlerRegistry.put(actionName, handler); + + logger.info("✓ Registered handler: '{}' -> {}", + actionName, + handlerClass.getSimpleName()); + successCount++; + + } catch (Exception e) { + + logger.error("✗ Failed to register handler: {}", + handlerClass.getName(), + e); + failureCount++; + } + } + + + logger.info("Auto-registration complete: {} successful, {} failed, {} total", + successCount, + failureCount, + successCount + failureCount); + + if (failureCount > 0) { + logger.warn("Some handlers failed to register. Check logs above for details."); + } + } + public TransportProtocol.Response ProcessedRequest(TransportProtocol.Request request){ String requestId = request.getRequestId(); logger.info("Processing request: {}", requestId); From a5bd6e2c398babde938fedf2243e01e24ddabe7c Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Miranda Date: Sat, 29 Nov 2025 09:52:54 -0300 Subject: [PATCH 27/39] Update src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../service/delegate/DelegateActionManager.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java index 3e35e4b..fa47150 100644 --- a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java +++ b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java @@ -72,17 +72,17 @@ public class DelegateActionManager { Constructor[] constructors = clazz.getDeclaredConstructors(); + Constructor[] constructors = clazz.getDeclaredConstructors(); + + // Sort constructors by parameter count (descending) to prefer DI constructors + java.util.Arrays.sort(constructors, (c1, c2) -> + Integer.compare(c2.getParameterCount(), c1.getParameterCount())); + for (Constructor constructor : constructors) { Class[] paramTypes = constructor.getParameterTypes(); - if (paramTypes.length == 0) { - logger.debug("Using no-arg constructor for {}", clazz.getSimpleName()); - return (ActionHandler) constructor.newInstance(); - } - - Object[] params = new Object[paramTypes.length]; boolean allDependenciesResolved = true; @@ -105,9 +105,8 @@ public class DelegateActionManager { if (allDependenciesResolved) { - logger.debug("Successfully resolved {} dependencies for {}", - paramTypes.length, - clazz.getSimpleName()); + logger.debug("Using constructor with {} params for {}", + paramTypes.length, clazz.getSimpleName()); return (ActionHandler) constructor.newInstance(params); } } From 5ce8f4ca2a2c2e53f6179b08fc5be6a46ea98842 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Sat, 29 Nov 2025 15:33:08 -0300 Subject: [PATCH 28/39] Improve `DelegateActionManager` error handling for invalid handlers - Add validation to ensure classes annotated with `@Action` implement `ActionHandler`, throwing `IllegalArgumentException` for mismatches. - Fix minor formatting issue in `@Action` annotation for `GetGenreByIdHandler`. --- .../service/delegate/DelegateActionManager.java | 7 +++++-- .../delegate/handler/genre/GetGenreByIdHandler.java | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java index fa47150..69d36d6 100644 --- a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java +++ b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java @@ -66,14 +66,17 @@ public class DelegateActionManager { @SuppressWarnings("unchecked") private ActionHandler instantiateHandler(Class clazz) throws Exception { + if(!ActionHandler.class.isAssignableFrom(clazz)){ + throw new IllegalArgumentException( + clazz.getName() + " is annotated with @Action but does not implement ActionHandler"); + + } logger.debug("Attempting to instantiate handler: {}", clazz.getSimpleName()); Constructor[] constructors = clazz.getDeclaredConstructors(); - Constructor[] constructors = clazz.getDeclaredConstructors(); - // Sort constructors by parameter count (descending) to prefer DI constructors java.util.Arrays.sort(constructors, (c1, c2) -> Integer.compare(c2.getParameterCount(), c1.getParameterCount())); diff --git a/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreByIdHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreByIdHandler.java index 9dfb2db..4dc4c71 100644 --- a/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreByIdHandler.java +++ b/src/main/java/com/mediamanager/service/delegate/handler/genre/GetGenreByIdHandler.java @@ -14,7 +14,7 @@ import org.apache.logging.log4j.Logger; import java.util.Optional; -@Action( "genre.getById") +@Action("genre.getById") public class GetGenreByIdHandler implements ActionHandler { private static final Logger logger = LogManager.getLogger(GetGenreByIdHandler.class); From 0a61f1f2fa2ca2851dd424df10e5553bc69e6337 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Sun, 30 Nov 2025 12:46:50 -0300 Subject: [PATCH 29/39] Implement Artist Management with CRUD Operations - Add `Artist` entity with JPA annotations and database mapping. - Create repository, service, and delegate handlers for Artist CRUD operations. - Register handlers (`artist.create`, `artist.getAll`, `artist.getById`, `artist.update`, `artist.delete`) in `DelegateActionManager`. - Introduce Protobuf definitions for Artist messages. - Update `initializeServices` in `DelegateActionManager` for Artist service support. - Add null-safe ID handling in `ArtistMapper` to prevent potential NPEs. --- .../com/mediamanager/mapper/ArtistMapper.java | 36 ++++++ .../java/com/mediamanager/model/Artist.java | 27 +++++ .../repository/ArtistRepository.java | 106 ++++++++++++++++++ .../service/artist/ArtistService.java | 65 +++++++++++ .../delegate/DelegateActionManager.java | 6 + .../handler/artist/CreateArtistHandler.java | 50 +++++++++ .../handler/artist/DeleteArtistHandle.java | 62 ++++++++++ .../handler/artist/GetArtistByIdHandler.java | 56 +++++++++ .../handler/artist/GetArtistHandler.java | 48 ++++++++ .../handler/artist/UpdateArtistHandler.java | 65 +++++++++++ src/main/proto/artist.proto | 53 +++++++++ 11 files changed, 574 insertions(+) create mode 100644 src/main/java/com/mediamanager/mapper/ArtistMapper.java create mode 100644 src/main/java/com/mediamanager/model/Artist.java create mode 100644 src/main/java/com/mediamanager/repository/ArtistRepository.java create mode 100644 src/main/java/com/mediamanager/service/artist/ArtistService.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/artist/CreateArtistHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/artist/DeleteArtistHandle.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/artist/GetArtistByIdHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/artist/GetArtistHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/artist/UpdateArtistHandler.java create mode 100644 src/main/proto/artist.proto diff --git a/src/main/java/com/mediamanager/mapper/ArtistMapper.java b/src/main/java/com/mediamanager/mapper/ArtistMapper.java new file mode 100644 index 0000000..75e6eb1 --- /dev/null +++ b/src/main/java/com/mediamanager/mapper/ArtistMapper.java @@ -0,0 +1,36 @@ +package com.mediamanager.mapper; + +import com.mediamanager.model.Artist; +import com.mediamanager.protocol.messages.ArtistMessages; + + +public class ArtistMapper { + public static ArtistMessages.Artist toProtobuf(Artist entity){ + if (entity == null) { + return null; + } + + ArtistMessages.Artist.Builder builder = ArtistMessages.Artist.newBuilder() + .setName(entity.getName()); + + // Only set ID when it's present and valid (> 0). Avoids NPE for null IDs. + Integer id = entity.getId(); + if (id != null && id > 0) { + builder.setId(id); + } + + return builder.build(); + } + public static Artist toEntity(ArtistMessages.Artist protobuf) { + if (protobuf == null) { + return null; + } + Artist entity = new Artist(); + + if (protobuf.getId() > 0) { + entity.setId(protobuf.getId()); + } + entity.setName(protobuf.getName()); + return entity; + } +} diff --git a/src/main/java/com/mediamanager/model/Artist.java b/src/main/java/com/mediamanager/model/Artist.java new file mode 100644 index 0000000..22fe490 --- /dev/null +++ b/src/main/java/com/mediamanager/model/Artist.java @@ -0,0 +1,27 @@ +package com.mediamanager.model; + +import jakarta.persistence.*; + +@Entity +@Table(name = "artist") +public class Artist { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Column(nullable = false) + private String name; + + public Integer getId() { + return id; + } + public void setId(Integer id) { + this.id = id; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/com/mediamanager/repository/ArtistRepository.java b/src/main/java/com/mediamanager/repository/ArtistRepository.java new file mode 100644 index 0000000..b41761d --- /dev/null +++ b/src/main/java/com/mediamanager/repository/ArtistRepository.java @@ -0,0 +1,106 @@ +package com.mediamanager.repository; + +import com.mediamanager.model.Artist; +import com.mediamanager.model.Genre; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.Optional; + +public class ArtistRepository { + private static final Logger logger = LogManager.getLogger(ArtistRepository.class); + + private final EntityManagerFactory entityManagerFactory; + + public ArtistRepository(EntityManagerFactory entityManagerFactory) { + this.entityManagerFactory = entityManagerFactory; + } + + public Artist save(Artist artist){ + logger.debug("Saving Artist: {}", artist.getName()); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + try { + em.persist(artist); + em.getTransaction().commit(); + logger.debug("Artist saved with ID: {}", artist.getId()); + return artist; + } catch (Exception e) { + em.getTransaction().rollback(); + logger.error("Error saving Artist", e); + throw e; + } finally { + if (em.isOpen()) em.close(); + } + } + + public List findAll(){ + logger.debug("Finding all Artists"); + EntityManager em = entityManagerFactory.createEntityManager(); + try { + return em + .createQuery("SELECT a FROM Artist a ORDER BY a.name", Artist.class) + .getResultList(); + } finally { + if (em.isOpen()) em.close(); + } + } + + public Optional findById(Integer id){ + logger.debug("Finding artist by ID: {}", id); + EntityManager em = entityManagerFactory.createEntityManager(); + try { + Artist artist = em.find(Artist.class, id); + return Optional.ofNullable(artist); + } finally { + if (em.isOpen()) em.close(); + } + + } + + public Artist update(Artist artist){ + logger.debug("Updating genre ID: {}", artist.getId()); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + try { + Artist updated = em.merge(artist); + em.getTransaction().commit(); + logger.debug("Genre updated successfully"); + return updated; + } catch (Exception e) { + em.getTransaction().rollback(); + logger.error("Error updating genre", e); + throw e; + } finally { + if (em.isOpen()) em.close(); + } + } + + public boolean deleteById(Integer id){ + logger.debug("Deleting Artist by ID: {}", id); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + try { + Artist artist = em.find(Artist.class, id); + if (artist == null) { + em.getTransaction().rollback(); + return false; + } + em.remove(artist); + em.getTransaction().commit(); + logger.debug("Artist deleted successfully"); + return true; + } catch (Exception e) { + em.getTransaction().rollback(); + logger.error("Error deleting genre", e); + throw e; + } finally { + if (em.isOpen()) em.close(); + } + } + } + + diff --git a/src/main/java/com/mediamanager/service/artist/ArtistService.java b/src/main/java/com/mediamanager/service/artist/ArtistService.java new file mode 100644 index 0000000..4686030 --- /dev/null +++ b/src/main/java/com/mediamanager/service/artist/ArtistService.java @@ -0,0 +1,65 @@ +package com.mediamanager.service.artist; + +import com.mediamanager.model.Artist; +import com.mediamanager.repository.ArtistRepository; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.Optional; + +public class ArtistService { + private static final Logger logger = LogManager.getLogger(ArtistService.class); + private final ArtistRepository artistRepository; + + public ArtistService(ArtistRepository artistRepository) { + this.artistRepository = artistRepository; + } + + public Artist createArtist(String name){ + logger.info("Creating artist: {}", name); + if (name == null || name.trim().isEmpty()) { + throw new IllegalArgumentException("Artist name cannot be empty"); + } + Artist artist = new Artist(); + artist.setName(name.trim()); + return artistRepository.save(artist); + } + + public List getAllArtists(){ + logger.info("Getting all artists"); + return artistRepository.findAll(); + } + + public Optional getArtistById(Integer id){ + logger.info("Getting artist by ID: {}", id); + return artistRepository.findById(id); + } + + public Optional updateArtist(Integer id, String name){ + logger.info("Updating artist ID {}: {}", id, name); + + if (name == null || name.trim().isEmpty()) { + throw new IllegalArgumentException("Artist name cannot be empty"); + } + + Optional existingArtist = artistRepository.findById(id); + + if(existingArtist.isEmpty()){ + logger.warn("Artist not found with ID: {}", id); + return Optional.empty(); + } + Artist artist = existingArtist.get(); + artist.setName(name.trim()); + Artist updatedArtist = artistRepository.update(artist); + return Optional.of(updatedArtist); + } + + public boolean deleteArtist(Integer id){ + logger.info("Deleting artist ID: {}", id); + return artistRepository.deleteById(id); + } + + + +} diff --git a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java index 69d36d6..80a1de6 100644 --- a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java +++ b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java @@ -2,7 +2,9 @@ package com.mediamanager.service.delegate; import com.google.protobuf.ByteString; import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.repository.ArtistRepository; import com.mediamanager.repository.GenreRepository; +import com.mediamanager.service.artist.ArtistService; import com.mediamanager.service.delegate.annotation.Action; import com.mediamanager.service.genre.GenreService; @@ -48,6 +50,10 @@ public class DelegateActionManager { serviceLocator.register(GenreService.class, genreService); + ArtistRepository artistRepository = new ArtistRepository(entityManagerFactory); + ArtistService artistService = new ArtistService(artistRepository); + + serviceLocator.register(ArtistService.class, artistService); serviceLocator.logRegisteredServices(); diff --git a/src/main/java/com/mediamanager/service/delegate/handler/artist/CreateArtistHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/artist/CreateArtistHandler.java new file mode 100644 index 0000000..268a4bd --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/artist/CreateArtistHandler.java @@ -0,0 +1,50 @@ +package com.mediamanager.service.delegate.handler.artist; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.ArtistMapper; +import com.mediamanager.model.Artist; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.ArtistMessages; +import com.mediamanager.service.artist.ArtistService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@Action("artist.create") +public class CreateArtistHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(CreateArtistHandler.class); + private final ArtistService artistService; + + public CreateArtistHandler(ArtistService artistService) { + this.artistService = artistService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException { + try{ + ArtistMessages.CreateArtistRequest createRequest = + ArtistMessages.CreateArtistRequest.parseFrom(requestPayload); + Artist artist = artistService.createArtist(createRequest.getName()); + ArtistMessages.Artist artistProto = ArtistMapper.toProtobuf(artist); + ArtistMessages.CreateArtistResponse createArtistResponse = ArtistMessages.CreateArtistResponse.newBuilder() + .setArtist(artistProto) + .build(); + return TransportProtocol.Response.newBuilder() + .setPayload(createArtistResponse.toByteString()); + } catch (IllegalArgumentException e) { + logger.error("Validation error", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(400) + .setPayload(ByteString.copyFromUtf8("Validation error: " + e.getMessage())); + + } catch (Exception e) { + logger.error("Error creating artist", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + } +} + diff --git a/src/main/java/com/mediamanager/service/delegate/handler/artist/DeleteArtistHandle.java b/src/main/java/com/mediamanager/service/delegate/handler/artist/DeleteArtistHandle.java new file mode 100644 index 0000000..2b2e2d9 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/artist/DeleteArtistHandle.java @@ -0,0 +1,62 @@ +package com.mediamanager.service.delegate.handler.artist; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.ArtistMessages; +import com.mediamanager.service.artist.ArtistService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@Action("artist.delete") +public class DeleteArtistHandle implements ActionHandler { + private static final Logger logger = LogManager.getLogger(DeleteArtistHandle.class); + + private final ArtistService artistService; + + public DeleteArtistHandle(ArtistService artistService) { + this.artistService = artistService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException { + + try { + ArtistMessages.DeleteArtistRequest deleteRequest = + ArtistMessages.DeleteArtistRequest.parseFrom(requestPayload); + int id = deleteRequest.getId(); + boolean success = artistService.deleteArtist(id); + ArtistMessages.DeleteArtistResponse deleteResponse; + if (success) { + deleteResponse = ArtistMessages.DeleteArtistResponse.newBuilder() + .setSuccess(true) + .setMessage("Artist deleted successfully") + .build(); + return TransportProtocol.Response.newBuilder() + .setPayload(deleteResponse.toByteString()); + } else { + deleteResponse = ArtistMessages.DeleteArtistResponse.newBuilder() + .setSuccess(false) + .setMessage("Artist not found") + .build(); + + return TransportProtocol.Response.newBuilder() + .setStatusCode(404) + .setPayload(deleteResponse.toByteString()); + } + } catch (Exception e) { + logger.error("Error deleting artist", e); + ArtistMessages.DeleteArtistResponse deleteResponse = + ArtistMessages.DeleteArtistResponse.newBuilder() + .setSuccess(false) + .setMessage("Error: " + e.getMessage()) + .build(); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(deleteResponse.toByteString()); + } + } + } diff --git a/src/main/java/com/mediamanager/service/delegate/handler/artist/GetArtistByIdHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/artist/GetArtistByIdHandler.java new file mode 100644 index 0000000..d98b48e --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/artist/GetArtistByIdHandler.java @@ -0,0 +1,56 @@ +package com.mediamanager.service.delegate.handler.artist; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.ArtistMapper; +import com.mediamanager.model.Artist; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.ArtistMessages; +import com.mediamanager.service.artist.ArtistService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +@Action(value = "artist.getById") +public class GetArtistByIdHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(GetArtistByIdHandler.class); + private final ArtistService artistService; + + public GetArtistByIdHandler(ArtistService artistService) { + this.artistService = artistService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException{ + + try{ + ArtistMessages.GetArtistByIdRequest getByIdRequest = + ArtistMessages.GetArtistByIdRequest.parseFrom(requestPayload); + int id = getByIdRequest.getId(); + + Optional artistOpt = artistService.getArtistById(id); + + if (artistOpt.isEmpty()){ + logger.warn("Artist not found with ID: {}", id); + return TransportProtocol.Response.newBuilder() + .setStatusCode(404) + .setPayload(ByteString.copyFromUtf8("Artist not found")); + } + ArtistMessages.Artist artistProto = ArtistMapper.toProtobuf(artistOpt.get()); + ArtistMessages.GetArtistByIdResponse getByIdResponse = ArtistMessages.GetArtistByIdResponse.newBuilder() + .setArtist(artistProto) + .build(); + return TransportProtocol.Response.newBuilder() + .setPayload(getByIdResponse.toByteString()); + } catch (Exception e) { + logger.error("Error getting artist by ID", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: "+ e.getMessage())); + } + } +} diff --git a/src/main/java/com/mediamanager/service/delegate/handler/artist/GetArtistHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/artist/GetArtistHandler.java new file mode 100644 index 0000000..bf91fff --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/artist/GetArtistHandler.java @@ -0,0 +1,48 @@ +package com.mediamanager.service.delegate.handler.artist; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.ArtistMapper; +import com.mediamanager.model.Artist; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.ArtistMessages; +import com.mediamanager.service.artist.ArtistService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; + + +@Action("artist.getAll") +public class GetArtistHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(GetArtistHandler.class); + + private final ArtistService artistService; + + public GetArtistHandler(ArtistService artistService){this.artistService = artistService;} + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException { + try{ + List artists = artistService.getAllArtists(); + ArtistMessages.GetArtistsResponse.Builder responseBuilder = ArtistMessages.GetArtistsResponse.newBuilder(); + + for (Artist artist : artists) { + ArtistMessages.Artist artistProto = ArtistMapper.toProtobuf(artist); + responseBuilder.addArtists(artistProto); + } + ArtistMessages.GetArtistsResponse getArtistsResponse = responseBuilder.build(); + + return TransportProtocol.Response.newBuilder() + .setPayload(getArtistsResponse.toByteString()); + + }catch (Exception e){ + logger.error("Error getting artists", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + } +} diff --git a/src/main/java/com/mediamanager/service/delegate/handler/artist/UpdateArtistHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/artist/UpdateArtistHandler.java new file mode 100644 index 0000000..69c3cd6 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/artist/UpdateArtistHandler.java @@ -0,0 +1,65 @@ +package com.mediamanager.service.delegate.handler.artist; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.ArtistMapper; +import com.mediamanager.model.Artist; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.ArtistMessages; +import com.mediamanager.service.artist.ArtistService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +@Action("artist.update") +public class UpdateArtistHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(UpdateArtistHandler.class); + private final ArtistService artistService; + + public UpdateArtistHandler(ArtistService artistService) { + this.artistService = artistService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException { + try{ + ArtistMessages.UpdateArtistRequest updateRequest = + ArtistMessages.UpdateArtistRequest.parseFrom(requestPayload); + + int id = updateRequest.getId(); + String newName = updateRequest.getName(); + + Optional artistOpt = artistService.updateArtist(id, newName); + + if(artistOpt.isEmpty()){ + logger.warn("Artist not found with ID: {}", id); + return TransportProtocol.Response.newBuilder() + .setStatusCode(404) + .setPayload(ByteString.copyFromUtf8("Artist not found")); + } + + ArtistMessages.Artist artistProto = ArtistMapper.toProtobuf(artistOpt.get()); + + ArtistMessages.UpdateArtistResponse updateResponse = ArtistMessages.UpdateArtistResponse.newBuilder() + .setArtist(artistProto) + .build(); + + return TransportProtocol.Response.newBuilder() + .setPayload(updateResponse.toByteString()); + + } catch (IllegalArgumentException e){ + logger.error("Validation error", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(400) + .setPayload(ByteString.copyFromUtf8("Validation error: " + e.getMessage())); + } catch (Exception e) { + logger.error("Error updating artist", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + } +} diff --git a/src/main/proto/artist.proto b/src/main/proto/artist.proto new file mode 100644 index 0000000..e4a4062 --- /dev/null +++ b/src/main/proto/artist.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; + +option java_package = "com.mediamanager.protocol.messages"; +option java_outer_classname = "ArtistMessages"; + +package mediamanager.messages; + +message Artist { + int32 id = 1; + string name = 2; +} + +message CreateArtistRequest { + string name = 1; +} + +message CreateArtistResponse { + Artist artist = 1; +} + +message GetArtistsRequest { + +} + +message GetArtistsResponse { + repeated Artist artists = 1; +} + +message GetArtistByIdRequest { + int32 id = 1; +} + +message GetArtistByIdResponse { + Artist artist = 1; +} + +message UpdateArtistRequest { + int32 id = 1; + string name = 2; // Novo nome +} + +message UpdateArtistResponse { + Artist Artist = 1; // Genre atualizado +} + +message DeleteArtistRequest { + int32 id = 1; +} + +message DeleteArtistResponse { + bool success = 1; + string message = 2; +} From 3c42fd9735e37be6c68b247b658ea1e544621566 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Miranda Date: Sun, 30 Nov 2025 12:54:18 -0300 Subject: [PATCH 30/39] Update src/main/java/com/mediamanager/mapper/ArtistMapper.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/main/java/com/mediamanager/mapper/ArtistMapper.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/mediamanager/mapper/ArtistMapper.java b/src/main/java/com/mediamanager/mapper/ArtistMapper.java index 75e6eb1..29f2ce9 100644 --- a/src/main/java/com/mediamanager/mapper/ArtistMapper.java +++ b/src/main/java/com/mediamanager/mapper/ArtistMapper.java @@ -10,8 +10,13 @@ public class ArtistMapper { return null; } + String name = entity.getName(); + if (name == null) { + throw new IllegalArgumentException("Artist name cannot be null"); + } + ArtistMessages.Artist.Builder builder = ArtistMessages.Artist.newBuilder() - .setName(entity.getName()); + .setName(name); // Only set ID when it's present and valid (> 0). Avoids NPE for null IDs. Integer id = entity.getId(); From 0df4b31a4d0d198656de9654ec161c946fb31cab Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Sun, 30 Nov 2025 12:59:57 -0300 Subject: [PATCH 31/39] Fix logging messages in `ArtistRepository` for clarity - Correct references from "genre" to "artist" in logging statements. - Remove unused `Genre` import for cleanup. --- .../com/mediamanager/repository/ArtistRepository.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/mediamanager/repository/ArtistRepository.java b/src/main/java/com/mediamanager/repository/ArtistRepository.java index b41761d..60d0b9d 100644 --- a/src/main/java/com/mediamanager/repository/ArtistRepository.java +++ b/src/main/java/com/mediamanager/repository/ArtistRepository.java @@ -1,7 +1,5 @@ package com.mediamanager.repository; - import com.mediamanager.model.Artist; -import com.mediamanager.model.Genre; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; import org.apache.logging.log4j.LogManager; @@ -62,17 +60,17 @@ public class ArtistRepository { } public Artist update(Artist artist){ - logger.debug("Updating genre ID: {}", artist.getId()); + logger.debug("Updating artist ID: {}", artist.getId()); EntityManager em = entityManagerFactory.createEntityManager(); em.getTransaction().begin(); try { Artist updated = em.merge(artist); em.getTransaction().commit(); - logger.debug("Genre updated successfully"); + logger.debug("Artist updated successfully"); return updated; } catch (Exception e) { em.getTransaction().rollback(); - logger.error("Error updating genre", e); + logger.error("Error updating artist", e); throw e; } finally { if (em.isOpen()) em.close(); @@ -95,7 +93,7 @@ public class ArtistRepository { return true; } catch (Exception e) { em.getTransaction().rollback(); - logger.error("Error deleting genre", e); + logger.error("Error deleting artist", e); throw e; } finally { if (em.isOpen()) em.close(); From 51a46c37e5e738a85ef7c0703c1513cce7699b60 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Sun, 30 Nov 2025 17:33:25 -0300 Subject: [PATCH 32/39] Rename `DeleteArtistHandle` to `DeleteArtistHandler` for consistency and update related references. Clean up Protobuf comment in `UpdateArtistResponse`. --- ...eArtistHandle.java => DeleteArtistHandler.java} | 14 +++++++------- src/main/proto/artist.proto | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) rename src/main/java/com/mediamanager/service/delegate/handler/artist/{DeleteArtistHandle.java => DeleteArtistHandler.java} (87%) diff --git a/src/main/java/com/mediamanager/service/delegate/handler/artist/DeleteArtistHandle.java b/src/main/java/com/mediamanager/service/delegate/handler/artist/DeleteArtistHandler.java similarity index 87% rename from src/main/java/com/mediamanager/service/delegate/handler/artist/DeleteArtistHandle.java rename to src/main/java/com/mediamanager/service/delegate/handler/artist/DeleteArtistHandler.java index 2b2e2d9..a3d3c72 100644 --- a/src/main/java/com/mediamanager/service/delegate/handler/artist/DeleteArtistHandle.java +++ b/src/main/java/com/mediamanager/service/delegate/handler/artist/DeleteArtistHandler.java @@ -11,12 +11,12 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @Action("artist.delete") -public class DeleteArtistHandle implements ActionHandler { - private static final Logger logger = LogManager.getLogger(DeleteArtistHandle.class); +public class DeleteArtistHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(DeleteArtistHandler.class); private final ArtistService artistService; - public DeleteArtistHandle(ArtistService artistService) { + public DeleteArtistHandler(ArtistService artistService) { this.artistService = artistService; } @@ -54,9 +54,9 @@ public class DeleteArtistHandle implements ActionHandler { .setSuccess(false) .setMessage("Error: " + e.getMessage()) .build(); - return TransportProtocol.Response.newBuilder() - .setStatusCode(500) - .setPayload(deleteResponse.toByteString()); - } + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(deleteResponse.toByteString()); } } + } diff --git a/src/main/proto/artist.proto b/src/main/proto/artist.proto index e4a4062..254e7f7 100644 --- a/src/main/proto/artist.proto +++ b/src/main/proto/artist.proto @@ -40,7 +40,7 @@ message UpdateArtistRequest { } message UpdateArtistResponse { - Artist Artist = 1; // Genre atualizado + Artist Artist = 1; } message DeleteArtistRequest { From db4217bec4e7e754c37a6d54ea5686b2af9adeaa Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Mon, 1 Dec 2025 00:16:57 -0300 Subject: [PATCH 33/39] Implement Composer Management with CRUD Operations - Add `Composer` entity with JPA annotations and database mapping. - Create repository, service, and delegate handlers for Composer CRUD operations: `create_composer`, `get_composers`, `get_composer_by_id`, `update_composer`, `delete_composer`. - Introduce Protobuf definitions for Composer messages. - Register Composer service and handlers in `DelegateActionManager`. - Add `ComposerMapper` to map between Protobuf and entity models. - Enhance error handling and logging for Composer operations. --- .../mediamanager/mapper/ComposerMapper.java | 38 +++++++ .../java/com/mediamanager/model/Composer.java | 31 ++++++ .../repository/ComposerRepository.java | 102 ++++++++++++++++++ .../service/composer/ComposerService.java | 70 ++++++++++++ .../delegate/DelegateActionManager.java | 11 ++ .../composer/CreateComposerHandler.java | 51 +++++++++ .../composer/DeleteComposerHandler.java | 53 +++++++++ .../composer/GetComposerByIdHandler.java | 57 ++++++++++ .../handler/composer/GetComposerHandler.java | 50 +++++++++ .../composer/UpdateComposerHandler.java | 56 ++++++++++ src/main/proto/composer.proto | 51 +++++++++ 11 files changed, 570 insertions(+) create mode 100644 src/main/java/com/mediamanager/mapper/ComposerMapper.java create mode 100644 src/main/java/com/mediamanager/model/Composer.java create mode 100644 src/main/java/com/mediamanager/repository/ComposerRepository.java create mode 100644 src/main/java/com/mediamanager/service/composer/ComposerService.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/composer/CreateComposerHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/composer/DeleteComposerHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/composer/GetComposerByIdHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/composer/GetComposerHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/composer/UpdateComposerHandler.java create mode 100644 src/main/proto/composer.proto diff --git a/src/main/java/com/mediamanager/mapper/ComposerMapper.java b/src/main/java/com/mediamanager/mapper/ComposerMapper.java new file mode 100644 index 0000000..cf783b5 --- /dev/null +++ b/src/main/java/com/mediamanager/mapper/ComposerMapper.java @@ -0,0 +1,38 @@ +package com.mediamanager.mapper; + +import com.mediamanager.model.Composer; +import com.mediamanager.protocol.messages.ComposerMessages; + +public class ComposerMapper { + public static ComposerMessages.Composer toProtobuf(Composer entity) { + if (entity == null) { + return null; + } + ComposerMessages.Composer.Builder builder = ComposerMessages.Composer.newBuilder() + .setName(entity.getName()); + + Integer id = entity.getId(); + if (id != null && id > 0) { + builder.setId(id); + } + + return builder.build(); + + } + + public static Composer toEntity(ComposerMessages.Composer protobuf) { + if (protobuf == null) { + return null; + } + + Composer entity = new Composer(); + + if (protobuf.getId() > 0) { + entity.setId(protobuf.getId()); + } + + entity.setName(protobuf.getName()); + + return entity; + } +} diff --git a/src/main/java/com/mediamanager/model/Composer.java b/src/main/java/com/mediamanager/model/Composer.java new file mode 100644 index 0000000..965bf6f --- /dev/null +++ b/src/main/java/com/mediamanager/model/Composer.java @@ -0,0 +1,31 @@ +package com.mediamanager.model; + + +import jakarta.persistence.*; + +@Entity +@Table(name = "composer") +public class Composer { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Column(nullable = false) + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/com/mediamanager/repository/ComposerRepository.java b/src/main/java/com/mediamanager/repository/ComposerRepository.java new file mode 100644 index 0000000..6e1a2aa --- /dev/null +++ b/src/main/java/com/mediamanager/repository/ComposerRepository.java @@ -0,0 +1,102 @@ +package com.mediamanager.repository; + +import com.mediamanager.model.Composer; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.Optional; + +public class ComposerRepository { + private static final Logger logger = LogManager.getLogger(ComposerRepository.class); + + private final EntityManagerFactory entityManagerFactory; + + public ComposerRepository(EntityManagerFactory entityManagerFactory) { + this.entityManagerFactory = entityManagerFactory; + } + + public Composer save(Composer composer) { + logger.debug("Saving composer: {}", composer.getName()); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + try{ + //ToDo: Add Id Validation + //ToDo: Add to all Repositories + em.persist(composer); + em.getTransaction().commit(); + logger.debug("Composer saved with IS: {}", composer.getId()); + return composer; + } catch (Exception e) { + em.getTransaction().rollback(); + logger.error("Error saving composer", e); + throw e; + }finally { + if (em.isOpen()) em.close(); + } + } + + public List findAll(){ + logger.debug("Finding all composers"); + EntityManager em = entityManagerFactory.createEntityManager(); + try{ + return em.createQuery("SELECT c FROM Composer c ORDER BY c.name", Composer.class).getResultList(); + } finally { + if (em.isOpen()) em.close(); + } + } + + public Optional findById(Integer id){ + logger.debug("Finding composer by ID: {}", id); + EntityManager em = entityManagerFactory.createEntityManager(); + try{ + Composer composer = em.find(Composer.class, id); + return Optional.ofNullable(composer); + } finally { + if (em.isOpen()) em.close(); + } + } + + public Composer update(Composer composer){ + logger.debug("Updating composer ID: {}", composer.getId()); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + try{ + Composer updated = em.merge(composer); + em.getTransaction().commit(); + logger.debug("Composer updated successfully"); + return updated; + } catch (Exception e) { + em.getTransaction().rollback(); + logger.error("Error updating composer", e); + throw e; + } finally { + if (em.isOpen()) em.close(); + } + } + + public boolean deleteById(Integer id){ + logger.debug("Deleting composer by ID: {}", id); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + try{ + Composer composer = em.find(Composer.class, id); + if (composer == null) { + em.getTransaction().rollback(); + return false; + } + em.remove(composer); + em.getTransaction().commit(); + logger.debug("Composer deleted successfully"); + return true; + } catch (Exception e) { + em.getTransaction().rollback(); + logger.error("Error deleting composer", e); + throw e; + }finally { + if (em.isOpen()) em.close(); + } + } +} diff --git a/src/main/java/com/mediamanager/service/composer/ComposerService.java b/src/main/java/com/mediamanager/service/composer/ComposerService.java new file mode 100644 index 0000000..4f2e5e7 --- /dev/null +++ b/src/main/java/com/mediamanager/service/composer/ComposerService.java @@ -0,0 +1,70 @@ +package com.mediamanager.service.composer; + +import com.mediamanager.model.Composer; +import com.mediamanager.repository.ComposerRepository; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.Optional; + +public class ComposerService { + private static final Logger logger = LogManager.getLogger(ComposerService.class); + + private final ComposerRepository composerRepository; + + public ComposerService(ComposerRepository composerRepository) { + this.composerRepository = composerRepository; + } + + public Composer createComposer(String name) { + logger.info("Creating composer: {}", name); + if(name == null || name.trim().isEmpty()) { + throw new IllegalArgumentException("Composer name cannot be empty"); + } + Composer composer = new Composer(); + composer.setName(name.trim()); + return composerRepository.save(composer); + + + } + + public List getAllComposers() { + logger.info("Getting all composers"); + return composerRepository.findAll(); + } + + public Optional getComposerById(Integer id) { + logger.info("Getting composer by ID: {}", id); + return composerRepository.findById(id); + } + + public Optional updateComposer(Integer id, String name) { + logger.info("Updating composer ID {}: {}", id, name); + + if(name == null || name.trim().isEmpty()) { + throw new IllegalArgumentException("Composer name cannot be empty"); + } + + Optional existingComposer = composerRepository.findById(id); + + if(existingComposer.isEmpty()) { + logger.warn("Composer not found with ID: {}", id); + return Optional.empty(); + } + + Composer composer = existingComposer.get(); + composer.setName(name.trim()); + + Composer updated = composerRepository.update(composer); + return Optional.of(updated); + + } + + public boolean deleteComposer(Integer id) { + logger.info("Deleting composer ID: {}", id); + return composerRepository.deleteById(id); + } + + +} diff --git a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java index 69d36d6..eb4ae76 100644 --- a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java +++ b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java @@ -2,7 +2,9 @@ package com.mediamanager.service.delegate; import com.google.protobuf.ByteString; import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.repository.ComposerRepository; import com.mediamanager.repository.GenreRepository; +import com.mediamanager.service.composer.ComposerService; import com.mediamanager.service.delegate.annotation.Action; import com.mediamanager.service.genre.GenreService; @@ -49,6 +51,15 @@ public class DelegateActionManager { serviceLocator.register(GenreService.class, genreService); + + + + + ComposerRepository composerRepository = new ComposerRepository(entityManagerFactory); + ComposerService composerService = new ComposerService(composerRepository); + serviceLocator.register(ComposerService.class, composerService); + + serviceLocator.logRegisteredServices(); logger.info("Services initialized successfully"); diff --git a/src/main/java/com/mediamanager/service/delegate/handler/composer/CreateComposerHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/composer/CreateComposerHandler.java new file mode 100644 index 0000000..5ec5658 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/composer/CreateComposerHandler.java @@ -0,0 +1,51 @@ +package com.mediamanager.service.delegate.handler.composer; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.ComposerMapper; +import com.mediamanager.model.Composer; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.ComposerMessages; +import com.mediamanager.service.composer.ComposerService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@Action("composer.create") +public class CreateComposerHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(CreateComposerHandler.class); + private final ComposerService composerService; + + public CreateComposerHandler(ComposerService composerService) { + this.composerService = composerService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException { + try{ + ComposerMessages.CreateComposerRequest CreateRequest = ComposerMessages.CreateComposerRequest + .parseFrom(requestPayload); + Composer composer = composerService.createComposer(CreateRequest.getName()); + ComposerMessages.Composer composerProto = ComposerMapper.toProtobuf(composer); + ComposerMessages.CreateComposerResponse createResponse = ComposerMessages.CreateComposerResponse + .newBuilder().setComposer(composerProto).build(); + return TransportProtocol.Response.newBuilder().setPayload(createResponse.toByteString()); + + } catch (IllegalArgumentException e){ + logger.error("Validation error", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(400) + .setPayload(ByteString.copyFromUtf8("Validation error: " + e.getMessage())); + + } catch (Exception e){ + logger.error("Error creating composer", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + + } +} diff --git a/src/main/java/com/mediamanager/service/delegate/handler/composer/DeleteComposerHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/composer/DeleteComposerHandler.java new file mode 100644 index 0000000..b159427 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/composer/DeleteComposerHandler.java @@ -0,0 +1,53 @@ +package com.mediamanager.service.delegate.handler.composer; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.ComposerMessages; +import com.mediamanager.service.composer.ComposerService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@Action( "composer.delete") +public class DeleteComposerHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(DeleteComposerHandler.class); + private final ComposerService composerService; + + + public DeleteComposerHandler(ComposerService composerService){ + this.composerService = composerService; + } + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException { + try { + ComposerMessages.DeleteComposerRequest deleteRequest = + ComposerMessages.DeleteComposerRequest.parseFrom(requestPayload); + int id = deleteRequest.getId(); + boolean success = composerService.deleteComposer(id); + ComposerMessages.DeleteComposerResponse deleteResponse; + if(success){ + deleteResponse = ComposerMessages.DeleteComposerResponse.newBuilder().setSuccess(true) + .setMessage("Composer deleted successfully").build(); + + + return TransportProtocol.Response.newBuilder() + .setPayload(deleteResponse.toByteString()); + }else { + deleteResponse = ComposerMessages.DeleteComposerResponse.newBuilder().setSuccess(false) + .setMessage("Composer not found").build(); + return TransportProtocol.Response.newBuilder().setStatusCode(404) + .setPayload(deleteResponse.toByteString()); + } + + } catch (Exception e) { + logger.error("Error deleting composer", e); + ComposerMessages.DeleteComposerResponse deleteResponse = ComposerMessages.DeleteComposerResponse + .newBuilder().setSuccess(false).setMessage("Error: " + e.getMessage()).build(); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500).setPayload(deleteResponse.toByteString()); + } + } +} diff --git a/src/main/java/com/mediamanager/service/delegate/handler/composer/GetComposerByIdHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/composer/GetComposerByIdHandler.java new file mode 100644 index 0000000..d1336b9 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/composer/GetComposerByIdHandler.java @@ -0,0 +1,57 @@ +package com.mediamanager.service.delegate.handler.composer; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.ComposerMapper; +import com.mediamanager.model.Composer; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.ComposerMessages; +import com.mediamanager.service.composer.ComposerService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +@Action( "composer.getById") +public class GetComposerByIdHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(GetComposerByIdHandler.class); + private final ComposerService composerService; + + public GetComposerByIdHandler(ComposerService composerService){ + this.composerService = composerService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException { + try{ + + ComposerMessages.GetComposerByIdRequest getByIdRequest = ComposerMessages.GetComposerByIdRequest + .parseFrom(requestPayload); + int id = getByIdRequest.getId(); + + Optional composerOpt = composerService.getComposerById(id); + + if (composerOpt.isEmpty()) { + logger.warn("Composer not found with ID: {}", id); + return TransportProtocol.Response.newBuilder() + .setStatusCode(404) + .setPayload(ByteString.copyFromUtf8("Composer not found")); + } + + ComposerMessages.Composer composerProto = ComposerMapper.toProtobuf(composerOpt.get()); + + ComposerMessages.GetComposerByIdResponse getByIdResponse = ComposerMessages.GetComposerByIdResponse + .newBuilder().setComposer(composerProto).build(); + + return TransportProtocol.Response.newBuilder().setPayload(getByIdResponse.toByteString()); + } catch (Exception e) { + logger.error("Error getting composer by ID", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + } +} diff --git a/src/main/java/com/mediamanager/service/delegate/handler/composer/GetComposerHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/composer/GetComposerHandler.java new file mode 100644 index 0000000..0d41487 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/composer/GetComposerHandler.java @@ -0,0 +1,50 @@ +package com.mediamanager.service.delegate.handler.composer; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.ComposerMapper; +import com.mediamanager.model.Composer; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.ComposerMessages; +import com.mediamanager.protocol.messages.GenreMessages; +import com.mediamanager.service.composer.ComposerService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; + +@Action( "composer.getAll") +public class GetComposerHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(GetComposerHandler.class); + private final ComposerService composerService; + + public GetComposerHandler(ComposerService composerService){ + this.composerService = composerService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException { + + try{ + List composers = composerService.getAllComposers(); + + ComposerMessages.GetComposersResponse.Builder responseBuilder = ComposerMessages.GetComposersResponse.newBuilder(); + for(Composer composer : composers){ + ComposerMessages.Composer composerProto = ComposerMapper.toProtobuf(composer); + responseBuilder.addComposers(composerProto); + } + ComposerMessages.GetComposersResponse getGenresResponse = responseBuilder.build(); + return TransportProtocol.Response.newBuilder().setPayload(getGenresResponse.toByteString()); + } catch (Exception e) { + logger.error("Error getting composers", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + + } + +} diff --git a/src/main/java/com/mediamanager/service/delegate/handler/composer/UpdateComposerHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/composer/UpdateComposerHandler.java new file mode 100644 index 0000000..d81af26 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/composer/UpdateComposerHandler.java @@ -0,0 +1,56 @@ +package com.mediamanager.service.delegate.handler.composer; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.ComposerMapper; +import com.mediamanager.model.Composer; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.ComposerMessages; +import com.mediamanager.service.composer.ComposerService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +@Action("composer.update") +public class UpdateComposerHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(UpdateComposerHandler.class); + private final ComposerService composerService; + public UpdateComposerHandler(ComposerService composerService){ + this.composerService = composerService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException { + try{ + ComposerMessages.UpdateComposerRequest updateRequest = ComposerMessages.UpdateComposerRequest + .parseFrom(requestPayload); + int id = updateRequest.getId(); + String newName = updateRequest.getName(); + Optional composerOpt = composerService.updateComposer(id, newName); + if (composerOpt.isEmpty()) { + logger.warn("Composer not found with ID: {}", id); + return TransportProtocol.Response.newBuilder() + .setStatusCode(404) + .setPayload(ByteString.copyFromUtf8("Composer not found")); + } + ComposerMessages.Composer composerProto = ComposerMapper.toProtobuf(composerOpt.get()); + ComposerMessages.UpdateComposerResponse updateResponse = ComposerMessages.UpdateComposerResponse + .newBuilder().setComposer(composerProto).build(); + return TransportProtocol.Response.newBuilder().setPayload(updateResponse.toByteString()); + }catch (IllegalArgumentException e){ + logger.error("Validation error", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(400) + .setPayload(ByteString.copyFromUtf8("Validation error: " + e.getMessage())); + } catch (Exception e) { + logger.error("Error updating composer", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + } +} diff --git a/src/main/proto/composer.proto b/src/main/proto/composer.proto new file mode 100644 index 0000000..ce82fba --- /dev/null +++ b/src/main/proto/composer.proto @@ -0,0 +1,51 @@ +syntax = "proto3"; + +option java_package = "com.mediamanager.protocol.messages"; +option java_outer_classname = "ComposerMessages"; + +package mediamanager.messages; + +message Composer { + int32 id =1; + string name =2; +} + +message CreateComposerRequest{ + string name =1; +} + +message CreateComposerResponse{ + Composer composer = 1; +} + +message GetComposersRequest{} + +message GetComposersResponse{ + repeated Composer composers = 1; +} + +message GetComposerByIdRequest{ + int32 id =1; +} + +message GetComposerByIdResponse{ + Composer composer =1; +} + +message UpdateComposerRequest{ + int32 id = 1; + string name=2; +} + +message UpdateComposerResponse{ + Composer composer = 1; +} + +message DeleteComposerRequest{ + int32 id = 1; +} + +message DeleteComposerResponse{ + bool success = 1; + string message = 2; +} From 2d77358d50e085e285550ef93e378948e57d0375 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Wed, 3 Dec 2025 03:15:39 -0300 Subject: [PATCH 34/39] Implement BitDepth Management with CRUD Operations - Add `BitDepth` entity with JPA annotations and database mapping. - Create repository, service, and delegate handlers for BitDepth CRUD operations: `bitdepth.create`, `bitdepth.getAll`, `bitdepth.getById`, `bitdepth.update`, `bitdepth.delete`. - Introduce Protobuf definitions for BitDepth messages. - Register BitDepth service and handlers in `DelegateActionManager`. - Create `BitDepthMapper` to map between Protobuf and entity models. - Enhance error handling and logging for BitDepth operations. --- .../mediamanager/mapper/BitDepthMapper.java | 39 +++++++ .../java/com/mediamanager/model/BitDepth.java | 32 ++++++ .../repository/BitDepthRepository.java | 100 ++++++++++++++++++ .../service/bitdepth/BitDepthService.java | 61 +++++++++++ .../delegate/DelegateActionManager.java | 12 +-- .../bitdepth/CreateBitDepthHandler.java | 51 +++++++++ .../bitdepth/DeleteBitDepthHandler.java | 62 +++++++++++ .../bitdepth/GetBitDepthByIdHandler.java | 56 ++++++++++ .../handler/bitdepth/GetBitDepthHandler.java | 48 +++++++++ .../bitdepth/UpdateBitDepthHandler.java | 65 ++++++++++++ src/main/proto/bitdepth.proto | 53 ++++++++++ 11 files changed, 572 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/mediamanager/mapper/BitDepthMapper.java create mode 100644 src/main/java/com/mediamanager/model/BitDepth.java create mode 100644 src/main/java/com/mediamanager/repository/BitDepthRepository.java create mode 100644 src/main/java/com/mediamanager/service/bitdepth/BitDepthService.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/bitdepth/CreateBitDepthHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/bitdepth/DeleteBitDepthHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/bitdepth/GetBitDepthByIdHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/bitdepth/GetBitDepthHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/bitdepth/UpdateBitDepthHandler.java create mode 100644 src/main/proto/bitdepth.proto diff --git a/src/main/java/com/mediamanager/mapper/BitDepthMapper.java b/src/main/java/com/mediamanager/mapper/BitDepthMapper.java new file mode 100644 index 0000000..873629c --- /dev/null +++ b/src/main/java/com/mediamanager/mapper/BitDepthMapper.java @@ -0,0 +1,39 @@ +package com.mediamanager.mapper; + +import com.mediamanager.model.BitDepth; +import com.mediamanager.protocol.messages.BitDepthMessages; + +public class BitDepthMapper { + public static BitDepthMessages.BitDepth toProtobuf(BitDepth entity) { + if (entity == null){ + return null; + } + + String value = entity.getValue(); + if (value == null) { + throw new IllegalArgumentException("Bit depth value cannot be null"); + } + BitDepthMessages.BitDepth.Builder builder = BitDepthMessages.BitDepth.newBuilder() + .setValue(value); + + Integer id = entity.getId(); + if (id != null && id > 0) { + builder.setId(id); + } + + return builder.build(); + } + + public static BitDepth toEntity(BitDepthMessages.BitDepth protobuf) { + if (protobuf == null) { + return null; + } + BitDepth entity = new BitDepth(); + + if (protobuf.getId() > 0) { + entity.setId(protobuf.getId()); + } + entity.setValue(protobuf.getValue()); + return entity; + } +} diff --git a/src/main/java/com/mediamanager/model/BitDepth.java b/src/main/java/com/mediamanager/model/BitDepth.java new file mode 100644 index 0000000..3b80217 --- /dev/null +++ b/src/main/java/com/mediamanager/model/BitDepth.java @@ -0,0 +1,32 @@ +package com.mediamanager.model; + +import jakarta.persistence.*; + +@Entity +@Table(name = "bit_depth") +public class BitDepth { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Column(nullable = false) + private String value; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} + diff --git a/src/main/java/com/mediamanager/repository/BitDepthRepository.java b/src/main/java/com/mediamanager/repository/BitDepthRepository.java new file mode 100644 index 0000000..24b9dc9 --- /dev/null +++ b/src/main/java/com/mediamanager/repository/BitDepthRepository.java @@ -0,0 +1,100 @@ +package com.mediamanager.repository; + +import com.mediamanager.model.BitDepth; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.Optional; + +public class BitDepthRepository { + private static final Logger logger = LogManager.getLogger(BitDepthRepository.class); + + private final EntityManagerFactory entityManagerFactory; + + public BitDepthRepository(EntityManagerFactory entityManagerFactory) { + this.entityManagerFactory = entityManagerFactory; + } + + public BitDepth save(BitDepth bitDepth) { + logger.debug("Saving BitDepth: {}", bitDepth.getValue()); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + try { + em.persist(bitDepth); + em.getTransaction().commit(); + logger.debug("BitDepth saved with ID: {}", bitDepth.getId()); + return bitDepth; + } catch (Exception e) { + em.getTransaction().rollback(); + logger.error("Error saving BitDepth", e); + throw e; + } finally { + if (em.isOpen()) em.close(); + } + } + + public List findAll(){ + logger.debug("Finding all BitDepths"); + EntityManager em = entityManagerFactory.createEntityManager(); + try{ + return em.createQuery("SELECT b FROM BitDepth b ORDER BY b.value", BitDepth.class).getResultList(); + }finally { + if (em.isOpen()) em.close(); + } + } + + public Optional findById(Integer id){ + logger.debug("Finding BitDepth by ID: {}", id); + EntityManager em = entityManagerFactory.createEntityManager(); + try{ + BitDepth bitDepth = em.find(BitDepth.class, id); + return Optional.ofNullable(bitDepth); + }finally { + if (em.isOpen()) em.close(); + } + } + + public BitDepth update(BitDepth bitDepth){ + logger.debug("Updating BitDepth ID: {}", bitDepth.getId()); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + try{ + BitDepth updated = em.merge(bitDepth); + em.getTransaction().commit(); + logger.debug("BitDepth updated successfully"); + return updated; + }catch (Exception e){ + em.getTransaction().rollback(); + logger.error("Error updating BitDepth", e); + throw e; + }finally { + if (em.isOpen()) em.close(); + } + } + + public boolean deleteById(Integer id){ + logger.debug("Deleting BitDepth by ID: {}", id); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + try{ + BitDepth bitDepth = em.find(BitDepth.class, id); + if (bitDepth == null) { + em.getTransaction().rollback(); + return false; + } + em.remove(bitDepth); + em.getTransaction().commit(); + logger.debug("BitDepth deleted successfully"); + return true; + } catch (Exception e) { + em.getTransaction().rollback(); + logger.error("Error deleting BitDepth", e); + throw e; + }finally { + if (em.isOpen()) em.close(); + } + } +} diff --git a/src/main/java/com/mediamanager/service/bitdepth/BitDepthService.java b/src/main/java/com/mediamanager/service/bitdepth/BitDepthService.java new file mode 100644 index 0000000..495e5d0 --- /dev/null +++ b/src/main/java/com/mediamanager/service/bitdepth/BitDepthService.java @@ -0,0 +1,61 @@ +package com.mediamanager.service.bitdepth; + +import com.mediamanager.model.BitDepth; +import com.mediamanager.repository.BitDepthRepository; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.Optional; + +public class BitDepthService { + private static final Logger logger = LogManager.getLogger(BitDepthService.class); + private final BitDepthRepository bitDepthRepository; + + public BitDepthService(BitDepthRepository bitDepthRepository) { + this.bitDepthRepository = bitDepthRepository; + } + public BitDepth createBitDepth(String value){ + logger.info("Creating bit-depth: {}", value); + if (value == null || value.trim().isEmpty()) { + throw new IllegalArgumentException("Bit-depth value cannot be empty"); + } + BitDepth bitDepth = new BitDepth(); + bitDepth.setValue(value.trim()); + return bitDepthRepository.save(bitDepth); + + } + + public List getAllBitDepths(){ + logger.info("Getting all bit-depths"); + return bitDepthRepository.findAll(); + } + + public Optional getBitDepthById(Integer id){ + logger.info("Getting bit-depth by ID: {}", id); + return bitDepthRepository.findById(id); + } + + public Optional updateBitDepth(Integer id, String value){ + logger.info("Updating bit-depth ID {}: {}", id, value); + + if (value == null || value.trim().isEmpty()) { + throw new IllegalArgumentException("Bit-depth value cannot be empty"); + } + Optional existingBitDepth = bitDepthRepository.findById(id); + if(existingBitDepth.isEmpty()){ + logger.warn("Bit-depth not found with ID: {}", id); + return Optional.empty(); + } + BitDepth bitDepth = existingBitDepth.get(); + bitDepth.setValue(value.trim()); + BitDepth updatedBitDepth = bitDepthRepository.update(bitDepth); + return Optional.of(updatedBitDepth); + + } + + public boolean deleteBitDepth(Integer id){ + logger.info("Deleting bit-depth ID: {}", id); + return bitDepthRepository.deleteById(id); + } +} diff --git a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java index ee5ae38..338d6a7 100644 --- a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java +++ b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java @@ -2,10 +2,9 @@ package com.mediamanager.service.delegate; import com.google.protobuf.ByteString; import com.mediamanager.protocol.TransportProtocol; -import com.mediamanager.repository.ComposerRepository; -import com.mediamanager.repository.GenreRepository; +import com.mediamanager.repository.*; +import com.mediamanager.service.bitdepth.BitDepthService; import com.mediamanager.service.composer.ComposerService; -import com.mediamanager.repository.ArtistRepository; import com.mediamanager.repository.GenreRepository; import com.mediamanager.service.artist.ArtistService; import com.mediamanager.service.delegate.annotation.Action; @@ -58,14 +57,13 @@ public class DelegateActionManager { serviceLocator.register(ArtistService.class, artistService); - - - - ComposerRepository composerRepository = new ComposerRepository(entityManagerFactory); ComposerService composerService = new ComposerService(composerRepository); serviceLocator.register(ComposerService.class, composerService); + BitDepthRepository bitDepthRepository = new BitDepthRepository(entityManagerFactory); + BitDepthService bitDepthService = new BitDepthService(bitDepthRepository); + serviceLocator.register(BitDepthService.class, bitDepthService); serviceLocator.logRegisteredServices(); diff --git a/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/CreateBitDepthHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/CreateBitDepthHandler.java new file mode 100644 index 0000000..726fa64 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/CreateBitDepthHandler.java @@ -0,0 +1,51 @@ +package com.mediamanager.service.delegate.handler.bitdepth; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.BitDepthMapper; +import com.mediamanager.model.BitDepth; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.ArtistMessages; +import com.mediamanager.protocol.messages.BitDepthMessages; +import com.mediamanager.service.bitdepth.BitDepthService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@Action("bitdepth.create") +public class CreateBitDepthHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(CreateBitDepthHandler.class); + private final BitDepthService bitDepthService; + + public CreateBitDepthHandler(BitDepthService bitDepthService) { + this.bitDepthService = bitDepthService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException { + try{ + BitDepthMessages.CreateBitDepthRequest createRequest = + BitDepthMessages.CreateBitDepthRequest.parseFrom(requestPayload); + BitDepth bitDepth = bitDepthService.createBitDepth(createRequest.getValue()); + BitDepthMessages.BitDepth BitDepthProto = BitDepthMapper.toProtobuf(bitDepth); + BitDepthMessages.CreateBitDepthResponse createBitDepthResponse = BitDepthMessages.CreateBitDepthResponse.newBuilder() + .setBitdepth(BitDepthProto) + .build(); + return TransportProtocol.Response.newBuilder() + .setPayload(createBitDepthResponse.toByteString()); + } catch (IllegalArgumentException e) { + logger.error("Validation error", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(400) + .setPayload(ByteString.copyFromUtf8("Validation error: " + e.getMessage())); + + } catch (Exception e) { + logger.error("Error creating artist", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + } +} + diff --git a/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/DeleteBitDepthHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/DeleteBitDepthHandler.java new file mode 100644 index 0000000..b71a9a1 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/DeleteBitDepthHandler.java @@ -0,0 +1,62 @@ +package com.mediamanager.service.delegate.handler.bitdepth; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.BitDepthMessages; +import com.mediamanager.service.bitdepth.BitDepthService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@Action("bitdepth.delete") +public class DeleteBitDepthHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(DeleteBitDepthHandler.class); + + private final BitDepthService bitDepthService; + + public DeleteBitDepthHandler(BitDepthService bitDepthService) { + this.bitDepthService = bitDepthService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException { + + try { + BitDepthMessages.DeleteBitDepthRequest deleteRequest = + BitDepthMessages.DeleteBitDepthRequest.parseFrom(requestPayload); + int id = deleteRequest.getId(); + boolean success = bitDepthService.deleteBitDepth(id); + BitDepthMessages.DeleteBitDepthResponse deleteResponse; + if (success) { + deleteResponse = BitDepthMessages.DeleteBitDepthResponse.newBuilder() + .setSuccess(true) + .setMessage("Bit-Depth deleted successfully") + .build(); + return TransportProtocol.Response.newBuilder() + .setPayload(deleteResponse.toByteString()); + } else { + deleteResponse = BitDepthMessages.DeleteBitDepthResponse.newBuilder() + .setSuccess(false) + .setMessage("Bit-Depth not found") + .build(); + + return TransportProtocol.Response.newBuilder() + .setStatusCode(404) + .setPayload(deleteResponse.toByteString()); + } + } catch (Exception e) { + logger.error("Error deleting bit-depth", e); + BitDepthMessages.DeleteBitDepthResponse deleteResponse = + BitDepthMessages.DeleteBitDepthResponse.newBuilder() + .setSuccess(false) + .setMessage("Error: " + e.getMessage()) + .build(); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(deleteResponse.toByteString()); + } + } + } diff --git a/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/GetBitDepthByIdHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/GetBitDepthByIdHandler.java new file mode 100644 index 0000000..96e630d --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/GetBitDepthByIdHandler.java @@ -0,0 +1,56 @@ +package com.mediamanager.service.delegate.handler.bitdepth; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.BitDepthMapper; +import com.mediamanager.model.BitDepth; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.BitDepthMessages; +import com.mediamanager.service.bitdepth.BitDepthService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +@Action(value = "bitdepth.getById") +public class GetBitDepthByIdHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(GetBitDepthByIdHandler.class); + private final BitDepthService bitDepthServicee; + + public GetBitDepthByIdHandler(BitDepthService bitDepthServicee) { + this.bitDepthServicee = bitDepthServicee; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException{ + + try{ + BitDepthMessages.GetBitDepthByIdRequest getByIdRequest = + BitDepthMessages.GetBitDepthByIdRequest.parseFrom(requestPayload); + int id = getByIdRequest.getId(); + + Optional bitDepthOpt = bitDepthServicee.getBitDepthById(id); + + if (bitDepthOpt.isEmpty()){ + logger.warn("BitDepth not found with ID: {}", id); + return TransportProtocol.Response.newBuilder() + .setStatusCode(404) + .setPayload(ByteString.copyFromUtf8("BitDepth not found")); + } + BitDepthMessages.BitDepth bitDepthProto = BitDepthMapper.toProtobuf(bitDepthOpt.get()); + BitDepthMessages.GetBitDepthByIdResponse getByIdResponse = BitDepthMessages.GetBitDepthByIdResponse.newBuilder() + .setBitdepth(bitDepthProto) + .build(); + return TransportProtocol.Response.newBuilder() + .setPayload(getByIdResponse.toByteString()); + } catch (Exception e) { + logger.error("Error getting bit-depth by ID", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: "+ e.getMessage())); + } + } +} diff --git a/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/GetBitDepthHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/GetBitDepthHandler.java new file mode 100644 index 0000000..71cf92f --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/GetBitDepthHandler.java @@ -0,0 +1,48 @@ +package com.mediamanager.service.delegate.handler.bitdepth; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.BitDepthMapper; +import com.mediamanager.model.BitDepth; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.BitDepthMessages; +import com.mediamanager.service.bitdepth.BitDepthService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; + + +@Action("bitdepth.getAll") +public class GetBitDepthHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(GetBitDepthHandler.class); + + private final BitDepthService bitDepthService; + + public GetBitDepthHandler(BitDepthService bitDepthService){this.bitDepthService = bitDepthService;} + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException { + try{ + List bitDepths = bitDepthService.getAllBitDepths(); + BitDepthMessages.GetBitDepthsResponse.Builder responseBuilder = BitDepthMessages.GetBitDepthsResponse.newBuilder(); + + for (BitDepth bitDepth : bitDepths) { + BitDepthMessages.BitDepth bitDepthProto = BitDepthMapper.toProtobuf(bitDepth); + responseBuilder.addBitdepths(bitDepthProto); + } + BitDepthMessages.GetBitDepthsResponse getBitDepthsResponse = responseBuilder.build(); + + return TransportProtocol.Response.newBuilder() + .setPayload(getBitDepthsResponse.toByteString()); + + }catch (Exception e){ + logger.error("Error getting bit-depths", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + } +} diff --git a/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/UpdateBitDepthHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/UpdateBitDepthHandler.java new file mode 100644 index 0000000..6150e64 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/UpdateBitDepthHandler.java @@ -0,0 +1,65 @@ +package com.mediamanager.service.delegate.handler.bitdepth; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.BitDepthMapper; +import com.mediamanager.model.BitDepth; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.BitDepthMessages; +import com.mediamanager.service.bitdepth.BitDepthService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +@Action("bitdepth.update") +public class UpdateBitDepthHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(UpdateBitDepthHandler.class); + private final BitDepthService bitDepthService; + + public UpdateBitDepthHandler(BitDepthService bitDepthService) { + this.bitDepthService = bitDepthService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException { + try{ + BitDepthMessages.UpdateBitDepthRequest updateRequest = + BitDepthMessages.UpdateBitDepthRequest.parseFrom(requestPayload); + + int id = updateRequest.getId(); + String newValue = updateRequest.getValue(); + + Optional bitDepthOpt = bitDepthService.updateBitDepth(id, newValue); + + if(bitDepthOpt.isEmpty()){ + logger.warn("BitDepth not found with ID: {}", id); + return TransportProtocol.Response.newBuilder() + .setStatusCode(404) + .setPayload(ByteString.copyFromUtf8("Artist not found")); + } + + BitDepthMessages.BitDepth bitDepthProto = BitDepthMapper.toProtobuf(bitDepthOpt.get()); + + BitDepthMessages.UpdateBitDepthResponse updateResponse = BitDepthMessages.UpdateBitDepthResponse.newBuilder() + .setBitdepth(bitDepthProto) + .build(); + + return TransportProtocol.Response.newBuilder() + .setPayload(updateResponse.toByteString()); + + } catch (IllegalArgumentException e){ + logger.error("Validation error", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(400) + .setPayload(ByteString.copyFromUtf8("Validation error: " + e.getMessage())); + } catch (Exception e) { + logger.error("Error updating bit-depth", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + } +} diff --git a/src/main/proto/bitdepth.proto b/src/main/proto/bitdepth.proto new file mode 100644 index 0000000..571dc9a --- /dev/null +++ b/src/main/proto/bitdepth.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; + +option java_package = "com.mediamanager.protocol.messages"; +option java_outer_classname = "BitDepthMessages"; + +package mediamanager.messages; + +message BitDepth { + int32 id = 1; + string value = 2; +} + +message CreateBitDepthRequest { + string value = 1; +} + +message CreateBitDepthResponse { + BitDepth bitdepth = 1; +} + +message GetBitDepthsRequest { + +} + +message GetBitDepthsResponse { + repeated BitDepth bitdepths = 1; +} + +message GetBitDepthByIdRequest { + int32 id = 1; +} + +message GetBitDepthByIdResponse { + BitDepth bitdepth = 1; +} + +message UpdateBitDepthRequest { + int32 id = 1; + string value = 2; // Novo nome +} + +message UpdateBitDepthResponse { + BitDepth bitdepth = 1; +} + +message DeleteBitDepthRequest { + int32 id = 1; +} + +message DeleteBitDepthResponse { + bool success = 1; + string message = 2; +} From cd4b35f3c9ac370927eb33b188a42679b12c6b0f Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Miranda Date: Fri, 5 Dec 2025 04:54:29 -0300 Subject: [PATCH 35/39] Update src/main/java/com/mediamanager/service/delegate/handler/bitdepth/CreateBitDepthHandler.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../delegate/handler/bitdepth/CreateBitDepthHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/CreateBitDepthHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/CreateBitDepthHandler.java index 726fa64..e055baf 100644 --- a/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/CreateBitDepthHandler.java +++ b/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/CreateBitDepthHandler.java @@ -41,7 +41,8 @@ public class CreateBitDepthHandler implements ActionHandler { .setPayload(ByteString.copyFromUtf8("Validation error: " + e.getMessage())); } catch (Exception e) { - logger.error("Error creating artist", e); + logger.error("Error creating bit-depth", e); + return TransportProtocol.Response.newBuilder() return TransportProtocol.Response.newBuilder() .setStatusCode(500) .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); From 93c458d85698aafb0eb1befa8c34920f93462adb Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Miranda Date: Fri, 5 Dec 2025 04:54:40 -0300 Subject: [PATCH 36/39] Update src/main/java/com/mediamanager/service/delegate/handler/bitdepth/UpdateBitDepthHandler.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../delegate/handler/bitdepth/UpdateBitDepthHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/UpdateBitDepthHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/UpdateBitDepthHandler.java index 6150e64..c786b12 100644 --- a/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/UpdateBitDepthHandler.java +++ b/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/UpdateBitDepthHandler.java @@ -38,7 +38,7 @@ public class UpdateBitDepthHandler implements ActionHandler { logger.warn("BitDepth not found with ID: {}", id); return TransportProtocol.Response.newBuilder() .setStatusCode(404) - .setPayload(ByteString.copyFromUtf8("Artist not found")); + .setPayload(ByteString.copyFromUtf8("BitDepth not found")); } BitDepthMessages.BitDepth bitDepthProto = BitDepthMapper.toProtobuf(bitDepthOpt.get()); From 92b4b21e60991058fec06ebd857ac83b7a9ab842 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Fri, 5 Dec 2025 17:02:02 -0300 Subject: [PATCH 37/39] Remove duplicate `TransportProtocol.Response.newBuilder()` call in `CreateBitDepthHandler`. --- .../service/delegate/handler/bitdepth/CreateBitDepthHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/CreateBitDepthHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/CreateBitDepthHandler.java index e055baf..db9e034 100644 --- a/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/CreateBitDepthHandler.java +++ b/src/main/java/com/mediamanager/service/delegate/handler/bitdepth/CreateBitDepthHandler.java @@ -42,7 +42,6 @@ public class CreateBitDepthHandler implements ActionHandler { } catch (Exception e) { logger.error("Error creating bit-depth", e); - return TransportProtocol.Response.newBuilder() return TransportProtocol.Response.newBuilder() .setStatusCode(500) .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); From 2294a57ffddacc1755c69d4f71de8dfeae1a15d3 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Fri, 5 Dec 2025 20:43:04 -0300 Subject: [PATCH 38/39] Implement bit-rate management feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add complete CRUD functionality for bit-rate management following the same pattern as bit-depth: - Create BitRate entity model with JPA annotations - Implement BitRateRepository with full CRUD operations - Add BitRateService with validation and business logic - Create BitRateMapper for protobuf/entity conversion - Implement action handlers: Create, GetAll, GetById, Update, Delete - Define bitrate.proto protobuf message definitions - Register BitRateService in DelegateActionManager 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../mediamanager/mapper/BitRateMapper.java | 39 +++++++ .../java/com/mediamanager/model/BitRate.java | 31 ++++++ .../repository/BitRateRepository.java | 101 ++++++++++++++++++ .../service/bitrate/BitRateService.java | 65 +++++++++++ .../delegate/DelegateActionManager.java | 5 + .../handler/bitrate/CreateBitRateHandler.java | 50 +++++++++ .../handler/bitrate/DeleteBitRateHandler.java | 62 +++++++++++ .../bitrate/GetBitRateByIdHandler.java | 56 ++++++++++ .../handler/bitrate/GetBitRateHandler.java | 48 +++++++++ .../handler/bitrate/UpdateBitRateHandler.java | 65 +++++++++++ src/main/proto/bitrate.proto | 53 +++++++++ 11 files changed, 575 insertions(+) create mode 100644 src/main/java/com/mediamanager/mapper/BitRateMapper.java create mode 100644 src/main/java/com/mediamanager/model/BitRate.java create mode 100644 src/main/java/com/mediamanager/repository/BitRateRepository.java create mode 100644 src/main/java/com/mediamanager/service/bitrate/BitRateService.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/bitrate/CreateBitRateHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/bitrate/DeleteBitRateHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/bitrate/GetBitRateByIdHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/bitrate/GetBitRateHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/bitrate/UpdateBitRateHandler.java create mode 100644 src/main/proto/bitrate.proto diff --git a/src/main/java/com/mediamanager/mapper/BitRateMapper.java b/src/main/java/com/mediamanager/mapper/BitRateMapper.java new file mode 100644 index 0000000..b6e5c5c --- /dev/null +++ b/src/main/java/com/mediamanager/mapper/BitRateMapper.java @@ -0,0 +1,39 @@ +package com.mediamanager.mapper; + +import com.mediamanager.model.BitRate; +import com.mediamanager.protocol.messages.BitRateMessages; + +public class BitRateMapper { + public static BitRateMessages.BitRate toProtobuf(BitRate entity) { + if (entity == null) { + return null; + } + + String value = entity.getValue(); + if (value == null) { + throw new IllegalArgumentException("Bit rate value cannot be null"); + } + + BitRateMessages.BitRate.Builder builder = BitRateMessages.BitRate.newBuilder() + .setValue(value); + + Integer id = entity.getId(); + if (id != null && id > 0) { + builder.setId(id); + } + return builder.build(); + } + + public static BitRate toEntity(BitRateMessages.BitRate protobuf) { + if (protobuf == null) { + return null; + } + BitRate entity = new BitRate(); + + if (protobuf.getId() > 0) { + entity.setId(protobuf.getId()); + } + entity.setValue(protobuf.getValue()); + return entity; + } +} diff --git a/src/main/java/com/mediamanager/model/BitRate.java b/src/main/java/com/mediamanager/model/BitRate.java new file mode 100644 index 0000000..c3f6fa2 --- /dev/null +++ b/src/main/java/com/mediamanager/model/BitRate.java @@ -0,0 +1,31 @@ +package com.mediamanager.model; + +import jakarta.persistence.*; + +@Entity +@Table(name = "bit_rate") +public class BitRate { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Column(nullable = false) + private String value; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/src/main/java/com/mediamanager/repository/BitRateRepository.java b/src/main/java/com/mediamanager/repository/BitRateRepository.java new file mode 100644 index 0000000..42c5343 --- /dev/null +++ b/src/main/java/com/mediamanager/repository/BitRateRepository.java @@ -0,0 +1,101 @@ +package com.mediamanager.repository; + +import com.mediamanager.model.BitRate; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.Optional; + +public class BitRateRepository { + private static final Logger logger = LogManager.getLogger(BitRateRepository.class); + + private final EntityManagerFactory entityManagerFactory; + + public BitRateRepository(EntityManagerFactory entityManagerFactory) { + this.entityManagerFactory = entityManagerFactory; + } + + public BitRate save(BitRate bitRate) { + logger.debug("Saving BitRate: {}", bitRate.getValue()); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + try { + em.persist(bitRate); + em.getTransaction().commit(); + logger.debug("BitRate saved with ID: {}", bitRate.getId()); + return bitRate; + } catch (Exception e) { + em.getTransaction().rollback(); + logger.error("Error saving BitRate", e); + throw e; + } finally { + if (em.isOpen()) em.close(); + } + } + + public List findAll(){ + logger.debug("Finding all BitRates"); + EntityManager em = entityManagerFactory.createEntityManager(); + try{ + return em.createQuery("SELECT b FROM BitRate b ORDER BY b.value", BitRate.class).getResultList(); + }finally { + if (em.isOpen()) em.close(); + } + } + + public Optional findById(Integer id){ + logger.debug("Finding BitRate by ID: {}", id); + EntityManager em = entityManagerFactory.createEntityManager(); + try{ + BitRate bitRate = em.find(BitRate.class, id); + return Optional.ofNullable(bitRate); + } finally { + if (em.isOpen()) em.close(); + } + } + + public BitRate update(BitRate bitRate){ + logger.debug("Updating BitRate ID: {}", bitRate.getId()); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + try{ + BitRate updated = em.merge(bitRate); + em.getTransaction().commit(); + logger.debug("BitRate updated with ID: {}", bitRate.getId()); + return updated; + } catch (Exception e) { + em.getTransaction().rollback(); + logger.error("Error updating BitRate", e); + throw e; + } finally { + if (em.isOpen()) em.close(); + } + } + + public boolean deleteById(Integer id){ + logger.debug("Deleting BitRate by ID: {}", id); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + try{ + BitRate bitRate = em.find(BitRate.class, id); + if (bitRate == null) { + em.getTransaction().rollback(); + return false; + } + em.remove(bitRate); + em.getTransaction().commit(); + logger.debug("BitRate deleted successfully"); + return true; + } catch (Exception e) { + em.getTransaction().rollback(); + logger.error("Error deleting BitRate", e); + throw e; + } finally { + if (em.isOpen()) em.close(); + } + + } +} diff --git a/src/main/java/com/mediamanager/service/bitrate/BitRateService.java b/src/main/java/com/mediamanager/service/bitrate/BitRateService.java new file mode 100644 index 0000000..57e32db --- /dev/null +++ b/src/main/java/com/mediamanager/service/bitrate/BitRateService.java @@ -0,0 +1,65 @@ +package com.mediamanager.service.bitrate; + + +import com.mediamanager.model.BitRate; +import com.mediamanager.repository.BitRateRepository; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.Optional; + +public class BitRateService { + private static final Logger logger = LogManager.getLogger(BitRateService.class); + private final BitRateRepository bitRateRepository; + + public BitRateService(BitRateRepository bitRateRepository) { + this.bitRateRepository = bitRateRepository; + } + + public BitRate createBitRate(String value){ + logger.info("Creating bit-rate: {}",value); + if (value == null || value.trim().isEmpty()) { + throw new IllegalArgumentException("Bit-rate value cannot be empty"); + } + BitRate bitRate = new BitRate(); + bitRate.setValue(value.trim()); + return bitRateRepository.save(bitRate); + } + + public List getAllBitRates(){ + logger.info("Getting all bit-rates"); + return bitRateRepository.findAll(); + } + + public Optional getBitRateById(Integer id){ + logger.info("Getting bit-rate by ID: {}", id); + return bitRateRepository.findById(id); + } + + public Optional updateBitRate(Integer id, String value){ + logger.info("Updating bit-rate ID {}: {}", id, value); + + if (value == null || value.trim().isEmpty()) { + throw new IllegalArgumentException("Bit-rate value cannot be empty"); + } + + Optional existingBitRate = bitRateRepository.findById(id); + + if(existingBitRate.isEmpty()) { + logger.warn("Bit-rate not found with ID: {}", id); + return Optional.empty(); + } + BitRate bitRate = existingBitRate.get(); + bitRate.setValue(value.trim()); + + BitRate updatedBitRate = bitRateRepository.update(bitRate); + return Optional.of(updatedBitRate); + } + + public boolean deleteBitRate(Integer id){ + logger.info("Deleting bit-rate ID: {}", id); + return bitRateRepository.deleteById(id); + } + +} diff --git a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java index 338d6a7..67daf24 100644 --- a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java +++ b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java @@ -4,6 +4,7 @@ import com.google.protobuf.ByteString; import com.mediamanager.protocol.TransportProtocol; import com.mediamanager.repository.*; import com.mediamanager.service.bitdepth.BitDepthService; +import com.mediamanager.service.bitrate.BitRateService; import com.mediamanager.service.composer.ComposerService; import com.mediamanager.repository.GenreRepository; import com.mediamanager.service.artist.ArtistService; @@ -65,6 +66,10 @@ public class DelegateActionManager { BitDepthService bitDepthService = new BitDepthService(bitDepthRepository); serviceLocator.register(BitDepthService.class, bitDepthService); + BitRateRepository bitRateRepository = new BitRateRepository(entityManagerFactory); + BitRateService bitRateService = new BitRateService(bitRateRepository); + serviceLocator.register(BitRateService.class, bitRateService); + serviceLocator.logRegisteredServices(); logger.info("Services initialized successfully"); diff --git a/src/main/java/com/mediamanager/service/delegate/handler/bitrate/CreateBitRateHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/bitrate/CreateBitRateHandler.java new file mode 100644 index 0000000..cfa9d2b --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/bitrate/CreateBitRateHandler.java @@ -0,0 +1,50 @@ +package com.mediamanager.service.delegate.handler.bitrate; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.BitRateMapper; +import com.mediamanager.model.BitRate; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.BitRateMessages; +import com.mediamanager.service.bitrate.BitRateService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@Action("bitrate.create") +public class CreateBitRateHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(CreateBitRateHandler.class); + private final BitRateService bitRateService; + + public CreateBitRateHandler(BitRateService bitRateService) { + this.bitRateService = bitRateService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException { + try{ + BitRateMessages.CreateBitRateRequest createRequest = + BitRateMessages.CreateBitRateRequest.parseFrom(requestPayload); + BitRate bitRate = bitRateService.createBitRate(createRequest.getValue()); + BitRateMessages.BitRate BitRateProto = BitRateMapper.toProtobuf(bitRate); + BitRateMessages.CreateBitRateResponse createBitRateResponse = BitRateMessages.CreateBitRateResponse.newBuilder() + .setBitrate(BitRateProto) + .build(); + return TransportProtocol.Response.newBuilder() + .setPayload(createBitRateResponse.toByteString()); + } catch (IllegalArgumentException e) { + logger.error("Validation error", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(400) + .setPayload(ByteString.copyFromUtf8("Validation error: " + e.getMessage())); + + } catch (Exception e) { + logger.error("Error creating bit-rate", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + } +} + diff --git a/src/main/java/com/mediamanager/service/delegate/handler/bitrate/DeleteBitRateHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/bitrate/DeleteBitRateHandler.java new file mode 100644 index 0000000..35c520e --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/bitrate/DeleteBitRateHandler.java @@ -0,0 +1,62 @@ +package com.mediamanager.service.delegate.handler.bitrate; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.BitRateMessages; +import com.mediamanager.service.bitrate.BitRateService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@Action("bitrate.delete") +public class DeleteBitRateHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(DeleteBitRateHandler.class); + + private final BitRateService bitRateService; + + public DeleteBitRateHandler(BitRateService bitRateService) { + this.bitRateService = bitRateService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException { + + try { + BitRateMessages.DeleteBitRateRequest deleteRequest = + BitRateMessages.DeleteBitRateRequest.parseFrom(requestPayload); + int id = deleteRequest.getId(); + boolean success = bitRateService.deleteBitRate(id); + BitRateMessages.DeleteBitRateResponse deleteResponse; + if (success) { + deleteResponse = BitRateMessages.DeleteBitRateResponse.newBuilder() + .setSuccess(true) + .setMessage("Bit-Rate deleted successfully") + .build(); + return TransportProtocol.Response.newBuilder() + .setPayload(deleteResponse.toByteString()); + } else { + deleteResponse = BitRateMessages.DeleteBitRateResponse.newBuilder() + .setSuccess(false) + .setMessage("Bit-Rate not found") + .build(); + + return TransportProtocol.Response.newBuilder() + .setStatusCode(404) + .setPayload(deleteResponse.toByteString()); + } + } catch (Exception e) { + logger.error("Error deleting bit-rate", e); + BitRateMessages.DeleteBitRateResponse deleteResponse = + BitRateMessages.DeleteBitRateResponse.newBuilder() + .setSuccess(false) + .setMessage("Error: " + e.getMessage()) + .build(); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(deleteResponse.toByteString()); + } + } + } diff --git a/src/main/java/com/mediamanager/service/delegate/handler/bitrate/GetBitRateByIdHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/bitrate/GetBitRateByIdHandler.java new file mode 100644 index 0000000..a88893b --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/bitrate/GetBitRateByIdHandler.java @@ -0,0 +1,56 @@ +package com.mediamanager.service.delegate.handler.bitrate; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.BitRateMapper; +import com.mediamanager.model.BitRate; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.BitRateMessages; +import com.mediamanager.service.bitrate.BitRateService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +@Action(value = "bitrate.getById") +public class GetBitRateByIdHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(GetBitRateByIdHandler.class); + private final BitRateService bitRateService; + + public GetBitRateByIdHandler(BitRateService bitRateService) { + this.bitRateService = bitRateService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException{ + + try{ + BitRateMessages.GetBitRateByIdRequest getByIdRequest = + BitRateMessages.GetBitRateByIdRequest.parseFrom(requestPayload); + int id = getByIdRequest.getId(); + + Optional bitRateOpt = bitRateService.getBitRateById(id); + + if (bitRateOpt.isEmpty()){ + logger.warn("BitRate not found with ID: {}", id); + return TransportProtocol.Response.newBuilder() + .setStatusCode(404) + .setPayload(ByteString.copyFromUtf8("BitRate not found")); + } + BitRateMessages.BitRate bitRateProto = BitRateMapper.toProtobuf(bitRateOpt.get()); + BitRateMessages.GetBitRateByIdResponse getByIdResponse = BitRateMessages.GetBitRateByIdResponse.newBuilder() + .setBitrate(bitRateProto) + .build(); + return TransportProtocol.Response.newBuilder() + .setPayload(getByIdResponse.toByteString()); + } catch (Exception e) { + logger.error("Error getting bit-rate by ID", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: "+ e.getMessage())); + } + } +} diff --git a/src/main/java/com/mediamanager/service/delegate/handler/bitrate/GetBitRateHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/bitrate/GetBitRateHandler.java new file mode 100644 index 0000000..87c6ca3 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/bitrate/GetBitRateHandler.java @@ -0,0 +1,48 @@ +package com.mediamanager.service.delegate.handler.bitrate; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.BitRateMapper; +import com.mediamanager.model.BitRate; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.BitRateMessages; +import com.mediamanager.service.bitrate.BitRateService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; + + +@Action("bitrate.getAll") +public class GetBitRateHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(GetBitRateHandler.class); + + private final BitRateService bitRateService; + + public GetBitRateHandler(BitRateService bitRateService){this.bitRateService = bitRateService;} + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException { + try{ + List bitRates = bitRateService.getAllBitRates(); + BitRateMessages.GetBitRatesResponse.Builder responseBuilder = BitRateMessages.GetBitRatesResponse.newBuilder(); + + for (BitRate bitRate : bitRates) { + BitRateMessages.BitRate bitRateProto = BitRateMapper.toProtobuf(bitRate); + responseBuilder.addBitrates(bitRateProto); + } + BitRateMessages.GetBitRatesResponse getBitRatesResponse = responseBuilder.build(); + + return TransportProtocol.Response.newBuilder() + .setPayload(getBitRatesResponse.toByteString()); + + }catch (Exception e){ + logger.error("Error getting bit-rates", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + } +} diff --git a/src/main/java/com/mediamanager/service/delegate/handler/bitrate/UpdateBitRateHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/bitrate/UpdateBitRateHandler.java new file mode 100644 index 0000000..f5e6dc9 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/bitrate/UpdateBitRateHandler.java @@ -0,0 +1,65 @@ +package com.mediamanager.service.delegate.handler.bitrate; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.BitRateMapper; +import com.mediamanager.model.BitRate; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.BitRateMessages; +import com.mediamanager.service.bitrate.BitRateService; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +@Action("bitrate.update") +public class UpdateBitRateHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(UpdateBitRateHandler.class); + private final BitRateService bitRateService; + + public UpdateBitRateHandler(BitRateService bitRateService) { + this.bitRateService = bitRateService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException { + try{ + BitRateMessages.UpdateBitRateRequest updateRequest = + BitRateMessages.UpdateBitRateRequest.parseFrom(requestPayload); + + int id = updateRequest.getId(); + String newValue = updateRequest.getValue(); + + Optional bitRateOpt = bitRateService.updateBitRate(id, newValue); + + if(bitRateOpt.isEmpty()){ + logger.warn("BitRate not found with ID: {}", id); + return TransportProtocol.Response.newBuilder() + .setStatusCode(404) + .setPayload(ByteString.copyFromUtf8("BitRate not found")); + } + + BitRateMessages.BitRate bitRateProto = BitRateMapper.toProtobuf(bitRateOpt.get()); + + BitRateMessages.UpdateBitRateResponse updateResponse = BitRateMessages.UpdateBitRateResponse.newBuilder() + .setBitrate(bitRateProto) + .build(); + + return TransportProtocol.Response.newBuilder() + .setPayload(updateResponse.toByteString()); + + } catch (IllegalArgumentException e){ + logger.error("Validation error", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(400) + .setPayload(ByteString.copyFromUtf8("Validation error: " + e.getMessage())); + } catch (Exception e) { + logger.error("Error updating bit-rate", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + } +} diff --git a/src/main/proto/bitrate.proto b/src/main/proto/bitrate.proto new file mode 100644 index 0000000..4f69ea3 --- /dev/null +++ b/src/main/proto/bitrate.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; + +option java_package = "com.mediamanager.protocol.messages"; +option java_outer_classname = "BitRateMessages"; + +package mediamanager.messages; + +message BitRate{ + int32 id =1; + string value = 2; +} + +message CreateBitRateRequest{ + string value = 1; +} + +message CreateBitRateResponse{ + BitRate bitrate = 1; +} + +message GetBitRatesRequest{ + +} + +message GetBitRatesResponse{ + repeated BitRate bitrates = 1; +} + +message GetBitRateByIdRequest{ + int32 id = 1; +} + +message GetBitRateByIdResponse{ + BitRate bitrate = 1; +} + +message UpdateBitRateRequest{ + int32 id = 1; + string value = 2; +} + +message UpdateBitRateResponse{ + BitRate bitrate = 1; +} + +message DeleteBitRateRequest{ + int32 id =1; +} + +message DeleteBitRateResponse{ + bool success = 1; + string message = 2; +} From c787eba20eefc0b0eaa1893462d3f8da23b453fe Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Fri, 5 Dec 2025 21:48:46 -0300 Subject: [PATCH 39/39] Fix field name casing in UpdateArtistResponse proto message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change field name from 'Artist' to 'artist' to follow protobuf naming conventions, which require lowercase field names with underscores for word separation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/main/proto/artist.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/proto/artist.proto b/src/main/proto/artist.proto index 254e7f7..1f68272 100644 --- a/src/main/proto/artist.proto +++ b/src/main/proto/artist.proto @@ -40,7 +40,7 @@ message UpdateArtistRequest { } message UpdateArtistResponse { - Artist Artist = 1; + Artist artist = 1; } message DeleteArtistRequest {