mirror of
https://github.com/KeymonSoft/jRDC-Multi.git
synced 2026-04-18 05:09:32 +00:00
- VERSION 5.09.14
``` 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 ```
This commit is contained in:
141
Cambios.bas
Normal file
141
Cambios.bas
Normal file
@@ -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
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = ?
|
||||
|
||||
194
Manager.bas
194
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("<h1>Panel de Administración jRDC</h1>")
|
||||
sb.Append($"Bienvenido, <b>${req.GetSession.GetAttribute("username")}</b><br>"$)
|
||||
sb.Append("<p class='nav'><a href='/manager?command=test'>Test</a> | <a href='/manager?command=ping'>Ping</a> | <a href='/manager?command=reload'>Reload</a> | <a href='/manager?command=rpm2'>Reiniciar (pm2)</a> | <a href='/manager?command=reviveBow'>Revive Bow</a></p><hr>")
|
||||
' sb.Append("<p class='nav'><a href='/manager?command=test'>Test</a> | <a href='/manager?command=ping'>Ping</a> | <a href='/manager?command=reload'>Reload</a> | <a href='/manager?command=rpm2'>Reiniciar (pm2)</a> | <a href='/manager?command=reviveBow'>Revive Bow</a></p><hr>")
|
||||
sb.Append("<p class='nav'><a href='/manager?command=test'>Test</a> | <a href='/manager?command=ping'>Ping</a> | <a href='/manager?command=reload'>Reload</a> | <a href='/manager?command=slowqueries'>Queries Lentos</a> | <a href='/manager?command=totalcon'>Estadísticas Pool</a> | <a href='/manager?command=rpm2'>Reiniciar (pm2)</a> | <a href='/manager?command=reviveBow'>Revive Bow</a></p><hr>")
|
||||
' sb.Append("<button onclick='toggleForm()'>Cambiar Contraseña</button>")
|
||||
sb.Append("<div id='changePassForm' style='display:none;'>")
|
||||
sb.Append("<h2>Cambiar Contraseña</h2><form action='/changepass' method='post'>")
|
||||
@@ -84,16 +85,113 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
' ### INICIO DE TU LÓGICA DE COMANDOS INTEGRADA ###
|
||||
' =========================================================================
|
||||
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})<br/>"$)
|
||||
sb.Append($"Queries en config.properties: <b>${Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).commands.Size}</b><br/>"$)
|
||||
sb.Append($"<b>JdbcUrl:</b> ${Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).config.Get("JdbcUrl")}</b><br/>"$)
|
||||
sb.Append($"<b>User:</b> ${Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).config.Get("User")}</b><br/>"$)
|
||||
sb.Append($"<b>ServerPort:</b> ${Main.srvr.Port}</b><br/><br/>"$)
|
||||
' Usamos un StringBuilder temporal para acumular los logs de la recarga antes de añadirlos al StringBuilder principal.
|
||||
Dim sbTemp As StringBuilder
|
||||
sbTemp.Initialize
|
||||
sbTemp.Append($"Iniciando recarga de configuración (Hot-Swap) ($DateTime{DateTime.Now})"$).Append("<br>" & CRLF)
|
||||
|
||||
' 1. Crear un nuevo mapa temporal para almacenar los conectores recién inicializados.
|
||||
Dim newConnectors As Map
|
||||
newConnectors.Initialize
|
||||
|
||||
' Guardamos una referencia al mapa de conectores actualmente activos.
|
||||
Dim oldConnectors As Map
|
||||
|
||||
Dim reloadSuccessful As Boolean = True
|
||||
|
||||
' *** INICIO DEL BLOQUE CRÍTICO 1: Obtener oldConnectors con ReentrantLock ***
|
||||
Dim lock1Acquired As Boolean = False ' Bandera para saber si el bloqueo fue adquirido.
|
||||
Try
|
||||
Main.MainConnectorsLock.RunMethod("lock", Null) ' Adquirimos el bloqueo.
|
||||
lock1Acquired = True ' Marcamos que el bloqueo fue adquirido.
|
||||
oldConnectors = Main.Connectors ' Obtenemos la referencia al mapa actual de conectores.
|
||||
Catch
|
||||
sbTemp.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para leer conectores antiguos: ${LastException.Message}"$).Append("<br>" & CRLF)
|
||||
reloadSuccessful = False ' Si falla aquí, la recarga no puede continuar.
|
||||
End Try
|
||||
If lock1Acquired Then
|
||||
Main.MainConnectorsLock.RunMethod("unlock", Null) ' Liberamos el bloqueo si fue adquirido.
|
||||
End If
|
||||
' *** FIN DEL BLOQUE CRÍTICO 1 ***
|
||||
|
||||
If Not(reloadSuccessful) Then ' Si el primer bloqueo falló o la asignación, salimos temprano.
|
||||
sb.Append(sbTemp.ToString) ' Añadimos los logs acumulados al StringBuilder principal.
|
||||
sb.Append($"¡ERROR: La recarga de configuración falló en la fase de bloqueo inicial! Los conectores antiguos siguen activos."$).Append("<br>" & CRLF)
|
||||
Return ' Salir del Handle ya que ocurrió un error crítico irrecuperable.
|
||||
End If
|
||||
|
||||
' 2. Iterar sobre las bases de datos configuradas y crear *nuevas* instancias de RDCConnector.
|
||||
For Each dbKey As String In Main.listaDeCP
|
||||
Try
|
||||
Dim newRDC As RDCConnector
|
||||
newRDC.Initialize(dbKey) ' Inicializa la nueva instancia con la configuración fresca.
|
||||
newConnectors.Put(dbKey, newRDC)
|
||||
|
||||
Dim newPoolStats As Map = newRDC.GetPoolStats
|
||||
sbTemp.Append($" -> ${dbKey}: Nuevo conector inicializado. Conexiones: ${newPoolStats.Get("TotalConnections")}"$).Append("<br>" & CRLF)
|
||||
|
||||
Catch
|
||||
sbTemp.Append($" -> ERROR CRÍTICO al inicializar nuevo conector para ${dbKey}: ${LastException.Message}"$).Append("<br>" & 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
|
||||
' 3. Si todos los nuevos conectores se inicializaron con éxito,
|
||||
' realizamos el "cambio de cartel" (hot-swap) de forma atómica.
|
||||
' *** INICIO DEL BLOQUE CRÍTICO 2: Reemplazar Main.Connectors con ReentrantLock ***
|
||||
Dim lock2Acquired As Boolean = False ' Bandera para saber si el bloqueo fue adquirido.
|
||||
Try
|
||||
Main.MainConnectorsLock.RunMethod("lock", Null) ' Adquirimos el bloqueo.
|
||||
lock2Acquired = True ' Marcamos que el bloqueo fue adquirido.
|
||||
Main.Connectors = newConnectors ' Reemplazamos el mapa de conectores completo por el nuevo.
|
||||
Catch
|
||||
sb.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para reemplazar conectores: ${LastException.Message}"$).Append("<br>" & CRLF)
|
||||
reloadSuccessful = False ' Si falla aquí, la recarga no se completó con éxito.
|
||||
End Try
|
||||
If lock2Acquired Then
|
||||
Main.MainConnectorsLock.RunMethod("unlock", Null) ' Liberamos el bloqueo si fue adquirido.
|
||||
End If
|
||||
' *** FIN DEL BLOQUE CRÍTICO 2 ***
|
||||
|
||||
If reloadSuccessful Then ' Si el segundo bloqueo y swap fue exitoso
|
||||
sb.Append($"¡Recarga de configuración completada con éxito (Hot-Swap)!"$).Append("<br>" & CRLF)
|
||||
sb.Append($"Nuevos conectores activos. Verificando estado final..."$).Append("<br>" & CRLF)
|
||||
|
||||
' Mostrar el estado de los *nuevos* conectores después del swap.
|
||||
If Main.Connectors.IsInitialized Then
|
||||
Dim liveStats As Map
|
||||
liveStats.Initialize
|
||||
For Each dbKey As String In Main.Connectors.Keys
|
||||
Dim currentConnector As RDCConnector = Main.Connectors.Get(dbKey).As(RDCConnector)
|
||||
liveStats.Put(dbKey, currentConnector.GetPoolStats) ' Obtiene las estadísticas en tiempo real.
|
||||
Next
|
||||
j.Initialize(liveStats)
|
||||
sb.Append($"Estado actual de los pools: ${j.ToString}"$).Append(CRLF) ' No <br> para JSON puro
|
||||
End If
|
||||
|
||||
' 4. Cerrar explícitamente los pools de conexión de las instancias antiguas.
|
||||
If oldConnectors.IsInitialized Then
|
||||
sb.Append("Cerrando conectores antiguos...").Append("<br>" & CRLF)
|
||||
For Each dbKey As String In oldConnectors.Keys
|
||||
Dim oldRDC As RDCConnector = oldConnectors.Get(dbKey).As(RDCConnector)
|
||||
If oldRDC <> Null And oldRDC.IsInitialized Then
|
||||
oldRDC.Close ' Llama al método Close que hemos añadido al RDCConnector.
|
||||
sb.Append($" -> Pool antiguo de ${dbKey} cerrado."$).Append("<br>" & CRLF)
|
||||
End If
|
||||
Next
|
||||
End If
|
||||
Else
|
||||
sb.Append($"¡ERROR: La recarga de configuración falló en la fase de reemplazo de conectores! Los conectores antiguos siguen activos."$).Append("<br>" & CRLF)
|
||||
End If
|
||||
Else
|
||||
sb.Append($"¡ERROR: La recarga de configuración falló durante la inicialización de nuevos conectores! Los conectores antiguos siguen activos."$).Append("<br>" & CRLF)
|
||||
' Si la recarga falló, los conectores antiguos (oldConnectors) se mantienen activos
|
||||
' y siguen sirviendo para evitar un paro del servicio.
|
||||
End If
|
||||
Else If Command = "test" Then
|
||||
Try
|
||||
Dim con As SQL = Main.Connectors.Get("DB1").As(RDCConnector).GetConnection("")
|
||||
@@ -174,13 +272,49 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
' j.Initialize(Global.mpBlockConnection)
|
||||
sb.Append(j.ToString)
|
||||
End If
|
||||
Else If Command = "totalcon" Then
|
||||
If GlobalParameters.mpTotalConnections.IsInitialized Then
|
||||
j.Initialize(GlobalParameters.mpTotalConnections)
|
||||
sb.Append(j.ToString)
|
||||
End If
|
||||
Else If Command = "ping" Then
|
||||
sb.Append($"Pong ($DateTime{DateTime.Now})"$)
|
||||
Else If Command = "totalcon" Then ' <<< Modificado: Ahora usa GetPoolStats para cada pool
|
||||
' Verificamos que el mapa global de conexiones esté inicializado.
|
||||
' Aunque no lo poblamos directamente, es un buen chequeo de estado.
|
||||
If GlobalParameters.mpTotalConnections.IsInitialized Then
|
||||
sb.Append("<h2>Estadísticas del Pool de Conexiones por DB:</h2>")
|
||||
|
||||
' Creamos un mapa LOCAL para almacenar las estadísticas de TODOS los pools de conexiones.
|
||||
Dim allPoolStats As Map
|
||||
allPoolStats.Initialize
|
||||
|
||||
' Iteramos sobre cada clave de base de datos que tenemos configurada (DB1, DB2, etc.).
|
||||
For Each dbKey As String In Main.listaDeCP
|
||||
' Obtenemos el conector RDC para la base de datos actual.
|
||||
Dim connector As RDCConnector = Main.Connectors.Get(dbKey).As(RDCConnector)
|
||||
|
||||
' Si el conector no está inicializado (lo cual no debería ocurrir si Main.AppStart funcionó),
|
||||
' registramos un error y pasamos al siguiente.
|
||||
If connector.IsInitialized = False Then
|
||||
Log($"Manager: ADVERTENCIA: El conector para ${dbKey} no está inicializado."$)
|
||||
Dim errorMap As Map = CreateMap("Error": "Conector no inicializado o no cargado correctamente")
|
||||
allPoolStats.Put(dbKey, errorMap)
|
||||
Continue ' Salta a la siguiente iteración del bucle.
|
||||
End If
|
||||
|
||||
' Llamamos al método GetPoolStats del conector para obtener las métricas de su pool.
|
||||
Dim poolStats As Map = connector.GetPoolStats
|
||||
|
||||
' Añadimos las estadísticas de este pool (poolStats) al mapa general (allPoolStats),
|
||||
' usando la clave de la base de datos (dbKey) como su identificador.
|
||||
allPoolStats.Put(dbKey, poolStats)
|
||||
Next
|
||||
|
||||
' Inicializamos el generador JSON con el mapa 'allPoolStats' (que ahora sí debería contener datos).
|
||||
' (La variable 'j' ya está declarada en Class_Globals de Manager.bas, no la declares de nuevo aquí).
|
||||
j.Initialize(allPoolStats)
|
||||
|
||||
' Añadimos la representación JSON de las estadísticas al StringBuilder para la respuesta HTML.
|
||||
sb.Append(j.ToString)
|
||||
Else
|
||||
sb.Append("El mapa de conexiones GlobalParameters.mpTotalConnections no está inicializado.")
|
||||
End If
|
||||
End If
|
||||
' =========================================================================
|
||||
' ### FIN DE TU LÓGICA DE COMANDOS ###
|
||||
|
||||
351
RDCConnector.bas
351
RDCConnector.bas
@@ -4,152 +4,271 @@ ModulesStructureVersion=1
|
||||
Type=Class
|
||||
Version=4.19
|
||||
@EndOfDesignText@
|
||||
'Class module
|
||||
' Módulo de clase: RDCConnector
|
||||
' Esta clase gestiona el pool de conexiones a una base de datos específica.
|
||||
' Cada instancia de RDCConnector maneja la conexión y los comandos para una base de datos.
|
||||
|
||||
Sub Class_Globals
|
||||
Private pool As ConnectionPool
|
||||
Private DebugQueries As Boolean
|
||||
Dim commands As Map
|
||||
Public serverPort As Int
|
||||
Public usePool As Boolean = True
|
||||
Dim config As Map
|
||||
Private pool As ConnectionPool ' Objeto principal para gestionar el pool de conexiones de la base de datos (usa C3P0 internamente).
|
||||
Private DebugQueries As Boolean ' Bandera para activar/desactivar el modo de depuración de queries.
|
||||
Dim commands As Map ' Almacena los comandos SQL específicos de esta base de datos, cargados de su archivo de configuración.
|
||||
Public serverPort As Int ' El puerto que el servidor HTTP usará, obtenido del archivo de configuración principal (config.properties).
|
||||
Public usePool As Boolean = True ' Indica si se debe usar el pool de conexiones (siempre True en este diseño).
|
||||
Dim config As Map ' Almacena la configuración completa (JdbcUrl, User, Password, etc.) cargada de su respectivo archivo .properties.
|
||||
End Sub
|
||||
|
||||
'Initializes the object. You can add parameters to this method if needed.
|
||||
' Subrutina de inicialización para el conector de una base de datos específica.
|
||||
' Se llama una vez por cada base de datos (DB1, DB2, DB3, DB4) al iniciar el servidor.
|
||||
' DB: El identificador único de la base de datos (ej. "DB1", "DB2").
|
||||
Public Sub Initialize(DB As String)
|
||||
' Log("RDCConnector Initialize")
|
||||
If DB.EqualsIgnoreCase("DB1") Then DB = "" 'Esto para el config.properties por default
|
||||
Dim config As Map = LoadConfigMap(DB)
|
||||
Log($"Inicializamos ${DB}, usuario: ${config.Get("User")}"$)
|
||||
pool.Initialize(config.Get("DriverClass"), config.Get("JdbcUrl"), config.Get("User"), config.Get("Password"))
|
||||
Dim jo As JavaObject = pool
|
||||
' Si el identificador es "DB1", se usa una cadena vacía para que File.ReadMap cargue "config.properties" (el archivo por defecto).
|
||||
If DB.EqualsIgnoreCase("DB1") Then DB = ""
|
||||
|
||||
' PASO 1: Cargar la configuración desde el archivo .properties correspondiente.
|
||||
' 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)
|
||||
|
||||
' Bloque Try-Catch para la inicialización y configuración del pool.
|
||||
' Esto capturará cualquier error crítico que impida la conexión inicial a la base de datos.
|
||||
Try
|
||||
' PASO 2: Inicializar el objeto B4X ConnectionPool.
|
||||
' Esto crea la instancia subyacente de com.mchange.v2.c3p0.ComboPooledDataSource (la librería C3P0).
|
||||
' En este punto, C3P0 solo se inicializa como objeto. Aún no intenta hacer conexiones activas.
|
||||
' Se le pasan los parámetros básicos para que C3P0 pueda construirse.
|
||||
pool.Initialize(config.Get("DriverClass"), config.Get("JdbcUrl"), config.Get("User"), config.Get("Password"))
|
||||
|
||||
Dim jo As JavaObject = pool ' Obtener la referencia JavaObject para acceder a métodos de configuración avanzados de C3P0.
|
||||
|
||||
' PASO 3: Aplicar *todas* las propiedades de configuración de C3P0 INMEDIATAMENTE.
|
||||
' Esto debe ocurrir *después* de 'pool.Initialize' pero *antes* de que C3P0 intente realmente adquirir conexiones.
|
||||
' Esto asegura que las configuraciones sean efectivas desde el primer intento de conexión.
|
||||
|
||||
' Lectura de los valores desde el archivo de configuración, con valores por defecto si no se encuentran.
|
||||
Dim initialPoolSize As Int = config.GetDefault("InitialPoolSize", 3)
|
||||
Dim minPoolSize As Int = config.GetDefault("MinPoolSize", 2)
|
||||
Dim maxPoolSize As Int = config.GetDefault("MaxPoolSize", 5)
|
||||
Dim acquireIncrement As Int = config.GetDefault("AcquireIncrement", 5)
|
||||
|
||||
' Leer valores del config.properties o usar valores por defecto
|
||||
' Si el parámetro no se encuentra en el archivo .properties, se usará el segundo valor (el por defecto).
|
||||
Dim initialPoolSize As Int = config.GetDefault("InitialPoolSize", 3) ' Por defecto 3
|
||||
Dim minPoolSize As Int = config.GetDefault("MinPoolSize", 2) ' Por defecto 3
|
||||
Dim maxPoolSize As Int = config.GetDefault("MaxPoolSize", 5) ' Por defecto 5
|
||||
|
||||
jo.RunMethod("setInitialPoolSize", Array(initialPoolSize)) ' Sets the inital pool size to 2
|
||||
jo.RunMethod("setMinPoolSize", Array(minPoolSize)) ' Sets the min pool size to 2
|
||||
jo.RunMethod("setMaxPoolSize", Array(maxPoolSize)) ' Max number of concurrent connections
|
||||
|
||||
' Define el tiempo máximo de inactividad en SEGUNDOS.
|
||||
' Una conexión que permanezca en el pool sin ser usada por más de 300 segundos (5 minutos)
|
||||
' será cerrada para liberar recursos, siempre y cuando no se viole el tamaño mínimo del pool (minPoolSize).
|
||||
jo.RunMethod("setMaxIdleTime", Array As Object(300))
|
||||
|
||||
' Define la "edad" o tiempo de vida máximo de una conexión en SEGUNDOS.
|
||||
' Después de 900 segundos (15 minutos) desde su creación, la conexión será marcada para ser
|
||||
' eliminada y reemplazada por una nueva la próxima vez que regrese al pool.
|
||||
' Esto previene problemas con conexiones "viciadas" y mantiene el pool saludable.
|
||||
jo.RunMethod("setMaxConnectionAge", Array As Object(900))
|
||||
|
||||
' Define el tiempo máximo de espera por una conexión en MILISEGUNDOS.
|
||||
' Si todas las conexiones del pool están ocupadas, una nueva petición esperará hasta
|
||||
' 60000 milisegundos (1 minuto). Si ninguna conexión se libera en ese lapso, la petición
|
||||
' fallará con un error. Esto evita que la aplicación se congele bajo carga pesada.
|
||||
jo.RunMethod("setCheckoutTimeout", Array As Object(60000))
|
||||
|
||||
' com.mchange.v2.c3p0.ComboPooledDataSource [
|
||||
' acquireIncrement -> 3,
|
||||
' acquireRetryAttempts -> 30,
|
||||
' acquireRetryDelay -> 1000,
|
||||
' autoCommitOnClose -> False,
|
||||
' automaticTestTable -> Null,
|
||||
' breakAfterAcquireFailure -> False,
|
||||
' checkoutTimeout -> 20000,
|
||||
' connectionCustomizerClassName -> Null,
|
||||
' connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester,
|
||||
' contextClassLoaderSource -> caller,
|
||||
' dataSourceName -> 2rvxvdb7cyxd8zlw6dyb|63021689,
|
||||
' debugUnreturnedConnectionStackTraces -> False,
|
||||
' description -> Null,
|
||||
' driverClass -> oracle.jdbc.driver.OracleDriver,
|
||||
' extensions -> {},
|
||||
' factoryClassLocation -> Null,
|
||||
' forceIgnoreUnresolvedTransactions -> False,
|
||||
' forceSynchronousCheckins -> False,
|
||||
' forceUseNamedDriverClass -> False,
|
||||
' identityToken -> 2rvxvdb7cyxd8zlw6dyb|63021689,
|
||||
' idleConnectionTestPeriod -> 600,
|
||||
' initialPoolSize -> 3,
|
||||
' jdbcUrl -> jdbc:oracle:thin:@//10.0.0.110:1521/DBKMT,
|
||||
' maxAdministrativeTaskTime -> 0,
|
||||
' maxConnectionAge -> 0,
|
||||
' maxIdleTime -> 1800,
|
||||
' maxIdleTimeExcessConnections -> 0,
|
||||
' maxPoolSize -> 5,
|
||||
' maxStatements -> 150,
|
||||
' maxStatementsPerConnection -> 0,
|
||||
' minPoolSize -> 3,
|
||||
' numHelperThreads -> 3,
|
||||
' preferredTestQuery -> DBMS_SESSION.SET_IDENTIFIER('whatever'),
|
||||
' privilegeSpawnedThreads -> False,
|
||||
' properties -> {password=******, user=******},
|
||||
' propertyCycle -> 0,
|
||||
' statementCacheNumDeferredCloseThreads -> 0,
|
||||
' testConnectionOnCheckin -> False,
|
||||
' testConnectionOnCheckout -> True,
|
||||
' unreturnedConnectionTimeout -> 0,
|
||||
' userOverrides -> {},
|
||||
' Configuración de los parámetros del pool de conexiones C3P0:
|
||||
jo.RunMethod("setInitialPoolSize", Array(initialPoolSize)) ' Define el número de conexiones que se intentarán crear al iniciar el pool.
|
||||
jo.RunMethod("setMinPoolSize", Array(minPoolSize)) ' Fija el número mínimo de conexiones que el pool mantendrá abiertas.
|
||||
jo.RunMethod("setMaxPoolSize", Array(maxPoolSize)) ' Define el número máximo de conexiones simultáneas.
|
||||
jo.RunMethod("setAcquireIncrement", Array(acquireIncrement)) ' Cuántas conexiones nuevas se añaden en lote si el pool se queda sin disponibles.
|
||||
jo.RunMethod("setMaxIdleTime", Array As Object(config.GetDefault("MaxIdleTime", 300))) ' Tiempo máximo de inactividad de una conexión antes de cerrarse (segundos).
|
||||
jo.RunMethod("setMaxConnectionAge", Array As Object(config.GetDefault("MaxConnectionAge", 900))) ' Tiempo máximo de vida de una conexión (segundos).
|
||||
jo.RunMethod("setCheckoutTimeout", Array As Object(config.GetDefault("CheckoutTimeout", 60000))) ' Tiempo máximo de espera por una conexión del pool (milisegundos).
|
||||
|
||||
' 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("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.
|
||||
' Este paso es VITAL. Obliga a C3P0 a intentar establecer las conexiones iniciales (InitialPoolSize)
|
||||
' *con la configuración ya establecida*. Si hay un problema de conectividad real, la excepción
|
||||
' se capturará aquí y se reportará.
|
||||
Dim tempCon As SQL = pool.GetConnection ' Adquiere una conexión para forzar al pool a inicializarse.
|
||||
If tempCon.IsInitialized Then
|
||||
tempCon.Close ' Devolvemos la conexión inmediatamente al pool para que esté disponible.
|
||||
End If
|
||||
|
||||
' com.mchange.v2.c3p0.ComboPooledDataSource [
|
||||
' acquireIncrement -> 3,
|
||||
' acquireRetryAttempts -> 30,
|
||||
' acquireRetryDelay -> 1000,
|
||||
' autoCommitOnClose -> False,
|
||||
' automaticTestTable -> Null,
|
||||
' breakAfterAcquireFailure -> False,
|
||||
' checkoutTimeout -> 20000,
|
||||
' connectionCustomizerClassName -> Null,
|
||||
' connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester,
|
||||
' contextClassLoaderSource -> caller,
|
||||
' dataSourceName -> 2rvxvdb7cyxd8zlw6dyb|63021689,
|
||||
' debugUnreturnedConnectionStackTraces -> False,
|
||||
' description -> Null,
|
||||
' driverClass -> oracle.jdbc.driver.OracleDriver,
|
||||
' extensions -> {},
|
||||
' factoryClassLocation -> Null,
|
||||
' forceIgnoreUnresolvedTransactions -> False,
|
||||
' forceSynchronousCheckins -> False,
|
||||
' forceUseNamedDriverClass -> False,
|
||||
' identityToken -> 2rvxvdb7cyxd8zlw6dyb|63021689,
|
||||
' idleConnectionTestPeriod -> 600,
|
||||
' initialPoolSize -> 3,
|
||||
' jdbcUrl -> jdbc:oracle:thin:@//10.0.0.110:1521/DBKMT,
|
||||
' maxAdministrativeTaskTime -> 0,
|
||||
' maxConnectionAge -> 0,
|
||||
' maxIdleTime -> 1800,
|
||||
' maxIdleTimeExcessConnections -> 0,
|
||||
' maxPoolSize -> 5,
|
||||
' maxStatements -> 150,
|
||||
' maxStatementsPerConnection -> 0,
|
||||
' minPoolSize -> 3,
|
||||
' numHelperThreads -> 3,
|
||||
' preferredTestQuery -> DBMS_SESSION.SET_IDENTIFIER('whatever'),
|
||||
' privilegeSpawnedThreads -> False,
|
||||
' properties -> {password=******, user=******},
|
||||
' propertyCycle -> 0,
|
||||
' statementCacheNumDeferredCloseThreads -> 0,
|
||||
' testConnectionOnCheckin -> False,
|
||||
' testConnectionOnCheckout -> True,
|
||||
' unreturnedConnectionTimeout -> 0,
|
||||
' userOverrides -> {},
|
||||
' usesTraditionalReflectiveProxies -> False
|
||||
' ]
|
||||
' ]
|
||||
'
|
||||
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}"$)
|
||||
End Try
|
||||
|
||||
' Configuración de depuración de queries. Se activa automáticamente si el proyecto se ejecuta en modo DEBUG.
|
||||
#If DEBUG
|
||||
' DebugQueries = True
|
||||
#Else
|
||||
DebugQueries = False
|
||||
#End If
|
||||
|
||||
' Dim jo2 As JavaObject = pool
|
||||
' Log(jo2.GetField("END_TO_END_CLIENTID_INDEX"))
|
||||
|
||||
' jo.RunMethod("setPreferredTestQuery", Array("BEGIN DBMS_SESSION.SET_IDENTIFIER('whatever'); END;"))
|
||||
' jo.RunMethod("setPreferredTestQuery", Array("alter session set current_schema=MYSCHEMA"))
|
||||
' jo2.RunMethod("setClientIdentifier",Array( "MAX")) ' Tiempo máximo de inactividad antes de cerrar una conexión
|
||||
#if DEBUG
|
||||
DebugQueries = True
|
||||
#else
|
||||
DebugQueries = False
|
||||
#end if
|
||||
' Se obtiene el puerto del servidor HTTP desde la configuración de esta base de datos.
|
||||
' Nota: En el diseño actual, el puerto principal lo define DB1 (config.properties).
|
||||
serverPort = config.Get("ServerPort")
|
||||
|
||||
' Asegura que el identificador DB no sea una cadena vacía para la carga de comandos.
|
||||
If DB = "" Then DB = "DB1"
|
||||
|
||||
' Carga los comandos SQL predefinidos de esta base de datos en el mapa global 'commandsMap'.
|
||||
LoadSQLCommands(config, DB)
|
||||
End Sub
|
||||
|
||||
' Carga el mapa de configuración (JdbcUrl, User, Password, etc.) desde el archivo .properties correspondiente.
|
||||
' DB: El identificador de la base de datos (ej. "DB1", "DB2").
|
||||
' Retorna un Mapa con la configuración leída.
|
||||
Private Sub LoadConfigMap(DB As String) As Map
|
||||
Private DBX As String = ""
|
||||
If DB <> "" Then DBX = "." & DB
|
||||
Log("===========================================")
|
||||
Log($"Leemos el config${DBX}.properties"$)
|
||||
If DB <> "" Then DBX = "." & DB ' Construye el sufijo del nombre de archivo (ej. ".DB2").
|
||||
Log($"Leemos el config${DBX}.properties"$) ' Mantenemos este log para confirmación de carga.
|
||||
Return File.ReadMap("./", "config" & DBX & ".properties")
|
||||
End Sub
|
||||
|
||||
' Obtiene la sentencia SQL completa para un comando dado desde el mapa de comandos cargado.
|
||||
' DB: El identificador de la base de datos.
|
||||
' Key: El nombre del comando SQL (ej. "select_user").
|
||||
' Retorna la sentencia SQL como String.
|
||||
Public Sub GetCommand(DB As String, Key As String) As String
|
||||
Log("==== GetCommand ====")
|
||||
' Log("|" & DB & "|" & Key & "|")
|
||||
commands = Main.commandsMap.get(DB).As(Map)
|
||||
commands = Main.commandsMap.get(DB).As(Map) ' Obtiene los comandos de la DB específica del mapa global.
|
||||
If commands.ContainsKey("sql." & Key) = False Then
|
||||
Log("*** Command not found: " & Key)
|
||||
Log("*** Command not found: " & Key) ' Este log es importante mantenerlo si un comando no se encuentra.
|
||||
End If
|
||||
' Log(commands.ContainsKey("sql." & Key))
|
||||
Log("========= Traemos """ & Key & """ ==========")
|
||||
Log(">>>>>> " & commands.Get("sql." & Key) & " <<<<<<")
|
||||
Return commands.Get("sql." & Key)
|
||||
Return commands.Get("sql." & Key) ' Retorna la sentencia SQL.
|
||||
End Sub
|
||||
|
||||
' Obtiene una conexión SQL funcional del pool de conexiones para la base de datos especificada.
|
||||
' DB: El identificador de la base de datos.
|
||||
' Retorna un objeto SQL (la conexión a la base de datos).
|
||||
Public Sub GetConnection(DB As String) As SQL
|
||||
Log("==== GetConnection ==== ")
|
||||
If DB.EqualsIgnoreCase("DB1") Then DB = "" 'Esto para el config.properties or default
|
||||
If DB.EqualsIgnoreCase("DB1") Then DB = ""
|
||||
' En modo de depuración, recarga los comandos SQL en cada petición.
|
||||
' Esto permite modificar queries en config.properties sin reiniciar el servidor.
|
||||
If DebugQueries Then LoadSQLCommands(LoadConfigMap(DB), DB)
|
||||
Return pool.GetConnection
|
||||
Return pool.GetConnection ' Retorna una conexión del pool.
|
||||
End Sub
|
||||
|
||||
' Carga todos los comandos SQL del mapa de configuración en el mapa global 'commandsMap'.
|
||||
' config2: El mapa de configuración de la DB actual.
|
||||
' DB: El identificador de la base de datos.
|
||||
Private Sub LoadSQLCommands(config2 As Map, DB As String)
|
||||
Log("==== LoadSQLCommands ==== ")
|
||||
Log($"Cargamos los comandos desde el config.${DB}.properties"$)
|
||||
Dim newCommands As Map
|
||||
newCommands.Initialize
|
||||
For Each k As String In config2.Keys
|
||||
If k.StartsWith("sql.") Then
|
||||
newCommands.Put(k, config2.Get(k))
|
||||
If k.StartsWith("sql.") Then ' Busca claves que comiencen con "sql." (ej. "sql.select_user").
|
||||
newCommands.Put(k, config2.Get(k)) ' Añade el comando al mapa.
|
||||
End If
|
||||
Next
|
||||
commands = newCommands
|
||||
' Log($"Inicializado: ${DB} "$ & Main.commandsMap.IsInitialized)
|
||||
Main.commandsMap.Put(DB, commands)
|
||||
commands = newCommands ' Actualiza el mapa de comandos de esta instancia de RDCConnector.
|
||||
Main.commandsMap.Put(DB, commands) ' Almacena el mapa de comandos en el mapa global 'commandsMap' de Main.
|
||||
End Sub
|
||||
|
||||
' Nuevo: Obtiene estadísticas detalladas del pool de conexiones.
|
||||
Public Sub GetPoolStats As Map
|
||||
Dim stats As Map
|
||||
stats.Initialize
|
||||
' Log("--- RDCConnector.GetPoolStats llamado ---") ' Log de inicio
|
||||
|
||||
If pool.IsInitialized Then
|
||||
' Log("RDCConnector.GetPoolStats: Pool está inicializado. Intentando obtener métricas.")
|
||||
Dim jo As JavaObject = pool ' Convertimos el objeto pool a JavaObject para acceder a sus métodos.
|
||||
Try
|
||||
' --- Métricas en tiempo real del pool ---
|
||||
Dim totalConn As Object = jo.RunMethod("getNumConnectionsAllUsers", Null)
|
||||
stats.Put("TotalConnections", totalConn)
|
||||
' Log($"RDCConnector.GetPoolStats: TotalConnections = ${totalConn}"$)
|
||||
|
||||
Dim busyConn As Object = jo.RunMethod("getNumBusyConnectionsAllUsers", Null)
|
||||
stats.Put("BusyConnections", busyConn)
|
||||
' Log($"RDCConnector.GetPoolStats: BusyConnections = ${busyConn}"$)
|
||||
|
||||
Dim idleConn As Object = jo.RunMethod("getNumIdleConnectionsAllUsers", Null)
|
||||
stats.Put("IdleConnections", idleConn)
|
||||
' Log($"RDCConnector.GetPoolStats: IdleConnections = ${idleConn}"$)
|
||||
|
||||
' --- Valores de configuración del pool (para referencia) ---
|
||||
Dim initialSize As Object = jo.RunMethod("getInitialPoolSize", Null)
|
||||
stats.Put("InitialPoolSize", initialSize)
|
||||
' Log($"RDCConnector.GetPoolStats: InitialPoolSize = ${initialSize}"$)
|
||||
|
||||
Dim minSize As Object = jo.RunMethod("getMinPoolSize", Null)
|
||||
stats.Put("MinPoolSize", minSize)
|
||||
' Log($"RDCConnector.GetPoolStats: MinPoolSize = ${minSize}"$)
|
||||
|
||||
Dim maxSize As Object = jo.RunMethod("getMaxPoolSize", Null)
|
||||
stats.Put("MaxPoolSize", maxSize)
|
||||
' Log($"RDCConnector.GetPoolStats: MaxPoolSize = ${maxSize}"$)
|
||||
|
||||
Dim acquireInc As Object = jo.RunMethod("getAcquireIncrement", Null)
|
||||
stats.Put("AcquireIncrement", acquireInc)
|
||||
' Log($"RDCConnector.GetPoolStats: AcquireIncrement = ${acquireInc}"$)
|
||||
|
||||
Dim maxIdle As Object = jo.RunMethod("getMaxIdleTime", Null)
|
||||
stats.Put("MaxIdleTime", maxIdle)
|
||||
' Log($"RDCConnector.GetPoolStats: MaxIdleTime = ${maxIdle}"$)
|
||||
|
||||
Dim maxAge As Object = jo.RunMethod("getMaxConnectionAge", Null)
|
||||
stats.Put("MaxConnectionAge", maxAge)
|
||||
' Log($"RDCConnector.GetPoolStats: MaxConnectionAge = ${maxAge}"$)
|
||||
|
||||
Dim checkoutTime As Object = jo.RunMethod("getCheckoutTimeout", Null)
|
||||
stats.Put("CheckoutTimeout", checkoutTime)
|
||||
' Log($"RDCConnector.GetPoolStats: CheckoutTimeout = ${checkoutTime}"$)
|
||||
|
||||
Catch
|
||||
Log("RDCConnector.GetPoolStats: ERROR CRÍTICO al obtener estadísticas del pool: " & LastException.Message)
|
||||
stats.Put("Error", LastException.Message)
|
||||
End Try
|
||||
Else
|
||||
Log("RDCConnector.GetPoolStats: ADVERTENCIA: Pool NO está inicializado. Retornando mapa con error.")
|
||||
stats.Put("Error", "Pool de conexiones no inicializado para esta DB.")
|
||||
End If
|
||||
|
||||
' *** CORRECCIÓN: Usamos JSONGenerator para serializar el mapa a String para el Log ***
|
||||
Dim tempJsonGen As JSONGenerator ' Declaramos un JSONGenerator temporal
|
||||
tempJsonGen.Initialize(stats) ' Lo inicializamos con el mapa 'stats'
|
||||
' Log("--- RDCConnector.GetPoolStats finalizado. Retornando stats: " & tempJsonGen.ToString & " ---") ' Log de fin con JSON
|
||||
|
||||
Return stats
|
||||
End Sub
|
||||
|
||||
' *** NUEVA SUBRUTINA: Cierra el pool de conexiones de forma ordenada usando JavaObject ***
|
||||
' Este método es crucial para liberar los recursos de la base de datos
|
||||
' cuando un conector RDC ya no es necesario o va a ser reemplazado.
|
||||
Public Sub Close
|
||||
If pool <> Null And pool.IsInitialized Then
|
||||
Log($"RDCConnector: Cerrando pool de conexiones."$)
|
||||
' Convertimos el objeto pool de B4X a un JavaObject para poder llamar a su método 'close()'
|
||||
' que no está expuesto directamente en la envoltura de B4X.
|
||||
Dim joPool As JavaObject = pool
|
||||
joPool.RunMethod("close", Null) ' Llamamos al método 'close()' del objeto Java subyacente de C3P0.
|
||||
End If
|
||||
End Sub
|
||||
310
jRDC_Multi.b4j
310
jRDC_Multi.b4j
@@ -30,40 +30,54 @@ Library6=jshell
|
||||
Library7=json
|
||||
Library8=jsql
|
||||
Library9=bcrypt
|
||||
Module1=ChangePassHandler
|
||||
Module10=ping
|
||||
Module11=RDCConnector
|
||||
Module12=TestHandler
|
||||
Module2=DBHandlerB4X
|
||||
Module3=DBHandlerJSON
|
||||
Module4=DoLoginHandler
|
||||
Module5=faviconHandler
|
||||
Module6=GlobalParameters
|
||||
Module7=LoginHandler
|
||||
Module8=LogoutHandler
|
||||
Module9=Manager
|
||||
Module1=Cambios
|
||||
Module10=Manager
|
||||
Module11=ping
|
||||
Module12=RDCConnector
|
||||
Module13=TestHandler
|
||||
Module2=ChangePassHandler
|
||||
Module3=DBHandlerB4X
|
||||
Module4=DBHandlerJSON
|
||||
Module5=DoLoginHandler
|
||||
Module6=faviconHandler
|
||||
Module7=GlobalParameters
|
||||
Module8=LoginHandler
|
||||
Module9=LogoutHandler
|
||||
NumberOfFiles=10
|
||||
NumberOfLibraries=9
|
||||
NumberOfModules=12
|
||||
NumberOfModules=13
|
||||
Version=10.3
|
||||
@EndOfDesignText@
|
||||
'Non-UI application (console / server application)
|
||||
#Region Project Attributes
|
||||
#CommandLineArgs:
|
||||
#MergeLibraries: True
|
||||
' VERSION 5.09.08
|
||||
'###########################################################################################################
|
||||
'###################### PULL #############################################################
|
||||
'Ctrl + click ide://run?file=%WINDIR%\System32\cmd.exe&Args=/c&Args=git&Args=pull
|
||||
'###########################################################################################################
|
||||
'###################### PUSH #############################################################
|
||||
'Ctrl + click ide://run?file=%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe&Args=github&Args=..\..\
|
||||
'###########################################################################################################
|
||||
'###################### PUSH TORTOISE GIT #########################################################
|
||||
'Ctrl + click ide://run?file=%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe&Args=TortoiseGitProc&Args=/command:commit&Args=/path:"../"&Args=/closeonend:2
|
||||
'###########################################################################################################
|
||||
|
||||
#Region Project Attributes
|
||||
#CommandLineArgs:
|
||||
#MergeLibraries: True
|
||||
' VERSION 5.09.014
|
||||
'###########################################################################################################
|
||||
'###################### PULL #############################################################
|
||||
'Ctrl + click ide://run?file=%WINDIR%\System32\cmd.exe&Args=/c&Args=git&Args=pull
|
||||
'###########################################################################################################
|
||||
'###################### PUSH #############################################################
|
||||
'Ctrl + click ide://run?file=%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe&Args=github&Args=..\..\
|
||||
'###########################################################################################################
|
||||
'###################### PUSH TORTOISE GIT #########################################################
|
||||
'Ctrl + click ide://run?file=%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe&Args=TortoiseGitProc&Args=/command:commit&Args=/path:"../"&Args=/closeonend:2
|
||||
'###########################################################################################################
|
||||
#End Region
|
||||
|
||||
'- 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.
|
||||
'change based on the jdbc jar file
|
||||
'#AdditionalJar: mysql-connector-java-5.1.27-bin
|
||||
'#AdditionalJar: postgresql-42.7.0
|
||||
@@ -72,104 +86,164 @@ Version=10.3
|
||||
#AdditionalJar: sqlite-jdbc-3.7.2
|
||||
|
||||
Sub Process_Globals
|
||||
Public srvr As Server
|
||||
Public const VERSION As Float = 2.23
|
||||
Type DBCommand (Name As String, Parameters() As Object)
|
||||
Type DBResult (Tag As Object, Columns As Map, Rows As List)
|
||||
Dim listaDeCP As List
|
||||
Dim cpFiles As List
|
||||
Public Connectors, commandsMap As Map
|
||||
Public SQL1 As SQL ' Objeto SQL para la base de datos de usuarios
|
||||
Private bc As BCrypt
|
||||
' --- Variables globales accesibles desde cualquier parte del proyecto ---
|
||||
Public srvr As Server ' El objeto principal del servidor HTTP de B4J.
|
||||
Public const VERSION As Float = 2.23 ' La versión actual de este servidor jRDC modificado.
|
||||
|
||||
' Tipos personalizados para la serialización y deserialización de datos
|
||||
' entre el cliente B4X (DBRequestManager) y el servidor jRDC2.
|
||||
Type DBCommand (Name As String, Parameters() As Object) ' Define un comando SQL.
|
||||
Type DBResult (Tag As Object, Columns As Map, Rows As List) ' Define la estructura de un resultado de consulta.
|
||||
|
||||
Public listaDeCP As List ' Contiene una lista de los identificadores de bases de datos configuradas (ej. "DB1", "DB2").
|
||||
Private cpFiles As List ' Una lista temporal para almacenar los nombres de archivos encontrados en el directorio.
|
||||
|
||||
' Mapas globales para gestionar los conectores de base de datos y los comandos SQL.
|
||||
Public Connectors, commandsMap As Map ' Connectors: Almacena las instancias de RDCConnector por DB.
|
||||
' commandsMap: Almacena los comandos SQL cargados para cada DB.
|
||||
|
||||
Public SQL1 As SQL ' Objeto SQL para interactuar con la base de datos de usuarios (SQLite).
|
||||
Private bc As BCrypt ' Objeto para realizar operaciones de hashing de contraseñas de forma segura (para autenticación).
|
||||
Public MainConnectorsLock As JavaObject ' Objeto de bloqueo para proteger Main.Connectors
|
||||
End Sub
|
||||
|
||||
Sub AppStart (Args() As String)
|
||||
' --- INICIO DE CAMBIOS ---
|
||||
' Inicializamos la base de datos. Se creará si no existe.
|
||||
' --- Subrutina principal que se ejecuta al iniciar la aplicación ---
|
||||
|
||||
' 1. Inicializa la base de datos local de usuarios (SQLite).
|
||||
' Esta base de datos se crea automáticamente si no existe y contiene los usuarios para el panel de administración.
|
||||
InitializeSQLiteDatabase
|
||||
' --- FIN DE CAMBIOS ---
|
||||
listaDeCP.Initialize
|
||||
srvr.Initialize("")
|
||||
Dim con As RDCConnector
|
||||
Connectors = srvr.CreateThreadSafeMap
|
||||
commandsMap.Initialize
|
||||
con.Initialize("DB1") 'Inicializamos el default de config.properties
|
||||
Connectors.Put("DB1", con)
|
||||
srvr.Port = con.serverPort
|
||||
listaDeCP.Add("DB1")
|
||||
|
||||
' 2. Inicializa los mapas globales definidos en GlobalParameters.bas.
|
||||
' Estos mapas se usan para monitorear el servidor y gestionar configuraciones dinámicas.
|
||||
GlobalParameters.mpLogs.Initialize ' Mapa para almacenar logs de actividad.
|
||||
GlobalParameters.mpTotalRequests.Initialize ' Mapa para contar peticiones por endpoint/DB.
|
||||
GlobalParameters.mpTotalConnections.Initialize ' Mapa para almacenar el estado de los pools de conexión por DB.
|
||||
GlobalParameters.mpBlockConnection.Initialize ' Mapa para gestionar IPs bloqueadas (si la funcionalidad está activa).
|
||||
|
||||
' 3. Inicializa las estructuras principales del servidor HTTP.
|
||||
listaDeCP.Initialize ' Inicializa la lista que contendrá los IDs de las bases de datos.
|
||||
srvr.Initialize("") ' Inicializa el objeto servidor HTTP.
|
||||
Connectors = srvr.CreateThreadSafeMap ' Crea un mapa seguro para almacenar instancias de RDCConnector (un conector por DB).
|
||||
commandsMap.Initialize ' Inicializa el mapa que almacenará los comandos SQL cargados de los archivos de configuración.
|
||||
|
||||
' <<<< NUEVA INICIALIZACIÓN: Creamos una instancia de ReentrantLock para proteger Main.Connectors >>>>
|
||||
MainConnectorsLock.InitializeNewInstance("java.util.concurrent.locks.ReentrantLock", Null)
|
||||
' <<<< HASTA AQUÍ LA NUEVA INICIALIZACIÓN >>>>
|
||||
|
||||
' === 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}"$)
|
||||
|
||||
' === 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)
|
||||
' en el mismo directorio donde se ejecuta el JAR.
|
||||
cpFiles = File.ListFiles("./")
|
||||
If cpFiles.Size > 0 Then
|
||||
Log(cpFiles)
|
||||
For i = 0 To cpFiles.Size - 1
|
||||
If cpFiles.Get(i) = "config.DB2.properties" Then ' Si existe el archivo DB2, lo usamos.
|
||||
Dim con As RDCConnector
|
||||
con.Initialize("DB2")
|
||||
Connectors.Put("DB2", con)
|
||||
listaDeCP.Add("DB2")
|
||||
End If
|
||||
If cpFiles.Get(i) = "config.DB3.properties" Then ' Si existe el archivo DB3, lo usamos.
|
||||
Dim con As RDCConnector
|
||||
con.Initialize("DB3")
|
||||
Connectors.Put("DB3", con)
|
||||
listaDeCP.Add("DB3")
|
||||
End If
|
||||
If cpFiles.Get(i) = "config.DB4.properties" Then ' Si existe el archivo DB4, lo usamos.
|
||||
con.Initialize("DB4")
|
||||
Connectors.Put("DB4", con)
|
||||
listaDeCP.Add("DB4")
|
||||
End If
|
||||
Next
|
||||
End If
|
||||
srvr.AddHandler("/ping", "ping", True) ' Agrega un manejador a la ruta "/test", asignando las solicitudes a la clase TestHandler, el último parámetro indica si el manejador debe ejecutar en un nuevo hilo (False en este caso)
|
||||
srvr.AddHandler("/test", "TestHandler", True) ' Agrega un manejador a la ruta "/test", asignando las solicitudes a la clase TestHandler, el último parámetro indica si el manejador debe ejecutar en un nuevo hilo (False en este caso)
|
||||
|
||||
' --- INICIO DE CAMBIOS ---
|
||||
' 1. Rutas para el sistema de Login
|
||||
srvr.AddHandler("/login", "LoginHandler", True) ' Sirve la página de login
|
||||
srvr.AddHandler("/dologin", "DoLoginHandler", True) ' Procesa el intento de login
|
||||
srvr.AddHandler("/logout", "LogoutHandler", True) ' Cierra la sesión
|
||||
srvr.AddHandler("/changepass", "ChangePassHandler", True)
|
||||
' 2. El handler del manager se queda igual, pero ahora estará protegido
|
||||
srvr.AddHandler("/manager", "Manager", True)
|
||||
' --- FIN DE CAMBIOS ---
|
||||
|
||||
srvr.AddHandler("/DBJ", "DBHandlerJSON", True)
|
||||
srvr.AddHandler("/dbrquery", "DBHandlerJSON", True)
|
||||
srvr.AddHandler("/favicon.ico", "faviconHandler", True)
|
||||
' srvr.AddHandler("/*", "DB1Handler", False) ' Si no se especifica una base de datos, entonces asignamos la solicitud a la DB1.
|
||||
|
||||
srvr.AddHandler("/*", "DBHandlerB4X", True)
|
||||
|
||||
srvr.Start
|
||||
Log("===========================================================")
|
||||
Log($"-=== jRDC is running on port: ${srvr.port} (version = $1.2{VERSION}) ===-"$)
|
||||
Log("===========================================================")
|
||||
StartMessageLoop
|
||||
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.")
|
||||
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.")
|
||||
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.")
|
||||
End If
|
||||
Next
|
||||
Else
|
||||
Log("Main.AppStart: No se encontraron archivos de configuración adicionales (config.DBx.properties).")
|
||||
End If
|
||||
|
||||
' Log final de las bases de datos que el servidor está gestionando.
|
||||
Dim sbListaDeCP_Log As StringBuilder
|
||||
sbListaDeCP_Log.Initialize
|
||||
For Each item As String In listaDeCP
|
||||
sbListaDeCP_Log.Append(item).Append(", ")
|
||||
Next
|
||||
If sbListaDeCP_Log.Length > 0 Then
|
||||
sbListaDeCP_Log.Remove(sbListaDeCP_Log.Length - 2, sbListaDeCP_Log.Length) ' Elimina la última ", "
|
||||
End If
|
||||
Log($"Main.AppStart: Bases de datos configuradas y listas: [${sbListaDeCP_Log.ToString}]"$)
|
||||
|
||||
' === 6. REGISTRO DE HANDLERS HTTP PARA EL SERVIDOR ===
|
||||
' Asocia rutas URL específicas con clases que manejarán las peticiones correspondientes.
|
||||
' El último parámetro (True) indica que el handler se ejecutará en un nuevo hilo,
|
||||
' lo que es recomendable para la mayoría de los casos para evitar bloqueos.
|
||||
srvr.AddHandler("/ping", "ping", True) ' Endpoint simple para verificar si el servidor está activo.
|
||||
srvr.AddHandler("/test", "TestHandler", True) ' Endpoint para pruebas de conexión y estado del servidor.
|
||||
srvr.AddHandler("/login", "LoginHandler", True) ' Muestra la página HTML de login.
|
||||
srvr.AddHandler("/dologin", "DoLoginHandler", True) ' Procesa el intento de inicio de sesión.
|
||||
srvr.AddHandler("/logout", "LogoutHandler", True) ' Cierra la sesión del usuario.
|
||||
srvr.AddHandler("/changepass", "ChangePassHandler", True) ' Permite a los usuarios cambiar su contraseña.
|
||||
srvr.AddHandler("/manager", "Manager", True) ' Panel de administración del servidor (requiere autenticación).
|
||||
srvr.AddHandler("/DBJ", "DBHandlerJSON", True) ' Handler para clientes web (ej. JavaScript, Node.js) que usan JSON.
|
||||
srvr.AddHandler("/dbrquery", "DBHandlerJSON", True) ' Un alias para el handler JSON, por si se usa en clientes específicos.
|
||||
srvr.AddHandler("/favicon.ico", "faviconHandler", True) ' Sirve el icono de la página (favicon).
|
||||
srvr.AddHandler("/*", "DBHandlerB4X", True) ' Handler por defecto para clientes B4X (DBRequestManager),
|
||||
' procesa peticiones dinámicamente según la URL.
|
||||
|
||||
' 7. Inicia el servidor HTTP.
|
||||
srvr.Start
|
||||
Log("===========================================================")
|
||||
Log($"-=== jRDC está funcionando en el puerto: ${srvr.Port} (versión = $1.2{VERSION}) ===-"$)
|
||||
Log("===========================================================")
|
||||
|
||||
' 8. Inicia el bucle de mensajes de B4J. Es esencial para que la aplicación
|
||||
' de servidor continúe ejecutándose y procesando eventos.
|
||||
StartMessageLoop
|
||||
|
||||
End Sub
|
||||
|
||||
' Nueva subrutina para crear y configurar la base de datos de usuarios
|
||||
' --- Subrutina para inicializar la base de datos de usuarios local (SQLite) ---
|
||||
' Esta base de datos se utiliza para almacenar credenciales de usuarios que pueden
|
||||
' acceder al panel de administración del servidor jRDC.
|
||||
Sub InitializeSQLiteDatabase
|
||||
Dim dbFileName As String = "users.db"
|
||||
' Si la base de datos no existe en la carpeta del .jar, la creamos
|
||||
If File.Exists(File.DirApp, dbFileName) = False Then
|
||||
Log("Creando nueva base de datos de usuarios: " & dbFileName)
|
||||
' Inicializamos la conexión
|
||||
SQL1.InitializeSQLite(File.DirApp, dbFileName, True)
|
||||
' Creamos la tabla de usuarios
|
||||
Dim createUserTable As String = "CREATE TABLE users (username TEXT PRIMARY KEY, password_hash TEXT NOT NULL)"
|
||||
SQL1.ExecNonQuery(createUserTable)
|
||||
|
||||
' Creamos un usuario por defecto para el primer inicio
|
||||
Dim defaultUser As String = "admin"
|
||||
Dim defaultPass As String = "12345"
|
||||
Dim hashedPass As String = bc.hashpw(defaultPass, bc.gensalt) ' bc.HashPassword(defaultPass)
|
||||
|
||||
SQL1.ExecNonQuery2("INSERT INTO users (username, password_hash) VALUES (?, ?)", Array As Object(defaultUser, hashedPass))
|
||||
Log($"Usuario por defecto creado -> user: ${defaultUser}, pass: ${defaultPass}"$)
|
||||
Else
|
||||
' Si ya existe, solo la abrimos
|
||||
SQL1.InitializeSQLite(File.DirApp, dbFileName, True)
|
||||
Log("Base de datos de usuarios cargada.")
|
||||
End If
|
||||
Dim dbFileName As String = "users.db" ' Nombre del archivo de la base de datos SQLite.
|
||||
|
||||
' Verifica si el archivo de la base de datos ya existe en el directorio de la aplicación.
|
||||
If File.Exists(File.DirApp, dbFileName) = False Then
|
||||
Log("Creando nueva base de datos de usuarios: " & dbFileName)
|
||||
' Inicializa la conexión a la base de datos SQLite, creándola si no existe (último parámetro en True).
|
||||
SQL1.InitializeSQLite(File.DirApp, dbFileName, True)
|
||||
|
||||
' Define y ejecuta la sentencia SQL para crear la tabla 'users'.
|
||||
Dim createUserTable As String = "CREATE TABLE users (username TEXT PRIMARY KEY, password_hash TEXT NOT NULL)"
|
||||
SQL1.ExecNonQuery(createUserTable)
|
||||
|
||||
' Crea un usuario por defecto para facilitar el primer acceso al panel de administración.
|
||||
Dim defaultUser As String = "admin"
|
||||
Dim defaultPass As String = "12345"
|
||||
' Genera un hash seguro de la contraseña usando BCrypt, lo cual es crucial para la seguridad.
|
||||
Dim hashedPass As String = bc.hashpw(defaultPass, bc.gensalt)
|
||||
' 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}"$)
|
||||
Else
|
||||
' Si el archivo de la base de datos ya existe, simplemente se abre.
|
||||
SQL1.InitializeSQLite(File.DirApp, dbFileName, True)
|
||||
Log("Base de datos de usuarios cargada.")
|
||||
End If
|
||||
End Sub
|
||||
' --- FIN DE CAMBIOS ---
|
||||
@@ -3,6 +3,7 @@ ModuleBookmarks1=
|
||||
ModuleBookmarks10=
|
||||
ModuleBookmarks11=
|
||||
ModuleBookmarks12=
|
||||
ModuleBookmarks13=
|
||||
ModuleBookmarks2=
|
||||
ModuleBookmarks3=
|
||||
ModuleBookmarks4=
|
||||
@@ -16,6 +17,7 @@ ModuleBreakpoints1=
|
||||
ModuleBreakpoints10=
|
||||
ModuleBreakpoints11=
|
||||
ModuleBreakpoints12=
|
||||
ModuleBreakpoints13=
|
||||
ModuleBreakpoints2=
|
||||
ModuleBreakpoints3=
|
||||
ModuleBreakpoints4=
|
||||
@@ -29,6 +31,7 @@ ModuleClosedNodes1=
|
||||
ModuleClosedNodes10=
|
||||
ModuleClosedNodes11=
|
||||
ModuleClosedNodes12=
|
||||
ModuleClosedNodes13=
|
||||
ModuleClosedNodes2=
|
||||
ModuleClosedNodes3=
|
||||
ModuleClosedNodes4=
|
||||
@@ -37,6 +40,6 @@ ModuleClosedNodes6=
|
||||
ModuleClosedNodes7=
|
||||
ModuleClosedNodes8=
|
||||
ModuleClosedNodes9=
|
||||
NavigationStack=DBHandlerJSON,SendErrorResponse,239,0,RDCConnector,LoadConfigMap,86,0,RDCConnector,GetCommand,90,0,faviconHandler,Initialize,12,0,faviconHandler,Class_Globals,8,0,Manager,Initialize,9,0,RDCConnector,Class_Globals,9,0,Main,AppStart,49,0,Manager,Handle,23,6,RDCConnector,Initialize,38,0
|
||||
NavigationStack=RDCConnector,Class_Globals,12,0,Manager,Initialize,8,0,RDCConnector,GetPoolStats,256,0,Main,Process_Globals,58,0,RDCConnector,Close,266,0,Manager,Handle,171,2,Main,AppStart,138,0,DBHandlerJSON,Handle,82,0,RDCConnector,Initialize,70,0,Cambios,Process_Globals,43,6
|
||||
SelectedBuild=0
|
||||
VisibleModules=2,3,11,9,12
|
||||
VisibleModules=3,4,12,10,13,1
|
||||
|
||||
Reference in New Issue
Block a user