domingo, 5 de febrero de 2012

Agilizar la entrada de datos en campos de tipo DATE

Si tenemos un campo de tipo DATE y que tiene la máscara DD/MM/YYYY el usuario puede introducir la fecha 05022012 y automáticamente se transforma en 05/02/2012, pero en muchos casos puede ser útil permitir al usuario lo siguiente:

  • Introducir 1 ó 2 dígitos, por ejemplo, teclear 5 y asumir el mes y año del SYSDATE transformando ese 5 tecleado por el usuario en la fecha 05/02/2012.
  • Introducir 4 dígitos, asumiendo los 2 primeros el día, los 2 dígitos siguientes el mes y el año obtenerlo del SYSDATE.
  • Introducir 6 dígitos y asumiendo los 2 primeros el día, los 2 siguientes el mes y los 2 últimos el año en formato de 2 dígitos, es decir, se le debe de sumar 2000, de forma que si el usuario teclea 050212 se asuma la fecha 05/02/2012.
Para hacerlo lo que haremos será capturar el error de conversión de máscara de formato FRM-50026 en el disparador ON-ERROR de la siguiente forma:

DECLARE
v_errtyp    VARCHAR2(3):= ERROR_TYPE;
v_errnum    NUMBER := ERROR_CODE;
v_errtxt    VARCHAR2(1000) := ERROR_TEXT;
v_fecha       DATE;
v_dia         VARCHAR2(2);
v_mes         VARCHAR2(2);
v_anho        VARCHAR2(4);
v_error       BOOLEAN := TRUE;
v_valor_campo VARCHAR2(30);
v_mascara     VARCHAR2(30);
BEGIN
  IF v_errtyp = 'FRM'
  AND v_errnum = 50026
  AND Get_Item_Property(:system.trigger_item, DATATYPE) = 'DATE'
  AND UPPER(Get_Item_Property(:system.trigger_item, FORMAT_MASK)) = 'DD/MM/YYYY' THEN
  -- Capturamos el FRM-50026 en campos de tipo DATE y con máscara DD/MM/YYYY
  BEGIN
   v_valor_campo := NAME_IN(:system.trigger_item);

      IF LENGTH(v_valor_campo) = 1 THEN
      -- El usuario sólo teclea un dígito
      v_dia := v_valor_campo;
      ELSIF LENGTH(v_valor_campo) IN (2, 4, 6) THEN
     -- Obtenemos el día de lo que ha tecleado el usuario
     v_dia := SUBSTR(v_valor_campo, 1, 2);
    END IF;

      IF LENGTH(v_valor_campo) IN (4, 6) THEN
      -- Obtenemos el mes, sólo si el usuario ha tecleado algo de 4 ó 6 dígitos
      v_mes := SUBSTR(v_valor_campo, 3, 2);
      END IF;

      IF LENGTH(v_valor_campo) = 6 THEN
      -- Obtenemos el año, sólo si el usuario ha tecleado algo 6 dígitos
      v_anho := TO_CHAR(TO_NUMBER(SUBSTR(v_valor_campo, 5, 2)) + 2000);
      END IF;

      -- Obtenemos el mes y el año de SYSDATE en caso de que haber sido tecleado por el usuario
      v_mes := NVL(v_mes, TO_CHAR(SYSDATE, 'MM'));
      v_anho := NVL(v_anho, TO_CHAR(SYSDATE, 'YYYY'));
      v_fecha := TO_DATE(v_dia || '/' || v_mes || '/' || v_anho, 'DD/MM/YYYY');
      -- El comando COPY únicamente está preparado para asignar un valor VARCHAR2,
      -- con variables de tipo DATE debemos de asegurar la máscar de formato
      -- con la que hará conversión.
      v_mascara := Get_Application_Property(BUILTIN_DATE_FORMAT);
      Set_Application_Property(BUILTIN_DATE_FORMAT, 'DD/MM/YYYY');
      COPY(TO_CHAR(v_fecha, 'DD/MM/YYYY'), :system.trigger_item);
      Set_Application_Property(BUILTIN_DATE_FORMAT, v_mascara);
      v_error := FALSE;
    EXCEPTION
      WHEN OTHERS THEN
        -- Ante cualquier error dejamos el mensaje original de error
        NULL;
    END;  
  END IF;

  IF v_error THEN
    -- No se ha podido transformar lo tecleado por el usuario en una fecha válida
    MESSAGE(v_errtyp || '-' || TO_CHAR(v_errnum) || ': ' || v_errtxt); 
    RAISE Form_Trigger_Failure;
  END IF;
END;

jueves, 12 de mayo de 2011

Cambiar icono de Java en Forms 10g y 11g

Vía: http://blog.edisa.com/2011/05/cambiar-icono-de-java-en-forms-10g-y-11g/

Cuando se ejecuta una aplicación en Forms 10g ó 11g en una ventana independiente del navegador, es decir, con separateframe=true, el icono que se le asigna a la ventana en la barra de tareas es el de la taza de café de Java.

Este icono se puede modificar para poner uno personalizado. Ejemplo:



Para cambiar el icono hay que utilizar el procedimiento WebUtil_SeparateFrame.SetIcon de la librería WEBUTIL.PLL.

Ejemplo: WebUtil_SeparateFrame.SetIcon(‘/forms/java/libra_menu.gif’);

El icono que se indica por parámetro debe de tener formato GIF o JPG y debe de encontrarse en la carpeta en donde están los jar de Java ($ORACLE_HOME/forms/java). En caso de no encontrar el archivo en esa carpeta dará el siguiente error: “WUB-603: No se ha podido cargar la imagen del icono …..”

El icono quedará guardado en la caché de Java para futuras ejecuciones:

jueves, 14 de abril de 2011

Maximizar Ventana MDI en Forms 11

Fuente: http://blog.edisa.com/2011/04/maximizar-ventana-mdi-en-forms-11/

En Forms 11 una de las novedades es la integración con JavaScript y eso se puede aprovechar para forzar el maximizado completo de la ventana MDI cuando que ejecuta en una ventana separada del navegador, es decir, cuando se ejecuta con el parámetro separateframe=true
Al ejecutar SET_WINDOW_PROPERTY(forms_mdi_window, window_state, maximize); el maximizado no es completo, hace un ajuste según el tamaño de la pantalla, pero no es equivalente a que si el usuario pulsa el botón de maximizado.
Para que sea equivalente a que el usuario pulse el botón de maximizar hay que realizar lo siguiente:
  • En el archivo formsweb.cfg añadir la siguiente línea a la sección de la configuración (El nombre que se le asigne a la variable es indiferente, pero tiene que ser el mismo dos sitios que están resaltados en negrita):
    • applet_name=OracleApplet
  • Desde Forms ejecutar:
    • web.javascript_eval_expr(‘document.OracleApplet.getFrame().setExtendedState(6)’);
Ejemplo con SET_WINDOW_PROPERTY(forms_mdi_window, window_state, maximize);


Resultado con SET_WINDOW_PROPERTY(forms_mdi_window, window_state, maximize);


Ejemplo ejecutando web.javascript_eval_expr(‘document.OracleApplet.getFrame().setExtendedState(6)’);



Otro ejemplo similar se puede ver en: http://blog.avanttic.com/2010/07/30/integracion-forms-11g-con-javascript-maximizar-forms_mdi_window/

sábado, 17 de abril de 2010

Forms6: Cerrar Report Background por código

Hace tiempo comenté aquí la forma de cerrar el Report Background Engine cada vez que se ejecuta un informe Forms6: Forzar cierre report background, en ese momento comenté que no me parecía muy optimo el cierre del Report Background Engine cada vez que se lanza un informe, pero alguna vez se me dio la necesidad de hacerlo por dos motivos:

  • El usuario tenía que ejecutar dos programas en Forms con distinta configuración de NLS_LANG, uno con SPANISH_SPAIN.UTF8 y otro con SPANISH_SPAIN.WE8ISO8859P15, de forma que al ejecutar un informe se abría el Report Background con la configuración de NLS_LANG del programa que lo llamaba y el otro programa al tener abierto el Report Background no cogía bien la configuración del NLS_LANG, en ese caso lo mejor es forzar que se cierre el Report Background por cada informe.
  • Conexiones remotas por Terminal Server con un programa publicado en Forms. Al lanzar un informe se abre el Report Background y queda abierto y al salir del programa se mantiene la conexión de Terminal Server por culpa del Report Background cuando en realidad ya no va a ser utilizado, en ese caso lo mejor sería forzar cerrar el Report Background al salir del programa, para ello podemos forzar que el Report Background se cierre ejecutando lo siguiente en el disparador KEY-EXIT del programa:
DECLARE
  pl_id ParamList; 
BEGIN 
  pl_id := Get_Parameter_List('CERRAR_RBE'); 

  IF NOT Id_Null(pl_id) THEN 
    Destroy_Parameter_List( pl_id ); 
  END IF; 

  pl_id := Create_Parameter_List('CERRAR_RBE'); 

  Add_Parameter(pl_id, 'PARAMFORM', TEXT_PARAMETER, 'NO');
  Add_Parameter(pl_id, 'ORACLE_SHUTDOWN', TEXT_PARAMETER, 'Yes');

  Run_Product(REPORTS, 'cerrar_rbe', SYNCHRONOUS, RUNTIME, FILESYSTEM, pl_id, NULL);
  Destroy_Parameter_List( pl_id );
END;

El informe 'cerrar_rbe' no es necesario que exista, simplemente se intenta ejecutar un informe pasándole el parámetro para forzar el cierre del Report Background.

martes, 19 de enero de 2010

¿Las claves foráneas producen bloqueos en Forms?

La respuesta es que si no hacemos bien las cosas es SI. Vamos a verlo mejor con un ejemplo con las típicas tablas de formación que utiliza Oracle,  DEPT y EMP:

CREATE TABLE dept (
  deptno  NUMBER(2) NOT NULL CONSTRAINT dept_pk PRIMARY KEY,
  dname   VARCHAR2(14),
  loc     VARCHAR2(13));

CREATE TABLE emp (
  empno    NUMBER(4) NOT NULL CONSTRAINT emp_pk PRIMARY KEY,
  ename    VARCHAR2(10),
  job      VARCHAR2(9),
  mgr      NUMBER(4),
  hiredate DATE,
  sal      NUMBER(7,2),
  comm     NUMBER(7,2),
  deptno   NUMBER(2) CONSTRAINT emp_ref_dept_fk
                     REFERENCES dept(deptno));


Insertamos un departamento:

INSERT INTO dept (deptno, dname, loc)
  VALUES (23, 'CONTABILIDAD', 'VIGO');

COMMIT;


Ahora insertamos un empleado:

INSERT INTO emp (empno,ename,job,mgr,
                 hiredate,
                 sal,comm,deptno)
  VALUES (1234, 'MARIOBROS', 'FONTA', 10,
          TO_DATE('16/05/1975', 'DD/MM/YYYY'),
          50000, 2000, 23);


Antes de hacer el commit, al ver los bloqueos veremos que están la tabla EMP y la DEPT bloqueadas.

SELECT lk.sid, lk.TYPE lock_type, lk.lmode,
       (SELECT o.object_name
          FROM dba_objects o
         WHERE o.object_id = lk.id1) object_name
  FROM gv$lock lk
 WHERE lk.TYPE IN ('TM', 'UL');


Lo que nos devolverá será esto:

   SID LO     LMODE OBJECT_NAME
------ -- --------- ----------------
   311 TM         3 EMP
   311 TM         3 DEPT


El bloqueo de la tabla EMP es por el registro que acabamos de insertar pero que todavía no está commitado, pero la tabla DEPT se bloquea por la clave foránea, ya que en el momento de insertar el registro en EMP existía el departamento 23 en DEPT, pero como no hemos comitado el insert en EMP tiene que bloquear el registro del departamento 23 para evitar que otra sesión borre ese registro o modifique el código del departamento.

Hasta aquí todo normal, el problema viene cuando otra sesión quiere modificar el nombre del departamento:

UPDATE dept
   SET dname = 'FINANCIERO'
 WHERE deptno = 23;


Con esta update nos dejará modificarlo sin problema, pero si en vez de hacer un update que únicamente modifica el nombre del departamento hacemos este otro (el resultado final de los datos en la tabla serán los mismos):

UPDATE dept
   SET deptno = 23,
       dname = 'FINANCIERO',
       loc = 'VIGO'
 WHERE deptno = 23;


El registro quedaría con los mismos datos, pero ya tenemos el bloqueo montado ya que le estamos diciendo que actualice el campo DEPTNO, pero la modificación de ese campo afectaría a registros introducidos por otra sesión y que todavía no están comitados, por lo que se quedará bloqueado.

Esta es la explicación del motivo, pero ¿porqué ocurre en Forms?. La respuesta la encontramos en la propiedad "Actualizar Sólo Columnas Cambiadas" a nivel de bloque, por defecto esa propiedad está a "No" con lo que para hacer un update actualizará todos los campos, incluso los que no ha modificado el usuario.

Lo recomendable para evitar bloqueos es poner esa propiedad al valor "Si".

domingo, 29 de noviembre de 2009

Inyección de código

Voy a mostrar un ejemplo de como mediante inyección de código podemos consultar registros de una tabla distinta a la que tiene el bloque como origen de consulta.

Para ver el ejemplo hacemos un form asociado a la tabla DEPT (tabla típica de los ejemplos de Oracle):


SQL> DESC DEPT;
 Nombre              ¿Nulo?   Tipo
 ------------------- -------- ------------------
 DEPTNO                       NUMBER(2)
 DNAME                        VARCHAR2(14)
 LOC                          VARCHAR2(13)

Pantalla con los datos de la tabla:


Ahora vamos a ver como con ese bloque mostrar el contenido de USER_TAB_COLUMS, para ello entramos en modo de entrada consulta (pulsando F7) y en cualquiera de los campos ponemos ":" (dos puntos) y ejecutamos consulta, y tendremos la siguiente pantalla:


El objetivo de esa ventana es que el usuario pueda introducir una condición avanzada, como podría ser un AND EXISTS, un BETWEEN, ..., pero la consulta Oracle Forms la envía a la base de datos tal cual ha sido escrita por el usuario, por lo que podemos introducir lo siguiente:

1=2) UNION ALL SELECT rownum, table_name, column_name FROM user_tab_columns --

El 1=2 es para evitar los registros de la tabla DEPT, luego se añade una UNION ALL para sacar los registros de la tabla que queramos, y la consulta la terminamos con -- ya que Forms a la consulta que introduce el usuario la mete entre paréntesis, de esta forma comentamos el último parénteseis.

El resultado será el siguiente:


Este funcionamiento se puede deshabilitar, evitando que al introducir ":" en modo de entrada de consulta en un campo de forma que no se abra al ventana en donde introducir una condición adicional, para ello en Forms6 en cliente servidor se puede introducir la siguiente entrada en el regedit:

FORMS60_RESTRICT_ENTER_QUERY con el valor 1

En Forms 10g para activar o desactivarlo hay que editar el archivo al que apunta la variable envFile del archivo de configuración formsweb.cfg y modificar / añadir la variable:


FORMS_RESTRICT_ENTER_QUERY=TRUE

  • TRUE: Está desactivado (opción recomendable).
  • FALSE: Está activado, se puede inyectar código.
NOTA: Corregida esta entrada, estaba al revés

Para más información se puede consultar la nota 163305.1 del metalink. También en http://www.red-database-security.com/wp/sql_injection_forms_us.pdf se puede obtener más información.

sábado, 28 de noviembre de 2009

Asignar una función la tecla escape en Forms10g

Una utilidad de la tecla ESC (Escape) y muy utilizada en muchas aplicaciones es la de cerrar la ventana abierta. Esta operación se puede configurar en Forms 10g editando el archivo frmweb.res y añadiendo la siguiente línea:

27 : 0 : "Escape" : 32 : "Salir"
  • 27: es el identificador "Java function number" asignado a la tecla escape.
  • 0: Indica el "Java modifiers number" es decir, qué combinación de teclas más la Escape se pulsarán para ejecutar el evento, el 0 quiere decir que no se utilizará en conjunto con otra tecla, si se pusiese 1 para ejecutar el evento habría que pulsar Mayúsculas+Esc, con 2 haríamos referencia a la tecla "Control", con 8 a la tecla "Alt". .
  • "Escape": Es un texto asignado por el usuario, no afecta al funcionamiento.
  • 32: es el identificador "Forms function number" que indentifica la operación que debe se debe de ejecutar en Forms. El 32 indica que se ejecutará KEY-EXIT.
  • "Salir": Es un texto asignado por el usuario, no afecta al funcionamiento.
El archivo fmrweb.res se encuentra en $ORACLE_HOME/forms/admin/resource/

En caso de configurar el servidor de aplicaciones en UTF8 se utiliza el archivo fmrweb_utf8.res.

Se considera que el servidor de aplicaciones está en UTF8 cuando en el archivo indicado en la entrada envFile del archivo formsweb.cfg tiene definida la siguiente variable:

NLS_LANG=Spanish_Spain.UTF8