Catégorie : Blog

  • Socle V004 – Migration V3 vers V4

    Socle V004 – Migration V3 vers V4

    25 – Guide de Migration V3 → V4

    Version : 4.0.0 Date : 2025-12-09

    1. Résumé des changements

    1.1 Ce qui change

    Aspect V3 V4
    Logging Logback Log4j2 + LogForwarder
    Persistance technique In-memory/Redis + H2 TechDB
    Auth AdminAuthFilter local + SocleAuthClient JWT
    Registry Supervisor local + WorkerRegistryClient

    1.2 Ce qui ne change PAS

    • Architecture MOP
    • Interface Worker
    • KvBus (in_memory / Redis)
    • SharedDataRegistry
    • Supervisor
    • HttpWorker
    • AdminRestApi
    • PipelineEngine
    • CircuitBreaker / Retry
    • Scheduler

    2. Checklist de migration

    □ Phase 1: Préparation
      □ Lire ce guide en entier
      □ Backup du projet V3
      □ Créer branche migration-v4
    
    □ Phase 2: Dépendances Maven
      □ Mettre à jour pom.xml
      □ Exclure Logback
      □ Ajouter Log4j2
      □ Ajouter H2
    
    □ Phase 3: Configuration
      □ Créer log4j2.xml
      □ Créer log4j2.component.properties
      □ Supprimer logback-spring.xml
      □ Mettre à jour application.yml
    
    □ Phase 4: Code (optionnel)
      □ Intégrer TechDbManager
      □ Intégrer SocleAuthClient
      □ Intégrer WorkerRegistryClient
    
    □ Phase 5: Tests
      □ Compiler
      □ Exécuter les tests
      □ Vérifier les logs
      □ Valider H2 Console
    
    □ Phase 6: Déploiement
      □ Variables d'environnement
      □ Test en staging
      □ Déploiement production
    

    3. Phase 1 : Préparation

    3.1 Backup

    # Backup du projet V3
    cp -r socle-v003 socle-v003-backup
    
    # Créer branche
    cd socle-v003
    git checkout -b migration-v4
    

    3.2 Version cible

    <version>4.0.0</version>
    

    4. Phase 2 : Dépendances Maven

    4.1 Modifications pom.xml

    <!-- AVANT (V3) -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
    </dependency>
    <dependency>
        <groupId>net.logstash.logback</groupId>
        <artifactId>logstash-logback-encoder</artifactId>
    </dependency>
    
    <!-- APRÈS (V4) -->
    <!-- Exclure Logback de Spring Boot -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
    <!-- Log4j2 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    
    <!-- LMAX Disruptor -->
    <dependency>
        <groupId>com.lmax</groupId>
        <artifactId>disruptor</artifactId>
        <version>4.0.0</version>
    </dependency>
    
    <!-- H2 Database -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>2.2.224</version>
    </dependency>
    

    4.2 Vérifier les exclusions

    S’assurer que Logback est exclu de TOUTES les dépendances Spring Boot :

    mvn dependency:tree | grep logback
    # Ne doit rien retourner
    

    5. Phase 3 : Configuration

    5.1 Créer log4j2.xml

    Créer src/main/resources/log4j2.xml :

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="WARN" monitorInterval="30">
        <Properties>
            <Property name="LOG_DIR">${env:LOG_DIR:-./logs}</Property>
            <Property name="APP_NAME">${env:APP_NAME:-socle-v4}</Property>
        </Properties>
    
        <Appenders>
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{ISO8601} %-5level [%thread] %logger{36} - %msg%n"/>
            </Console>
    
            <RollingFile name="File"
                         fileName="${LOG_DIR}/${APP_NAME}.log"
                         filePattern="${LOG_DIR}/${APP_NAME}-%d{yyyy-MM-dd}-%i.log.gz">
                <PatternLayout pattern="%d{ISO8601} %-5level [%thread] %logger{36} - %msg%n"/>
                <Policies>
                    <TimeBasedTriggeringPolicy interval="1"/>
                    <SizeBasedTriggeringPolicy size="100MB"/>
                </Policies>
                <DefaultRolloverStrategy max="30"/>
            </RollingFile>
        </Appenders>
    
        <Loggers>
            <Logger name="eu.lmvi.socle" level="${env:LOG_LEVEL:-INFO}" additivity="false">
                <AppenderRef ref="Console"/>
                <AppenderRef ref="File"/>
            </Logger>
            <Logger name="org.springframework" level="WARN"/>
            <Root level="INFO">
                <AppenderRef ref="Console"/>
                <AppenderRef ref="File"/>
            </Root>
        </Loggers>
    </Configuration>
    

    5.2 Créer log4j2.component.properties

    Créer src/main/resources/log4j2.component.properties :

    Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
    AsyncLogger.RingBufferSize=262144
    AsyncLogger.WaitStrategy=Sleep
    log4j2.formatMsgNoLookups=true
    

    5.3 Supprimer logback-spring.xml

    rm src/main/resources/logback-spring.xml
    

    5.4 Mettre à jour application.yml

    Ajouter les nouvelles configurations V4 :

    # Ajouter à application.yml existant
    
    socle:
      # ... config V3 existante ...
    
      # NOUVEAU V4: H2 TechDB
      techdb:
        enabled: ${TECHDB_ENABLED:true}
        url: jdbc:h2:file:${TECHDB_PATH:./data/socle-techdb};MODE=PostgreSQL;DB_CLOSE_DELAY=-1
        username: socle
        password: ${TECHDB_PASSWORD:socle}
        console:
          enabled: ${H2_CONSOLE_ENABLED:false}
          path: /h2-console
    
      # NOUVEAU V4: LogForwarder (optionnel)
      logging:
        forwarder:
          enabled: ${LOG_FORWARDER_ENABLED:false}
          transport-mode: ${LOG_TRANSPORT_MODE:http}
          log-hub-url: ${LOG_HUB_URL:}
    
      # NOUVEAU V4: Auth Client (optionnel)
      auth:
        enabled: ${AUTH_ENABLED:false}
        server-url: ${AUTH_SERVER_URL:}
        api-key: ${API_KEY:}
    
      # NOUVEAU V4: Worker Registry (optionnel)
      worker-registry:
        enabled: ${WORKER_REGISTRY_ENABLED:false}
        server-url: ${WORKER_REGISTRY_URL:}
    
    # Logging
    logging:
      config: classpath:log4j2.xml
    

    6. Phase 4 : Code (optionnel)

    Les composants V4 sont optionnels et activés via configuration. Aucune modification de code n’est obligatoire.

    6.1 Si vous voulez utiliser TechDB

    @Autowired(required = false)
    private TechDbManager techDbManager;
    
    // Utilisation
    if (techDbManager != null) {
        techDbManager.saveOffset("kafka", "topic-0", offset, null);
    }
    

    6.2 Si vous voulez utiliser AuthClient

    @Autowired(required = false)
    private SocleAuthClient authClient;
    
    // Utilisation
    if (authClient != null && authClient.isAuthenticated()) {
        String token = authClient.getValidAccessToken();
    }
    

    6.3 Si vous voulez utiliser WorkerRegistry

    @Autowired(required = false)
    private WorkerRegistryClient registryClient;
    
    // L'intégration MOP est automatique si enabled
    

    7. Phase 5 : Tests

    7.1 Compilation

    mvn clean compile
    

    Erreurs possibles :

    • package ch.qos.logback does not exist → Logback pas complètement exclu
    • cannot find symbol: class Logger → Import SLF4J correct ?

    7.2 Tests unitaires

    mvn test
    

    7.3 Vérification logs

    mvn spring-boot:run
    

    Vérifier :

    • Logs apparaissent en console
    • Format correct
    • Pas d’erreur Log4j2

    7.4 H2 Console

    Si H2_CONSOLE_ENABLED=true :

    1. Ouvrir http://localhost:8080/h2-console
    2. JDBC URL: jdbc:h2:file:./data/socle-techdb
    3. Vérifier les tables créées

    8. Phase 6 : Déploiement

    8.1 Variables d’environnement (minimum)

    # Existantes V3 (inchangées)
    APP_NAME=my-app
    ENV_NAME=PROD
    HTTP_PORT=8080
    
    # Nouvelles V4 (optionnelles)
    TECHDB_ENABLED=true
    TECHDB_PATH=./data/techdb
    H2_CONSOLE_ENABLED=false
    LOG_FORWARDER_ENABLED=false
    AUTH_ENABLED=false
    WORKER_REGISTRY_ENABLED=false
    

    8.2 Docker

    Mettre à jour le Dockerfile si nécessaire :

    # Pas de changement requis si vous utilisez le JAR
    FROM eclipse-temurin:21-jre
    COPY target/socle-v004-4.0.0.jar app.jar
    ENTRYPOINT ["java", "-jar", "app.jar"]
    

    8.3 Kubernetes

    Mettre à jour les ConfigMaps/Secrets avec les nouvelles variables.

    9. Mapping des configurations

    9.1 Logback → Log4j2

    Logback Log4j2
    <appender class="ConsoleAppender"> <Console name="...">
    <appender class="RollingFileAppender"> <RollingFile name="...">
    <encoder><pattern> <PatternLayout pattern="...">
    <root level="INFO"> <Root level="INFO">
    <logger name="..." level="..."> <Logger name="..." level="...">

    9.2 Pattern identique

    Le pattern de log reste identique :

    %d{ISO8601} %-5level [%thread] %logger{36} - %msg%n
    

    10. Rollback

    En cas de problème, pour revenir à V3 :

    # Restaurer le pom.xml V3
    git checkout HEAD~1 -- pom.xml
    
    # Restaurer logback-spring.xml
    git checkout HEAD~1 -- src/main/resources/logback-spring.xml
    
    # Supprimer fichiers V4
    rm src/main/resources/log4j2.xml
    rm src/main/resources/log4j2.component.properties
    
    # Rebuild
    mvn clean package
    

    11. FAQ

    Q: Dois-je modifier mon code de logging ?

    Non. Le code reste identique :

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    private static final Logger log = LoggerFactory.getLogger(MyClass.class);
    log.info("Message");
    

    Q: Les MDC fonctionnent-ils ?

    Oui. MDC fonctionne de manière identique avec Log4j2.

    Q: Puis-je activer les fonctionnalités V4 progressivement ?

    Oui. Toutes les fonctionnalités V4 sont optionnelles :

    • TECHDB_ENABLED=false → Pas de H2
    • LOG_FORWARDER_ENABLED=false → Pas de LogForwarder
    • AUTH_ENABLED=false → Pas d’auth JWT
    • WORKER_REGISTRY_ENABLED=false → Pas de registry

    Q: Performance : V4 est-il plus rapide ?

    Oui pour le logging. Log4j2 avec AsyncLoggers (LMAX Disruptor) est 6-68x plus rapide que Logback en mode async.

    12. Références

  • Socle V004 – Architecture

    Socle V004 – Architecture

    02 – Architecture du Socle V4

    Version : 4.0.0 Date : 2025-01-25

    1. Vue d’ensemble

    ┌─────────────────────────────────────────────────────────────────┐
    │                         SOCLE V4                                │
    │                                                                 │
    │  ┌─────────────────────────────────────────────────────────┐   │
    │  │                    MOP (inchangé)                        │   │
    │  │  - Orchestration Workers                                 │   │
    │  │  - Lifecycle management                                  │   │
    │  │  - Scheduling doWork()                                   │   │
    │  └─────────────────────────────────────────────────────────┘   │
    │                              │                                  │
    │  ┌───────────┬───────────┬───┴───────┬─────────────┐           │
    │  │           │           │           │             │           │
    │  ▼           ▼           ▼           ▼             ▼           │
    │ ┌─────┐  ┌───────┐  ┌────────┐  ┌─────────┐  ┌──────────┐     │
    │ │KvBus│  │Shared │  │Supervi-│  │ HTTP    │  │ Workers  │     │
    │ │     │  │Data   │  │sor     │  │ Worker  │  │ métier   │     │
    │ └─────┘  └───────┘  └────────┘  └─────────┘  └──────────┘     │
    │                                                                 │
    │  ════════════════════ NOUVEAUTÉS V4 ════════════════════════   │
    │                                                                 │
    │  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐     │
    │  │ H2 TechDB   │  │ Log4j2 +    │  │ Clients centraux    │     │
    │  │ (embarqué)  │  │ LogForwarder│  │ - SocleAuthClient   │     │
    │  │             │  │             │  │ - WorkerRegistry    │     │
    │  └─────────────┘  └─────────────┘  └─────────────────────┘     │
    │                                                                 │
    │  ┌─────────────┐  ┌─────────────────────────────────────┐      │
    │  │ Status      │  │ Pipeline V2                          │      │
    │  │ Dashboard   │  │ (Queue/Claim/Ack, DLQ, at-least-once)│      │
    │  │ (port 9374) │  │                                      │      │
    │  └─────────────┘  └─────────────────────────────────────┘      │
    │                                                                 │
    └─────────────────────────────────────────────────────────────────┘
    

    2. Architecture en couches

    ┌─────────────────────────────────────────────────────────────────┐
    │                    COUCHE APPLICATION                           │
    │  Workers métier + Contrôleurs REST                              │
    ├─────────────────────────────────────────────────────────────────┤
    │                    COUCHE FRAMEWORK (SOCLE)                     │
    │  MOP + Core Components + Nouveautés V4                          │
    ├─────────────────────────────────────────────────────────────────┤
    │                    COUCHE INFRASTRUCTURE                        │
    │  Tomcat (HTTP), H2 (TechDB), Redis (KV), Kafka/NATS (Msg)      │
    └─────────────────────────────────────────────────────────────────┘
    

    3. Structure des packages

    eu.lmvi.socle/
    │
    │  ══════ COMPOSANTS V3 (conservés) ══════
    ├── mop/
    │   └── MainOrchestratorProcess.java    # Orchestrateur central
    ├── worker/
    │   └── Worker.java                     # Interface de base
    ├── config/
    │   └── SocleConfiguration.java         # Configuration centralisée
    ├── kv/
    │   ├── KvBus.java                      # Abstraction KV
    │   ├── KvImplementation.java           # Interface implémentation
    │   ├── InMemoryKvImplementation.java   # Implémentation mémoire
    │   └── RedisKvImplementation.java      # Implémentation Redis
    ├── shared/
    │   └── SharedDataRegistry.java         # Registre données partagées
    ├── supervisor/
    │   └── Supervisor.java                 # Monitoring heartbeats
    ├── http/
    │   ├── HttpWorker.java                 # Worker HTTP
    │   ├── TomcatManager.java              # Gestion Tomcat
    │   └── GracefulShutdownFilter.java     # Filtre drain
    ├── admin/
    │   └── AdminRestApi.java               # API REST admin
    ├── metrics/
    │   └── SocleMetrics.java               # Métriques
    ├── pipeline/
    │   └── PipelineEngine.java             # Traitement asynchrone
    ├── resilience/
    │   ├── CircuitBreaker.java             # Circuit Breaker
    │   └── RetryExecutor.java              # Retry avec backoff
    ├── scheduler/
    │   ├── WorkerScheduler.java            # Scheduler
    │   └── CronExpression.java             # Parser cron
    ├── security/
    │   ├── AdminAuthFilter.java            # Auth admin
    │   └── RateLimitFilter.java            # Rate limiting
    │
    │  ══════ NOUVEAUX COMPOSANTS V4 ══════
    ├── techdb/
    │   ├── TechDbManager.java              # Gestionnaire H2
    │   ├── TechDbConfig.java               # Configuration Spring
    │   └── TechDbRepository.java           # Repository
    ├── logging/
    │   ├── SocleLogForwarderAppender.java  # Appender Log4j2
    │   ├── LogTransport.java               # Interface transport
    │   ├── HttpLogTransport.java           # Transport HTTP
    │   ├── NatsLogTransport.java           # Transport NATS
    │   └── H2FallbackStorage.java          # Fallback H2
    └── client/
        ├── auth/
        │   ├── SocleAuthClient.java        # Interface auth
        │   ├── AuthTokenManager.java       # Gestion tokens
        │   └── AuthTokens.java             # DTO tokens
        └── registry/
            ├── WorkerRegistryClient.java   # Client registry
            ├── WorkerRegistration.java     # DTO registration
            └── WorkerHeartbeat.java        # DTO heartbeat
    

    4. Flux de démarrage

    1. Spring Boot initialise
    2. SocleConfiguration charge la config (.env + YAML)
    3. Spring crée les beans @Component
    4. MOP.start() appelé (ApplicationReadyEvent)
       │
       ├── 4.1 [V4] TechDbManager.initialize()
       │         └── Création tables H2
       │         └── Restauration offsets
       │
       ├── 4.2 [V4] SocleAuthClient.login()
       │         └── Obtention JWT
       │
       ├── 4.3 [V4] WorkerRegistryClient.register()
       │         └── Enregistrement au Registry
       │
       ├── 4.4 SharedDataRegistry.initialize()
       ├── 4.5 KvBus.initialize()
       ├── 4.6 Supervisor.start()
       ├── 4.7 Metrics.start()
       │
       ├── 4.8 Workers triés par START_PRIORITY
       │   └── Pour chaque worker:
       │       ├── worker.initialize()
       │       ├── worker.start()
       │       ├── Register avec Supervisor
       │       └── Schedule si isScheduled()
       │
       ├── 4.9 HttpWorker.start() [priorité 1000 = dernier]
       └── 4.10 Boucle principale doWork()
    

    5. Flux de shutdown

    1. Signal SIGTERM (ou /admin/shutdown)
    2. MOP.stop() appelé
    3. État → DRAINING
       │
       ├── 3.1 HttpWorker.startDraining()
       │         └── Refuse nouvelles connexions
       ├── 3.2 HttpWorker.awaitDrain(timeout)
       │         └── Attente requêtes en cours
       │
       ├── 3.3 Workers par STOP_PRIORITY (petit = premier)
       │   ├── HttpWorker.stop() [priorité 0]
       │   └── Autres workers [priorités 1-999]
       │
       ├── 3.4 [V4] WorkerRegistryClient.unregister()
       ├── 3.5 [V4] TechDbManager.close()
       │
       ├── 3.6 Supervisor.shutdown()
       ├── 3.7 SharedData.close()
       └── 3.8 État → STOPPED
    

    6. Dépendances entre composants

    ┌──────────────────────────────────────────────────────────────┐
    │                        MOP (orchestrateur)                    │
    │                              │                                │
    │         ┌────────────────────┼────────────────────┐          │
    │         │                    │                    │          │
    │         ▼                    ▼                    ▼          │
    │   ┌───────────┐       ┌───────────┐       ┌───────────┐     │
    │   │ TechDB    │◄──────│ Supervisor│───────►│ Workers   │     │
    │   │ Manager   │       │           │        │           │     │
    │   └─────┬─────┘       └─────┬─────┘        └─────┬─────┘     │
    │         │                   │                    │           │
    │         │                   ▼                    │           │
    │         │            ┌───────────┐               │           │
    │         │            │ Worker    │◄──────────────┘           │
    │         │            │ Registry  │                           │
    │         │            │ Client    │                           │
    │         │            └─────┬─────┘                           │
    │         │                  │                                 │
    │         ▼                  ▼                                 │
    │   ┌───────────┐     ┌───────────┐                           │
    │   │ H2        │     │ SocleAuth │                           │
    │   │ Database  │     │ Client    │                           │
    │   └───────────┘     └─────┬─────┘                           │
    │                           │                                  │
    │                           ▼                                  │
    │                    ┌─────────────┐                           │
    │                    │ LogForwarder│──► HTTP/NATS (sortant)   │
    │                    │ Appender    │                           │
    │                    └─────────────┘                           │
    └──────────────────────────────────────────────────────────────┘
    

    7. Architecture du Logging V4

    ┌─────────────────────────────────────────────────────────────┐
    │                      Application                             │
    │                                                              │
    │  Logger.info("message")                                      │
    │         │                                                    │
    │         ▼                                                    │
    │  ┌─────────────────────────────────────────────────────┐    │
    │  │              Log4j2 AsyncLoggers                     │    │
    │  │              (LMAX Disruptor)                        │    │
    │  └────────────────────┬────────────────────────────────┘    │
    │                       │                                      │
    │         ┌─────────────┼─────────────┐                       │
    │         ▼             ▼             ▼                       │
    │  ┌───────────┐ ┌───────────┐ ┌─────────────────────┐       │
    │  │  Console  │ │  File     │ │ SocleLogForwarder   │       │
    │  │  Appender │ │  Appender │ │ Appender            │       │
    │  └───────────┘ └───────────┘ └──────────┬──────────┘       │
    │                                         │                   │
    └─────────────────────────────────────────┼───────────────────┘
                                              │
                        ┌─────────────────────┴─────────────────────┐
                        │                                           │
                        ▼                                           ▼
                ┌──────────────┐                           ┌──────────────┐
                │ HTTP Transport│                           │ NATS Transport│
                │ → LogHub     │                           │ → JetStream  │
                └──────┬───────┘                           └──────┬───────┘
                       │                                          │
                       │  (si échec)                              │
                       ▼                                          │
                ┌──────────────┐                                  │
                │ H2 Fallback  │◄─────────────────────────────────┘
                │ Storage      │
                └──────────────┘
    

    8. Patterns de conception

    Pattern Composant Usage
    Orchestrator MOP Orchestre tout le lifecycle
    Observer KvBus pub/sub Communication inter-workers
    Strategy KvImplementation In-memory ou Redis
    Registry SharedDataRegistry État partagé
    Circuit Breaker CircuitBreaker Résilience pannes
    Factory Spring DI Création composants
    Builder Configuration Construction config

    9. Thread Safety

    Mécanisme Usage
    ConcurrentHashMap Maps partagées
    AtomicLong/Boolean Compteurs atomiques
    ReentrantReadWriteLock Opérations complexes
    BlockingQueue Queues thread-safe
    CompletableFuture Opérations async
    ScheduledExecutorService Scheduling
    LMAX Disruptor Ring buffer logging

    10. Points d’extension

    Créer un nouveau Worker

    @Component
    public class MonWorker implements Worker {
        // Implémenter l'interface Worker
    }
    

    Ajouter une implémentation KvBus

    public class MonKvImplementation implements KvImplementation {
        // Implémenter l'interface
    }
    

    Créer un Pipeline Stage

    public class MonStage implements PipelineStage<MonType> {
        // Implémenter le traitement
    }
    

    11. Références

  • Socle V004 – Scheduler

    Socle V004 – Scheduler

    12 – Scheduler

    Version : 4.0.0 Date : 2025-12-09

    1. Introduction

    Le Scheduler permet d’exécuter des Workers selon des expressions cron ou à intervalles réguliers.

    Caractéristiques

    • Support des expressions cron standard
    • Exécution à intervalle fixe
    • Gestion du chevauchement
    • Intégration avec le MOP

    2. Configuration

    2.1 application.yml

    socle:
      scheduler:
        enabled: ${SCHEDULER_ENABLED:true}
        thread-pool-size: ${SCHEDULER_POOL_SIZE:4}
        default-timezone: ${SCHEDULER_TIMEZONE:Europe/Paris}
    

    2.2 Variables d’environnement

    Variable Description Défaut
    SCHEDULER_ENABLED Activer le scheduler true
    SCHEDULER_POOL_SIZE Taille du thread pool 4
    SCHEDULER_TIMEZONE Timezone par défaut Europe/Paris

    3. Types de scheduling

    3.1 Cron

    Expressions cron standard (6 champs) :

    ┌───────────── seconde (0-59)
    │ ┌───────────── minute (0-59)
    │ │ ┌───────────── heure (0-23)
    │ │ │ ┌───────────── jour du mois (1-31)
    │ │ │ │ ┌───────────── mois (1-12)
    │ │ │ │ │ ┌───────────── jour de la semaine (0-6, 0=dimanche)
    │ │ │ │ │ │
    * * * * * *
    

    Exemples :

    • 0 0 6 * * ? : Tous les jours à 6h00
    • 0 */15 * * * ? : Toutes les 15 minutes
    • 0 0 0 1 * ? : Premier jour de chaque mois à minuit
    • 0 30 8 ? * MON-FRI : 8h30 du lundi au vendredi

    3.2 Intervalle fixe

    Exécution périodique simple :

    @Override
    public long getCycleIntervalMs() {
        return 60000;  // Toutes les minutes
    }
    

    4. Worker schedulé

    4.1 Avec expression cron

    @Component
    public class DailyReportWorker implements Worker {
    
        private static final Logger log = LoggerFactory.getLogger(DailyReportWorker.class);
    
        @Override
        public String getName() {
            return "daily-report-worker";
        }
    
        @Override
        public String getSchedule() {
            return "0 0 6 * * ?";  // Tous les jours à 6h
        }
    
        @Override
        public boolean isScheduled() {
            return true;
        }
    
        @Override
        public void doWork() {
            log.info("Generating daily report...");
            generateReport();
        }
    
        private void generateReport() {
            // Génération du rapport
        }
    
        // Autres méthodes Worker...
    }
    

    4.2 Avec intervalle

    @Component
    public class HealthCheckWorker implements Worker {
    
        @Override
        public String getName() {
            return "health-check-worker";
        }
    
        @Override
        public String getSchedule() {
            return null;  // Pas de cron
        }
    
        @Override
        public boolean isScheduled() {
            return false;  // Pas schedulé par cron
        }
    
        @Override
        public long getCycleIntervalMs() {
            return 30000;  // Toutes les 30 secondes
        }
    
        @Override
        public void doWork() {
            checkHealth();
        }
    }
    

    5. Interface Scheduler

    package eu.lmvi.socle.scheduler;
    
    public interface Scheduler {
    
        /**
         * Planifie un job cron
         */
        void scheduleCron(String jobId, String cronExpression, Runnable task);
    
        /**
         * Planifie un job à intervalle fixe
         */
        void scheduleInterval(String jobId, long intervalMs, Runnable task);
    
        /**
         * Planifie un job à intervalle fixe avec délai initial
         */
        void scheduleInterval(String jobId, long initialDelayMs, long intervalMs, Runnable task);
    
        /**
         * Planifie un job one-shot
         */
        void scheduleOnce(String jobId, long delayMs, Runnable task);
    
        /**
         * Annule un job
         */
        void cancel(String jobId);
    
        /**
         * Vérifie si un job est planifié
         */
        boolean isScheduled(String jobId);
    
        /**
         * Liste les jobs planifiés
         */
        List<ScheduledJob> getScheduledJobs();
    
        /**
         * Démarre le scheduler
         */
        void start();
    
        /**
         * Arrête le scheduler
         */
        void stop();
    }
    

    6. Implémentation

    package eu.lmvi.socle.scheduler;
    
    @Component
    public class DefaultScheduler implements Scheduler {
    
        private static final Logger log = LoggerFactory.getLogger(DefaultScheduler.class);
    
        private final ScheduledExecutorService executor;
        private final ConcurrentHashMap<String, ScheduledFuture<?>> jobs = new ConcurrentHashMap<>();
        private final ConcurrentHashMap<String, ScheduledJob> jobInfo = new ConcurrentHashMap<>();
        private final ZoneId timezone;
    
        public DefaultScheduler(SocleConfiguration config) {
            int poolSize = config.getScheduler().getThreadPoolSize();
            this.executor = Executors.newScheduledThreadPool(poolSize,
                r -> new Thread(r, "scheduler-" + System.currentTimeMillis()));
            this.timezone = ZoneId.of(config.getScheduler().getDefaultTimezone());
        }
    
        @Override
        public void scheduleCron(String jobId, String cronExpression, Runnable task) {
            CronExpression cron = CronExpression.parse(cronExpression);
    
            Runnable scheduledTask = () -> {
                log.debug("Executing cron job: {}", jobId);
                try {
                    task.run();
                } catch (Exception e) {
                    log.error("Error executing job {}: {}", jobId, e.getMessage(), e);
                }
                // Replanifier la prochaine exécution
                scheduleNextCronExecution(jobId, cron, task);
            };
    
            scheduleNextCronExecution(jobId, cron, task);
    
            jobInfo.put(jobId, new ScheduledJob(jobId, "cron", cronExpression, null, Instant.now()));
            log.info("Scheduled cron job: {} with expression: {}", jobId, cronExpression);
        }
    
        private void scheduleNextCronExecution(String jobId, CronExpression cron, Runnable task) {
            ZonedDateTime now = ZonedDateTime.now(timezone);
            ZonedDateTime next = cron.next(now);
    
            if (next != null) {
                long delayMs = Duration.between(now, next).toMillis();
    
                ScheduledFuture<?> future = executor.schedule(() -> {
                    task.run();
                    scheduleNextCronExecution(jobId, cron, task);
                }, delayMs, TimeUnit.MILLISECONDS);
    
                jobs.put(jobId, future);
            }
        }
    
        @Override
        public void scheduleInterval(String jobId, long intervalMs, Runnable task) {
            scheduleInterval(jobId, 0, intervalMs, task);
        }
    
        @Override
        public void scheduleInterval(String jobId, long initialDelayMs, long intervalMs, Runnable task) {
            Runnable wrappedTask = () -> {
                log.debug("Executing interval job: {}", jobId);
                try {
                    task.run();
                } catch (Exception e) {
                    log.error("Error executing job {}: {}", jobId, e.getMessage(), e);
                }
            };
    
            ScheduledFuture<?> future = executor.scheduleAtFixedRate(
                wrappedTask, initialDelayMs, intervalMs, TimeUnit.MILLISECONDS);
    
            jobs.put(jobId, future);
            jobInfo.put(jobId, new ScheduledJob(jobId, "interval", null, intervalMs, Instant.now()));
    
            log.info("Scheduled interval job: {} every {}ms", jobId, intervalMs);
        }
    
        @Override
        public void scheduleOnce(String jobId, long delayMs, Runnable task) {
            ScheduledFuture<?> future = executor.schedule(() -> {
                log.debug("Executing one-shot job: {}", jobId);
                try {
                    task.run();
                } finally {
                    jobs.remove(jobId);
                    jobInfo.remove(jobId);
                }
            }, delayMs, TimeUnit.MILLISECONDS);
    
            jobs.put(jobId, future);
            jobInfo.put(jobId, new ScheduledJob(jobId, "once", null, delayMs, Instant.now()));
    
            log.info("Scheduled one-shot job: {} in {}ms", jobId, delayMs);
        }
    
        @Override
        public void cancel(String jobId) {
            ScheduledFuture<?> future = jobs.remove(jobId);
            if (future != null) {
                future.cancel(false);
                jobInfo.remove(jobId);
                log.info("Cancelled job: {}", jobId);
            }
        }
    
        @Override
        public boolean isScheduled(String jobId) {
            return jobs.containsKey(jobId);
        }
    
        @Override
        public List<ScheduledJob> getScheduledJobs() {
            return new ArrayList<>(jobInfo.values());
        }
    
        @Override
        public void start() {
            log.info("Scheduler started");
        }
    
        @Override
        public void stop() {
            log.info("Stopping scheduler...");
            jobs.values().forEach(f -> f.cancel(false));
            jobs.clear();
            executor.shutdown();
            try {
                if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
                    executor.shutdownNow();
                }
            } catch (InterruptedException e) {
                executor.shutdownNow();
                Thread.currentThread().interrupt();
            }
            log.info("Scheduler stopped");
        }
    }
    

    7. Gestion du chevauchement

    7.1 Éviter le chevauchement

    @Component
    public class LongRunningWorker implements Worker {
    
        private final AtomicBoolean running = new AtomicBoolean(false);
    
        @Override
        public void doWork() {
            // Éviter l'exécution concurrente
            if (!running.compareAndSet(false, true)) {
                log.warn("Previous execution still running, skipping");
                return;
            }
    
            try {
                doLongWork();
            } finally {
                running.set(false);
            }
        }
    
        private void doLongWork() {
            // Traitement long
        }
    }
    

    7.2 Avec verrou distribué (multi-instances)

    @Component
    public class DistributedScheduledWorker implements Worker {
    
        @Autowired
        private KvBus kvBus;
    
        @Override
        public void doWork() {
            String lockKey = "lock:job:" + getName();
    
            // Tenter d'acquérir le lock
            if (!kvBus.putIfAbsent(lockKey, "locked", Duration.ofMinutes(10))) {
                log.debug("Job already running on another instance");
                return;
            }
    
            try {
                executeJob();
            } finally {
                kvBus.delete(lockKey);
            }
        }
    }
    

    8. Intégration MOP

    Le MOP intègre automatiquement les workers schedulés :

    // Dans MainOrchestratorProcess
    private void startScheduledWorkers() {
        for (Worker worker : workers) {
            if (worker.isScheduled() && worker.getSchedule() != null) {
                scheduler.scheduleCron(
                    "worker:" + worker.getName(),
                    worker.getSchedule(),
                    () -> {
                        if (worker.isHealthy()) {
                            worker.doWork();
                        }
                    }
                );
            }
        }
    }
    

    9. API Admin

    @RestController
    @RequestMapping("/admin/scheduler")
    public class SchedulerController {
    
        @Autowired
        private Scheduler scheduler;
    
        @GetMapping("/jobs")
        public List<ScheduledJob> listJobs() {
            return scheduler.getScheduledJobs();
        }
    
        @PostMapping("/jobs/{jobId}/cancel")
        public ResponseEntity<Void> cancelJob(@PathVariable String jobId) {
            if (scheduler.isScheduled(jobId)) {
                scheduler.cancel(jobId);
                return ResponseEntity.ok().build();
            }
            return ResponseEntity.notFound().build();
        }
    
        @PostMapping("/jobs/{jobId}/trigger")
        public ResponseEntity<Void> triggerJob(@PathVariable String jobId) {
            // Exécution immédiate one-shot
            scheduler.scheduleOnce(jobId + "-manual-" + System.currentTimeMillis(), 0, () -> {
                // Trouver et exécuter le worker correspondant
            });
            return ResponseEntity.accepted().build();
        }
    }
    

    10. Expressions Cron communes

    Expression Description
    0 0 * * * ? Toutes les heures
    0 */15 * * * ? Toutes les 15 minutes
    0 0 6 * * ? Tous les jours à 6h
    0 0 0 * * ? Tous les jours à minuit
    0 0 0 * * SUN Tous les dimanches à minuit
    0 0 0 1 * ? Premier jour du mois
    0 0 8 ? * MON-FRI 8h en semaine
    0 0 */2 * * ? Toutes les 2 heures

    11. Bonnes pratiques

    DO

    • Utiliser des noms de jobs uniques et descriptifs
    • Gérer le chevauchement pour les jobs longs
    • Utiliser des locks distribués en multi-instances
    • Logger le début et la fin des jobs
    • Monitorer l’exécution des jobs

    DON’T

    • Ne pas planifier des jobs trop fréquents sans nécessité
    • Ne pas ignorer les erreurs dans les jobs
    • Ne pas créer trop de threads
    • Ne pas bloquer indéfiniment dans un job

    12. Références

  • Socle V004 – Janino

    Socle V004 – Janino

    29 – Janino (Compilateur Java Dynamique)

    Version : 4.0.0 Date : 2026-01-17

    1. Introduction

    Janino est un compilateur Java embarqué qui permet de compiler du code source Java en bytecode JVM à la volée. Contrairement au ScriptEngine existant (interprété), Janino offre des performances natives car le code est réellement compilé.

    Positionnement

    ┌─────────────────────────────────────────────────────────────────┐
    │                      Socle V004 Scripts                          │
    ├─────────────────────────────────────────────────────────────────┤
    │                                                                  │
    │  ┌─────────────────────┐       ┌─────────────────────┐          │
    │  │    ScriptEngine     │       │   JaninoEngine      │          │
    │  │    (existant)       │       │   (NOUVEAU)         │          │
    │  ├─────────────────────┤       ├─────────────────────┤          │
    │  │ - JavaScript        │       │ - Java pur          │          │
    │  │ - BeanShell         │       │ - Bytecode natif    │          │
    │  │ - Interprété        │       │ - Haute performance │          │
    │  │ - Typage dynamique  │       │ - Typage statique   │          │
    │  └─────────────────────┘       └─────────────────────┘          │
    │                                                                  │
    └─────────────────────────────────────────────────────────────────┘
    

    Cas d’usage

    Situation Recommandation
    Calculs financiers (frais, taxes) Janino
    Validations métier complexes Janino
    Transformations haute performance Janino
    Scripts simples, prototypage ScriptEngine
    Formules configurables Janino

    2. Architecture

    2.1 Composants

    eu.lmvi.socle.janino/
    ├── JaninoEngine.java              # Moteur principal
    ├── JaninoWorker.java              # Worker de gestion
    ├── JaninoScript.java              # Script compilé
    ├── JaninoClassLoader.java         # ClassLoader sécurisé
    ├── JaninoConfiguration.java       # Configuration Spring
    ├── JaninoCompilationException.java # Exception
    └── interfaces/
        ├── Calculator.java            # Interface calculateur
        ├── Executable.java            # Interface exécutable
        ├── Validator.java             # Interface validateur
        └── ValidationResult.java      # Résultat validation
    

    2.2 Diagramme de classes

    ┌───────────────────────┐
    │   JaninoWorker        │  (implements Worker)
    │   @Component          │
    ├───────────────────────┤
    │ - janinoEngine        │
    │ - config              │
    │ - techDb              │
    │ - supervisor          │
    ├───────────────────────┤
    │ + execute()           │
    │ + compileScript()     │
    │ + forceReload()       │
    │ + getEngine()         │
    └───────────┬───────────┘
                │
                ▼
    ┌───────────────────────┐
    │   JaninoEngine        │
    │   @Component          │
    ├───────────────────────┤
    │ - scriptCache         │
    │ - classLoader         │
    ├───────────────────────┤
    │ + compile()           │
    │ + execute()           │
    │ + reload()            │
    │ + getStats()          │
    └───────────┬───────────┘
                │
                ▼
    ┌───────────────────────┐
    │   JaninoScript        │
    ├───────────────────────┤
    │ - name                │
    │ - compiledClass       │
    │ - executionCount      │
    │ - avgExecutionTimeNs  │
    └───────────────────────┘
    

    3. Configuration

    3.1 application.yml

    socle:
      janino:
        # Activer Janino (défaut: false)
        enabled: ${JANINO_ENABLED:false}
    
        # Répertoire des scripts Java
        scripts-path: ${JANINO_SCRIPTS_PATH:./repository/scripts/java}
    
        # Intervalle de rechargement (défaut: 5 minutes)
        reload-interval-ms: ${JANINO_RELOAD_INTERVAL:300000}
    
        # Nombre max de classes en cache
        max-cached-classes: ${JANINO_MAX_CACHED:100}
    
        # Sécurité
        security:
          # Packages bloqués dans les scripts
          blocked-packages:
            - java.io
            - java.net
            - java.lang.reflect
            - java.lang.invoke
            - sun.
            - com.sun.
    
          # Timeout d'exécution max
          max-execution-time-ms: ${JANINO_MAX_EXEC_TIME:5000}
    

    3.2 Variables d’environnement

    Variable Description Défaut
    JANINO_ENABLED Activer Janino false
    JANINO_SCRIPTS_PATH Répertoire scripts ./repository/scripts/java
    JANINO_RELOAD_INTERVAL Intervalle reload (ms) 300000
    JANINO_MAX_CACHED Max classes en cache 100
    JANINO_MAX_EXEC_TIME Timeout exécution (ms) 5000

    4. Interfaces de Scripts

    Les scripts Java doivent implémenter une des interfaces suivantes :

    4.1 Calculator

    Pour les calculs (frais, taxes, conversions).

    package eu.lmvi.socle.janino.interfaces;
    
    public interface Calculator<T> {
        T calculate(Map<String, Object> context);
    }
    

    Exemple :

    import eu.lmvi.socle.janino.interfaces.Calculator;
    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.util.Map;
    
    public class FeeCalculator implements Calculator<BigDecimal> {
    
        private static final BigDecimal FEE_RATE = new BigDecimal("0.0026");
    
        @Override
        public BigDecimal calculate(Map<String, Object> context) {
            BigDecimal amount = (BigDecimal) context.get("amount");
            String side = (String) context.get("side");
    
            BigDecimal rate = "MAKER".equals(side)
                ? new BigDecimal("0.0016")
                : FEE_RATE;
    
            return amount.multiply(rate).setScale(8, RoundingMode.HALF_UP);
        }
    }
    

    4.2 Executable

    Pour les exécutions génériques.

    package eu.lmvi.socle.janino.interfaces;
    
    public interface Executable {
        Object execute(Map<String, Object> context);
    }
    

    Exemple :

    import eu.lmvi.socle.janino.interfaces.Executable;
    import java.util.Map;
    
    public class OrderProcessor implements Executable {
    
        @Override
        public Object execute(Map<String, Object> context) {
            String orderId = (String) context.get("orderId");
            Double amount = (Double) context.get("amount");
    
            // Logique de traitement...
    
            return Map.of(
                "status", "PROCESSED",
                "orderId", orderId,
                "processedAmount", amount * 0.99
            );
        }
    }
    

    4.3 Validator

    Pour les validations métier.

    package eu.lmvi.socle.janino.interfaces;
    
    public interface Validator {
        ValidationResult validate(Object input);
    }
    

    Exemple :

    import eu.lmvi.socle.janino.interfaces.Validator;
    import eu.lmvi.socle.janino.interfaces.ValidationResult;
    import java.math.BigDecimal;
    
    public class OrderValidator implements Validator {
    
        private static final BigDecimal MIN_AMOUNT = new BigDecimal("10.00");
        private static final BigDecimal MAX_AMOUNT = new BigDecimal("100000.00");
    
        @Override
        public ValidationResult validate(Object input) {
            Order order = (Order) input;
            ValidationResult result = new ValidationResult();
    
            if (order.getAmount().compareTo(MIN_AMOUNT) < 0) {
                result.addError("AMOUNT_TOO_LOW",
                    "Amount must be >= " + MIN_AMOUNT);
            }
    
            if (order.getAmount().compareTo(MAX_AMOUNT) > 0) {
                result.addError("AMOUNT_TOO_HIGH",
                    "Amount must be <= " + MAX_AMOUNT);
            }
    
            if (order.getPair() == null || order.getPair().isEmpty()) {
                result.addError("INVALID_PAIR", "Trading pair is required");
            }
    
            return result;
        }
    }
    

    5. Utilisation

    5.1 Structure des scripts

    repository/scripts/java/
    ├── fees/
    │   ├── KrakenFeeCalculator.java
    │   ├── BinanceFeeCalculator.java
    │   └── CryptoComFeeCalculator.java
    ├── validators/
    │   ├── OrderValidator.java
    │   └── AmountValidator.java
    └── processors/
        ├── OrderProcessor.java
        └── TradeProcessor.java
    

    5.2 Injection dans un Worker

    @Component
    public class TradingWorker implements Worker {
    
        @Autowired
        private JaninoWorker janinoWorker;
    
        @Override
        public void doWork() {
            // Exécuter un calculateur
            Map<String, Object> context = Map.of(
                "amount", new BigDecimal("1000.00"),
                "side", "TAKER"
            );
    
            BigDecimal fee = janinoWorker.execute(
                "KrakenFeeCalculator",
                context,
                BigDecimal.class
            );
    
            log.info("Fee calculated: {}", fee);
        }
    }
    

    5.3 Compilation à la volée

    @Autowired
    private JaninoWorker janinoWorker;
    
    public void compileCustomScript() {
        String source = """
            import eu.lmvi.socle.janino.interfaces.Calculator;
            import java.math.BigDecimal;
            import java.util.Map;
    
            public class CustomCalculator implements Calculator<BigDecimal> {
                @Override
                public BigDecimal calculate(Map<String, Object> context) {
                    BigDecimal value = (BigDecimal) context.get("value");
                    return value.multiply(new BigDecimal("1.05"));
                }
            }
            """;
    
        janinoWorker.compileScript("CustomCalculator", source);
    
        // Exécuter
        BigDecimal result = janinoWorker.execute(
            "CustomCalculator",
            Map.of("value", new BigDecimal("100")),
            BigDecimal.class
        );
    }
    

    5.4 Accès direct au moteur

    @Autowired
    private JaninoWorker janinoWorker;
    
    public void advancedUsage() {
        JaninoEngine engine = janinoWorker.getEngine();
    
        // Vérifier si un script est compilé
        boolean ready = engine.isCompiled("FeeCalculator");
    
        // Liste des scripts compilés
        Set<String> scripts = engine.getCompiledScripts();
    
        // Statistiques détaillées
        Map<String, Map<String, Object>> stats = engine.getScriptStats();
    
        // Forcer le rechargement
        janinoWorker.forceReload();
    }
    

    6. Hot-Reload

    Le JaninoWorker surveille automatiquement les modifications des fichiers .java dans le répertoire configuré.

    Fonctionnement

    1. À chaque cycle (reload-interval-ms), le worker scanne le répertoire
    2. Pour chaque fichier modifié (timestamp changé), le script est recompilé
    3. Le nouveau bytecode remplace l’ancien dans le cache
    4. Les prochaines exécutions utilisent la nouvelle version

    Logs

    [exec:xxx][step:janino_reloaded] Reloaded script: FeeCalculator
    [exec:xxx][step:janino_reload_cycle] Reload cycle completed (5 scripts)
    

    Forcer le rechargement

    // Recharger tous les scripts
    janinoWorker.forceReload();
    

    7. Securite

    7.1 Validation des Imports (Source-Level)

    Le JaninoEngine valide les imports avant la compilation pour bloquer l’acces aux packages dangereux. Cette approche (validation au niveau du code source) est plus compatible avec les fat-jars Spring Boot que la precedente approche ClassLoader.

    Package bloque Raison
    java.io Acces fichiers
    java.net Acces reseau
    java.lang.reflect Reflection
    java.lang.invoke MethodHandles
    sun.* Classes internes
    com.sun.* Classes internes

    7.2 Fonctionnement

    Source Java → validateSourceSecurity() → Compilation Janino → Execution
                         ↓
                  Analyse des imports
                         ↓
                Blocage si package interdit
    

    Le moteur analyse les declarations import dans le code source et rejette le script si un package bloque est detecte.

    7.3 Tentative d’acces bloque

    // Ce script sera bloque AVANT compilation
    import java.io.File;  // BLOQUE!
    
    public class MaliciousScript implements Executable {
        @Override
        public Object execute(Map<String, Object> context) {
            File file = new File("/etc/passwd");
            return null;
        }
    }
    

    Erreur :

    JaninoCompilationException: Security violation in script 'MaliciousScript':
    Import of blocked package 'java.io' is not allowed. Blocked import: java.io.File
    

    7.4 Configuration personnalisee

    socle:
      janino:
        security:
          blocked-packages:
            - java.io
            - java.net
            - java.lang.reflect
            - java.lang.invoke
            - sun.
            - com.sun.
            - com.mycompany.internal  # Packages internes custom
    

    8. Métriques et Monitoring

    8.1 Stats du Worker

    Map<String, Object> stats = janinoWorker.getStats();
    
    // Résultat:
    {
        "name": "janino_worker",
        "running": true,
        "healthy": true,
        "scripts_path": "./repository/scripts/java",
        "reload_interval_ms": 300000,
        "reload_count": 15,
        "last_reload_at": "2026-01-17T10:30:00Z",
        "scripts_compiled": 5,
        "compilation_count": 8,
        "compilation_errors": 0,
        "execution_count": 1234,
        "execution_errors": 2
    }
    

    8.2 Stats par script

    Map<String, Map<String, Object>> scriptStats = janinoWorker.getEngine().getScriptStats();
    
    // Résultat:
    {
        "KrakenFeeCalculator": {
            "class": "KrakenFeeCalculator",
            "compiled_at": 1705487400000,
            "execution_count": 500,
            "avg_execution_us": 12
        },
        "BinanceFeeCalculator": {
            "class": "BinanceFeeCalculator",
            "compiled_at": 1705487400000,
            "execution_count": 300,
            "avg_execution_us": 8
        }
    }
    

    8.3 TechDB

    Le worker persiste ses stats dans la table janino_stats :

    SELECT * FROM janino_stats;
    
    -- worker_name | scripts_count | compilation_count | execution_count | reload_count | last_reload
    -- janino_worker | 5 | 8 | 1234 | 15 | 2026-01-17 10:30:00
    

    9. Comparaison avec ScriptEngine

    Critère ScriptEngine JaninoEngine
    Langages JavaScript, BeanShell Java pur
    Exécution Interprétée Compilée (bytecode)
    Performance ~1000x plus lent Native JVM
    Typage Dynamique Statique
    IDE Support Limité Complet (Java)
    Debug Difficile Standard Java
    Hot-reload Non Oui
    Sécurité Sandbox complexe ClassLoader isolation
    Courbe apprentissage Nouveau langage Java existant

    Quand utiliser quoi ?

    Situation Recommandation
    Calculs critiques (frais, taxes) JaninoEngine
    Validations complexes JaninoEngine
    Scripts simples, one-liners ScriptEngine
    Prototypage rapide ScriptEngine
    Logique métier complexe JaninoEngine
    Transformations JSON basiques ScriptEngine

    10. Dépannage

    10.1 Script non compilé

    Erreur :

    IllegalStateException: Script not compiled: MyScript
    

    Solution :

    • Vérifier que le fichier existe dans scripts-path
    • Vérifier l’extension .java
    • Consulter les logs pour les erreurs de compilation

    10.2 Erreur de compilation

    Erreur :

    JaninoCompilationException: Failed to compile script: MyScript
    

    Solution :

    • Vérifier la syntaxe Java
    • Vérifier les imports
    • Vérifier que la classe implémente une interface valide

    10.3 Classe non trouvée

    Erreur :

    Cannot find class name in source
    

    Solution :

    • Le source doit contenir public class NomDeLaClasse
    • Le nom de la classe doit correspondre au nom du fichier

    10.4 Package bloque

    Erreur :

    JaninoCompilationException: Security violation in script 'MyScript':
    Import of blocked package 'java.io' is not allowed
    

    Solution :

    • Utiliser uniquement les packages autorises
    • Si necessaire, modifier la config blocked-packages

    10.5 Cannot load simple types (Fat-Jar Spring Boot)

    Erreur :

    org.codehaus.commons.compiler.CompileException: Cannot load simple types
    

    Cause : Ce probleme survient dans les fat-jars Spring Boot car le classloader personnalise casse la resolution des modules Java 9+.

    Solution : Cette erreur a ete corrigee dans le socle V4. Le JaninoEngine utilise maintenant le context classloader directement :

    // JaninoEngine.java - ligne 117-121
    ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
    if (contextLoader == null) {
        contextLoader = getClass().getClassLoader();
    }
    compiler.setParentClassLoader(contextLoader);
    

    Si vous rencontrez encore cette erreur, verifiez que vous utilisez la derniere version du socle.

    10.6 Assignment conversion not possible from Object

    Erreur :

    Line 35, Column 41: Assignment conversion not possible from type "java.lang.Object" to type "java.math.BigDecimal"
    

    Cause : Janino ne supporte pas l’inference de type pour Map.get().

    Solution : Ajouter un cast explicite :

    // AVANT (erreur)
    BigDecimal rate = myMap.get(key);
    
    // APRES (correct)
    BigDecimal rate = (BigDecimal) myMap.get(key);
    

    10.7 Invalid escape sequence

    Erreur :

    Line 27, Column 35: Invalid escape sequence
    

    Cause : Les sequences d’echappement regex (\s, \d, \{) doivent etre double-echappees.

    Solution :

    // AVANT (erreur)
    Pattern p = Pattern.compile("\s+");
    
    // APRES (correct)
    Pattern p = Pattern.compile("\\s+");
    

    10.8 Invocation of static interface methods

    Erreur :

    Invocation of static interface methods only available for target version 8+
    

    Cause : Janino ne supporte pas Map.of(), Set.of(), List.of() (Java 9+).

    Solution : Utiliser des blocs static :

    // AVANT (erreur)
    private static final Map<String, String> DATA = Map.of("a", "b");
    
    // APRES (correct)
    private static final Map<String, String> DATA = new HashMap<>();
    static {
        DATA.put("a", "b");
    }
    

    11. Bonnes Pratiques

    DO

    • Implémenter une des interfaces (Calculator, Executable, Validator)
    • Utiliser des types explicites (pas de var)
    • Gérer les exceptions dans le script
    • Tester les scripts avant déploiement
    • Utiliser des noms de classes descriptifs

    DON’T

    • Ne pas utiliser java.io, java.net, java.lang.reflect
    • Ne pas stocker d’état entre les exécutions (stateless)
    • Ne pas faire d’opérations bloquantes longues
    • Ne pas utiliser de dépendances externes non disponibles

    12. Limitations Janino et Compatibilite

    Janino est un compilateur Java simplifie qui ne supporte pas toutes les fonctionnalites du langage Java moderne. Cette section documente les limitations et les solutions.

    12.1 Fonctionnalites Non Supportees

    Fonctionnalite Version Java Statut Janino
    Methodes generiques <T> Java 5+ NON SUPPORTE
    Map.of(), Set.of(), List.of() Java 9+ NON SUPPORTE
    switch expressions Java 14+ NON SUPPORTE
    var (inference de type) Java 10+ NON SUPPORTE
    Records Java 16+ NON SUPPORTE
    Pattern matching Java 16+ NON SUPPORTE
    Text blocks """ Java 15+ NON SUPPORTE

    12.2 Solutions et Contournements

    A. Pas de methodes generiques

    // INTERDIT - Ne compile pas
    private <T extends Number> T extractValue(String json, String key, Class<T> type) {
        // ...
    }
    
    // CORRECT - Methodes specifiques par type
    private Integer extractIntValue(String json, String key) {
        // implementation pour Integer
    }
    
    private Double extractDoubleValue(String json, String key) {
        // implementation pour Double
    }
    

    B. Pas de Map.of() / Set.of() / List.of()

    // INTERDIT - Ne compile pas
    private static final Map<String, BigDecimal> RATES = Map.of(
        "FR", new BigDecimal("0.015"),
        "DE", new BigDecimal("0.018")
    );
    
    // CORRECT - Bloc static avec HashMap
    private static final Map<String, BigDecimal> RATES = new HashMap<>();
    static {
        RATES.put("FR", new BigDecimal("0.015"));
        RATES.put("DE", new BigDecimal("0.018"));
    }
    
    // CORRECT - Pour Set
    private static final Set<String> COUNTRIES = new HashSet<>();
    static {
        COUNTRIES.add("FR");
        COUNTRIES.add("DE");
    }
    

    C. Cast explicite pour Map.get()

    // INTERDIT - "Assignment conversion not possible from Object to BigDecimal"
    BigDecimal rate = RATES.get(country);
    
    // CORRECT - Cast explicite
    BigDecimal rate = (BigDecimal) RATES.get(country);
    if (rate == null) rate = BigDecimal.ZERO;
    

    D. Pas de switch expressions

    // INTERDIT - Ne compile pas
    String result = switch (status) {
        case "A" -> "Active";
        case "I" -> "Inactive";
        default -> "Unknown";
    };
    
    // CORRECT - Switch statement classique
    String result;
    switch (status) {
        case "A": result = "Active"; break;
        case "I": result = "Inactive"; break;
        default: result = "Unknown";
    }
    

    E. Double echappement des regex

    // INTERDIT - "Invalid escape sequence"
    Pattern p = Pattern.compile("\s+");
    Pattern p2 = Pattern.compile("\{.*\}");
    
    // CORRECT - Double echappement
    Pattern p = Pattern.compile("\\s+");
    Pattern p2 = Pattern.compile("\\{.*\\}");
    

    12.3 Template de Script Compatible

    Voici un template de script qui fonctionne avec Janino :

    import eu.lmvi.socle.janino.interfaces.Calculator;
    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.util.Map;
    import java.util.HashMap;
    import java.util.Set;
    import java.util.HashSet;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class MyCalculator implements Calculator<BigDecimal> {
    
        // Collections statiques avec bloc static
        private static final Map<String, BigDecimal> RATES = new HashMap<>();
        private static final Set<String> VALID_CODES = new HashSet<>();
    
        static {
            RATES.put("A", new BigDecimal("0.10"));
            RATES.put("B", new BigDecimal("0.20"));
    
            VALID_CODES.add("X");
            VALID_CODES.add("Y");
        }
    
        @Override
        public BigDecimal calculate(Map<String, Object> context) {
            // Extraction avec cast explicite
            BigDecimal amount = extractAmount(context.get("amount"));
            String code = (String) context.get("code");
            if (code == null) code = "A";
    
            // Acces Map avec cast
            BigDecimal rate = (BigDecimal) RATES.get(code);
            if (rate == null) rate = new BigDecimal("0.15");
    
            return amount.multiply(rate).setScale(2, RoundingMode.HALF_UP);
        }
    
        // Methode d'extraction type-safe (pas de generiques)
        private BigDecimal extractAmount(Object value) {
            if (value == null) return BigDecimal.ZERO;
            if (value instanceof BigDecimal) return (BigDecimal) value;
            if (value instanceof Number) {
                return BigDecimal.valueOf(((Number) value).doubleValue());
            }
            try {
                return new BigDecimal(value.toString());
            } catch (NumberFormatException e) {
                return BigDecimal.ZERO;
            }
        }
    }
    

    12.4 Checklist de Compatibilite

    Avant de deployer un script Janino, verifiez :

    • [ ] Pas de <T> dans les signatures de methodes
    • [ ] Pas de Map.of(), Set.of(), List.of()
    • [ ] Pas de var pour les declarations
    • [ ] Pas de switch expressions (fleches ->)
    • [ ] Cast explicite (Type) pour tous les Map.get()
    • [ ] Double echappement \\ dans les regex
    • [ ] Pas de text blocks """
    • [ ] Imports explicites (pas de wildcards import java.util.*)

    13. Exemple Complet

    13.1 Script : TradingFeeCalculator.java

    import eu.lmvi.socle.janino.interfaces.Calculator;
    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.util.Map;
    import java.util.HashMap;
    
    /**
     * Calculateur de frais de trading multi-exchange.
     * Compatible Janino (pas de switch expressions, pas de Map.of)
     */
    public class TradingFeeCalculator implements Calculator<BigDecimal> {
    
        // Taux MAKER par exchange
        private static final Map<String, BigDecimal> MAKER_RATES = new HashMap<>();
        // Taux TAKER par exchange
        private static final Map<String, BigDecimal> TAKER_RATES = new HashMap<>();
    
        static {
            MAKER_RATES.put("KRAKEN", new BigDecimal("0.0016"));
            MAKER_RATES.put("BINANCE", new BigDecimal("0.0010"));
            MAKER_RATES.put("COINBASE", new BigDecimal("0.0040"));
    
            TAKER_RATES.put("KRAKEN", new BigDecimal("0.0026"));
            TAKER_RATES.put("BINANCE", new BigDecimal("0.0010"));
            TAKER_RATES.put("COINBASE", new BigDecimal("0.0060"));
        }
    
        private static final BigDecimal DEFAULT_RATE = new BigDecimal("0.0025");
    
        @Override
        public BigDecimal calculate(Map<String, Object> context) {
            String exchange = (String) context.get("exchange");
            BigDecimal amount = (BigDecimal) context.get("amount");
            String side = (String) context.get("side");
    
            if (exchange == null) exchange = "DEFAULT";
            if (side == null) side = "TAKER";
    
            BigDecimal feeRate = getFeeRate(exchange.toUpperCase(), side.toUpperCase());
    
            return amount.multiply(feeRate).setScale(8, RoundingMode.HALF_UP);
        }
    
        private BigDecimal getFeeRate(String exchange, String side) {
            boolean isMaker = "MAKER".equals(side);
    
            // Cast explicite requis par Janino
            BigDecimal rate;
            if (isMaker) {
                rate = (BigDecimal) MAKER_RATES.get(exchange);
            } else {
                rate = (BigDecimal) TAKER_RATES.get(exchange);
            }
    
            if (rate == null) {
                rate = DEFAULT_RATE;
            }
            return rate;
        }
    }
    

    13.2 Worker utilisant le script

    Note : Ce Worker est du code Java standard compile par javac/Maven, pas un script Janino. Il peut donc utiliser List.of(), Map.of() et les fonctionnalites Java modernes.

    @Component
    public class FeeWorker implements Worker {
    
        private static final Logger log = LoggerFactory.getLogger(FeeWorker.class);
    
        @Autowired
        private JaninoWorker janinoWorker;
    
        @Override
        public String getName() {
            return "fee-worker";
        }
    
        @Override
        public void doWork() {
            // Calculer les frais pour différents exchanges
            List<String> exchanges = List.of("KRAKEN", "BINANCE", "COINBASE");
            BigDecimal amount = new BigDecimal("10000.00");
    
            for (String exchange : exchanges) {
                Map<String, Object> context = Map.of(
                    "exchange", exchange,
                    "amount", amount,
                    "side", "TAKER"
                );
    
                BigDecimal fee = janinoWorker.execute(
                    "TradingFeeCalculator",
                    context,
                    BigDecimal.class
                );
    
                log.info("Fee for {} on amount {}: {}",
                    exchange, amount, fee);
            }
        }
    
        // ... autres méthodes Worker
    }
    

    14. References

  • Socle V004 – Scheduler

    Socle V004 – Scheduler

    12 – Scheduler

    Version : 4.0.0 Date : 2025-12-09

    1. Introduction

    Le Scheduler permet d’exécuter des Workers selon des expressions cron ou à intervalles réguliers.

    Caractéristiques

    • Support des expressions cron standard
    • Exécution à intervalle fixe
    • Gestion du chevauchement
    • Intégration avec le MOP

    2. Configuration

    2.1 application.yml

    socle:
      scheduler:
        enabled: ${SCHEDULER_ENABLED:true}
        thread-pool-size: ${SCHEDULER_POOL_SIZE:4}
        default-timezone: ${SCHEDULER_TIMEZONE:Europe/Paris}
    

    2.2 Variables d’environnement

    Variable Description Défaut
    SCHEDULER_ENABLED Activer le scheduler true
    SCHEDULER_POOL_SIZE Taille du thread pool 4
    SCHEDULER_TIMEZONE Timezone par défaut Europe/Paris

    3. Types de scheduling

    3.1 Cron

    Expressions cron standard (6 champs) :

    ┌───────────── seconde (0-59)
    │ ┌───────────── minute (0-59)
    │ │ ┌───────────── heure (0-23)
    │ │ │ ┌───────────── jour du mois (1-31)
    │ │ │ │ ┌───────────── mois (1-12)
    │ │ │ │ │ ┌───────────── jour de la semaine (0-6, 0=dimanche)
    │ │ │ │ │ │
    * * * * * *
    

    Exemples :

    • 0 0 6 * * ? : Tous les jours à 6h00
    • 0 */15 * * * ? : Toutes les 15 minutes
    • 0 0 0 1 * ? : Premier jour de chaque mois à minuit
    • 0 30 8 ? * MON-FRI : 8h30 du lundi au vendredi

    3.2 Intervalle fixe

    Exécution périodique simple :

    @Override
    public long getCycleIntervalMs() {
        return 60000;  // Toutes les minutes
    }
    

    4. Worker schedulé

    4.1 Avec expression cron

    @Component
    public class DailyReportWorker implements Worker {
    
        private static final Logger log = LoggerFactory.getLogger(DailyReportWorker.class);
    
        @Override
        public String getName() {
            return "daily-report-worker";
        }
    
        @Override
        public String getSchedule() {
            return "0 0 6 * * ?";  // Tous les jours à 6h
        }
    
        @Override
        public boolean isScheduled() {
            return true;
        }
    
        @Override
        public void doWork() {
            log.info("Generating daily report...");
            generateReport();
        }
    
        private void generateReport() {
            // Génération du rapport
        }
    
        // Autres méthodes Worker...
    }
    

    4.2 Avec intervalle

    @Component
    public class HealthCheckWorker implements Worker {
    
        @Override
        public String getName() {
            return "health-check-worker";
        }
    
        @Override
        public String getSchedule() {
            return null;  // Pas de cron
        }
    
        @Override
        public boolean isScheduled() {
            return false;  // Pas schedulé par cron
        }
    
        @Override
        public long getCycleIntervalMs() {
            return 30000;  // Toutes les 30 secondes
        }
    
        @Override
        public void doWork() {
            checkHealth();
        }
    }
    

    5. Interface Scheduler

    package eu.lmvi.socle.scheduler;
    
    public interface Scheduler {
    
        /**
         * Planifie un job cron
         */
        void scheduleCron(String jobId, String cronExpression, Runnable task);
    
        /**
         * Planifie un job à intervalle fixe
         */
        void scheduleInterval(String jobId, long intervalMs, Runnable task);
    
        /**
         * Planifie un job à intervalle fixe avec délai initial
         */
        void scheduleInterval(String jobId, long initialDelayMs, long intervalMs, Runnable task);
    
        /**
         * Planifie un job one-shot
         */
        void scheduleOnce(String jobId, long delayMs, Runnable task);
    
        /**
         * Annule un job
         */
        void cancel(String jobId);
    
        /**
         * Vérifie si un job est planifié
         */
        boolean isScheduled(String jobId);
    
        /**
         * Liste les jobs planifiés
         */
        List<ScheduledJob> getScheduledJobs();
    
        /**
         * Démarre le scheduler
         */
        void start();
    
        /**
         * Arrête le scheduler
         */
        void stop();
    }
    

    6. Implémentation

    package eu.lmvi.socle.scheduler;
    
    @Component
    public class DefaultScheduler implements Scheduler {
    
        private static final Logger log = LoggerFactory.getLogger(DefaultScheduler.class);
    
        private final ScheduledExecutorService executor;
        private final ConcurrentHashMap<String, ScheduledFuture<?>> jobs = new ConcurrentHashMap<>();
        private final ConcurrentHashMap<String, ScheduledJob> jobInfo = new ConcurrentHashMap<>();
        private final ZoneId timezone;
    
        public DefaultScheduler(SocleConfiguration config) {
            int poolSize = config.getScheduler().getThreadPoolSize();
            this.executor = Executors.newScheduledThreadPool(poolSize,
                r -> new Thread(r, "scheduler-" + System.currentTimeMillis()));
            this.timezone = ZoneId.of(config.getScheduler().getDefaultTimezone());
        }
    
        @Override
        public void scheduleCron(String jobId, String cronExpression, Runnable task) {
            CronExpression cron = CronExpression.parse(cronExpression);
    
            Runnable scheduledTask = () -> {
                log.debug("Executing cron job: {}", jobId);
                try {
                    task.run();
                } catch (Exception e) {
                    log.error("Error executing job {}: {}", jobId, e.getMessage(), e);
                }
                // Replanifier la prochaine exécution
                scheduleNextCronExecution(jobId, cron, task);
            };
    
            scheduleNextCronExecution(jobId, cron, task);
    
            jobInfo.put(jobId, new ScheduledJob(jobId, "cron", cronExpression, null, Instant.now()));
            log.info("Scheduled cron job: {} with expression: {}", jobId, cronExpression);
        }
    
        private void scheduleNextCronExecution(String jobId, CronExpression cron, Runnable task) {
            ZonedDateTime now = ZonedDateTime.now(timezone);
            ZonedDateTime next = cron.next(now);
    
            if (next != null) {
                long delayMs = Duration.between(now, next).toMillis();
    
                ScheduledFuture<?> future = executor.schedule(() -> {
                    task.run();
                    scheduleNextCronExecution(jobId, cron, task);
                }, delayMs, TimeUnit.MILLISECONDS);
    
                jobs.put(jobId, future);
            }
        }
    
        @Override
        public void scheduleInterval(String jobId, long intervalMs, Runnable task) {
            scheduleInterval(jobId, 0, intervalMs, task);
        }
    
        @Override
        public void scheduleInterval(String jobId, long initialDelayMs, long intervalMs, Runnable task) {
            Runnable wrappedTask = () -> {
                log.debug("Executing interval job: {}", jobId);
                try {
                    task.run();
                } catch (Exception e) {
                    log.error("Error executing job {}: {}", jobId, e.getMessage(), e);
                }
            };
    
            ScheduledFuture<?> future = executor.scheduleAtFixedRate(
                wrappedTask, initialDelayMs, intervalMs, TimeUnit.MILLISECONDS);
    
            jobs.put(jobId, future);
            jobInfo.put(jobId, new ScheduledJob(jobId, "interval", null, intervalMs, Instant.now()));
    
            log.info("Scheduled interval job: {} every {}ms", jobId, intervalMs);
        }
    
        @Override
        public void scheduleOnce(String jobId, long delayMs, Runnable task) {
            ScheduledFuture<?> future = executor.schedule(() -> {
                log.debug("Executing one-shot job: {}", jobId);
                try {
                    task.run();
                } finally {
                    jobs.remove(jobId);
                    jobInfo.remove(jobId);
                }
            }, delayMs, TimeUnit.MILLISECONDS);
    
            jobs.put(jobId, future);
            jobInfo.put(jobId, new ScheduledJob(jobId, "once", null, delayMs, Instant.now()));
    
            log.info("Scheduled one-shot job: {} in {}ms", jobId, delayMs);
        }
    
        @Override
        public void cancel(String jobId) {
            ScheduledFuture<?> future = jobs.remove(jobId);
            if (future != null) {
                future.cancel(false);
                jobInfo.remove(jobId);
                log.info("Cancelled job: {}", jobId);
            }
        }
    
        @Override
        public boolean isScheduled(String jobId) {
            return jobs.containsKey(jobId);
        }
    
        @Override
        public List<ScheduledJob> getScheduledJobs() {
            return new ArrayList<>(jobInfo.values());
        }
    
        @Override
        public void start() {
            log.info("Scheduler started");
        }
    
        @Override
        public void stop() {
            log.info("Stopping scheduler...");
            jobs.values().forEach(f -> f.cancel(false));
            jobs.clear();
            executor.shutdown();
            try {
                if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
                    executor.shutdownNow();
                }
            } catch (InterruptedException e) {
                executor.shutdownNow();
                Thread.currentThread().interrupt();
            }
            log.info("Scheduler stopped");
        }
    }
    

    7. Gestion du chevauchement

    7.1 Éviter le chevauchement

    @Component
    public class LongRunningWorker implements Worker {
    
        private final AtomicBoolean running = new AtomicBoolean(false);
    
        @Override
        public void doWork() {
            // Éviter l'exécution concurrente
            if (!running.compareAndSet(false, true)) {
                log.warn("Previous execution still running, skipping");
                return;
            }
    
            try {
                doLongWork();
            } finally {
                running.set(false);
            }
        }
    
        private void doLongWork() {
            // Traitement long
        }
    }
    

    7.2 Avec verrou distribué (multi-instances)

    @Component
    public class DistributedScheduledWorker implements Worker {
    
        @Autowired
        private KvBus kvBus;
    
        @Override
        public void doWork() {
            String lockKey = "lock:job:" + getName();
    
            // Tenter d'acquérir le lock
            if (!kvBus.putIfAbsent(lockKey, "locked", Duration.ofMinutes(10))) {
                log.debug("Job already running on another instance");
                return;
            }
    
            try {
                executeJob();
            } finally {
                kvBus.delete(lockKey);
            }
        }
    }
    

    8. Intégration MOP

    Le MOP intègre automatiquement les workers schedulés :

    // Dans MainOrchestratorProcess
    private void startScheduledWorkers() {
        for (Worker worker : workers) {
            if (worker.isScheduled() && worker.getSchedule() != null) {
                scheduler.scheduleCron(
                    "worker:" + worker.getName(),
                    worker.getSchedule(),
                    () -> {
                        if (worker.isHealthy()) {
                            worker.doWork();
                        }
                    }
                );
            }
        }
    }
    

    9. API Admin

    @RestController
    @RequestMapping("/admin/scheduler")
    public class SchedulerController {
    
        @Autowired
        private Scheduler scheduler;
    
        @GetMapping("/jobs")
        public List<ScheduledJob> listJobs() {
            return scheduler.getScheduledJobs();
        }
    
        @PostMapping("/jobs/{jobId}/cancel")
        public ResponseEntity<Void> cancelJob(@PathVariable String jobId) {
            if (scheduler.isScheduled(jobId)) {
                scheduler.cancel(jobId);
                return ResponseEntity.ok().build();
            }
            return ResponseEntity.notFound().build();
        }
    
        @PostMapping("/jobs/{jobId}/trigger")
        public ResponseEntity<Void> triggerJob(@PathVariable String jobId) {
            // Exécution immédiate one-shot
            scheduler.scheduleOnce(jobId + "-manual-" + System.currentTimeMillis(), 0, () -> {
                // Trouver et exécuter le worker correspondant
            });
            return ResponseEntity.accepted().build();
        }
    }
    

    10. Expressions Cron communes

    Expression Description
    0 0 * * * ? Toutes les heures
    0 */15 * * * ? Toutes les 15 minutes
    0 0 6 * * ? Tous les jours à 6h
    0 0 0 * * ? Tous les jours à minuit
    0 0 0 * * SUN Tous les dimanches à minuit
    0 0 0 1 * ? Premier jour du mois
    0 0 8 ? * MON-FRI 8h en semaine
    0 0 */2 * * ? Toutes les 2 heures

    11. Bonnes pratiques

    DO

    • Utiliser des noms de jobs uniques et descriptifs
    • Gérer le chevauchement pour les jobs longs
    • Utiliser des locks distribués en multi-instances
    • Logger le début et la fin des jobs
    • Monitorer l’exécution des jobs

    DON’T

    • Ne pas planifier des jobs trop fréquents sans nécessité
    • Ne pas ignorer les erreurs dans les jobs
    • Ne pas créer trop de threads
    • Ne pas bloquer indéfiniment dans un job

    12. Références

  • Socle V004 – Architecture

    Socle V004 – Architecture

    02 – Architecture du Socle V4

    Version : 4.0.0 Date : 2025-01-25

    1. Vue d’ensemble

    ┌─────────────────────────────────────────────────────────────────┐
    │                         SOCLE V4                                │
    │                                                                 │
    │  ┌─────────────────────────────────────────────────────────┐   │
    │  │                    MOP (inchangé)                        │   │
    │  │  - Orchestration Workers                                 │   │
    │  │  - Lifecycle management                                  │   │
    │  │  - Scheduling doWork()                                   │   │
    │  └─────────────────────────────────────────────────────────┘   │
    │                              │                                  │
    │  ┌───────────┬───────────┬───┴───────┬─────────────┐           │
    │  │           │           │           │             │           │
    │  ▼           ▼           ▼           ▼             ▼           │
    │ ┌─────┐  ┌───────┐  ┌────────┐  ┌─────────┐  ┌──────────┐     │
    │ │KvBus│  │Shared │  │Supervi-│  │ HTTP    │  │ Workers  │     │
    │ │     │  │Data   │  │sor     │  │ Worker  │  │ métier   │     │
    │ └─────┘  └───────┘  └────────┘  └─────────┘  └──────────┘     │
    │                                                                 │
    │  ════════════════════ NOUVEAUTÉS V4 ════════════════════════   │
    │                                                                 │
    │  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐     │
    │  │ H2 TechDB   │  │ Log4j2 +    │  │ Clients centraux    │     │
    │  │ (embarqué)  │  │ LogForwarder│  │ - SocleAuthClient   │     │
    │  │             │  │             │  │ - WorkerRegistry    │     │
    │  └─────────────┘  └─────────────┘  └─────────────────────┘     │
    │                                                                 │
    │  ┌─────────────┐  ┌─────────────────────────────────────┐      │
    │  │ Status      │  │ Pipeline V2                          │      │
    │  │ Dashboard   │  │ (Queue/Claim/Ack, DLQ, at-least-once)│      │
    │  │ (port 9374) │  │                                      │      │
    │  └─────────────┘  └─────────────────────────────────────┘      │
    │                                                                 │
    └─────────────────────────────────────────────────────────────────┘
    

    2. Architecture en couches

    ┌─────────────────────────────────────────────────────────────────┐
    │                    COUCHE APPLICATION                           │
    │  Workers métier + Contrôleurs REST                              │
    ├─────────────────────────────────────────────────────────────────┤
    │                    COUCHE FRAMEWORK (SOCLE)                     │
    │  MOP + Core Components + Nouveautés V4                          │
    ├─────────────────────────────────────────────────────────────────┤
    │                    COUCHE INFRASTRUCTURE                        │
    │  Tomcat (HTTP), H2 (TechDB), Redis (KV), Kafka/NATS (Msg)      │
    └─────────────────────────────────────────────────────────────────┘
    

    3. Structure des packages

    eu.lmvi.socle/
    │
    │  ══════ COMPOSANTS V3 (conservés) ══════
    ├── mop/
    │   └── MainOrchestratorProcess.java    # Orchestrateur central
    ├── worker/
    │   └── Worker.java                     # Interface de base
    ├── config/
    │   └── SocleConfiguration.java         # Configuration centralisée
    ├── kv/
    │   ├── KvBus.java                      # Abstraction KV
    │   ├── KvImplementation.java           # Interface implémentation
    │   ├── InMemoryKvImplementation.java   # Implémentation mémoire
    │   └── RedisKvImplementation.java      # Implémentation Redis
    ├── shared/
    │   └── SharedDataRegistry.java         # Registre données partagées
    ├── supervisor/
    │   └── Supervisor.java                 # Monitoring heartbeats
    ├── http/
    │   ├── HttpWorker.java                 # Worker HTTP
    │   ├── TomcatManager.java              # Gestion Tomcat
    │   └── GracefulShutdownFilter.java     # Filtre drain
    ├── admin/
    │   └── AdminRestApi.java               # API REST admin
    ├── metrics/
    │   └── SocleMetrics.java               # Métriques
    ├── pipeline/
    │   └── PipelineEngine.java             # Traitement asynchrone
    ├── resilience/
    │   ├── CircuitBreaker.java             # Circuit Breaker
    │   └── RetryExecutor.java              # Retry avec backoff
    ├── scheduler/
    │   ├── WorkerScheduler.java            # Scheduler
    │   └── CronExpression.java             # Parser cron
    ├── security/
    │   ├── AdminAuthFilter.java            # Auth admin
    │   └── RateLimitFilter.java            # Rate limiting
    │
    │  ══════ NOUVEAUX COMPOSANTS V4 ══════
    ├── techdb/
    │   ├── TechDbManager.java              # Gestionnaire H2
    │   ├── TechDbConfig.java               # Configuration Spring
    │   └── TechDbRepository.java           # Repository
    ├── logging/
    │   ├── SocleLogForwarderAppender.java  # Appender Log4j2
    │   ├── LogTransport.java               # Interface transport
    │   ├── HttpLogTransport.java           # Transport HTTP
    │   ├── NatsLogTransport.java           # Transport NATS
    │   └── H2FallbackStorage.java          # Fallback H2
    └── client/
        ├── auth/
        │   ├── SocleAuthClient.java        # Interface auth
        │   ├── AuthTokenManager.java       # Gestion tokens
        │   └── AuthTokens.java             # DTO tokens
        └── registry/
            ├── WorkerRegistryClient.java   # Client registry
            ├── WorkerRegistration.java     # DTO registration
            └── WorkerHeartbeat.java        # DTO heartbeat
    

    4. Flux de démarrage

    1. Spring Boot initialise
    2. SocleConfiguration charge la config (.env + YAML)
    3. Spring crée les beans @Component
    4. MOP.start() appelé (ApplicationReadyEvent)
       │
       ├── 4.1 [V4] TechDbManager.initialize()
       │         └── Création tables H2
       │         └── Restauration offsets
       │
       ├── 4.2 [V4] SocleAuthClient.login()
       │         └── Obtention JWT
       │
       ├── 4.3 [V4] WorkerRegistryClient.register()
       │         └── Enregistrement au Registry
       │
       ├── 4.4 SharedDataRegistry.initialize()
       ├── 4.5 KvBus.initialize()
       ├── 4.6 Supervisor.start()
       ├── 4.7 Metrics.start()
       │
       ├── 4.8 Workers triés par START_PRIORITY
       │   └── Pour chaque worker:
       │       ├── worker.initialize()
       │       ├── worker.start()
       │       ├── Register avec Supervisor
       │       └── Schedule si isScheduled()
       │
       ├── 4.9 HttpWorker.start() [priorité 1000 = dernier]
       └── 4.10 Boucle principale doWork()
    

    5. Flux de shutdown

    1. Signal SIGTERM (ou /admin/shutdown)
    2. MOP.stop() appelé
    3. État → DRAINING
       │
       ├── 3.1 HttpWorker.startDraining()
       │         └── Refuse nouvelles connexions
       ├── 3.2 HttpWorker.awaitDrain(timeout)
       │         └── Attente requêtes en cours
       │
       ├── 3.3 Workers par STOP_PRIORITY (petit = premier)
       │   ├── HttpWorker.stop() [priorité 0]
       │   └── Autres workers [priorités 1-999]
       │
       ├── 3.4 [V4] WorkerRegistryClient.unregister()
       ├── 3.5 [V4] TechDbManager.close()
       │
       ├── 3.6 Supervisor.shutdown()
       ├── 3.7 SharedData.close()
       └── 3.8 État → STOPPED
    

    6. Dépendances entre composants

    ┌──────────────────────────────────────────────────────────────┐
    │                        MOP (orchestrateur)                    │
    │                              │                                │
    │         ┌────────────────────┼────────────────────┐          │
    │         │                    │                    │          │
    │         ▼                    ▼                    ▼          │
    │   ┌───────────┐       ┌───────────┐       ┌───────────┐     │
    │   │ TechDB    │◄──────│ Supervisor│───────►│ Workers   │     │
    │   │ Manager   │       │           │        │           │     │
    │   └─────┬─────┘       └─────┬─────┘        └─────┬─────┘     │
    │         │                   │                    │           │
    │         │                   ▼                    │           │
    │         │            ┌───────────┐               │           │
    │         │            │ Worker    │◄──────────────┘           │
    │         │            │ Registry  │                           │
    │         │            │ Client    │                           │
    │         │            └─────┬─────┘                           │
    │         │                  │                                 │
    │         ▼                  ▼                                 │
    │   ┌───────────┐     ┌───────────┐                           │
    │   │ H2        │     │ SocleAuth │                           │
    │   │ Database  │     │ Client    │                           │
    │   └───────────┘     └─────┬─────┘                           │
    │                           │                                  │
    │                           ▼                                  │
    │                    ┌─────────────┐                           │
    │                    │ LogForwarder│──► HTTP/NATS (sortant)   │
    │                    │ Appender    │                           │
    │                    └─────────────┘                           │
    └──────────────────────────────────────────────────────────────┘
    

    7. Architecture du Logging V4

    ┌─────────────────────────────────────────────────────────────┐
    │                      Application                             │
    │                                                              │
    │  Logger.info("message")                                      │
    │         │                                                    │
    │         ▼                                                    │
    │  ┌─────────────────────────────────────────────────────┐    │
    │  │              Log4j2 AsyncLoggers                     │    │
    │  │              (LMAX Disruptor)                        │    │
    │  └────────────────────┬────────────────────────────────┘    │
    │                       │                                      │
    │         ┌─────────────┼─────────────┐                       │
    │         ▼             ▼             ▼                       │
    │  ┌───────────┐ ┌───────────┐ ┌─────────────────────┐       │
    │  │  Console  │ │  File     │ │ SocleLogForwarder   │       │
    │  │  Appender │ │  Appender │ │ Appender            │       │
    │  └───────────┘ └───────────┘ └──────────┬──────────┘       │
    │                                         │                   │
    └─────────────────────────────────────────┼───────────────────┘
                                              │
                        ┌─────────────────────┴─────────────────────┐
                        │                                           │
                        ▼                                           ▼
                ┌──────────────┐                           ┌──────────────┐
                │ HTTP Transport│                           │ NATS Transport│
                │ → LogHub     │                           │ → JetStream  │
                └──────┬───────┘                           └──────┬───────┘
                       │                                          │
                       │  (si échec)                              │
                       ▼                                          │
                ┌──────────────┐                                  │
                │ H2 Fallback  │◄─────────────────────────────────┘
                │ Storage      │
                └──────────────┘
    

    8. Patterns de conception

    Pattern Composant Usage
    Orchestrator MOP Orchestre tout le lifecycle
    Observer KvBus pub/sub Communication inter-workers
    Strategy KvImplementation In-memory ou Redis
    Registry SharedDataRegistry État partagé
    Circuit Breaker CircuitBreaker Résilience pannes
    Factory Spring DI Création composants
    Builder Configuration Construction config

    9. Thread Safety

    Mécanisme Usage
    ConcurrentHashMap Maps partagées
    AtomicLong/Boolean Compteurs atomiques
    ReentrantReadWriteLock Opérations complexes
    BlockingQueue Queues thread-safe
    CompletableFuture Opérations async
    ScheduledExecutorService Scheduling
    LMAX Disruptor Ring buffer logging

    10. Points d’extension

    Créer un nouveau Worker

    @Component
    public class MonWorker implements Worker {
        // Implémenter l'interface Worker
    }
    

    Ajouter une implémentation KvBus

    public class MonKvImplementation implements KvImplementation {
        // Implémenter l'interface
    }
    

    Créer un Pipeline Stage

    public class MonStage implements PipelineStage<MonType> {
        // Implémenter le traitement
    }
    

    11. Références

  • Socle V004 – Scheduler

    Socle V004 – Scheduler

    12 – Scheduler

    Version : 4.0.0 Date : 2025-12-09

    1. Introduction

    Le Scheduler permet d’exécuter des Workers selon des expressions cron ou à intervalles réguliers.

    Caractéristiques

    • Support des expressions cron standard
    • Exécution à intervalle fixe
    • Gestion du chevauchement
    • Intégration avec le MOP

    2. Configuration

    2.1 application.yml

    socle:
      scheduler:
        enabled: ${SCHEDULER_ENABLED:true}
        thread-pool-size: ${SCHEDULER_POOL_SIZE:4}
        default-timezone: ${SCHEDULER_TIMEZONE:Europe/Paris}
    

    2.2 Variables d’environnement

    Variable Description Défaut
    SCHEDULER_ENABLED Activer le scheduler true
    SCHEDULER_POOL_SIZE Taille du thread pool 4
    SCHEDULER_TIMEZONE Timezone par défaut Europe/Paris

    3. Types de scheduling

    3.1 Cron

    Expressions cron standard (6 champs) :

    ┌───────────── seconde (0-59)
    │ ┌───────────── minute (0-59)
    │ │ ┌───────────── heure (0-23)
    │ │ │ ┌───────────── jour du mois (1-31)
    │ │ │ │ ┌───────────── mois (1-12)
    │ │ │ │ │ ┌───────────── jour de la semaine (0-6, 0=dimanche)
    │ │ │ │ │ │
    * * * * * *
    

    Exemples :

    • 0 0 6 * * ? : Tous les jours à 6h00
    • 0 */15 * * * ? : Toutes les 15 minutes
    • 0 0 0 1 * ? : Premier jour de chaque mois à minuit
    • 0 30 8 ? * MON-FRI : 8h30 du lundi au vendredi

    3.2 Intervalle fixe

    Exécution périodique simple :

    @Override
    public long getCycleIntervalMs() {
        return 60000;  // Toutes les minutes
    }
    

    4. Worker schedulé

    4.1 Avec expression cron

    @Component
    public class DailyReportWorker implements Worker {
    
        private static final Logger log = LoggerFactory.getLogger(DailyReportWorker.class);
    
        @Override
        public String getName() {
            return "daily-report-worker";
        }
    
        @Override
        public String getSchedule() {
            return "0 0 6 * * ?";  // Tous les jours à 6h
        }
    
        @Override
        public boolean isScheduled() {
            return true;
        }
    
        @Override
        public void doWork() {
            log.info("Generating daily report...");
            generateReport();
        }
    
        private void generateReport() {
            // Génération du rapport
        }
    
        // Autres méthodes Worker...
    }
    

    4.2 Avec intervalle

    @Component
    public class HealthCheckWorker implements Worker {
    
        @Override
        public String getName() {
            return "health-check-worker";
        }
    
        @Override
        public String getSchedule() {
            return null;  // Pas de cron
        }
    
        @Override
        public boolean isScheduled() {
            return false;  // Pas schedulé par cron
        }
    
        @Override
        public long getCycleIntervalMs() {
            return 30000;  // Toutes les 30 secondes
        }
    
        @Override
        public void doWork() {
            checkHealth();
        }
    }
    

    5. Interface Scheduler

    package eu.lmvi.socle.scheduler;
    
    public interface Scheduler {
    
        /**
         * Planifie un job cron
         */
        void scheduleCron(String jobId, String cronExpression, Runnable task);
    
        /**
         * Planifie un job à intervalle fixe
         */
        void scheduleInterval(String jobId, long intervalMs, Runnable task);
    
        /**
         * Planifie un job à intervalle fixe avec délai initial
         */
        void scheduleInterval(String jobId, long initialDelayMs, long intervalMs, Runnable task);
    
        /**
         * Planifie un job one-shot
         */
        void scheduleOnce(String jobId, long delayMs, Runnable task);
    
        /**
         * Annule un job
         */
        void cancel(String jobId);
    
        /**
         * Vérifie si un job est planifié
         */
        boolean isScheduled(String jobId);
    
        /**
         * Liste les jobs planifiés
         */
        List<ScheduledJob> getScheduledJobs();
    
        /**
         * Démarre le scheduler
         */
        void start();
    
        /**
         * Arrête le scheduler
         */
        void stop();
    }
    

    6. Implémentation

    package eu.lmvi.socle.scheduler;
    
    @Component
    public class DefaultScheduler implements Scheduler {
    
        private static final Logger log = LoggerFactory.getLogger(DefaultScheduler.class);
    
        private final ScheduledExecutorService executor;
        private final ConcurrentHashMap<String, ScheduledFuture<?>> jobs = new ConcurrentHashMap<>();
        private final ConcurrentHashMap<String, ScheduledJob> jobInfo = new ConcurrentHashMap<>();
        private final ZoneId timezone;
    
        public DefaultScheduler(SocleConfiguration config) {
            int poolSize = config.getScheduler().getThreadPoolSize();
            this.executor = Executors.newScheduledThreadPool(poolSize,
                r -> new Thread(r, "scheduler-" + System.currentTimeMillis()));
            this.timezone = ZoneId.of(config.getScheduler().getDefaultTimezone());
        }
    
        @Override
        public void scheduleCron(String jobId, String cronExpression, Runnable task) {
            CronExpression cron = CronExpression.parse(cronExpression);
    
            Runnable scheduledTask = () -> {
                log.debug("Executing cron job: {}", jobId);
                try {
                    task.run();
                } catch (Exception e) {
                    log.error("Error executing job {}: {}", jobId, e.getMessage(), e);
                }
                // Replanifier la prochaine exécution
                scheduleNextCronExecution(jobId, cron, task);
            };
    
            scheduleNextCronExecution(jobId, cron, task);
    
            jobInfo.put(jobId, new ScheduledJob(jobId, "cron", cronExpression, null, Instant.now()));
            log.info("Scheduled cron job: {} with expression: {}", jobId, cronExpression);
        }
    
        private void scheduleNextCronExecution(String jobId, CronExpression cron, Runnable task) {
            ZonedDateTime now = ZonedDateTime.now(timezone);
            ZonedDateTime next = cron.next(now);
    
            if (next != null) {
                long delayMs = Duration.between(now, next).toMillis();
    
                ScheduledFuture<?> future = executor.schedule(() -> {
                    task.run();
                    scheduleNextCronExecution(jobId, cron, task);
                }, delayMs, TimeUnit.MILLISECONDS);
    
                jobs.put(jobId, future);
            }
        }
    
        @Override
        public void scheduleInterval(String jobId, long intervalMs, Runnable task) {
            scheduleInterval(jobId, 0, intervalMs, task);
        }
    
        @Override
        public void scheduleInterval(String jobId, long initialDelayMs, long intervalMs, Runnable task) {
            Runnable wrappedTask = () -> {
                log.debug("Executing interval job: {}", jobId);
                try {
                    task.run();
                } catch (Exception e) {
                    log.error("Error executing job {}: {}", jobId, e.getMessage(), e);
                }
            };
    
            ScheduledFuture<?> future = executor.scheduleAtFixedRate(
                wrappedTask, initialDelayMs, intervalMs, TimeUnit.MILLISECONDS);
    
            jobs.put(jobId, future);
            jobInfo.put(jobId, new ScheduledJob(jobId, "interval", null, intervalMs, Instant.now()));
    
            log.info("Scheduled interval job: {} every {}ms", jobId, intervalMs);
        }
    
        @Override
        public void scheduleOnce(String jobId, long delayMs, Runnable task) {
            ScheduledFuture<?> future = executor.schedule(() -> {
                log.debug("Executing one-shot job: {}", jobId);
                try {
                    task.run();
                } finally {
                    jobs.remove(jobId);
                    jobInfo.remove(jobId);
                }
            }, delayMs, TimeUnit.MILLISECONDS);
    
            jobs.put(jobId, future);
            jobInfo.put(jobId, new ScheduledJob(jobId, "once", null, delayMs, Instant.now()));
    
            log.info("Scheduled one-shot job: {} in {}ms", jobId, delayMs);
        }
    
        @Override
        public void cancel(String jobId) {
            ScheduledFuture<?> future = jobs.remove(jobId);
            if (future != null) {
                future.cancel(false);
                jobInfo.remove(jobId);
                log.info("Cancelled job: {}", jobId);
            }
        }
    
        @Override
        public boolean isScheduled(String jobId) {
            return jobs.containsKey(jobId);
        }
    
        @Override
        public List<ScheduledJob> getScheduledJobs() {
            return new ArrayList<>(jobInfo.values());
        }
    
        @Override
        public void start() {
            log.info("Scheduler started");
        }
    
        @Override
        public void stop() {
            log.info("Stopping scheduler...");
            jobs.values().forEach(f -> f.cancel(false));
            jobs.clear();
            executor.shutdown();
            try {
                if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
                    executor.shutdownNow();
                }
            } catch (InterruptedException e) {
                executor.shutdownNow();
                Thread.currentThread().interrupt();
            }
            log.info("Scheduler stopped");
        }
    }
    

    7. Gestion du chevauchement

    7.1 Éviter le chevauchement

    @Component
    public class LongRunningWorker implements Worker {
    
        private final AtomicBoolean running = new AtomicBoolean(false);
    
        @Override
        public void doWork() {
            // Éviter l'exécution concurrente
            if (!running.compareAndSet(false, true)) {
                log.warn("Previous execution still running, skipping");
                return;
            }
    
            try {
                doLongWork();
            } finally {
                running.set(false);
            }
        }
    
        private void doLongWork() {
            // Traitement long
        }
    }
    

    7.2 Avec verrou distribué (multi-instances)

    @Component
    public class DistributedScheduledWorker implements Worker {
    
        @Autowired
        private KvBus kvBus;
    
        @Override
        public void doWork() {
            String lockKey = "lock:job:" + getName();
    
            // Tenter d'acquérir le lock
            if (!kvBus.putIfAbsent(lockKey, "locked", Duration.ofMinutes(10))) {
                log.debug("Job already running on another instance");
                return;
            }
    
            try {
                executeJob();
            } finally {
                kvBus.delete(lockKey);
            }
        }
    }
    

    8. Intégration MOP

    Le MOP intègre automatiquement les workers schedulés :

    // Dans MainOrchestratorProcess
    private void startScheduledWorkers() {
        for (Worker worker : workers) {
            if (worker.isScheduled() && worker.getSchedule() != null) {
                scheduler.scheduleCron(
                    "worker:" + worker.getName(),
                    worker.getSchedule(),
                    () -> {
                        if (worker.isHealthy()) {
                            worker.doWork();
                        }
                    }
                );
            }
        }
    }
    

    9. API Admin

    @RestController
    @RequestMapping("/admin/scheduler")
    public class SchedulerController {
    
        @Autowired
        private Scheduler scheduler;
    
        @GetMapping("/jobs")
        public List<ScheduledJob> listJobs() {
            return scheduler.getScheduledJobs();
        }
    
        @PostMapping("/jobs/{jobId}/cancel")
        public ResponseEntity<Void> cancelJob(@PathVariable String jobId) {
            if (scheduler.isScheduled(jobId)) {
                scheduler.cancel(jobId);
                return ResponseEntity.ok().build();
            }
            return ResponseEntity.notFound().build();
        }
    
        @PostMapping("/jobs/{jobId}/trigger")
        public ResponseEntity<Void> triggerJob(@PathVariable String jobId) {
            // Exécution immédiate one-shot
            scheduler.scheduleOnce(jobId + "-manual-" + System.currentTimeMillis(), 0, () -> {
                // Trouver et exécuter le worker correspondant
            });
            return ResponseEntity.accepted().build();
        }
    }
    

    10. Expressions Cron communes

    Expression Description
    0 0 * * * ? Toutes les heures
    0 */15 * * * ? Toutes les 15 minutes
    0 0 6 * * ? Tous les jours à 6h
    0 0 0 * * ? Tous les jours à minuit
    0 0 0 * * SUN Tous les dimanches à minuit
    0 0 0 1 * ? Premier jour du mois
    0 0 8 ? * MON-FRI 8h en semaine
    0 0 */2 * * ? Toutes les 2 heures

    11. Bonnes pratiques

    DO

    • Utiliser des noms de jobs uniques et descriptifs
    • Gérer le chevauchement pour les jobs longs
    • Utiliser des locks distribués en multi-instances
    • Logger le début et la fin des jobs
    • Monitorer l’exécution des jobs

    DON’T

    • Ne pas planifier des jobs trop fréquents sans nécessité
    • Ne pas ignorer les erreurs dans les jobs
    • Ne pas créer trop de threads
    • Ne pas bloquer indéfiniment dans un job

    12. Références

  • TEST






    Nouveau Test avec image V2



    Nouveau Test avec image V2

    Avec un fichier

    Slot_Car_Street.pdf

    Debut de Code en javascript

    Fichier Jar :

    registry-handler-1.0.0.jar.zip

    javascript
    import { Block } from '../../../../data/protocols/blocks';
    import { ToHtml } from '../../../../domain/use-cases/to-html';
    import { blockToInnerText } from '../../../helpers/block-to-inner-text';
    import hljs from '../../../../config/highlightConfig';
    import prettier from 'prettier';
    
    export class CodeBlockToHtml implements ToHtml {
      private readonly _block: Block;
    
      constructor(block: Block) {
        this._block = block;
      }
    
      async convert(): Promise<string> {
        const language = this._language ? this._language : 'plaintext';  // Fallback au texte brut si pas de langue spécifiée
        const languageClass = `language-${language}`;
    
        // Obtenir le texte du bloc et formater avec Prettier
        let code = blockToInnerText(this._block).replace(/(\s{4}|\t)/g, '  ');
        try {
          code = prettier.format(code, { semi: false, parser: "babel" }); // Assure-toi de choisir le bon parser selon le langage
        } catch (error) {
          console.error("Prettier formatting failed:", error);
        }
    
        // Appliquer la coloration syntaxique avec Highlight.js
        const highlightedCode = hljs.highlight(code, { language }).value;
    
        // Retourner le code HTML formaté
        return Promise.resolve(
            `<pre><code class="${languageClass}">${highlightedCode}</code></pre>`
        );
      }
    
      private get _language(): string {
        return this._block.properties?.language?.toLowerCase().replace(/ /g, '');
      }
    }

    Image

    Notion Image

    Autres images

    Notion Image

    Tableau

    Resolving The Problem

    The following table lists the ports that IBM i Access and related functions use for communication with the IBM i OS System:

    PC Function Server Name Port Non-SSL Port SSL
    • Server Mapper • as-svrmap • 449 • —
    • License Management • as-central • 8470 • 9470
    • Database Access • as-database • 8471 • 9471
    • Data Queues • as-dtaq • 8472 • 9472
    • IFS Access usingAccess/Navigator • as-file • 8473 • 9473
    • Network Printers • as-netprt • 8474 • 9474
    • Remote Command • as-rmtcmd • 8475 • 9475
    • Signon Verification • as-signon • 8476 • 9476
    • Telnet (5250 Emulation) • telnet • 23 • 992
    • Navigator for i (Heritage version) • as-nav • 2004 • 2005
    • New Navigator for i • as-new-nav • 2002 • 2003
    • Digital Certificate Manager • as-admin3-http • 2006 • 2007
    • HTTP Administration • as-admin • 2001 • 2010
    • DDM/DRDA • DDM/DRDA • 446 • 448
    • NetServer • netbios > • 137 • —
    • NetServer • netbios > • 139 • —
    • NetServer (CIFS) • CIFS • 445 • —
    • Service Tools Server • as-sts • 3000 • —

    If any of the above ports are restricted by using a firewall or any other mechanism, IBM i Access or related functions might fail to operate. For assistance with configuring ports or working with a firewall beyond the above information, contact the firewall provider or obtain a consulting agreement.

    Note:

    The following ports are common to most IBM i Access Client products such as ODBC, Telnet, and other specific functions:

    Port 449 is used to look up service by name and return the port number.

    Ports 8470 and 9470(TLS/SSL) are used for host code page translation tables and licensing functions.

    Ports 8475 and 9475(TLS/SSL) are used to check for application administration restrictions.

    Ports 8476 and 9476(TLS/SSL) are used for checking signon verification to authenticate.

    depending on your needs you may only need the above ports and the port(s) for your function/application.For the ports used by the Console, see document N1015344, IBM iSeries Port Assignments with Operations Console.

    For a list of currently listening IPv4 daemons on the IBM i, run the following SQL statement:

    sql
    SELECT LOCAL_PORT, LOCAL_PORT_NAME, BIND_USER, IDLE_TIME
    FROM QSYS2.NETSTAT_INFO
    where LOCAL_ADDRESS = '0.0.0.0';

    Other options are the CL command NETSTAT OPTION(*CNN) or to review the output from WRKSRVTBLE OUTPUT(*PRINT).

    Tache 1
    Tache 2
    Tache 3

    • Puce 1
    • Puce 2

    La maison de la rue adjaçante est encombréeLa maison de la rue adjaçante est encombréeLa maison de la rue adjaçante est encombréeLa maison de la rue adjaçante est encombréeLa maison de la rue adjaçante est encombréeLa maison de la rue adjaçante est encombréeLa maison de la rue adjaçante est encombréeLa maison de la rue adjaçante est encombrée

    Test Unique

    ℹ️

    Avec un bel encadrement et avec plusieurs lignesAvec un bel encadrement et avec plusieurs lignesAvec un bel encadrement et avec plusieurs lignesAvec un bel encadrement et avec plusieurs lignesAvec un bel encadrement et avec plusieurs lignesAvec un bel encadrement et avec plusieurs lignesAvec un bel encadrement et avec plusieurs lignesAvec un bel encadrement et avec plusieurs lignesAvec un bel encadrement et avec plusieurs lignes

    Test de titre 1

    Test de titre 2

    Test de titre 3

    en gras, en italique, sous ligné, barré

    Champs de l’entité « Person »

    Voici les principaux champs disponibles pour l’entité « Person » selon Schema.org :

    1. additionalName: Un nom supplémentaire pour la personne, souvent un deuxième prénom.
    1. address: L’adresse postale de la personne.
    1. affiliation: Une organisation à laquelle la personne est affiliée, comme une entreprise ou une institution.
    1. alumniOf: Une organisation éducative ou une école dont la personne est un ancien élève.
    1. award: Une récompense ou un prix que la personne a reçu.
    1. birthDate: La date de naissance de la personne.
    1. birthPlace: Le lieu de naissance de la personne.
    1. brand: La marque associée à la personne, souvent utilisée pour des célébrités ou des entrepreneurs.
    1. children: Les enfants de la personne.
    1. colleague: Les collègues de la personne.
    1. contactPoint: Un point de contact pour la personne.
    1. deathDate: La date de décès de la personne.
    1. deathPlace: Le lieu de décès de la personne.
    1. duns: Le numéro DUNS de la personne.
    1. email: L’adresse email de la personne.
    1. familyName: Le nom de famille de la personne.
    1. faxNumber: Le numéro de fax de la personne.
    1. follows: Les personnes que cette personne suit (par exemple sur les réseaux sociaux).
    1. funder: Une entité qui finance cette personne.
    1. gender: Le genre de la personne (masculin, féminin, etc.).
    1. givenName: Le prénom de la personne.
    1. globalLocationNumber: Le numéro de localisation global pour la personne.
    1. hasCredential: Les informations d’identification de la personne.
    1. hasOccupation: La profession de la personne.
    1. hasOfferCatalog: Un catalogue d’offres que la personne possède.
    1. hasPOS: Un point de vente que la personne possède.
    1. height: La taille de la personne.
    1. homeLocation: L’emplacement de la résidence principale de la personne.
    1. honorificPrefix: Un préfixe honorifique pour la personne (par exemple, Dr., M., Mme).
    1. honorificSuffix: Un suffixe honorifique pour la personne (par exemple, Jr., Sr.).
    1. interactionStatistic: Les statistiques d’interaction pour la personne.
    1. isicV4: Le code ISIC pour la personne.
    1. jobTitle: Le titre du poste de la personne.
    1. knows: Les connaissances de la
    mermaid
    graph LR
    SymmetricDS_OVH[(« SymmetricDS-OVH »)]
    db-prod[(« PostgreSQL (db-reflet)
    Port: 5432″)]
    pgadmin[(« pgAdmin
    Port: 8888″)]
    redis[(« Redis (cache)
    Port: 6379″)]
    zookeeper[(« Zookeeper
    Port: 2181″)]
    kafka[(« Kafka
    Port: 9092″)]
    kafka_manager[(« Kafka Manager
    Port: 9000″)]
    directus[(« Directus
    Port: 8055″)]
    budibase[(« Budibase
    Port: 10000″)]
    n8n[(« n8n
    Port: 443″)]
    me[(« MONDE \n Extérieur
    Port: 443″)]
    cosmo[(« PostgreSQL (Cosmo)
    Port: 5432″)]
    directus-c[(« Directus
    4 Cosmo
    Port: 8055″)]

    db-prod –>|Cache| redis
    db-prod –>|Publie| kafka
    directus –>|Cache| redis
    pgadmin –>|Administre| db-prod
    budibase –>|CRUD| db-prod
    directus –>|Propose des APIS| db-prod
    kafka –>|Depends on| zookeeper
    kafka_manager –>|Depends on| zookeeper
    kafka_manager –>|Manage Topics| kafka

    SymmetricDS_OVH –>|Envoi| db-prod
    SymmetricDS_OVH –>|Envoi| kafka
    n8n –> |publie| kafka
    kafka –>|consomme| n8n
    db-prod –>|trigger| n8n
    n8n –> |publie| me
    directus-c –> |publie| cosmo
    cosmo –> |publie| directus-c
    n8n –> |GraphQL| directus-c

    python
    # flake8: noqa
    # This file is used for deploying replicate models
    # running: cog predict -i img=@inputs/whole_imgs/10045.png -i version='v1.4' -i scale=2
    # push: cog push r8.im/tencentarc/gfpgan
    # push (backup): cog push r8.im/xinntao/gfpgan
    
    import os
    
    os.system('python setup.py develop')
    os.system('pip install realesrgan')
    
    import cv2
    import shutil
    import tempfile
    import torch
    from basicsr.archs.srvgg_arch import SRVGGNetCompact
    
    from gfpgan import GFPGANer
    
    try:
        from cog import BasePredictor, Input, Path
        from realesrgan.utils import RealESRGANer
    except Exception:
        print('please install cog and realesrgan package')
    
    
    class Predictor(BasePredictor):
    
        def setup(self):
            os.makedirs('output', exist_ok=True)
            # download weights
            if not os.path.exists('gfpgan/weights/realesr-general-x4v3.pth'):
                os.system(
                    'wget https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-general-x4v3.pth -P ./gfpgan/weights'
                )
            if not os.path.exists('gfpgan/weights/GFPGANv1.2.pth'):
                os.system(
                    'wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.2.pth -P ./gfpgan/weights')
            if not os.path.exists('gfpgan/weights/GFPGANv1.3.pth'):
                os.system(
                    'wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth -P ./gfpgan/weights')
            if not os.path.exists('gfpgan/weights/GFPGANv1.4.pth'):
                os.system(
                    'wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth -P ./gfpgan/weights')
            if not os.path.exists('gfpgan/weights/RestoreFormer.pth'):
                os.system(
                    'wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.4/RestoreFormer.pth -P ./gfpgan/weights'
                )
    
            # background enhancer with RealESRGAN
            model = SRVGGNetCompact(num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=32, upscale=4, act_type='prelu')
            model_path = 'gfpgan/weights/realesr-general-x4v3.pth'
            half = True if torch.cuda.is_available() else False
            self.upsampler = RealESRGANer(
                scale=4, model_path=model_path, model=model, tile=0, tile_pad=10, pre_pad=0, half=half)
    
            # Use GFPGAN for face enhancement
            self.face_enhancer = GFPGANer(
                model_path='gfpgan/weights/GFPGANv1.4.pth',
                upscale=2,
                arch='clean',
                channel_multiplier=2,
                bg_upsampler=self.upsampler)
            self.current_version = 'v1.4'
    
        def predict(
                self,
                img: Path = Input(description='Input'),
                version: str = Input(
                    description='GFPGAN version. v1.3: better quality. v1.4: more details and better identity.',
                    choices=['v1.2', 'v1.3', 'v1.4', 'RestoreFormer'],
                    default='v1.4'),
                scale: float = Input(description='Rescaling factor', default=2),
        ) -> Path:
            weight = 0.5
            print(img, version, scale, weight)
            try:
                extension = os.path.splitext(os.path.basename(str(img)))[1]
                img = cv2.imread(str(img), cv2.IMREAD_UNCHANGED)
                if len(img.shape) == 3 and img.shape[2] == 4:
                    img_mode = 'RGBA'
                elif len(img.shape) == 2:
                    img_mode = None
                    img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
                else:
                    img_mode = None
    
                h, w = img.shape[0:2]
                if h < 300:
                    img = cv2.resize(img, (w * 2, h * 2), interpolation=cv2.INTER_LANCZOS4)
    
                if self.current_version != version:
                    if version == 'v1.2':
                        self.face_enhancer = GFPGANer(
                            model_path='gfpgan/weights/GFPGANv1.2.pth',
                            upscale=2,
                            arch='clean',
                            channel_multiplier=2,
                            bg_upsampler=self.upsampler)
                        self.current_version = 'v1.2'
                    elif version == 'v1.3':
                        self.face_enhancer = GFPGANer(
                            model_path='gfpgan/weights/GFPGANv1.3.pth',
                            upscale=2,
                            arch='clean',
                            channel_multiplier=2,
                            bg_upsampler=self.upsampler)
                        self.current_version = 'v1.3'
                    elif version == 'v1.4':
                        self.face_enhancer = GFPGANer(
                            model_path='gfpgan/weights/GFPGANv1.4.pth',
                            upscale=2,
                            arch='clean',
                            channel_multiplier=2,
                            bg_upsampler=self.upsampler)
                        self.current_version = 'v1.4'
                    elif version == 'RestoreFormer':
                        self.face_enhancer = GFPGANer(
                            model_path='gfpgan/weights/RestoreFormer.pth',
                            upscale=2,
                            arch='RestoreFormer',
                            channel_multiplier=2,
                            bg_upsampler=self.upsampler)
    
                try:
                    _, _, output = self.face_enhancer.enhance(
                        img, has_aligned=False, only_center_face=False, paste_back=True, weight=weight)
                except RuntimeError as error:
                    print('Error', error)
    
                try:
                    if scale != 2:
                        interpolation = cv2.INTER_AREA if scale < 2 else cv2.INTER_LANCZOS4
                        h, w = img.shape[0:2]
                        output = cv2.resize(output, (int(w * scale / 2), int(h * scale / 2)), interpolation=interpolation)
                except Exception as error:
                    print('wrong scale input.', error)
    
                if img_mode == 'RGBA':  # RGBA images should be saved in png format
                    extension = 'png'
                # save_path = f'output/out.{extension}'
                # cv2.imwrite(save_path, output)
                out_path = Path(tempfile.mkdtemp()) / f'out.{extension}'
                cv2.imwrite(str(out_path), output)
            except Exception as error:
                print('global exception: ', error)
            finally:
                clean_folder('output')
            return out_path
    
    
    def clean_folder(folder):
        for filename in os.listdir(folder):
            file_path = os.path.join(folder, filename)
            try:
                if os.path.isfile(file_path) or os.path.islink(file_path):
                    os.unlink(file_path)
                elif os.path.isdir(file_path):
                    shutil.rmtree(file_path)
            except Exception as e:
                print(f'Failed to delete {file_path}. Reason: {e}')
    


  • Réinventer l’utilisation de l’ordinateur avec l’interpréteur ouvert

    Réinventer l’utilisation de l’ordinateur avec l’interpréteur ouvert

    L’évolution du traitement du langage naturel a donné naissance à l’interpréteur ouvert, une nouvelle manière d’interagir avec les ordinateurs. Cette interface en langage naturel ouvre des horizons inexplorés pour les tâches informatiques quotidiennes, allant de la création de contenus multimédias à l’analyse complexe de données.

    Les fondements de l’interprétation en langage naturel

    Le Traitement du Langage Naturel (TAL) figure au centre de l’innovation permettant aux ordinateurs de comprendre et d’agir sur les commandes humaines. Cette technologie a connu une évolution remarquable depuis ses origines, marquées par des programmes comme ELIZA, qui simulait une conversation sans réelle compréhension du langage. Aujourd’hui, grâce aux avancées considérables en intelligence artificielle et en apprentissage machine, les systèmes de traitement du langage naturel peuvent non seulement comprendre le sens littéral des mots mais aussi saisir les nuances et le contexte d’une demande.

    Au cœur de ces progrès, la capacité des algorithmes à analyser et interpréter les données linguistiques s’est nettement améliorée. Les techniques comme l’analyse syntaxique et sémantique permettent aux machines de décomposer les phrases en éléments compréhensibles et de les relier à des actions spécifiques. Cette compréhension est renforcée par l’apprentissage profond, qui dote les ordinateurs de la capacité à tirer des enseignements de vastes ensembles de données textuelles, leur permettant d’améliorer continuellement leur performance.

    Les applications actuelles du TAL s’étendent bien au-delà de la compréhension textuelle. Elles englobent la traduction automatique, la reconnaissance vocale, et même l’analyse de sentiments. L’interpréteur ouvert s’inscrit dans cette évolution comme un outil révolutionnaire, offrant aux utilisateurs la possibilité de commander et contrôler leurs ordinateurs en utilisant simplement le langage naturel. Cette innovation s’appuie sur les principes fondamentaux du TAL pour interpréter les commandes des utilisateurs, que ce soit pour créer un document, manipuler des fichiers multimédias, ou naviguer sur le web, rendant la technologie plus accessible et intuitive pour tous.

    Les progrès accomplis depuis ELIZA jusqu’aux systèmes sophistiqués d’aujourd’hui illustrent non seulement l’évolution du TAL, mais aussi la manière dont cette technologie continue de réinventer l’interaction entre l’homme et la machine. Avec l’avènement de l’interpréteur ouvert, l’informatique entre dans une nouvelle ère où les barrières linguistiques entre l’humain et l’ordinateur s’estompent, ouvrant la voie à une utilisation plus naturelle et efficace des technologies de l’information.

    La programmation accessible par le langage naturel

    La programmation a toujours été perçue comme un domaine réservé à une élite versée dans le maniement complexe des langages de programmation. Des premiers langages de haut niveau comme FORTRAN et COBOL, inventés dans les années 1950 et 1960, jusqu’aux paradigmes modernes tels que la programmation orientée objet, l’évolution a été constante. La création des premiers compilateurs a marqué une révolution, permettant la traduction du code source en langage machine, rendant l’exécution des programmes plus efficace. Cependant, malgré ces avancées, l’accès à la programmation est resté limité par la barrière du langage technique.

    L’avènement de l’interpréteur ouvert repousse aujourd’hui ces limites, inaugurant une ère où les commandes en langage naturel permettent d’exécuter des tâches de programmation complexes. Cet outil transforme fondamentalement l’approche de la programmation en la rendant accessible à tous, sans nécessité de maîtriser les syntaxes codifiées des langages traditionnels. L’utilisateur dialogue en langage naturel avec son ordinateur, qui interprète ces commandes et exécute les tâches demandées, allant de la gestion de bases de données à la manipulation de fichiers, en passant par la création et l’édition de contenus multimédias.

    Cette transformation marque un tournant dans la programmation du futur. L’interpréteur ouvert démocratise l’accès à la programmation, permettant à un public bien plus large de participer à la création et à l’innovation technologique. Il encourage également une nouvelle manière de penser la résolution de problèmes informatiques, où la compréhension et l’expression en langage naturel priment sur la connaissance de syntaxes complexes.

    L’impact de cette innovation se mesure également dans l’enseignement et l’apprentissage de la programmation. En simplifiant l’accès au codage, l’interpréteur ouvert constitue un outil pédagogique précieux, ouvrant les portes de l’informatique à des publics jusqu’ici éloignés de ce domaine. Cette accessibilité accrue pourrait même remodeler les professions futures, où la capacité à interagir avec les machines en langage naturel deviendra aussi fondamentale que l’est aujourd’hui la maîtrise de l’outil informatique.

    Ainsi, en offrant une interface intuitive de programmation en langage naturel, l’interpréteur ouvert ne se contente pas de simplifier l’accès à la programmation ; il redéfinit les contours mêmes de ce que signifie « programmer ». Cette mutation promet de révolutionner la manière dont nous interagissons avec les technologies, facilitant une intégration encore plus profonde et personnalisée de l’informatique dans notre quotidien.

    Interprétation avancée pour l’analyse de données

    Après avoir exploré la révolution de la programmation accessible via le langage naturel, où l’interpréteur ouvert simplifie les processus de codage, nous plongeons maintenant dans le monde de l’analyse de données. La capacité à interpréter, nettoyer, analyser et visualiser des données complexe est cruciale dans le paysage actuel de l’information. Historiquement, cette discipline exigeait une compréhension approfondie des statistiques et une maîtrise de logiciels spécialisés.

    L’extraction de données, ou data mining, a évolué depuis ses débuts, s’appuyant d’abord sur des statistiques simples pour évoluer vers des modèles prédictifs et des algorithmes complexes d’apprentissage automatique. La visualisation des données, quant à elle, a transformé la manière dont nous interprétons les ensembles de données, permettant de déceler des tendances et des anomalies auparavant invisibles.

    L’introduction de l’interpréteur ouvert dans ce paysage change radicalement la donne. En utilisant des commandes en langage naturel, même sans expertise en statistiques, les utilisateurs peuvent effectuer des analyses prédictives, nettoyer des ensembles de données de grande taille et intégrer différentes sources de données pour une analyse plus holistique. Ce processus d’intégration, essentiel pour comprendre les tendances à travers divers secteurs ou phénomènes, devient ainsi plus accessible.

    La visualisation des données, un autre aspect crucial de l’analyse, bénéficie également de cette avancée. En commandant à l’interpréteur de générer des graphiques ou des cartes à partir d’un ensemble de données spécifique, les utilisateurs peuvent créer des visualisations complexes sans connaissances préalables en bibliothèques de visualisation comme Matplotlib ou D3.js.

    Par exemple, un utilisateur souhaitant comprendre la répartition géographique de ses clients pourrait simplement demander à l’interpréteur de créer une carte de chaleur à partir de ses données de vente. Ce qui aurait autrefois exigé des heures de travail manuel – nettoyage des données, apprentissage d’une bibliothèque de visualisation, écriture du code – se résume maintenant à une simple instruction en langage naturel.

    L’impact de l’interpréteur ouvert sur l’analyse de données est donc profond, démocratisant l’accès à des insights complexes et facilitant une prise de décision basée sur des données pour un éventail beaucoup plus large d’individus et d’organisations. Il marque une étape significative vers la réinvention de l’utilisation de l’ordinateur, où la complexité technique cède la place à l’intuitivité et à l’accessibilité.

    Conclusions

    L’interpréteur ouvert transforme radicalement notre approche de l’utilisation de l’ordinateur. À travers une simple conversation, il réduit la barrière technique et démocratise l’accès à des opérations complexes pour tout utilisateur.

  • Neque porro quisquam est qui

    Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?

    Nullam ac erat. Donec a ipsum. Nullam tristique leo id lacus. Sed sed pede non arcu ornare interdum. Maecenas justo elit, rutrum in, adipiscing id, laoreet in, libero. Fusce quam. Donec a urna a enim gravida dictum. Nunc elementum, mauris sit amet imperdiet iaculis, dolor metus consectetuer mi, et tempor elit enim at massa. Duis ac nisl. Nullam pulvinar. Curabitur quam sapien, lobortis eu, luctus vitae, volutpat ac, metus. Nam elementum. Fusce fringilla. Praesent pulvinar turpis vitae justo cursus pulvinar. In hac habitasse platea dictumst. Nunc fermentum tellus interdum libero. Phasellus eros. Vestibulum euismod nunc at eros. Pellentesque nec ligula non tellus accumsan molestie. Pellentesque felis massa, tincidunt at, pulvinar id, placerat nec, velit.

    Cras rhoncus ipsum ac dolor. In hac habitasse platea dictumst. Nulla dapibus ultricies pede. Quisque augue risus, porttitor nec, suscipit ut, pellentesque quis, lacus. Duis adipiscing purus eu metus pharetra porttitor. Aenean sapien nisi, sodales non, facilisis nec, ultricies et, erat. Cras aliquam. Maecenas mi. Sed lacus arcu, malesuada id, ultricies et, ornare non, dolor. Maecenas turpis lacus, vehicula nec, blandit in, laoreet a, nibh. Donec aliquet. In et leo tincidunt tortor rhoncus convallis. Nulla facilisi. Praesent bibendum semper eros. Morbi risus. Nam tellus leo, ullamcorper egestas, venenatis quis, viverra ac, mauris. In hac habitasse platea dictumst. Curabitur at velit vel sem auctor hendrerit. Integer mauris orci, vehicula eu, feugiat ac, hendrerit ut, dolor. Fusce elit nulla, gravida quis, vulputate eu, rutrum vel, lectus. Integer cursus luctus nisl. Quisque quam. Aliquam lectus urna, porta in, viverra eu, pellentesque a, massa. Etiam eros sapien, porta et, aliquam et, bibendum sit amet, erat. Sed condimentum interdum lacus. In ut ante non felis tincidunt porta. Aenean aliquet ornare sem. Nunc dignissim, erat sit amet vulputate cursus, elit magna facilisis massa, quis hendrerit nunc odio id dui. Proin interdum dictum arcu. Pellentesque erat ante, ultricies ac, porttitor ac, dictum et, purus. Donec enim odio, gravida ut, imperdiet quis, rutrum ut, lacus.

    Etiam pharetra scelerisque diam. Maecenas varius augue vel urna. Vestibulum erat nisl, fringilla vel, mollis et, aliquam non, elit. Nulla malesuada turpis nec velit. Donec vitae sem a metus dictum molestie. Ut dignissim, odio non porttitor convallis, sapien leo viverra lorem, a consequat mauris erat sit amet dui. Nullam rutrum feugiat massa. Nullam lacinia purus vitae massa. Pellentesque vel tortor eget nulla ullamcorper vehicula. Cras egestas euismod magna. Praesent laoreet aliquet nulla. Aliquam arcu. Proin vel neque non ligula sodales sagittis. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed magna. Pellentesque aliquet mollis arcu. Morbi at felis. Suspendisse ligula. Aliquam nisl arcu, vehicula vitae, hendrerit ut, sodales eu, augue. Sed tristique pretium risus. Ut luctus, dui quis commodo luctus, quam nulla ultricies lorem, eu ornare nulla metus at leo. Praesent rhoncus sapien sit amet mauris. Aenean commodo erat eu eros. Morbi tristique, risus sed consequat bibendum, enim augue tincidunt quam, ac semper libero velit vitae eros.