Unterschiedliche Wege um APEX Anwendungsparameter zu setzen

Von Tobias Arnhold 9.10.2015
Viele APEX Anwendungen benötigen zur korrekten Ausführung User-spezifische nicht veränderbare Variablen. Auf diese Variablen bezieht man sich, um sicherheitsrelevante Abfragen für den angemeldeten User auszuführen.

Hier eine kurze Liste an möglichen Anwendungsfällen:
- User ID
- User basierte Parameter
  (Land / Region / Ort / Niederlassung / Firmen- und Anwendungsspezifische Hintergründe)
- Rechte und Rollen
- Berechtigungen auf Zeilenebene
- Schreib- und Leserechte

Diese Variablen sollten je nach Inhalt und Verwendung auf eine der folgenden Arten gespeichert werden:
- APEX Application Items (Mit Protection Level: Restricted - May not be set from browser)
- APEX Collections (Eine Zeile)
- Eigene User- und Session spezifische Tabelle

Hinweis:
Die genannten Inhalte sollten nicht in Page-Items abgespeichert werden, auf diese Sie sich später beziehen.
Beispiel: P1_USER_ID

Die Vorteile ergeben sich wie folgt:
Application Items lassen sich leicht auslesen (Debugging) und verarbeiten (da diese immer pro Session gespeichert werden).

Collections und Custom-Tabellen haben erhebliche Performancevorteile bei Verwendung innerhalb von Datenbank-Views. Außerdem können mehr als 4000 Zeichen (CLOB) abgespeichert werden.
Beim Debugging und entwickeln (beispielsweise Views) sind eigene Tabellen (Imho) einfacher zu verarbeiten, dafür werden Collections automatisch pro Session angelegt und wieder gelöscht.

Was ist nun zu empfehlen?
Wenn die Größe einer Anwendung nicht feststeht oder es sich generell um eine kleine bis mittelgroße Anwendung handelt (10-60 Seiten), dann sollte man mit Application Items starten und im späteren Verlauf die Technik bei Notwendigkeit umstellen.
Wenn von vornherein klar ist, dass die Anwendung mehr als 100 Seiten groß wird und viele Nutzer mit vielen unterschiedlichen Attributen und Berechtigungen arbeiten, dann sind Collections oder eigene Tabellen zu bevorzugen.

Wie kann ein solcher Prozess aussehen?
Prinzipiell sollte ein solcher Prozess als "Application Process" angelegt sein. Außerdem sollte die komplette Logik in eine Package Prozedur ausgelagert werden und als Übergabeparameter den :APP_USER beinhalten.

Aufruf-Beispiel: 
APP_PACKAGE.START_APPLICATION (P_APP_USER => UPPER(:APP_USER));

Die Ausführung sollte dann nur einmal nach der Anmeldung geschehen:
Process Point: On New Instance (new session)
Info:
Hierbei hatte ich das ein oder andere Mal Probleme bei der korrekten Ausführung.
Daher ist alternativ auch eine PL/SQL Prüfung möglich:
:APP_USER <> 'nobody' and :AI_USER_ID IS NULL

Hinweis:
Achten Sie innerhalb ihrer Package Prozedur darauf, dass Sie Fehler in eine entsprechende Fehlertabelle wegschreiben.

Code Beispiel zum setzen der User ID:
-- ID auslesen
SELECT ID 
INTO V_USER_ID 
FROM MY_USER_TABELLE 
WHERE UPPER(USER_NAME) = P_APP_USER;
 

-- Item setzen
APEX_UTIL.SET_SESSION_STATE (
        P_NAME => 'AI_USER_ID',
        P_VALUE => V_USER_ID

);

Komplettes Beispiel:
Zentraler - Application Process

Package Prozedur:
Basierend auf einem alten Package-Beispiel, könnte der Code wie folgt aussehen:
create or replace package body pkg_login_example as
/* Package Variables */
  gv_proc_name    varchar2(100);
  gv_action       varchar2(4000);
  gv_ora_error    varchar2(4000);
  gv_custom_error varchar2(4000);
  gv_parameter    varchar2(4000);
  -- Globale User-Variable direkt im Package, Übergabeparameter ist so nicht notwendig
  gv_user         varchar2(20) := upper(nvl(v('APP_USER'),user));
  
/* Save errors */
/*
  --------------------------------------------------------
  --  DDL for Table ERR_LOG
  --------------------------------------------------------

  CREATE TABLE "ERR_LOG" 
   ( 
    "PROC_NAME" VARCHAR2(200), 
     "ACTION" VARCHAR2(4000), 
     "APP_ID" NUMBER, 
     "APP_PAGE_ID" NUMBER, 
     "APP_USER" VARCHAR2(20), 
     "ORA_ERROR" VARCHAR2(4000), 
     "CUSTOM_ERROR" VARCHAR2(4000), 
     "PARAMETER" VARCHAR2(4000), 
     "TIME_STAMP" DATE
   ) ;
/

*/
procedure add_err  is
pragma autonomous_transaction;
begin
     insert
     into err_log
      ( proc_name,action,app_id,app_page_id,app_user,ora_error,custom_error,parameter,time_stamp )
      values
      ( gv_proc_name,gv_action,nvl(v('APP_ID'),0),nvl(v('APP_PAGE_ID'),0),nvl(nvl(v('APP_USER'),user),'Unknown'),
        gv_ora_error,gv_custom_error,gv_parameter,sysdate );
     commit;
end;


/* ************************************************************************************************************************************** */
/* Anwendungsparameter zu setzen                                                                                                          */  
/* ************************************************************************************************************************************** */

procedure start_application
is
  v_user_id    my_user_table.id%type;
  v_user_role  my_user_table.user_role%type;

begin
  gv_proc_name := 'pkg_login_example.start_application';
  gv_parameter := '';
  
  gv_action := '1. Get USER_ID'; 
  select id 
  into v_user_id 
  from my_user_table
  where upper(user_name) = gv_user; 

  apex_util.set_session_state (p_name => 'AI_USER_ID',p_value => v_user_id);
  
  gv_action := '2. Get USER_ROLE'; 
  select user_role 
  into v_user_role
  from my_user_table
  where upper(user_name) = gv_user; 
  
  apex_util.set_session_state (p_name => 'AI_USER_ROLE',p_value => v_user_role);
  
  gv_action := '3. Get REGION_ROLES or whatever'; 
  -- ...
  
  commit;
exception
when others then   
      gv_ora_error := sqlerrm;
      gv_custom_error := 'Internal Error. Action canceled.';
      rollback;
      add_err; raise_application_error(-20001, gv_custom_error);
end;
 
end pkg_login_example;


Post Tags: