mirror of
https://github.com/KeymonSoft/jRDC-Multi.git
synced 2026-04-17 21:06:24 +00:00
- 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.
264 lines
13 KiB
QBasic
264 lines
13 KiB
QBasic
B4J=true
|
|
Group=Default Group
|
|
ModulesStructureVersion=1
|
|
Type=Class
|
|
Version=10.3
|
|
@EndOfDesignText@
|
|
' Handler class for JSON requests from Web Clients (JavaScript/axios)
|
|
' VERSIÓN 16 (Comentarios y Mensajes en Español):
|
|
' - Se añaden comentarios detallados a la versión con mensajes de error en español.
|
|
' - Revisa que el 'query' exista en config.properties antes de continuar.
|
|
' - Asegura que la conexión a la BD se cierre en todos los 'Return' para evitar fugas.
|
|
Sub Class_Globals
|
|
' Declara una variable privada para mantener una instancia del conector RDC.
|
|
' Este objeto maneja la comunicación con la base de datos.
|
|
Private Connector As RDCConnector
|
|
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).
|
|
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.
|
|
resp.SetHeader("Access-Control-Allow-Origin", "*") ' Permite peticiones desde cualquier origen.
|
|
resp.SetHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS") ' Métodos HTTP permitidos.
|
|
resp.SetHeader("Access-Control-Allow-Headers", "Content-Type") ' Encabezados permitidos en la petición.
|
|
|
|
' El método OPTIONS es una "petición de comprobación previa" (preflight request) que envían los navegadores
|
|
' para verificar los permisos CORS antes de enviar la petición real (ej. POST).
|
|
' Si es una petición OPTIONS, simplemente terminamos la ejecución sin procesar nada más.
|
|
If req.Method = "OPTIONS" Then Return
|
|
|
|
' 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.
|
|
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.
|
|
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")
|
|
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).
|
|
Dim paramsMap As Map = RootMap.Get("params") ' Un mapa con los parámetros para la consulta.
|
|
' Log(RootMap)
|
|
' Verifica si en el JSON se especificó un nombre de base de datos diferente con la clave "dbx".
|
|
If RootMap.Get("dbx") <> Null Then DB = RootMap.Get("dbx") ' Si se especifica, usamos la BD indicada, si no, se queda "DB1".
|
|
|
|
' 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.
|
|
Return
|
|
End If
|
|
|
|
' Prepara una lista para almacenar las claves de los parámetros.
|
|
Dim paramKeys As List
|
|
paramKeys.Initialize
|
|
' Si el mapa de parámetros existe y está inicializado...
|
|
If paramsMap <> Null And paramsMap.IsInitialized Then
|
|
' ...itera sobre todas las claves y las añade a la lista 'paramKeys'.
|
|
For Each key As String In paramsMap.Keys
|
|
paramKeys.Add(key)
|
|
Next
|
|
End If
|
|
' Ordena las claves alfabéticamente. Esto es crucial para asegurar que los parámetros
|
|
' se pasen a la consulta SQL en un orden consistente y predecible.
|
|
paramKeys.Sort(True)
|
|
|
|
' Prepara una lista para almacenar los valores de los parámetros en el orden correcto.
|
|
Dim orderedParams As List
|
|
orderedParams.Initialize
|
|
' Itera sobre la lista de claves ya ordenada.
|
|
For Each key As String In paramKeys
|
|
' Añade el valor correspondiente a cada clave a la lista 'orderedParams'.
|
|
orderedParams.Add(paramsMap.Get(key))
|
|
Next
|
|
|
|
' Obtiene una conexión a la base de datos del pool de conexiones.
|
|
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 NUEVA 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 NUEVA 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.
|
|
If sqlCommand.Contains("?") 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.
|
|
Dim receivedParams As Int = orderedParams.Size
|
|
' Compara si la cantidad de parámetros esperados y recibidos es diferente.
|
|
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.
|
|
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 ordenada de parámetros.
|
|
rs = con.ExecQuery2(sqlCommand, orderedParams)
|
|
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) ==========
|
|
' =================================================================
|
|
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length
|
|
Dim receivedParams As Int = orderedParams.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.
|
|
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, orderedParams)
|
|
' 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.")
|
|
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
|
|
|
|
|
|
' --- Subrutinas de ayuda para respuestas JSON ---
|
|
|
|
' Construye y envía una respuesta JSON de éxito.
|
|
Private Sub SendSuccessResponse(resp As ServletResponse, dataMap As Map)
|
|
' Añade el campo "success": true al mapa de datos para indicar que todo salió bien.
|
|
dataMap.Put("success", True)
|
|
' Crea un generador de JSON.
|
|
Dim jsonGenerator As JSONGenerator
|
|
jsonGenerator.Initialize(dataMap)
|
|
' Establece el tipo de contenido de la respuesta a "application/json".
|
|
resp.ContentType = "application/json"
|
|
' Escribe la cadena JSON generada en el cuerpo de la respuesta HTTP.
|
|
resp.Write(jsonGenerator.ToString)
|
|
End Sub
|
|
|
|
' Construye y envía una respuesta JSON de error.
|
|
Private Sub SendErrorResponse(resp As ServletResponse, statusCode As Int, errorMessage As String)
|
|
' Personaliza el mensaje de error si es un error común de parámetros de Oracle o JDBC.
|
|
If errorMessage.Contains("Índice de columnas no válido") Or errorMessage.Contains("ORA-17003") Then errorMessage = "NUMERO DE PARAMETROS EQUIVOCADO: " & errorMessage
|
|
' Crea un mapa con el estado de error y el mensaje.
|
|
Dim resMap As Map = CreateMap("success": False, "error": errorMessage)
|
|
' Genera la cadena JSON a partir del mapa.
|
|
Dim jsonGenerator As JSONGenerator
|
|
jsonGenerator.Initialize(resMap)
|
|
' Establece el código de estado HTTP (ej. 400 para error del cliente, 500 para error del servidor).
|
|
resp.Status = statusCode
|
|
' Establece el tipo de contenido y escribe la respuesta de error.
|
|
resp.ContentType = "application/json"
|
|
resp.Write(jsonGenerator.ToString)
|
|
End Sub
|
|
|
|
|