Erscheinungsmonat | APEX-Version | Datenbankversion |
August 2015 | ab 5.0 | ab 11.1 |
Das PL/SQL-Paket APEX_UTIL taucht in Blogs, Howto-Dokumenten und in der Dokumentation immer wieder auf - und wenn man es sich mal ansieht, entdeckt man eine Fülle von Prozeduren und Funktionen - manche kann man direkt verwenden, andere sind im Grunde genommen nur für APEX selbst gedacht. Es ist über den Lauf der Zeit gar nicht so einfach, den Überblick zu behalten: Mit jedem Release kommen einige Prozeduren und Funktionen neu dazu, andere werden überflüssig. Dieser Tipp gibt Hilfestellung und erläutert, ohne Anspruch auf Vollständigkeit, einige der in APEX_UTIL enthaltenen Funktionen und Prozeduren.
APEX_UTIL ist selbst nur ein Public Synonym - es verzweigt auf das Paket HTMLDB_UTIL im zur Zeit aktiven APEX-Repository-Schema (bspw. APEX_050000). Mit Werkzeugen wie dem SQL Developer kann man sich die Package Spezifikation ansehen (nicht natürlich die Implementierung, den Package Body). Ein Blick lohnt auf jeden Fall, denn anhand der enthaltenen Kommentare lernt man doch einiges über die Funktionalität. Wer sich die Package Specification mit SQL*Plus ansehen möchte, kann das mit folgender SQL-Abfrage tun.
SQL> set heading off SQL> set feedback off SQL> set lines 200 SQL> set pages 5000 SQL> spool apex_util.pls SQL> select text from all_source where owner='APEX_050000' and name='HTMLDB_UTIL' and type='PACKAGE' order by line SQL> spool off
Der Inhalt der generierten Datei apex_util.pls sieht dann in etwa wie folgt aus.
-------------------------------------------------------------------------------- package htmldb_util authid definer as -------------------------------------------------------------------------------- -- -- Copyright (c) Oracle Corporation 2003 - 2015. All Rights Reserved. -- -- NAME -- htmldb_util.sql -- DESCRIPTION -- APEX Utilities -- Provides procedural access for advanced APEX application development -- -- SECURITY -- Publicly executable. -- -- MODIFIED (MM/DD/YYYY) -- mhichwa 10/07/2003 - Created -- jstraub 10/16/2003 - Added pause procedure -- sspadafo 06/27/2004 - Add get_file_id function (Bug 3449543) -- msewtz 07/06/2004 - Added get_since -- : -- cneumuel 02/24/2015 - In set_session_state: add p_commit -- cneumuel 07/01/2015 - Made package definer rights again (bug #21257683) -- -------------------------------------------------------------------------------- c_must_not_be_public_user constant varchar2(30) := 'MUST_NOT_BE_PUBLIC_USER'; g_blob blob; -- BLOB uploaded via APEX listener g_blob_mime_type varchar2(512) := null; -- BLOB mimetype via APEX listener g_blob_file_charset varchar2(512) := null; -- BLOB file_charset via APEX listener g_blob_action varchar2(512) := null; -- owner:table:blob column: g_pk1 varchar2(4000) := null; g_pk2 varchar2(4000) := null; empty_vc_arr wwv_flow_global.vc_arr2; g_request_cookies utl_http.cookie_table; g_response_cookies utl_http.cookie_table; type header is record (name varchar2(256), value varchar2(1024)); type header_table is table of header index by binary_integer; :
Nicht alle enthaltenen Funktionen sind dokumentiert - und auch in diesem Tipp stellen wir einige undokumentierte Funktionen vor. Diese sollten stets mit Vorsicht eingesetzt werden, denn was nicht dokumentiert ist, kann sich ohne Vorwarnung ändern oder auch ganz verschwinden. Sie sollten genau wissen, was Sie tun. Wenn Sie eine undokumentierte Funktion verwenden möchten, kapseln Sie diese stets in eine eigene PL/SQL-Funktion oder ein eigenes Package, damit sie nur ein einer Stelle referenziert und bei Bedarf einfach ersetzt werden können. Keine gute Idee ist, es undokumentierte Funktionsaufrufe kreuz und quer in einer APEX-Anwendung zu verteilen.
Nahezu jeder APEX-Entwickler kennt die STRING_TO_TABLE und TABLE_TO_STRING-Funktionen, die man beispielsweise dann braucht, wenn man die Inhalte eines APEX-Checkbox-Elements in PL/SQL verarbeiten möchte. Das APEX-Item enthält, durch Doppelpunkte getrennt, die angeklickten Optionen. APEX_UTIL.STRING_TO_TABLE wandelt diesen String in ein PL/SQL-Array um. Beide Funktionen arbeiten auch außerhalb von APEX, wie das folgende Beispiel zeigt.
declare l_array apex_application_global.vc_arr2; begin l_array := apex_util.string_to_table('APEX:PL/SQL:XMLDB:JSON:SPATIAL'); dbms_output.put_line('String to Array ...'); dbms_output.put_line('*******************'); for i in l_array.first..l_array.last loop dbms_output.put_line(i || ': "' || l_array(i) || '"'); end loop; dbms_output.put_line('Array to String ...'); dbms_output.put_line('*******************'); dbms_output.put_line(apex_util.table_to_string(l_array, '#')); end; / String to Array ... ******************* 1: "APEX" 2: "PL/SQL" 3: "XMLDB" 4: "JSON" 5: "SPATIAL" Array to String ... ******************* APEX#PL/SQL#XMLDB#JSON#SPATIAL
Ab APEX 5.0 ist die Session State Protection für neue Anwendungen standardmäßig aktiviert - die APEX-URLs (f?p=) sind dann zusätzlich mit einer Prüfsumme versehen, um URL-Manipulationen zu vermeiden. Solange APEX die URLs selbst generiert ("Link"-Spalten in Berichten), passiert das alles automatisch. Oft erzeugt man als Entwickler aber URLs in SQL-Abfragen oder PL/SQL-Code; wenn für die Zielseite die Session-State Protection aktiv, muss auch hier die Prüfsumme hinzugefügt werden. Die Funktion APEX_UTIL.PREPARE_URL erledigt das, wie das folgende Codebeispiel zeigt.
SQL> select apex_util.prepare_url('f?p=113:1:::::P1_NEW:1') from dual; APEX_UTIL.PREPARE_URL('F?P=113:1:::::P1_NEW:1') -------------------------------------------------------------------------------- f?p=113:1:::::P1_NEW:1&cs=1xBor6CnGPRf02gDSmLx4ALxAIq8 1 Zeile wurde ausgewählt.
Ist die Zielseite als modaler Dialog definiert, sieht das Ergebnis nochmals anders aus.
SQL> select apex_util.prepare_url('f?p=113:1:::::P1_NEW:1') from dual; APEX_UTIL.PREPARE_URL('F?P=113:1:::::P1_NEW:1') -------------------------------------------------------------------------------- javascript:apex.navigation.dialog('f?p=113:1:::::P1_NEW:1\u0026cs=1xBor6CnGPRf02 gDSmLx4ALxAIq8\u0026p_dialog_cs=GmDjNWKHEudebb_F0aEAyIIhDNg',{title:'Home',heigh t:'500',width:'720',maxWidth:'960',modal:true,dialog:null},'t-Dialog--standard', this);
Man kann ganz generell festhalten, dass man als APEX-Entwickler jede per SQL und PL/SQL generierte URL mit der Funktion APEX_UTIL.PREPARE_URL "nachbehandeln" sollte. Auch wenn die Session-State-Protection inaktiv und die Seite kein modaler Dialog ist - das kann sich immer mal ändern. Eine mit PREPARE_URL bearbeitete URL funktioniert dann immer noch.
Häufig liest man in Blogs von der Prozedur SLEEP im Paket DBMS_LOCK; man benötigt das manchmal beim Testen, wenn die eine APEX-Seite einfach nur einige Sekunden nichts tun soll. Nun ist es so, dass der "normale" Datenbankuser keine Rechte an DBMS_LOCK hat - und ein Datenbankadministrator schaltet die Rechte an diesem mächtigen Paket nur ungern frei. Aber mit APEX_UTIL.PAUSE (nicht dokumentiert) gibt es eine Alternative, die von jedem genutzt werden kann - allerdings haben die Entwickler ein Maximum eingebaut - mehr als 120 Sekunden pausiert die Funktion in keinem Fall.
Sehr nützlich sind die verschiedenen GET_SINCE-Funktionen. Damit können Sie den Unterschied zwischen einem gegebenen und dem aktuellen Datum in "lesbarer" Form ausgeben lassen. Dazu ein Beispiel.
select apex_util.get_timeframe(1.03216731) DIFFERENZ from dual; DIFFERENZ -------------------------- vor 31 Minuten select apex_util.get_since(sysdate - 42) DIFFERENZ from dual DIFFERENZ -------------------------- vor 6 Wochen select apex_util.get_since(sysdate + 10) DIFFERENZ from dual DIFFERENZ -------------------------- 10 Tage ab jetzt
Damit das Ergebnis auf Deutsch ausgeliefert wird, muss die APEX-Entwicklungsumgebung auf deutsch vorhanden sein - die deutsche Übersetzung muss also eingespielt werden. Analoges gilt für Französich, Spanisch und andere Sprachen. Übersetzungen werden demzufolge auch nur für die Sprachen unterstützt, in denen die APEX-Entwicklungsumgebung angeboten wird. In allen anderen Fällen wird der Text auf Englisch zurückgegeben. In Berichten müssen Sie das aber nicht programmieren: Vergeben Sie für Ihre Datumsspalten hier einfach die Formatmaske SINCE - dann macht APEX das automatisch!
Mit der Funktion FILESIZE_MASK (nicht dokumentiert) können Sie die Größe von Dateien oder LOB-Objekten, die standardmäßig in Bytes angegeben werden, in eine lesbare Form konvertieren. Die Ausgabe ist zur Darstellung gedacht - je kleiner die Datei ist, desto genauer wird die Anzeige.
SQL> select apex_util.filesize_mask(3249878713) from dual; APEX_UTIL.FILESIZE_MASK(3249878713) ----------------------------------- 3GB
Auch die in Berichten häufig verwendete Formatmaske PCT_GRAPH, mit der sich ein Zahlenwert als kleine Balkengrafik visualisieren lässt, steht als Funktion HTML_PCT_GRAPH_MASK in APEX_UTIL bereit. Solche Balkengrafiken können also nicht nur in Berichten, sondern überall in APEX-Anwendungen genutzt werden.
SQL> select apex_util.html_pct_graph_mask(33,200) from dual; APEX_UTIL.HTML_PCT_GRAPH_MASK(33,200) -------------------------------------------------------------------------------- <div style="width:200px;height:14px;background:#dddddd;border-top:1px solid #aaa aaa;border-left:1px solid #aaaaaa;border-bottom:1px solid #ffffff;border-right:1 px solid #ffffff;"><img src="/i/1px_trans.gif" width="66" height="14" border="0" style="background:#3f863f;"></div>
Und das sieht dann so aus:
Das zurückgegebene HTML-Fragment kann bspw. in einer Region vom Typ Dynamic PL/SQL Content, mit der Funktion HTP.x ausgegeben oder in einem APEX-Plugin genutzt werden.
Möchte längere man Integer-Zahlen (bspw. Primärschlüsselwerte) etwas kompakter darstellen, so bieten sich die Funktionen COMPRESS_INT und UNCOMPRESS_INT (nicht dokumentiert) an.
SQL> select apex_util.compress_int(8787321) from dual; APEX_UTIL.COMPRESS_INT(8787321) -------------------------------------------------------------- TFYZX 1 Zeile wurde ausgewählt. SQL> select apex_util.uncompress_int('TFYZX') from dual; APEX_UTIL.UNCOMPRESS_INT('TFYZX') --------------------------------- 8787321 1 Zeile wurde ausgewählt.
Wer die Nutzer seiner Applikation im APEX-Workspace selbst verwaltet, und in der Anwendung einen Dialog zum Ändern des Passworts bereitstellen möchte, findet ab APEX 5.0 mit APEX_UTIL.RESET_PASSWORD eine Funktion dafür. Sie nimmt den Usernamen sowie das alte und das neue Password entgegen. Wird die Prozedur von einem Datenbankuser mit der APEX_ADMINISTRATOR_ROLE ausgeführt, so muss das alte Password nicht mitgegeben werden.
Die Funktion GET_HASH ist nützlich, wenn Sie eine Prüfsumme über eine Werteliste selbst berechnen wollen. Das braucht man, wenn man, bspw. nach einer etwas längerlaufenden Prozedur, wissen möchte, ob Inhalte sich geändert haben. Anstelle eines Einzelvergleichs berechnen Sie die Prüfsummen und vergleichen diese - hat sich der Hash geändert, dann haben sich auch die Werte geändert.
SQL> select ename, job, apex_util.get_hash(wwv_flow_t_varchar2(empno, ename, job)) hash from emp where deptno = 10 ENAME JOB HASH ---------- --------- ------------------------------ CLARK MANAGER b_S9mZGgGiblhj82fBECvsQUfsw KING PRESIDENT cGpl-IYuefH_MPXD8KBv4E5LlDc MILLER CLERK trBGYYqA4Tjh7GmmPuyFUoohFDE 3 Zeilen ausgewählt. SQL> update emp set job='NIX' where ename='MILLER'; 1 Zeile wurde aktualisiert. SQL> select ename, job, apex_util.get_hash(wwv_flow_t_varchar2(empno, ename, job)) hash from emp where deptno = 10; ENAME JOB HASH ---------- --------- ------------------------------ CLARK MANAGER b_S9mZGgGiblhj82fBECvsQUfsw KING PRESIDENT cGpl-IYuefH_MPXD8KBv4E5LlDc MILLER NIX xZieMR69RbUIninGJhyMYPROkQw 3 Zeilen ausgewählt.
Mit den Funktionen GET_PRINT_DOCUMENT und DOWNLOAD_PRINT_DOCUMENT kann das APEX PDF-Printing mit PL/SQL (zum Beispiel für Scheduler Jobs) angesprochen werden. Voraussetzung ist die korrekte Einrichtung des Print Servers, also dem Oracle BI Publisher oder dem freien Apache FOP. GET_PRINT_DOCUMENT liegt in verschiedenen Überladungen vor - XML und Layout können direkt als CLOB übergeben werden, es können aber auch bereits hochgeladene Report Layouts angesprochen werden. Das Ergebnis (meist PDF) kommt in jedem Fall als BLOB zurück und kann von dort aus weiter verarbeitet werden - denkbar wäre der Versand per Email, das einfache Speichern in eine Tabelle oder das "Posten" an einen REST Service.
Wer mit dem Universal Theme arbeitet und (mit dem ThemeRoller) mehrere Theme Styles erstellt hat, kann mit der Funktion APEX_UTIL.SET_CURRENT_THEME_STYLE (nicht dokumentiert) den aktuellen Style programmatisch ändern. Dies wird übrigens auch im Administrationsbereich vieler Packaged Applications verwendet. Die folgende Abbildung zeigt den PL/SQL-Prozess Set Theme Style auf Seite 2 des Sample Geolocation Showcase.
Abbildung 1: PL/SQL Code zum Umstellen des aktuellen Theme Style einer Anwendung
Mit SET_SESSION_LIFETIME_SECONDS und SET_SESSION_MAX_IDLE_SECONDS kann der Session-Timeout dynamisch gesetzt werden. So wird es möglich, die Session-Timeouts basierend auf dem Nutzernamen oder der IP-Adresse (Intranet oder Internet) unterschiedlich zu gestalten. Diese Funktionen ruft man am besten in einem Application Process vom Typ PL/SQL zum Zeitpunkt On New Instance (new Session) auf.
Wer genau hinschaut, erkennt, dass APEX_UTIL mit JSON_FROM ARRAY oder JSON_FROM SQL auch einige Funktionen zum Erzeugen von JSON enthält. Diese Funktionen sind allerdings nur noch für die Rückwärtskompatibilität vorhanden und sollten nicht mehr verwendet werden - das in APEX 5.0 neu eingeführte PL/SQL Paket APEX_JSON leistet wesentlich bessere Dienste.
Auch für interaktive Berichte findet man Hilfsfunktionen in APEX_UTIL. IR_FILTER, IR_CLEAR, IR_DELETE_REPORT und IR_DELETE_SUBSCRIPTION sollte man jedoch nicht mehr einsetzen, da diese Funktionen (und einige mehr) im Paket APEX_IR enthalten sind.
Diese Übersicht über das PL/SQL Paket APEX_UTIL ist bei weitem nicht vollständig. Neben den hier betrachteten bietet es eine Reihe von weiteren Funktionen und Prozeduren an:
APEX_UTIL ist ein sehr mächtiges PL/SQL-Paket, dass jeder APEX-Entwickler kennen sollte. Bislang wurde es mit jeder neuen APEX-Version erweitert - beim Beschäftigen mit einem neuen APEX-Release gehört ein Blick auf APEX_UTIL quasi zum Pflichtprogramm. Schauen Sie also nochmals rein und probieren Sie es aus.