starface-mail2fax/v8-9-10/Mail2FaxBlock.java

855 lines
32 KiB
Java

import de.vertico.starface.module.core.model.VariableType;
import de.vertico.starface.module.core.model.Visibility;
import de.vertico.starface.module.core.runtime.IBaseExecutable;
import de.vertico.starface.module.core.runtime.IRuntimeEnvironment;
import de.vertico.starface.module.core.runtime.annotations.Function;
import de.vertico.starface.module.core.runtime.annotations.InputVar;
import de.vertico.starface.module.core.runtime.annotations.OutputVar;
import javax.mail.*;
import javax.mail.internet.MimeBodyPart;
import javax.mail.search.FlagTerm;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
/**
* Mail2FaxBlock - STARFACE Custom Block
*
* Ruft E-Mails ab und sendet PDF-Anhänge als Fax.
* Die Ziel-Faxnummer wird aus dem E-Mail-Betreff gelesen.
*
* Für STARFACE 8.x, 9.x, 10.x (Java 21)
*/
@Function(
visibility = Visibility.Private,
description = "Ruft E-Mails ab und sendet PDF-Anhänge als Fax"
)
public class Mail2FaxBlock implements IBaseExecutable {
// Fax result constants (instead of inner class to avoid separate .class file)
private static final int FAX_SUCCESS = 0;
private static final int FAX_RETRY = 1;
private static final int FAX_FAIL = 2;
// Lock um parallele Ausführung zu verhindern
private static final ReentrantLock executionLock = new ReentrantLock();
// Pfade für Tracking-Dateien
private static final String DATA_DIR = "/var/starface/module-data";
private static final String PROCESSED_FILE = DATA_DIR + "/mail2fax_processed.txt";
private static final String RETRY_FILE = DATA_DIR + "/mail2fax_retry.txt";
// ============================================================
// INPUT VARIABLEN - werden im Module Designer konfiguriert
// ============================================================
@InputVar(
label = "Mail Server",
description = "IMAP oder POP3 Server Adresse",
type = VariableType.STRING
)
public String mailServer = "";
@InputVar(
label = "Mail Port",
description = "Server Port (993 für IMAPS, 995 für POP3S, 143 für IMAP, 110 für POP3)",
type = VariableType.NUMBER
)
public Integer mailPort = 993;
@InputVar(
label = "Protokoll",
description = "IMAP oder POP3",
type = VariableType.STRING
)
public String mailProtocol = "IMAP";
@InputVar(
label = "Benutzername",
description = "E-Mail Benutzername",
type = VariableType.STRING
)
public String mailUsername = "";
@InputVar(
label = "Passwort",
description = "E-Mail Passwort",
type = VariableType.STRING
)
public String mailPassword = "";
@InputVar(
label = "SSL verwenden",
description = "SSL/TLS für die Verbindung aktivieren",
type = VariableType.BOOLEAN
)
public Boolean mailUseSsl = true;
@InputVar(
label = "Ordner",
description = "E-Mail Ordner (Standard: INBOX)",
type = VariableType.STRING
)
public String mailFolder = "INBOX";
@InputVar(
label = "Nach Verarbeitung löschen",
description = "E-Mails nach erfolgreicher Verarbeitung löschen (nur IMAP, bei POP3 werden E-Mails immer nach erfolgreichem Versand gelöscht)",
type = VariableType.BOOLEAN
)
public Boolean deleteAfterProcess = false;
@InputVar(
label = "Fax-Benutzer",
description = "STARFACE Benutzer für den Fax-Versand (muss Fax-Berechtigung haben)",
type = VariableType.STARFACE_USER
)
public Integer faxAccountId = 0;
@InputVar(
label = "Absender-Faxnummer",
description = "Ausgehende Faxnummer (z.B. +49721123456)",
type = VariableType.STRING
)
public String faxSenderNumber = "";
@InputVar(
label = "Erlaubte Absender",
description = "Komma-getrennte Liste erlaubter Absender-Adressen (leer = alle erlaubt)",
type = VariableType.STRING
)
public String authorizedSenders = "";
@InputVar(
label = "Betreff-Präfix",
description = "Präfix im Betreff (optional). Wenn gesetzt, muss der Betreff mit diesem Präfix beginnen (z.B. 'FAXMaik:'). Ermöglicht Filter-Regeln im E-Mail-Client für mehrere Fax-Instanzen.",
type = VariableType.STRING
)
public String subjectPrefix = "";
@InputVar(
label = "PIN",
description = "Sicherheits-PIN (optional). Wenn gesetzt, muss die PIN im E-Mail-Text enthalten sein um das Fax zu senden.",
type = VariableType.STRING
)
public String pin = "";
@InputVar(
label = "Max. Wiederholungen",
description = "Maximale Anzahl Wiederholungsversuche bei besetzter Leitung",
type = VariableType.NUMBER
)
public Integer maxRetries = 3;
@InputVar(
label = "Wartezeit (Minuten)",
description = "Minuten zwischen Wiederholungsversuchen",
type = VariableType.NUMBER
)
public Integer retryDelayMinutes = 5;
// ============================================================
// OUTPUT VARIABLEN - Ergebnisse der Ausführung
// ============================================================
@OutputVar(
label = "Verarbeitete E-Mails",
description = "Anzahl der verarbeiteten E-Mails",
type = VariableType.NUMBER
)
public Integer processedCount = 0;
@OutputVar(
label = "Gesendete Faxe",
description = "Anzahl erfolgreich gesendeter Faxe",
type = VariableType.NUMBER
)
public Integer sentFaxCount = 0;
@OutputVar(
label = "Fehleranzahl",
description = "Anzahl der Fehler",
type = VariableType.NUMBER
)
public Integer errorCount = 0;
@OutputVar(
label = "Wartende Wiederholungen",
description = "Anzahl der Faxe die auf Wiederholung warten",
type = VariableType.NUMBER
)
public Integer pendingRetries = 0;
@OutputVar(
label = "Status",
description = "Status-Meldung der letzten Ausführung",
type = VariableType.STRING
)
public String statusMessage = "";
// Runtime Environment
private IRuntimeEnvironment runtime;
private org.apache.logging.log4j.Logger log;
@Override
public void execute(IRuntimeEnvironment runtime) throws Exception {
this.runtime = runtime;
this.log = runtime.getLog();
// Pflichtfelder validieren
List<String> missingFields = new ArrayList<>();
if (mailServer == null || mailServer.trim().isEmpty()) {
missingFields.add("mailServer (Mail Server)");
}
if (mailPort == null || mailPort <= 0) {
missingFields.add("mailPort (Port)");
}
if (mailUsername == null || mailUsername.trim().isEmpty()) {
missingFields.add("mailUsername (Benutzername)");
}
if (mailPassword == null || mailPassword.trim().isEmpty()) {
missingFields.add("mailPassword (Passwort)");
}
if (faxAccountId == null || faxAccountId <= 0) {
missingFields.add("faxAccountId (Fax-Benutzer)");
}
if (faxSenderNumber == null || faxSenderNumber.trim().isEmpty()) {
missingFields.add("faxSenderNumber (Absender-Faxnummer)");
}
if (!missingFields.isEmpty()) {
statusMessage = "Konfigurationsfehler: Pflichtfelder nicht ausgefüllt";
log.error("Mail2Fax: " + statusMessage + ": " + String.join(", ", missingFields));
return;
}
// Versuche Lock zu bekommen - wenn nicht verfügbar, läuft bereits eine Instanz
if (!executionLock.tryLock()) {
log.info("Mail2Fax: Bereits eine Instanz aktiv, überspringe Ausführung");
statusMessage = "Übersprungen - bereits aktiv";
return;
}
try {
log.info("Mail2Fax: Starte E-Mail-Abruf von " + mailServer);
// Datenverzeichnis erstellen
Files.createDirectories(Paths.get(DATA_DIR));
// Zuerst Retry-Queue verarbeiten
processRetryQueue();
// Dann neue E-Mails abrufen
fetchAndProcessEmails();
statusMessage = String.format(
"Fertig: %d E-Mails verarbeitet, %d Faxe gesendet, %d Fehler, %d warten auf Retry",
processedCount, sentFaxCount, errorCount, pendingRetries
);
log.info("Mail2Fax: " + statusMessage);
} finally {
executionLock.unlock();
}
}
/**
* Verarbeitet die Retry-Queue für fehlgeschlagene Faxe
*/
private void processRetryQueue() {
try {
Path retryPath = Paths.get(RETRY_FILE);
if (!Files.exists(retryPath)) {
return;
}
List<String> lines = Files.readAllLines(retryPath);
List<String> remaining = new ArrayList<>();
long now = System.currentTimeMillis();
for (String line : lines) {
if (line.trim().isEmpty()) continue;
// Format: timestamp|retryCount|destination|senderNumber|pdfPath|messageId
String[] parts = line.split("\\|", 6);
if (parts.length < 6) continue;
long scheduledTime = Long.parseLong(parts[0]);
int retryCount = Integer.parseInt(parts[1]);
String destination = parts[2];
String senderNumber = parts[3];
String pdfPath = parts[4];
String messageId = parts[5];
if (now < scheduledTime) {
// Noch nicht Zeit für Retry
remaining.add(line);
pendingRetries++;
continue;
}
// Retry durchführen
File pdfFile = new File(pdfPath);
if (!pdfFile.exists()) {
log.warn("Mail2Fax: Retry-PDF nicht mehr vorhanden: " + pdfPath);
continue;
}
log.info("Mail2Fax: Retry #" + retryCount + " für " + destination);
int result = sendFax(destination, senderNumber, pdfFile);
if (result == FAX_SUCCESS) {
sentFaxCount++;
pdfFile.delete(); // Temp-Datei löschen
markAsProcessed(messageId);
} else if (result == FAX_RETRY && retryCount < maxRetries) {
// Erneut in Queue
long nextRetry = now + (retryDelayMinutes * 60 * 1000L);
remaining.add(nextRetry + "|" + (retryCount + 1) + "|" + destination + "|" +
senderNumber + "|" + pdfPath + "|" + messageId);
pendingRetries++;
} else {
log.error("Mail2Fax: Endgültig fehlgeschlagen nach " + retryCount + " Versuchen: " + destination);
errorCount++;
pdfFile.delete();
}
}
// Aktualisierte Queue speichern
Files.write(retryPath, remaining);
} catch (Exception e) {
log.error("Mail2Fax: Fehler bei Retry-Queue: " + e.getMessage(), e);
}
}
/**
* Ruft E-Mails ab und verarbeitet sie
*/
private void fetchAndProcessEmails() {
Store store = null;
Folder folder = null;
try {
// Session konfigurieren
Properties props = new Properties();
String protocol = mailProtocol.toUpperCase();
if (protocol.equals("IMAP")) {
if (mailUseSsl) {
props.put("mail.store.protocol", "imaps");
props.put("mail.imaps.host", mailServer);
props.put("mail.imaps.port", String.valueOf(mailPort));
props.put("mail.imaps.ssl.enable", "true");
} else {
props.put("mail.store.protocol", "imap");
props.put("mail.imap.host", mailServer);
props.put("mail.imap.port", String.valueOf(mailPort));
}
} else { // POP3
if (mailUseSsl) {
props.put("mail.store.protocol", "pop3s");
props.put("mail.pop3s.host", mailServer);
props.put("mail.pop3s.port", String.valueOf(mailPort));
props.put("mail.pop3s.ssl.enable", "true");
} else {
props.put("mail.store.protocol", "pop3");
props.put("mail.pop3.host", mailServer);
props.put("mail.pop3.port", String.valueOf(mailPort));
}
}
Session session = Session.getInstance(props);
store = session.getStore();
store.connect(mailServer, mailPort, mailUsername, mailPassword);
folder = store.getFolder(mailFolder);
folder.open(Folder.READ_WRITE);
// Nachrichten abrufen
Message[] messages;
if (protocol.equals("IMAP")) {
// IMAP: Nur ungelesene
messages = folder.search(new FlagTerm(new Flags(Flags.Flag.SEEN), false));
} else {
// POP3: Alle (Tracking über processed-Liste)
messages = folder.getMessages();
}
log.info("Mail2Fax: " + messages.length + " Nachrichten gefunden");
for (Message message : messages) {
try {
processMessage(message, protocol.equals("POP3"), folder);
processedCount++;
} catch (Exception e) {
log.error("Mail2Fax: Fehler bei Nachricht: " + e.getMessage(), e);
errorCount++;
}
}
} catch (Exception e) {
// Bei Verbindungsfehlern nur eine saubere Meldung ohne Stacktrace
String msg = e.getMessage();
if (e instanceof com.sun.mail.util.MailConnectException ||
e instanceof java.net.ConnectException ||
e instanceof java.net.UnknownHostException ||
e instanceof java.net.SocketTimeoutException ||
(msg != null && (msg.contains("Connection refused") ||
msg.contains("connect") ||
msg.contains("timeout") ||
msg.contains("Unknown host")))) {
log.error("Mail2Fax: Verbindung zu " + mailServer + ":" + mailPort + " fehlgeschlagen - " + msg);
} else {
log.error("Mail2Fax: E-Mail-Abruf fehlgeschlagen: " + msg, e);
}
errorCount++;
statusMessage = "Fehler: " + msg;
} finally {
try {
if (folder != null && folder.isOpen()) {
folder.close(true); // expunge deleted messages
}
if (store != null) {
store.close();
}
} catch (Exception e) {
log.warn("Mail2Fax: Fehler beim Schließen: " + e.getMessage());
}
}
}
/**
* Verarbeitet eine einzelne E-Mail
*/
private void processMessage(Message message, boolean isPop3, Folder folder) throws Exception {
String messageId = getMessageId(message);
// Bei POP3: Prüfen ob bereits verarbeitet
if (isPop3 && isAlreadyProcessed(messageId)) {
log.debug("Mail2Fax: Nachricht bereits verarbeitet: " + messageId);
return;
}
String from = message.getFrom()[0].toString();
String subject = message.getSubject();
log.info("Mail2Fax: Verarbeite E-Mail von " + from + " - Betreff: " + subject);
// Absender prüfen
if (!isAuthorizedSender(from)) {
log.warn("Mail2Fax: Nicht autorisierter Absender: " + from);
if (!isPop3) {
message.setFlag(Flags.Flag.SEEN, true);
}
return;
}
// PIN prüfen (wenn gesetzt)
if (pin != null && !pin.trim().isEmpty()) {
String emailBody = getEmailBodyText(message);
if (emailBody == null || !emailBody.contains(pin)) {
log.error("Mail2Fax: PIN in Emailtext nicht vorhanden oder falsch");
if (!isPop3) {
message.setFlag(Flags.Flag.SEEN, true);
}
return;
}
log.debug("Mail2Fax: PIN erfolgreich validiert");
}
// Ziel-Faxnummer aus Betreff extrahieren
String destination = extractFaxNumber(subject);
if (destination == null || destination.isEmpty()) {
// Spezifische Fehlermeldung je nach Grund
if (subjectPrefix != null && !subjectPrefix.trim().isEmpty() && !subject.startsWith(subjectPrefix.trim())) {
// Präfix stimmt nicht überein → Email NICHT als gelesen markieren
// So können andere Instanzen mit passendem Präfix die Email verarbeiten
log.debug("Mail2Fax: Betreff-Präfix stimmt nicht überein. Erwartet: '" + subjectPrefix.trim() + "', Betreff: " + subject + " - Email bleibt ungelesen für andere Instanzen");
return; // Ohne setFlag(SEEN) - andere Instanz könnte passen
} else {
// Keine gültige Faxnummer → als gelesen markieren
log.warn("Mail2Fax: Keine gültige Faxnummer im Betreff: " + subject);
if (!isPop3) {
message.setFlag(Flags.Flag.SEEN, true);
}
}
return;
}
// PDF-Anhänge suchen und verarbeiten
List<File> pdfFiles = extractPdfAttachments(message);
if (pdfFiles.isEmpty()) {
log.warn("Mail2Fax: Keine PDF-Anhänge gefunden");
if (!isPop3) {
message.setFlag(Flags.Flag.SEEN, true);
}
return;
}
// Jeden PDF-Anhang als Fax senden
boolean allSuccess = true;
for (File pdfFile : pdfFiles) {
int result = sendFax(destination, faxSenderNumber, pdfFile);
if (result == FAX_SUCCESS) {
sentFaxCount++;
pdfFile.delete();
} else if (result == FAX_RETRY) {
// In Retry-Queue aufnehmen
addToRetryQueue(destination, faxSenderNumber, pdfFile, messageId);
allSuccess = false;
} else {
errorCount++;
pdfFile.delete();
allSuccess = false;
}
}
// E-Mail als verarbeitet markieren
if (allSuccess) {
if (isPop3) {
// POP3: Als verarbeitet speichern und löschen
markAsProcessed(messageId);
message.setFlag(Flags.Flag.DELETED, true);
log.info("Mail2Fax: POP3-Nachricht zum Löschen markiert");
} else {
// IMAP: Als gelesen markieren, optional löschen
message.setFlag(Flags.Flag.SEEN, true);
if (deleteAfterProcess) {
message.setFlag(Flags.Flag.DELETED, true);
}
}
}
}
/**
* Ermittelt die Message-ID einer E-Mail
*/
private String getMessageId(Message message) {
try {
String[] headers = message.getHeader("Message-ID");
if (headers != null && headers.length > 0) {
return headers[0];
}
// Fallback: Hash aus Datum und Betreff
return String.valueOf((message.getSentDate() + "|" + message.getSubject()).hashCode());
} catch (Exception e) {
return String.valueOf(System.currentTimeMillis());
}
}
/**
* Prüft ob eine Nachricht bereits verarbeitet wurde (für POP3)
*/
private boolean isAlreadyProcessed(String messageId) {
try {
Path path = Paths.get(PROCESSED_FILE);
if (!Files.exists(path)) {
return false;
}
List<String> processed = Files.readAllLines(path);
return processed.contains(messageId);
} catch (Exception e) {
return false;
}
}
/**
* Markiert eine Nachricht als verarbeitet (für POP3)
*/
private void markAsProcessed(String messageId) {
try {
Path path = Paths.get(PROCESSED_FILE);
List<String> processed = Files.exists(path) ?
new ArrayList<>(Files.readAllLines(path)) : new ArrayList<>();
if (!processed.contains(messageId)) {
processed.add(messageId);
// Maximal 1000 Einträge behalten
while (processed.size() > 1000) {
processed.remove(0);
}
Files.write(path, processed);
}
} catch (Exception e) {
log.warn("Mail2Fax: Konnte Nachricht nicht als verarbeitet markieren: " + e.getMessage());
}
}
/**
* Fügt ein fehlgeschlagenes Fax zur Retry-Queue hinzu
*/
private void addToRetryQueue(String destination, String senderNumber, File pdfFile, String messageId) {
try {
long nextRetry = System.currentTimeMillis() + (retryDelayMinutes * 60 * 1000L);
String entry = nextRetry + "|1|" + destination + "|" + senderNumber + "|" +
pdfFile.getAbsolutePath() + "|" + messageId;
Path path = Paths.get(RETRY_FILE);
List<String> entries = Files.exists(path) ?
new ArrayList<>(Files.readAllLines(path)) : new ArrayList<>();
entries.add(entry);
Files.write(path, entries);
pendingRetries++;
log.info("Mail2Fax: Fax zur Retry-Queue hinzugefügt: " + destination);
} catch (Exception e) {
log.error("Mail2Fax: Konnte nicht zur Retry-Queue hinzufügen: " + e.getMessage());
}
}
/**
* Prüft ob der Absender autorisiert ist
*/
private boolean isAuthorizedSender(String from) {
if (authorizedSenders == null || authorizedSenders.trim().isEmpty()) {
return true; // Keine Einschränkung
}
String fromLower = from.toLowerCase();
String[] authorized = authorizedSenders.split(",");
for (String auth : authorized) {
if (fromLower.contains(auth.trim().toLowerCase())) {
return true;
}
}
return false;
}
/**
* Extrahiert die Faxnummer aus dem E-Mail-Betreff
*
* Wenn ein Betreff-Präfix konfiguriert ist, muss der Betreff damit beginnen.
* Der Präfix wird dann entfernt, bevor die Faxnummer extrahiert wird.
*
* Beispiel: subjectPrefix="FAXMaik:", subject="FAXMaik: +49721123456" → "+49721123456"
*/
private String extractFaxNumber(String subject) {
if (subject == null || subject.isEmpty()) {
return null;
}
// Wenn Präfix gesetzt ist, prüfen und entfernen
if (subjectPrefix != null && !subjectPrefix.trim().isEmpty()) {
String prefix = subjectPrefix.trim();
if (!subject.startsWith(prefix)) {
log.debug("Mail2Fax: Betreff beginnt nicht mit Präfix '" + prefix + "': " + subject);
return null;
}
// Präfix entfernen
subject = subject.substring(prefix.length()).trim();
log.debug("Mail2Fax: Präfix entfernt, verbleibender Betreff: " + subject);
}
// Entferne alle Zeichen außer Ziffern und +
String cleaned = subject.replaceAll("[^0-9+]", "");
// Prüfe auf gültiges Format
if (cleaned.matches("\\+?[0-9]{6,}")) {
return cleaned;
}
return null;
}
/**
* Extrahiert den Textinhalt einer E-Mail (für PIN-Prüfung)
*/
private String getEmailBodyText(Message message) {
try {
Object content = message.getContent();
if (content instanceof String) {
return (String) content;
} else if (content instanceof Multipart) {
Multipart multipart = (Multipart) content;
StringBuilder bodyText = new StringBuilder();
for (int i = 0; i < multipart.getCount(); i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
String contentType = bodyPart.getContentType().toLowerCase();
// Nur Text-Parts auslesen, keine Anhänge
if (contentType.startsWith("text/plain") || contentType.startsWith("text/html")) {
Object partContent = bodyPart.getContent();
if (partContent instanceof String) {
bodyText.append((String) partContent);
}
}
}
return bodyText.toString();
}
} catch (Exception e) {
log.warn("Mail2Fax: Fehler beim Lesen des E-Mail-Textes: " + e.getMessage());
}
return null;
}
/**
* Extrahiert PDF-Anhänge aus der E-Mail
*/
private List<File> extractPdfAttachments(Message message) throws Exception {
List<File> pdfFiles = new ArrayList<>();
Object content = message.getContent();
if (content instanceof Multipart) {
Multipart multipart = (Multipart) content;
for (int i = 0; i < multipart.getCount(); i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
if (Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition()) ||
(bodyPart.getFileName() != null && bodyPart.getFileName().toLowerCase().endsWith(".pdf"))) {
String fileName = bodyPart.getFileName();
if (fileName != null && fileName.toLowerCase().endsWith(".pdf")) {
// Temp-Datei erstellen
File tempFile = File.createTempFile("mail2fax_", ".pdf");
try (InputStream is = bodyPart.getInputStream();
FileOutputStream fos = new FileOutputStream(tempFile)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
log.info("Mail2Fax: PDF extrahiert: " + fileName);
pdfFiles.add(tempFile);
}
}
}
}
return pdfFiles;
}
/**
* Sendet ein PDF als Fax über die STARFACE API
*
* Verwendet den internen FaxHandler über den StarfaceComponentProvider.
*
* @return FAX_SUCCESS, FAX_RETRY oder FAX_FAIL
*/
private int sendFax(String destination, String callingNumber, File pdfFile) {
try {
log.info("Mail2Fax: Sende Fax an " + destination + " von " + callingNumber);
// Den eingebauten SendFax-Block direkt instanziieren und aufrufen
Class<?> sendFaxClass = Class.forName(
"de.vertico.starface.module.core.runtime.functions.callHandling.call.SendFax"
);
Object sendFaxBlock = sendFaxClass.getDeclaredConstructor().newInstance();
// Input-Variablen setzen via Reflection
// accountId (Integer) - Benutzer-ID für Fax-Versand
setFieldValue(sendFaxClass, sendFaxBlock, "accountId", faxAccountId);
// extention (String) - Ziel-Faxnummer (Tippfehler im Original!)
setFieldValue(sendFaxClass, sendFaxBlock, "extention", destination);
// signalNumber (String) - Absender-Nummer
setFieldValue(sendFaxClass, sendFaxBlock, "signalNumber", callingNumber);
// signalName (String) - Absender-Name (optional, gleich wie Nummer)
setFieldValue(sendFaxClass, sendFaxBlock, "signalName", callingNumber);
// resource (String) - Pfad zur PDF-Datei
setFieldValue(sendFaxClass, sendFaxBlock, "resource", pdfFile.getAbsolutePath());
// execute() aufrufen mit unserem Runtime
java.lang.reflect.Method executeMethod = sendFaxClass.getMethod("execute",
Class.forName("de.vertico.starface.module.core.runtime.IRuntimeEnvironment"));
executeMethod.invoke(sendFaxBlock, runtime);
// Ergebnis prüfen (exitStatus Feld)
try {
java.lang.reflect.Field exitStatusField = sendFaxClass.getDeclaredField("exitStatus");
exitStatusField.setAccessible(true);
Object exitStatus = exitStatusField.get(sendFaxBlock);
log.info("Mail2Fax: SendFax exitStatus: " + exitStatus);
if (exitStatus != null) {
String status = exitStatus.toString();
// BUSY, FAILED oder andere temporäre Fehler → Retry
if (status.contains("BUSY") || status.contains("FAILED") ||
status.contains("NO_ANSWER") || status.contains("TIMEOUT")) {
log.info("Mail2Fax: Temporärer Fehler (" + status + "), wird zur Retry-Queue hinzugefügt");
return FAX_RETRY;
}
// SENT → Erfolg
if (status.contains("SENT")) {
log.info("Mail2Fax: Fax erfolgreich gesendet an " + destination);
return FAX_SUCCESS;
}
// Unbekannter Status → als Fehler behandeln
log.warn("Mail2Fax: Unbekannter exitStatus: " + status);
return FAX_FAIL;
}
} catch (NoSuchFieldException e) {
log.debug("Mail2Fax: Kein exitStatus Feld");
}
// Kein exitStatus gefunden → als Erfolg annehmen (Fallback)
log.info("Mail2Fax: Fax gesendet an " + destination + " (kein exitStatus verfügbar)");
return FAX_SUCCESS;
} catch (Exception e) {
String error = e.getMessage() != null ? e.getMessage().toLowerCase() : "";
log.error("Mail2Fax: Fax-Fehler: " + e.getMessage(), e);
// Prüfe ob Retry sinnvoll ist
if (error.contains("busy") || error.contains("besetzt") ||
error.contains("no answer") || error.contains("keine antwort") ||
error.contains("temporarily") || error.contains("timeout")) {
log.info("Mail2Fax: Temporärer Fehler, wird erneut versucht");
return FAX_RETRY;
}
return FAX_FAIL;
}
}
/**
* Versucht ein Feld zu setzen, gibt true zurück wenn erfolgreich
*/
private boolean trySetField(Class<?> clazz, Object obj, String fieldName, Object value) {
try {
java.lang.reflect.Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
log.debug("Mail2Fax: " + fieldName + " gesetzt: " + value);
return true;
} catch (NoSuchFieldException e) {
return false;
} catch (Exception e) {
log.warn("Mail2Fax: Fehler beim Setzen von " + fieldName + ": " + e.getMessage());
return false;
}
}
/**
* Setzt ein Feld oder wirft Exception
*/
private void setFieldValue(Class<?> clazz, Object obj, String fieldName, Object value) throws Exception {
java.lang.reflect.Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
log.debug("Mail2Fax: " + fieldName + " gesetzt: " + value);
}
}