first commit
This commit is contained in:
commit
601a0c2757
|
|
@ -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 <starface-ip>
|
||||
|
||||
# 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.
|
||||
Binary file not shown.
|
|
@ -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 <starface-host> [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 <starface-host> [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@<starface-host>"
|
||||
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"
|
||||
|
|
@ -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
|
||||
Binary file not shown.
|
|
@ -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<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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <starface-ip>
|
||||
```
|
||||
|
||||
### 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)
|
||||
|
||||

|
||||
|
||||
**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.
|
||||
|
|
@ -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 <starface-ip>"
|
||||
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@<starface>:/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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue