E-Sante adapter overview

E-Sante adapter overview

Table of Contents

  1. Overview

  2. System Context

  3. High-Level Architecture

  4. Module Structure

  5. Component Architecture

  6. Data Flow

  7. Technology Stack

  8. Design Patterns

  9. Security Architecture

  10. Integration Points

  11. Deployment Architecture

  12. Configuration Management


1. Overview

The SORMAS eSanté Adapter is a Java EE web application that serves as a bridge between the Luxembourg eSanté platform and the SORMAS surveillance system. It processes two primary types of external messages:

  • Doctor Declarations (dataMInf) - XML-based disease notifications from physicians

  • Laboratory Messages (CDA R2) - Clinical Document Architecture Release 2 lab results

The adapter implements the ExternalMessageAdapterFacade interface from the SORMAS API, enabling seamless integration with the core SORMAS platform.

Key Responsibilities

  • Retrieve external messages from SFTP servers

  • Validate and parse XML documents

  • Transform external data formats to SORMAS internal structures

  • Generate HTML reports for human review

  • Provide version information and health checks


2. System Context

image-20260108-120834.png

External Actors

  • eSanté SFTP Server - Provides XML files for doctor declarations

  • Laboratory Systems - Submit CDA R2 lab messages

  • SORMAS Platform - Consumes external messages through facade interface

  • Keycloak (Optional) - Authentication and authorization for REST endpoints


3. High-Level Architecture

The adapter follows a layered architecture pattern with clear separation of concerns:

image-20260108-120904.png

4. Module Structure

Package Organization

de.symeda.sormas.esante/ ├── ESanteMessageFacadeEjb # Main EJB facade implementation ├── ESanteMessageHtmlConverter # HTML conversion utilities ├── converter/ # [DEPRECATED] Legacy converters │ ├── mappings/ │ └── source/ ├── doctordeclaration/ # Doctor Declaration Processing │ ├── ApplicationStartupListener # Lifecycle management │ ├── SystemConfigurationConstants │ ├── XmlExternalMessagesProcessor # Main XML processor │ │ │ ├── client/ # SFTP Client │ │ ├── SftpConfiguration │ │ ├── SftpManager # Singleton - backoff logic │ │ └── SftpVfsClient # VFS-based SFTP operations │ │ │ ├── mapping/ # Doctor Declaration Mappers │ │ ├── ExternalMessageXmlConverter │ │ ├── MaladieToDiseaseMapper │ │ │ │ │ └── diseases/ # Disease-specific mappers │ │ ├── AbstractDiseasesMapper │ │ ├── TubeDiseaseMapper │ │ ├── PneuDiseaseMapper │ │ ├── PertDiseaseMapper │ │ ├── MeasDiseaseMapper │ │ └── TubeMappingHelper │ │ │ └── xml/ # XML Processing │ ├── DataMInfJaxbParser │ ├── DataMInfHtmlTransformer │ └── minf/ # JAXB Generated classes ├── labmessage/ # Lab Message Processing │ ├── mapping/ # Lab Message Mappers │ │ ├── LabMessageExternalMessageConverter │ │ ├── LabMessageCommonMapper │ │ ├── LabMessagePersonMapper │ │ ├── LabMessageDiseasesMapper │ │ │ │ │ └── diseases/ # Disease-specific lab mappers │ │ ├── AbstractLabMessageDiseaseMapper │ │ ├── DiseaseObservationCode │ │ │ │ │ ├── sars/ # SARS-CoV-2 │ │ ├── cryp/ # Cryptosporidiosis │ │ ├── giar/ # Giardiasis │ │ ├── rsv/ # RSV │ │ ├── meas/ # Measles │ │ ├── grip/ # Influenza │ │ ├── meni/ # Meningitis │ │ ├── pert/ # Pertussis │ │ ├── pneu/ # Pneumonia │ │ ├── tube/ # Tuberculosis │ │ │ │ │ ├── processors/ # Observation processors │ │ └── util/ # Mapping utilities │ │ │ └── xml/ # XML Processing │ ├── CDA2LabMessageConverter # XSL transformation │ ├── LabMessageJaxbParser │ ├── FhirToHtmlTransformer │ │ │ └── cdalabmessage/ # JAXB Generated classes ├── mapping/ # Common Mapping Utilities │ ├── MappingException │ ├── MappingHelper │ ├── ConversionFunctions │ └── ExternalMessageMapper ├── parser/ # [DEPRECATED] Legacy SAX parsers ├── rest/ # REST API │ ├── RestConfig # JAX-RS Application │ ├── ESanteResources # REST Endpoints for CDA messages │ │ │ └── security/ # Security Components │ ├── KeycloakHttpAuthenticationMechanism │ ├── KeycloakIdentityStore │ └── config/ │ └── KeycloakConfigResolver └── xml/ # Base XML Processing ├── AbstractSimpleJaxbParser └── XmlHelper

5. Component Architecture

5.1 Core Components

ESanteMessageFacadeEjb

Type: Stateless Session Bean
Responsibility: Main entry point implementing ExternalMessageAdapterFacade

Methods:

  • getExternalMessages(Date since) - Retrieves and processes external messages

  • convertToHTML(ExternalMessageDto) - Converts message to HTML for display

  • convertToPDF(ExternalMessageDto) - Placeholder for PDF conversion

  • getVersion() - Returns adapter version from version.txt

Dependencies:

  • SftpManager - SFTP download orchestration

  • XmlExternalMessagesProcessor - XML processing


SftpManager

Type: Singleton Session Bean
Responsibility: Manages SFTP downloads with exponential backoff

Key Features:

  • Exponential backoff retry logic (5m, 10m, 20m, 40m, 80m)

  • Circuit breaker pattern support

  • Thread-safe download coordination

  • Configurable backoff enable/disable

State:

  • retryCount - Current retry attempt

  • nextAttemptTime - Timestamp for next allowed attempt

  • lastError - Most recent exception

Dependencies:

  • SftpVfsClient - Actual SFTP operations


SftpVfsClient

Type: Stateless Session Bean
Responsibility: Performs SFTP operations using Apache Commons VFS

Key Operations:

  • downloadAndValidate() - Downloads XML files from SFTP server

  • File validation (XML extension check)

  • Automatic file cleanup after processing

Configuration:

  • Injected via SftpConfiguration CDI producer

  • Reads from esante-adapter.properties


XmlExternalMessagesProcessor

Type: Stateless Session Bean
Responsibility: Processes doctor declaration XML files

Processing Flow:

  1. Scan directory for .xml files

  2. Parse and validate each file

  3. Convert to ExternalMessageDto

  4. Move processed files to .done

  5. Move failed files to .error with .errors log

Features:

  • Disease acceptance filtering

  • Concurrent processing support

  • Comprehensive error logging

  • Processed file tracking

Dependencies:

  • ExternalMessageXmlConverter - XML to DTO conversion


5.2 Converter Components

ExternalMessageXmlConverter (Doctor Declarations)

Responsibility: Converts doctor declaration XML to ExternalMessageDto

Architecture:

ExternalMessageXmlConverter ├─▶ DataMInfJaxbParser (Parse & Validate) └─▶ MaladieToDiseaseMapper └─▶ Disease-Specific Mappers ├─▶ TubeDiseaseMapper ├─▶ PneuDiseaseMapper ├─▶ PertDiseaseMapper └─▶ MeasDiseaseMapper

Process:

  1. Parse XML using JAXB

  2. Extract common fields (patient, doctor, dates)

  3. Delegate disease-specific mapping

  4. Store original XML in externalMessageDetails


LabMessageExternalMessageConverter (Lab Messages)

Responsibility: Converts CDA R2 lab messages to ExternalMessageDto

Architecture:

LabMessageExternalMessageConverter ├─▶ CDA2LabMessageConverter (XSL Transform) ├─▶ LabMessageJaxbParser (Parse & Validate) └─▶ Mapper Chain ├─▶ LabMessageCommonMapper ├─▶ LabMessagePersonMapper └─▶ LabMessageDiseasesMapper └─▶ Disease-Specific Mappers (11 diseases)

Process:

  1. Transform CDA XML → intermediate lab message format (XSL)

  2. Parse and validate transformed XML (JAXB)

  3. Apply common mapper (document metadata)

  4. Apply person mapper (patient demographics)

  5. Apply disease mappers (observations, tests)


5.3 Disease Mapper Architecture

Both doctor declarations and lab messages use disease-specific mappers following a common pattern:

Doctor Declaration Mappers

Base Class: AbstractDiseasesMapper

public abstract class AbstractDiseasesMapper { protected final Disease disease; // Template method public ExternalMessageDto map(MaladieType maladie, ExternalMessageDto externalMessage); // Disease-specific implementation protected abstract ExternalMessageDto mapInternal(...); // Support check public abstract boolean supports(MaladieType maladie); }

Concrete Mappers:

  • TubeDiseaseMapper - Most complex, handles 30+ TUBE codes

  • PneuDiseaseMapper - Pneumonia and IPI

  • PertDiseaseMapper - Pertussis symptoms

  • MeasDiseaseMapper - Measles complications

Mapping Scope:

  • Symptoms mapping

  • Case classification

  • Clinical manifestations

  • Pathogen tests from declaration

  • Risk factors and travel history


Lab Message Mappers

Base Class: AbstractLabMessageDiseaseMapper

public abstract class AbstractLabMessageDiseaseMapper { protected final String diseaseCode; protected final Disease mappedDisease; // Template method public ExternalMessageDto map(DiseaseType source, ExternalMessageDto target); // Observation mapping (abstract) protected abstract ExternalMessageDto mapAdditionalPropertiesFromObservations(...); // Support check public boolean supports(DiseaseType disease); }

Observation Processing Pattern:
Each disease mapper uses observation processors to handle specific codes:

// Example: SARS-CoV-2 class SarsLabMessageDiseaseMapper { private final Map<String, ObservationProcessor> processors; // Register processors in constructor processors.put("SARS-1", new TestTypeProcessor()); processors.put("SARS-2", new TestResultProcessor()); // ... etc }

Processor Types:

  • TestTypeProcessor - Maps observation to PathogenTestType

  • TestResultProcessor - Maps observation to PathogenTestResultType

  • SampleMaterialProcessor - Maps to SampleMaterial

  • DateProcessor - Extracts and parses dates

  • SymptomProcessor - Maps to SymptomsDto fields


5.4 Observation Code Registry

Lab messages use enumerated observation codes for each disease:

// Example: SARS-CoV-2 public enum SarsDiseaseObservationCode { SARS_1("SARS-1", "Test type"), SARS_2("SARS-2", "Test result"), SARS_3("SARS-3", "Laboratory method"), // ... up to SARS-300+ }

Benefits:

  • Type-safe observation code handling

  • Self-documenting code structure

  • Easy to extend with new codes

  • Validation during compilation


5.5 HTML Transformation Components

DataMInfHtmlTransformer

Responsibility: Converts doctor declaration XML to HTML

Architecture:

  • Uses XSL templates for transformation

  • Modular template structure:

    • dataMInf_main.xsl - Main template

    • dataMInf_medecin.xsl - Doctor information

    • dataMInf_patient.xsl - Patient information

    • dataMInf_maladie.xsl - Disease section

    • dataMInf_maladie_tube.xsl - Tuberculosis details

    • dataMInf_maladie_pneu.xsl - Pneumonia details

    • dataMInf_maladie_pert.xsl - Pertussis details

    • dataMInf_maladie_meas.xsl - Measles details

    • dataMInf_maladie_common_*.xsl - Common sections

Output: Bootstrap-styled HTML report


FhirToHtmlTransformer

Responsibility: Converts lab message to HTML

Note: Despite the name, this transforms the intermediate lab message format (not pure FHIR) to HTML.


6. Data Flow

6.1 Doctor Declaration Flow

image-20260108-121107.png

 


6.2 Lab Message Flow

x.png

 


7. Technology Stack

Core Technologies

Component

Technology

Version

Purpose

Component

Technology

Version

Purpose

Platform

Java EE

8+

Enterprise application framework

Application Server

Payara Server

5.x/6.x

Java EE runtime

Build Tool

Maven

3.6+

Dependency management & build

Language

Java

11+

Implementation language

Major Libraries

Library

Purpose

Library

Purpose

Apache Commons VFS2

SFTP file system access

JAXB

XML binding and marshalling

Saxon

XSLT 2.0 transformations

Jackson

JSON processing for REST

Jersey

JAX-RS REST implementation

SLF4J + Logback

Logging framework

Keycloak

OAuth/OIDC authentication

Testing Stack

Library

Purpose

Library

Purpose

JUnit 5

Unit testing framework

Mockito

Mocking framework

AssertJ

Fluent assertions

Testcontainers

Integration testing (SFTP)


8. Design Patterns

8.1 Facade Pattern

Location: ESanteMessageFacadeEjb
Purpose: Provides a simplified interface to SORMAS platform

@Stateless(name = "ESanteMessageFacade") public class ESanteMessageFacadeEjb implements ExternalMessageAdapterFacade { // Unified interface hiding complexity }

8.2 Template Method Pattern

Location: AbstractDiseasesMapper, AbstractLabMessageDiseaseMapper
Purpose: Defines skeleton of mapping algorithm, subclasses fill in details

public abstract class AbstractDiseasesMapper { // Template method public final ExternalMessageDto map(...) { // Common logic validateInput(); ExternalMessageDto dto = mapInternal(...); postProcess(dto); return dto; } // Subclass implements specific mapping protected abstract ExternalMessageDto mapInternal(...); }

8.3 Strategy Pattern

Location: Disease mappers, Observation processors
Purpose: Encapsulates interchangeable algorithms

// Different strategies for different diseases Map<String, DiseaseMapper> mappers; DiseaseMapper mapper = mappers.get(diseaseCode); mapper.map(source, target);

8.4 Chain of Responsibility

Location: Lab message mapper chain
Purpose: Sequential processing of mappers

// Chain: Common → Person → Diseases externalMessage = commonMapper.map(source, externalMessage); externalMessage = personMapper.map(source, externalMessage); externalMessage = diseasesMapper.map(source, externalMessage);

8.5 Singleton Pattern

Location: SftpManager, Parser instances
Purpose: Single instance with controlled access

@Singleton @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER) public class SftpManager { // Managed by container } // Manual singleton for parsers public class DataMInfJaxbParser { private static DataMInfJaxbParser instance; public static synchronized DataMInfJaxbParser getInstance() {...} }

8.6 Factory Pattern

Location: Mapper registration, Processor creation
Purpose: Creates objects without specifying exact class

// Disease mapper factory public class LabMessageDiseasesMapper { private void registerMappers() { mappers.add(new SarsLabMessageDiseaseMapper()); mappers.add(new TubeLabMessageDiseaseMapper()); // ... etc } }

8.7 Adapter Pattern

Location: Entire module
Purpose: Converts eSanté format to SORMAS format

The whole adapter is an implementation of the Adapter pattern, converting between incompatible interfaces.


8.8 Circuit Breaker Pattern (Partial)

Location: SftpManager
Purpose: Prevents cascading failures from SFTP issues

public class SftpManager { private boolean circuitBreakerEnabled; public void attemptDownload() { if (!shouldAttemptNow()) { // Circuit open - skip attempt return; } // Proceed with download } }

9. Security Architecture

9.1 Authentication

Keycloak Integration (Optional)

Components:

  • KeycloakHttpAuthenticationMechanism - Intercepts HTTP requests

  • KeycloakIdentityStore - Validates credentials

  • KeycloakConfigResolver - Loads Keycloak configuration

Flow:

REST Request KeycloakHttpAuthenticationMechanism Validate Token KeycloakIdentityStore Extract Roles/Permissions Execute Request

Configuration:

  • Can be configured via external JSON or system properties

  • Supports both public and confidential clients

  • Bearer token authentication


9.2 Authorization

Currently implements basic authorization through Keycloak roles.

Future Enhancement: Role-based access control (RBAC) for specific operations.


9.3 Data Security

XML Validation

  • XSD Schema Validation - All XML documents validated against schemas

  • Prevents: XML injection, malformed documents

Input Sanitization

  • MappingHelper.sanitizeXmlString() - Removes control characters

  • Prevents: XML entity expansion attacks

Secure Transport

  • SFTP - Encrypted file transfer

  • HTTPS - Required for REST endpoints (configured in Payara)


9.4 Error Handling

Principle: Fail securely, log appropriately, don't expose internals

catch (Exception e) { // Log full details logger.error("Processing failed", e); // Return sanitized error to client return new ExternalMessageResult<>(null, new Date(), false, "Le message eSanté n'a pas pu être converti"); }

10. Integration Points

10.1 SORMAS Platform Integration

Interface: ExternalMessageAdapterFacade
Protocol: EJB Remote (JNDI)
Binding: java:global/sormas-esante-adapter/ESanteMessageFacade

Configuration in SORMAS:

# sormas.properties interface.demis.jndiName=java:global/sormas-esante-adapter/ESanteMessageFacade

Methods Called by SORMAS:

  • getExternalMessages(Date since) - Polled periodically

  • convertToHTML(ExternalMessageDto) - When user views message

  • convertToPDF(ExternalMessageDto) - If PDF generation requested

  • getVersion() - For diagnostics


10.2 eSanté Platform Integration

Protocol: SFTP (via Apache Commons VFS)
Frequency: Periodic polling with exponential backoff

Configuration Management:

Configuration is managed through SORMAS System Configuration, not via properties files. The adapter automatically creates a default configuration category on first startup via ApplicationStartupListener.

Configuration Category: DOCTOR_DECLARATION

Configuration Values:

// Retrieved via SystemConfigurationValueFacade DOCTOR_DECLARATION_SFTP_IP // SFTP server IP address DOCTOR_DECLARATION_SFTP_PORT // SFTP server port (default: 22) DOCTOR_DECLARATION_SFTP_USERNAME // SFTP username DOCTOR_DECLARATION_SFTP_PASSWORD // SFTP password (encrypted) DOCTOR_DECLARATION_SORMAS_DIRECTORY // Remote directory path DOCTOR_DECLARATION_SFTP_FILE_PATTERN // Filename regex pattern DOCTOR_DECLARATION_ACCEPTED_DISEASES // Pipe-separated disease list

Configuration Initialization:

The ApplicationStartupListener ensures configuration exists on deployment:

  • Creates DOCTOR_DECLARATION category if missing

  • Sets default values for all configuration keys

  • Enables encryption for password field

  • Provides validation patterns (IP address, port, path)

Configuration Access Pattern:

// SftpConfiguration.java String sftpIp = FacadeProvider.getSystemConfigurationValueFacade() .getValue("DOCTOR_DECLARATION_SFTP_IP");

File Naming Convention:

  • Doctor declarations: *.xml