From e04cdded472806cb71c75214233c9134e08878e6 Mon Sep 17 00:00:00 2001 From: Jose Alberto Guerra Ugalde Date: Mon, 15 Sep 2025 11:44:16 -0600 Subject: [PATCH] - VERSION 5.09.14 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` feat: Implement hot-swap for DB config reload and JSON POST support **Cambios Principales:** 1. **Hot-Swap para recarga de configuraciones de DB sin reiniciar servidor** 2. **Migración a ReentrantLock para sincronización por incompatibilidad con Sync** 3. **Soporte para peticiones POST con Content-Type: application/json** 4. **Mejoras en inicialización del pool de conexiones y soporte multi-DB** **Problemas Resueltos:** - Falta de "Hot-Swap" en `reload`: El comando no permitía recarga dinámica de configuraciones sin reinicio - Ausencia de mecanismo de cierre de pools en RDCConnector para liberación ordenada de conexiones - Incompatibilidad con `Sync` en entorno B4X - Procesamiento incorrecto de peticiones POST con Content-Type: application/json - Inicialización incorrecta de pools C3P0 con TotalConnections: 0 - Configuración inconsistente de parámetros críticos de C3P0 - jdbcUrl truncada/vacía en logs por shadowing de variables **Cambios Implementados:** **Manager.bas:** - Reemplazo completo de lógica para comando "reload" - Creación de nuevos conectores antes de reemplazar los antiguos - Sincronización con ReentrantLock para acceso thread-safe - Patrón seguro de bloqueo sin `Finally` usando bandera booleana - Cierre explícito de oldConnectors después del reemplazo - Validación de inicialización y control de errores robusto - Registro detallado en log HTML del proceso **RDCConnector.bas:** - Implementación de método `Public Sub Close()` para liberar pools C3P0 - Corrección de shadowing de variable `config` en LoadConfigMap - Reordenamiento de Initialize - Configuración completa de C3P0 antes de adquirir conexiones - Forzar reportes de errores con acquireRetryAttempts y breakAfterAcquireFailure - Activación forzada del pool con conexión temporal **Main.bas:** - Declaración de `MainConnectorsLock As JavaObject` (ReentrantLock) - Inicialización del lock en AppStart - Declaración separada de conectores (con1, con2, con3, con4) **DBHandlerJSON.bas:** - Detección de peticiones POST con Content-Type: application/json - Lectura de JSON desde InputStream en lugar de parámetro URL - Cierre explícito del InputStream para liberación de recursos - Corrección de nombres de variables para evitar conflictos - Mensajes de error mejorados para ambos métodos (legacy y nuevo) **Beneficios:** - Recarga en caliente de configuraciones DB sin interrupción de servicio - Mayor disponibilidad y mantenibilidad del servidor - Prevención de fugas de recursos con cierre ordenado de pools - Compatibilidad con estándares APIs web (POST application/json) - Inicialización robusta y confiable de pools de conexiones - Mejor reporting de errores y diagnóstico de problemas - Soporte multi-DB más estable y confiable ``` --- Cambios.bas | 141 +++++++++++++++ DBHandlerB4X.bas | 9 + DBHandlerJSON.bas | 110 ++++------- Files/config.DB2.properties | 12 +- Files/config.DB3.properties | 7 +- Files/config.DB4.properties | 8 +- Files/config.properties | 9 +- Manager.bas | 194 +++++++++++++++++--- RDCConnector.bas | 351 ++++++++++++++++++++++++------------ jRDC_Multi.b4j | 310 +++++++++++++++++++------------ jRDC_Multi.b4j.meta | 7 +- 11 files changed, 815 insertions(+), 343 deletions(-) create mode 100644 Cambios.bas diff --git a/Cambios.bas b/Cambios.bas new file mode 100644 index 0000000..c6ef7a6 --- /dev/null +++ b/Cambios.bas @@ -0,0 +1,141 @@ +B4J=true +Group=Default Group +ModulesStructureVersion=1 +Type=StaticCode +Version=10.3 +@EndOfDesignText@ +' ######################################## +' ##### HISTORIAL DE CAMBIOS ##### +' ######################################## +Sub Process_Globals + '- VERSION X.XX.XX (cabios 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.13.3 + '- 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 + '- 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 +' feat: Mejora la inicialización del pool de conexiones y el soporte multi-DB. +' +' - Este commit aborda y resuelve varios problemas críticos relacionados con la inicialización +' del pool de conexiones (C3P0) para múltiples bases de datos y la depuración de logs +' en el servidor jRDC2-Multi. +' +' **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 diff --git a/DBHandlerB4X.bas b/DBHandlerB4X.bas index b7b2dfb..5c20cf0 100644 --- a/DBHandlerB4X.bas +++ b/DBHandlerB4X.bas @@ -120,6 +120,15 @@ Sub Handle(req As ServletRequest, resp As ServletResponse) If con <> Null And con.IsInitialized Then con.Close ' Registra en el log el comando ejecutado, cuánto tiempo tardó y la IP del cliente. Log($"Command: ${q}, took: ${DateTime.Now - start}ms, client=${req.RemoteAddress}"$) + + ' *** NUEVO: Insertar el log en la base de datos SQLite *** + Dim duration As Long = DateTime.Now - start + Try + Main.SQL1.ExecNonQuery2("INSERT INTO query_logs (query_name, duration_ms, timestamp, db_key, client_ip) VALUES (?, ?, ?, ?, ?)", _ + Array As Object(q, duration, DateTime.Now, dbKey, req.RemoteAddress)) + Catch + Log("Error al guardar log de query en SQLite (DBHandlerB4X): " & LastException.Message) + End Try End Sub ' Ejecuta una consulta única usando el protocolo V2 (B4XSerializator). diff --git a/DBHandlerJSON.bas b/DBHandlerJSON.bas index d80d936..01edce3 100644 --- a/DBHandlerJSON.bas +++ b/DBHandlerJSON.bas @@ -12,14 +12,13 @@ Sub Class_Globals End Sub ' Subrutina de inicialización de la clase. Se llama cuando se crea un objeto de esta clase. -' En este caso, no se necesita ninguna inicialización específica. Public Sub Initialize - End Sub +' Este es el método principal que maneja las peticiones HTTP entrantes (req) y prepara la respuesta (resp). ' Este es el método principal que maneja las peticiones HTTP entrantes (req) y prepara la respuesta (resp). Sub Handle(req As ServletRequest, resp As ServletResponse) - Log("============== DB1JsonHandler ==============") + ' --- Headers CORS (Cross-Origin Resource Sharing) --- ' Estos encabezados son necesarios para permitir que un cliente web (ej. una página con JavaScript) ' que se encuentra en un dominio diferente pueda hacer peticiones a este servidor. @@ -34,37 +33,54 @@ Sub Handle(req As ServletRequest, resp As ServletResponse) ' Establece "DB1" como el nombre de la base de datos por defecto. Dim DB As String = "DB1" - ' Obtiene el objeto conector para la base de datos por defecto desde el objeto Main. + + ' Obtiene el objeto conector para la base de datos por defecto o especificada. Connector = Main.Connectors.Get(DB) + ' Declara una variable para la conexión SQL. Dim con As SQL ' Inicia un bloque Try...Catch para manejar posibles errores durante la ejecución. Try - ' Obtiene el valor del parámetro 'j' de la petición. Se espera que contenga una cadena JSON. - Dim jsonString As String = req.GetParameter("j") - ' Verifica si el parámetro 'j' es nulo o está vacío. + Dim jsonString As String + + ' *** INICIO DE LA LÓGICA CORREGIDA PARA LEER JSON DEL CUERPO (POST) O DE PARÁMETROS (GET/POST Form-urlencoded) *** + If req.Method = "POST" And req.ContentType.Contains("application/json") Then + ' Para peticiones POST con Content-Type: application/json, el JSON viene en el cuerpo (InputStream). + Dim Is0 As InputStream = req.InputStream ' ¡CORREGIDO: Usamos Is0 en lugar de Is para evitar conflicto con palabra reservada! + ' Usamos Bit.InputStreamToBytes de la librería jcore para leer el stream completo a un array de bytes. + Dim bytes() As Byte = Bit.InputStreamToBytes(Is0) + jsonString = BytesToString(bytes, 0, bytes.Length, "UTF8") ' Convertimos los bytes a String UTF8. + Is0.Close ' ¡Es CRÍTICO cerrar el InputStream para liberar los recursos del sistema! + Else + ' Para peticiones GET o POST con Content-Type como 'application/x-www-form-urlencoded', + ' el JSON se espera en el parámetro 'j' de la URL. + jsonString = req.GetParameter("j") + End If + ' *** FIN DE LA LÓGICA CORREGIDA PARA LEER JSON *** + + ' Verifica si la cadena JSON obtenida es nula o está vacía. If jsonString = Null Or jsonString = "" Then - ' Si falta el parámetro, envía una respuesta de error 400 (Bad Request) y termina la ejecución. - SendErrorResponse(resp, 400, "Falta el parametro 'j' en el URL") + ' Si falta el JSON, envía una respuesta de error 400 (Bad Request) y termina la ejecución. + SendErrorResponse(resp, 400, "Falta el parámetro 'j' en el URL o el cuerpo JSON en la petición.") Return End If ' Crea un objeto JSONParser para analizar la cadena JSON. Dim parser As JSONParser parser.Initialize(jsonString) + ' Convierte la cadena JSON en un objeto Map, que es como un diccionario (clave-valor). Dim RootMap As Map = parser.NextObject ' Extrae los datos necesarios del JSON. - Dim execType As String = RootMap.GetDefault("exec", "") ' Tipo de ejecución: "executeQuery" o "executeCommand". - Dim queryName As String = RootMap.Get("query") ' Nombre del comando SQL (definido en config.properties). - - ' Se obtiene "params" como una Lista en lugar de un Mapa. + Dim execType As String = RootMap.GetDefault("exec", "") ' Tipo de ejecución: "executeQuery" (SELECT) o "executeCommand" (INSERT/UPDATE/DELETE). + Dim queryName As String = RootMap.Get("query") ' Nombre del comando SQL (definido en config.properties). + + ' Se obtiene "params" como una Lista en lugar de un Mapa, para soportar el ordenamiento de B4A. Dim paramsList As List = RootMap.Get("params") - ' Si la lista de parámetros es nula (no se proporcionó en el JSON), - ' la inicializamos como una lista vacía para evitar errores más adelante. + ' Si la lista de parámetros es nula (no se proporcionó en el JSON), la inicializamos como una lista vacía. If paramsList = Null Or paramsList.IsInitialized = False Then paramsList.Initialize End If @@ -74,101 +90,66 @@ Sub Handle(req As ServletRequest, resp As ServletResponse) ' Valida que el nombre de la base de datos (DB) exista en la lista de conexiones configuradas en Main. If Main.listaDeCP.IndexOf(DB) = -1 Then - SendErrorResponse(resp, 400, "Parametro 'DB' invalido. El nombre '" & DB & "' no es válido.") - ' Se añade Return para detener la ejecución si la BD no es válida. + SendErrorResponse(resp, 400, "Parámetro 'DB' inválido. El nombre '" & DB & "' no es válido.") Return End If - ' Obtiene una conexión a la base de datos del pool de conexiones. + ' Obtiene una conexión a la base de datos del pool de conexiones para la DB seleccionada. con = Connector.GetConnection(DB) + ' Obtiene la cadena SQL del archivo de configuración usando el nombre de la consulta (queryName). Dim sqlCommand As String = Connector.GetCommand(DB, queryName) ' <<< INICIO VALIDACIÓN: VERIFICAR SI EL COMANDO EXISTE >>> - ' Comprueba si el comando SQL (query) especificado en el JSON fue encontrado en el archivo de configuración. If sqlCommand = Null Or sqlCommand = "null" Or sqlCommand.Trim = "" Then - ' Si no se encontró el comando, crea un mensaje de error claro. Dim errorMessage As String = $"El comando '${queryName}' no fue encontrado en el config.properties de '${DB}'."$ - ' Registra el error en el log del servidor para depuración. Log(errorMessage) - ' Envía una respuesta de error 400 (Bad Request) al cliente en formato JSON. SendErrorResponse(resp, 400, errorMessage) - ' Cierra la conexión a la base de datos antes de salir para evitar fugas de conexión. If con <> Null And con.IsInitialized Then con.Close - ' Detiene la ejecución del método Handle para esta petición. Return End If ' <<< FIN VALIDACIÓN >>> ' Comprueba el tipo de ejecución solicitado ("executeQuery" o "executeCommand"). If execType.ToLowerCase = "executequery" Then - ' Declara una variable para almacenar el resultado de la consulta. Dim rs As ResultSet - - ' Si el comando SQL contiene placeholders ('?'), significa que espera parámetros. - ' Se usa 'paramsList' directamente en lugar de 'orderedParams'. If sqlCommand.Contains("?") Or paramsList.Size > 0 Then ' ================================================================= ' === VALIDACIÓN DE CONTEO DE PARÁMETROS ========================== ' ================================================================= - ' Calcula cuántos parámetros espera la consulta contando el número de '?'. Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length - ' Obtiene cuántos parámetros se recibieron de la lista. Dim receivedParams As Int = paramsList.Size - Log($"expectedParams: ${expectedParams}, receivedParams: ${receivedParams}"$) - If expectedParams <> receivedParams Then - ' Si no coinciden, envía un error 400 detallado. - SendErrorResponse(resp, 400, $"Número de parametros equivocado para '${queryName}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$) - ' Cierra la conexión antes de salir para evitar fugas. + SendErrorResponse(resp, 400, $"Número de parámetros equivocado para '${queryName}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$) If con <> Null And con.IsInitialized Then con.Close - ' Detiene la ejecución para evitar un error en la base de datos. Return End If ' ================================================================= - ' Ejecuta la consulta pasando el comando SQL y la lista de parámetros. rs = con.ExecQuery2(sqlCommand, paramsList) Else - ' Si no hay '?', ejecuta la consulta directamente sin parámetros. rs = con.ExecQuery(sqlCommand) End If ' --- Procesamiento de resultados --- - ' Prepara una lista para almacenar todas las filas del resultado. Dim ResultList As List ResultList.Initialize - ' Usa un objeto JavaObject para acceder a los metadatos del resultado (info de columnas). Dim jrs As JavaObject = rs Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null) - ' Obtiene el número de columnas en el resultado. Dim cols As Int = rsmd.RunMethod("getColumnCount", Null) - - ' Itera sobre cada fila del resultado (ResultSet). Do While rs.NextRow - ' Crea un mapa para almacenar los datos de la fila actual (columna -> valor). Dim RowMap As Map RowMap.Initialize - ' Itera sobre cada columna de la fila. For i = 1 To cols - ' Obtiene el nombre de la columna. Dim ColumnName As String = rsmd.RunMethod("getColumnName", Array(i)) - ' Obtiene el valor de la columna. Dim value As Object = jrs.RunMethod("getObject", Array(i)) - ' Añade la pareja (nombre_columna, valor) al mapa de la fila. RowMap.Put(ColumnName, value) Next - ' Añade el mapa de la fila a la lista de resultados. ResultList.Add(RowMap) Loop - ' Cierra el ResultSet para liberar recursos de la base de datos. rs.Close - - ' Envía una respuesta de éxito con la lista de resultados en formato JSON. SendSuccessResponse(resp, CreateMap("result": ResultList)) - Else If execType.ToLowerCase = "executecommand" Then - ' Si es un comando (INSERT, UPDATE, DELETE), también valida los parámetros. If sqlCommand.Contains("?") Then ' ================================================================= ' === VALIDACIÓN DE CONTEO DE PARÁMETROS (para Comandos) ========== @@ -176,38 +157,23 @@ Sub Handle(req As ServletRequest, resp As ServletResponse) 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 parametros equivocado para '${queryName}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$) - ' Cierra la conexión antes de salir. + SendErrorResponse(resp, 400, $"Número de parámetros equivocado para '${queryName}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$) If con <> Null And con.IsInitialized Then con.Close - ' Detiene la ejecución. Return End If ' ================================================================= End If - - ' Ejecuta el comando que no devuelve resultados (NonQuery) con sus parámetros. con.ExecNonQuery2(sqlCommand, paramsList) - ' Envía una respuesta de éxito con un mensaje de confirmación. SendSuccessResponse(resp, CreateMap("message": "Command executed successfully")) - Else - ' Si el valor de 'exec' no es ni "executeQuery" ni "executeCommand", envía un error. - SendErrorResponse(resp, 400, "Parametro 'exec' inválido. '" & execType & "' no es un valor permitido.") + SendErrorResponse(resp, 400, "Parámetro 'exec' inválido. '" & execType & "' no es un valor permitido.") End If - Catch - ' Si ocurre cualquier error inesperado en el bloque Try... - ' Registra la excepción completa en el log del servidor para diagnóstico. Log(LastException) - ' Envía una respuesta de error 500 (Internal Server Error) con el mensaje de la excepción. SendErrorResponse(resp, 500, LastException.Message) End Try - - ' Este bloque se ejecuta siempre al final, haya habido error o no, *excepto si se usó Return antes*. - ' Comprueba si el objeto de conexión fue inicializado y sigue abierto. + If con <> Null And con.IsInitialized Then - ' Cierra la conexión para devolverla al pool y que pueda ser reutilizada. - ' Esto es fundamental para no agotar las conexiones a la base de datos. con.Close End If End Sub diff --git a/Files/config.DB2.properties b/Files/config.DB2.properties index 5281325..e64e7a3 100644 --- a/Files/config.DB2.properties +++ b/Files/config.DB2.properties @@ -11,7 +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 +JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_Multi + +# Configuración del pool de conexiones para DB2 +InitialPoolSize=3 +MinPoolSize=2 +MaxPoolSize=10 +AcquireIncrement=5 # SVR-KEYMON-PRODUCCION--> Usuario User=SALMA @@ -44,11 +50,13 @@ 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.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; diff --git a/Files/config.DB3.properties b/Files/config.DB3.properties index e4bf6f5..9e96b5f 100644 --- a/Files/config.DB3.properties +++ b/Files/config.DB3.properties @@ -11,8 +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.12:1521/DBKMT +JdbcUrl=jdbc:oracle:thin:@//192.168.101.12:1521/DBKMT?v$session.program=jRDC_Multi +# Configuración del pool de conexiones para DB2 +InitialPoolSize=3 +MinPoolSize=2 +MaxPoolSize=10 +AcquireIncrement=5 # SVR-KEYMON-PRODUCCION--> Usuario #User=GUNA diff --git a/Files/config.DB4.properties b/Files/config.DB4.properties index 6a6392a..a12e987 100644 --- a/Files/config.DB4.properties +++ b/Files/config.DB4.properties @@ -11,7 +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 +JdbcUrl=jdbc:oracle:thin:@//192.168.101.13:1521/DBKMT?v$session.program=jRDC_Multi + +# Configuración del pool de conexiones para DB2 +InitialPoolSize=3 +MinPoolSize=2 +MaxPoolSize=10 +AcquireIncrement=5 # SVR-KEYMON-PRODUCCION--> Usuario User=SALMA diff --git a/Files/config.properties b/Files/config.properties index 458ed53..e177db6 100644 --- a/Files/config.properties +++ b/Files/config.properties @@ -11,8 +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.10:1521/DBKMT?oracle.jdbc.defaultClientIdentifier=jRDC_Multi +JdbcUrl=jdbc:oracle:thin:@//192.168.101.10:1521/DBKMT?v$session.program=jRDC_Multi +# Configuración del pool de conexiones para DB1 +InitialPoolSize=3 +MinPoolSize=2 +MaxPoolSize=10 +AcquireIncrement=5 # SVR-KEYMON-PRODUCCION--> Usuario User=GUNA @@ -46,6 +51,8 @@ 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.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 c9d456a..93c1f3c 100644 --- a/Manager.bas +++ b/Manager.bas @@ -28,21 +28,21 @@ Sub Handle(req As ServletRequest, resp As ServletResponse) ' --- MANEJO ESPECIAL PARA SNAPSHOT --- ' El comando "snapshot" no devuelve HTML, sino una imagen. Lo manejamos por separado al principio. If Command = "snapshot" Then - Try - resp.ContentType = "image/png" - Dim robot, toolkit As JavaObject - robot.InitializeNewInstance("java.awt.Robot", Null) - toolkit.InitializeStatic("java.awt.Toolkit") - Dim screenRect As JavaObject - screenRect.InitializeNewInstance("java.awt.Rectangle", Array As Object( _ - toolkit.RunMethodJO("getDefaultToolkit", Null).RunMethod("getScreenSize", Null))) - Dim image As JavaObject = robot.RunMethod("createScreenCapture", Array As Object(screenRect)) - Dim ImageIO As JavaObject - ImageIO.InitializeStatic("javax.imageio.ImageIO").RunMethod("write", Array As Object(image, "png", resp.OutputStream)) - Catch - resp.SendError(500, LastException.Message) - End Try - Return ' Detenemos la ejecución aquí para no enviar más HTML. +' Try +' resp.ContentType = "image/png" +' Dim robot, toolkit As JavaObject +' robot.InitializeNewInstance("java.awt.Robot", Null) +' toolkit.InitializeStatic("java.awt.Toolkit") +' Dim screenRect As JavaObject +' screenRect.InitializeNewInstance("java.awt.Rectangle", Array As Object( _ +' toolkit.RunMethodJO("getDefaultToolkit", Null).RunMethod("getScreenSize", Null))) +' Dim image As JavaObject = robot.RunMethod("createScreenCapture", Array As Object(screenRect)) +' Dim ImageIO As JavaObject +' ImageIO.InitializeStatic("javax.imageio.ImageIO").RunMethod("write", Array As Object(image, "png", resp.OutputStream)) +' Catch +' resp.SendError(500, LastException.Message) +' End Try +' Return ' Detenemos la ejecución aquí para no enviar más HTML. End If ' --- FIN DE MANEJO ESPECIAL --- @@ -67,7 +67,8 @@ Sub Handle(req As ServletRequest, resp As ServletResponse) ' --- Cabecera, Botón y Formulario Oculto (igual que antes) --- sb.Append("

Panel de Administración jRDC

") sb.Append($"Bienvenido, ${req.GetSession.GetAttribute("username")}
"$) - sb.Append("
") +' sb.Append("
") + sb.Append("
") ' sb.Append("") sb.Append("