11 KiB
Plan: PCK_NET_STORAGE
Stand: 2026-04-08 Status: Implementiert
Ziel
PL/SQL Package PCK_NET_STORAGE kapselt alle Zugriffe auf OCI Object Storage über REST API.
Wird in Oracle APEX für einen Dateimanager genutzt (Browse, Upload, Download, Rename, Move, Delete).
Dateien
tables/
lg_app_log.tab
types/
t_net_storage_row.typ ← Schema-Level Type (siehe Hinweis)
t_net_storage_tab.typ ← Nested Table von t_net_storage_row
packages/
pck_log.pkh / pck_log.pkb
pck_net_storage.pkh / pck_net_storage.pkb
pck_auto_import.pkh / pck_auto_import.pkb
Hinweis Schema-Level Types: TABLE() in SQL erfordert in Oracle schema-level Types.
t_net_storage_row / t_net_storage_tab werden ausschließlich intern für den
sys_refcursor von f_list_objects benötigt — keine Auswirkung auf die öffentliche API.
Authentifizierung
APEX Web Credentials (Typ: Oracle Cloud Infrastructure)
Static ID: OCI_OBJ_STORE_CRED
Wird in APEX Admin UI einmalig angelegt mit: OCI User OCID, Tenancy OCID, Fingerprint, Private Key (PEM).
In PL/SQL referenziert via p_credential_static_id => c_credential_id in apex_web_service.make_rest_request.
Grund: DBMS_CLOUD ist auf dieser DB nicht installiert. APEX übernimmt OCI HTTP Signature V1 (SHA-256) automatisch.
Konfigurationsparameter
Alle zur Laufzeit via pck_system.f_get_par_wert_by_programmid:
| Parameter-ID | Inhalt |
|---|---|
NET_STORAGE_BUCKET |
Bucket-Name |
NET_STORAGE_NAMESPACE |
OCI Object Storage Namespace |
NET_STORAGE_REGION |
z.B. eu-frankfurt-1 |
NET_STORAGE_TENANT_ID |
Erlaubter Root-Prefix, z.B. mandant_42/ |
NET_STORAGE_APEX_CREDENTIAL_ID |
Credential Static ID für apex_web_service calls, z.B. 'OCI_OBJ_STORE_CRED' |
Öffentliche Schnittstelle (Package Spec)
Record Type t_object_meta
| Feld | Typ |
|---|---|
object_name |
varchar2(1024) |
object_size |
number |
last_modified |
date |
content_type |
varchar2(256) |
etag |
varchar2(256) |
Öffentliche Prozeduren und Funktionen
Namenskonvention: Prozeduren p_-Präfix, Funktionen f_-Präfix. Parameter immer i_-Präfix.
| Name | Typ | Signatur | Rückgabe | Anmerkung |
|---|---|---|---|---|
f_list_objects |
Funktion | (i_prefix, i_delimiter, i_start_with, i_limit) |
sys_refcursor |
Cursor-Spalten: object_name, object_size, last_modified, is_folder(Y/N), etag |
f_download_object |
Funktion | (i_object_key) |
blob |
|
p_upload_object |
Prozedur | (i_object_key, i_content blob, i_content_type) |
— | |
p_delete_object |
Prozedur | (i_object_key) |
— | |
p_delete_folder |
Prozedur | (i_prefix) |
— | intern: f_list_objects-Loop + p_delete_object |
p_rename_object |
Prozedur | (i_object_key, i_new_name) |
— | intern: OCI RenameObject API |
p_move_object |
Prozedur | (i_object_key, i_target_prefix) |
— | intern: OCI RenameObject API |
p_create_folder |
Prozedur | (i_prefix, i_folder_name) |
— | PUT leeres Objekt mit trailing / |
f_get_object_metadata |
Funktion | (i_object_key) |
t_object_meta |
HEAD Request |
Rechteprüfungen:
Zu beginn von delete-operationen muss folgender Aufruf durchgeführt werden, um die Rechte des nutzers zu prüfen: pck_mitarbeiterrecht.p_hat_recht('ADMIN');
Bei anderen schreib-Operationen, wie rename oder move muss die Prüfung von folgendem Recht erfolgen:
pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES');
Bei lese-Operationen wie f_get_object_metadata und f_download_object muss folgendes Recht geprüft werden:
pck_mitarbeiterrecht.p_hat_recht('LESEN_ALLES');
Schlägt eine Prüfung fehl, wirft die p_hat_recht Prozedur einen speziellen Application Error mit Fehlermeldung für den Benutzer.
Private Helpers (nur Body)
| Name | Typ | Zweck |
|---|---|---|
f_get_cfg |
Funktion | Wrapper für pck_system.f_get_par_wert_by_programmid |
f_build_url |
Funktion | Baut vollständige OCI REST URL (/n/{ns}/b/{bucket}/o/{key}) |
p_assert_allowed |
Prozedur | Tenant-Prefix-Guard + Path-Traversal-Prüfung |
f_make_request |
Funktion | Wrapper für apex_web_service.make_rest_request, wertet HTTP-Status aus, gibt Response-Body zurück |
Pagination (f_list_objects)
OCI liefert max. 1000 Objekte pro Request. f_list_objects ruft OCI intern in einer Schleife auf
(nextStartWith-Token aus JSON-Response) bis alle Seiten geladen sind. Ergebnis wird in einer
lokalen Collection gesammelt, am Ende als sys_refcursor geöffnet.
i_limit = 0 → unbegrenzt, i_limit > 0 → Abbruch wenn Limit erreicht.
OCI-Response trennt Unterordner (prefixes-Array) von Dateien (objects-Array) — beide werden
eingesammelt, Ordner mit is_folder = 'Y'.
Fehlerbehandlung
f_make_request wirft bei HTTP-Fehler:
| HTTP-Status | Fehlercode | Meldung |
|---|---|---|
| 404 | -20001 | Object not found: {key} |
| 401 / 403 | -20002 | OCI authentication failed |
| 409 | -20007 | Object already exists: {key} |
| sonstige 4xx/5xx | -20006 | OCI API error {status}: {response body} |
p_assert_allowed wirft:
| Fall | Fehlercode | Meldung |
|---|---|---|
.. im Pfad |
-20004 | Path traversal attempt detected |
| Pfad außerhalb Tenant-Prefix | -20005 | Access denied: outside tenant scope |
Bucket nicht erreichbar → -20003 via f_make_request
Sicherheit / Tenant-Isolation
p_assert_allowed wird am Anfang jeder öffentlichen Funktion/Prozedur aufgerufen:
- Tenant-Prefix laden via
f_get_cfg('NET_STORAGE_TENANT_ID') ..-Sequenzen im übergebenen Key prüfen → -20004- Prüfen ob Key mit Tenant-Prefix beginnt → -20005
Dateieingang-Verarbeitung
OCI-Struktur
Der Quarkus Automaton legt Dateien in einem Unterordner ab, der nach der ZIP-Datei benannt ist. Unterordner innerhalb der ZIP werden beibehalten:
eingang/<zip-name>/datei1.csv
eingang/<zip-name>/unterordner/datei3.csv
eingang/<zip-name>/_READY_FOR_DB_PROCESSING_ ← Marker
Marker-Datei
Der Quarkus Automaton legt nach erfolgreichem Upload aller Dateien eine leere Marker-Datei ab:
eingang/<zip-name>/_READY_FOR_DB_PROCESSING_
Die DB verarbeitet einen Unterordner ausschließlich wenn der Marker vorhanden ist. Der Marker wird erst gelöscht wenn alle Dateien des Unterordners erfolgreich verarbeitet wurden — so werden fehlgeschlagene Dateien beim nächsten Lauf erneut versucht.
Prozedur p_process_incoming_ba_data (in pck_auto_import, nicht in pck_net_storage)
1. Unterordner von eingang/ auflisten (f_list_objects mit delimiter='/')
2. Für jeden Unterordner:
a. Marker eingang/<zip-name>/_READY_FOR_DB_PROCESSING_ vorhanden?
→ nein: überspringen (unvollständiger Upload)
b. Alle Dateien in eingang/<zip-name>/ auflisten (ohne Marker)
c. Für jede Datei einzeln:
begin
Fachliche Verarbeitung (Import) — noch kein Commit
log_object_ref := 'eingang/<zip-name>/datei.csv' ← ZIP aus Pfad ableitbar
pck_net_storage.p_move_object(datei → Zielordner/<zip-name>/)
commit
pck_log.p_info(...)
exception when others then
rollback
pck_log.p_error(i_object_ref => 'eingang/<zip-name>/datei.csv', ...)
end
d. Noch Dateien in eingang/<zip-name>/ (außer Marker)?
→ nein: pck_net_storage.p_delete_object(Marker) — Batch abgeschlossen
→ ja: Marker bleibt — nächster Lauf versucht verbleibende Dateien
Rollback nach fehlgeschlagenem p_move_object macht auch den Import rückgängig.
Kein Verhalten wird aus lg_app_log abgeleitet — es dient ausschließlich dem Audit.
ORDS-Endpunkt
POST /ords/.../net_storage/process_incoming_ba_data → ruft p_process_incoming_ba_data auf.
Wird von automaton nach erfolgreichem Upload aufgerufen.
Schlägt der Aufruf fehl: APEX Automation greift beim nächsten Stundenlauf.
APEX REST Data Source (manuell einrichten)
Für das APEX Interactive Report im Dateimanager-Browser:
Name: NET_STORAGE_LIST_OBJECTS
Base-URL: https://objectstorage.{region}.oraclecloud.com
Endpoint: /n/{namespace}/b/{bucket}/o
Method: GET
Web Credential: OCI_OBJ_STORE_CRED
URL-Parameter:
prefix → aktueller Ordner-Prefix (Bind-Variable aus APEX)
delimiter → /
limit → 1000
start → für Pagination (optional)
Spalten-Mapping:
objects[*].name → OBJECT_NAME
objects[*].size → OBJECT_SIZE
objects[*].timeModified → LAST_MODIFIED
objects[*].etag → ETAG
Hinweis: prefixes (Unterordner) und objects (Dateien) sind zwei separate JSON-Arrays.
Für ein gemeinsames IR entweder zwei REST Sources mit UNION, oder f_list_objects über
eine APEX Collection als IR-Quelle nutzen.
Logging: lg_app_log + pck_log
Tabelle lg_app_log
Allgemeine Anwendungs-Log-Tabelle, modulübergreifend nutzbar. Keine Indizes (Log-Volumen ist gering, Ad-hoc-Suche reicht ohne Index).
| Spalte | Typ | Pflicht | Beschreibung |
|---|---|---|---|
lg_app_log_id |
number generated by default as identity |
ja | PK |
log_timestamp |
timestamp |
ja | Zeitpunkt des Log-Eintrags |
log_level |
varchar2(10 char) |
ja | INFO, WARN, ERROR |
log_module |
varchar2(100 char) |
ja | z.B. PCK_NET_STORAGE |
log_action |
varchar2(100 char) |
nein | z.B. PROCESS_FILE |
log_object_ref |
varchar2(512 char) |
nein | z.B. eingang/datei.csv |
log_message |
varchar2(4000 char) |
nein | Kurzmeldung |
log_detail |
clob |
nein | Stack Trace, JSON, etc. |
log_user |
varchar2(100 char) |
nein | APEX- oder DB-User |
log_session_id |
number |
nein | Oracle Session-ID |
Keine Audit-Spalten (Log-Tabelle ist self-auditing durch log_timestamp + log_user).
Package pck_log
Öffentliche Prozeduren:
| Name | Signatur |
|---|---|
p_info |
(i_module, i_action, i_message, i_object_ref default null) |
p_warn |
(i_module, i_action, i_message, i_object_ref default null) |
p_error |
(i_module, i_action, i_message, i_detail default null, i_object_ref default null) |
Intern immer PRAGMA AUTONOMOUS_TRANSACTION + COMMIT — Log-Einträge sind
transaktionsunabhängig und überleben Rollbacks der Haupttransaktion.
OCI API Endpunkte (Referenz)
| Operation | Method | Pfad |
|---|---|---|
| List Objects | GET | /n/{ns}/b/{bucket}/o?prefix=...&delimiter=/ |
| Get Object | GET | /n/{ns}/b/{bucket}/o/{objectName} |
| Put Object | PUT | /n/{ns}/b/{bucket}/o/{objectName} |
| Delete Object | DELETE | /n/{ns}/b/{bucket}/o/{objectName} |
| Head Object | HEAD | /n/{ns}/b/{bucket}/o/{objectName} |
| Rename Object | POST | /n/{ns}/b/{bucket}/actions/renameObject |