802 lines
29 KiB
Java
802 lines
29 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 = "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()) {
|
|
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
|
|
*/
|
|
private String extractFaxNumber(String subject) {
|
|
if (subject == null || subject.isEmpty()) {
|
|
return null;
|
|
}
|
|
|
|
// 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 && exitStatus.toString().contains("FAILED")) {
|
|
return FAX_RETRY;
|
|
}
|
|
} catch (NoSuchFieldException e) {
|
|
log.debug("Mail2Fax: Kein exitStatus Feld");
|
|
}
|
|
|
|
log.info("Mail2Fax: Fax erfolgreich gesendet an " + destination);
|
|
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);
|
|
}
|
|
}
|