E-Sante adapter overview
Table of Contents
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
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:
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 messagesconvertToHTML(ExternalMessageDto)- Converts message to HTML for displayconvertToPDF(ExternalMessageDto)- Placeholder for PDF conversiongetVersion()- Returns adapter version fromversion.txt
Dependencies:
SftpManager- SFTP download orchestrationXmlExternalMessagesProcessor- 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 attemptnextAttemptTime- Timestamp for next allowed attemptlastError- 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 serverFile validation (XML extension check)
Automatic file cleanup after processing
Configuration:
Injected via
SftpConfigurationCDI producerReads from
esante-adapter.properties
XmlExternalMessagesProcessor
Type: Stateless Session Bean
Responsibility: Processes doctor declaration XML files
Processing Flow:
Scan directory for
.xmlfilesParse and validate each file
Convert to
ExternalMessageDtoMove processed files to
.doneMove failed files to
.errorwith.errorslog
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:
Parse XML using JAXB
Extract common fields (patient, doctor, dates)
Delegate disease-specific mapping
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:
Transform CDA XML → intermediate lab message format (XSL)
Parse and validate transformed XML (JAXB)
Apply common mapper (document metadata)
Apply person mapper (patient demographics)
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 codesPneuDiseaseMapper- Pneumonia and IPIPertDiseaseMapper- Pertussis symptomsMeasDiseaseMapper- 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 PathogenTestTypeTestResultProcessor- Maps observation to PathogenTestResultTypeSampleMaterialProcessor- Maps to SampleMaterialDateProcessor- Extracts and parses datesSymptomProcessor- 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 templatedataMInf_medecin.xsl- Doctor informationdataMInf_patient.xsl- Patient informationdataMInf_maladie.xsl- Disease sectiondataMInf_maladie_tube.xsl- Tuberculosis detailsdataMInf_maladie_pneu.xsl- Pneumonia detailsdataMInf_maladie_pert.xsl- Pertussis detailsdataMInf_maladie_meas.xsl- Measles detailsdataMInf_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
6.2 Lab Message Flow
7. Technology Stack
Core Technologies
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 |
|---|---|
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 |
|---|---|
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 requestsKeycloakIdentityStore- Validates credentialsKeycloakConfigResolver- 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 charactersPrevents: 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 periodicallyconvertToHTML(ExternalMessageDto)- When user views messageconvertToPDF(ExternalMessageDto)- If PDF generation requestedgetVersion()- 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_DECLARATIONcategory if missingSets 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