mirror of
https://github.com/KeymonSoft/jRDC-Multi.git
synced 2026-04-17 21:06:24 +00:00
- 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) paradefinir 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.
246 lines
12 KiB
QBasic
246 lines
12 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)
|
|
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).
|
|
|
|
' Se obtiene "params" como una Lista en lugar de un Mapa.
|
|
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.
|
|
If paramsList = Null Or paramsList.IsInitialized = False Then
|
|
paramsList.Initialize
|
|
End If
|
|
|
|
' 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
|
|
|
|
' 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 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.
|
|
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) ==========
|
|
' =================================================================
|
|
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.
|
|
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.")
|
|
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
|