commit 601a0c275772645cd1483747401cd86d6aaa7ecc Author: Stefan Hacker Date: Sat Jan 10 13:03:28 2026 +0100 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a0bbce --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# Mail2Fax für STARFACE + +Custom Block für den STARFACE Module Designer - E-Mails abrufen und PDF-Anhänge als Fax versenden. + +## Quick Start + +```bash +# 1. Java 21 installieren (falls nötig) +./install-java21.sh + +# 2. STARFACE APIs holen +./fetch-starface-libs.sh + +# 3. Block kompilieren +cd v8-9-10 +./build-block.sh +``` + +## Projektstruktur + +``` +mail2fax/ +├── fetch-starface-libs.sh # Holt STARFACE APIs per SCP +├── install-java21.sh # Installiert Java 21 +├── README.md +└── v8-9-10/ # Für STARFACE 8/9/10 + ├── Mail2FaxBlock.java # Custom Block Quellcode + ├── build-block.sh # Kompilier-Script + ├── libs/starface/ # STARFACE JARs + └── README.md # Detaillierte Anleitung +``` + +## Funktionsweise + +1. Block ruft E-Mails per IMAP/POP3 ab +2. Ziel-Faxnummer wird aus dem Betreff gelesen +3. PDF-Anhänge werden als Fax gesendet +4. E-Mails werden als gelesen markiert oder gelöscht + +## Installation + +Siehe [v8-9-10/README.md](v8-9-10/README.md) für die Anleitung zur Integration im Module Designer. diff --git a/fax2Mail_v36.sfm b/fax2Mail_v36.sfm new file mode 100644 index 0000000..c7cab35 Binary files /dev/null and b/fax2Mail_v36.sfm differ diff --git a/fetch-starface-libs.sh b/fetch-starface-libs.sh new file mode 100755 index 0000000..2674714 --- /dev/null +++ b/fetch-starface-libs.sh @@ -0,0 +1,275 @@ +#!/bin/bash +# +# STARFACE API Libraries Fetcher +# Holt die benötigten JARs und Klassen von einer STARFACE Installation via SCP +# +# Verwendung: +# ./fetch-starface-libs.sh [user] [target-dir] +# +# Beispiele: +# ./fetch-starface-libs.sh 192.168.1.100 +# ./fetch-starface-libs.sh starface.firma.local root +# ./fetch-starface-libs.sh 10.0.0.5 root v8-9-10/libs/starface +# + +set -e + +# Farben für Output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Argumente +STARFACE_HOST="${1:-}" +SSH_USER="${2:-root}" +TARGET_DIR="${3:-v8-9-10/libs/starface}" + +# Script-Verzeichnis +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +echo -e "${BLUE}========================================" +echo " STARFACE API Libraries Fetcher" +echo -e "========================================${NC}" +echo "" + +# Hilfe anzeigen +if [ -z "$STARFACE_HOST" ] || [ "$STARFACE_HOST" == "-h" ] || [ "$STARFACE_HOST" == "--help" ]; then + echo "Verwendung: $0 [user] [target-dir]" + echo "" + echo "Argumente:" + echo " starface-host IP-Adresse oder Hostname der STARFACE (erforderlich)" + echo " user SSH-Benutzer (Standard: root)" + echo " target-dir Zielverzeichnis für die JARs (Standard: v8-9-10/libs/starface)" + echo "" + echo "Beispiele:" + echo " $0 192.168.1.100" + echo " $0 starface.firma.local root" + echo " $0 10.0.0.5 root v8-9-10/libs/starface" + echo "" + echo "Voraussetzungen:" + echo " - SSH-Zugang zur STARFACE (root oder sudo-Benutzer)" + echo " - SSH-Key oder Passwort-Authentifizierung" + echo "" + echo "Tipp: SSH-Key einrichten für passwortloses Login:" + echo " ssh-copy-id root@" + exit 0 +fi + +echo -e "STARFACE Host: ${GREEN}$STARFACE_HOST${NC}" +echo -e "SSH User: ${GREEN}$SSH_USER${NC}" +echo -e "Zielordner: ${GREEN}$TARGET_DIR${NC}" +echo "" + +# Zielverzeichnis erstellen +mkdir -p "$TARGET_DIR" + +# STARFACE Version und Pfade ermitteln +echo -e "${YELLOW}Ermittle STARFACE Version...${NC}" + +STARFACE_VERSION=$(ssh -o ConnectTimeout=10 "${SSH_USER}@${STARFACE_HOST}" \ + "cat /etc/starface-release 2>/dev/null || head -1 /opt/starface/version.txt 2>/dev/null || echo 'unknown'" 2>/dev/null || echo "connection-failed") + +if [ "$STARFACE_VERSION" == "connection-failed" ]; then + echo -e "${RED}Fehler: Konnte keine Verbindung zu $STARFACE_HOST herstellen${NC}" + echo "" + echo "Mögliche Ursachen:" + echo " - Falscher Hostname/IP" + echo " - SSH-Dienst nicht erreichbar" + echo " - Firewall blockiert Port 22" + echo "" + echo "Prüfe die Verbindung mit: ssh ${SSH_USER}@${STARFACE_HOST}" + exit 1 +fi + +echo -e "STARFACE Version: ${GREEN}$STARFACE_VERSION${NC}" +echo "" + +# Pfade +LIB_PATH="/opt/tomcat/webapps/localhost/starface/WEB-INF/lib" +CLASSES_PATH="/opt/tomcat/webapps/localhost/starface/WEB-INF/classes" + +# Prüfen welcher Pfad existiert +echo -e "${YELLOW}Prüfe Verzeichnisstruktur...${NC}" + +ACTUAL_LIB_PATH=$(ssh "${SSH_USER}@${STARFACE_HOST}" \ + "if [ -d '$LIB_PATH' ]; then echo '$LIB_PATH'; fi" 2>/dev/null) + +ACTUAL_CLASSES_PATH=$(ssh "${SSH_USER}@${STARFACE_HOST}" \ + "if [ -d '$CLASSES_PATH' ]; then echo '$CLASSES_PATH'; fi" 2>/dev/null) + +if [ -z "$ACTUAL_LIB_PATH" ]; then + echo -e "${RED}Fehler: STARFACE Bibliotheksverzeichnis nicht gefunden${NC}" + exit 1 +fi + +echo -e "Bibliothekspfad: ${GREEN}$ACTUAL_LIB_PATH${NC}" +echo -e "Klassenpfad: ${GREEN}$ACTUAL_CLASSES_PATH${NC}" +echo "" + +# Alte Stub-Dateien löschen +rm -f "$TARGET_DIR/starface-stubs.jar" 2>/dev/null +rm -f "$TARGET_DIR/starface-classes.jar" 2>/dev/null + +# ============================================================ +# STARFACE 9.x: Module-API ist in /classes, nicht in JARs +# ============================================================ + +echo -e "${YELLOW}Prüfe auf STARFACE 9.x Struktur (Module-API in classes)...${NC}" + +HAS_MODULE_CLASSES=$(ssh "${SSH_USER}@${STARFACE_HOST}" \ + "if [ -d '$CLASSES_PATH/de/vertico/starface/module' ]; then echo 'yes'; fi" 2>/dev/null) + +if [ "$HAS_MODULE_CLASSES" == "yes" ]; then + echo -e "${GREEN}STARFACE 9.x erkannt - Module-API in classes gefunden${NC}" + echo "" + echo -e "${YELLOW}Erstelle JAR aus STARFACE classes...${NC}" + + # Temporäres Verzeichnis auf STARFACE erstellen und JAR bauen + ssh "${SSH_USER}@${STARFACE_HOST}" "cd $CLASSES_PATH && jar cf /tmp/starface-classes.jar de/vertico/starface/module de/vertico/starface/persistence 2>/dev/null || jar cf /tmp/starface-classes.jar de/vertico/starface/module" + + # JAR herunterladen + scp -q "${SSH_USER}@${STARFACE_HOST}:/tmp/starface-classes.jar" "$TARGET_DIR/" + ssh "${SSH_USER}@${STARFACE_HOST}" "rm -f /tmp/starface-classes.jar" + + echo -e " ${GREEN}starface-classes.jar erstellt${NC}" + echo "" +fi + +# ============================================================ +# JAR-Dateien herunterladen +# ============================================================ + +# JARs für STARFACE 9.x (neue Namensgebung) +STARFACE9_JARS=( + "starface-db-*.jar" + "starface-commons-*.jar" + "starface-domain-objects-*.jar" + "starface-rpc-*.jar" + "starface-ng-*.jar" +) + +# Allgemeine JARs (alle Versionen) +COMMON_JARS=( + "log4j-api-*.jar" + "log4j-core-*.jar" +) + +# Legacy JARs (STARFACE 8.x und älter) +LEGACY_JARS=( + "starface-module-*.jar" + "starface-persistence-*.jar" +) + +echo -e "${YELLOW}Lade STARFACE JARs herunter...${NC}" + +download_jars() { + local jar_array=("$@") + for pattern in "${jar_array[@]}"; do + files=$(ssh "${SSH_USER}@${STARFACE_HOST}" "ls $ACTUAL_LIB_PATH/$pattern 2>/dev/null" || true) + + if [ -n "$files" ]; then + for file in $files; do + filename=$(basename "$file") + echo -n " $filename: " + scp -q "${SSH_USER}@${STARFACE_HOST}:$file" "$TARGET_DIR/" 2>/dev/null + echo -e "${GREEN}OK${NC}" + done + fi + done +} + +# Download alle JAR-Typen +download_jars "${STARFACE9_JARS[@]}" +download_jars "${COMMON_JARS[@]}" +download_jars "${LEGACY_JARS[@]}" + +echo "" + +# ============================================================ +# Zusammenfassung +# ============================================================ + +JAR_COUNT=$(ls -1 "$TARGET_DIR"/*.jar 2>/dev/null | wc -l) + +echo -e "${BLUE}========================================" +echo -e " Download abgeschlossen!" +echo -e "========================================${NC}" +echo "" +echo -e "Heruntergeladene Dateien: ${GREEN}$JAR_COUNT${NC}" +echo -e "Speicherort: ${GREEN}$TARGET_DIR/${NC}" +echo "" + +if [ "$JAR_COUNT" -gt 0 ]; then + echo "Inhalt:" + ls -lh "$TARGET_DIR"/*.jar 2>/dev/null | while read line; do + filename=$(echo "$line" | awk '{print $NF}') + size=$(echo "$line" | awk '{print $5}') + basename=$(basename "$filename") + echo -e " ${GREEN}$basename${NC} ($size)" + done + echo "" + + # Prüfen ob Module-API vorhanden ist + if [ -f "$TARGET_DIR/starface-classes.jar" ] || ls "$TARGET_DIR"/starface-module-*.jar 2>/dev/null | grep -q .; then + echo -e "${GREEN}Module-API gefunden - du kannst jetzt bauen:${NC}" + echo " cd v8-9-10 && ./build-block.sh" + else + echo -e "${YELLOW}Warnung: Module-API nicht gefunden${NC}" + echo "Das Build-Script wird Stub-Klassen erstellen." + fi +else + echo -e "${RED}Keine JARs heruntergeladen. Prüfe die Verbindung und Berechtigungen.${NC}" + exit 1 +fi + +# ============================================================ +# JavaMail-Bibliothek prüfen und ggf. installieren +# ============================================================ + +echo "" +echo -e "${YELLOW}Prüfe JavaMail-Bibliothek auf STARFACE...${NC}" + +HAS_JAVAMAIL=$(ssh "${SSH_USER}@${STARFACE_HOST}" \ + "ls $ACTUAL_LIB_PATH/*mail*.jar 2>/dev/null | head -1" || true) + +if [ -n "$HAS_JAVAMAIL" ]; then + echo -e "${GREEN}JavaMail bereits vorhanden:${NC}" + ssh "${SSH_USER}@${STARFACE_HOST}" "ls -1 $ACTUAL_LIB_PATH/*mail*.jar 2>/dev/null" | while read jar; do + echo -e " ${GREEN}$(basename $jar)${NC}" + done +else + echo -e "${YELLOW}JavaMail nicht gefunden - wird installiert...${NC}" + + # JavaMail herunterladen falls nicht lokal vorhanden + DEPS_DIR="$SCRIPT_DIR/v8-9-10/libs/deps" + mkdir -p "$DEPS_DIR" + + if [ ! -f "$DEPS_DIR/javax.mail.jar" ]; then + echo " Lade javax.mail.jar herunter..." + curl -sL -o "$DEPS_DIR/javax.mail.jar" \ + "https://repo1.maven.org/maven2/com/sun/mail/javax.mail/1.6.2/javax.mail-1.6.2.jar" + fi + + if [ ! -f "$DEPS_DIR/activation.jar" ]; then + echo " Lade activation.jar herunter..." + curl -sL -o "$DEPS_DIR/activation.jar" \ + "https://repo1.maven.org/maven2/javax/activation/activation/1.1.1/activation-1.1.1.jar" + fi + + # Auf STARFACE kopieren + echo " Kopiere nach STARFACE..." + scp -q "$DEPS_DIR/javax.mail.jar" "${SSH_USER}@${STARFACE_HOST}:$ACTUAL_LIB_PATH/" + scp -q "$DEPS_DIR/activation.jar" "${SSH_USER}@${STARFACE_HOST}:$ACTUAL_LIB_PATH/" + + echo -e "${GREEN}JavaMail erfolgreich auf STARFACE installiert!${NC}" + echo "" + echo -e "${YELLOW}WICHTIG: STARFACE-Dienst neustarten für Aktivierung:${NC}" + echo " ssh ${SSH_USER}@${STARFACE_HOST} 'systemctl restart tomcat'" +fi + +echo "" +echo -e "${GREEN}Fertig! Du kannst jetzt den Mail2FaxBlock kompilieren:${NC}" +echo " cd v8-9-10 && ./build-block.sh" diff --git a/install-java21.sh b/install-java21.sh new file mode 100755 index 0000000..0f7805a --- /dev/null +++ b/install-java21.sh @@ -0,0 +1,194 @@ +#!/bin/bash +# +# Java 21 JDK Installationsscript +# Für Debian/Ubuntu und andere Linux-Distributionen +# + +set -e + +# Farben +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +echo -e "${BLUE}========================================" +echo " Java 21 JDK Installation" +echo -e "========================================${NC}" +echo "" + +# Prüfen ob Java 21 bereits installiert ist +if command -v javac &> /dev/null; then + JAVA_VERSION=$(javac -version 2>&1 | awk '{print $2}' | cut -d'.' -f1) + if [ "$JAVA_VERSION" == "21" ]; then + echo -e "${GREEN}Java 21 ist bereits installiert!${NC}" + javac -version + java -version + exit 0 + else + echo -e "${YELLOW}Java $JAVA_VERSION gefunden, aber Java 21 wird benötigt${NC}" + fi +fi + +# Betriebssystem erkennen +if [ -f /etc/os-release ]; then + . /etc/os-release + OS=$ID + OS_LIKE=$ID_LIKE +else + OS=$(uname -s) +fi + +echo -e "Erkanntes OS: ${GREEN}$OS${NC}" +echo "" + +install_debian_ubuntu() { + echo -e "${YELLOW}Installiere Java 21 via apt...${NC}" + echo "" + + # Prüfen ob openjdk-21-jdk verfügbar ist + if apt-cache show openjdk-21-jdk &> /dev/null; then + echo "OpenJDK 21 ist in den Repositories verfügbar" + sudo apt update + sudo apt install -y openjdk-21-jdk + else + echo "OpenJDK 21 nicht in Standard-Repos, verwende Adoptium/Temurin..." + + # Adoptium Repository hinzufügen + sudo apt install -y wget apt-transport-https gnupg + + wget -qO - https://packages.adoptium.net/artifactory/api/gpg/key/public | sudo gpg --dearmor -o /usr/share/keyrings/adoptium.gpg + + echo "deb [signed-by=/usr/share/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/adoptium.list + + sudo apt update + sudo apt install -y temurin-21-jdk + fi +} + +install_fedora_rhel() { + echo -e "${YELLOW}Installiere Java 21 via dnf/yum...${NC}" + echo "" + + if command -v dnf &> /dev/null; then + sudo dnf install -y java-21-openjdk-devel + else + sudo yum install -y java-21-openjdk-devel + fi +} + +install_arch() { + echo -e "${YELLOW}Installiere Java 21 via pacman...${NC}" + echo "" + sudo pacman -S --noconfirm jdk21-openjdk +} + +install_opensuse() { + echo -e "${YELLOW}Installiere Java 21 via zypper...${NC}" + echo "" + sudo zypper install -y java-21-openjdk-devel +} + +install_manual() { + echo -e "${YELLOW}Manuelle Installation von Eclipse Temurin JDK 21...${NC}" + echo "" + + ARCH=$(uname -m) + case $ARCH in + x86_64) + JDK_ARCH="x64" + ;; + aarch64) + JDK_ARCH="aarch64" + ;; + *) + echo -e "${RED}Nicht unterstützte Architektur: $ARCH${NC}" + exit 1 + ;; + esac + + JDK_URL="https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2%2B13/OpenJDK21U-jdk_${JDK_ARCH}_linux_hotspot_21.0.2_13.tar.gz" + INSTALL_DIR="/opt/java" + + echo "Lade JDK herunter..." + wget -q --show-progress -O /tmp/openjdk21.tar.gz "$JDK_URL" + + echo "Entpacke nach $INSTALL_DIR..." + sudo mkdir -p "$INSTALL_DIR" + sudo tar -xzf /tmp/openjdk21.tar.gz -C "$INSTALL_DIR" + rm /tmp/openjdk21.tar.gz + + JDK_DIR=$(ls -d $INSTALL_DIR/jdk-21* | head -1) + + echo "Erstelle Symlinks..." + sudo ln -sf "$JDK_DIR/bin/java" /usr/local/bin/java + sudo ln -sf "$JDK_DIR/bin/javac" /usr/local/bin/javac + sudo ln -sf "$JDK_DIR/bin/jar" /usr/local/bin/jar + + # JAVA_HOME setzen + echo "" + echo -e "${YELLOW}Füge JAVA_HOME zu ~/.bashrc hinzu...${NC}" + echo "" >> ~/.bashrc + echo "# Java 21" >> ~/.bashrc + echo "export JAVA_HOME=$JDK_DIR" >> ~/.bashrc + echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrc + + export JAVA_HOME="$JDK_DIR" + export PATH="$JAVA_HOME/bin:$PATH" +} + +# Installation basierend auf OS +case $OS in + debian|ubuntu|linuxmint|pop) + install_debian_ubuntu + ;; + fedora|rhel|centos|rocky|almalinux) + install_fedora_rhel + ;; + arch|manjaro|endeavouros) + install_arch + ;; + opensuse*|sles) + install_opensuse + ;; + *) + # Prüfe ob debian-basiert + if [[ "$OS_LIKE" == *"debian"* ]]; then + install_debian_ubuntu + elif [[ "$OS_LIKE" == *"rhel"* ]] || [[ "$OS_LIKE" == *"fedora"* ]]; then + install_fedora_rhel + elif [[ "$OS_LIKE" == *"arch"* ]]; then + install_arch + else + echo -e "${YELLOW}Unbekanntes OS, versuche manuelle Installation...${NC}" + install_manual + fi + ;; +esac + +echo "" +echo -e "${BLUE}========================================" +echo " Installation abgeschlossen!" +echo -e "========================================${NC}" +echo "" + +# Verifizieren +if command -v javac &> /dev/null; then + echo -e "${GREEN}Java Version:${NC}" + java -version + echo "" + echo -e "${GREEN}Java Compiler:${NC}" + javac -version + echo "" + echo -e "${GREEN}Du kannst jetzt das Modul bauen:${NC}" + echo " cd v8-9-10 && ./build.sh" +else + echo -e "${RED}Installation fehlgeschlagen. Bitte manuell installieren.${NC}" + echo "" + echo "Manuelle Installation:" + echo " 1. Lade JDK 21 von https://adoptium.net herunter" + echo " 2. Entpacke nach /opt/java/" + echo " 3. Füge zu PATH hinzu: export PATH=/opt/java/jdk-21/bin:\$PATH" + exit 1 +fi diff --git a/v8-9-10/Mail2FaxBlock.class b/v8-9-10/Mail2FaxBlock.class new file mode 100644 index 0000000..600c9c2 Binary files /dev/null and b/v8-9-10/Mail2FaxBlock.class differ diff --git a/v8-9-10/Mail2FaxBlock.java b/v8-9-10/Mail2FaxBlock.java new file mode 100644 index 0000000..cbaab48 --- /dev/null +++ b/v8-9-10/Mail2FaxBlock.java @@ -0,0 +1,801 @@ +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 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 lines = Files.readAllLines(retryPath); + List 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 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 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 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 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 extractPdfAttachments(Message message) throws Exception { + List 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); + } +} diff --git a/v8-9-10/README.md b/v8-9-10/README.md new file mode 100644 index 0000000..d347653 --- /dev/null +++ b/v8-9-10/README.md @@ -0,0 +1,138 @@ +# Mail2FaxBlock - STARFACE Custom Block + +Ein Custom Block für den STARFACE Module Designer, der E-Mails abruft und PDF-Anhänge als Fax versendet. + +## Features + +- **IMAP/POP3 Unterstützung** mit SSL/TLS +- **POP3**: E-Mails werden IMMER nach erfolgreichem Versand gelöscht (+ Tracking um Duplikate zu vermeiden) +- **IMAP**: E-Mails werden als gelesen markiert (optional löschen) +- **Retry-Logik**: Bei besetzter Leitung oder Fehler wird automatisch erneut versucht +- **Konfigurierbare Wiederholungen**: Anzahl und Wartezeit einstellbar +- **PIN-Schutz**: Optionale Sicherheits-PIN im E-Mail-Text erforderlich + +## Kompatibilität + +- STARFACE 8.x, 9.x, 10.x (Java 21) + +## Dateien + +``` +v8-9-10/ +├── Mail2FaxBlock.java # Quellcode des Custom Blocks +├── build-block.sh # Kompilier-Script +├── libs/starface/ # STARFACE API JARs +└── README.md +``` + +## Build + +### 1. STARFACE APIs holen (falls noch nicht geschehen) + +```bash +cd .. +./fetch-starface-libs.sh +``` + +### 2. Block kompilieren + +```bash +./build-block.sh +``` + +Ergebnis: `Mail2FaxBlock.class` + +## Installation im Module Designer + +### 1. Neues Modul erstellen +- STARFACE Admin → Module → Module Designer +- "Neues Modul erstellen" +- Name: "Mail2Fax" + +### 2. Block hochladen +- Tab "Ressourcen" +- "Datei hochladen" → `Mail2FaxBlock.class` + +### 3. Funktion erstellen +- Tab "Funktionen" +- Neue Funktion erstellen +- Den hochgeladenen Block als Implementierung auswählen + +### 4. Eingabe-Variablen konfigurieren + +| Variable | Typ | Beschreibung | Default | +|----------|-----|--------------|---------| +| mailServer | STRING | IMAP/POP3 Server | | +| mailPort | NUMBER | Port | 993 | +| mailProtocol | STRING | "IMAP" oder "POP3" | IMAP | +| mailUsername | STRING | E-Mail Benutzer | | +| mailPassword | STRING | E-Mail Passwort | | +| mailUseSsl | BOOLEAN | SSL aktivieren | true | +| mailFolder | STRING | Ordner | INBOX | +| deleteAfterProcess | BOOLEAN | E-Mails löschen (nur IMAP) | false | +| faxAccountId | STARFACE_USER | Fax-Benutzer (Dropdown) | | +| faxSenderNumber | STRING | Absender-Faxnummer | | +| authorizedSenders | STRING | Erlaubte Absender (optional) | | +| pin | STRING | Sicherheits-PIN (optional) | | +| maxRetries | NUMBER | Max. Wiederholungsversuche | 3 | +| retryDelayMinutes | NUMBER | Minuten zwischen Versuchen | 5 | + +### 5. Timer konfigurieren + +Der Block muss regelmäßig ausgeführt werden um E-Mails abzurufen. Dafür den Timer im Module Designer konfigurieren: + +1. Tab **"Timer"** öffnen +2. Auf **[+]** klicken um einen neuen Schedule hinzuzufügen +3. Intervall festlegen (empfohlen: alle 60 Sekunden) + +![Timer-Tab im Module Designer](screenshots/timer-tab.png) + +**Hinweis:** Der Block hat einen eingebauten Lock-Mechanismus. Wenn der Timer erneut auslöst während der Block noch läuft, wird die neue Ausführung automatisch übersprungen. Keine Gefahr von Duplikaten. + +### 6. Modul aktivieren + +## Benutzung + +1. E-Mail an das konfigurierte Postfach senden +2. **Betreff** = Ziel-Faxnummer (z.B. `+49721123456`) +3. **Anhang** = PDF-Datei(en) +4. **E-Mail-Text** = PIN (falls konfiguriert) + +Das Modul ruft regelmäßig E-Mails ab und sendet PDFs als Fax. + +## PIN-Schutz + +Wenn eine PIN konfiguriert ist, muss diese im E-Mail-Text enthalten sein, damit das Fax gesendet wird. + +- **PIN nicht gesetzt**: Alle E-Mails werden verarbeitet (nur Absender-Prüfung falls konfiguriert) +- **PIN gesetzt**: Die PIN muss irgendwo im E-Mail-Text (plain text oder HTML) vorkommen +- **PIN nicht gefunden**: E-Mail wird als gelesen markiert, kein Fax gesendet, Log-Eintrag "PIN in Emailtext nicht vorhanden oder falsch" + +## Output-Variablen + +| Variable | Typ | Beschreibung | +|----------|-----|--------------| +| processedCount | NUMBER | Verarbeitete E-Mails | +| sentFaxCount | NUMBER | Gesendete Faxe | +| errorCount | NUMBER | Fehleranzahl | +| pendingRetries | NUMBER | Wartende Wiederholungsversuche | +| statusMessage | STRING | Status-Meldung | + +## Retry-Verhalten + +Bei folgenden Fehlern wird automatisch erneut versucht: +- Leitung besetzt +- Keine Antwort +- Übertragungsfehler + +Die Retry-Daten werden gespeichert in: +- `/var/starface/module-data/mail2fax_retry.txt` + +Nach Erreichen von `maxRetries` wird der Fax-Versuch verworfen. + +## POP3-Tracking + +Da POP3 keine "gelesen"-Flags unterstützt, speichert der Block verarbeitete Message-IDs in: +- `/var/starface/module-data/mail2fax_processed.txt` + +So werden Duplikate vermieden, auch wenn E-Mails nicht sofort gelöscht werden können. diff --git a/v8-9-10/build-block.sh b/v8-9-10/build-block.sh new file mode 100755 index 0000000..fa1ca75 --- /dev/null +++ b/v8-9-10/build-block.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# +# Kompiliert den Mail2FaxBlock für STARFACE Module Designer +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +echo "========================================" +echo " Mail2FaxBlock Kompilierung" +echo "========================================" +echo "" + +# JavaMail Bibliothek herunterladen falls nicht vorhanden +DEPS_DIR="libs/deps" +mkdir -p "$DEPS_DIR" + +if [ ! -f "$DEPS_DIR/javax.mail.jar" ]; then + echo "Lade JavaMail Bibliothek herunter..." + curl -sL -o "$DEPS_DIR/javax.mail.jar" \ + "https://repo1.maven.org/maven2/com/sun/mail/javax.mail/1.6.2/javax.mail-1.6.2.jar" + echo " javax.mail.jar heruntergeladen" +fi + +if [ ! -f "$DEPS_DIR/activation.jar" ]; then + echo "Lade Activation Framework herunter..." + curl -sL -o "$DEPS_DIR/activation.jar" \ + "https://repo1.maven.org/maven2/javax/activation/activation/1.1.1/activation-1.1.1.jar" + echo " activation.jar heruntergeladen" +fi + +echo "" + +# Classpath zusammenbauen - STARFACE JARs +CLASSPATH="" +for jar in libs/starface/*.jar; do + if [ -f "$jar" ]; then + CLASSPATH="$CLASSPATH:$jar" + fi +done + +# Zusätzliche Dependencies hinzufügen +for jar in libs/deps/*.jar; do + if [ -f "$jar" ]; then + CLASSPATH="$CLASSPATH:$jar" + fi +done + +CLASSPATH="${CLASSPATH:1}" + +if [ -z "$CLASSPATH" ]; then + echo "FEHLER: Keine STARFACE JARs gefunden in libs/starface/" + echo "Führe zuerst aus: ../fetch-starface-libs.sh " + exit 1 +fi + +echo "Kompiliere Mail2FaxBlock.java..." + +# Kompilieren - wichtig: nur die .class Datei, kein Package! +javac -source 21 -target 21 \ + -cp "$CLASSPATH" \ + -proc:none \ + Mail2FaxBlock.java + +if [ -f "Mail2FaxBlock.class" ]; then + echo "" + echo "========================================" + echo " Erfolgreich!" + echo "========================================" + echo "" + echo "Datei: Mail2FaxBlock.class" + ls -lh Mail2FaxBlock.class + echo "" + echo "WICHTIG: Die JavaMail-Bibliothek muss auch auf der STARFACE sein!" + echo "Falls Fehler auftreten, kopiere javax.mail.jar nach STARFACE:" + echo " scp libs/deps/javax.mail.jar root@:/opt/tomcat/webapps/localhost/starface/WEB-INF/lib/" + echo "" + echo "Nächste Schritte:" + echo "1. STARFACE Admin öffnen" + echo "2. Module → Module Designer → Neues Modul" + echo "3. Unter 'Ressourcen' die Mail2FaxBlock.class hochladen" + echo "4. Neuen Funktionsbaustein erstellen und Block verknüpfen" + echo "5. Timer-Baustein hinzufügen für regelmäßige Ausführung" + echo "" +else + echo "FEHLER: Kompilierung fehlgeschlagen" + exit 1 +fi diff --git a/v8-9-10/libs/deps/activation.jar b/v8-9-10/libs/deps/activation.jar new file mode 100644 index 0000000..1b703ab Binary files /dev/null and b/v8-9-10/libs/deps/activation.jar differ diff --git a/v8-9-10/libs/deps/javax.mail.jar b/v8-9-10/libs/deps/javax.mail.jar new file mode 100644 index 0000000..0cd0528 Binary files /dev/null and b/v8-9-10/libs/deps/javax.mail.jar differ diff --git a/v8-9-10/libs/starface/log4j-api-2.23.1.jar b/v8-9-10/libs/starface/log4j-api-2.23.1.jar new file mode 100644 index 0000000..0e8e3f5 Binary files /dev/null and b/v8-9-10/libs/starface/log4j-api-2.23.1.jar differ diff --git a/v8-9-10/libs/starface/log4j-core-2.23.1.jar b/v8-9-10/libs/starface/log4j-core-2.23.1.jar new file mode 100644 index 0000000..4a5d553 Binary files /dev/null and b/v8-9-10/libs/starface/log4j-core-2.23.1.jar differ diff --git a/v8-9-10/libs/starface/starface-classes.jar b/v8-9-10/libs/starface/starface-classes.jar new file mode 100644 index 0000000..99208be Binary files /dev/null and b/v8-9-10/libs/starface/starface-classes.jar differ diff --git a/v8-9-10/libs/starface/starface-commons-9.0.3.4.jar b/v8-9-10/libs/starface/starface-commons-9.0.3.4.jar new file mode 100644 index 0000000..de20433 Binary files /dev/null and b/v8-9-10/libs/starface/starface-commons-9.0.3.4.jar differ diff --git a/v8-9-10/libs/starface/starface-db-9.0.3.4.jar b/v8-9-10/libs/starface/starface-db-9.0.3.4.jar new file mode 100644 index 0000000..2aa3b01 Binary files /dev/null and b/v8-9-10/libs/starface/starface-db-9.0.3.4.jar differ diff --git a/v8-9-10/libs/starface/starface-db-converter-9.0.3.4.jar b/v8-9-10/libs/starface/starface-db-converter-9.0.3.4.jar new file mode 100644 index 0000000..995d6b5 Binary files /dev/null and b/v8-9-10/libs/starface/starface-db-converter-9.0.3.4.jar differ diff --git a/v8-9-10/libs/starface/starface-domain-objects-9.0.3.4.jar b/v8-9-10/libs/starface/starface-domain-objects-9.0.3.4.jar new file mode 100644 index 0000000..82eca86 Binary files /dev/null and b/v8-9-10/libs/starface/starface-domain-objects-9.0.3.4.jar differ diff --git a/v8-9-10/libs/starface/starface-ng-9.0.3.4.jar b/v8-9-10/libs/starface/starface-ng-9.0.3.4.jar new file mode 100644 index 0000000..85fa0dd Binary files /dev/null and b/v8-9-10/libs/starface/starface-ng-9.0.3.4.jar differ diff --git a/v8-9-10/libs/starface/starface-ng-api-9.0.3.4.jar b/v8-9-10/libs/starface/starface-ng-api-9.0.3.4.jar new file mode 100644 index 0000000..95d004b Binary files /dev/null and b/v8-9-10/libs/starface/starface-ng-api-9.0.3.4.jar differ diff --git a/v8-9-10/libs/starface/starface-rpc-9.0.3.4.jar b/v8-9-10/libs/starface/starface-rpc-9.0.3.4.jar new file mode 100644 index 0000000..dc9eca0 Binary files /dev/null and b/v8-9-10/libs/starface/starface-rpc-9.0.3.4.jar differ