package de.galabau.dateieingang.oci; import com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider; import com.oracle.bmc.objectstorage.ObjectStorage; import com.oracle.bmc.objectstorage.ObjectStorageClient; import com.oracle.bmc.objectstorage.requests.PutObjectRequest; import de.galabau.dateieingang.config.OciConfig; import de.galabau.dateieingang.exception.OciException; import de.galabau.dateieingang.model.FileEntry; import de.galabau.dateieingang.model.ProcessingContext; import de.galabau.dateieingang.model.ProcessingStatus; import io.quarkus.logging.Log; import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; /** * Lädt die entpackten Dateien und den Marker in OCI Object Storage hoch. * Authentifizierung via OCI HTTP Signature V1 (entspricht APEX Web Credential vom Typ OCI). */ @ApplicationScoped public class OciUploadService { @Inject OciConfig config; private ObjectStorage client; @PostConstruct void init() { SimpleAuthenticationDetailsProvider auth = SimpleAuthenticationDetailsProvider.builder() .tenantId(config.tenancyId()) .userId(config.userId()) .fingerprint(config.fingerprint()) .region(com.oracle.bmc.Region.fromRegionId(config.region())) .privateKeySupplier(() -> { try { return Files.newInputStream(Path.of(config.privateKeyPath())); } catch (IOException e) { throw new RuntimeException("OCI Private Key nicht lesbar: " + config.privateKeyPath(), e); } }) .build(); this.client = ObjectStorageClient.builder().build(auth); } /** * Lädt alle Dateien aus {@code context.extractedFiles} sowie den Marker in OCI hoch. * Dateien mit {@code isMarker = true} werden übersprungen — der Marker wird separat * am Ende hochgeladen, um sicherzustellen dass er erst nach allen Dateien erscheint. * * @param context enthält die Liste der hochzuladenden Dateien und den Ziel-Prefix * @throws OciException bei Verbindungs- oder Upload-Fehlern */ public void upload(ProcessingContext context) throws OciException { context.status = ProcessingStatus.PARTIALLY_UPLOADED; List files = context.extractedFiles.stream() .filter(e -> !e.isMarker) .toList(); for (FileEntry entry : files) { String key = buildKey(context.zipNameWithoutExt, entry.relativePath); entry.ociKey = key; putFile(key, context.localExtractDir.resolve(entry.relativePath), entry.fileSize); Log.debugf("Datei hochgeladen: %s (%d Bytes)", key, entry.fileSize); } String markerKey = buildKey(context.zipNameWithoutExt, "_READY_FOR_DB_PROCESSING_"); putEmpty(markerKey); context.markerUploaded = true; context.status = ProcessingStatus.MARKER_UPLOADED; Log.infof("OCI-Upload abgeschlossen: %d Datei(en) + Marker in '%s'", files.size(), buildPrefix(context.zipNameWithoutExt)); } private String buildPrefix(String zipNameWithoutExt) { return config.tenantPrefix() + config.incomingPrefix() + zipNameWithoutExt + "/"; } private String buildKey(String zipNameWithoutExt, String relativePath) { return buildPrefix(zipNameWithoutExt) + relativePath; } private void putFile(String key, Path localFile, long fileSize) throws OciException { try (InputStream is = Files.newInputStream(localFile)) { client.putObject(PutObjectRequest.builder() .namespaceName(config.namespace()) .bucketName(config.bucket()) .objectName(key) .putObjectBody(is) .contentLength(fileSize) .build()); } catch (Exception e) { throw new OciException("OCI-Upload fehlgeschlagen für '" + key + "'", e); } } private void putEmpty(String key) throws OciException { try (InputStream is = InputStream.nullInputStream()) { client.putObject(PutObjectRequest.builder() .namespaceName(config.namespace()) .bucketName(config.bucket()) .objectName(key) .putObjectBody(is) .contentLength(0L) .build()); } catch (Exception e) { throw new OciException("OCI-Upload Marker fehlgeschlagen für '" + key + "'", e); } } }