Everything Cloud? Das Event für die Oracle-Community

Everything Cloud? AOUG AK 23 – Die Plattform für die Oracle-Community

AOUG Anwenderkonferenz 2023!

Die AOUG Anwenderkonferenz findet auch dieses Jahr (12-13 Juni) im Austria Trend Hotel Savoyen Vienna statt. Sie ist die ideale Plattform, um sich über aktuelle Trends, Technologien und Lösungen im Oracle Technologie-Umfeld zu informieren.

Nur bei der Anwenderkonferenz:

  • Gibt es die einzigartige Möglichkeit aktuelle Herausforderungen mit einem Oracle-Partner zu besprechen
  • Gedanken und Ideen mit der Community auszutauschen
  • Neueste Technologien zu erforschen
  • Das Wissen anderer Anwender:innen nutzen

Das Publikum ist ein Querschnitt der Branche: Techniker, IT-Leiter, Oracle-Partner, Hersteller und Anbieter von Hard-und Software-Lösungen.

DIE Plattform für die Oracle Community

Die AOUG Anwenderkonferenz ist eine jährliche Veranstaltung, die sich an alle Oracle-Anwender:innen richtet. Hier kommen Expert:innen und Praktiker:innen zusammen, um ihre Erfahrungen und Ideen auszutauschen, neue Trends zu diskutieren und sich über die neuesten Entwicklungen bei Oracle-Produkten zu informieren. Sie bietet eine hervorragende Gelegenheit, sich mit Kolleg:innen aus der Branche zu vernetzen und wertvolle Kontakte zu knüpfen. Hier kann man von den Best Practices anderer Unternehmen lernen und wertvolle Einblicke in neue Technologien gewinnen.

Anwenderkonferenz 23 – “Everything Cloud? – Alles in die Cloud oder vielleicht doch nicht?

Das Spektrum der Vorträge reicht dieses Jahr von den Core Datenbank Themen, über On-Premise und Cloud Infrastrukturen, bis hin zu aktuellen Development Themen. Die Keynote von Dominic Giles, Master Product Manager für die Oracle Datenbank beschäftigt sich mit der Frage: “ Oracle Database: What’s next“. Weiters dürfen wir Mike Dietrich mit einem On-Hands Workshop (Upgrade and migrate to Oracle 23c) und zwei Vorträgen, sowie mehrere Oracle Anwendervorträge aus dem Cloud Umfeld, bei der Konferenz begrüßen.

Auch DBConcepts ist dieses Jahr mit zwei Vorträgen dabei. Roland Brandfellner hält einen Vortag über Automatisches Testen von APEX Applikationen und Andreas Schlögl ist mit dem Thema “cost based or expansion: a troubleshooting case study” dabei.

In 1 ½ Tagen mit insgesamt 36 Vorträgen kann man sich informieren, Gedanken, Herausforderungen und Ideen innerhalb der Community besprechen.

Am 12. Juni abends, werden alle angemeldeten Konferenzteilnehmer:innen zum mittlerweile traditionellen AOUG Bowling Abend mit anschließendem Abendessen in der Luftburg eingeladen.

Falls Sie noch nicht angemeldet sind –> geht es hier zur Anmeldung!

 

Oracle APEX: HTML Tags bei Substitution Syntax

Oracle APEX: HTML Tags bei Substitution Syntax

In Oracle APEX können Page und Application Items auf mehrere Arten verwendet werden. Mittels Bind-Syntax (:PXXX_ITEM) etwa in SQL oder PL/SQL Ausdrücken oder mittels Substitution-Syntax in Texten. Das bedeutet wir können beispielsweise in einem Page Process ein Hidden Page Item mit einem Wert versorgen, wie der Erfolgsmeldung eines Funktionsaufrufs, und dieses dann per Substitution-Syntax als Erfolgsmeldung ausgeben lassen (siehe folgender Screenshot).

Blogbeitrag:Oracle APEX

Wenn nun HTML-Tags in dem Rückgabewert sind, dann werden diese escaped und damit im Klartext angezeigt, was das folgende Verhalten verursacht.

Blogbeitrag:Oracle APEX

Leider gibt es keine offensichtliche deklarative Möglichkeit, dieses Verhalten zu unterbinden. HTML-Tags werden bei der Verwendung der Substitution Syntax aus Sicherheitsgründen (Stichwort XSS – Cross Site Scripting) immer escaped. Man kann allerdings in der Substition-Syntax Zusätze wie !RAW verwenden, wodurch sich das Escape Verhalten beeinflussen lässt. Der !RAW Zusatz zum Beispiel bewirkt, dass der Text as-is, also exakt so wie er gespeichert ist ausgegeben wird (VORSICHT: Bei Texten über die man keine 100%ige Kontrolle hat, weil sie z.B. durch User Eingaben entstehen ist das eine massive Sicherheitslücke in Bezug auf Cross Site Scripting!!!). Die Zusätze bestehen immer aus einem ! und einem vordefinierten Filterbegriff welche direkt VOR dem Punkt am Ende ergänzt, also z.B. &PXXX_ITEM!RAW. Wenn wir also die oben gezeigte Logik auf folgenden Aufruf anpassen

Blogbeitrag:Oracle APEX

 

Abgesehen von RAW gibt es lt. Doku noch folgende Optionen:

  • HTML: escaped HTML-reservierte Zeichen (ident zur Funktion HTML im Package APEX_ESCAPE)
  • ATTR: escaped Zeichen, welche für HTML-Attribute reserviert sind (ident zur Funktion HTML_ATTRIBUTE im Package APEX_ESCAPE)
  • JS: escaped Zeichen, die im Javascript Kontext reserviert sind (ident zur Funktion JS_LITERAL im Package APEX_ESCAPE)
  • STRIPHTML: entfernt alle HTML-Tags aus dem Wert und escaped alle HTML-reservierten Zeichen

Informationen dazu findet ihr hier.

Oracle SQL MATCH_RECOGNIZE

Ein sehr mächtiges und effizientes Konstrukt gibt es seit der Oracle Version 12C: MATCH_RECOGNIZE. Vereinfacht gesagt lassen sich mit MATCH_RECOGNIZE regular expressions auf Zeilen anwenden, um so bestimmte Muster zu erkennen. Beispielsweise lassen sich somit in der Finanzwelt bestimmte Aktienkurs-Verläufe erkennen. Dieser Blogbeitrag soll dazu dienen, sich diesem Thema anhand einfacher und fiktiver Beispiele langsam anzunähern.
Grundlegender Aufbau:

 

 

PATTERN_PARTITION_CLAUSE

Diese ist optional. Hier kann man die Daten sortieren (ORDER BY) und in Gruppen aufteilen (PARTITION BY). Um bei jeder Ausführung der Query konsistente Ergebnisse zu erhalten, sollte zumindest ein ORDER BY eingebaut werden.

PATTERN_MEASURES_CLAUSE

Dieser Abschnitt definiert die Spalten, welche im SELECT Teil ausgegeben werden. Es gibt einige inbuilt functions, welche hier verwendet werden können. Dazu später mehr.

PATTERN_DEF_DUR_CLAUSE

Hier wird die eigentliche magic definiert. In diesem Abschnitt werden die patterns definiert, welche über jede Zeile ausgeführt werden. Gibt es ein match, sprich wird genau dieses Muster identifiziert, wird die Zeile ausgegeben.

 

Wir schauen uns anhand einiger Beispiele die einzelnen Abschnitte näher an. Zu diesem Zwecke habe ich eine Tabelle mit Aktiendaten erstellt, welche wie folgt definiert ist (Das Skript kann am Ende des Artikels heruntergeladen werden):

 

 

 

 

 

 

Die Tabelle beinhaltet Aktienkurse eines fiktiven Unternehmens. Wir starten mit einer simplen Abfrage, anhand welcher ich auf die Struktur näher eingehen möchte: Alle Handelstage, an denen es einen oder mehrere Handelstage gibt, welche einen Startkurs größer als 17 haben:

 

 

 

 

 

 

 

 

 

Nachfolgend eine Erklärung der einzelnen Codeteile:

PARTITION BY: Unterteilt die Daten je nach Titel in einzelne Gruppen. In unserem Fall haben wir nur einen Titel in unseren Testdaten, wodurch es sowieso nur eine Gruppe gibt, nämlich die der „Datenbank AG“.

ORDER BY: Sortiert die Daten in der jeweiligen Gruppe je nach Handelstag aufsteigend.

MEASURES: Definiert, welche Spalten im result set zusätzlich ausgegeben werden. Hier sehen zwei inbuilt functions: CLASSIFIER() und MATCH_NUMBER(). CLASSIFIER () gibt an, welche pattern variable für diese Zeile gematched hat. MATCH_NUMBER () vergibt für gematchte Gruppe eine eigene Nummer, welche bei 1 startet und sich für jede neue Gruppe um 1 erhöht.

ALL ROWS PER MATCH: Definiert, dass bei aufeinanderfolgenden matches jede Zeile ausgegeben wird. Das pendant dazu wäre ONE ROW PER MATCH, welches auch der Default Wert ist. Hier wird dann nur die erste Zeile der jeweiligen Gruppe ausgegeben. In unserem Beispiel sehen wir, dass für Gruppe 4 jede einzelne Zeile ausgegeben wird; würden wir stattdessen ONE ROW PER MATCH verwenden, würden wir für Gruppe 4 nur die erste Zeile sehen. Das Ganze hat dann auch Auswirkungen auf die oberen drei Bereiche: Bei ALL ROWS PER MATCH, so wie wir es verwenden, könnte ich den den PARTITION BY, ORDER BY und MEASURES Bereich ganz weglassen, da sowieso alle Spalten im result set ausgegeben werden. Verwende ich hingegen ONE ROW PER MATCH, muss ich entweder Spalten im MEASURES Bereich angeben und/oder die ORDER BY / PARTITION BY Klausel angeben. Das Prinzip ist dem GROUP BY also sehr ähnlich.

PATTERN: Hier liste ich die einzelnen Variablen auf, welche im DEFINE Bereich definiert sind und auf die jeweilige Zeile zutreffen müssen. In unserem Beispiel ist das die Variable „start_1“ mit einem regular expression chracter „+“. Dies bedeutet, dass es einen oder mehrere Handelstage geben muss, an denen der Startkurs höher als 17 ist.

DEFINE: Hier werden die Variablen mit ihren conditions definiert, welche im PATTERN Bereich verwendet werden können.

 

 

 

 

 

 

 

Wie können wir das Ergebnis interpretieren?

Auf die Handelstage 01.12.2022, 03.12.2022 und 09.12.2022 folgen jeweils Tage, an denen der Startkurs kleiner als 17 ist. Der 13.12. ist der erste Tag einer Reihe von Handelstagen, wo der Startkurs höher als 17 ist.

Im nächsten Beispiel wollen wir alle Handelstage ausgeben, an denen exakt zwei Mal in Folge der Startkurs höher ist als der Schlusskurs am Vortag.

 

 

 

 

 

 

 

Hier sehen wir, dass das SQL im Prinzip ähnlich aufgebaut ist als im Beispiel eins. Im DEFINE Bereich ist unsere pattern Variable mit der dazugehörigen condition definiert. Diese besagt, dass der Startkurs der jeweiligen Zeile höher sein muss als der Schlusskurs der darüberliegenden Zeile. Hierzu verwende ich die Funktion PREV(), welche dann genau auf die darüberliegende Zeile verweist. An diesem Beispiel sehen wir auch, dass es Sinn macht, die Daten zu sortieren, da wir nicht immer wissen, in welcher Reihenfolge die Daten aus der Datenbank abgefragt werden (das Ergebnis ist also nicht deterministisch). Im PATTERN Bereich holen wir uns die Variable und wandeln diese nun mit „{2}“ zu einem regulären Ausdruck um. Dies bedeutet, dass wir zwei Handelstage in Folge haben wollen, auf welche die condition zutrifft. Zur Kontrolle definieren wir im MEASURES Bereich die Spalte „schlusskurs_vortag“. Da MATCH_RECOGNIZE wie eine INLINE VIEW zuerst ausgeführt wird, können wir diese im SELECT Teil verwenden und ausgeben.

 

 

 

 

 

Wie können wir das Ergebnis interpretieren?

Wir sehen nun immer zwei Handelstage in Folge, an denen der Startkurs höher ist als der Schlusskurs am Vortag.

Für das nächste Beispiel überlegen wir uns folgendes Muster: An Handelstag eins soll der Startkurs zwischen 16 und 17 sein, an Handelstag zwei soll der Startkurs größer als 17 sein, an Handelstag drei soll der Startkurs wieder zwischen 16 und 17 sein.

 

 

 

 

 

 

 

 

Dieses SQL enthält eine Erweiterung, nämlich AFTER MATCH SKIP TO LAST. Diese Syntax bedeutet, dass wenn es ein match gibt, die Suche nach einem neuen Match bei der letzten pattern Variable beginnt.

 

 

 

 

 

Wie können wir das Ergebnis interpretieren?

Wir sehen nun alle Handelstage, die genau unserem Muster entsprechen. Würden wir AFTER MATCH SKIP TO LAST weglassen, würden nur die ersten drei Handelstage im result set erscheinen, da die Suche erst am 22.12.2022 weitergehen würde.

In unserem letzten Beispiel wollen wir eine typische V-Formation erkennen. Dies bedeutet, dass der Aktienkurs zunächst einbricht und sich nach Erreichen der Talsohle wieder erholt.

 

 

 

 

 

 

 

 

 

 

 

 

Hier haben wir im DEFINE Bereich drei pattern Variablen definiert, wobei man die Definition der ersten Variable „strt“ auch weglassen könnte, da sie sowieso jede Zeile matched. Diese dient für uns lediglich als Einstiegspunkt in unsere Mustersuche.

 

 

 

 

 

 

 

Wie können wir das Ergebnis interpretieren?

Wir sehen in unserem result set anhand der Gruppennummer, welche Handelstage eine V-Formation bilden und somit zu einer Gruppe gehören. Das Ende der jeweiligen V-Formation ist zugelich der Beginn einer neuen Kursbewegung nach unten.

Wir haben uns nun anhand einiger Beispiel angesehen, was MATCH_RECOGNIZE ist und was für Abfragen wir damit durchführen können.

 

 

APEX: Calendar Custom Classes

APEX: Calendar Custom Classes

Der native Kalender in APEX bietet bereits eine Vielzahl an möglichen Klassen um Termine passend anzuzeigen. Die Inline-Hilfe in APEX bietet folgende Liste von 14 Klassen an

 

Wie der letzte Satz andeutet, ist auch das Hinzufügen von eigenen zusätzlichen Klassen möglich.
Da die Dokumentation hier keine klaren Hinweise bietet, soll hier kurz aufgelistet werden, wie diese Klassen anzulegen sind.

Die beste Orientierung bietet hier eine der Klassen, welche von APEX bereitgestellt werden:
.fc .fc-event.apex-cal-black {
   background-color:#303030;
   border-color:#303030;
   color:#fff
}

Der Klassenname, welche in APEX in einer Spalte übergeben wird muss also in auf folgende Weise in einem CSS-File oder Inline auf der Seite eingebaut werden:

.fc .fc-event.[CUSTOM-CLASS_NAME] {
   CSS-Eigenschaften
}

Bei den CSS-Eigenschaften kann man sich ebenfalls an den Default-Klassen orientieren, die drei relevantesten Eigenschaften sind offensichtlicherweise Background, (Foreground-/Font-)Color und Border. Ein paar weitere Ideen, welche anderen CSS-Eigenschaften hier noch sinnvoll einsetzbar sind wären z.B.:

  • Alles was die Schrift betrifft wie
    *Font-weight
    *Font-family
    *Font-decoration

 

  •  Weitere Spielereien wie
    *Border-radius
    *Border-style

Als kleine Unterstützung bei der Recherche für weitere Styling Möglichkeiten:
Die Elemente selbst sind Links (<a>), welche über eine andere Klasse (fc-h-event) als Block-Element dargestellt wird, Klassen welche sich also lediglich auf Inline-Elemente auswirken werden keinerlei Effekt haben.

 

JavaScript in Quick Picks in Oracle APEX

Wer diesen Blog schon öfter besucht hat, wird das Feature der Quick Picks bereits kennen. Diese dienen dazu, Usereingaben zu optimieren, indem mittels eines Klicks auf ein entsprechendes Quick Pick direkt ein Wert in ein Feld eingetragen wird.

 Quickpicks_dynamic_01

Ein Nachteil der Quick Picks ist aber, dass standardmäßig nur vorgefertigte (sprich beim Rendern der Seite bereits definierte) Werte eingesetzt werden können. Auch im letzten Blogeintrag über Quick Picks, in dem wir die Anzahl der Quick Picks dynamisch variierbar gemacht haben, haben wir dieses Problem.
Würde man mit den Quick Picks nun beispielsweise immer den aktuellen Wert eines anderen Feldes übernehmen wollen, so ist dies nicht einfach so möglich.
Angenommen es wäre zum Beispiel gewünscht, den Wert des Feldes „Betrag“ (in der oben angeführten Abbildung) mittels eines Klicks auf einen Quick Pick in das Feld „Betrag bezahlt“ zu übernehmen.
Mit den bisher bekannten Methoden kann man zwar das Feld als Quick Pick angeben (siehe folgende Abbildung), allerdings wurde der zu setzende Betrag bereits beim Rendern der Seite erstellt. War dieser davor „1234“ und wurde nachträglich vom Endbenutzer auf „12345678“ geändert, so wäre auch der Quick Pick immer noch mit dem Wert „1234“ definiert und zwar solange, bis die Region (oder die gesamte Seite) erneut geladen werden.

Quickpick_Eigenschaften_marked

Wie Leser des letzten Beitrages über Quick Picks bereits wissen, arbeiten diese selbst mit JavaScript-Code. Es ist also möglich, beim Erstellen eigener Quick Picks beliebigen JavaScript-Code auszuführen. Anstatt also einen Quick Pick zu generieren, der mittels JavaScript ein Item mit einem vordefinierten Wert befüllt, kann dieser Code auch direkt den Wert zum aktuellen Zeitpunkt aus einem anderen Item auslesen.
Der HTML-Code für das entsprechende Quick Pick würde in diesem Beispiel also wie folgt aussehen:

<span class="apex-quick-picks"><a href="javascript:$s('#CURRENT_ITEM_NAME#',$v('P1_BETRAG'), 'JS-QP')">JS-QP</a></span>

„$s“ ist die in Oracle APEX verwendete JavaScript-Methode zum setzten von Items, während „$v“ die Methode zum Auslesen eines Wertes eines beliebigen Items verwendet werden kann. In diesem Beispiel wird also der Wert aus Item „P1_BETRAG“ in das Item „#CURRENT_ITEM_NAME#“ gesetzt.
„#CURRENT_ITEM_NAME#“ ist nur ein Platzhalter für das gewünschte Item. Wenn dieser HTML-Code nun im Post-Text eines gewünschten Items eingetragen wird (in diesem Fall im Item „P1_BETRAG_BEZAHLT“), wird dieser durch den korrekten Itemnamen ersetzt.
Quickpicks advanced

Um das Layout der Quick Picks korrekt darzustellen muss noch im selben Eigenschaftsfenster im Bereich „Quick Picks“ das Flag „Show Quick Picks“ auf „Yes“ gesetzt werden.

Nun wird der entsprechende HTML-Code mittels CSS in die bekannte Formatierung und Positionierung gebracht. Bei Klick auf den Quick Pick wird immer der aktuelle Wert des Feldes „Betrag“ übernommen, ganz egal, wann dieser geändert wurde.

Dynamic Quick Picks

Um die Quick Picks nun wieder dynamischer zu erstellen, kann man natürlich auf „Shortcuts“ in den Shared Components“ zurückgreifen und die Quick Picks über PL/SQL dynamisch erstellen.

Oracle APEX

Wenn man einen neuen Shortcut erstellt muss als Type „PL/SQL Function Body“ ausgewählt und im Feld „Shortcut“ ein entsprechender PL/SL-Code geschrieben werden.

declare
  v_qp varchar2(32767);
begin
  v_qp := apex_string.format(q'{%1}', 'P1_BETRAG', 'JS-QP');
  return apex_string.format('%0', v_qp);
end;

Create Shortcut

Danach muss der Name des Shortcuts noch im Eigenschaftsfenster des gewünschten Items unter „Post Text“ mittels Anführungszeichen angegeben werden, um APEX mitzuteilen, dass hier der aus dem Shortcut generierte HTML-Code ausgegeben werden soll.

Advanced Post

Man erhält dasselbe gewünschte Ergebnis, es ist aber bei der Umsetzung mit Shortcuts möglich, beliebig viele Quick Picks mit unterschiedlicher Funktion zu erstellen, ohne ständig das Item anpassen zu müssen.

Dynamic Quick Picks

Generell ist es so nun möglich, jeden erdenklichen JavaScript-Code in Quick Picks zu verpacken, es muss nicht einmal eine Quick Pick-Funktionalität dahinterstehen.

Dynamische Quick Picks in Oracle APEX

Wer seine Web-Applikation in Oracle APEX auf Usability optimiert, wird wahrscheinlich schon einmal über das Feature der sogenannten Quick Picks gestoßen sein.
Unter Quick Picks versteht man vordefinierte Usereingaben, die ein Endanwender mittels Mausklick auslösen kann und das entsprechende Feld mit einem vordefinierten Wert befüllt, ohne diesen extra eintippen zu müssen.

 Quickpicks_dynamic_01

Konfiguriert werden diese Quick Picks über das Eigenschafts-Fenster des jeweiligen Page-Items.

Quickpick Eigenschaften

Quick Picks können also die Eingabe des Endbenutzers ein wenig beschleunigen, allerdings sieht es auf den ersten Blick so aus, als ob nur statische Werte vergeben werden können. Dies ist nicht ganz korrekt, denn mit der üblichen Notation in Oracle APEX können auch Werte aus beliebigen Items angesprochen werden (Kaufmännisches Und + Item-Name + Punkt), dies sieht zum Beispiel wie folgt aus:

Quickpick_Eigenschaften_marked

Diese Art der Definition führt dazu, dass der Wert aus dem jeweiligen Item als Quick Pick gesetzt wird.

Quickpick dynamic zweiter wert

Einzige Bedingung hierfür ist allerdings, dass der Wert bereits beim Aufbau der Seite gesetzt wird, denn der Quick Pick wird vorab mit dem Wert des entsprechenden referenzierten Items erstellt und ändert sich nicht dynamisch mit. Auch ist die Anzahl der Quick Picks vorab auf die statisch definierten Labels und Values beschränkt, es ist also auf diese Art beispielsweise nicht möglich dynamisch einmal zwei und einmal drei Quick Picks unter einem Feld anzuzeigen.

Um nun beliebig viele Werte dynamisch als Quick Picks anzuzeigen, muss man etwas tiefer in die Trickkiste greifen. Um dies zu ermöglichen, bedient man sich dem Wissen, dass die Quick Picks über eine CSS-Klasse positioniert und gestaltet werden und der entsprechende HTML-Code einfach an das gewünschte Item angehängt wird. Mit diesem Wissen ist es nun möglich, die Quick Picks eigenständig zu erstellen.

Für die hier verwendete Testseite wurde eine simple Tabelle erstellt, welche beliebig viele Werte und Bezeichnungen für Quick Picks enthält. Die Tabelle wurde QP_TAB genannt und beinhaltet aktuell folgende Werte:

Werte Tabelle Quickpick

Ziel soll es nun sein für jede Zeile dieser Tabelle einen separaten Quick Pick für ein Item zu generieren. (Allgemein ist es egal, woher die Daten für die Quick Picks stammen, einzige Bedingung ist, dass man diese mittels PL/SQL ermitteln kann.)

Um die Anzahl der Quick Picks nun auch dynamisch umzusetzen zu können, benötigt man ein weiteres Oracle APEX Feature: die sogenannten „Shortcuts“. Diese befinden sich in den Shared Components wie im folgenden Screenshot ersichtlich ist.

Oracle Apex

Klickt man nun auf Shortcuts, kann man auf der Folgeseite ein neues Objekt erstellen.

Create Shortcut

Wichtig hierbei ist, dass als „Type“ „PL/SQL Function Body” ausgewählt wird und dann im Feld „Shortcut“ ein entsprechender Code geschrieben wird, der die gewünschten Daten in eine entsprechende HTML-Struktur bringt. Der Code für das hier verwendete Beispiel sieht nun folgendermaßen aus:

declare
  v_qp varchar2(32767);
begin
  for rec_qp in (select bezeichnung, wert from qp_tab) loop
    if v_qp is not null then
      v_qp := v_qp || ', ';
    end if;
      v_qp := v_qp || apex_string.format(q'{%1}', rec_qp.wert, rec_qp.bezeichnung);
  end loop;
  return apex_string.format('%0', v_qp);
end;

Wichtig ist hier eigentlich nur, dass die einzelnen Links (die resultierenden Quick Picks) in einem Span-Container der CSS-Klasse „apex-quick-picks“ liegen.

Der eigentliche Link ist dann Javascript-Code, welcher mit „$s“ das Setzen eines Items mit der entsprechenden Angabe des Wertes umsetzt. (Der Platzhalter „#CURRENT_ITEM_NAME#“ wird im folgenden Schritt aufgelöst.)

Wieder zurück auf der Seite kann man im Eigenschaftsfenster des gewünschten Items nun im „Advanced“-Bereich im „Post Text“-Feld den Namen des zuvor erstellten Shortcuts in Anführungszeichen eintragen. Dadurch wird der durch den Shortcut generierte HTML-Text nach dem Item im Quellcode eingefügt. Oracle APEX weiß nun auch, dass der Platzhalter „#CURRENT_ITEM_NAME#“ mit dem Namen des Items ersetzt wird (diese Funktionalität des Ansprechens des Item-Namens ist über den Post Text allgemein möglich).

Bei den Quick Picks werden diesmal keine statischen Werte angegeben (da der Code für die Quick Picks ja bereits durch den Shortcut an das Item angehängt wurde). Um aber die Positionierung des durch den Shortcut erstellten HTML-Code auch korrekt anzuzeigen, wird das Flag „Show Quick Picks“ auf „Yes“ gesetzt.

So erhält man die dynamisch erstellten Quick Picks:

Würde man in Tabelle QP_TAB nun einen weiteren Eintrag erfassen, würde dieser auch entsprechend auf der APEX-Seite als weiterer Quick Pick erscheinen, ohne dass man dafür das Item anpassen müsste.

Die einzige Limitierung der Anzahl der Quick Picks entsteht durch die Zeichenanzahl des VARCHAR2 im angelegten Shortcut.

Oracle APEX: Custom error messages mit APEX_ERROR

Die Haupt-Anwendungsfälle im Zusammenhang mit der Ausgabe von Fehlermeldungen in APEX-Applikationen dürften die meisten Entwickler nach einer verhältnismäßig kurzen Einarbeitungszeit kennen:

  • Entweder werden Validations ausgeführt, schlagen an und sollen den Endanwender darüber informieren, dass eine entsprechende Bedingung verletzt wurde.
  • Oder während der Ausführung eines Page Processes tritt ein Fehler auf, über den der Endanwender in Kenntnis gesetzt werden soll.

 

Wollten Entwickler in älteren APEX-Versionen eigene Fehlermeldungen ausgeben, geschah dies häufig über die Nutzung der Variable apex_application.g_print_success_message unter zusätzlicher Angabe von Style-Informationen:

apex_application.g_print_success_message := '<span style="color:red;">Achtung: Bei der Aufbereitung der Datei-Inhalte sind insgesamt 23 Fehler aufgetreten, die Daten konnten daher nicht importiert werden.</span>';

Mittlerweile jedoch stellt das APEX-eigene Package APEX_ERROR viel elegantere Möglichkeiten für den Umgang mit eigenen Fehlermeldungen zur Verfügung:

 

Die überladene Prozedur ADD_ERROR bietet verschiedene Varianten an um eigene Fehlermeldungen auf den Error Stack zu legen, die Darstellung übernimmt dabei APEX, so dass die Notwendigkeit zu expliziten Style-Angaben entfallen kann. Ein entsprechender Prozess im Page Processing könnte damit beispielsweise so aussehen:

DECLARE
 -- define some variables
 v_status NUMBER;
 ...
BEGIN
-- do some fancy stuff
...
 v_status := my_schema.my_package.my_function(p_var => '[MY_VALUE]');

IF v_status > 0 THEN
apex_error.add_error(p_display_location => apex_error.c_inline_in_notification,
p_message => 'Achtung: Es ist ein Fehler aufgetreten, so dass…');
 END IF;
EXCEPTION
WHEN OTHERS THEN
-- do some error logging
...
RAISE;
END;

Das eigentliche Potential entfaltet dieses Package aber dann, wenn man auf auftretende Fehler speziell reagieren will, beispielsweise weil gewisse Fehler häufiger auftreten und die Seite speziell für bestimmte Fehlerfälle bestimmte zusätzliche Button-Aktionen oder Auswertungen zur Verfügung stellen soll. Dafür kann man statt der Variable v_status im obigen Code ein Hidden Item P12_STATUS setzen und nach dem Ausführen der Prozesse über einen Branch die Seite neu aufrufen und das Item dabei übergeben. Der Aufruf von APEX_ERROR geschieht nun nicht mehr im Page Processing sondern im Page Rendering und nur dann wenn P12_STATUS einen Wert > 0 enthält

IF :P12_STATUS = '1' THEN
apex_error.add_error(p_display_location => apex_error.c_inline_in_notification,
 p_message => 'Achtung: Das Hochladen der Daten war nicht erfolgreich, so dass…');
ELSIF :P12_STATUS = '2' THEN
 apex_error.add_error(p_display_location => apex_error.c_inline_in_notification,
p_message => 'Achtung: Das Parsen der Daten ist fehlgeschlagen, so dass…');
ELSE
...
END IF;

Zusätzlich besteht nun natürlich die Möglichkeit status-abhängig zusätzliche Items etc. anzubieten – zum Beispiel ein Button um die Daten erneut hochzuladen für P12_STATUS = ‚1‘ oder ein Report mit den Parsing-Fehlern für P12_STATUS = ‚2‘.

Oracle APEX Ein Stolperstein im Upgrade auf Version 5.1

Oracle APEX: Ein Stolperstein im Upgrade auf Version 5.1

Beim Upgrade von Oracle APEX Applikationen auf die schon etwas ältere Version 5.1 wird gerne ein kleines Detail übersehen – vor allem auch deswegen weil die Auswirkungen nur dann sichtbar sind, wenn man alte und neue Version wirklich genau testet und vergleicht oder alternativ die Release Notes aufmerksam liest:

Im Abschnitt 1.4.4 der Release Notes befindet sich der Absatz:

End Date Displayed Inclusive — In release 5.0, the CSS calendar considered the end date of an all-day event as exclusive (similar to the jQuery FullCalendar Plugin).
In release 5.1, the end date is inclusive like all other Oracle Application Express components.

Das bedeutet, dass für die korrekte Darstellung eines mehrtägigen Termins in einer Calendar-Region in Version 5.0 folgende Abfrage notwendig war:

SELECT 'SomeCSSClass'         AS cal_class,
       t1.appointment_start   AS cal_date_from,
       t1.appointment_end + 1 AS cal_date_upto,
       'SomeLabel'            AS cal_label,
       'SomeLink'             AS cal_link
FROM   appointment_table t1

Mit dem Upgrade auf 5.1 entfällt die Notwendigkeit für das un-intuitive „+ 1“ und die Abfrage reduziert sich auf:

SELECT 'SomeCSSClass'         AS cal_class,
       t1.appointment_start   AS cal_date_from,
       t1.appointment_end     AS cal_date_upto,
       'SomeLabel'            AS cal_label,
       'SomeLink'             AS cal_link
FROM   appointment_table t1

Vor allem beim Überspringen von Applikations-Versionen kommt es dann mitunter vor, dass die Zwischenschritte ein wenig stiefmütterlich behandelt werden und sich Probleme wie dieses einschleichen.

Daher bietet es sich bei Oracle APEX Upgrades immer an, noch einmal einen kurzen Blick in die entsprechenden Release Notes zu werfen.

OAUTH2 Authentifizierung für ORDS REST Services

OAUTH2 Authentifizierung für ORDS REST Services

Oracle bietet über den ORDS (Oracle REST Data Services) die Möglichkeit REST Services in der Datenbank zu erstellen.

Um diese auch entsprechend zu schützen kann man den Zugriff darauf mittels einer OAUTH2 Authentifizierung einschränken. Die Authentifizierung und Autorisierung arbeiten mit Benutzern, Rollen und Berechtigungen (Privileges).

Der Aufbau ist dabei wie folgt:

Authentifizierung und Autorisierung

Abbildung 1: Rollen

Das bedeutet, ein Benutzer kann mehrere Rollen besitzen, welche ihm wiederum jeweils mehrere Berechtigungen zuweisen. Eine Berechtigung wiederum gilt für 1 oder mehrere Module (=Services) und darin eingebettete Ressourcen. Die Einschränkung auf Ressourcen wird allerdings nicht über eine Verknüpfung festgelegt, sondern einen Textfilter, welcher mit Regular Expressions arbeitet.

Die einzelnen Elemente können folgendermaßen angelegt werden:

Rolle

  • Per PL/SQL mit einem Aufruf der Prozedur create_role im Package ords.
  • Über das entsprechende REST Service Interface in APEX

Abbildung 1: Rollen

Berechtigung

  • Per PL/SQL mit einem Aufruf der Prozedur create_privilege im Package ords. Hier wird gibt es zwei Aufrufe, entweder wird nur 1 Rolle übergeben oder ein Array an Rollen.
  • Über das entsprechende REST Service Interface in APEX

Abbildung 2: Berechtigungen

Abbildung 3: Berechtigungen Detail

Berechtigungen Details

Zuweisungen der Berechtigung zum Modul werden mittels der Prozedur set_module_privilege im Package ords erstellt oder wie in Abbildung 3 zu sehen über das REST Service Interface in APEX.

Zuweisungen der Berechtigung zur jeweiligen Ressource werden mittels der Prozedur create_privilege_mapping im Package ords erstellt oder wie in Abbildung 3 zu sehen über das REST Service Interface in APEX.

 

ACHTUNG!!!

Es scheint hier einen Bug zu geben wenn ein Universal-Model verwendet wird – sprich „/*“.

Dieser wird dann nicht modulmäßig eingeschränkt und wirkt sich dann auch auf alle APEX Applikationen aus, welche dann potentiell nicht mehr aufgerufen werden können.

Benutzer

Benutzer können AUSSCHLIESSLICH per PL/SQL mittels der Prozedur create_client im Package oauth erzeugt werden!

Die Rolle muss dann ebenfalls per PL/SQL zugewiesen werden mittels der Funktion grant_client_role im Package oauth.

Die Tatsache, dass die Benutzer nicht über das Interface angelegt werden können, muss unbedingt berücksichtig werden.

Einsatz der angelegten Objekte

Um das Service nun zu nutzen muss eine Authentifizierung durchgeführt werden. Beim Anlegen des OAUTH Clients wird ein Benutzername übergeben. Mit diesem können dann die benötigten Benutzerdaten aus der Tabelle user_ords_clients geholt werden z.B. mit folgendem Statement:

SELECT client_id, client_secret FROM user_ords_clients WHERE name = 'NAME_DES_USERS';

Die client_id und das client_secret werden dann wiederum benötigt um den OAUTH Zugriffstoken zu erhalten. Dazu wird standardmäßig ein eigenes Webservice bereitgestellt, welches mit einer speziellen URL erreicht werden kann. Die URL setzt sich aus dem Basispfad zusammen und wird am Ende mit oauth/token ergänzt – z.B. https://www.myserver.at/ords/apex/oauth/token

Dieser URL werden client_id und client_secret als HTTP-Authentifizierung mitgegeben (User:Passwort), im Payload wird „grant_type=client_credentials“ mitgegeben. Als Antwort erhält man ein JSON mit folgenden Attributen:

  • access_token: der Token selbst
  • token_type: ist immer „bearer“ – muss mit angegeben werden mit dem Token
  • expires_in: Gültigkeitsdauer in Sekunden – standardmäßig 1 Stunde (dieser Wert kann nicht individuell je Service angepasst werden, nur global für alle Services dieser ORDS Instanz)

Beim Aufruf der eigentlichen Services welches durch die OAUTH Authentifizierung geschützt wird, wird dann im Header des Requests folgendes mitgegeben:

„Authorization: Bearer [OAUTH Token]“

OAUTH Token ist der vorhin mittels Webservice generierte Token. Falls der Token nicht mehr gilt (sprich die Gültigkeit abgelaufen ist oder der Token schlicht falsch ist) – gibt das Service einen HTTP 403 Forbidden Fehler zurück.

Oracle APEX: WWV_FLOW_FND_USER_FK-Verletzung beim Importieren von Workspaces

Ich bin jüngst im Rahmen eines APEX-Projekts auf ein Problem gestoßen, für das ich im Oracle Support Portal keinen einzigen Eintrag finden konnte – nach einigem Überlegen und Testen aber auf eine relativ einfache Lösung gekommen bin.

Ausgangslage

Das erste Arbeitspaket im Rahmen des Projektes ist fertig entwickelt und soll dem Kunden zur Abnahme in der dafür von diesem bereitgestellten Test-Umgebung deployed werden. Da es in dieser Umgebung keine APEX-Entwicklungs-Umgebung gibt, sind die Sourcen per SQLplus, PL/SQL-Developer oder mit ähnlichen Tools zu deployen.

Nach der fehlerfreien Erstellung von Schemas, Tabellen, Packages etc. folgt der Import des APEX-Workspaces; das aus APEX exportierte Script hierfür schaut sinngemäß wie folgt aus:

set define off verify off feedback off
whenever sqlerror exit sql.sqlcode rollback
--------------------------------------------------------------------------------
--
-- ORACLE Application Express (APEX) export file
--
-- You should run the script connected to SQL*Plus as the Oracle user
-- APEX_050100 or as the owner (parsing schema) of the application.
--
-- NOTE: Calls to apex_application_install override the defaults below.
--
--------------------------------------------------------------------------------
begin
wwv_flow_api.import_begin (
p_version_yyyy_mm_dd=>'[DATE]'
,p_default_workspace_id=>276...332
);
end;
/
prompt WORKSPACE 276...332
--
-- Workspace, User Group, User, and Team Development Export:
-- Date and Time: [DATE_AN_TIME]
-- Exported By: ADMIN
-- Export Type: Workspace Export
-- Version: 5.1.2.00.09
-- Instance ID: 714...698
--
-- Import:
-- Using Instance Administration / Manage Workspaces
-- or
-- Using SQL*Plus as the Oracle user APEX_050100

begin
wwv_flow_api.set_security_group_id(p_security_group_id=>276...332);
end;
/
----------------
-- W O R K S P A C E
-- Creating a workspace will not create database schemas or objects.
-- This API creates only the meta data for this APEX workspace
prompt Creating workspace LEHM...
begin
wwv_flow_fnd_user_api.create_company (
p_id => 276...354
,p_provisioning_company_id => 276...332
,p_short_name => '[SHORT_NAME]'
,p_display_name => '[DISPLAY_NAME]'
,p_first_schema_provisioned => '[SCHEMA_NAME]'
,p_company_schemas => '[SCHEMA_NAMES]'
,p_account_status => 'ASSIGNED'
,p_allow_plsql_editing => 'Y'
,p_allow_app_building_yn => 'Y'
,p_allow_packaged_app_ins_yn => 'Y'
,p_allow_sql_workshop_yn => 'Y'
,p_allow_websheet_dev_yn => 'Y'
,p_allow_team_development_yn => 'Y'
,p_allow_to_be_purged_yn => 'Y'
,p_allow_restful_services_yn => 'Y'
,p_source_identifier => '[IDENTIFIER]'
,p_path_prefix => '[PATH_PREFIX]'
,p_files_version => 1
);
end;
/
----------------
-- G R O U P S
--
prompt Creating Groups...
begin
wwv_flow_api.create_user_groups (
p_id => 927...937,
p_GROUP_NAME => 'OAuth2 Client Developer',
p_SECURITY_GROUP_ID => [ID],
p_GROUP_DESC => 'Users authorized to register OAuth2 Client Applications');
end;
/
begin
wwv_flow_api.create_user_groups (
p_id => 927...936,
p_GROUP_NAME => 'RESTful Services',
p_SECURITY_GROUP_ID => [ID],
p_GROUP_DESC => 'Users authorized to use RESTful Services with this workspace');
end;
/
begin
wwv_flow_api.create_user_groups (
p_id => 927...912,
p_GROUP_NAME => 'SQL Developer',
p_SECURITY_GROUP_ID => [ID],
p_GROUP_DESC => 'Users authorized to use SQL Developer with this workspace');
end;
/
prompt Creating group grants...
----------------
-- U S E R S
-- User repository for use with APEX cookie-based authentication.
--
prompt Creating Users...
begin
wwv_flow_fnd_user_api.create_fnd_user (
p_user_id => '276...332',
p_user_name => 'ADMIN',
p_first_name => '',
p_last_name => '',
p_description => '',
p_email_address => '[MAIL_ADDRESS]',
p_web_password => '[PASSWORD_STRING]',
p_web_password_format => '[PASSWORD_FORMAT]',
p_group_ids => '',
p_developer_privs => 'ADMIN:CREATE:DATA_LOADER:EDIT:HELP:MONITOR:SQL',
p_default_schema => '[SCHEMA_NAME]',
p_account_locked => 'N',
p_account_expiry => to_date('[DATE_AND_TIME]','YYYYMMDDHH24MI'),
p_failed_access_attempts => 0,
p_change_password_on_first_use => 'Y',
p_first_password_use_occurred => 'Y',
p_allow_app_building_yn => 'Y',
p_allow_sql_workshop_yn => 'Y',
p_allow_websheet_dev_yn => 'Y',
p_allow_team_development_yn => 'Y',
p_allow_access_to_schemas => '');
end;
/
[MORE_USER_CREATION_STEPS]
prompt Check Compatibility...
begin
-- This date identifies the minimum version required to import this file.
wwv_flow_team_api.check_version(p_version_yyyy_mm_dd=>'[DATE]');
end;
/

begin wwv_flow.g_import_in_progress := true; wwv_flow.g_user := USER; end; 
/
[SOME_EMPTY_ANONYMOUS_BLOCKS]
begin
wwv_flow_api.import_end(p_auto_install_sup_obj => nvl(wwv_flow_application_install.get_auto_install_sup_obj, false));
commit;
end;
/
set verify on feedback on define on
prompt ...done

Das Problem

An der Stelle, wo der erste User-Account erstellt werden soll, schlägt die Script-Ausführung dann fehl:

ORA-02291: Integritäts-Constraint (APEX_050100.WWV_FLOW_FND_USER_FK) verletzt – übergeordneter Schlüssel nicht gefunden

ORA-06512: in „APEX_050100.WWV_FLOW_FND_USER_INT“, Zeile 2010

ORA-06512: in „APEX_050100.WWV_FLOW_FND_USER_API“, Zeile 328

ORA-06512: in Zeile 2

 

Eine Suche im Oracle Support Portal nach WWV_FLOW_FND_USER_FK liefert (heute am 14.01.2020) exakt Null Ergebnisse – es ist also Eigeninitiative gefragt.
Eine Web-Suche offenbart recht schnell, dass der genannte Foreign Key ein Verweis auf den Workspace ist – was einigermaßen überraschend ist, da der Abschnitt der Workspace-Erstellung aus dem Script mit PL/SQL procedure successfully completed erfolgreich durchgelaufen war.

 

Die Lösung

Auf die Lösung bin ich erst gestoßen als ich im PL/SQL-Developer jeden Schritt einzeln ausgeführt habe. Bereits nach dem ersten Block erkannte der Developer eine aktive offene Transaktion – dies ist an entsprechenden Icons zu erkennen.

Die nachfolgenden Schritte verursachten keinerlei Probleme – bis die erste User Group importiert werden sollte. Hier fiel dann auf, dass dem Developer zufolge die Transaktion verschwand. Und der nachfolgende Import von User Accounts schlug daraufhin mit der bereits beschriebenen Fehlermeldung fehl.

Als ich die User-Group-Erstellung ausließ, funktionierte auch der User-Account-Import wieder problemlos. 😊

Da die User Groups im Rahmen des genannten Projekts nicht weiter verwendet werden, ist die Lösung ausreichend: das Entfernen der Erstellung der User Groups aus dem Workspace-Erstellungs-Scripts.

Die Ursache

Die genaue Ursache lässt sich ohne den entschlüsselten Code des Packages WWV_FLOW_API nur schwer ermitteln – höchstwahrscheinlich fehlt in der Procedure CREATE_USER_GROUPS aber ein RAISE um einen auftretenden Fehler weiterzureichen…