Grimm s’oppose au scan classpath par requête : tout le travail est fait une fois, au démarrage du container CDI, et le résultat est mis en cache. Cette page décrit la séquence exacte du pipeline, le rôle de la BCE Vauban et les invariants de threading.
Séparation grimm-core / grimm-cdi-vauban
grimm-core est du Java pur. Il ne connaît ni CDI, ni JAX-RS Runtime — uniquement les types de la spec (@Path est lu comme annotation, pas comme handler). Il s’utilise hors container, par exemple pour un outillage Maven qui produirait un OpenAPI à la compilation.
grimm-cdi-vauban est la couche d’intégration : il branche grimm-core sur la Build Compatible Extension Vauban, expose la ressource /openapi, et fournit le Supplier<OpenAPI> consommé par les outils en aval.
Pipeline de construction
Six étapes, exécutées une seule fois à l’activation du container :
-
StaticFileReader— chercheMETA-INF/openapi.yaml,.yml, puis.json. Lecture via le classloader courant. RetourneOptional<OpenAPI>. -
ModelReaderInvoker— simp.openapi.model.readerest défini, instancie la classe via réflexion (lookup CDI privilégié, fallback constructeur sans argument) et appellebuildModel(). -
AnnotationScanner— parcourt les classes@Pathdu module CDI courant, lit les annotations MP OpenAPI (@Operation,@Parameter,@Schema, etc.) et construit unOpenAPIpartiel. La sortie est balisée parAnnotationSource(sealed sub-type deModelSource). -
ModelMerger— fusionne les troisModelSource(sealed :StaticFileSource,ReaderSource,AnnotationSource) en un seulOpenAPI. Stratégie : pour les opérations, les annotations gagnent ; pour lesComponents, l’union dédoublonnée parname; pour lesservers, voirConfigApplier. -
FilterInvoker— simp.openapi.filterest défini, charge leOASFilter(lookup CDI ou constructeur sans argument) et le déroule sur l’arbre. Le filtre est invoqué récursivement via un visiteur typéswitchsur les types modèles sealed. -
GrimmModelCache— encapsule leOpenAPIfinal. Lecture lock-free, écriture une seule fois.
BCE Vauban : GrimmExtension
L’orchestration est portée par io.vidocq.grimm.cdi.GrimmExtension, une Build Compatible Extension :
// Pseudo-déclaration — la BCE véritable utilise les hooks @Discovery,
// @Enhancement, @Registration et @Synthesis de CDI 4.1 Lite.
@Discovery
public void declareScanRoots(ScannedTypes types) { /* ... */ }
@Synthesis
public void synthesizeOpenApiBean(SyntheticComponents synth) {
// Synthétise un bean ApplicationScoped pour GrimmModelCache,
// alimenté au @PostConstruct par le pipeline complet.
}
Le bean GrimmModelCache est @ApplicationScoped. Son @PostConstruct exécute le pipeline ; getDocument() se contente de retourner la référence cachée.
Modèle de threading
Aucun verrouillage à chaud
GrimmModelCache stocke le document dans une référence finale, initialisée une fois dans @PostConstruct. Toute lecture ultérieure est lock-free : aucun synchronized, aucun ReentrantLock, aucun volatile au sens « happens-before » critique — la final du record applique la sémantique JMM.
Virtual threads pour les opérations potentiellement bloquantes
L’invocation d’un OASModelReader ou d’un OASFilter applicatif peut, en théorie, déclencher des appels bloquants (lecture de fichier, base de données). Quand le runtime hôte (Cassini + Chappe) tourne sur l’exécuteur virtuel par défaut (Executors.newVirtualThreadPerTaskExecutor()), ces invocations héritent du contexte virtuel — aucun pool plateforme n’est créé par Grimm.
Sérialisation YAML et JSON
grimm-core embarque deux sérialiseurs écrits à la main (pas de SnakeYAML, pas de Jackson) :
-
JsonSerializer— émet JSON 2020-12 brut, sans dépendance. -
YamlSerializer— émet YAML 1.2 compatible OpenAPI 3.1, indentation deux espaces, scalaires bloc pour les chaînes multilignes.
Symétriquement, JsonDeserializer et YamlDeserializer consomment les fichiers statiques.
OpenApiModelMapper et OpenApiValueMapper traduisent entre l’arbre serialisé et le modèle OpenAPI interne.
Endpoint /openapi
OpenApiResource est un bean JAX-RS @ApplicationScoped du module grimm-cdi-vauban. Son constructeur injecte GrimmModelCache. La méthode getOpenApi(format, accept) :
-
Résout le format via
?format=(prioritaire, spec §2.3) puisAccept(spec §2.2, défaut YAML). -
Récupère le document du cache.
-
Sérialise vers YAML ou JSON selon le format choisi.
-
Retourne une
Responseavec leContent-Typecorrespondant.
Aucun verrou n’est pris ; la sérialisation peut elle-même se paralléliser sans risque.