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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] - 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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 { From 5aeb54516c0df6428c10f1b44357517f842e3b53 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Sat, 6 Dec 2025 02:41:48 -0300 Subject: [PATCH 40/44] Implement sampling rate management with full CRUD operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces comprehensive sampling rate management functionality to the MediaManager system, following the established patterns from BitDepth and BitRate implementations. Core Components Added: - SamplingRate entity model with JPA annotations for database persistence - SamplingRateRepository with full CRUD operations (save, findAll, findById, update, deleteById) - SamplingRateService business logic layer with validation and error handling - SamplingRateMapper for bidirectional entity-protobuf conversions Protocol Buffers: - Defined samplingrate.proto with complete message specifications - SamplingRate message structure (id, value) - Request/Response pairs for all CRUD operations (Create, GetAll, GetById, Update, Delete) - Fixed typo in UpdateSamplingRateRequest (valeu -> value) Action Handlers: - CreateSamplingRateHandler: Validates and creates new sampling rate entries - GetSamplingRateHandler: Retrieves all sampling rates from the database - GetSamplingRateByIdHandler: Fetches individual sampling rates with 404 handling - UpdateSamplingRateHandler: Updates existing sampling rates with validation - DeleteSamplingRateHandler: Removes sampling rates with success confirmation Integration: - Registered SamplingRateService in DelegateActionManager - All handlers follow the @Action annotation pattern for automatic discovery - Consistent error handling with appropriate HTTP status codes (400, 404, 500) This implementation provides a complete foundation for managing audio sampling rates within the media management system, enabling clients to perform all standard CRUD operations through the established protocol buffer interface. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .../mapper/SamplingRateMapper.java | 35 ++++++ .../com/mediamanager/model/SamplingRate.java | 30 +++++ .../repository/SamplingRateRepository.java | 103 ++++++++++++++++++ .../delegate/DelegateActionManager.java | 5 + .../CreateSamplingRateHandler.java | 50 +++++++++ .../DeleteSamplingRateHandler.java | 62 +++++++++++ .../GetSamplingRateByIdHandler.java | 56 ++++++++++ .../samplingrate/GetSamplingRateHandler.java | 48 ++++++++ .../UpdateSamplingRateHandler.java | 65 +++++++++++ .../samplingrate/SamplingRateService.java | 59 ++++++++++ src/main/proto/samplingrate.proto | 51 +++++++++ 11 files changed, 564 insertions(+) create mode 100644 src/main/java/com/mediamanager/mapper/SamplingRateMapper.java create mode 100644 src/main/java/com/mediamanager/model/SamplingRate.java create mode 100644 src/main/java/com/mediamanager/repository/SamplingRateRepository.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/samplingrate/CreateSamplingRateHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/samplingrate/DeleteSamplingRateHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/samplingrate/GetSamplingRateByIdHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/samplingrate/GetSamplingRateHandler.java create mode 100644 src/main/java/com/mediamanager/service/delegate/handler/samplingrate/UpdateSamplingRateHandler.java create mode 100644 src/main/java/com/mediamanager/service/samplingrate/SamplingRateService.java create mode 100644 src/main/proto/samplingrate.proto diff --git a/src/main/java/com/mediamanager/mapper/SamplingRateMapper.java b/src/main/java/com/mediamanager/mapper/SamplingRateMapper.java new file mode 100644 index 0000000..d1701cd --- /dev/null +++ b/src/main/java/com/mediamanager/mapper/SamplingRateMapper.java @@ -0,0 +1,35 @@ +package com.mediamanager.mapper; + +import com.mediamanager.model.SamplingRate; +import com.mediamanager.protocol.messages.SamplingRateMessages; + +public class SamplingRateMapper { + public static SamplingRateMessages.SamplingRate toProtobuf(SamplingRate entity) { + if (entity == null) { + return null; + } + String value = entity.getValue(); + if (value == null || value.isEmpty()) { + throw new IllegalArgumentException("Value cannot be null or empty"); + } + SamplingRateMessages.SamplingRate.Builder builder = SamplingRateMessages.SamplingRate.newBuilder() + .setValue(value); + Integer id = entity.getId(); + if (id != null) { + builder.setId(id); + } + return builder.build(); + } + + public static SamplingRate toEntity(SamplingRateMessages.SamplingRate protobuf) { + if (protobuf == null) { + return null; + } + SamplingRate entity = new SamplingRate(); + if (protobuf.getId() >0) { + entity.setId(protobuf.getId()); + } + entity.setValue(protobuf.getValue()); + return entity; + } +} diff --git a/src/main/java/com/mediamanager/model/SamplingRate.java b/src/main/java/com/mediamanager/model/SamplingRate.java new file mode 100644 index 0000000..e090226 --- /dev/null +++ b/src/main/java/com/mediamanager/model/SamplingRate.java @@ -0,0 +1,30 @@ +package com.mediamanager.model; + +import jakarta.persistence.*; + +@Entity +@Table(name = "sampling_rate") +public class SamplingRate { + @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/SamplingRateRepository.java b/src/main/java/com/mediamanager/repository/SamplingRateRepository.java new file mode 100644 index 0000000..e434b9b --- /dev/null +++ b/src/main/java/com/mediamanager/repository/SamplingRateRepository.java @@ -0,0 +1,103 @@ +package com.mediamanager.repository; + + +import com.mediamanager.model.SamplingRate; +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 SamplingRateRepository { + private static final Logger logger = LogManager.getLogger(SamplingRateRepository.class); + + private final EntityManagerFactory entityManagerFactory; + + public SamplingRateRepository(EntityManagerFactory entityManagerFactory) { + this.entityManagerFactory = entityManagerFactory; + } + + public SamplingRate save(SamplingRate samplingRate) { + logger.debug("Saving SamplingRate: {}", samplingRate.getValue()); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + try { + em.persist(samplingRate); + em.getTransaction().commit(); + logger.debug("SamplingRate has been saved successfully"); + return samplingRate; + } catch (Exception e) { + em.getTransaction().rollback(); + logger.error("Error while saving SamplingRate: {}", e.getMessage()); + throw e; + } finally { + if (em.isOpen()) em.close(); + } + } + + public List findAll() { + logger.debug("Finding All SamplingRate"); + EntityManager em = entityManagerFactory.createEntityManager(); + try{ + return em.createQuery("select s from SamplingRate s", SamplingRate.class).getResultList(); + }finally { + if (em.isOpen()) em.close(); + } + } + + public Optional findById(Integer id) { + logger.debug("Finding SamplingRate with id: {}", id); + EntityManager em = entityManagerFactory.createEntityManager(); + try{ + SamplingRate samplingRate = em.find(SamplingRate.class, id); + return Optional.of(samplingRate); + }finally { + if (em.isOpen()) em.close(); + } + } + + public SamplingRate update(SamplingRate samplingRate) { + logger.debug("Updating SamplingRate: {}", samplingRate.getValue()); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + try { + SamplingRate updated = em.merge(samplingRate); + em.getTransaction().commit(); + logger.debug("SamplingRate has been updated successfully"); + return updated; + } catch (Exception e) { + em.getTransaction().rollback(); + logger.error("Error while updating SamplingRate: {}", e.getMessage()); + throw e; + } finally { + if (em.isOpen()) em.close(); + } + } + + public boolean deleteById(Integer id){ + logger.debug("Deleting SamplingRate with id: {}", id); + EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + try{ + SamplingRate samplingRate = em.find(SamplingRate.class, id); + if (samplingRate == null) { + em.getTransaction().rollback(); + return false; + } + em.remove(samplingRate); + em.getTransaction().commit(); + logger.debug("SamplingRate has been deleted successfully"); + return true; + } catch (Exception e) { + em.getTransaction().rollback(); + logger.error("Error while deleting SamplingRate: {}", e.getMessage()); + throw e; + } finally { + if (em.isOpen()) em.close(); + } + } + +} diff --git a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java index 67daf24..e4715ba 100644 --- a/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java +++ b/src/main/java/com/mediamanager/service/delegate/DelegateActionManager.java @@ -11,6 +11,7 @@ import com.mediamanager.service.artist.ArtistService; import com.mediamanager.service.delegate.annotation.Action; import com.mediamanager.service.genre.GenreService; +import com.mediamanager.service.samplingrate.SamplingRateService; import jakarta.persistence.EntityManagerFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -70,6 +71,10 @@ public class DelegateActionManager { BitRateService bitRateService = new BitRateService(bitRateRepository); serviceLocator.register(BitRateService.class, bitRateService); + SamplingRateRepository samplingRateRepository = new SamplingRateRepository(entityManagerFactory); + SamplingRateService samplingRateService = new SamplingRateService(samplingRateRepository); + serviceLocator.register(SamplingRateService.class, samplingRateService); + serviceLocator.logRegisteredServices(); logger.info("Services initialized successfully"); diff --git a/src/main/java/com/mediamanager/service/delegate/handler/samplingrate/CreateSamplingRateHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/samplingrate/CreateSamplingRateHandler.java new file mode 100644 index 0000000..a3e0715 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/samplingrate/CreateSamplingRateHandler.java @@ -0,0 +1,50 @@ +package com.mediamanager.service.delegate.handler.samplingrate; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.SamplingRateMapper; +import com.mediamanager.model.SamplingRate; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.SamplingRateMessages; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import com.mediamanager.service.samplingrate.SamplingRateService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@Action("samplingrate.create") +public class CreateSamplingRateHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(CreateSamplingRateHandler.class); + private final SamplingRateService samplingRateService; + + public CreateSamplingRateHandler(SamplingRateService samplingRateService) { + this.samplingRateService = samplingRateService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException { + try{ + SamplingRateMessages.CreateSamplingRateRequest createRequest = + SamplingRateMessages.CreateSamplingRateRequest.parseFrom(requestPayload); + SamplingRate samplingRate = samplingRateService.createSamplingRate(createRequest.getValue()); + SamplingRateMessages.SamplingRate samplingRateProto = SamplingRateMapper.toProtobuf(samplingRate); + SamplingRateMessages.CreateSamplingRateResponse createSamplingRateResponse = SamplingRateMessages.CreateSamplingRateResponse.newBuilder() + .setSamplingrate(samplingRateProto) + .build(); + return TransportProtocol.Response.newBuilder() + .setPayload(createSamplingRateResponse.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 sampling 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/samplingrate/DeleteSamplingRateHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/samplingrate/DeleteSamplingRateHandler.java new file mode 100644 index 0000000..1881045 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/samplingrate/DeleteSamplingRateHandler.java @@ -0,0 +1,62 @@ +package com.mediamanager.service.delegate.handler.samplingrate; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.SamplingRateMessages; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import com.mediamanager.service.samplingrate.SamplingRateService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@Action("samplingrate.delete") +public class DeleteSamplingRateHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(DeleteSamplingRateHandler.class); + + private final SamplingRateService samplingRateService; + + public DeleteSamplingRateHandler(SamplingRateService samplingRateService) { + this.samplingRateService = samplingRateService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException { + + try { + SamplingRateMessages.DeleteSamplingRateRequest deleteRequest = + SamplingRateMessages.DeleteSamplingRateRequest.parseFrom(requestPayload); + int id = deleteRequest.getId(); + boolean success = samplingRateService.deleteSamplingRate(id); + SamplingRateMessages.DeleteSamplingRateResponse deleteResponse; + if (success) { + deleteResponse = SamplingRateMessages.DeleteSamplingRateResponse.newBuilder() + .setSuccess(true) + .setMessage("Sampling rate deleted successfully") + .build(); + return TransportProtocol.Response.newBuilder() + .setPayload(deleteResponse.toByteString()); + } else { + deleteResponse = SamplingRateMessages.DeleteSamplingRateResponse.newBuilder() + .setSuccess(false) + .setMessage("Sampling rate not found") + .build(); + + return TransportProtocol.Response.newBuilder() + .setStatusCode(404) + .setPayload(deleteResponse.toByteString()); + } + } catch (Exception e) { + logger.error("Error deleting sampling rate", e); + SamplingRateMessages.DeleteSamplingRateResponse deleteResponse = + SamplingRateMessages.DeleteSamplingRateResponse.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/samplingrate/GetSamplingRateByIdHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/samplingrate/GetSamplingRateByIdHandler.java new file mode 100644 index 0000000..f17ddd6 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/samplingrate/GetSamplingRateByIdHandler.java @@ -0,0 +1,56 @@ +package com.mediamanager.service.delegate.handler.samplingrate; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.SamplingRateMapper; +import com.mediamanager.model.SamplingRate; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.SamplingRateMessages; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import com.mediamanager.service.samplingrate.SamplingRateService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +@Action(value = "samplingrate.getById") +public class GetSamplingRateByIdHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(GetSamplingRateByIdHandler.class); + private final SamplingRateService samplingRateService; + + public GetSamplingRateByIdHandler(SamplingRateService samplingRateService) { + this.samplingRateService = samplingRateService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) + throws InvalidProtocolBufferException{ + + try{ + SamplingRateMessages.GetSamplingRateByIDRequest getByIdRequest = + SamplingRateMessages.GetSamplingRateByIDRequest.parseFrom(requestPayload); + int id = getByIdRequest.getId(); + + Optional samplingRateOpt = samplingRateService.getSamplingRateById(id); + + if (samplingRateOpt.isEmpty()){ + logger.warn("SamplingRate not found with ID: {}", id); + return TransportProtocol.Response.newBuilder() + .setStatusCode(404) + .setPayload(ByteString.copyFromUtf8("SamplingRate not found")); + } + SamplingRateMessages.SamplingRate samplingRateProto = SamplingRateMapper.toProtobuf(samplingRateOpt.get()); + SamplingRateMessages.GetSamplingRateByIdResponse getByIdResponse = SamplingRateMessages.GetSamplingRateByIdResponse.newBuilder() + .setSamplingrate(samplingRateProto) + .build(); + return TransportProtocol.Response.newBuilder() + .setPayload(getByIdResponse.toByteString()); + } catch (Exception e) { + logger.error("Error getting sampling 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/samplingrate/GetSamplingRateHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/samplingrate/GetSamplingRateHandler.java new file mode 100644 index 0000000..5e51e46 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/samplingrate/GetSamplingRateHandler.java @@ -0,0 +1,48 @@ +package com.mediamanager.service.delegate.handler.samplingrate; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.SamplingRateMapper; +import com.mediamanager.model.SamplingRate; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.SamplingRateMessages; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import com.mediamanager.service.samplingrate.SamplingRateService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; + + +@Action("samplingrate.getAll") +public class GetSamplingRateHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(GetSamplingRateHandler.class); + + private final SamplingRateService samplingRateService; + + public GetSamplingRateHandler(SamplingRateService samplingRateService){this.samplingRateService = samplingRateService;} + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException { + try{ + List samplingRates = samplingRateService.getAllSamplingRates(); + SamplingRateMessages.GetSamplingRatesResponse.Builder responseBuilder = SamplingRateMessages.GetSamplingRatesResponse.newBuilder(); + + for (SamplingRate samplingRate : samplingRates) { + SamplingRateMessages.SamplingRate samplingRateProto = SamplingRateMapper.toProtobuf(samplingRate); + responseBuilder.addSamplingrates(samplingRateProto); + } + SamplingRateMessages.GetSamplingRatesResponse getSamplingRatesResponse = responseBuilder.build(); + + return TransportProtocol.Response.newBuilder() + .setPayload(getSamplingRatesResponse.toByteString()); + + }catch (Exception e){ + logger.error("Error getting sampling 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/samplingrate/UpdateSamplingRateHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/samplingrate/UpdateSamplingRateHandler.java new file mode 100644 index 0000000..770ea82 --- /dev/null +++ b/src/main/java/com/mediamanager/service/delegate/handler/samplingrate/UpdateSamplingRateHandler.java @@ -0,0 +1,65 @@ +package com.mediamanager.service.delegate.handler.samplingrate; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.mediamanager.mapper.SamplingRateMapper; +import com.mediamanager.model.SamplingRate; +import com.mediamanager.protocol.TransportProtocol; +import com.mediamanager.protocol.messages.SamplingRateMessages; +import com.mediamanager.service.delegate.ActionHandler; +import com.mediamanager.service.delegate.annotation.Action; +import com.mediamanager.service.samplingrate.SamplingRateService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Optional; + +@Action("samplingrate.update") +public class UpdateSamplingRateHandler implements ActionHandler { + private static final Logger logger = LogManager.getLogger(UpdateSamplingRateHandler.class); + private final SamplingRateService samplingRateService; + + public UpdateSamplingRateHandler(SamplingRateService samplingRateService) { + this.samplingRateService = samplingRateService; + } + + @Override + public TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException { + try{ + SamplingRateMessages.UpdateSamplingRateRequest updateRequest = + SamplingRateMessages.UpdateSamplingRateRequest.parseFrom(requestPayload); + + int id = updateRequest.getId(); + String newValue = updateRequest.getValue(); + + Optional samplingRateOpt = samplingRateService.updateSamplingRate(id, newValue); + + if(samplingRateOpt.isEmpty()){ + logger.warn("SamplingRate not found with ID: {}", id); + return TransportProtocol.Response.newBuilder() + .setStatusCode(404) + .setPayload(ByteString.copyFromUtf8("SamplingRate not found")); + } + + SamplingRateMessages.SamplingRate samplingRateProto = SamplingRateMapper.toProtobuf(samplingRateOpt.get()); + + SamplingRateMessages.UpdateSamplingRateResponse updateResponse = SamplingRateMessages.UpdateSamplingRateResponse.newBuilder() + .setSamplingrate(samplingRateProto) + .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 sampling rate", e); + return TransportProtocol.Response.newBuilder() + .setStatusCode(500) + .setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage())); + } + } +} diff --git a/src/main/java/com/mediamanager/service/samplingrate/SamplingRateService.java b/src/main/java/com/mediamanager/service/samplingrate/SamplingRateService.java new file mode 100644 index 0000000..cb304e9 --- /dev/null +++ b/src/main/java/com/mediamanager/service/samplingrate/SamplingRateService.java @@ -0,0 +1,59 @@ +package com.mediamanager.service.samplingrate; + +import com.mediamanager.model.SamplingRate; +import com.mediamanager.repository.SamplingRateRepository; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.Optional; + +public class SamplingRateService { + private static final Logger logger = LogManager.getLogger(SamplingRateService.class); + private final SamplingRateRepository repository; + + public SamplingRateService(SamplingRateRepository repository) { + this.repository = repository; + } + + public SamplingRate createSamplingRate(String value) { + logger.debug("Creating sampling rate:{}", value); + if (value == null || value.trim().isEmpty()) { + throw new IllegalArgumentException("Sampling-Rate value cannot be null or empty"); + } + SamplingRate samplingRate = new SamplingRate(); + samplingRate.setValue(value); + return repository.save(samplingRate); + } + + public List getAllSamplingRates() { + logger.info("Getting all sampling rates"); + return repository.findAll(); + } + + public Optional getSamplingRateById(Integer id) { + logger.info("Getting sampling rate by id:{}", id); + return repository.findById(id); + } + + public Optional updateSamplingRate(Integer id, String value) { + logger.info("Updating sampling rate:{}", value); + if (value == null || value.trim().isEmpty()) { + throw new IllegalArgumentException("Sampling-Rate value cannot be null or empty");} + Optional existingSamplingRate = repository.findById(id); + if(existingSamplingRate.isEmpty()) { + logger.warn("Sampling rate not found with id:{}", id); + return Optional.empty(); + } + SamplingRate samplingRate = existingSamplingRate.get(); + samplingRate.setValue(value); + SamplingRate updatedSamplingRate = repository.update(samplingRate); + return Optional.of(updatedSamplingRate); + } + + public boolean deleteSamplingRate(Integer id) { + logger.info("Deleting sampling rate:{}", id); + return repository.deleteById(id); + } + +} diff --git a/src/main/proto/samplingrate.proto b/src/main/proto/samplingrate.proto new file mode 100644 index 0000000..595278f --- /dev/null +++ b/src/main/proto/samplingrate.proto @@ -0,0 +1,51 @@ +syntax = "proto3"; + +option java_package = "com.mediamanager.protocol.messages"; +option java_outer_classname = "SamplingRateMessages"; + +package mediamanager.messages; + +message SamplingRate{ + int32 id=1; + string value=2; +} + +message CreateSamplingRateRequest{ + string value= 1; +} + +message CreateSamplingRateResponse{ + SamplingRate samplingrate =1; +} + +message GetSamplingRatesRequest{} + +message GetSamplingRatesResponse{ + repeated SamplingRate samplingrates = 1; +} + +message GetSamplingRateByIDRequest{ + int32 id =1; +} + +message GetSamplingRateByIdResponse{ + SamplingRate samplingrate = 1; +} + +message UpdateSamplingRateRequest{ + int32 id = 1; + string value = 2; +} + +message UpdateSamplingRateResponse{ + SamplingRate samplingrate = 1; +} + +message DeleteSamplingRateRequest{ + int32 id =1; +} + +message DeleteSamplingRateResponse{ + bool success = 1; + string message =2; +} \ No newline at end of file From 5ff972ebcb2175c1ee6f8aa6b8358b3b0b78d81d Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Sat, 6 Dec 2025 02:55:36 -0300 Subject: [PATCH 41/44] Fix message name casing in SamplingRate proto definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standardize the naming convention by changing `GetSamplingRateByIDRequest` to `GetSamplingRateByIdRequest`, using "Id" instead of "ID" for consistency with other proto message names across the codebase. Updated the corresponding handler to reference the corrected message name. This aligns with the established naming pattern and improves code consistency following the same convention applied in other proto definitions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .../handler/samplingrate/GetSamplingRateByIdHandler.java | 4 ++-- src/main/proto/samplingrate.proto | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/mediamanager/service/delegate/handler/samplingrate/GetSamplingRateByIdHandler.java b/src/main/java/com/mediamanager/service/delegate/handler/samplingrate/GetSamplingRateByIdHandler.java index f17ddd6..c791798 100644 --- a/src/main/java/com/mediamanager/service/delegate/handler/samplingrate/GetSamplingRateByIdHandler.java +++ b/src/main/java/com/mediamanager/service/delegate/handler/samplingrate/GetSamplingRateByIdHandler.java @@ -28,8 +28,8 @@ public class GetSamplingRateByIdHandler implements ActionHandler { throws InvalidProtocolBufferException{ try{ - SamplingRateMessages.GetSamplingRateByIDRequest getByIdRequest = - SamplingRateMessages.GetSamplingRateByIDRequest.parseFrom(requestPayload); + SamplingRateMessages.GetSamplingRateByIdRequest getByIdRequest = + SamplingRateMessages.GetSamplingRateByIdRequest.parseFrom(requestPayload); int id = getByIdRequest.getId(); Optional samplingRateOpt = samplingRateService.getSamplingRateById(id); diff --git a/src/main/proto/samplingrate.proto b/src/main/proto/samplingrate.proto index 595278f..7a5f316 100644 --- a/src/main/proto/samplingrate.proto +++ b/src/main/proto/samplingrate.proto @@ -24,7 +24,7 @@ message GetSamplingRatesResponse{ repeated SamplingRate samplingrates = 1; } -message GetSamplingRateByIDRequest{ +message GetSamplingRateByIdRequest{ int32 id =1; } From 4fa147282fdc3d4ce7f5a4812ed014747edb0822 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Miranda Date: Sat, 6 Dec 2025 22:07:39 -0300 Subject: [PATCH 42/44] Update src/main/java/com/mediamanager/service/samplingrate/SamplingRateService.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../mediamanager/service/samplingrate/SamplingRateService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/mediamanager/service/samplingrate/SamplingRateService.java b/src/main/java/com/mediamanager/service/samplingrate/SamplingRateService.java index cb304e9..1b9144e 100644 --- a/src/main/java/com/mediamanager/service/samplingrate/SamplingRateService.java +++ b/src/main/java/com/mediamanager/service/samplingrate/SamplingRateService.java @@ -32,6 +32,9 @@ public class SamplingRateService { } public Optional getSamplingRateById(Integer id) { + if (id == null) { + throw new IllegalArgumentException("ID cannot be null"); + } logger.info("Getting sampling rate by id:{}", id); return repository.findById(id); } From ff09d1b89a85d10af1f0c2c3b89054ba3c44e1ef Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Sat, 6 Dec 2025 22:18:40 -0300 Subject: [PATCH 43/44] Fix null handling and validation in SamplingRate management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit addresses potential runtime exceptions and improves input validation: - Replace Optional.of() with Optional.ofNullable() in SamplingRateRepository.findById() to properly handle cases where no sampling rate is found, preventing NullPointerException - Add null validation for id parameter in SamplingRateService.deleteSamplingRate() to ensure proper error handling before repository operations - Clean up code formatting in updateSamplingRate() validation block These changes enhance the robustness of the sampling rate management feature by preventing NPEs and providing clearer error messages for invalid inputs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .../com/mediamanager/repository/SamplingRateRepository.java | 2 +- .../service/samplingrate/SamplingRateService.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/mediamanager/repository/SamplingRateRepository.java b/src/main/java/com/mediamanager/repository/SamplingRateRepository.java index e434b9b..4d2fd82 100644 --- a/src/main/java/com/mediamanager/repository/SamplingRateRepository.java +++ b/src/main/java/com/mediamanager/repository/SamplingRateRepository.java @@ -53,7 +53,7 @@ public class SamplingRateRepository { EntityManager em = entityManagerFactory.createEntityManager(); try{ SamplingRate samplingRate = em.find(SamplingRate.class, id); - return Optional.of(samplingRate); + return Optional.ofNullable(samplingRate); }finally { if (em.isOpen()) em.close(); } diff --git a/src/main/java/com/mediamanager/service/samplingrate/SamplingRateService.java b/src/main/java/com/mediamanager/service/samplingrate/SamplingRateService.java index 1b9144e..b347815 100644 --- a/src/main/java/com/mediamanager/service/samplingrate/SamplingRateService.java +++ b/src/main/java/com/mediamanager/service/samplingrate/SamplingRateService.java @@ -42,7 +42,8 @@ public class SamplingRateService { public Optional updateSamplingRate(Integer id, String value) { logger.info("Updating sampling rate:{}", value); if (value == null || value.trim().isEmpty()) { - throw new IllegalArgumentException("Sampling-Rate value cannot be null or empty");} + throw new IllegalArgumentException("Sampling-Rate value cannot be null or empty"); + } Optional existingSamplingRate = repository.findById(id); if(existingSamplingRate.isEmpty()) { logger.warn("Sampling rate not found with id:{}", id); @@ -55,6 +56,9 @@ public class SamplingRateService { } public boolean deleteSamplingRate(Integer id) { + if (id == null) { + throw new IllegalArgumentException("Sampling rate id cannot be null"); + } logger.info("Deleting sampling rate:{}", id); return repository.deleteById(id); } From 94297e75b9b87eb18d7f13c3f291ebe48d1d5f8b Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Sat, 6 Dec 2025 22:30:51 -0300 Subject: [PATCH 44/44] Add id validation and improve code formatting in updateSamplingRate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enhance input validation and code quality in the SamplingRate service: - Add null check for id parameter in updateSamplingRate() method to prevent potential NullPointerException when calling repository.findById() - Standardize indentation throughout the updateSamplingRate() method body, improving code readability and consistency with project style guidelines This complements the previous validation improvements by ensuring all method parameters are properly validated before use, creating a more defensive and robust API surface. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .../samplingrate/SamplingRateService.java | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/mediamanager/service/samplingrate/SamplingRateService.java b/src/main/java/com/mediamanager/service/samplingrate/SamplingRateService.java index b347815..005e8e5 100644 --- a/src/main/java/com/mediamanager/service/samplingrate/SamplingRateService.java +++ b/src/main/java/com/mediamanager/service/samplingrate/SamplingRateService.java @@ -40,19 +40,22 @@ public class SamplingRateService { } public Optional updateSamplingRate(Integer id, String value) { - logger.info("Updating sampling rate:{}", value); - if (value == null || value.trim().isEmpty()) { - throw new IllegalArgumentException("Sampling-Rate value cannot be null or empty"); - } - Optional existingSamplingRate = repository.findById(id); - if(existingSamplingRate.isEmpty()) { - logger.warn("Sampling rate not found with id:{}", id); - return Optional.empty(); - } - SamplingRate samplingRate = existingSamplingRate.get(); - samplingRate.setValue(value); - SamplingRate updatedSamplingRate = repository.update(samplingRate); - return Optional.of(updatedSamplingRate); + if (id == null) { + throw new IllegalArgumentException("ID cannot be null"); + } + logger.info("Updating sampling rate:{}", value); + if (value == null || value.trim().isEmpty()) { + throw new IllegalArgumentException("Sampling-Rate value cannot be null or empty"); + } + Optional existingSamplingRate = repository.findById(id); + if(existingSamplingRate.isEmpty()) { + logger.warn("Sampling rate not found with id:{}", id); + return Optional.empty(); + } + SamplingRate samplingRate = existingSamplingRate.get(); + samplingRate.setValue(value); + SamplingRate updatedSamplingRate = repository.update(samplingRate); + return Optional.of(updatedSamplingRate); } public boolean deleteSamplingRate(Integer id) {