diff --git a/Cambios.bas b/Cambios.bas index bbd64e0..15d13b1 100644 --- a/Cambios.bas +++ b/Cambios.bas @@ -10,174 +10,154 @@ Version=10.3 Sub Process_Globals - '- VERSION X.XX.XX (cambios a implementar) - '- Agregar que se puedan usar cualquier cantidad de archivos config.properties - '- Agregar que se pueda recargar solo un archivo de configuracion o todos a la vez. - '- Agregar que el "Test" del manager revise (con el query de Jorge) cuantas conexiones hay actualmente activas, - '- o si no en el test, un nuevo handler, talvez "Conexiones". - '- Agregar una forma de probar con carga el servidor - '- Agregar la opcion de "Queries lentos" - - '- VERSION 5.09.15 - ' - feat: Consolidación de mejoras en monitoreo, gestión de pools y hot-swap de configuración. - ' - ' Este commit integra y consolida todas las mejoras recientes en la robustez, - ' monitoreo de rendimiento y flexibilidad del servidor jRDC2-Multi. - ' - ' **Módulos Afectados:** Main.bas, RDCConnector.bas, DBHandlerB4X.bas, DBHandlerJSON.bas, Manager.bas, GlobalParameters.bas. - ' - ' **Cambios Clave Implementados:** - ' - ' 1. **Monitoreo Preciso y Robusto del Pool de Conexiones (C3P0) y Peticiones Activas:** - ' * Se corrigieron las métricas de `BusyConnections` y `TotalConnections` en `query_logs` y el panel `Manager` para reflejar el estado real del pool de C3P0. Esto se logró capturando `BusyConnections` directamente del pool *inmediatamente después* de que un handler adquiere una conexión en `DBHandlerJSON.bas` y `DBHandlerB4X.bas` [1, 2]. - ' * El contador de peticiones activas por base de datos (`GlobalParameters.ActiveRequestsCountByDB`) ahora se incrementa y decrementa de forma consistente. Se asegura que la `dbKey` se resuelva *antes* de la operación de conteo y se aplica una coerción explícita a `Int` (`.As(Int)`) para todas las operaciones, resolviendo inconsistencias de tipo [3, 4]. - ' * La lógica de decremento en la subrutina `Private Sub CleanupAndLog` (presente en `DBHandlerJSON.bas` y `DBHandlerB4X.bas`) se hizo más robusta, verificando que el contador sea mayor que cero antes de decrementar para evitar valores negativos [5]. - ' * Beneficio: Monitoreo preciso y fiable en tiempo real del uso del pool de conexiones y la carga de peticiones activas, mejorando el diagnóstico y la estabilidad del servidor [5, 6]. - ' - ' 2. **Implementación Completa de "Hot-Swap" para Recarga de Configuraciones de DB:** - ' * La lógica del comando `reload` en `Manager.bas` fue completamente rediseñada para permitir la recarga dinámica de configuraciones de bases de datos sin reiniciar el servidor [7, 8]. - ' * Se utiliza una instancia de `java.util.concurrent.locks.ReentrantLock` (`MainConnectorsLock`) declarada e inicializada en `Main.bas` para proteger de forma atómica la lectura y reemplazo del mapa `Main.Connectors`, que es compartido por múltiples hilos [9, 10]. - ' * Se añadió un método `Public Sub Close` en `RDCConnector.bas` [8], que utiliza `JavaObject` para invocar el método `close()` del `ConnectionPool` (C3P0) subyacente. Esto permite un cierre ordenado de los pools de conexión antiguos, liberando sus recursos de la base de datos de manera limpia durante el "hot-swap" [11, 12]. - ' * La implementación en `Manager.bas` incluye un manejo seguro del bloqueo sin `Finally` (usando una bandera booleana `lockAcquired`) y lógica de validación para abortar la recarga si ocurren errores críticos, manteniendo los conectores antiguos activos para evitar interrupciones del servicio [12, 13]. - ' * Beneficio: Capacidad crítica para actualizar configuraciones de conexión a bases de datos en caliente, mejorando la disponibilidad, simplificando el mantenimiento y previniendo fugas de recursos [14]. - ' - ' 3. **Manejo Mejorado de Peticiones POST con JSON en el Cuerpo:** - ' * `DBHandlerJSON.bas` fue modificado para detectar y procesar correctamente las peticiones POST que envían el payload JSON directamente en el cuerpo (con `Content-Type: application/json`), en lugar de solo en el parámetro `j` de la URL [14, 15]. - ' * Se asegura la lectura completa del `InputStream` y su cierre explícito para liberar recursos [15, 16]. - ' * Beneficio: Compatibilidad con estándares API web modernos, mejorando la robustez y la adherencia a los estándares sin comprometer la retrocompatibilidad con el "Método Legacy" (GET con parámetro `j`) [16]. - ' - ' 4. **Robustecimiento de la Inicialización del Pool de Conexiones (C3P0):** - ' * La subrutina `Initialize` en `RDCConnector.bas` fue reordenada y fortalecida. Ahora asegura que la configuración de C3P0 se cargue completamente en la variable de clase `config` y que todas las propiedades del pool se apliquen mediante `jo.RunMethod` *inmediatamente después* de `pool.Initialize` y *antes* de que el pool intente adquirir conexiones [17, 18]. - ' * Se añadieron las líneas `jo.RunMethod("setAcquireRetryAttempts", Array As Object(1))` y `jo.RunMethod("setBreakAfterAcquireFailure", Array As Object(True))` en `RDCConnector.bas`. Estas son cruciales para forzar a C3P0 a lanzar una `SQLException` explícita si falla al crear las conexiones iniciales, en lugar de fallar silenciosamente [18, 19]. - ' * Se implementó una "activación forzada" del pool (`Dim tempCon As SQL = pool.GetConnection` seguido de `tempCon.Close`) dentro de un bloque `Try...Catch` en `RDCConnector.Initialize`. Esto obliga al pool a establecer las conexiones iniciales (`InitialPoolSize`) con la configuración ya aplicada, permitiendo la captura de errores reales si la conexión a la base de datos falla [20]. - ' * Beneficio: Diagnóstico temprano y preciso de problemas de conexión a la base de datos, evitando situaciones donde `TotalConnections` mostraba `0` o la `jdbcUrl` aparecía truncada [19, 21]. - ' - ' 5. **Nuevas Funcionalidades en el Panel de Administración (Manager):** - ' * Se añadió el comando `slowqueries` al `Manager` para permitir la visualización de las 20 consultas más lentas registradas en la tabla `query_logs` de SQLite [22]. - ' * Se mejoró el comando `totalcon` en `Manager.bas` para mostrar estadísticas detalladas de *todos* los pools de conexión C3P0 configurados, obteniendo métricas en tiempo real (TotalConnections, BusyConnections, IdleConnections, etc.) de cada `RDCConnector` [2, 22]. - ' * Beneficio: Mayor visibilidad y control proactivo sobre el rendimiento y el uso de recursos del servidor desde la interfaz de administración. - ' - ' 6. **Optimización de la Gestión de Logs (`query_logs`):** - ' * Se implementó un `Public timerLogs As Timer` en `Main.bas` [conversación], que se inicializa en `AppStart` y ejecuta periódicamente (cada 10 minutos) la subrutina `borraArribaDe15000Logs`. - ' * La subrutina `borraArribaDe15000Logs` recorta la tabla `query_logs` en `users.db` para mantener solo los 15,000 registros más recientes, y luego realiza un `vacuum` para optimizar el espacio en disco utilizado por la base de datos SQLite [conversación]. - ' * Beneficio: Prevención del crecimiento excesivo de la base de datos de logs de rendimiento, manteniendo un historial manejable y optimizando el uso del almacenamiento a largo plazo. - - '- VERSION 5.09.14 (Ahora consolidado en 5.09.15) - ' -feat: Implementación robusta de monitoreo de pool de conexiones y peticiones activas - ' -Este commit resuelve problemas críticos en el monitoreo del pool de conexiones (C3P0) y el conteo de peticiones activas por base de datos, mejorando significativamente la visibilidad y fiabilidad del rendimiento del servidor jRDC2-Multi. - ' -Problemas Identificados y Resueltos: - ' -1. **Métricas de `BusyConnections` y `TotalConnections` inconsistentes o siempre en `0` en el `Manager` y `query_logs`:** - ' * **Problema**: Anteriormente, la métrica `busy_connections` en `query_logs` a menudo reportaba `0` o no reflejaba el estado real. De manera similar, el panel de `Manager?command=totalcon` consistentemente mostraba `BusyConnections: 0` y `TotalConnections` estancadas en `InitialPoolSize`, a pesar de que Oracle sí reportaba conexiones activas. Esto generaba confusión sobre el uso real y la expansión del pool. - ' * **Solución**: Se modificó la lógica en los *handlers* (`DBHandlerJSON.bas` y `DBHandlerB4X.bas`) para capturar la métrica `BusyConnections` directamente del pool de C3P0 **inmediatamente después de que el *handler* adquiere una conexión** (`con = Connector.GetConnection(finalDbKey)`). Este valor se pasa explícitamente a la subrutina `Main.LogQueryPerformance` para su registro en `query_logs` y para ser consumido por `Manager.bas` a través de `RDCConnector.GetPoolStats`. Esto garantiza que el valor registrado y reportado refleje con precisión el número de conexiones activas en el instante de su adquisición. Pruebas exhaustivas confirmaron que C3P0 sí reporta conexiones ocupadas y sí expande `TotalConnections` hasta `MaxPoolSize` cuando la demanda lo exige. - ' -2. **Contador `handler_active_requests` no decrementaba correctamente:** - ' * **Problema**: El contador de peticiones activas por base de datos (`GlobalParameters.ActiveRequestsCountByDB`) no mostraba un decremento consistente, resultando en un conteo que solo aumentaba o mostraba valores erráticos en los logs. - ' * **Solución**: - ' * Se aseguró la declaración `Public ActiveRequestsCountByDB As Map` en `GlobalParameters.bas`. - ' * Se garantizó su inicialización como un `srvr.CreateThreadSafeMap` en `Main.AppStart` para un manejo concurrente seguro de los contadores. - ' * En `DBHandlerJSON.bas`, la `dbKey` (obtenida del parámetro `dbx` del JSON) ahora se resuelve *antes* de incrementar el contador, asegurando que el incremento y el decremento se apliquen siempre a la misma clave de base de datos correcta. - ' * Se implementó una coerción explícita a `Int` (`.As(Int)`) para todas las operaciones de lectura y escritura (`GetDefault`, `Put`) en `GlobalParameters.ActiveRequestsCountByDB`, resolviendo problemas de tipo que causaban inconsistencias y el fallo en el decremento. - ' * La lógica de decremento en `Private Sub CleanupAndLog` (presente en ambos *handlers*) se hizo más robusta, verificando que el contador sea mayor que cero antes de decrementar para evitar valores negativos. - ' -Beneficios de estos Cambios: - ' * **Monitoreo Preciso y Fiable**: Las métricas `busy_connections` y `handler_active_requests` en `query_logs` y el panel `Manager` ahora son totalmente fiables, proporcionando una visión clara y en tiempo real del uso del pool de conexiones y la carga de peticiones activas por base de datos. - ' * **Diagnóstico Mejorado**: La visibilidad interna del estado del pool de C3P0 durante las pruebas confirma que la configuración de `RDCConnector` es correcta y que el pool se expande y contrae según lo esperado por la demanda. - ' * **Robustez del Código**: La gestión de contadores de peticiones activas es ahora consistente, thread-safe y a prueba de fallos de tipo, mejorando la estabilidad general del servidor bajo carga. - - '- VERSION 5.09.13.3 (Ahora consolidado en 5.09.15) - '- Implementación de "Hot-Swap" para recarga de configuraciones de DB sin reiniciar el servidor. - '- Migración a ReentrantLock para sincronización debido a incompatibilidad con 'Sync'. - '- **Problemas Resueltos:** - '- 1. **Falta de "Hot-Swap" en `reload`:** El comando `reload` en `Manager.bas` no permitía la recarga dinámica de las configuraciones de la base de datos (config.properties) sin necesidad de reiniciar el servidor. La implementación anterior simplemente re-inicializaba las instancias existentes de `RDCConnector` in-situ, sin liberar los recursos de los pools de conexión anteriores, lo cual era ineficiente y propenso a errores. - '- 2. **Ausencia de un mecanismo de cierre de pools:** No existía un método `Close` en `RDCConnector.bas` que permitiera cerrar ordenadamente los `ConnectionPool` (C3P0) y liberar las conexiones a la base de datos, lo que era crítico para un "hot-swap" limpio . - '- 3. **Incompatibilidad con `Sync`:** La palabra clave `Sync` de B4X no era reconocida por el entorno de desarrollo del usuario, impidiendo su uso para la sincronización de hilos necesaria en el "hot-swap". - '- 4. **Ausencia de `Finally` en B4X:** La palabra clave `Finally` (común en otros lenguajes como Java para asegurar la liberación de recursos) no está disponible directamente en B4X, lo cual planteó un desafío para garantizar la liberación del `ReentrantLock` de forma segura. - '- **Cambios Implementados:** - '- **En `Main.bas`:** - '- * **Declaración de `MainConnectorsLock`:** Se añadió `Public MainConnectorsLock As JavaObject` en `Sub Process_Globals` para declarar una instancia de `java.util.concurrent.locks.ReentrantLock`, que servirá como objeto de bloqueo global para proteger el mapa `Main.Connectors`. - '- * **Inicialización de `MainConnectorsLock`:** Se inicializó `MainConnectorsLock.InitializeNewInstance("java.util.concurrent.locks.ReentrantLock", Null)` en `Sub AppStart`, asegurando que el objeto de bloqueo esté listo al inicio del servidor. - '- **En `RDCConnector.bas`:** - '- * **Método `Public Sub Close`:** Se añadió esta subrutina al final del módulo. Utiliza `JavaObject` para invocar `joPool.RunMethod("close", Null)` sobre la instancia subyacente de C3P0, permitiendo un cierre ordenado y la liberación de todas las conexiones del pool . - '- **En `Manager.bas`:** - '- * **Reemplazo completo de la lógica `If Command = "reload" Then`:** - '- * **Creación de `newConnectors`:** Se crea un mapa temporal (`Dim newConnectors As Map`) para inicializar las **nuevas instancias** de `RDCConnector` con la configuración fresca de los archivos `.properties` . - '- * **Preservación de `oldConnectors`:** Se almacena una referencia al mapa `Main.Connectors` actual en un nuevo mapa (`Dim oldConnectors As Map`) para tener acceso a los conectores antiguos que necesitan ser cerrados . - '- * **Sincronización con `ReentrantLock`:** Para proteger la manipulación del mapa `Main.Connectors` (que es compartido por múltiples hilos), se utilizan `Main.MainConnectorsLock.RunMethod("lock", Null)` y `Main.MainConnectorsLock.RunMethod("unlock", Null)`. Esto asegura que el reemplazo del mapa sea atómico, es decir, que solo un hilo pueda acceder a `Main.Connectors` durante la lectura y la escritura . - '- * **Manejo de Bloqueo Seguro sin `Finally`:** Dado que `Finally` no está disponible en B4X, se implementó un patrón con una bandera booleana (`lockAcquired`) dentro de un bloque `Try...Catch` para garantizar que `unlock()` siempre se ejecute si `lock()` fue exitoso, previniendo interbloqueos . - '- * **Cierre explícito de `oldConnectors`:** Después de que los `newConnectors` reemplazan a los `oldConnectors`, se itera sobre el mapa `oldConnectors` y se llama a `oldRDC.Close` para cada conector, liberando sus recursos de base de datos de manera limpia . - '- * **Validación de inicialización y control de errores:** Se agregó lógica para verificar el éxito de la inicialización de los nuevos conectores y abortar el "hot-swap" si ocurre un error crítico, manteniendo los conectores antiguos activos para evitar una interrupción del servicio . - '- * **Registro detallado:** Se mejoró la salida del log HTML del `Manager` para mostrar el proceso de recarga, las estadísticas de los pools recién inicializados y el cierre de los antiguos, incluyendo JSON detallado de las métricas de C3P0 . - '- • Beneficio: Estos cambios dotan al servidor jRDC2-Multi de una capacidad crítica para actualizar sus configuraciones de conexión a bases de datos en caliente, sin necesidad de reiniciar el servicio. Esto mejora la disponibilidad, simplifica el mantenimiento y previene fugas de recursos al asegurar el cierre ordenado de los pools de conexión antiguos. - - '- VERSION 5.09.13.2 (Ahora consolidado en 5.09.15) - '- Módulo: DBHandlerJSON.bas - '- Descripción de Cambios: Manejo de Peticiones POST con Content-Type: application/json - '- • Problema Identificado: La implementación anterior de DBHandlerJSON procesaba las peticiones POST esperando que el payload JSON se encontrara en el parámetro j de la URL (req.GetParameter("j")). Esto impedía la correcta lectura de peticiones POST que utilizaban Content-Type: application/json, donde el JSON se envía directamente en el cuerpo de la petición (InputStream). Como resultado, los clientes recibían un error indicando la ausencia del parámetro j . - '- • Solución Implementada: - '- 1. Se modificó la lógica en el método Handle para detectar explícitamente las peticiones POST con Content-Type igual a application/json. - '- 2. En estos casos, el payload JSON ahora se lee directamente del InputStream de la petición (req.InputStream). - '- 3. Se utilizó Bit.InputStreamToBytes(Is0) para leer el cuerpo completo de la petición a un Array de bytes, seguido de BytesToString para convertirlo en la cadena JSON. - '- 4. Se añadió el cierre explícito del InputStream (Is0.Close) para asegurar la liberación de recursos . - '- 5. Se corrigió el nombre de la variable Is a Is0 para evitar un conflicto con la palabra reservada Is de B4X . - '- 6. Se actualizó el mensaje de error para aclarar que el JSON puede faltar tanto en el parámetro j como en el cuerpo de la petición. - '- • Beneficio: Esta corrección asegura que el DBHandlerJSON sea compatible con el "Método Recomendado" de POST con application/json, mejorando la robustez y la adherencia a los estándares de las APIs web, Sin comprometer la retrocompatibilidad con el "Método Legacy" (GET con parámetro j). - - '- VERSION 5.09.13 (Ahora consolidado en 5.09.15) - ' feat: Mejora la inicialización del pool de conexiones y el soporte multi-DB. - ' - ' **Problemas Resueltos:** - ' - ' 1. **Inicialización de `TotalConnections: 0` en todos los pools:** Anteriormente, el Log mostraba 0 conexiones inicializadas para todas las bases de datos (DB1, DB2, DB3, DB4) durante `AppStart`, a pesar de que los `handlers` de `DBHandlerB4X` y `DBHandlerJSON` podían conectarse más tarde bajo demanda. Esto indicaba un fallo silencioso en la creación de conexiones iniciales por parte de C3P0. - ' 2. **Configuración inconsistente de C3P0:** Parámetros críticos de C3P0 como `acquireRetryAttempts` y `breakAfterAcquireFailure` no se aplicaban correctamente al inicio, manteniendo los valores por defecto que ocultaban errores de conexión. - ' 3. **`jdbcUrl` truncado/vacío:** Se observó que la `jdbcUrl` aparecía truncada o vacía en algunos logs de C3P0, indicando un problema en la carga de la configuración. - ' - ' **Cambios Implementados:** - ' - ' **En `Main.bas`:** - ' - ' * **Declaración de conectores:** Se aseguró la declaración de variables `Dim conX As RDCConnector` separadas para cada conector (con1, con2, con3, con4) para evitar conflictos de variables y asegurar la inicialización correcta. - ' - ' **En `RDCConnector.bas`:** - ' - ' * **Corrección de shadowing de `config`:** Se modificó `LoadConfigMap(DB)` para asignar directamente a la variable de clase `config` (eliminando `Dim` local), resolviendo el problema de la `jdbcUrl` truncada y asegurando que cada `RDCConnector` use su configuración específica de manera persistente. - ' * **Reordenamiento y robustecimiento de `Initialize`:** - ' * **Carga de `config`:** Se asegura que `config` se cargue completamente en la variable de clase antes de cualquier operación del pool. - ' * **Configuración de C3P0:** Todas las propiedades del pool (incluyendo `setInitialPoolSize`, `setMinPoolSize`, `setMaxPoolSize`, `setMaxIdleTime`, etc. ahora se aplican mediante `jo.RunMethod` *inmediatamente después* de `pool.Initialize` y *antes* de que el pool intente adquirir conexiones. - ' * **Forzar reportes de errores:** Se añadieron las líneas `jo.RunMethod("setAcquireRetryAttempts", Array As Object(1))` y `jo.RunMethod("setBreakAfterAcquireFailure", Array As Object(True))`. Estas son cruciales para forzar a C3P0 a lanzar una `SQLException` explícita si falla al crear las conexiones iniciales, en lugar de fallar silenciosamente. - ' * **Activación forzada del pool:** Se implementó `Dim tempCon As SQL = pool.GetConnection` seguido de `tempCon.Close` dentro de un bloque `Try...Catch`. Esto obliga al pool a establecer las conexiones iniciales (`InitialPoolSize`) con la configuración ya aplicada, permitiendo la captura de errores reales si la conexión falla. - - '- VERSION 5.09.08 - '- Se agregó que se puedan configurar en el config.properties los siguientes parametros: - '- - setInitialPoolSize = 3 - '- - setMinPoolSize = 2 - '- - setMaxPoolSize = 5 - '- Se agregaron en duro a RDConnector los siguientes parametros: - '- - setMaxIdleTime <-- Tiempo máximo de inactividad de la conexión. - '- - setMaxConnectionAge <-- Tiempo de vida máximo de una conexión. - '- - setCheckoutTimeout <-- Tiempo máximo de espera por una conexión. - '- Se agregó en el config.properties, al final del "JdbcUrl" este parametro, que le indica al servidor de Oracle - '- el nombre del cliente que se está conectando "?v$session.program=jRDC_Multi" - '- VERSION 5.09.08 - '- Se cambio el codigo para que en lugar de esperar un mapa con los parametros del query y nombres de los parametros (par1, par2, etc) para definir el ordenamiento, ahora se espera una lista [1,"2",3], y el orden de los parametros se toma directamente del orden en el que se mandan, de la misma forma que en B4A. - '- VERSION 5.09.04 - '- Se cambio el nombre del handler de B4X a DBHandlerB4X. - '- Se quitaron los handlers que ya no servian. - '- VERSION 5.09.01 - '- Se corrigieron errores en "Manager". - '- Se cambiaron nombres de handlers. - '- Se corrigio un error en la ruta de "www/login.html". - '- VERSION 5.08.31 - '- Se corrigio que no avisaba cuando el query no requeria parametros y si se enviaban (en el JSONHandler) - '- VERSION 5.08.30 - '- Se cambiaron los 4 handlers de B4A a uno solo que toma el DB de la ruta automáticamente. - '- Se agregaron validaciones del numero de parametros y si el query no los requiere o se dan de mas o de menos, manda un error especificando eso, ya no se reciben errores directos de la base de datos, esto fue tanto para B4A como para JSON. - '- Se modificó el Readme.md para incluir todos estos cambios. - '- VERSION 5.08.25 - '- Se modificaron los archivos de reinicio de los servicios (servidor y Bow) y se cambio el menu del "manager" para que a seccion de "reload" incluya la liga a reinciar Bow. - '- VERSION 5.08.02 - '- Se hizo un cambio para tratar de que las conexiones se "identifiquen" con Oracle y Jorge pueda saber que conexiones/recursos estamos ocupando - '- VERSION 4.11.14 - '- Se agregó el parametro "setMaxPoolSize=5" para que solo genere 5 conexiones a la base de datos, antes generaba 15. - '- Se quitaron lineas previamente comentadas. - '- VERSION 4.11.09 - '- Commit inicial on Nov 9, 2024 +' - VERSION X.XX.XX (cambios a implementar) +' - Agregar que se puedan usar cualquier cantidad de archivos config.properties +' - Agregar que se pueda recargar solo un archivo de configuracion o todos a la vez. +' - Agregar que el "Test" del manager revise (con el query de Jorge) cuantas conexiones hay actualmente activas, +' - o si no en el test, un nuevo handler, talvez "Conexiones". +' - Agregar una forma de probar con carga el servidor +' +' - VERSION 5.09.16 +' feat: Implementa tolerancia de parámetros configurable y mejora estabilidad general del servidor. +' La tolerancia de parametros permite que si un query requiere 3 parametros y se mandan 4, NO mande un error, +' solo manda a la base de datos los parametros correctos y tira los extras, y guarda una "ADVERTENCIA" en el Log de errores. +' +' Este commit introduce la funcionalidad de `parameterTolerance` configurable y aborda varias mejoras críticas para la estabilidad y eficiencia del jRDC2-Multi. +' +' Principales cambios y beneficios: +' - **Tolerancia de Parámetros**: Añade la propiedad `parameterTolerance` en `config.properties` para controlar el manejo de parámetros de más. Cuando está habilitada, recorta los parámetros excesivos; si está deshabilitada (modo estricto, por defecto), genera un error, aumentando la robustez de la validación. +' - **Inicialización Multi-DB Confiable**: Corrige la lógica de inicialización en `Main.AppStart` para `RDCConnector` de DB3 y DB4, asegurando que cada base de datos tenga su propio *pool* de conexiones correctamente configurado. +' - **Optimización de Ejecución SQL**: Elimina llamadas duplicadas a `ExecQuery2` y `ExecNonQuery2` en `DBHandlerB4X.bas`, garantizando que solo los parámetros validados se utilicen y evitando ejecuciones redundantes en la base de datos. +' - **Refactorización y Limpieza**: Se eliminó la declaración duplicada de `ActiveRequestsCountByDB` en `Main.bas` y la subrutina `Handle0` obsoleta en `Manager.bas`, mejorando la claridad y mantenibilidad del código. +' +' - VERSION 5.09.15 +' +' 1. **Nuevas Funcionalidades en el Panel de Administración (Manager):** +' * Se añadió el comando `slowqueries` al `Manager` para permitir la visualización de las 20 consultas más lentas registradas en la tabla `query_logs` de SQLite [22]. +' * Se mejoró el comando `totalcon` en `Manager.bas` para mostrar estadísticas detalladas de *todos* los pools de conexión C3P0 configurados, obteniendo métricas en tiempo real (TotalConnections, BusyConnections, IdleConnections, etc.) de cada `RDCConnector` [2, 22]. +' * Beneficio: Mayor visibilidad y control proactivo sobre el rendimiento y el uso de recursos del servidor desde la interfaz de administración. +' +' 2. **Optimización de la Gestión de Logs (`query_logs`):** +' * Se implementó un `Public timerLogs As Timer` en `Main.bas` [conversación], que se inicializa en `AppStart` y ejecuta periódicamente (cada 10 minutos) la subrutina `borraArribaDe15000Logs`. +' * La subrutina `borraArribaDe15000Logs` recorta la tabla `query_logs` en `users.db` para mantener solo los 15,000 registros más recientes, y luego realiza un `vacuum` para optimizar el espacio en disco utilizado por la base de datos SQLite [conversación]. +' * Beneficio: Prevención del crecimiento excesivo de la base de datos de logs de rendimiento, manteniendo un historial manejable y optimizando el uso del almacenamiento a largo plazo. +' +' - VERSION 5.09.14 (Ahora consolidado en 5.09.15) +' -feat: Implementación robusta de monitoreo de pool de conexiones y peticiones activas +' -Este commit resuelve problemas críticos en el monitoreo del pool de conexiones (C3P0) y el conteo de peticiones activas por base de datos, mejorando significativamente la visibilidad y fiabilidad del rendimiento del servidor jRDC2-Multi. +' -Problemas Identificados y Resueltos: +' -1. **Métricas de `BusyConnections` y `TotalConnections` inconsistentes o siempre en `0` en el `Manager` y `query_logs`:** +' * **Problema**: Anteriormente, la métrica `busy_connections` en `query_logs` a menudo reportaba `0` o no reflejaba el estado real. De manera similar, el panel de `Manager?command=totalcon` consistentemente mostraba `BusyConnections: 0` y `TotalConnections` estancadas en `InitialPoolSize`, a pesar de que Oracle sí reportaba conexiones activas. Esto generaba confusión sobre el uso real y la expansión del pool. +' * **Solución**: Se modificó la lógica en los *handlers* (`DBHandlerJSON.bas` y `DBHandlerB4X.bas`) para capturar la métrica `BusyConnections` directamente del pool de C3P0 **inmediatamente después de que el *handler* adquiere una conexión** (`con = Connector.GetConnection(finalDbKey)`). Este valor se pasa explícitamente a la subrutina `Main.LogQueryPerformance` para su registro en `query_logs` y para ser consumido por `Manager.bas` a través de `RDCConnector.GetPoolStats`. Esto garantiza que el valor registrado y reportado refleje con precisión el número de conexiones activas en el instante de su adquisición. Pruebas exhaustivas confirmaron que C3P0 sí reporta conexiones ocupadas y sí expande `TotalConnections` hasta `MaxPoolSize` cuando la demanda lo exige. +' -2. **Contador `handler_active_requests` no decrementaba correctamente:** +' * **Problema**: El contador de peticiones activas por base de datos (`GlobalParameters.ActiveRequestsCountByDB`) no mostraba un decremento consistente, resultando en un conteo que solo aumentaba o mostraba valores erráticos en los logs. +' * **Solución**: +' * Se aseguró la declaración `Public ActiveRequestsCountByDB As Map` en `GlobalParameters.bas`. +' * Se garantizó su inicialización como un `srvr.CreateThreadSafeMap` en `Main.AppStart` para un manejo concurrente seguro de los contadores. +' * En `DBHandlerJSON.bas`, la `dbKey` (obtenida del parámetro `dbx` del JSON) ahora se resuelve *antes* de incrementar el contador, asegurando que el incremento y el decremento se apliquen siempre a la misma clave de base de datos correcta. +' * Se implementó una coerción explícita a `Int` (`.As(Int)`) para todas las operaciones de lectura y escritura (`GetDefault`, `Put`) en `GlobalParameters.ActiveRequestsCountByDB`, resolviendo problemas de tipo que causaban inconsistencias y el fallo en el decremento. +' * La lógica de decremento en `Private Sub CleanupAndLog` (presente en ambos *handlers*) se hizo más robusta, verificando que el contador sea mayor que cero antes de decrementar para evitar valores negativos. +' -Beneficios de estos Cambios: +' * **Monitoreo Preciso y Fiable**: Las métricas `busy_connections` y `handler_active_requests` en `query_logs` y el panel `Manager` ahora son totalmente fiables, proporcionando una visión clara y en tiempo real del uso del pool de conexiones y la carga de peticiones activas por base de datos. +' * **Diagnóstico Mejorado**: La visibilidad interna del estado del pool de C3P0 durante las pruebas confirma que la configuración de `RDCConnector` es correcta y que el pool se expande y contrae según lo esperado por la demanda. +' * **Robustez del Código**: La gestión de contadores de peticiones activas es ahora consistente, thread-safe y a prueba de fallos de tipo, mejorando la estabilidad general del servidor bajo carga. +' +' - VERSION 5.09.13.3 (Ahora consolidado en 5.09.15) +' - Implementación de "Hot-Swap" para recarga de configuraciones de DB sin reiniciar el servidor. +' - Migración a ReentrantLock para sincronización debido a incompatibilidad con 'Sync'. +' - **Problemas Resueltos:** +' - 1. **Falta de "Hot-Swap" en `reload`:** El comando `reload` en `Manager.bas` no permitía la recarga dinámica de las configuraciones de la base de datos (config.properties) sin necesidad de reiniciar el servidor. La implementación anterior simplemente re-inicializaba las instancias existentes de `RDCConnector` in-situ, sin liberar los recursos de los pools de conexión anteriores, lo cual era ineficiente y propenso a errores. +' - 2. **Ausencia de un mecanismo de cierre de pools:** No existía un método `Close` en `RDCConnector.bas` que permitiera cerrar ordenadamente los `ConnectionPool` (C3P0) y liberar las conexiones a la base de datos, lo que era crítico para un "hot-swap" limpio . +' - 3. **Incompatibilidad con `Sync`:** La palabra clave `Sync` de B4X no era reconocida por el entorno de desarrollo del usuario, impidiendo su uso para la sincronización de hilos necesaria en el "hot-swap". +' - 4. **Ausencia de `Finally` en B4X:** La palabra clave `Finally` (común en otros lenguajes como Java para asegurar la liberación de recursos) no está disponible directamente en B4X, lo cual planteó un desafío para garantizar la liberación del `ReentrantLock` de forma segura. +' - **Cambios Implementados:** +' - **En `Main.bas`:** +' - * **Declaración de `MainConnectorsLock`:** Se añadió `Public MainConnectorsLock As JavaObject` en `Sub Process_Globals` para declarar una instancia de `java.util.concurrent.locks.ReentrantLock`, que servirá como objeto de bloqueo global para proteger el mapa `Main.Connectors`. +' - * **Inicialización de `MainConnectorsLock`:** Se inicializó `MainConnectorsLock.InitializeNewInstance("java.util.concurrent.locks.ReentrantLock", Null)` en `Sub AppStart`, asegurando que el objeto de bloqueo esté listo al inicio del servidor. +' - **En `RDCConnector.bas`:** +' - * **Método `Public Sub Close`:** Se añadió esta subrutina al final del módulo. Utiliza `JavaObject` para invocar `joPool.RunMethod("close", Null)` sobre la instancia subyacente de C3P0, permitiendo un cierre ordenado y la liberación de todas las conexiones del pool . +' - **En `Manager.bas`:** +' - * **Reemplazo completo de la lógica `If Command = "reload" Then`:** +' - * **Creación de `newConnectors`:** Se crea un mapa temporal (`Dim newConnectors As Map`) para inicializar las **nuevas instancias** de `RDCConnector` con la configuración fresca de los archivos `.properties` . +' - * **Preservación de `oldConnectors`:** Se almacena una referencia al mapa `Main.Connectors` actual en un nuevo mapa (`Dim oldConnectors As Map`) para tener acceso a los conectores antiguos que necesitan ser cerrados . +' - * **Sincronización con `ReentrantLock`:** Para proteger la manipulación del mapa `Main.Connectors` (que es compartido por múltiples hilos), se utilizan `Main.MainConnectorsLock.RunMethod("lock", Null)` y `Main.MainConnectorsLock.RunMethod("unlock", Null)`. Esto asegura que el reemplazo del mapa sea atómico, es decir, que solo un hilo pueda acceder a `Main.Connectors` durante la lectura y la escritura . +' - * **Manejo de Bloqueo Seguro sin `Finally`:** Dado que `Finally` no está disponible en B4X, se implementó un patrón con una bandera booleana (`lockAcquired`) dentro de un bloque `Try...Catch` para garantizar que `unlock()` siempre se ejecute si `lock()` fue exitoso, previniendo interbloqueos . +' - * **Cierre explícito de `oldConnectors`:** Después de que los `newConnectors` reemplazan a los `oldConnectors`, se itera sobre el mapa `oldConnectors` y se llama a `oldRDC.Close` para cada conector, liberando sus recursos de base de datos de manera limpia . +' - * **Validación de inicialización y control de errores:** Se agregó lógica para verificar el éxito de la inicialización de los nuevos conectores y abortar el "hot-swap" si ocurre un error crítico, manteniendo los conectores antiguos activos para evitar una interrupción del servicio . +' - * **Registro detallado:** Se mejoró la salida del log HTML del `Manager` para mostrar el proceso de recarga, las estadísticas de los pools recién inicializados y el cierre de los antiguos, incluyendo JSON detallado de las métricas de C3P0 . +' - • Beneficio: Estos cambios dotan al servidor jRDC2-Multi de una capacidad crítica para actualizar sus configuraciones de conexión a bases de datos en caliente, sin necesidad de reiniciar el servicio. Esto mejora la disponibilidad, simplifica el mantenimiento y previene fugas de recursos al asegurar el cierre ordenado de los pools de conexión antiguos. +' +' - VERSION 5.09.13.2 (Ahora consolidado en 5.09.15) +' - Módulo: DBHandlerJSON.bas +' - Descripción de Cambios: Manejo de Peticiones POST con Content-Type: application/json +' - • Problema Identificado: La implementación anterior de DBHandlerJSON procesaba las peticiones POST esperando que el payload JSON se encontrara en el parámetro j de la URL (req.GetParameter("j")). Esto impedía la correcta lectura de peticiones POST que utilizaban Content-Type: application/json, donde el JSON se envía directamente en el cuerpo de la petición (InputStream). Como resultado, los clientes recibían un error indicando la ausencia del parámetro j . +' - • Solución Implementada: +' - 1. Se modificó la lógica en el método Handle para detectar explícitamente las peticiones POST con Content-Type igual a application/json. +' - 2. En estos casos, el payload JSON ahora se lee directamente del InputStream de la petición (req.InputStream). +' - 3. Se utilizó Bit.InputStreamToBytes(Is0) para leer el cuerpo completo de la petición a un Array de bytes, seguido de BytesToString para convertirlo en la cadena JSON. +' - 4. Se añadió el cierre explícito del InputStream (Is0.Close) para asegurar la liberación de recursos . +' - 5. Se corrigió el nombre de la variable Is a Is0 para evitar un conflicto con la palabra reservada Is de B4X . +' - 6. Se actualizó el mensaje de error para aclarar que el JSON puede faltar tanto en el parámetro j como en el cuerpo de la petición. +' - • Beneficio: Esta corrección asegura que el DBHandlerJSON sea compatible con el "Método Recomendado" de POST con application/json, mejorando la robustez y la adherencia a los estándares de las APIs web, Sin comprometer la retrocompatibilidad con el "Método Legacy" (GET con parámetro j). +' +' - VERSION 5.09.13 (Ahora consolidado en 5.09.15) +' feat: Mejora la inicialización del pool de conexiones y el soporte multi-DB. +' +' **Problemas Resueltos:** +' +' 1. **Inicialización de `TotalConnections: 0` en todos los pools:** Anteriormente, el Log mostraba 0 conexiones inicializadas para todas las bases de datos (DB1, DB2, DB3, DB4) durante `AppStart`, a pesar de que los `handlers` de `DBHandlerB4X` y `DBHandlerJSON` podían conectarse más tarde bajo demanda. Esto indicaba un fallo silencioso en la creación de conexiones iniciales por parte de C3P0. +' 2. **Configuración inconsistente de C3P0:** Parámetros críticos de C3P0 como `acquireRetryAttempts` y `breakAfterAcquireFailure` no se aplicaban correctamente al inicio, manteniendo los valores por defecto que ocultaban errores de conexión. +' 3. **`jdbcUrl` truncado/vacío:** Se observó que la `jdbcUrl` aparecía truncada o vacía en algunos logs de C3P0, indicando un problema en la carga de la configuración. +' +' **Cambios Implementados:** +' +' **En `Main.bas`:** +' +' * **Declaración de conectores:** Se aseguró la declaración de variables `Dim conX As RDCConnector` separadas para cada conector (con1, con2, con3, con4) para evitar conflictos de variables y asegurar la inicialización correcta. +' +' **En `RDCConnector.bas`:** +' +' * **Corrección de shadowing de `config`:** Se modificó `LoadConfigMap(DB)` para asignar directamente a la variable de clase `config` (eliminando `Dim` local), resolviendo el problema de la `jdbcUrl` truncada y asegurando que cada `RDCConnector` use su configuración específica de manera persistente. +' * **Reordenamiento y robustecimiento de `Initialize`:** +' * **Carga de `config`:** Se asegura que `config` se cargue completamente en la variable de clase antes de cualquier operación del pool. +' * **Configuración de C3P0:** Todas las propiedades del pool (incluyendo `setInitialPoolSize`, `setMinPoolSize`, `setMaxPoolSize`, `setMaxIdleTime`, etc. ahora se aplican mediante `jo.RunMethod` *inmediatamente después* de `pool.Initialize` y *antes* de que el pool intente adquirir conexiones. +' * **Forzar reportes de errores:** Se añadieron las líneas `jo.RunMethod("setAcquireRetryAttempts", Array As Object(1))` y `jo.RunMethod("setBreakAfterAcquireFailure", Array As Object(True))`. Estas son cruciales para forzar a C3P0 a lanzar una `SQLException` explícita si falla al crear las conexiones iniciales, en lugar de fallar silenciosamente. +' * **Activación forzada del pool:** Se implementó `Dim tempCon As SQL = pool.GetConnection` seguido de `tempCon.Close` dentro de un bloque `Try...Catch`. Esto obliga al pool a establecer las conexiones iniciales (`InitialPoolSize`) con la configuración ya aplicada, permitiendo la captura de errores reales si la conexión falla. +' +' - VERSION 5.09.08 +' - Se agregó que se puedan configurar en el config.properties los siguientes parametros: +' - - setInitialPoolSize = 3 +' - - setMinPoolSize = 2 +' - - setMaxPoolSize = 5 +' - Se agregaron en duro a RDConnector los siguientes parametros: +' - - setMaxIdleTime <-- Tiempo máximo de inactividad de la conexión. +' - - setMaxConnectionAge <-- Tiempo de vida máximo de una conexión. +' - - setCheckoutTimeout <-- Tiempo máximo de espera por una conexión. +' - Se agregó en el config.properties, al final del "JdbcUrl" este parametro, que le indica al servidor de Oracle +' - el nombre del cliente que se está conectando "?v$session.program=jRDC_Multi" +' - VERSION 5.09.08 +' - Se cambio el codigo para que en lugar de esperar un mapa con los parametros del query y nombres de los parametros (par1, par2, etc) para definir el ordenamiento, ahora se espera una lista [1,"2",3], y el orden de los parametros se toma directamente del orden en el que se mandan, de la misma forma que en B4A. +' - VERSION 5.09.04 +' - Se cambio el nombre del handler de B4X a DBHandlerB4X. +' - Se quitaron los handlers que ya no servian. +' - VERSION 5.09.01 +' - Se corrigieron errores en "Manager". +' - Se cambiaron nombres de handlers. +' - Se corrigio un error en la ruta de "www/login.html". +' - VERSION 5.08.31 +' - Se corrigio que no avisaba cuando el query no requeria parametros y si se enviaban (en el JSONHandler) +' - VERSION 5.08.30 +' - Se cambiaron los 4 handlers de B4A a uno solo que toma el DB de la ruta automáticamente. +' - Se agregaron validaciones del numero de parametros y si el query no los requiere o se dan de mas o de menos, manda un error especificando eso, ya no se reciben errores directos de la base de datos, esto fue tanto para B4A como para JSON. +' - Se modificó el Readme.md para incluir todos estos cambios. +' - VERSION 5.08.25 +' - Se modificaron los archivos de reinicio de los servicios (servidor y Bow) y se cambio el menu del "manager" para que a seccion de "reload" incluya la liga a reinciar Bow. +' - VERSION 5.08.02 +' - Se hizo un cambio para tratar de que las conexiones se "identifiquen" con Oracle y Jorge pueda saber que conexiones/recursos estamos ocupando +' - VERSION 4.11.14 +' - Se agregó el parametro "setMaxPoolSize=5" para que solo genere 5 conexiones a la base de datos, antes generaba 15. +' - Se quitaron lineas previamente comentadas. +' - VERSION 4.11.09 +' - Commit inicial on Nov 9, 2024 End Sub \ No newline at end of file diff --git a/ChangePassHandler.bas b/ChangePassHandler.bas index 2ad28e0..2665945 100644 --- a/ChangePassHandler.bas +++ b/ChangePassHandler.bas @@ -32,8 +32,7 @@ Public Sub Handle(req As ServletRequest, resp As ServletResponse) Try Dim storedHash As String = Main.SQL1.ExecQuerySingleResult2("SELECT password_hash FROM users WHERE username = ?", Array As String(currentUser)) - - Log("--- Probando con contraseña fija ---") + Log("Valor de la BD (storedHash): " & storedHash) If storedHash = Null Or bc.checkpw(currentPass, storedHash) = False Then ' <<--- CAMBIO CLAVE AQUÍ resp.Write("") diff --git a/DBHandlerB4X.bas b/DBHandlerB4X.bas index 4f0bacb..c6482d2 100644 --- a/DBHandlerB4X.bas +++ b/DBHandlerB4X.bas @@ -17,7 +17,7 @@ Sub Class_Globals ' La siguiente sección de constantes y utilidades se compila condicionalmente ' solo si la directiva #if VERSION1 está activa. Esto es para dar soporte ' a una versión antigua del protocolo de comunicación de DBRequestManager. - #if VERSION1 +' #if VERSION1 ' Constantes para identificar los tipos de datos en la serialización personalizada (protocolo V1). Private const T_NULL = 0, T_STRING = 1, T_SHORT = 2, T_INT = 3, T_LONG = 4, T_FLOAT = 5 _ ,T_DOUBLE = 6, T_BOOLEAN = 7, T_BLOB = 8 As Byte @@ -25,7 +25,7 @@ Sub Class_Globals Private bc As ByteConverter ' Utilidad para comprimir/descomprimir streams de datos (usado en V1). Private cs As CompressedStreams - #end if +' #end if ' Mapa para convertir tipos de columna JDBC de fecha/hora a los nombres de métodos de Java ' para obtener los valores correctos de ResultSet. @@ -68,13 +68,11 @@ Sub Handle(req As ServletRequest, resp As ServletResponse) ' Verifica si el dbKey extraído corresponde a una base de datos configurada y cargada en Main. If Main.Connectors.ContainsKey(dbKey) = False Then - ' Si la base de datos no es válida, se construye un mensaje de error y se envía. Dim ErrorMsg As String = $"Invalid DB key specified in URL: '${dbKey}'. Valid keys are: ${Main.listaDeCP}"$ Log(ErrorMsg) - SendPlainTextError(resp, 400, ErrorMsg) ' Envía una respuesta de error al cliente. - ' No se llama a CleanupAndLog aquí, ya que el contador de peticiones no se ha incrementado - ' y no se ha obtenido ninguna conexión del pool. - Return ' Termina la ejecución del handler. + Main.LogServerError("ERROR", "DBHandlerB4X.Handle", ErrorMsg, dbKey, Null, req.RemoteAddress) ' <-- Nuevo Log + SendPlainTextError(resp, 400, ErrorMsg) + Return End If ' === FIN DE LA LÓGICA DINÁMICA === @@ -130,7 +128,7 @@ Sub Handle(req As ServletRequest, resp As ServletResponse) CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) Return ' Salida temprana si hay un error. End If - #if VERSION1 +' #if VERSION1 ' Estas ramas se compilan solo si #if VERSION1 está activo (para protocolo antiguo). Else if method = "query" Then in = cs.WrapInputStream(in, "gzip") ' Descomprime el stream de entrada si es protocolo V1. @@ -148,7 +146,7 @@ Sub Handle(req As ServletRequest, resp As ServletResponse) CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) Return End If - #end if +' #end if Else if method = "batch2" Then ' Ejecuta un lote de comandos (INSERT, UPDATE, DELETE) utilizando el protocolo V2. q = ExecuteBatch2(dbKey, con, in, resp) @@ -158,18 +156,19 @@ Sub Handle(req As ServletRequest, resp As ServletResponse) Return ' Salida temprana si hay un error. End If Else - ' Si el método solicitado no es reconocido, se registra un error y se envía una respuesta adecuada. - Log("Unknown method: " & method) + Dim ErrorMsg As String = "Unknown method: " & method + Log(ErrorMsg) + Main.LogServerError("ERROR", "DBHandlerB4X.Handle", ErrorMsg, dbKey, method, req.RemoteAddress) ' <-- Nuevo Log SendPlainTextError(resp, 500, "unknown method") - q = "unknown_method_handler" ' Aseguramos un valor para 'q' para que el log sea informativo. + q = "unknown_method_handler" duration = DateTime.Now - start CleanupAndLog(dbKey, q, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) - Return ' Salida temprana. + Return End If Catch ' --- CATCH: Maneja errores generales de ejecución o de SQL --- - ' Si ocurre una excepción inesperada durante el procesamiento de la petición. Log(LastException) ' Registra la excepción completa en el log. + Main.LogServerError("ERROR", "DBHandlerB4X.Handle", LastException.Message, dbKey, q, req.RemoteAddress) ' <-- Nuevo Log SendPlainTextError(resp, 500, LastException.Message) ' Envía un error 500 al cliente. q = "error_in_b4x_handler" ' Aseguramos un valor para 'q' en caso de excepción. End Try ' --- FIN: Bloque Try principal --- @@ -238,34 +237,33 @@ Private Sub ExecuteQuery2 (DB As String, con As SQL, in As InputStream, resp As If sqlCommand = Null Or sqlCommand = "null" Or sqlCommand.Trim = "" Then Dim errorMessage As String = $"El comando '${cmd.Name}' no fue encontrado en el config.properties de '${DB}'."$ Log(errorMessage) + Main.LogServerError("ERROR", "DBHandlerB4X.ExecuteQuery2", errorMessage, DB, cmd.Name, Null) ' Envía un error 400 (Bad Request) al cliente informando del problema. SendPlainTextError(resp, 400, errorMessage) Return "error" ' Retorna un texto para el log. End If ' <<< FIN NUEVA VALIDACIÓN >>> - ' --- INICIO VALIDACIÓN DE PARÁMETROS --- - ' Comprueba si el SQL espera parámetros o si se recibieron parámetros. - If sqlCommand.Contains("?") Or (cmd.Parameters <> Null And cmd.Parameters.Length > 0) Then - ' Cuenta cuántos '?' hay en la sentencia SQL para saber cuántos parámetros se esperan. - Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length - ' Cuenta cuántos parámetros se recibieron. - Dim receivedParams As Int - If cmd.Parameters = Null Then receivedParams = 0 Else receivedParams = cmd.Parameters.Length - - ' Compara el número de parámetros esperados con los recibidos. - If expectedParams <> receivedParams Then - Dim errorMessage As String = $"Número de parametros equivocado para "${cmd.Name}". Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$ - Log(errorMessage) - ' Si no coinciden, envía un error 400 al cliente. - SendPlainTextError(resp, 400, errorMessage) - Return "error" - End If + ' <<< INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA >>> + ' Convertimos el array de Object() de cmd.Parameters a una List para la utilidad de validación. + Dim paramsAsList As List + paramsAsList.Initialize + If cmd.Parameters <> Null Then + For Each p As Object In cmd.Parameters + paramsAsList.Add(p) + Next End If - ' --- FIN VALIDACIÓN --- - ' Ejecuta la consulta SQL con los parámetros proporcionados. - Dim rs As ResultSet = con.ExecQuery2(sqlCommand, cmd.Parameters) + Dim validationResult As ParameterValidationResult = ParameterValidationUtils.ValidateAndAdjustParameters(cmd.Name, DB, sqlCommand, paramsAsList, Connector.IsParameterToleranceEnabled) + + If validationResult.Success = False Then + SendPlainTextError(resp, 400, validationResult.ErrorMessage) + Return "error" ' Salida temprana si la validación falla. + End If + + ' Ejecuta la consulta SQL con la lista de parámetros validada. + Dim rs As ResultSet = con.ExecQuery2(sqlCommand, validationResult.ParamsToExecute) + ' <<< FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA >>> ' Si el límite es 0 o negativo, lo establece a un valor muy alto (máximo entero). If limit <= 0 Then limit = 0x7fffffff 'max int @@ -367,28 +365,32 @@ Private Sub ExecuteBatch2(DB As String, con As SQL, in As InputStream, resp As S con.Rollback ' Deshace la transacción si un comando es inválido. Dim errorMessage As String = $"El comando '${cmd.Name}' no fue encontrado en el config.properties de '${DB}'."$ Log(errorMessage) + Main.LogServerError("ERROR", "DBHandlerB4X.ExecuteBatch2", errorMessage, DB, cmd.Name, Null) SendPlainTextError(resp, 400, errorMessage) Return "error" End If ' <<< FIN NUEVA VALIDACIÓN >>> - ' --- INICIO VALIDACIÓN DE PARÁMETROS DENTRO DEL BATCH --- - If sqlCommand.Contains("?") Or (cmd.Parameters <> Null And cmd.Parameters.Length > 0) Then - Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length - Dim receivedParams As Int - If cmd.Parameters = Null Then receivedParams = 0 Else receivedParams = cmd.Parameters.Length - ' Si el número de parámetros no coincide, deshace la transacción y envía error. - If expectedParams <> receivedParams Then - con.Rollback - Dim errorMessage As String = $"Número de parametros equivocado para "${cmd.Name}". Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$ - Log(errorMessage) - SendPlainTextError(resp, 400, errorMessage) - Return "error" - End If + ' <<< INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA DENTRO DEL BATCH >>> + ' Convertimos el array de Object() de cmd.Parameters a una List para la utilidad de validación. + Dim paramsAsList As List + paramsAsList.Initialize + If cmd.Parameters <> Null Then + For Each p As Object In cmd.Parameters + paramsAsList.Add(p) + Next End If - ' --- FIN VALIDACIÓN --- - con.ExecNonQuery2(sqlCommand, cmd.Parameters) ' Ejecuta el comando (no es una consulta, no devuelve filas). + Dim validationResult As ParameterValidationResult = ParameterValidationUtils.ValidateAndAdjustParameters(cmd.Name, DB, sqlCommand, paramsAsList, Connector.IsParameterToleranceEnabled) + + If validationResult.Success = False Then + con.Rollback ' ¡Importante hacer rollback si la validación falla dentro de una transacción! + SendPlainTextError(resp, 400, validationResult.ErrorMessage) + Return "error" ' Salida temprana si la validación falla. + End If + + con.ExecNonQuery2(sqlCommand, validationResult.ParamsToExecute) ' Ejecuta el comando con la lista de parámetros validada. + ' <<< FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA DENTRO DEL BATCH >>> Next res.Rows.Add(Array As Object(0)) ' Añade una fila simbólica al resultado para indicar éxito. @@ -397,6 +399,7 @@ Private Sub ExecuteBatch2(DB As String, con As SQL, in As InputStream, resp As S ' Si cualquier comando falla, se captura el error. con.Rollback ' Se deshacen todos los cambios hechos en la transacción. Log(LastException) ' Registra la excepción. + Main.LogServerError("ERROR", "DBHandlerB4X.ExecuteBatch2", LastException.Message, DB, "batch_execution_error", Null) SendPlainTextError(resp, 500, LastException.Message) ' Envía un error 500 al cliente. End Try @@ -410,7 +413,7 @@ End Sub ' --- Subrutinas para manejar la ejecución de queries y batches (Protocolo V1 - Compilación Condicional) --- ' Este código se compila solo si #if VERSION1 está activo, para mantener compatibilidad con clientes antiguos. -#if VERSION1 +'#if VERSION1 ' Ejecuta un lote de comandos usando el protocolo V1. Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As ServletResponse) As String @@ -434,28 +437,23 @@ Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As Se con.Rollback ' Deshace la transacción si un comando es inválido. Dim errorMessage As String = $"El comando '${queryName}' no fue encontrado en el config.properties de '${DB}'."$ Log(errorMessage) + Main.LogServerError("ERROR", "DBHandlerB4X.ExecuteBatch (V1)", errorMessage, DB, queryName, Null) SendPlainTextError(resp, 400, errorMessage) Return "error" End If - ' <<< FIN NUEVA VALIDACIÓN >>> + ' <<< FIN NUEVA VALIDACIÓN >>> - ' --- INICIO VALIDACIÓN DE PARÁMETROS DENTRO DEL BATCH (V1) --- - If sqlCommand.Contains("?") Or (params <> Null And params.Size > 0) Then - Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length - Dim receivedParams As Int - If params = Null Then receivedParams = 0 Else receivedParams = params.Size + ' <<< INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA DENTRO DEL BATCH (V1) >>> + Dim validationResult As ParameterValidationResult = ParameterValidationUtils.ValidateAndAdjustParameters(queryName, DB, sqlCommand, params, Connector.IsParameterToleranceEnabled) - If expectedParams <> receivedParams Then - con.Rollback - Dim errorMessage As String = $"Número de parametros equivocado para "${queryName}". Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$ - Log(errorMessage) - SendPlainTextError(resp, 400, errorMessage) - Return "error" - End If - End If - ' --- FIN VALIDACIÓN --- + If validationResult.Success = False Then + con.Rollback ' ¡Importante hacer rollback si la validación falla dentro de una transacción! + SendPlainTextError(resp, 400, validationResult.ErrorMessage) + Return "error" ' Salida temprana si la validación falla. + End If - con.ExecNonQuery2(sqlCommand, params) ' Ejecuta el comando. + con.ExecNonQuery2(sqlCommand, validationResult.ParamsToExecute) ' Ejecuta el comando con la lista de parámetros validada. + ' <<< FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA DENTRO DEL BATCH (V1) >>> Next con.TransactionSuccessful ' Confirma la transacción. @@ -473,6 +471,7 @@ Private Sub ExecuteBatch(DB As String, con As SQL, in As InputStream, resp As Se Catch con.Rollback Log(LastException) + Main.LogServerError("ERROR", "DBHandlerB4X.ExecuteBatch (V1)", LastException.Message, DB, "batch_execution_error_v1", Null) SendPlainTextError(resp, 500, LastException.Message) End Try @@ -495,28 +494,23 @@ Private Sub ExecuteQuery(DB As String, con As SQL, in As InputStream, resp As Se If theSql = Null Or theSql ="null" Or theSql.Trim = "" Then Dim errorMessage As String = $"El comando '${queryName}' no fue encontrado en el config.properties de '${DB}'."$ Log(errorMessage) + Main.LogServerError("ERROR", "DBHandlerB4X.ExecuteQuery (V1)", errorMessage, DB, queryName, Null) SendPlainTextError(resp, 400, errorMessage) Return "error" End If - ' <<< FIN NUEVA VALIDACIÓN >>> + ' <<< FIN NUEVA VALIDACIÓN >>> - ' --- INICIO VALIDACIÓN DE PARÁMETROS (V1) --- - If theSql.Contains("?") Or (params <> Null And params.Size > 0) Then - Dim expectedParams As Int = theSql.Length - theSql.Replace("?", "").Length - Dim receivedParams As Int - If params = Null Then receivedParams = 0 Else receivedParams = params.Size + ' <<< INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA (V1) >>> + Dim validationResult As ParameterValidationResult = ParameterValidationUtils.ValidateAndAdjustParameters(queryName, DB, theSql, params, Connector.IsParameterToleranceEnabled) - If expectedParams <> receivedParams Then - Dim errorMessage As String = $"Número de parametros equivocado para "${queryName}". Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$ - Log(errorMessage) - SendPlainTextError(resp, 400, errorMessage) - Return "error" - End If - End If - ' --- FIN VALIDACIÓN --- + If validationResult.Success = False Then + SendPlainTextError(resp, 400, validationResult.ErrorMessage) + Return "error" ' Salida temprana si la validación falla. + End If - ' Ejecuta la consulta. - Dim rs As ResultSet = con.ExecQuery2(theSql, params) + ' Ejecuta la consulta con la lista de parámetros validada. + Dim rs As ResultSet = con.ExecQuery2(theSql, validationResult.ParamsToExecute) + ' <<< FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA (V1) >>> If limit <= 0 Then limit = 0x7fffffff 'max int @@ -691,7 +685,7 @@ Private Sub ReadList(in As InputStream) As List Return l1 End Sub -#end If ' Fin del bloque de compilación condicional para VERSION1 +'#end If ' Fin del bloque de compilación condicional para VERSION1 ' Envía una respuesta de error en formato de texto plano. ' Esto evita la página de error HTML por defecto que genera resp.SendError. @@ -716,6 +710,8 @@ Private Sub SendPlainTextError(resp As ServletResponse, statusCode As Int, error Catch ' Si algo falla al intentar enviar la respuesta de error, lo registra en el log ' para que no se pierda la causa original del problema. - Log("Error sending plain text error response: " & LastException) + Dim ErrorMsg As String = "Error sending plain text error response: " & LastException + Log(ErrorMsg) + Main.LogServerError("ERROR", "DBHandlerB4X.SendPlainTextError", ErrorMsg, Null, Null, Null) End Try End Sub \ No newline at end of file diff --git a/DBHandlerJSON.bas b/DBHandlerJSON.bas index d2f74f5..24b822f 100644 --- a/DBHandlerJSON.bas +++ b/DBHandlerJSON.bas @@ -63,11 +63,12 @@ Sub Handle(req As ServletRequest, resp As ServletResponse) ' Validación inicial: Si no hay JSON, se envía un error 400. If jsonString = Null Or jsonString = "" Then - SendErrorResponse(resp, 400, "Falta el parámetro 'j' en el URL o el cuerpo JSON en la petición.") + Dim ErrorMsg As String = "Falta el parámetro 'j' en el URL o el cuerpo JSON en la petición." + SendErrorResponse(resp, 400, ErrorMsg) + Main.LogServerError("ERROR", "DBHandlerJSON.Handle", ErrorMsg, finalDbKey, queryNameForLog, req.RemoteAddress) ' <-- Nuevo Log duration = DateTime.Now - start - ' Llama a CleanupAndLog para registrar que hubo un error, pero con contadores a 0 o inicializados. CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) - Return ' Salida temprana si no hay JSON válido. + Return End If Dim parser As JSONParser @@ -108,10 +109,12 @@ Sub Handle(req As ServletRequest, resp As ServletResponse) ' Validación: Si el dbKey no es válido o no está configurado en Main.listaDeCP. If Main.listaDeCP.IndexOf(finalDbKey) = -1 Then - SendErrorResponse(resp, 400, "Parámetro 'DB' inválido. El nombre '" & finalDbKey & "' no es válido.") + Dim ErrorMsg As String = "Parámetro 'DB' inválido. El nombre '" & finalDbKey & "' no es válido." + SendErrorResponse(resp, 400, ErrorMsg) + Main.LogServerError("ERROR", "DBHandlerJSON.Handle", ErrorMsg, finalDbKey, queryNameForLog, req.RemoteAddress) ' <-- Nuevo Log duration = DateTime.Now - start CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) - Return ' Salida temprana si la DB no es válida. + Return End If con = Connector.GetConnection(finalDbKey) ' ¡La conexión a la BD se obtiene aquí del pool de conexiones! @@ -135,31 +138,30 @@ Sub Handle(req As ServletRequest, resp As ServletResponse) If sqlCommand = Null Or sqlCommand = "null" Or sqlCommand.Trim = "" Then Dim errorMessage As String = $"El comando '${queryNameForLog}' no fue encontrado en el config.properties de '${finalDbKey}'."$ Log(errorMessage) + Main.LogServerError("ERROR", "DBHandlerJSON.Handle", errorMessage, finalDbKey, queryNameForLog, req.RemoteAddress) ' <-- Nuevo Log SendErrorResponse(resp, 400, errorMessage) duration = DateTime.Now - start CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) - Return ' Salida temprana. + Return End If ' --- Lógica para ejecutar diferentes tipos de comandos basados en el parámetro 'execType' --- If execType.ToLowerCase = "executequery" Then - Dim rs As ResultSet - ' Validación de parámetros para ExecuteQuery. - If sqlCommand.Contains("?") Or paramsList.Size > 0 Then - Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length - Dim receivedParams As Int = paramsList.Size - Log($"expectedParams: ${expectedParams}, receivedParams: ${receivedParams}"$) - If expectedParams <> receivedParams Then - SendErrorResponse(resp, 400, $"Número de parámetros equivocado para '${queryNameForLog}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$) - duration = DateTime.Now - start - CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) - Return ' Salida temprana. - End If - rs = con.ExecQuery2(sqlCommand, paramsList) ' Ejecuta la consulta con parámetros. - Else - rs = con.ExecQuery(sqlCommand) ' Ejecuta la consulta sin parámetros. + ' --- INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA --- + Dim validationResult As ParameterValidationResult = ParameterValidationUtils.ValidateAndAdjustParameters(queryNameForLog, finalDbKey, sqlCommand, paramsList, Connector.IsParameterToleranceEnabled) + + If validationResult.Success = False Then + SendErrorResponse(resp, 400, validationResult.ErrorMessage) + duration = DateTime.Now - start + CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) + Return ' Salida temprana. End If + Dim rs As ResultSet + ' Ejecuta la consulta SQL con la lista de parámetros validada. + rs = con.ExecQuery2(sqlCommand, validationResult.ParamsToExecute) + ' --- FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA --- + Dim ResultList As List ResultList.Initialize ' Lista para almacenar los resultados de la consulta. Dim jrs As JavaObject = rs ' Objeto Java subyacente del ResultSet para metadatos. @@ -180,29 +182,29 @@ Sub Handle(req As ServletRequest, resp As ServletResponse) SendSuccessResponse(resp, CreateMap("result": ResultList)) ' Envía la respuesta JSON de éxito. Else If execType.ToLowerCase = "executecommand" Then - ' Validación de parámetros para ExecuteCommand. - If sqlCommand.Contains("?") Then - Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length - Dim receivedParams As Int = paramsList.Size - If expectedParams <> receivedParams Then - SendErrorResponse(resp, 400, $"Número de parámetros equivocado para '${queryNameForLog}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$) - duration = DateTime.Now - start - CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) - Return ' Salida temprana. - End If - End If - con.ExecNonQuery2(sqlCommand, paramsList) ' Ejecuta un comando (INSERT, UPDATE, DELETE). - SendSuccessResponse(resp, CreateMap("message": "Command executed successfully")) ' Envía confirmación de éxito. + ' --- INICIO VALIDACIÓN DE PARÁMETROS CENTRALIZADA --- + Dim validationResult As ParameterValidationResult = ParameterValidationUtils.ValidateAndAdjustParameters(queryNameForLog, finalDbKey, sqlCommand, paramsList, Connector.IsParameterToleranceEnabled) + If validationResult.Success = False Then + SendErrorResponse(resp, 400, validationResult.ErrorMessage) + duration = DateTime.Now - start + CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con) + Return ' Salida temprana. + End If + + con.ExecNonQuery2(sqlCommand, validationResult.ParamsToExecute) ' Ejecuta un comando con la lista de parámetros validada. + SendSuccessResponse(resp, CreateMap("message": "Command executed successfully")) ' Envía confirmación de éxito. + ' --- FIN VALIDACIÓN DE PARÁMETROS CENTRALIZADA --- Else - ' Si el tipo de ejecución no es reconocido. - SendErrorResponse(resp, 400, "Parámetro 'exec' inválido. '" & execType & "' no es un valor permitido.") + Dim ErrorMsg As String = "Parámetro 'exec' inválido. '" & execType & "' no es un valor permitido." + SendErrorResponse(resp, 400, ErrorMsg) + Main.LogServerError("ERROR", "DBHandlerJSON.Handle", ErrorMsg, finalDbKey, queryNameForLog, req.RemoteAddress) ' <-- Nuevo Log ' El flujo continúa hasta la limpieza final si no hay un Return explícito. End If Catch ' --- CATCH: Maneja errores generales de ejecución o de SQL/JSON --- - ' Si ocurre una excepción inesperada durante el procesamiento de la petición. Log(LastException) ' Registra la excepción completa en el log. + Main.LogServerError("ERROR", "DBHandlerJSON.Handle", LastException.Message, finalDbKey, queryNameForLog, req.RemoteAddress) ' <-- Nuevo Log SendErrorResponse(resp, 500, LastException.Message) ' Envía un error 500 al cliente. queryNameForLog = "error_processing_json" ' Para registrar que hubo un error en el log. End Try ' --- FIN: Bloque Try principal --- diff --git a/Files/config.DB2.properties b/Files/config.DB2.properties index e64e7a3..70e6c4f 100644 --- a/Files/config.DB2.properties +++ b/Files/config.DB2.properties @@ -11,13 +11,19 @@ DriverClass=oracle.jdbc.driver.OracleDriver #GOHAN ---> server #JdbcUrl=jdbc:oracle:thin:@//10.0.0.205:1521/DBKMT #JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT -JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_Multi +JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_MultiSALMA -# Configuración del pool de conexiones para DB2 +# Configuración del pool de conexiones InitialPoolSize=3 MinPoolSize=2 -MaxPoolSize=10 -AcquireIncrement=5 +MaxPoolSize=15 +AcquireIncrement=2 + +# Configuración de tolerancia de parámetros: +# 1 = Habilita la tolerancia a parámetros de más (se recortarán los excesivos). +# 0 = Deshabilita la tolerancia (el servidor será estricto y lanzará un error si hay parámetros de más). +# Por defecto, si no se especifica o el valor es diferente de 1, la tolerancia estará DESHABILITADA (modo estricto). +parameterTolerance=1 # SVR-KEYMON-PRODUCCION--> Usuario User=SALMA @@ -50,7 +56,7 @@ Debug=true sql.traeConexion=select 'DB2' as conexion from dual sql.select_soporte=select * from GUNA.soporte sql.select_conexion=SELECT 'OK' AS VALOR FROM DUAL -sql.traeConexion4=SELECT (?) AS p1, (?) AS p2, (?) AS p3 FROM DUAL +sql.traeConexion4=SELECT 'DB2' as db, (?) AS p1, (?) AS p2, (?) AS p3 FROM DUAL sql.select_version=select cat_ve_version from cat_version sql.select_version_GV2=select cat_ve_version from GUNA.cat_version sql.selectAlmacen=select * from cat_almacen where cat_al_id = ? diff --git a/Files/config.DB3.properties b/Files/config.DB3.properties index 9e96b5f..f98be91 100644 --- a/Files/config.DB3.properties +++ b/Files/config.DB3.properties @@ -11,20 +11,21 @@ DriverClass=oracle.jdbc.driver.OracleDriver #GOHAN ---> server #JdbcUrl=jdbc:oracle:thin:@//10.0.0.205:1521/DBKMT #JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT -JdbcUrl=jdbc:oracle:thin:@//192.168.101.12:1521/DBKMT?v$session.program=jRDC_Multi +JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_MultiSALMA -# Configuración del pool de conexiones para DB2 +# Configuración del pool de conexiones InitialPoolSize=3 MinPoolSize=2 -MaxPoolSize=10 -AcquireIncrement=5 +MaxPoolSize=15 +AcquireIncrement=2 # SVR-KEYMON-PRODUCCION--> Usuario -#User=GUNA -#Password=GUNAD2015M +User=SALMA +Password=SALMAD2016M + +#User=TORRADOCONAUTO +#Password=TORRADOCONAUTOD2016M -User=TORRADOCONAUTO -Password=TORRADOCONAUTOD2016M #--> Puertos #SAC - DFR - MDA / GOHAN -->COBRANZA @@ -46,8 +47,19 @@ Debug=true ################# ################## -sql.traeConexion=select 'DB3' as conexion from dual +sql.traeConexion=select 'DB2' as conexion from dual sql.select_soporte=select * from GUNA.soporte +sql.select_conexion=SELECT 'OK' AS VALOR FROM DUAL +sql.traeConexion4=SELECT 'DB2' as db, (?) AS p1, (?) AS p2, (?) AS p3 FROM DUAL +sql.select_version=select cat_ve_version from cat_version +sql.select_version_GV2=select cat_ve_version from GUNA.cat_version +sql.selectAlmacen=select * from cat_almacen where cat_al_id = ? +sql.sv=select * from cat_rutas where CAT_RU_RUTA = ? +sql.verify_device=select * from kelloggs.GUIDs where almacen = ? and ruta = ? +sql.registarMovil=insert into kelloggs.GUIDs (almacen, ruta, guid, estatus) values (?, ?, ?, 'ok') +sql.update_usuario_guna_nobajas=UPDATE GUNA.CAT_LOGINS SET CAT_LO_ESTATUS = 'Activo',CAT_LO_CONECTADO ='0' WHERE CAT_LO_ESTATUS != 'Baja' and CAT_LO_USUARIO = (?) + +sql.proc_usuario=BEGIN EXECUTE IMMEDIATE ('DECLARE Cursor_SYS Sys_Refcursor; BEGIN SP_ACTIVAR_USUARIO( '''||(?)||''',Cursor_SYS); end;'); END; sql.select_almacenes_KELL=select CAT_AG_ID, CAT_AG_NOMBRE from KELLOGGS.cat_agencias order by CAT_AG_NOMBRE sql.select_almacenes_GUNA=select CAT_AG_ID, CAT_AG_NOMBRE from GUNA.cat_agencias order by CAT_AG_NOMBRE diff --git a/Files/config.DB4.properties b/Files/config.DB4.properties index a12e987..f98be91 100644 --- a/Files/config.DB4.properties +++ b/Files/config.DB4.properties @@ -11,13 +11,13 @@ DriverClass=oracle.jdbc.driver.OracleDriver #GOHAN ---> server #JdbcUrl=jdbc:oracle:thin:@//10.0.0.205:1521/DBKMT #JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT -JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_Multi +JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_MultiSALMA -# Configuración del pool de conexiones para DB2 +# Configuración del pool de conexiones InitialPoolSize=3 MinPoolSize=2 -MaxPoolSize=10 -AcquireIncrement=5 +MaxPoolSize=15 +AcquireIncrement=2 # SVR-KEYMON-PRODUCCION--> Usuario User=SALMA @@ -31,7 +31,7 @@ Password=SALMAD2016M #SAC - DFR - MDA / GOHAN -->COBRANZA #ServerPort=1783 #GUNA - SALMA - DURAKELO - DBC / SVR-KEYMON-PRODUCCION --> DISTRIBUIDORAS -ServerPort=9000 +ServerPort=9010 #CMG - TORRADO / TRUNKS -->COBRANZA/ GM #ServerPort=1781 @@ -47,9 +47,19 @@ Debug=true ################# ################## -sql.traeConexion=select 'DB4' as conexion from dual +sql.traeConexion=select 'DB2' as conexion from dual sql.select_soporte=select * from GUNA.soporte sql.select_conexion=SELECT 'OK' AS VALOR FROM DUAL +sql.traeConexion4=SELECT 'DB2' as db, (?) AS p1, (?) AS p2, (?) AS p3 FROM DUAL +sql.select_version=select cat_ve_version from cat_version +sql.select_version_GV2=select cat_ve_version from GUNA.cat_version +sql.selectAlmacen=select * from cat_almacen where cat_al_id = ? +sql.sv=select * from cat_rutas where CAT_RU_RUTA = ? +sql.verify_device=select * from kelloggs.GUIDs where almacen = ? and ruta = ? +sql.registarMovil=insert into kelloggs.GUIDs (almacen, ruta, guid, estatus) values (?, ?, ?, 'ok') +sql.update_usuario_guna_nobajas=UPDATE GUNA.CAT_LOGINS SET CAT_LO_ESTATUS = 'Activo',CAT_LO_CONECTADO ='0' WHERE CAT_LO_ESTATUS != 'Baja' and CAT_LO_USUARIO = (?) + +sql.proc_usuario=BEGIN EXECUTE IMMEDIATE ('DECLARE Cursor_SYS Sys_Refcursor; BEGIN SP_ACTIVAR_USUARIO( '''||(?)||''',Cursor_SYS); end;'); END; sql.select_almacenes_KELL=select CAT_AG_ID, CAT_AG_NOMBRE from KELLOGGS.cat_agencias order by CAT_AG_NOMBRE sql.select_almacenes_GUNA=select CAT_AG_ID, CAT_AG_NOMBRE from GUNA.cat_agencias order by CAT_AG_NOMBRE diff --git a/Files/config.properties b/Files/config.properties index e177db6..e30705b 100644 --- a/Files/config.properties +++ b/Files/config.properties @@ -13,11 +13,17 @@ DriverClass=oracle.jdbc.driver.OracleDriver #JdbcUrl=jdbc:oracle:thin:@//10.0.0.236:1521/DBKMT JdbcUrl=jdbc:oracle:thin:@//192.168.101.10:1521/DBKMT?v$session.program=jRDC_Multi -# Configuración del pool de conexiones para DB1 +# Configuración del pool de conexiones InitialPoolSize=3 MinPoolSize=2 -MaxPoolSize=10 -AcquireIncrement=5 +MaxPoolSize=15 +AcquireIncrement=2 + +# Configuración de tolerancia de parámetros: +# 1 = Habilita la tolerancia a parámetros de más (se recortarán los excesivos). +# 0 = Deshabilita la tolerancia (el servidor será estricto y lanzará un error si hay parámetros de más). +# Por defecto, si no se especifica o el valor es diferente de 1, la tolerancia estará DESHABILITADA (modo estricto). +parameterTolerance=1 # SVR-KEYMON-PRODUCCION--> Usuario User=GUNA @@ -52,7 +58,7 @@ sql.select_revisaClienteCredito_GUNA2=select (select count(CAT_CL_CODIGO) from G sql.traeConexion=select 'DB1' as conexion from dual sql.traeConexion2=select 'DB1' as conexion from dual sql.traeConexion3=select '1' as par1, 2 as par2 from dual -sql.traeConexion4=SELECT (?) AS p1, (?) AS p2, (?) AS p3 FROM DUAL +sql.traeConexion4=SELECT 'DB1' as db, (?) AS p1, (?) AS p2, (?) AS p3 FROM DUAL sql.select_soporte=select * from GUNA.soporte sql.select_conexion=SELECT 'OK' AS VALOR FROM DUAL sql.selectAlmacen=select cat_al_id, cat_al_desc, cat_al_archftp from cat_almacen where cat_al_id = ? diff --git a/Manager.bas b/Manager.bas index 3dc59db..38325a2 100644 --- a/Manager.bas +++ b/Manager.bas @@ -154,14 +154,14 @@ Sub Handle(req As ServletRequest, resp As ServletResponse) Dim newPoolStats As Map = newRDC.GetPoolStats sbTemp.Append($" -> ${dbKey}: Nuevo conector inicializado. Conexiones: ${newPoolStats.Get("TotalConnections")}"$).Append("
" & CRLF) - + Catch sbTemp.Append($" -> ERROR CRÍTICO al inicializar nuevo conector para ${dbKey}: ${LastException.Message}"$).Append("
" & CRLF) reloadSuccessful = False Exit ' Si uno falla, abortamos la recarga completa para evitar un estado inconsistente. End Try Next - + sb.Append(sbTemp.ToString) ' Añadimos los logs acumulados de la inicialización al StringBuilder principal. If reloadSuccessful Then @@ -380,112 +380,3 @@ Sub Handle(req As ServletRequest, resp As ServletResponse) If GlobalParameters.mpLogs.IsInitialized Then GlobalParameters.mpLogs.Put(Command, "Manager : " & Command & " - Time : " & DateTime.Time(DateTime.Now)) End Sub - -Sub Handle0(req As ServletRequest, resp As ServletResponse) - ' 1. --- Bloque de Seguridad (se mantiene igual) --- - If req.GetSession.GetAttribute2("user_is_authorized", False) = False Then - resp.SendRedirect("/login") - Return - End If - - Dim Command As String = req.GetParameter("command") - If Command = "" Then Command = "ping" - Log($"Command: ${Command}"$) - resp.ContentType = "text/html" - - ' 2. --- Construimos la ESTRUCTURA de la página --- - Dim sb As StringBuilder - sb.Initialize - - ' Estilos para la página - sb.Append("") - - ' Cabecera y bienvenida - sb.Append("

Panel de Administración jRDC

") - sb.Append($"Bienvenido, ${req.GetSession.GetAttribute("username")}
"$) - - ' Menú de navegación (se define una sola vez) - sb.Append("") - - ' Formulario para cambiar contraseña - sb.Append("
") - sb.Append("

Cambiar Contraseña

") - sb.Append("
") - sb.Append("Contraseña Actual:
") - sb.Append("Nueva Contraseña:
") - sb.Append("Confirmar Nueva Contraseña:
") - sb.Append("") - sb.Append("
") - - ' Sección para el resultado del comando - sb.Append("

Resultado del Comando: '" & Command & "'

") - sb.Append("
") - - ' 3. --- Lógica de TUS COMANDOS (modificada para usar sb.Append) --- - If Command = "reload" Then - Private estaDB As String = "" - For i = 0 To Main.listaDeCP.Size - 1 - Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).Initialize(Main.listaDeCP.get(i)) - If Main.listaDeCP.get(i) <> "DB1" Then estaDB = "." & Main.listaDeCP.get(i) Else estaDB = "" - sb.Append($"Recargando config${estaDB}.properties ($DateTime{DateTime.Now})
"$) - sb.Append($"Queries en config.properties: ${Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).commands.Size}
"$) - sb.Append($"JdbcUrl: ${Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).config.Get("JdbcUrl")}
"$) - sb.Append($"User: ${Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).config.Get("User")}
"$) - sb.Append($"ServerPort: ${Main.srvr.Port}

"$) - Next - else If Command = "stop" Then - ' Tu código para "stop" - else If Command = "rsx" Then - Log($"Ejecutamos ${File.DirApp}\start.bat"$) - sb.Append($"Ejecutamos ${File.DirApp}\start.bat"$) - Public shl As Shell - shl.Initialize("shl","cmd",Array("/c",File.DirApp & "\start.bat " & Main.srvr.Port)) - shl.WorkingDirectory = File.DirApp - shl.Run(-1) - else If Command = "rpm2" Then - Log($"Ejecutamos ${File.DirApp}\reiniciaProcesoPM2.bat"$) - sb.Append($"Ejecutamos ${File.DirApp}\reiniciaProcesoPM2.bat"$) - Public shl As Shell - shl.Initialize("shl","cmd",Array("/c",File.DirApp & "\reiniciaProcesoPM2.bat " & Main.srvr.Port)) - shl.WorkingDirectory = File.DirApp - shl.Run(-1) - else If Command = "reviveBow" Then - Log($"Ejecutamos ${File.DirApp}\reiniciaProcesoBow.bat"$) - sb.Append($"Ejecutamos ${File.DirApp}\reiniciaProcesoBow.bat

"$) - sb.Append($"!!!BOW REINICIANDO!!!"$) - Public shl As Shell - shl.Initialize("shl","cmd",Array("/c",File.DirApp & "\reiniciaProcesoBow.bat " & Main.srvr.Port)) - shl.WorkingDirectory = File.DirApp - shl.Run(-1) - else if Command = "totalrequests" Then - If GlobalParameters.mpTotalRequests.IsInitialized Then - j.Initialize(GlobalParameters.mpTotalRequests) - sb.Append(j.ToString) - End If - else if Command = "ping" Then - sb.Append($"Pong ($DateTime{DateTime.Now})"$) - End If - '...(aquí continuaría el resto de tus Else If)... - - - ' 4. --- Cerramos la página y la enviamos TODA JUNTA --- - sb.Append("
") ' Cierre de div.output - sb.Append("

Cerrar Sesión

") - sb.Append("") - - resp.Write(sb.ToString) ' Se envía toda la página de una vez - - ' Lógica final de logs (se mantiene igual) - If GlobalParameters.mpLogs.IsInitialized Then GlobalParameters.mpLogs.Put(Command, "Manager : " & Command & " - Time : " & DateTime.Time(DateTime.Now)) -End Sub - diff --git a/ParameterValidationUtils.bas b/ParameterValidationUtils.bas new file mode 100644 index 0000000..ad8e95d --- /dev/null +++ b/ParameterValidationUtils.bas @@ -0,0 +1,69 @@ +B4J=true +Group=Default Group +ModulesStructureVersion=1 +Type=StaticCode +Version=10.3 +@EndOfDesignText@ +' Archivo: ParameterValidationUtils.bas +' Módulo de utilidad: ParameterValidationUtils +' Centraliza la lógica de validación y ajuste de parámetros SQL. +' Ahora soporta recorte de parámetros excesivos. + +Sub Process_Globals + ' El Type ParameterValidationResult está declarado en Main.bas, no se declara aquí. +End Sub + +' Valida y ajusta la lista de parámetros para la ejecución SQL, aplicando la lógica de tolerancia. +' Retorna un ParameterValidationResult indicando éxito/error y los parámetros a usar. +Public Sub ValidateAndAdjustParameters (CommandName As String, DBKey As String, sqlCommand As String, receivedParams As List, IsToleranceEnabled As Boolean) As ParameterValidationResult + Dim res As ParameterValidationResult + res.Initialize + res.Success = True ' Asumimos éxito inicialmente + + Log(">>>> IsToleranceEnabled: " & IsToleranceEnabled) + + ' Aseguramos que receivedParams esté inicializada, incluso si está vacía o Null + If receivedParams = Null Or receivedParams.IsInitialized = False Then + receivedParams.Initialize ' Inicializa una lista vacía si es Null o no inicializada. + End If + + ' Contar cuántos '?' hay en la sentencia SQL para saber cuántos parámetros se esperan. + Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length + Dim receivedParamsSize As Int = receivedParams.Size + + If receivedParamsSize < expectedParams Then + ' Caso 1: Se recibieron MENOS parámetros de los esperados. Esto es un error. + res.Success = False + res.ErrorMessage = $"ERROR: Número de parámetros insuficiente para "${CommandName}" (DB: ${DBKey}). Se esperaban ${expectedParams} y se recibieron ${receivedParamsSize}."$ + Log(res.ErrorMessage) + Main.LogServerError("ERROR", "ParameterValidationUtils.ValidateAndAdjustParameters", res.ErrorMessage, DBKey, CommandName, Null) ' <-- Nuevo Log + Return res + Else If receivedParamsSize > expectedParams Then + ' Caso 2: Se recibieron MÁS parámetros de los esperados. + If IsToleranceEnabled Then ' Solo recortamos si la tolerancia está habilitada + Dim adjustedParams As List + adjustedParams.Initialize + For i = 0 To expectedParams - 1 + adjustedParams.Add(receivedParams.Get(i)) + Next + res.ParamsToExecute = adjustedParams + res.Success = True + Dim WarningMsg As String = $"ADVERTENCIA: Se recibieron más parámetros de los esperados para "${CommandName}" (DB: ${DBKey}). Se esperaban ${expectedParams} y se recibieron ${receivedParamsSize}. Se ajustó la lista de parámetros a ${expectedParams} elementos."$ + Log(WarningMsg) + Main.LogServerError("ADVERTENCIA", "ParameterValidationUtils.ValidateAndAdjustParameters", WarningMsg, DBKey, CommandName, Null) ' <-- Nuevo Log [6] + Else + ' Si la tolerancia NO está habilitada, esto es un error crítico. + res.Success = False + res.ErrorMessage = $"ERROR: Número de parámetros excesivo para "${CommandName}" (DB: ${DBKey}). Se esperaban ${expectedParams} y se recibieron ${receivedParamsSize}. La tolerancia a parámetros de más está DESHABILITADA."$ + Log(res.ErrorMessage) + Main.LogServerError("ERROR", "ParameterValidationUtils.ValidateAndAdjustParameters", res.ErrorMessage, DBKey, CommandName, Null) + Return res + End If + Else + ' Caso 3: Se recibieron el número EXACTO de parámetros. Todo bien. + res.ParamsToExecute = receivedParams ' Usamos la lista original tal cual. + res.Success = True ' Confirmamos éxito. + End If + + Return res +End Sub \ No newline at end of file diff --git a/RDCConnector.bas b/RDCConnector.bas index 9b52b4b..6228fe0 100644 --- a/RDCConnector.bas +++ b/RDCConnector.bas @@ -31,6 +31,9 @@ Sub Class_Globals ' Almacena la configuración completa (DriverClass, JdbcUrl, User, Password, InitialPoolSize, etc.) ' cargada de su respectivo archivo .properties. Public config As Map + + ' Indica si la tolerancia a parámetros de más está activa. + Public IsParameterToleranceEnabled As Boolean End Sub ' Subrutina de inicialización para el conector de una base de datos específica. @@ -44,6 +47,16 @@ Public Sub Initialize(DB As String) ' Es CRUCIAL que se asigne a la variable de CLASE 'config' (sin 'Dim' local) ' para que la configuración cargada del archivo sea persistente para esta instancia del conector. config = LoadConfigMap(DB) + + ' Leer la configuración de tolerancia de parámetros + Dim toleranceSetting As Int = config.GetDefault("parameterTolerance", 0).As(Int) ' Por defecto, 0 (estricto) + IsParameterToleranceEnabled = (toleranceSetting = 1) ' La tolerancia se habilita si el valor es 1 + + If IsParameterToleranceEnabled Then + Log($"RDCConnector.Initialize para ${DB}: Tolerancia a parámetros de más, HABILITADA."$) + Else + Log($"RDCConnector.Initialize para ${DB}: Tolerancia a parámetros de más, DESHABILITADA (modo estricto)."$) + End If ' Bloque Try-Catch para la inicialización y configuración del pool. ' Esto es esencial para capturar cualquier error crítico que impida la conexión inicial a la base de datos. @@ -78,7 +91,7 @@ Public Sub Initialize(DB As String) ' LÍNEAS CRÍTICAS PARA FORZAR UN COMPORTAMIENTO NO SILENCIOSO DE C3P0: ' Por defecto, C3P0 puede reintentar muchas veces y no lanzar una excepción si las conexiones iniciales fallan. ' Estas líneas fuerzan a C3P0 a ser estricto y reportar errores de inmediato. - jo.RunMethod("setAcquireRetryAttempts", Array As Object(1)) ' Limita los intentos iniciales de adquisición a 1. + jo.RunMethod("setAcquireRetryAttempts", Array As Object(2)) ' Limita los intentos iniciales de adquisición a 1. jo.RunMethod("setBreakAfterAcquireFailure", Array As Object(True)) ' ¡Forza a C3P0 a lanzar una excepción si falla al adquirir conexiones! ' PASO 4: Forzar la creación de conexiones iniciales y verificar el estado. @@ -138,7 +151,9 @@ Public Sub Initialize(DB As String) Catch ' Si ocurre un error durante la inicialización del pool o al forzar la conexión, ' este Log es CRÍTICO para el diagnóstico, especialmente en un entorno de producción. - Log($"RDCConnector.Initialize para ${DB}: ERROR CRÍTICO al inicializar/forzar conexión: ${LastException.Message}"$) + Dim ErrorMsg As String = $"RDCConnector.Initialize para ${DB}: ERROR CRÍTICO al inicializar/forzar conexión: ${LastException.Message}"$ + Log(ErrorMsg) + Main.LogServerError("ERROR", "RDCConnector.Initialize", ErrorMsg, DB, Null, Null) End Try ' Configuración de depuración de queries. Se activa automáticamente si el proyecto se ejecuta en modo DEBUG. @@ -178,7 +193,9 @@ Public Sub GetCommand(DB As String, Key As String) As String ' Obtiene los comandos de la DB específica del mapa global Main.commandsMap. commands = Main.commandsMap.Get(DB).As(Map) If commands.ContainsKey("sql." & Key) = False Then - Log($"RDCConnector.GetCommand: *** Comando no encontrado: '${Key}' para DB: '${DB}' ***"$) ' Log importante si un comando no se encuentra. + Dim ErrorMsg As String = $"RDCConnector.GetCommand: *** Comando no encontrado: '${Key}' para DB: '${DB}' ***"$ + Log(ErrorMsg) + Main.LogServerError("ERROR", "RDCConnector.GetCommand", ErrorMsg, DB, Key, Null) ' Log importante si un comando no se encuentra. End If Return commands.Get("sql." & Key) ' Retorna la sentencia SQL. End Sub @@ -285,12 +302,16 @@ Public Sub GetPoolStats As Map Catch ' Si ocurre un error al obtener las estadísticas, se registra y se añade un mensaje de error al mapa. - Log("RDCConnector.GetPoolStats: ERROR CRÍTICO al obtener estadísticas del pool: " & LastException.Message) + Dim ErrorMsg As String = "RDCConnector.GetPoolStats: ERROR CRÍTICO al obtener estadísticas del pool: " & LastException.Message + Log(ErrorMsg) + Main.LogServerError("ERROR", "RDCConnector.GetPoolStats", ErrorMsg, "Todas", Null, Null) ' <-- Nuevo Log stats.Put("Error", LastException.Message) End Try Else ' Si el pool no está inicializado, se registra una advertencia y se devuelve un mapa con un error. - Log("RDCConnector.GetPoolStats: ADVERTENCIA: Pool NO está inicializado. Retornando mapa con error.") + Dim WarningMsg As String = "RDCConnector.GetPoolStats: ADVERTENCIA: Pool NO está inicializado. Retornando mapa con error." + Log(WarningMsg) + Main.LogServerError("ADVERTENCIA", "RDCConnector.GetPoolStats", WarningMsg, "Todas", Null, Null) ' <-- Nuevo Log stats.Put("Error", "Pool de conexiones no inicializado para esta DB.") End If diff --git a/TestHandler.bas b/TestHandler.bas index c6d7643..785770b 100644 --- a/TestHandler.bas +++ b/TestHandler.bas @@ -22,7 +22,7 @@ Sub Handle(req As ServletRequest, resp As ServletResponse) ' Dim con As SQL = Main.rdcConnectorDB1.GetConnection("") Dim con As SQL = Main.Connectors.Get("DB1").As(RDCConnector).GetConnection("") resp.Write("Connection successful.

") - Private estaDB As String = "" + Dim estaDB As String = "" Log(Main.listaDeCP) For i = 0 To Main.listaDeCP.Size - 1 If Main.listaDeCP.get(i) <> "" Then estaDB = "." & Main.listaDeCP.get(i) diff --git a/jRDC_Multi.b4j b/jRDC_Multi.b4j index a6c580e..28e3e7b 100644 --- a/jRDC_Multi.b4j +++ b/jRDC_Multi.b4j @@ -32,9 +32,10 @@ Library8=jsql Library9=bcrypt Module1=Cambios Module10=Manager -Module11=ping -Module12=RDCConnector -Module13=TestHandler +Module11=ParameterValidationUtils +Module12=ping +Module13=RDCConnector +Module14=TestHandler Module2=ChangePassHandler Module3=DBHandlerB4X Module4=DBHandlerJSON @@ -45,7 +46,7 @@ Module8=LoginHandler Module9=LogoutHandler NumberOfFiles=10 NumberOfLibraries=9 -NumberOfModules=13 +NumberOfModules=14 Version=10.3 @EndOfDesignText@ 'Non-UI application (console / server application) @@ -53,7 +54,7 @@ Version=10.3 #Region Project Attributes #CommandLineArgs: #MergeLibraries: True -' VERSION 5.09.15 +' VERSION 5.09.16 '########################################################################################################### '###################### PULL ############################################################# 'Ctrl + click ide://run?file=%WINDIR%\System32\cmd.exe&Args=/c&Args=git&Args=pull @@ -107,11 +108,15 @@ Sub Process_Globals ' Objeto de bloqueo (ReentrantLock) para proteger el mapa Main.Connectors durante operaciones de recarga (Hot-Swap). Public MainConnectorsLock As JavaObject - ' Mapa para contar las peticiones activas por cada base de datos. Es thread-safe. - Public ActiveRequestsCountByDB As Map - ' Timer para ejecutar tareas periódicas, como la limpieza de logs. Public timerLogs As Timer + + ' Tipo para encapsular el resultado de la validación de parámetros. + Type ParameterValidationResult ( _ + Success As Boolean, _ + ErrorMessage As String, _ + ParamsToExecute As List _ ' La lista de parámetros final a usar en la ejecución SQL +) End Sub Sub AppStart (Args() As String) @@ -149,12 +154,20 @@ Sub AppStart (Args() As String) ' === 4. INICIALIZACIÓN DEL CONECTOR PARA LA BASE DE DATOS PRINCIPAL (DB1) === ' DB1 siempre usa el archivo 'config.properties' por defecto. - Dim con1 As RDCConnector ' Declara una variable específica y única para el conector de DB1. - con1.Initialize("DB1") ' Inicializa la instancia del conector para "DB1". - Connectors.Put("DB1", con1) ' Asocia el identificador "DB1" con su instancia de RDCConnector. - srvr.Port = con1.serverPort ' El puerto del servidor HTTP se obtiene del config.properties de DB1. - listaDeCP.Add("DB1") ' Añade "DB1" a la lista de bases de datos gestionadas. - Log($"Main.AppStart: Conector 'DB1' inicializado exitosamente en puerto: ${srvr.Port}"$) + Try + Dim con1 As RDCConnector ' Declara una variable específica y única para el conector de DB1. + con1.Initialize("DB1") ' Inicializa la instancia del conector para "DB1". + Connectors.Put("DB1", con1) ' Asocia el identificador "DB1" con su instancia de RDCConnector. + srvr.Port = con1.serverPort ' El puerto del servidor HTTP se obtiene del config.properties de DB1. + listaDeCP.Add("DB1") ' Añade "DB1" a la lista de bases de datos gestionadas. + Log($"Main.AppStart: Conector 'DB1' inicializado exitosamente en puerto: ${srvr.Port}"$) + Catch + Dim ErrorMsg As String = $"Main.AppStart: ERROR CRÍTICO al inicializar conector 'DB1': ${LastException.Message}"$ + Log(ErrorMsg) + LogServerError("ERROR", "Main.AppStart", ErrorMsg, "DB1", Null, Null) + ' Si DB1 falla, el servidor no puede arrancar correctamente. + ExitApplication + End Try ' === 5. DETECCIÓN E INICIALIZACIÓN DE BASES DE DATOS ADICIONALES (DB2, DB3, DB4) === ' El servidor busca archivos de configuración adicionales (ej. config.DB2.properties) @@ -164,27 +177,48 @@ Sub AppStart (Args() As String) For i = 0 To cpFiles.Size - 1 ' Procesa 'config.DB2.properties' If cpFiles.Get(i) = "config.DB2.properties" Then - Dim con2 As RDCConnector ' Declara una variable específica y única para el conector de DB2. - con2.Initialize("DB2") ' Inicializa la instancia del conector para "DB2". - Connectors.Put("DB2", con2) ' Asocia "DB2" con su instancia de RDCConnector. - listaDeCP.Add("DB2") ' Añade "DB2" a la lista de bases de datos. - Log("Main.AppStart: Conector 'DB2' inicializado exitosamente.") + Try + Dim con2 As RDCConnector + con2.Initialize("DB2") + Connectors.Put("DB2", con2) + listaDeCP.Add("DB2") + Log("Main.AppStart: Conector 'DB2' inicializado exitosamente.") + Catch + Dim ErrorMsg As String = $"Main.AppStart: ERROR al inicializar conector 'DB2': ${LastException.Message}"$ + Log(ErrorMsg) + LogServerError("ERROR", "Main.AppStart", ErrorMsg, "DB2", Null, Null) + ' Si un conector secundario falla, el servidor puede continuar, pero es importante saberlo. + End Try End If ' Procesa 'config.DB3.properties' If cpFiles.Get(i) = "config.DB3.properties" Then - Dim con3 As RDCConnector ' Declara una variable específica y única para el conector de DB3. - con3.Initialize("DB3") ' Inicializa la instancia del conector para "DB3". - Connectors.Put("DB3", con3) ' Asocia "DB3" con su instancia de RDCConnector. - listaDeCP.Add("DB3") ' Añade "DB3" a la lista de bases de datos. - Log("Main.AppStart: Conector 'DB3' inicializado exitosamente.") + Try + Dim con3 As RDCConnector + con3.Initialize("DB3") + Connectors.Put("DB3", con3) + listaDeCP.Add("DB3") + Log("Main.AppStart: Conector 'DB3' inicializado exitosamente.") + Catch + Dim ErrorMsg As String = $"Main.AppStart: ERROR al inicializar conector 'DB3': ${LastException.Message}"$ + Log(ErrorMsg) + LogServerError("ERROR", "Main.AppStart", ErrorMsg, "DB3", Null, Null) + ' Si un conector secundario falla, el servidor puede continuar, pero es importante saberlo. + End Try End If ' Procesa 'config.DB4.properties' If cpFiles.Get(i) = "config.DB4.properties" Then - Dim con4 As RDCConnector ' Declara una variable específica y única para el conector de DB4. - con4.Initialize("DB4") ' Inicializa la instancia del conector para "DB4". - Connectors.Put("DB4", con4) ' Asocia "DB4" con su instancia de RDCConnector. - listaDeCP.Add("DB4") ' Añade "DB4" a la lista de bases de datos. - Log("Main.AppStart: Conector 'DB4' inicializado exitosamente.") + Try + Dim con4 As RDCConnector + con4.Initialize("DB4") + Connectors.Put("DB4", con4) + listaDeCP.Add("DB4") + Log("Main.AppStart: Conector 'DB4' inicializado exitosamente.") + Catch + Dim ErrorMsg As String = $"Main.AppStart: ERROR al inicializar conector 'DB4': ${LastException.Message}"$ + Log(ErrorMsg) + LogServerError("ERROR", "Main.AppStart", ErrorMsg, "DB4", Null, Null) + ' Si un conector secundario falla, el servidor puede continuar, pero es importante saberlo. + End Try End If Next Else @@ -261,6 +295,15 @@ Sub InitializeSQLiteDatabase ' Inserta el usuario por defecto en la tabla 'users'. SQL1.ExecNonQuery2("INSERT INTO users (username, password_hash) VALUES (?, ?)", Array As Object(defaultUser, hashedPass)) Log($"Usuario por defecto creado -> user: ${defaultUser}, pass: ${defaultPass}"$) + + Dim createQueryLogsTable As String = "CREATE TABLE query_logs (id INTEGER PRIMARY KEY AUTOINCREMENT, query_name TEXT, duration_ms INTEGER, timestamp INTEGER, db_key TEXT, client_ip TEXT, busy_connections INTEGER, handler_active_requests INTEGER)" + SQL1.ExecNonQuery(createQueryLogsTable) + + ' >>> INICIO: Creación de la tabla errores con columnas de error/advertencia <<< + Log("Creando tabla 'errores' para registrar eventos.") + Dim createErrorsTable As String = "CREATE TABLE errores (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER, type TEXT, source TEXT, message TEXT, db_key TEXT, command_name TEXT, client_ip TEXT)" + SQL1.ExecNonQuery(createErrorsTable) + ' >>> FIN: Creación de la tabla errores <<< Else ' Si el archivo de la base de datos ya existe, simplemente se abre. SQL1.InitializeSQLite(File.DirApp, dbFileName, True) @@ -311,6 +354,19 @@ Sub InitializeSQLiteDatabase Log("Añadiendo columna 'handler_active_requests' a query_logs.") SQL1.ExecNonQuery("ALTER TABLE query_logs ADD COLUMN handler_active_requests INTEGER DEFAULT 0") End If + + ' >>> INICIO: Lógica de migración para 'errores' si la DB ya existía <<< + Log("Verificando y migrando tabla 'errores' si es necesario.") + If SQL1.ExecQuerySingleResult("SELECT name FROM sqlite_master WHERE type='table' AND name='errores'") = Null Then + Log("Tabla 'errores' no encontrada, creándola.") + Dim createErrorsTable As String = "CREATE TABLE errores (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER, type TEXT, source TEXT, message TEXT, db_key TEXT, command_name TEXT, client_ip TEXT)" + SQL1.ExecNonQuery(createErrorsTable) + Else + ' Si la tabla ya existe, podrías añadir lógica para ALTER TABLE si se añaden nuevas columnas en el futuro. + ' Por ahora, asumimos que la estructura inicial es suficiente. + Log("Tabla 'errores' ya existe.") + End If + ' >>> FIN: Lógica de migración para 'errores' <<< End If ' >>> FIN: Lógica de migración (ALTER TABLE) <<< End If @@ -330,13 +386,33 @@ Public Sub LogQueryPerformance(QueryName As String, DurationMs As Long, DbKey As End Try End Sub +' Subrutina para registrar errores y advertencias en la tabla 'errores'. +' Type: "ERROR" o "ADVERTENCIA" +' Source: Módulo.Subrutina donde ocurrió el evento (ej. "DBHandlerJSON.Handle") +' Message: El mensaje descriptivo del error/advertencia. +' DBKey: La clave de la base de datos involucrada (ej. "DB1", "DB2"), Null si no aplica. +' CommandName: El nombre del comando SQL (ej. "select_user"), Null si no aplica. +' ClientIp: La dirección IP del cliente que originó la petición, Null si no aplica. +Public Sub LogServerError(Type0 As String, Source As String, Message As String, DBKey As String, CommandName As String, ClientIp As String) + Try + SQL1.ExecNonQuery2("INSERT INTO errores (timestamp, type, source, message, db_key, command_name, client_ip) VALUES (?, ?, ?, ?, ?, ?, ?)", _ + Array As Object(DateTime.Now, Type0, Source, Message, DBKey, CommandName, ClientIp)) + Catch + Log("ERROR CRÍTICO: Fallo al guardar el log de error/advertencia en SQLite (Main.LogServerError): " & LastException.Message) + ' En este punto, no podemos hacer mucho más que loggear el fallo en la consola, + ' para evitar un bucle infinito de errores de logging. + End Try +End Sub + ' Subrutina de evento para el Timer 'timerLogs'. ' Se ejecuta periódicamente (cada 10 minutos) para limpiar la tabla de logs. Sub TimerLogs_Tick Try borraArribaDe15000Logs ' Llama a la función para limpiar los logs. Catch - Log("ERROR en TimerLogs_Tick al intentar borrar logs: " & LastException.Message) + Dim ErrorMsg As String = "ERROR en TimerLogs_Tick al intentar borrar logs: " & LastException.Message + Log(ErrorMsg) + LogServerError("ERROR", "Main.TimerLogs_Tick", ErrorMsg, Null, "log_cleanup", Null) ' <-- Nuevo Log End Try End Sub diff --git a/jRDC_Multi.b4j.meta b/jRDC_Multi.b4j.meta index c40cfa1..2172a9d 100644 --- a/jRDC_Multi.b4j.meta +++ b/jRDC_Multi.b4j.meta @@ -4,6 +4,7 @@ ModuleBookmarks10= ModuleBookmarks11= ModuleBookmarks12= ModuleBookmarks13= +ModuleBookmarks14= ModuleBookmarks2= ModuleBookmarks3= ModuleBookmarks4= @@ -18,6 +19,7 @@ ModuleBreakpoints10= ModuleBreakpoints11= ModuleBreakpoints12= ModuleBreakpoints13= +ModuleBreakpoints14= ModuleBreakpoints2= ModuleBreakpoints3= ModuleBreakpoints4= @@ -32,14 +34,15 @@ ModuleClosedNodes10= ModuleClosedNodes11= ModuleClosedNodes12= ModuleClosedNodes13= +ModuleClosedNodes14= ModuleClosedNodes2= -ModuleClosedNodes3= +ModuleClosedNodes3=9,10,11,12,13,14,15,16 ModuleClosedNodes4= ModuleClosedNodes5= ModuleClosedNodes6= ModuleClosedNodes7= ModuleClosedNodes8= ModuleClosedNodes9= -NavigationStack=Main,Process_Globals,48,0,Manager,Class_Globals,9,0,Manager,Initialize,14,0,Manager,Handle0,463,0,Manager,Handle,157,4,faviconHandler,Handle,31,0,Main,AppStart,76,1,Main,TimerLogs_Tick,291,0,Main,LogQueryPerformance,281,0,Cambios,Process_Globals,41,0 +NavigationStack=DBHandlerB4X,ExecuteBatch2,386,0,DBHandlerB4X,ExecuteBatch,449,0,DBHandlerB4X,ExecuteQuery,506,0,DBHandlerJSON,Handle,148,0,Main,Process_Globals,59,0,Manager,Handle,375,0,TestHandler,Handle,25,1,ChangePassHandler,Handle,17,0,Main,AppStart,157,0,Cambios,Process_Globals,13,6 SelectedBuild=0 -VisibleModules=3,4,12,1,2,5,10,6 +VisibleModules=3,4,13,1,10,11,14,2