2026-04-08 16:23:02 +02:00
|
|
|
create or replace package body pck_net_storage as
|
|
|
|
|
|
2026-04-09 11:36:47 +02:00
|
|
|
c_log_module constant lg_app_log.log_module%type := 'NETZLAUFWERK';
|
|
|
|
|
|
2026-04-08 16:23:02 +02:00
|
|
|
-- ==================== Private Helpers ====================
|
|
|
|
|
|
|
|
|
|
function f_build_url (
|
|
|
|
|
i_object_key in varchar2 default null
|
|
|
|
|
,i_action in varchar2 default null
|
|
|
|
|
) return varchar2
|
|
|
|
|
/*Kopf------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Beschreibung: Baut die vollständige OCI Object Storage URL aus den Konfigurationsparametern.
|
|
|
|
|
-- Entweder für eine Bucket-Action, ein einzelnes Objekt oder den Bucket-Root.
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Parameter: i_object_key Objektschlüssel (Pfad im Bucket); null für Bucket-Root oder Action-URL
|
|
|
|
|
-- i_action OCI Bucket-Action (z.B. renameObject); null für Objekt-URL
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Rückgabe: Vollständige URL als VARCHAR2
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- MA Datum Änderung
|
|
|
|
|
-- SCK 2026-04-08 Funktion erstellt
|
|
|
|
|
------------------------------------------------------------------------------------------------Kopf*/
|
|
|
|
|
is
|
|
|
|
|
l_base varchar2(1024);
|
|
|
|
|
begin
|
|
|
|
|
l_base := 'https://objectstorage.'
|
2026-04-09 13:10:47 +02:00
|
|
|
|| pck_system.f_get_par_wert_by_programmid('NETSTORE_REGION')
|
2026-04-08 16:23:02 +02:00
|
|
|
|| '.oraclecloud.com/n/'
|
2026-04-09 13:10:47 +02:00
|
|
|
|| pck_system.f_get_par_wert_by_programmid('NETSTORE_NAMESPACE')
|
2026-04-08 16:23:02 +02:00
|
|
|
|| '/b/'
|
2026-04-09 13:10:47 +02:00
|
|
|
|| pck_system.f_get_par_wert_by_programmid('NETSTORE_BUCKET');
|
2026-04-08 16:23:02 +02:00
|
|
|
|
|
|
|
|
if i_action is not null
|
|
|
|
|
then
|
|
|
|
|
return l_base || '/actions/' || i_action;
|
|
|
|
|
elsif i_object_key is not null
|
|
|
|
|
then
|
|
|
|
|
-- Sonderzeichen kodieren, Schrägstriche im Key unverändert lassen
|
|
|
|
|
return l_base || '/o/' || utl_url.escape(i_object_key, false);
|
|
|
|
|
else
|
|
|
|
|
return l_base || '/o';
|
|
|
|
|
end if;
|
|
|
|
|
end f_build_url;
|
|
|
|
|
|
2026-04-10 12:41:44 +02:00
|
|
|
-- Normalisiert einen Ordnerpfad: stellt sicher, dass er mit / endet.
|
|
|
|
|
-- null bleibt null (= Bucket-Root).
|
|
|
|
|
function f_normalize_prefix (i_prefix in varchar2) return varchar2
|
|
|
|
|
is
|
|
|
|
|
begin
|
|
|
|
|
if i_prefix is null
|
|
|
|
|
then
|
|
|
|
|
return null;
|
|
|
|
|
end if;
|
|
|
|
|
|
|
|
|
|
return rtrim(i_prefix, '/') || '/';
|
|
|
|
|
end f_normalize_prefix;
|
|
|
|
|
|
2026-04-08 16:23:02 +02:00
|
|
|
procedure p_assert_allowed (i_object_key in varchar2)
|
|
|
|
|
/*Kopf------------------------------------------------------------------------------------------------
|
2026-04-10 12:41:44 +02:00
|
|
|
-- Beschreibung: Prüft den Objektschlüssel auf Gültigkeit, Path-Traversal-Angriffe und Tenant-Scope.
|
|
|
|
|
-- Wirft Application Error -20008 bei null-Key, -20004 bei Path Traversal,
|
|
|
|
|
-- -20005 bei Scope-Verletzung.
|
2026-04-08 16:23:02 +02:00
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Parameter: i_object_key Zu prüfender Objektschlüssel
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- MA Datum Änderung
|
|
|
|
|
-- SCK 2026-04-08 Prozedur erstellt
|
2026-04-10 12:41:44 +02:00
|
|
|
-- SCK 2026-04-10 Null-Prüfung und führender-Slash-Check ergänzt
|
2026-04-08 16:23:02 +02:00
|
|
|
------------------------------------------------------------------------------------------------Kopf*/
|
|
|
|
|
is
|
|
|
|
|
l_tenant_prefix varchar2(256);
|
|
|
|
|
begin
|
2026-04-10 12:41:44 +02:00
|
|
|
if i_object_key is null
|
|
|
|
|
then
|
|
|
|
|
raise_application_error(-20008, 'Object key darf nicht null sein');
|
|
|
|
|
end if;
|
|
|
|
|
|
|
|
|
|
if instr(i_object_key, '..') > 0 or substr(i_object_key, 1, 1) = '/'
|
2026-04-08 16:23:02 +02:00
|
|
|
then
|
|
|
|
|
raise_application_error(-20004, 'Path traversal attempt detected');
|
|
|
|
|
end if;
|
|
|
|
|
|
2026-04-09 13:10:47 +02:00
|
|
|
l_tenant_prefix := pck_system.f_get_par_wert_by_programmid('NETSTORE_TENANT_ID');
|
2026-04-08 16:23:02 +02:00
|
|
|
|
|
|
|
|
if l_tenant_prefix is not null and length(l_tenant_prefix) > 0
|
|
|
|
|
then
|
|
|
|
|
if substr(i_object_key, 1, length(l_tenant_prefix)) != l_tenant_prefix
|
|
|
|
|
then
|
|
|
|
|
raise_application_error(-20005, 'Access denied: outside tenant scope');
|
|
|
|
|
end if;
|
|
|
|
|
end if;
|
|
|
|
|
end p_assert_allowed;
|
|
|
|
|
|
|
|
|
|
function f_make_request (
|
|
|
|
|
i_method in varchar2
|
|
|
|
|
,i_url in varchar2
|
|
|
|
|
,i_body_clob in clob default null
|
|
|
|
|
,i_body_blob in blob default null
|
|
|
|
|
,i_content_type in varchar2 default null
|
|
|
|
|
) return clob
|
|
|
|
|
/*Kopf------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Beschreibung: Führt einen HTTP-Request gegen die OCI Object Storage API aus.
|
|
|
|
|
-- Wertet den HTTP-Statuscode aus und löst bei Fehler einen Application Error aus.
|
2026-04-09 13:10:47 +02:00
|
|
|
-- Authentifizierung erfolgt über APEX Web Credential (NETSTORE_CRED_ID).
|
2026-04-08 16:23:02 +02:00
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Parameter: i_method HTTP-Methode (GET, PUT, DELETE, POST, HEAD)
|
|
|
|
|
-- i_url Vollständige Ziel-URL
|
|
|
|
|
-- i_body_clob Optionaler Request-Body als CLOB (z.B. JSON)
|
|
|
|
|
-- i_body_blob Optionaler Request-Body als BLOB (Binärinhalt)
|
|
|
|
|
-- i_content_type Optionaler Content-Type Header
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Rückgabe: Response-Body als CLOB (bei HEAD-Requests leer)
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- MA Datum Änderung
|
|
|
|
|
-- SCK 2026-04-08 Funktion erstellt
|
|
|
|
|
------------------------------------------------------------------------------------------------Kopf*/
|
|
|
|
|
is
|
2026-04-10 16:25:09 +02:00
|
|
|
l_response clob;
|
|
|
|
|
l_status number;
|
|
|
|
|
l_header_index number := 1;
|
|
|
|
|
l_content_length number;
|
2026-04-08 16:23:02 +02:00
|
|
|
begin
|
2026-04-09 13:10:47 +02:00
|
|
|
|
2026-04-08 16:23:02 +02:00
|
|
|
if i_content_type is not null
|
|
|
|
|
then
|
2026-04-10 16:25:09 +02:00
|
|
|
apex_web_service.g_request_headers(l_header_index).name := 'Content-Type';
|
|
|
|
|
apex_web_service.g_request_headers(l_header_index).value := i_content_type;
|
2026-04-09 13:10:47 +02:00
|
|
|
l_header_index := l_header_index + 1;
|
2026-04-08 16:23:02 +02:00
|
|
|
end if;
|
|
|
|
|
|
2026-04-10 16:25:09 +02:00
|
|
|
-- Content-Length immer mitsenden (OCI-Signing erfordert es bei PUT/POST,
|
|
|
|
|
-- 0 bei bodylosem Request ist valide)
|
|
|
|
|
if i_body_blob is not null and dbms_lob.getlength(i_body_blob) > 0
|
|
|
|
|
then
|
|
|
|
|
l_content_length := dbms_lob.getlength(i_body_blob);
|
|
|
|
|
elsif i_body_clob is not null and dbms_lob.getlength(i_body_clob) > 0
|
|
|
|
|
then
|
|
|
|
|
l_content_length := dbms_lob.getlength(i_body_clob);
|
|
|
|
|
else
|
|
|
|
|
l_content_length := 0;
|
|
|
|
|
end if;
|
|
|
|
|
|
|
|
|
|
apex_web_service.g_request_headers(l_header_index).name := 'Content-Length';
|
|
|
|
|
apex_web_service.g_request_headers(l_header_index).value := l_content_length;
|
|
|
|
|
l_header_index := l_header_index + 1;
|
|
|
|
|
|
2026-04-10 15:26:40 +02:00
|
|
|
apex_debug.info('url: ' || i_url);
|
|
|
|
|
|
2026-04-08 16:23:02 +02:00
|
|
|
l_response := apex_web_service.make_rest_request(
|
|
|
|
|
p_url => i_url
|
|
|
|
|
,p_http_method => i_method
|
|
|
|
|
,p_body => coalesce(i_body_clob, empty_clob())
|
|
|
|
|
,p_body_blob => coalesce(i_body_blob, empty_blob())
|
2026-04-09 13:10:47 +02:00
|
|
|
,p_credential_static_id => pck_system.f_get_par_wert_by_programmid('NETSTORE_CRED_ID')
|
2026-04-10 12:13:05 +02:00
|
|
|
,p_wallet_path => pck_system.f_get_par_wert_by_programmid('NETSTORE_WALLET_PATH')
|
2026-04-08 16:23:02 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
l_status := apex_web_service.g_status_code;
|
|
|
|
|
|
|
|
|
|
if l_status = 404
|
|
|
|
|
then
|
|
|
|
|
raise_application_error(-20001, 'Object not found');
|
|
|
|
|
elsif l_status in (401, 403)
|
|
|
|
|
then
|
|
|
|
|
raise_application_error(-20002, 'OCI authentication failed');
|
|
|
|
|
elsif l_status = 409
|
|
|
|
|
then
|
|
|
|
|
raise_application_error(-20007, 'Object already exists');
|
|
|
|
|
elsif l_status >= 400
|
|
|
|
|
then
|
|
|
|
|
raise_application_error(-20006,
|
|
|
|
|
'OCI API error ' || l_status || ': ' || dbms_lob.substr(l_response, 500, 1));
|
|
|
|
|
end if;
|
|
|
|
|
|
|
|
|
|
return l_response;
|
|
|
|
|
end f_make_request;
|
|
|
|
|
|
2026-04-10 16:25:09 +02:00
|
|
|
-- Interne Implementierung ohne Rechteprüfung — wird von f_list_objects und p_delete_folder (Leerprüfung) genutzt
|
2026-04-08 16:23:02 +02:00
|
|
|
function f_list_objects_internal (
|
2026-04-10 12:17:51 +02:00
|
|
|
i_parent_folder in varchar2
|
|
|
|
|
,i_include_subfolders in varchar2
|
|
|
|
|
,i_start_with in varchar2
|
|
|
|
|
,i_limit in number
|
2026-04-09 09:42:29 +02:00
|
|
|
) return t_net_storage_tab
|
2026-04-08 16:23:02 +02:00
|
|
|
/*Kopf------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Beschreibung: Listet Objekte und Unterordner im Bucket ohne Rechte- oder Scope-Prüfung.
|
|
|
|
|
-- Paginiert automatisch über nextStartWith bis alle Ergebnisse geladen sind.
|
2026-04-10 16:25:09 +02:00
|
|
|
-- Wird von f_list_objects (öffentlich) und p_delete_folder (Leerprüfung) intern genutzt.
|
2026-04-08 16:23:02 +02:00
|
|
|
------------------------------------------------------------------------------------------------------
|
2026-04-10 12:17:51 +02:00
|
|
|
-- Parameter: i_parent_folder Ordnerpfad im Bucket (z.B. eingang/)
|
|
|
|
|
-- i_include_subfolders 'Y' = alle Dateien rekursiv, 'N' = nur direkte Kinder des Ordners
|
|
|
|
|
-- i_start_with Optionaler Startpunkt für Paginierung
|
|
|
|
|
-- i_limit Maximale Anzahl Ergebnisse (0 = unbegrenzt)
|
2026-04-08 16:23:02 +02:00
|
|
|
------------------------------------------------------------------------------------------------------
|
2026-04-09 09:42:29 +02:00
|
|
|
-- Rückgabe: Collection t_net_storage_tab mit allen gefundenen Objekten
|
2026-04-08 16:23:02 +02:00
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- MA Datum Änderung
|
|
|
|
|
-- SCK 2026-04-08 Funktion erstellt
|
|
|
|
|
------------------------------------------------------------------------------------------------Kopf*/
|
|
|
|
|
is
|
2026-04-10 12:41:44 +02:00
|
|
|
l_url varchar2(4000);
|
|
|
|
|
l_response clob;
|
|
|
|
|
l_result t_net_storage_tab := t_net_storage_tab();
|
|
|
|
|
l_next_start varchar2(1024);
|
|
|
|
|
l_count number := 0;
|
|
|
|
|
l_done boolean := false;
|
|
|
|
|
l_cur_start varchar2(1024) := i_start_with;
|
|
|
|
|
l_parent_folder varchar2(1024) := f_normalize_prefix(i_parent_folder);
|
2026-04-10 14:18:15 +02:00
|
|
|
l_obj_path t_object_path;
|
2026-04-10 12:41:44 +02:00
|
|
|
c_page_size constant number := 1000;
|
2026-04-08 16:23:02 +02:00
|
|
|
begin
|
|
|
|
|
while not l_done
|
|
|
|
|
loop
|
|
|
|
|
l_url := f_build_url()
|
2026-04-10 12:58:55 +02:00
|
|
|
|| '?limit=' || c_page_size
|
|
|
|
|
|| '&fields=name,size,etag,timeModified'
|
2026-04-10 12:41:44 +02:00
|
|
|
|| (case when l_parent_folder is not null then '&prefix=' || utl_url.escape(l_parent_folder, false) else '' end)
|
2026-04-10 12:17:51 +02:00
|
|
|
|| (case when i_include_subfolders = 'N' then '&delimiter=/' else '' end);
|
2026-04-08 16:23:02 +02:00
|
|
|
|
|
|
|
|
if l_cur_start is not null
|
|
|
|
|
then
|
|
|
|
|
l_url := l_url || '&start=' || utl_url.escape(l_cur_start, false);
|
|
|
|
|
end if;
|
|
|
|
|
|
|
|
|
|
l_response := f_make_request('GET', l_url);
|
|
|
|
|
|
|
|
|
|
-- Dateien aus objects-Array einlesen
|
|
|
|
|
for rec in (
|
|
|
|
|
select jt.object_name
|
|
|
|
|
,jt.object_size
|
|
|
|
|
,jt.last_modified
|
|
|
|
|
,jt.etag
|
|
|
|
|
from json_table(l_response, '$.objects[*]'
|
|
|
|
|
columns (
|
|
|
|
|
object_name varchar2(1024) path '$.name'
|
|
|
|
|
,object_size number path '$.size'
|
|
|
|
|
,last_modified varchar2(50) path '$.timeModified'
|
|
|
|
|
,etag varchar2(256) path '$.etag'
|
|
|
|
|
)) jt
|
|
|
|
|
)
|
|
|
|
|
loop
|
2026-04-10 14:18:15 +02:00
|
|
|
l_obj_path := f_split_object_key(rec.object_name);
|
2026-04-08 16:23:02 +02:00
|
|
|
l_result.extend;
|
|
|
|
|
l_result(l_result.last) := t_net_storage_row(
|
|
|
|
|
rec.object_name
|
2026-04-10 14:18:15 +02:00
|
|
|
,l_obj_path.path
|
|
|
|
|
,l_obj_path.filename
|
2026-04-10 12:58:55 +02:00
|
|
|
-- Explizit angelegte Ordner sind Zero-Byte-Objekte mit trailing /;
|
2026-04-10 14:18:15 +02:00
|
|
|
-- size, last_modified und etag sind für Ordner nicht relevant
|
2026-04-10 12:58:55 +02:00
|
|
|
,(case when rec.object_name like '%/' then null else rec.object_size end)
|
|
|
|
|
,(case when rec.object_name like '%/' then null else to_date(substr(rec.last_modified, 1, 19), 'YYYY-MM-DD"T"HH24:MI:SS') end)
|
|
|
|
|
,(case when rec.object_name like '%/' then 'Y' else 'N' end)
|
|
|
|
|
,(case when rec.object_name like '%/' then null else rec.etag end)
|
2026-04-08 16:23:02 +02:00
|
|
|
);
|
|
|
|
|
l_count := l_count + 1;
|
|
|
|
|
|
|
|
|
|
if i_limit > 0 and l_count >= i_limit
|
|
|
|
|
then
|
|
|
|
|
l_done := true;
|
|
|
|
|
exit;
|
|
|
|
|
end if;
|
|
|
|
|
end loop;
|
|
|
|
|
|
|
|
|
|
-- Unterordner aus prefixes-Array einlesen
|
|
|
|
|
if not l_done
|
|
|
|
|
then
|
|
|
|
|
for rec in (
|
|
|
|
|
select jt.prefix_name
|
|
|
|
|
from json_table(l_response, '$.prefixes[*]'
|
|
|
|
|
columns (
|
|
|
|
|
prefix_name varchar2(1024) path '$'
|
|
|
|
|
)) jt
|
|
|
|
|
)
|
|
|
|
|
loop
|
2026-04-10 14:18:15 +02:00
|
|
|
l_obj_path := f_split_object_key(rec.prefix_name);
|
2026-04-08 16:23:02 +02:00
|
|
|
l_result.extend;
|
|
|
|
|
l_result(l_result.last) := t_net_storage_row(
|
|
|
|
|
rec.prefix_name
|
2026-04-10 14:18:15 +02:00
|
|
|
,l_obj_path.path
|
|
|
|
|
,l_obj_path.filename
|
|
|
|
|
,null
|
2026-04-08 16:23:02 +02:00
|
|
|
,null
|
|
|
|
|
,'Y'
|
|
|
|
|
,null
|
|
|
|
|
);
|
|
|
|
|
l_count := l_count + 1;
|
|
|
|
|
|
|
|
|
|
if i_limit > 0 and l_count >= i_limit
|
|
|
|
|
then
|
|
|
|
|
l_done := true;
|
|
|
|
|
exit;
|
|
|
|
|
end if;
|
|
|
|
|
end loop;
|
|
|
|
|
end if;
|
|
|
|
|
|
|
|
|
|
-- Nächste Seite prüfen
|
|
|
|
|
if not l_done
|
|
|
|
|
then
|
|
|
|
|
l_next_start := json_value(l_response, '$.nextStartWith');
|
|
|
|
|
|
|
|
|
|
if l_next_start is null
|
|
|
|
|
then
|
|
|
|
|
l_done := true;
|
|
|
|
|
else
|
|
|
|
|
l_cur_start := l_next_start;
|
|
|
|
|
end if;
|
|
|
|
|
end if;
|
|
|
|
|
end loop;
|
|
|
|
|
|
2026-04-10 12:51:47 +02:00
|
|
|
-- Implizite Ordner aus Object-Keys ableiten.
|
|
|
|
|
-- Die OCI-API liefert virtuelle Ordner (nie als Zero-Byte-Objekt angelegt) nur
|
|
|
|
|
-- über $.prefixes, und auch nur wenn delimiter gesetzt ist. Bei rekursivem Abruf
|
|
|
|
|
-- fehlen sie daher komplett. Wir leiten alle Zwischenpfade aus den Object-Keys ab
|
|
|
|
|
-- und ergänzen fehlende Ordner-Einträge.
|
|
|
|
|
declare
|
|
|
|
|
l_new_folders apex_t_varchar2;
|
|
|
|
|
begin
|
|
|
|
|
select distinct folder_path
|
|
|
|
|
bulk collect into l_new_folders
|
|
|
|
|
from (
|
|
|
|
|
-- Innere Query: alle Zwischenpfade aus den Datei-Keys ableiten.
|
|
|
|
|
--
|
|
|
|
|
-- connect by level iteriert von 1 bis zur Anzahl der Slashes im Key.
|
|
|
|
|
-- instr(..., '/', 1, level) liefert die Position des n-ten Slashes.
|
|
|
|
|
-- substr(..., 1, <position>) schneidet den Key bis einschließlich
|
|
|
|
|
-- dieses Slashes ab — das Ergebnis ist der Ordnerpfad auf Ebene n.
|
|
|
|
|
--
|
|
|
|
|
-- Beispiel für 'mandant/Eingang/batch-001/datei.pdf' (3 Slashes):
|
|
|
|
|
-- level 1 → 'mandant/'
|
|
|
|
|
-- level 2 → 'mandant/Eingang/'
|
|
|
|
|
-- level 3 → 'mandant/Eingang/batch-001/'
|
|
|
|
|
--
|
2026-04-10 14:18:15 +02:00
|
|
|
-- prior object_key = object_key : bindet jede Zeile an sich selbst,
|
2026-04-10 12:51:47 +02:00
|
|
|
-- damit connect by die Levels pro Zeile unabhängig hochzählt.
|
2026-04-10 14:18:15 +02:00
|
|
|
-- prior sys_guid() is not null : verhindert Cycle-Detection-Fehler,
|
2026-04-10 12:51:47 +02:00
|
|
|
-- da keine echte Eltern-Kind-Beziehung vorliegt.
|
2026-04-10 14:18:15 +02:00
|
|
|
select substr(r.object_key, 1, instr(r.object_key, '/', 1, level)) as folder_path
|
2026-04-10 12:51:47 +02:00
|
|
|
from table(l_result) r
|
|
|
|
|
where r.is_folder = 'N'
|
2026-04-10 14:18:15 +02:00
|
|
|
connect by level <= regexp_count(r.object_key, '/')
|
|
|
|
|
and prior r.object_key = r.object_key
|
|
|
|
|
and prior sys_guid() is not null
|
2026-04-10 12:51:47 +02:00
|
|
|
)
|
|
|
|
|
-- Nur Pfade unterhalb des Parent-Folders behalten:
|
|
|
|
|
-- like-Bedingung schließt Vorfahren-Pfade aus (z.B. 'mandant/', 'mandant/Eingang/'
|
|
|
|
|
-- wenn der Parent-Folder 'mandant/Eingang/batch/' ist).
|
|
|
|
|
-- != schließt den Parent-Folder selbst aus.
|
|
|
|
|
-- Bei null-Parent-Folder (Bucket-Root): like '%' = immer wahr, chr(0) passt
|
|
|
|
|
-- auf keinen gültigen Key → beide Bedingungen greifen nicht.
|
|
|
|
|
where folder_path like nvl(l_parent_folder, '') || '%'
|
|
|
|
|
and folder_path != nvl(l_parent_folder, chr(0))
|
|
|
|
|
-- Bereits vorhandene Ordner-Einträge ausschließen (explizit angelegte
|
|
|
|
|
-- Zero-Byte-Objekte oder via $.prefixes gelieferte virtuelle Ordner).
|
|
|
|
|
minus
|
2026-04-10 14:18:15 +02:00
|
|
|
select object_key
|
2026-04-10 12:51:47 +02:00
|
|
|
from table(l_result)
|
|
|
|
|
where is_folder = 'Y';
|
|
|
|
|
|
|
|
|
|
for i in 1..l_new_folders.count
|
|
|
|
|
loop
|
2026-04-10 14:18:15 +02:00
|
|
|
l_obj_path := f_split_object_key(l_new_folders(i));
|
2026-04-10 12:51:47 +02:00
|
|
|
l_result.extend;
|
|
|
|
|
l_result(l_result.last) := t_net_storage_row(
|
|
|
|
|
l_new_folders(i)
|
2026-04-10 14:18:15 +02:00
|
|
|
,l_obj_path.path
|
|
|
|
|
,l_obj_path.filename
|
|
|
|
|
,null
|
2026-04-10 12:51:47 +02:00
|
|
|
,null
|
|
|
|
|
,'Y'
|
|
|
|
|
,null
|
|
|
|
|
);
|
|
|
|
|
end loop;
|
|
|
|
|
end;
|
|
|
|
|
|
2026-04-09 09:42:29 +02:00
|
|
|
return l_result;
|
2026-04-08 16:23:02 +02:00
|
|
|
end f_list_objects_internal;
|
|
|
|
|
|
|
|
|
|
-- ==================== Öffentliche Funktionen ====================
|
|
|
|
|
|
2026-04-10 12:13:05 +02:00
|
|
|
function f_split_object_key (i_object_key in varchar2) return t_object_path
|
|
|
|
|
/*Kopf------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Beschreibung: Extrahiert Pfad und Dateiname aus einem OCI-Objektschlüssel.
|
|
|
|
|
-- Bei Ordner-Keys (trailing Slash) wird der Ordnername als Dateiname zurückgegeben.
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Parameter: i_object_key Vollständiger Objektschlüssel (z.B. mandant/Eingang/Import/datei.pdf)
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Rückgabe: t_object_path Record mit path (inkl. trailing Slash) und filename
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- MA Datum Änderung
|
|
|
|
|
-- SCK 2026-04-09 Funktion erstellt
|
|
|
|
|
------------------------------------------------------------------------------------------------Kopf*/
|
|
|
|
|
is
|
|
|
|
|
l_key varchar2(1024);
|
|
|
|
|
l_pos number;
|
|
|
|
|
l_result t_object_path;
|
|
|
|
|
begin
|
|
|
|
|
-- Trailing Slash entfernen damit Ordner-Keys genauso behandelt werden wie Datei-Keys
|
|
|
|
|
l_key := rtrim(i_object_key, '/');
|
|
|
|
|
|
|
|
|
|
l_pos := instr(l_key, '/', -1);
|
|
|
|
|
|
|
|
|
|
if l_pos > 0
|
|
|
|
|
then
|
|
|
|
|
l_result.path := substr(l_key, 1, l_pos);
|
|
|
|
|
l_result.filename := substr(l_key, l_pos + 1);
|
|
|
|
|
else
|
|
|
|
|
l_result.path := null;
|
|
|
|
|
l_result.filename := l_key;
|
|
|
|
|
end if;
|
|
|
|
|
|
|
|
|
|
return l_result;
|
|
|
|
|
end f_split_object_key;
|
|
|
|
|
|
2026-04-08 16:23:02 +02:00
|
|
|
function f_list_objects (
|
2026-04-10 12:17:51 +02:00
|
|
|
i_parent_folder in varchar2
|
|
|
|
|
,i_include_subfolders in varchar2 default 'N'
|
|
|
|
|
,i_start_with in varchar2 default null
|
|
|
|
|
,i_limit in number default 0
|
2026-04-09 09:42:29 +02:00
|
|
|
) return t_net_storage_tab
|
2026-04-08 16:23:02 +02:00
|
|
|
/*Kopf------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Beschreibung: Listet Objekte und Unterordner im Bucket mit Rechteprüfung und Scope-Validierung.
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
2026-04-10 12:17:51 +02:00
|
|
|
-- Parameter: i_parent_folder Ordnerpfad im Bucket (z.B. eingang/)
|
|
|
|
|
-- i_include_subfolders 'Y' = alle Dateien rekursiv inkl. Unterordner, 'N' = nur direkte Dateien im Ordner (Standard)
|
|
|
|
|
-- i_start_with Optionaler Startpunkt für Paginierung
|
|
|
|
|
-- i_limit Maximale Anzahl Ergebnisse (0 = unbegrenzt)
|
2026-04-08 16:23:02 +02:00
|
|
|
------------------------------------------------------------------------------------------------------
|
2026-04-09 09:42:29 +02:00
|
|
|
-- Rückgabe: Collection t_net_storage_tab mit allen gefundenen Objekten
|
2026-04-08 16:23:02 +02:00
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- MA Datum Änderung
|
|
|
|
|
-- SCK 2026-04-08 Funktion erstellt
|
|
|
|
|
------------------------------------------------------------------------------------------------Kopf*/
|
|
|
|
|
is
|
2026-04-10 12:41:44 +02:00
|
|
|
l_parent_folder varchar2(1024) := f_normalize_prefix(i_parent_folder);
|
2026-04-08 16:23:02 +02:00
|
|
|
begin
|
|
|
|
|
pck_mitarbeiterrecht.p_hat_recht('LESEN_ALLES');
|
2026-04-10 12:41:44 +02:00
|
|
|
if l_parent_folder is not null
|
|
|
|
|
then
|
|
|
|
|
p_assert_allowed(l_parent_folder);
|
|
|
|
|
end if;
|
|
|
|
|
return f_list_objects_internal(l_parent_folder, i_include_subfolders, i_start_with, i_limit);
|
2026-04-08 16:23:02 +02:00
|
|
|
end f_list_objects;
|
|
|
|
|
|
|
|
|
|
function f_download_object (i_object_key in varchar2) return blob
|
|
|
|
|
/*Kopf------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Beschreibung: Lädt ein einzelnes Objekt aus dem OCI Bucket als BLOB herunter.
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Parameter: i_object_key Vollständiger Objektschlüssel im Bucket
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Rückgabe: Dateiinhalt als BLOB
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- MA Datum Änderung
|
|
|
|
|
-- SCK 2026-04-08 Funktion erstellt
|
|
|
|
|
------------------------------------------------------------------------------------------------Kopf*/
|
|
|
|
|
is
|
|
|
|
|
l_response blob;
|
|
|
|
|
l_status number;
|
|
|
|
|
begin
|
|
|
|
|
pck_mitarbeiterrecht.p_hat_recht('LESEN_ALLES');
|
|
|
|
|
p_assert_allowed(i_object_key);
|
|
|
|
|
|
|
|
|
|
l_response := apex_web_service.make_rest_request_b(
|
|
|
|
|
p_url => f_build_url(i_object_key)
|
|
|
|
|
,p_http_method => 'GET'
|
2026-04-09 13:10:47 +02:00
|
|
|
,p_credential_static_id => pck_system.f_get_par_wert_by_programmid('NETSTORE_CRED_ID')
|
2026-04-08 16:23:02 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
l_status := apex_web_service.g_status_code;
|
|
|
|
|
|
|
|
|
|
if l_status = 404
|
|
|
|
|
then
|
|
|
|
|
raise_application_error(-20001, 'Object not found: ' || i_object_key);
|
|
|
|
|
elsif l_status in (401, 403)
|
|
|
|
|
then
|
|
|
|
|
raise_application_error(-20002, 'OCI authentication failed');
|
|
|
|
|
elsif l_status >= 400
|
|
|
|
|
then
|
|
|
|
|
raise_application_error(-20006, 'OCI API error ' || l_status);
|
|
|
|
|
end if;
|
|
|
|
|
|
|
|
|
|
return l_response;
|
|
|
|
|
end f_download_object;
|
|
|
|
|
|
|
|
|
|
procedure p_upload_object (
|
|
|
|
|
i_object_key in varchar2
|
|
|
|
|
,i_content in blob
|
|
|
|
|
,i_content_type in varchar2
|
|
|
|
|
)
|
|
|
|
|
/*Kopf------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Beschreibung: Lädt ein Objekt in den OCI Bucket hoch (PUT). Überschreibt vorhandene Objekte.
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Parameter: i_object_key Zielpfad im Bucket
|
|
|
|
|
-- i_content Dateiinhalt als BLOB
|
|
|
|
|
-- i_content_type MIME-Type des Inhalts (z.B. application/octet-stream)
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- MA Datum Änderung
|
|
|
|
|
-- SCK 2026-04-08 Prozedur erstellt
|
|
|
|
|
------------------------------------------------------------------------------------------------Kopf*/
|
|
|
|
|
is
|
|
|
|
|
l_response clob;
|
2026-04-09 13:10:47 +02:00
|
|
|
l_obj_path t_object_path;
|
2026-04-08 16:23:02 +02:00
|
|
|
begin
|
|
|
|
|
pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES');
|
|
|
|
|
p_assert_allowed(i_object_key);
|
|
|
|
|
|
2026-04-10 12:41:44 +02:00
|
|
|
if substr(i_object_key, -1) = '/'
|
|
|
|
|
then
|
|
|
|
|
raise_application_error(-20012, 'Object Key darf nicht mit / enden — zum Anlegen von Ordnern p_create_folder verwenden');
|
|
|
|
|
end if;
|
|
|
|
|
|
2026-04-09 13:10:47 +02:00
|
|
|
-- TEST
|
|
|
|
|
--l_response := f_make_request(
|
|
|
|
|
-- i_method => 'PUT'
|
|
|
|
|
-- ,i_url => f_build_url(i_object_key)
|
|
|
|
|
-- ,i_body_blob => i_content
|
|
|
|
|
-- ,i_content_type => i_content_type
|
|
|
|
|
--);
|
2026-04-09 11:36:47 +02:00
|
|
|
|
2026-04-09 13:10:47 +02:00
|
|
|
l_obj_path := f_split_object_key(i_object_key);
|
2026-04-09 11:36:47 +02:00
|
|
|
pck_log.p_info(
|
|
|
|
|
i_module => c_log_module
|
|
|
|
|
,i_action => 'UPLOAD'
|
2026-04-09 13:10:47 +02:00
|
|
|
,i_message => 'Datei "' || l_obj_path.filename || '" hochgeladen | Ordner: ' || l_obj_path.path
|
2026-04-09 11:36:47 +02:00
|
|
|
,i_object_ref => i_object_key
|
|
|
|
|
);
|
2026-04-08 16:23:02 +02:00
|
|
|
end p_upload_object;
|
|
|
|
|
|
|
|
|
|
procedure p_delete_object (i_object_key in varchar2)
|
|
|
|
|
/*Kopf------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Beschreibung: Löscht ein einzelnes Objekt aus dem OCI Bucket (DELETE).
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Parameter: i_object_key Vollständiger Objektschlüssel im Bucket
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- MA Datum Änderung
|
|
|
|
|
-- SCK 2026-04-08 Prozedur erstellt
|
|
|
|
|
------------------------------------------------------------------------------------------------Kopf*/
|
|
|
|
|
is
|
|
|
|
|
l_response clob;
|
2026-04-09 13:10:47 +02:00
|
|
|
l_obj_path t_object_path;
|
2026-04-08 16:23:02 +02:00
|
|
|
begin
|
|
|
|
|
pck_mitarbeiterrecht.p_hat_recht('ADMIN');
|
|
|
|
|
p_assert_allowed(i_object_key);
|
|
|
|
|
|
2026-04-10 15:26:40 +02:00
|
|
|
l_response := f_make_request(
|
|
|
|
|
i_method => 'DELETE'
|
|
|
|
|
,i_url => f_build_url(i_object_key)
|
|
|
|
|
);
|
2026-04-09 11:36:47 +02:00
|
|
|
|
2026-04-09 13:10:47 +02:00
|
|
|
l_obj_path := f_split_object_key(i_object_key);
|
2026-04-09 11:36:47 +02:00
|
|
|
pck_log.p_info(
|
|
|
|
|
i_module => c_log_module
|
|
|
|
|
,i_action => 'DELETE'
|
2026-04-09 13:10:47 +02:00
|
|
|
,i_message => 'Datei "' || l_obj_path.filename || '" gelöscht | Ordner: ' || l_obj_path.path
|
2026-04-09 11:36:47 +02:00
|
|
|
,i_object_ref => i_object_key
|
|
|
|
|
);
|
2026-04-08 16:23:02 +02:00
|
|
|
end p_delete_object;
|
|
|
|
|
|
2026-04-10 16:25:09 +02:00
|
|
|
procedure p_delete_folder (i_folder_key in varchar2)
|
2026-04-08 16:23:02 +02:00
|
|
|
/*Kopf------------------------------------------------------------------------------------------------
|
2026-04-10 16:25:09 +02:00
|
|
|
-- Beschreibung: Löscht einen leeren Ordner im OCI Bucket.
|
|
|
|
|
-- Schlägt fehl, wenn noch Objekte oder Unterordner vorhanden sind.
|
2026-04-08 16:23:02 +02:00
|
|
|
------------------------------------------------------------------------------------------------------
|
2026-04-10 16:25:09 +02:00
|
|
|
-- Parameter: i_folder_key Kompletter Ordner name inkl. Pfad (z.B. eingang/batch-001/)
|
2026-04-08 16:23:02 +02:00
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- MA Datum Änderung
|
|
|
|
|
-- SCK 2026-04-08 Prozedur erstellt
|
2026-04-10 16:25:09 +02:00
|
|
|
-- SCK 2026-04-10 Rekursives Löschen entfernt — Ordner muss leer sein
|
2026-04-08 16:23:02 +02:00
|
|
|
------------------------------------------------------------------------------------------------Kopf*/
|
|
|
|
|
is
|
2026-04-09 09:42:29 +02:00
|
|
|
l_objects t_net_storage_tab;
|
|
|
|
|
l_response clob;
|
2026-04-09 13:10:47 +02:00
|
|
|
l_obj_path t_object_path;
|
2026-04-10 16:25:09 +02:00
|
|
|
l_prefix varchar2(1024) := f_normalize_prefix(i_folder_key);
|
|
|
|
|
l_count number;
|
2026-04-08 16:23:02 +02:00
|
|
|
begin
|
|
|
|
|
pck_mitarbeiterrecht.p_hat_recht('ADMIN');
|
2026-04-10 12:41:44 +02:00
|
|
|
p_assert_allowed(l_prefix);
|
2026-04-08 16:23:02 +02:00
|
|
|
|
2026-04-10 16:25:09 +02:00
|
|
|
-- Direkte Kinder prüfen (Dateien und Unterordner)
|
|
|
|
|
l_objects := f_list_objects_internal(
|
|
|
|
|
i_parent_folder => l_prefix
|
|
|
|
|
,i_include_subfolders => 'N'
|
|
|
|
|
,i_start_with => null
|
|
|
|
|
,i_limit => 0
|
|
|
|
|
);
|
2026-04-08 16:23:02 +02:00
|
|
|
|
2026-04-10 16:25:09 +02:00
|
|
|
/*
|
|
|
|
|
apex_debug.info('p_delete_folder: prefix=%s, Anzahl gefundene Einträge=%s', l_prefix, l_objects.count);
|
|
|
|
|
for i in 1 .. l_objects.count
|
|
|
|
|
loop
|
|
|
|
|
apex_debug.info(' [%s] key=%s | is_folder=%s', i, l_objects(i).object_key, l_objects(i).is_folder);
|
|
|
|
|
end loop;
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
-- Den Ordner selbst (object_key = l_prefix) aus der Zählung ausschließen
|
|
|
|
|
select count(*)
|
|
|
|
|
into l_count
|
|
|
|
|
from table(l_objects)
|
|
|
|
|
where object_key != l_prefix;
|
|
|
|
|
|
|
|
|
|
if l_count > 0
|
|
|
|
|
then
|
|
|
|
|
raise_application_error(-20017, 'Ordner ist nicht leer und kann nicht gelöscht werden');
|
|
|
|
|
end if;
|
|
|
|
|
|
|
|
|
|
-- Ordner-Objekt selbst löschen
|
|
|
|
|
l_response := f_make_request(
|
|
|
|
|
i_method => 'DELETE'
|
|
|
|
|
,i_url => f_build_url(l_prefix)
|
|
|
|
|
);
|
2026-04-09 13:10:47 +02:00
|
|
|
|
2026-04-10 12:41:44 +02:00
|
|
|
l_obj_path := f_split_object_key(l_prefix);
|
2026-04-09 11:36:47 +02:00
|
|
|
pck_log.p_info(
|
|
|
|
|
i_module => c_log_module
|
|
|
|
|
,i_action => 'DELETE_FOLDER'
|
2026-04-10 16:25:09 +02:00
|
|
|
,i_message => 'Ordner "' || l_obj_path.filename || '" gelöscht | Pfad: ' || l_obj_path.path
|
2026-04-10 12:41:44 +02:00
|
|
|
,i_object_ref => l_prefix
|
2026-04-09 11:36:47 +02:00
|
|
|
);
|
2026-04-08 16:23:02 +02:00
|
|
|
end p_delete_folder;
|
|
|
|
|
|
|
|
|
|
procedure p_rename_object (
|
|
|
|
|
i_object_key in varchar2
|
|
|
|
|
,i_new_name in varchar2
|
|
|
|
|
)
|
|
|
|
|
/*Kopf------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Beschreibung: Benennt ein Objekt innerhalb desselben Verzeichnisses um.
|
|
|
|
|
-- Verwendet die OCI renameObject-Action (kein physisches Kopieren).
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Parameter: i_object_key Vollständiger Objektschlüssel des Quelldatei
|
|
|
|
|
-- i_new_name Neuer Dateiname (ohne Pfad)
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- MA Datum Änderung
|
|
|
|
|
-- SCK 2026-04-08 Prozedur erstellt
|
|
|
|
|
------------------------------------------------------------------------------------------------Kopf*/
|
|
|
|
|
is
|
|
|
|
|
l_new_key varchar2(1024);
|
|
|
|
|
l_body clob;
|
|
|
|
|
l_response clob;
|
2026-04-09 13:10:47 +02:00
|
|
|
l_obj_path t_object_path;
|
2026-04-08 16:23:02 +02:00
|
|
|
begin
|
|
|
|
|
pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES');
|
|
|
|
|
p_assert_allowed(i_object_key);
|
|
|
|
|
|
2026-04-10 12:41:44 +02:00
|
|
|
if i_new_name is null or length(trim(i_new_name)) = 0
|
|
|
|
|
then
|
|
|
|
|
raise_application_error(-20013, 'Neuer Dateiname darf nicht leer sein');
|
|
|
|
|
end if;
|
|
|
|
|
|
|
|
|
|
if instr(i_new_name, '/') > 0
|
|
|
|
|
then
|
2026-04-10 15:26:40 +02:00
|
|
|
raise_application_error(-20014, 'Dateiname darf keinen Schrägstrich enthalten — zum Verschieben explizite Verschieben-Funktion verwenden');
|
2026-04-10 12:41:44 +02:00
|
|
|
end if;
|
|
|
|
|
|
2026-04-10 15:26:40 +02:00
|
|
|
l_obj_path := f_split_object_key(i_object_key);
|
|
|
|
|
l_new_key := l_obj_path.path || i_new_name;
|
|
|
|
|
p_assert_allowed(l_new_key);
|
|
|
|
|
|
|
|
|
|
if l_new_key = i_object_key
|
2026-04-08 16:23:02 +02:00
|
|
|
then
|
2026-04-10 15:26:40 +02:00
|
|
|
raise_application_error(-20016, 'Der Dateiname darf beim Umbenennen nicht unverändert bleiben.');
|
2026-04-08 16:23:02 +02:00
|
|
|
end if;
|
|
|
|
|
|
2026-04-09 08:05:55 +02:00
|
|
|
select json_object(
|
|
|
|
|
'sourceName' value i_object_key
|
|
|
|
|
,'newName' value l_new_key
|
|
|
|
|
)
|
|
|
|
|
into l_body
|
|
|
|
|
from dual;
|
2026-04-08 16:23:02 +02:00
|
|
|
|
2026-04-10 15:26:40 +02:00
|
|
|
l_response := f_make_request(
|
|
|
|
|
i_method => 'POST'
|
|
|
|
|
,i_url => f_build_url(i_action => 'renameObject')
|
|
|
|
|
,i_body_clob => l_body
|
|
|
|
|
,i_content_type => 'application/json'
|
|
|
|
|
);
|
2026-04-09 11:36:47 +02:00
|
|
|
|
|
|
|
|
pck_log.p_info(
|
|
|
|
|
i_module => c_log_module
|
|
|
|
|
,i_action => 'RENAME'
|
2026-04-09 13:10:47 +02:00
|
|
|
,i_message => 'Datei "' || l_obj_path.filename || '" umbenannt in "' || i_new_name || '" | Ordner: ' || l_obj_path.path
|
2026-04-09 11:36:47 +02:00
|
|
|
,i_object_ref => i_object_key
|
|
|
|
|
);
|
2026-04-08 16:23:02 +02:00
|
|
|
end p_rename_object;
|
|
|
|
|
|
|
|
|
|
procedure p_move_object (
|
|
|
|
|
i_object_key in varchar2
|
|
|
|
|
,i_target_prefix in varchar2
|
|
|
|
|
)
|
|
|
|
|
/*Kopf------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Beschreibung: Verschiebt ein Objekt in einen anderen Ordner im selben Bucket.
|
|
|
|
|
-- Verwendet die OCI renameObject-Action (kein physisches Kopieren).
|
|
|
|
|
-- Der Dateiname bleibt erhalten; nur der Pfad ändert sich.
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Parameter: i_object_key Vollständiger Objektschlüssel der Quelldatei
|
|
|
|
|
-- i_target_prefix Zielpräfix inkl. trailing Slash (z.B. verarbeitet/batch-001/)
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- MA Datum Änderung
|
|
|
|
|
-- SCK 2026-04-08 Prozedur erstellt
|
|
|
|
|
------------------------------------------------------------------------------------------------Kopf*/
|
|
|
|
|
is
|
2026-04-10 12:41:44 +02:00
|
|
|
l_filename varchar2(1024);
|
|
|
|
|
l_new_key varchar2(1024);
|
|
|
|
|
l_body clob;
|
|
|
|
|
l_response clob;
|
|
|
|
|
l_obj_path t_object_path;
|
|
|
|
|
l_target_prefix varchar2(1024);
|
2026-04-08 16:23:02 +02:00
|
|
|
begin
|
|
|
|
|
pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES');
|
|
|
|
|
p_assert_allowed(i_object_key);
|
|
|
|
|
|
2026-04-10 12:41:44 +02:00
|
|
|
if i_target_prefix is null
|
|
|
|
|
then
|
|
|
|
|
raise_application_error(-20015, 'Zielpräfix darf nicht null sein');
|
|
|
|
|
end if;
|
|
|
|
|
|
|
|
|
|
l_target_prefix := f_normalize_prefix(i_target_prefix);
|
|
|
|
|
p_assert_allowed(l_target_prefix);
|
|
|
|
|
|
2026-04-08 16:23:02 +02:00
|
|
|
-- Dateinamen aus dem aktuellen Key extrahieren
|
|
|
|
|
if instr(i_object_key, '/') > 0
|
|
|
|
|
then
|
|
|
|
|
l_filename := substr(i_object_key, instr(i_object_key, '/', -1) + 1);
|
|
|
|
|
else
|
|
|
|
|
l_filename := i_object_key;
|
|
|
|
|
end if;
|
|
|
|
|
|
2026-04-10 12:41:44 +02:00
|
|
|
l_new_key := l_target_prefix || l_filename;
|
2026-04-08 16:23:02 +02:00
|
|
|
p_assert_allowed(l_new_key);
|
|
|
|
|
|
2026-04-09 08:05:55 +02:00
|
|
|
select json_object(
|
|
|
|
|
'sourceName' value i_object_key
|
|
|
|
|
,'newName' value l_new_key
|
|
|
|
|
)
|
|
|
|
|
into l_body
|
|
|
|
|
from dual;
|
2026-04-08 16:23:02 +02:00
|
|
|
|
2026-04-09 13:10:47 +02:00
|
|
|
-- TEST
|
|
|
|
|
--l_response := f_make_request(
|
|
|
|
|
-- i_method => 'POST'
|
|
|
|
|
-- ,i_url => f_build_url(i_action => 'renameObject')
|
|
|
|
|
-- ,i_body_clob => l_body
|
|
|
|
|
-- ,i_content_type => 'application/json'
|
|
|
|
|
--);
|
2026-04-09 11:36:47 +02:00
|
|
|
|
2026-04-09 13:10:47 +02:00
|
|
|
l_obj_path := f_split_object_key(i_object_key);
|
2026-04-09 11:36:47 +02:00
|
|
|
pck_log.p_info(
|
|
|
|
|
i_module => c_log_module
|
|
|
|
|
,i_action => 'MOVE'
|
2026-04-10 12:41:44 +02:00
|
|
|
,i_message => 'Datei "' || l_obj_path.filename || '" verschoben | Von: ' || l_obj_path.path || ' | Nach: ' || l_target_prefix
|
2026-04-09 11:36:47 +02:00
|
|
|
,i_object_ref => i_object_key
|
|
|
|
|
);
|
2026-04-08 16:23:02 +02:00
|
|
|
end p_move_object;
|
|
|
|
|
|
|
|
|
|
procedure p_create_folder (
|
2026-04-10 15:45:47 +02:00
|
|
|
i_parent_folder in varchar2
|
2026-04-08 16:23:02 +02:00
|
|
|
,i_folder_name in varchar2
|
|
|
|
|
)
|
|
|
|
|
/*Kopf------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Beschreibung: Legt einen neuen Ordner im OCI Bucket an.
|
|
|
|
|
-- Ordner werden als leeres Objekt mit trailing Slash simuliert.
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
2026-04-10 15:45:47 +02:00
|
|
|
-- Parameter: i_parent_folder Übergeordneter Pfad inkl. trailing Slash (z.B. eingang/)
|
2026-04-08 16:23:02 +02:00
|
|
|
-- i_folder_name Name des neuen Ordners (ohne Slash)
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- MA Datum Änderung
|
|
|
|
|
-- SCK 2026-04-08 Prozedur erstellt
|
|
|
|
|
------------------------------------------------------------------------------------------------Kopf*/
|
|
|
|
|
is
|
|
|
|
|
l_folder_key varchar2(1024);
|
|
|
|
|
l_response clob;
|
2026-04-09 13:10:47 +02:00
|
|
|
l_obj_path t_object_path;
|
2026-04-10 15:45:47 +02:00
|
|
|
l_prefix varchar2(1024) := f_normalize_prefix(i_parent_folder);
|
2026-04-08 16:23:02 +02:00
|
|
|
begin
|
|
|
|
|
pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES');
|
2026-04-10 12:41:44 +02:00
|
|
|
|
|
|
|
|
if i_folder_name is null or length(trim(i_folder_name)) = 0
|
|
|
|
|
then
|
|
|
|
|
raise_application_error(-20010, 'Ordnername darf nicht leer sein');
|
|
|
|
|
end if;
|
|
|
|
|
|
|
|
|
|
if instr(i_folder_name, '/') > 0
|
|
|
|
|
then
|
|
|
|
|
raise_application_error(-20011, 'Ordnername darf keinen Schrägstrich enthalten');
|
|
|
|
|
end if;
|
|
|
|
|
|
|
|
|
|
if l_prefix is not null
|
|
|
|
|
then
|
|
|
|
|
p_assert_allowed(l_prefix);
|
|
|
|
|
end if;
|
2026-04-08 16:23:02 +02:00
|
|
|
|
|
|
|
|
-- Ordner als leeres Objekt mit trailing Slash anlegen
|
2026-04-10 12:41:44 +02:00
|
|
|
l_folder_key := l_prefix || i_folder_name || '/';
|
2026-04-08 16:23:02 +02:00
|
|
|
p_assert_allowed(l_folder_key);
|
|
|
|
|
|
2026-04-10 12:41:44 +02:00
|
|
|
l_response := f_make_request(
|
|
|
|
|
i_method => 'PUT'
|
|
|
|
|
,i_url => f_build_url(l_folder_key)
|
|
|
|
|
,i_body_blob => empty_blob()
|
|
|
|
|
,i_content_type => 'application/octet-stream'
|
|
|
|
|
);
|
2026-04-09 11:36:47 +02:00
|
|
|
|
2026-04-09 13:10:47 +02:00
|
|
|
l_obj_path := f_split_object_key(l_folder_key);
|
2026-04-09 11:36:47 +02:00
|
|
|
pck_log.p_info(
|
|
|
|
|
i_module => c_log_module
|
|
|
|
|
,i_action => 'CREATE_FOLDER'
|
2026-04-09 13:10:47 +02:00
|
|
|
,i_message => 'Ordner "' || l_obj_path.filename || '" angelegt | Pfad: ' || l_obj_path.path
|
2026-04-09 11:36:47 +02:00
|
|
|
,i_object_ref => l_folder_key
|
|
|
|
|
);
|
2026-04-08 16:23:02 +02:00
|
|
|
end p_create_folder;
|
|
|
|
|
|
|
|
|
|
function f_get_object_metadata (i_object_key in varchar2) return t_object_meta
|
|
|
|
|
/*Kopf------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Beschreibung: Ruft die Metadaten eines Objekts per HEAD-Request ab (kein Download des Inhalts).
|
|
|
|
|
-- Liest Größe, Content-Type, Last-Modified und ETag aus den Response-Headern.
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Parameter: i_object_key Vollständiger Objektschlüssel im Bucket
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- Rückgabe: t_object_meta Record mit object_name, object_size, last_modified, content_type, etag
|
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
|
-- MA Datum Änderung
|
|
|
|
|
-- SCK 2026-04-08 Funktion erstellt
|
|
|
|
|
------------------------------------------------------------------------------------------------Kopf*/
|
|
|
|
|
is
|
|
|
|
|
l_response clob;
|
|
|
|
|
l_result t_object_meta;
|
|
|
|
|
l_hdr_name varchar2(256);
|
|
|
|
|
begin
|
|
|
|
|
pck_mitarbeiterrecht.p_hat_recht('LESEN_ALLES');
|
|
|
|
|
p_assert_allowed(i_object_key);
|
|
|
|
|
|
|
|
|
|
-- HEAD-Anfrage: leerer Response-Body, Metadaten in Response-Headern
|
|
|
|
|
l_response := f_make_request(
|
|
|
|
|
i_method => 'HEAD'
|
|
|
|
|
,i_url => f_build_url(i_object_key)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
l_result.object_name := i_object_key;
|
|
|
|
|
|
|
|
|
|
for i in 1..apex_web_service.g_headers.count
|
|
|
|
|
loop
|
|
|
|
|
l_hdr_name := lower(apex_web_service.g_headers(i).name);
|
|
|
|
|
|
|
|
|
|
case l_hdr_name
|
|
|
|
|
when 'content-length'
|
|
|
|
|
then
|
|
|
|
|
l_result.object_size := to_number(apex_web_service.g_headers(i).value);
|
|
|
|
|
when 'content-type'
|
|
|
|
|
then
|
|
|
|
|
l_result.content_type := apex_web_service.g_headers(i).value;
|
|
|
|
|
when 'last-modified'
|
|
|
|
|
then
|
|
|
|
|
-- HTTP RFC 7231 Format: "Thu, 01 Jan 2026 00:00:00 GMT"
|
|
|
|
|
l_result.last_modified := to_date(
|
|
|
|
|
apex_web_service.g_headers(i).value
|
|
|
|
|
,'DY, DD MON YYYY HH24:MI:SS "GMT"'
|
|
|
|
|
,'NLS_DATE_LANGUAGE=AMERICAN'
|
|
|
|
|
);
|
|
|
|
|
when 'etag'
|
|
|
|
|
then
|
|
|
|
|
l_result.etag := apex_web_service.g_headers(i).value;
|
|
|
|
|
else
|
|
|
|
|
null;
|
|
|
|
|
end case;
|
|
|
|
|
end loop;
|
|
|
|
|
|
|
|
|
|
return l_result;
|
|
|
|
|
end f_get_object_metadata;
|
|
|
|
|
|
|
|
|
|
end pck_net_storage;
|
|
|
|
|
/
|