doc #7
This commit is contained in:
1 parent
773e8314c6
commit
92d8a3e351
12 files changed
+385
-19
No files matched your search
@@ -4,6 +4,14 @@
|
||||
|
||||
Within Rental Management, `Lease` owns its contractual lifecycle, dates, rent terms, and lease-party relationships while referencing independent `Apartment` and `Tenant` aggregates by ID. Activation freezes the landlord identity and address, apartment address, tenant identities and addresses, guarantors, and financial terms; later changes are effective-dated immutable `LeaseAmendment` records that preserve both contract versions and may only recalculate future unpaid dues. `RentDue` and `RentPayment` are independent aggregates; a positive payment owns allocations whose sum cannot exceed the amount received, each allocation cannot exceed its due's remaining balance, and one payment may cover multiple dues only within the same lease. Any surplus stays unallocated. `Guarantor` remains internal to `Lease` until it gains an independent lifecycle or must be shared across leases.
|
||||
|
||||
`Apartment` owns stable `PrivateArea` values covering rooms and annexes under the tenant's exclusive use. A lease snapshot freezes those identifiers and descriptions; furniture-inventory lines, property-condition observations, and photo zones all reference them, while the regulatory main-room count is derived rather than stored independently.
|
||||
|
||||
`FurnitureInventory` is also an independent aggregate referenced by `LeaseId`, not a growing collection inside `Lease`. It remains editable with the lease draft, every generated `LeaseDocumentRevision` freezes its own inventory snapshot for the printed contract, and changing the inventory obsoletes that revision; lease finalization then freezes the aggregate while separately retaining any declared handwritten entry corrections.
|
||||
|
||||
`PropertyConditionReport` is an independent aggregate identified by its own ID and uniquely constrained by `(LandlordId, LeaseId, ENTRY|EXIT)`. It owns its observations, photo references, finalization evidence, and append-only supplements; `Lease` does not embed that growing inspection history.
|
||||
|
||||
Creating the `EXIT` report snapshots the effective finalized `ENTRY` report, including accepted supplements, while preserving the stable room, zone, inventory-line, and entry-photo references needed for deterministic comparison. The exit draft then evolves independently; every entry photo requires one explicitly linked exit counterpart or an audited absence reason, while additional unpaired exit photos remain allowed.
|
||||
|
||||
A new `Lease` is always a mutable, permanently deletable `DRAFT`; draft overlap is allowed and creates no dues. Explicit activation reserves the period in the apartment's versioned `ApartmentOccupancy` aggregate, which retains active and archived lease periods and rejects inclusive overlap, then creates one `RentDue` per landlord, lease, and occupied calendar month under a unique database constraint. Making concurrent activations contend on the same occupancy document prevents write skew that a query-only overlap check would allow. Boundary months prorate rent and charges by occupied calendar days. An amendment split combines its before and after portions before performing one monetary rounding step, while settlement and due-date timing remain independent facts on the due.
|
||||
|
||||
Lease activation, amendment reconciliation, payment confirmation and later allocation, reversal, and refund confirmation run in Mongo transactions whenever they change more than one aggregate document. Versioned aggregate documents reject stale concurrent writes; occupancy reservations, contract snapshots, generated or reconciled dues, payments, reversals, and refunds therefore commit or roll back without a single-process lock or manual database compensation. A payment owns at most one allocation per due, preventing two stale copies of one due from being mutated inside the same command.
|
||||
|
||||
@@ -5,3 +5,5 @@
|
||||
Lease endings are represented by immutable, audited facts owned by `Lease`: a standard furnished lease has no real end until a tenant notice or mutually agreed early end is recorded, while a student lease retains its fixed nine-month end. Tenant notices freeze the legal rule and calculated notice period that applied when received; agreed early endings, accepted withdrawals, administrative invalidations, evidence versions, and closure confirmations are appended without erasing history. Operational labels such as notice in progress, awaiting closure, and unauthorized occupancy are derived from those facts and civil dates rather than persisted as additional lease statuses or advanced by a scheduler. User-entered civil dates are validated in the browser-provided IANA time zone and stored without an artificial UTC conversion, while audit timestamps are instants recorded in UTC.
|
||||
|
||||
A known contract end immediately bounds the otherwise open-ended apartment occupancy and rent dues, including a prorated final month and removal of later unallocated dues. When that recalculation makes an existing allocation exceed its due, the due temporarily exposes a required financial correction while the confirmed payment and its allocation remain immutable; only the existing explicit reversal workflow removes the allocation before the correct payment is recorded. Closure once the contract end is reached and the actual key-return date is known is the sole transition to `ARCHIVED`; keys may have been returned earlier, and closure neither clears outstanding debts nor prevents later settlement of existing dues. A successor lease may be finalized without date overlap before the former lease is closed, but any later withdrawal or correction that would create overlap is rejected. Landlord-issued notices, tenant notices for shared leases, security-deposit settlement, exit inspections, and occupancy compensation remain outside this increment.
|
||||
|
||||
Issue #7 extends that model by making the finalized exit `PropertyConditionReport` the authoritative source of the actual key-return date. Lease closure remains an explicit later command, allowed only after the contract end, and reuses that date without asking the landlord to enter the same fact again; finalizing the report never archives the lease automatically.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Normalize property-condition photos before retaining them
|
||||
|
||||
**Status**: accepted
|
||||
|
||||
Inspection-photo uploads are auto-oriented from EXIF, fitted without upscaling inside an orientation-aware 1920 × 1080 frame while preserving aspect ratio, converted by the server to WebP at 75% quality, and stripped of metadata before receiving their stable reference and SHA-256 fingerprint. The normalized image is the immutable evidence retained and shared with the parties; the original upload is deleted after successful conversion, trading original fidelity and device-supplied metadata for bounded local storage and reduced privacy leakage while allowing the landlord to review and retake a photo before finalization. Reliable capture references and audit timestamps remain application-controlled facts rather than EXIF claims.
|
||||
|
||||
The server delegates this normalization to ImageMagick installed in the runtime image and invoked directly without a shell. Its security policy denies every coder and delegate by default, enables only the input formats explicitly accepted by the product and WebP output, restricts paths to application-controlled temporary files, and bounds dimensions, sequence length, memory, mapped memory, disk, threads, and execution time. The application additionally enforces a process timeout, validates the resulting format and dimensions, and publishes the normalized file only after successful verification. This avoids depending on small Java WebP wrappers while keeping the image processor replaceable behind one adapter.
|
||||
|
||||
Accepted uploads are single-image JPEG, PNG, WebP, and HEIC/HEIF files, identified from their decoded content rather than trusted extensions or client-supplied media types. Animated or multi-image inputs and every other format are rejected. HEIC/HEIF support is supplied by the runtime ImageMagick build through its system `libheif` codec; the container build verifies that the four accepted decoders and the WebP encoder are actually available.
|
||||
|
||||
Upload byte counts and source pixel dimensions are operational resource controls, not domain rules. ImageMagick limits remain configurable and their defaults are calibrated with representative and adversarial images under the target container memory and expected concurrency; they are not inferred from codec capabilities. Exceeding a resource limit or failing normalization deletes the temporary source, leaves the report draft unchanged, and returns an actionable retry error. No stable photo reference is created until the normalized result has passed format and dimension verification.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Allow declared handwritten inventory corrections at lease entry
|
||||
|
||||
**Status**: accepted
|
||||
|
||||
When the entry condition report reveals a difference from the furniture inventory printed in the latest lease revision, the parties may correct that inventory by hand and initial the correction before signing. The printed revision number is never changed by hand: it continues to identify the generated base document, while the declared corrections form a separate contractual layer. Lease finalization requires the landlord to answer whether such differences exist, confirm which corrections prefilled from the structured report were actually written and initialed, and attest that every party initialed them; correction text may be adjusted and exceptional inventory corrections added, while changes to any other lease term still require a regenerated revision. The signed scan becomes the contractual evidence.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Reset development data instead of preserving compatibility
|
||||
|
||||
**Status**: accepted
|
||||
|
||||
The application is not in production, so existing development data and contracts are not compatibility constraints. Until an explicit later decision declares production data must be preserved, domain and schema changes replace obsolete Mongo documents, reference data, and API contracts directly; all callers and tests are updated together, and development data is deleted or reseeded instead of adding migrations, compatibility adapters, or transitional read/write paths.
|
||||
@@ -0,0 +1,161 @@
|
||||
# Issue #7 — normalisation WebP côté backend Java
|
||||
|
||||
> **Superseded:** the Java-library recommendation in this note was rejected in favor of ImageMagick. See [Issue #7 — socle pérenne de normalisation des photos](issue-7-maintainable-image-stack.md) and ADR 0017. The capability and limitation findings below remain historical research.
|
||||
|
||||
Recherche arrêtée au 23 juillet 2026. Les conclusions ci-dessous reposent uniquement sur les API, dépôts et métadonnées de publication des projets concernés.
|
||||
|
||||
## Conclusion
|
||||
|
||||
La proposition « JPEG/PNG/WebP, 20 Mo, 50 mégapixels » ne découle d'aucune limite de bibliothèque. Les codecs ne fournissent pas une politique de sécurité prête à l'emploi et ne justifient donc ni 20 Mo ni 50 MP.
|
||||
|
||||
L'option minimale qui couvre l'ADR-0017 est :
|
||||
|
||||
- `net.coobird:thumbnailator:0.4.21` pour l'orientation EXIF JPEG, le calcul de taille et le redimensionnement ;
|
||||
- `com.github.usefulness:webp-imageio:0.11.0` pour l'encodeur WebP lossy, explicitement épinglé car Spring Boot ne le gère pas ;
|
||||
- les lecteurs ImageIO du JDK pour les formats d'entrée.
|
||||
|
||||
Elle permet une implémentation courte, mais avec trois précautions indispensables :
|
||||
|
||||
1. `size(1920, 1080)` de Thumbnailator **agrandit** une petite image. Pour respecter « sans upscaling », il faut calculer `scale = min(1, 1920 / largeur_orientée, 1080 / hauteur_orientée)` ou utiliser `scale(1)` quand l'image orientée tient déjà dans le cadre.
|
||||
2. L'auto-orientation de Thumbnailator est centrée sur l'EXIF d'un JPEG (arbre `javax_imageio_jpeg_image_1.0` ou segment APP1). Elle n'est pas une garantie générale pour l'EXIF embarqué dans un TIFF, PNG ou WebP.
|
||||
3. Ni Thumbnailator ni `webp-imageio` n'impose un plafond de pixels protecteur. Le plafond d'octets existant ne protège pas seul contre une image très compressée demandant beaucoup de mémoire une fois décodée. Le seuil opérationnel doit faire l'objet d'une décision séparée, mesurée avec la mémoire allouée au conteneur, et non être présenté comme une contrainte du codec.
|
||||
|
||||
Pour les seuls uploads photo usuels JPEG/PNG, c'est le meilleur rapport simplicité/couverture. Si le ticket exige une orientation EXIF uniforme pour **tous** les conteneurs acceptés, remplacer la lecture EXIF de Thumbnailator par `metadata-extractor` et appliquer explicitement l'une des huit transformations est plus robuste, au prix de code métier supplémentaire.
|
||||
|
||||
## Ce que fournit le JDK 25 seul
|
||||
|
||||
L'API ImageIO du JDK 25 garantit des lecteurs et écrivains pour **BMP, GIF, JPEG, PNG, TIFF et WBMP**. WebP ne fait pas partie des plug-ins standards. Source : [documentation Java SE 25 de `javax.imageio`](https://docs.oracle.com/en/java/javase/25/docs/api/java.desktop/javax/imageio/package-summary.html).
|
||||
|
||||
Conséquences :
|
||||
|
||||
- entrées réellement décodables sans dépendance : BMP, GIF, JPEG, PNG, TIFF, WBMP ;
|
||||
- sortie WebP : impossible sans plug-in ou outil externe ;
|
||||
- redimensionnement/rotation : possible avec Java2D (`BufferedImage`, `Graphics2D`, `AffineTransform`), donc aucune dépendance n'est techniquement nécessaire pour les pixels ;
|
||||
- orientation EXIF : ImageIO ne l'applique pas automatiquement. Le lecteur JPEG expose notamment les marqueurs APP dans son arbre de métadonnées, mais il faut analyser l'APP1/TIFF et appliquer la transformation soi-même ;
|
||||
- métadonnées : `IIOImage` permet de les transmettre à un écrivain, mais une image réencodée avec des métadonnées `null` ne recopie pas celles de l'original ;
|
||||
- animation/multipage : le plug-in GIF sait écrire des séquences et les lecteurs peuvent exposer plusieurs images, mais la commodité `ImageIO.read(...)` ne lit que l'image d'index 0.
|
||||
|
||||
Le JDK seul échoue donc sur deux exigences du ticket : WebP et auto-orientation EXIF. Réimplémenter un parseur EXIF pour éviter une petite dépendance n'est pas l'option la plus simple ni la moins risquée.
|
||||
|
||||
## Option recommandée : Thumbnailator + usefulness/webp-imageio
|
||||
|
||||
### Capacités réelles
|
||||
|
||||
| Sujet | Comportement vérifié |
|
||||
|---|---|
|
||||
| Formats d'entrée | Ceux des lecteurs ImageIO présents : avec le JDK et `webp-imageio`, BMP, GIF, JPEG, PNG, TIFF, WBMP et WebP. Le type MIME et l'extension ne sont pas nécessaires à la sélection du lecteur. HEIC/HEIF et AVIF ne sont pas décodés par cette combinaison. |
|
||||
| WebP en sortie | Oui, lossy ou lossless. Le plug-in enregistre un `ImageWriter` ImageIO WebP. |
|
||||
| Qualité 75 | Oui. Thumbnailator transmet `0.75f` à `ImageWriteParam.setCompressionQuality`; `WebPWriteParam` multiplie cette valeur par 100 pour libwebp. Il faut conserver le type de compression `Lossy`. |
|
||||
| Orientation EXIF | Oui pour les huit valeurs d'orientation JPEG. Activée par défaut dans Thumbnailator et activable explicitement avec `useExifOrientation(true)`. Le code ajoute la rotation/réflexion avant les autres filtres et échange les dimensions pour les orientations 5 à 8. |
|
||||
| Portée EXIF | JPEG surtout : le parseur lit l'arbre natif JPEG et son repli capture le segment APP1. Le lecteur usefulness retourne toujours `null` pour les métadonnées WebP ; un WebP portant une orientation EXIF ne sera donc pas réorienté par cette combinaison. |
|
||||
| Métadonnées de sortie | Supprimées : Thumbnailator appelle l'écrivain avec `new IIOImage(img, null, null)` et l'écrivain usefulness retourne `null` pour ses métadonnées par défaut/conversion. Le WebP produit ne reprend donc ni EXIF, ni XMP, ni GPS, ni miniature. |
|
||||
| Animation | Thumbnailator lit l'image 0. Son usage ordinaire aplatit donc un GIF multipage sur sa première image. Le lecteur usefulness annonce toujours une seule image et n'expose pas l'animation WebP. Il n'y a pas de rejet automatique uniforme des animations. |
|
||||
| Redimensionnement | Conservation du ratio et filtres de redimensionnement fournis. Attention : `size(1920, 1080)` peut agrandir ; le non-upscaling nécessite le `min(1, ...)` explicite. |
|
||||
| Dépendances natives | `webp-imageio` utilise JNI et embarque libwebp. Binaires présents pour Linux x86, x86_64, ARM/ARMv6/ARMv7/AArch64, PPC64 ; macOS x86_64/AArch64 ; Windows x86/x86_64. Aucun paquet système libwebp n'est à installer sur ces cibles. |
|
||||
| Java/JDK | Thumbnailator 0.4.21 produit un JAR Java ancien et sans dépendance runtime propre. `webp-imageio` annonce Java 11, cible un bytecode ancien et teste actuellement les JDK 17, 21 et 24. Java 25 n'est pas déclaré dans sa matrice ; sa compatibilité doit donc rester couverte par un test d'intégration du projet sous le JDK 25 réel. Aucune intégration Spring spécifique n'est requise. |
|
||||
| Licences | Thumbnailator : MIT. usefulness/webp-imageio : Apache-2.0 ; binaires libwebp sous la licence du projet libwebp. |
|
||||
| Maintenance | Thumbnailator 0.4.21 a été publié le 1er octobre 2025 et son dépôt reste actif en 2026. usefulness/webp-imageio 0.11.0 a été publié le 15 juin 2026 avec libwebp 1.6.0. |
|
||||
| Limites/protections | Aucun plafond de pixels documenté. Le lecteur usefulness met le flux WebP compressé entier dans un `byte[]`, refuse seulement une longueur connue supérieure à `Integer.MAX_VALUE`, puis alloue le raster décodé. Thumbnailator contient un mécanisme optionnel de sous-échantillonnage dépendant de la mémoire libre, mais ce n'est ni une validation d'upload ni une limite contractuelle. |
|
||||
|
||||
Sources primaires :
|
||||
|
||||
- [README, plateformes, usage, licence de usefulness/webp-imageio](https://github.com/usefulness/webp-imageio)
|
||||
- [release 0.11.0 et contraintes glibc](https://github.com/usefulness/webp-imageio/releases/tag/v0.11.0)
|
||||
- [lecteur usefulness : une image, flux en `byte[]`, aucune métadonnée](https://github.com/usefulness/webp-imageio/blob/master/webp-imageio/src/main/kotlin/com/luciad/imageio/webp/internal/WebPReader.kt)
|
||||
- [écrivain usefulness : aucune métadonnée, encodage RGB/RGBA](https://github.com/usefulness/webp-imageio/blob/master/webp-imageio/src/main/kotlin/com/luciad/imageio/webp/internal/WebPWriter.kt)
|
||||
- [`WebPWriteParam` : qualité ImageIO 0..1 vers libwebp 0..100](https://github.com/usefulness/webp-imageio/blob/master/webp-imageio/src/main/kotlin/com/luciad/imageio/webp/WebPWriteParam.kt)
|
||||
- [binaires natifs embarqués](https://github.com/usefulness/webp-imageio/tree/master/webp-imageio/src/main/resources/native)
|
||||
- [matrice de CI usefulness](https://github.com/usefulness/webp-imageio/blob/master/.github/workflows/default.yml)
|
||||
- [source de l'orientation EXIF Thumbnailator](https://github.com/coobird/thumbnailator/blob/master/src/main/java/net/coobird/thumbnailator/util/exif/ExifUtils.java)
|
||||
- [transformations des huit orientations](https://github.com/coobird/thumbnailator/blob/master/src/main/java/net/coobird/thumbnailator/util/exif/ExifFilterUtils.java)
|
||||
- [ordre orientation puis lecture/redimensionnement](https://github.com/coobird/thumbnailator/blob/master/src/main/java/net/coobird/thumbnailator/tasks/io/InputStreamImageSource.java)
|
||||
- [sortie Thumbnailator sans métadonnées et transmission de la qualité](https://github.com/coobird/thumbnailator/blob/master/src/main/java/net/coobird/thumbnailator/tasks/io/OutputStreamImageSink.java)
|
||||
- [publication et licence Thumbnailator](https://github.com/coobird/thumbnailator)
|
||||
|
||||
### Forme minimale de l'implémentation
|
||||
|
||||
Le traitement doit rester explicite sur les étapes suivantes :
|
||||
|
||||
1. ouvrir un `ImageInputStream` et sélectionner le lecteur d'après le contenu ;
|
||||
2. lire largeur, hauteur, nombre d'images quand le lecteur sait le fournir, et l'orientation EXIF avant le raster complet ;
|
||||
3. déterminer les dimensions après orientation ;
|
||||
4. calculer `min(1, 1920 / largeur, 1080 / hauteur)` afin de ne jamais agrandir ;
|
||||
5. lire, orienter et redimensionner ;
|
||||
6. écrire avec le writer WebP en `Lossy`, qualité `0.75f`, et `IIOImage(..., null, null)` ;
|
||||
7. rouvrir le résultat, vérifier dimensions/format, puis seulement attribuer la référence stable et supprimer l'original comme prévu par l'ADR.
|
||||
|
||||
Thumbnailator raccourcit les étapes 3 à 6 ; il ne dispense pas de la validation de la source ni de la décision sur les animations et les ressources.
|
||||
|
||||
## Alternative : Java2D + usefulness/webp-imageio + metadata-extractor
|
||||
|
||||
`com.drewnoakes:metadata-extractor` lit l'EXIF, notamment l'orientation, dans JPEG, TIFF, WebP, PNG et plusieurs autres conteneurs. Il ne décode pas les pixels, ne redimensionne pas et n'écrit pas WebP. Sa version actuelle cible Java 8, n'emploie pas de code natif et est sous Apache-2.0.
|
||||
|
||||
Cette variante conserve les mêmes entrées pixel réellement décodables que JDK + usefulness, mais rend la lecture de l'orientation indépendante du lecteur ImageIO. Il faut ensuite écrire explicitement les huit transformations `AffineTransform`, le calcul de taille et le redimensionnement Java2D. L'écriture et les contraintes natives restent celles de usefulness/webp-imageio.
|
||||
|
||||
Avantage : comportement EXIF plus large et contrôlable, erreurs de métadonnées observables. Inconvénient : plus de code sensible qu'avec Thumbnailator. À choisir uniquement si l'exigence porte bien sur l'EXIF de plusieurs conteneurs, et pas seulement sur les JPEG de téléphone.
|
||||
|
||||
Sources primaires : [formats et usage de metadata-extractor](https://github.com/drewnoakes/metadata-extractor), [POM, Java 8 et licence](https://github.com/drewnoakes/metadata-extractor/blob/master/pom.xml), [constante `TAG_ORIENTATION` de l'IFD0](https://github.com/drewnoakes/metadata-extractor/blob/master/Source/com/drew/metadata/exif/ExifIFD0Directory.java).
|
||||
|
||||
## Alternative : Scrimage WebP
|
||||
|
||||
`com.sksamuel.scrimage:scrimage-webp:4.6.6` offre une API plus intégrée : lecture ImageIO, metadata-extractor, auto-orientation activée par défaut, `bound(...)` pour réduire sans agrandir et `new WebpWriter().withQ(75)`.
|
||||
|
||||
Elle n'est pas la solution minimale pour ce backend :
|
||||
|
||||
- l'artefact WebP entraîne `scrimage-core`, TwelveMonkeys JPEG, metadata-extractor, commons-io, pngj, commons-lang3 et SLF4J ;
|
||||
- l'écriture passe par un PNG temporaire puis exécute `cwebp` dans un processus ;
|
||||
- la bibliothèque extrait des exécutables embarqués dans un fichier temporaire et doit pouvoir les exécuter ;
|
||||
- les binaires embarqués couvrent Linux x86_64/AArch64, macOS x86_64/ARM64 et Windows x64, moins de cibles que usefulness ;
|
||||
- les ressources du module contiennent libwebp 1.4.0, alors que usefulness 0.11.0 embarque 1.6.0 ;
|
||||
- le timeout de processus est de cinq minutes, mais aucune limite de pixels ou d'octets n'est imposée ;
|
||||
- le projet cible Java 11 et est activement publié en 2026 sous Apache-2.0.
|
||||
|
||||
Scrimage sait aussi traiter les animations via des API dédiées, fonctionnalité inutile au ticket tant qu'aucune politique d'animation n'est décidée.
|
||||
|
||||
Sources primaires : [dépôt et release actuelle Scrimage](https://github.com/sksamuel/scrimage), [dépendances de `scrimage-core`](https://github.com/sksamuel/scrimage/blob/master/scrimage-core/build.gradle.kts), [écrivain WebP et qualité](https://github.com/sksamuel/scrimage/blob/master/scrimage-webp/src/main/java/com/sksamuel/scrimage/webp/WebpWriter.java), [exécution de `cwebp`](https://github.com/sksamuel/scrimage/blob/master/scrimage-webp/src/main/java/com/sksamuel/scrimage/webp/CWebpHandler.java), [plateformes et extraction des binaires](https://github.com/sksamuel/scrimage/blob/master/scrimage-webp/src/main/java/com/sksamuel/scrimage/webp/WebpHandler.java), [auto-orientation par défaut](https://github.com/sksamuel/scrimage/blob/master/scrimage-core/src/main/java/com/sksamuel/scrimage/nio/ImmutableImageLoader.java).
|
||||
|
||||
## TwelveMonkeys ImageIO
|
||||
|
||||
TwelveMonkeys 3.14.x est activement maintenu, Java 8+, pur Java et BSD-3-Clause. Son plug-in WebP sait **lire**, y compris les trames d'animation, et analyser ICC/EXIF/XMP ; il ne sait pas écrire WebP. Son tableau de formats indique explicitement `R = oui`, `W = non`.
|
||||
|
||||
Il peut améliorer la validation d'un WebP entrant ou apporter un lecteur JPEG plus tolérant (notamment aux variantes rencontrées dans la nature), mais ne remplace pas usefulness pour la sortie. L'associer à usefulness installe deux lecteurs WebP concurrents, ajoute plusieurs JARs et ne réalise toujours pas automatiquement l'orientation. Ce n'est donc pas justifié pour la première implémentation de l'issue #7.
|
||||
|
||||
Sources primaires : [table des formats TwelveMonkeys](https://github.com/haraldk/TwelveMonkeys#file-formats-supported), [source du lecteur WebP et des animations/métadonnées](https://github.com/haraldk/TwelveMonkeys/blob/master/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java), [licence BSD-3-Clause](https://github.com/haraldk/TwelveMonkeys/blob/master/LICENSE.txt), [versions Maven Central](https://repo1.maven.org/maven2/com/twelvemonkeys/imageio/imageio-webp/).
|
||||
|
||||
## Décisions qui restent au produit, pas à la bibliothèque
|
||||
|
||||
### Formats acceptés
|
||||
|
||||
La combinaison recommandée **peut** décoder BMP, GIF, JPEG, PNG, TIFF, WBMP et WebP. Cela ne signifie pas que l'API doit tous les accepter. Pour un parcours « photo de constat », JPEG et PNG couvrent le cas minimal ; accepter WebP entrant est simple mais son orientation EXIF n'est pas couverte par Thumbnailator. HEIC/HEIF exige un autre décodeur et n'est fourni par aucune option minimale ci-dessus.
|
||||
|
||||
La validation doit utiliser le lecteur capable de décoder le flux, pas seulement l'extension ou le `Content-Type` déclaré.
|
||||
|
||||
### Animations et fichiers multipages
|
||||
|
||||
Les bibliothèques ne convergent pas vers une politique : GIF/TIFF peuvent contenir plusieurs images, usefulness ne représente pas l'animation WebP, Thumbnailator choisit l'image 0, TwelveMonkeys expose les trames. Il faut décider explicitement entre rejet et aplatissement sur la première image ; aucun des deux choix ne découle d'une limite technique.
|
||||
|
||||
### Octets et pixels
|
||||
|
||||
Aucun seuil « 20 Mo » ou « 50 MP » n'est fourni ou recommandé par ces bibliothèques. Les faits techniques sont seulement :
|
||||
|
||||
- le multipart Spring possède déjà une limite globale configurable ;
|
||||
- les pixels décodés consomment typiquement au moins `largeur × hauteur × 4` octets, auxquels s'ajoutent les buffers du lecteur, de l'image orientée, du redimensionnement et de l'encodeur ;
|
||||
- usefulness charge en plus le WebP compressé complet en mémoire ;
|
||||
- une limite d'octets compressés ne borne pas la mémoire du raster ;
|
||||
- les dimensions peuvent être interrogées avant le décodage complet avec ImageIO pour prendre une décision.
|
||||
|
||||
La prochaine décision devrait donc partir du budget mémoire et du parallélisme réels du conteneur, avec des images adversariales et des photos de téléphones représentatives. En l'absence de cette mesure, conserver provisoirement la limite multipart existante et ne pas inventer un plafond « métier » présenté comme une capacité de la bibliothèque ; en revanche, le décodage d'uploads non fiables devra recevoir une protection de ressources avant exposition publique.
|
||||
|
||||
## Recommandation finale pour l'issue #7
|
||||
|
||||
Adopter Thumbnailator 0.4.21 + usefulness/webp-imageio 0.11.0, restreindre initialement l'entrée aux formats réellement requis par le parcours, et tester sous JDK 25 :
|
||||
|
||||
- les huit orientations EXIF JPEG ;
|
||||
- paysage/portrait au-dessus et au-dessous de 1920 × 1080, en vérifiant l'absence d'agrandissement ;
|
||||
- sortie WebP lossy à `0.75f` ;
|
||||
- absence d'EXIF/XMP/GPS dans le résultat ;
|
||||
- comportement choisi pour GIF/TIFF/WebP animés ;
|
||||
- échec propre d'un flux tronqué ou non-image ;
|
||||
- disponibilité du binaire JNI dans l'image Docker de production.
|
||||
|
||||
Ne fixer aucun nouveau seuil octets/pixels dans cette décision de bibliothèque. Le documenter séparément une fois le budget mémoire et la concurrence connus.
|
||||
@@ -0,0 +1,114 @@
|
||||
# Issue #7 — socle pérenne de normalisation des photos
|
||||
|
||||
Recherche arrêtée au 23 juillet 2026. Les affirmations techniques ci-dessous reposent sur la documentation et les dépôts officiels des projets.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Pour éviter de dépendre de petits wrappers Java et de binaires natifs embarqués dans des JAR, le choix le plus durable et le plus simple est **ImageMagick 7 en ligne de commande**, installé dans l'image Docker et invoqué par Java avec `ProcessBuilder`.
|
||||
|
||||
Après une lecture contrôlée du format, du nombre d'images et des dimensions orientées, une seule commande de conversion couvre le pipeline décidé pour l'issue #7. L'adaptateur choisit `1920x1080>` pour un paysage ou un carré et `1080x1920>` pour un portrait :
|
||||
|
||||
```text
|
||||
magick <entrée>[0] -auto-orient -resize <cadre-orienté> -strip -orient Undefined -quality 75 -define webp:lossless=false webp:<sortie>
|
||||
```
|
||||
|
||||
Dans `ProcessBuilder`, chaque élément est un argument distinct : il ne faut pas passer cette chaîne à un shell. Les chemins d'entrée et de sortie doivent être des chemins temporaires produits par l'application, pas des noms fournis directement par l'utilisateur.
|
||||
|
||||
Cette commande :
|
||||
|
||||
- applique l'une des orientations EXIF avant le redimensionnement ;
|
||||
- conserve le ratio dans une boîte de 1920 × 1080 en paysage ou 1080 × 1920 en portrait et, grâce au suffixe `>`, ne redimensionne que les images plus grandes ;
|
||||
- supprime profils, commentaires, EXIF, XMP et chunks de métadonnées connus, puis efface explicitement toute orientation résiduelle ;
|
||||
- écrit un WebP avec pertes de qualité 75 ;
|
||||
- sélectionne la première image avec `[0]`. Accepter, rejeter ou aplatir les formats animés reste une décision produit, pas une conséquence d'ImageMagick.
|
||||
|
||||
Sources : [auto-orientation, qualité, redimensionnement et suppression des métadonnées](https://imagemagick.org/command-line-options/), [géométrie `WxH>` et conservation du ratio](https://imagemagick.org/command-line-processing/), [options de l'encodeur WebP](https://imagemagick.org/webp/).
|
||||
|
||||
## Pourquoi ce choix répond au risque de maintenance
|
||||
|
||||
ImageMagick n'est pas une petite bibliothèque Java :
|
||||
|
||||
- le projet est maintenu par **ImageMagick Studio LLC** ;
|
||||
- son dépôt compte plus de 24 000 commits et 277 publications ;
|
||||
- la version 7.1.2-27 a été publiée le 5 juillet 2026 ;
|
||||
- le projet publie des versions Linux, macOS et Windows et possède un canal d'avis de sécurité actif ;
|
||||
- le support WebP s'appuie sur la bibliothèque `libwebp` du système, au lieu d'enfermer une copie dans le JAR de l'application.
|
||||
|
||||
Sources : [organisation ImageMagick Studio LLC](https://imagemagick.org/support/), [dépôt et historique des versions](https://github.com/ImageMagick/ImageMagick), [téléchargements officiels](https://imagemagick.org/download/), [avis de sécurité officiels](https://github.com/ImageMagick/ImageMagick/security/advisories).
|
||||
|
||||
Aucun projet ne garantit qu'il sera maintenu éternellement. Ici, le risque est toutefois limité par deux propriétés : le projet est ancien, très diffusé et activement publié ; l'intégration applicative repose sur un contrat de processus minuscule, sans types ni API Java propres à ImageMagick. Un éventuel remplacement ne toucherait que l'appel de normalisation et l'image Docker.
|
||||
|
||||
## Sécurité des uploads non fiables
|
||||
|
||||
La contrepartie d'ImageMagick est sa grande surface fonctionnelle. **L'installation par défaut, ouverte, ne doit pas traiter directement les uploads.** Le projet fournit précisément une politique pour réduire cette surface :
|
||||
|
||||
- refuser tous les modules puis autoriser seulement les formats d'entrée retenus et l'écriture WebP ;
|
||||
- interdire tous les délégués externes et filtres ;
|
||||
- interdire les lectures indirectes (`@...`), les flux standard et les chemins sensibles ;
|
||||
- limiter largeur, hauteur et longueur des séquences ;
|
||||
- borner mémoire, mémoire mappée, disque temporaire, threads et durée ;
|
||||
- utiliser un répertoire temporaire privé ;
|
||||
- vérifier la politique chargée avec `magick identify -list policy`.
|
||||
|
||||
ImageMagick documente des limites distinctes pour la mémoire, le `mmap`, le disque, la durée, la largeur, la hauteur et le nombre d'images. La limite `area` ne rejette pas une image : elle fait basculer son cache de pixels vers le disque. Les valeurs d'exemple de la documentation (`256MiB`, `4KP`, etc.) ne sont **pas** des recommandations pour LMNP Organizer ; elles devront être choisies selon la mémoire réelle du conteneur et la concurrence mesurée. Le timeout Java et la limite mémoire du conteneur restent la dernière barrière.
|
||||
|
||||
Source : [politique de sécurité ImageMagick et exemple complet](https://imagemagick.org/security-policy/), [ressources et variables de limite](https://imagemagick.org/resources/).
|
||||
|
||||
## Comparaison
|
||||
|
||||
| Option | Pipeline complet | Pérennité observable | Sécurité et ressources | Coût d'intégration | Verdict |
|
||||
|---|---|---|---|---|---|
|
||||
| **ImageMagick 7 CLI** | Oui, en une commande | ImageMagick Studio LLC, 277 versions, publication active en juillet 2026 | Politique fine : formats, chemins, délégués, mémoire, disque, dimensions, séquences, temps | Un paquet système, un appel `ProcessBuilder`, un `policy.xml` | **Recommandé** |
|
||||
| **libvips / `vipsthumbnail`** | Oui, en une commande | Projet actif, 102 versions, utilisé notamment par Mastodon, Sharp, Rails et MediaWiki | Faible mémoire, blocage des opérations non fiables et dimension maximale ; garde-fous moins complets qu'ImageMagick au niveau CLI | Un paquet système et un appel `ProcessBuilder` | Excellente alternative si les mesures montrent qu'ImageMagick consomme trop |
|
||||
| **`cwebp` officiel** | Non | Implémentation de référence WebP publiée par Google, API documentée en version 1.6.0 | Option `-low_memory`, mais pas de politique complète de décodage/upload | Nécessite du code ou un second outil pour lire l'orientation et calculer la taille | Ne pas retenir seul |
|
||||
| **Bibliothèques Java ImageIO** | Non | TwelveMonkeys est établi et actif | Décodage dans le processus JVM ; pas de politique équivalente | WebP TwelveMonkeys est en lecture seule ; un autre encodeur reste nécessaire | Ne résout pas le problème de dépendance |
|
||||
|
||||
## Détail : libvips
|
||||
|
||||
`vipsthumbnail` sait réaliser la même transformation une fois le cadre orienté choisi :
|
||||
|
||||
```text
|
||||
vipsthumbnail <entrée> --size <cadre-orienté> --path <sortie>.webp[Q=75,keep=none]
|
||||
```
|
||||
|
||||
La documentation garantit que :
|
||||
|
||||
- la sortie tient dans la boîte demandée ;
|
||||
- le suffixe `>` limite le redimensionnement aux images plus grandes ;
|
||||
- l'orientation des métadonnées est appliquée par défaut ;
|
||||
- `keep=none` ne conserve aucune métadonnée ;
|
||||
- le writer WebP est avec pertes par défaut et `Q` accepte 0 à 100 ;
|
||||
- les formats disponibles dépendent des codecs présents à la compilation, notamment `libwebp` et `libheif`.
|
||||
|
||||
libvips est un projet C majeur, pas un petit wrapper : son dépôt officiel le donne comme moteur de Mastodon, Sharp, imgproxy, Rails et MediaWiki ; il publie encore régulièrement des correctifs issus du fuzzing. Il expose `VIPS_BLOCK_UNTRUSTED` pour bloquer les opérations dépendant de bibliothèques non durcies et `VIPS_MAX_COORD` pour borner une dimension. Il consomme généralement moins de mémoire qu'ImageMagick.
|
||||
|
||||
Il reste en second choix ici parce que la demande priorise la notoriété et la pérennité perçue, et parce qu'ImageMagick offre une politique de sécurité plus complète directement adaptée à un service d'upload. Si un benchmark réel révèle un problème de mémoire ou de débit, remplacer la commande par `vipsthumbnail` est une évolution localisée.
|
||||
|
||||
Sources : [guide officiel `vipsthumbnail`](https://www.libvips.org/API/current/using-vipsthumbnail.html), [`thumbnail` : orientation, boîte et absence d'agrandissement](https://www.libvips.org/API/current/ctor.Image.thumbnail.html), [écriture WebP](https://www.libvips.org/API/current/method.Image.webpsave.html), [formats, utilisateurs et dépendances officielles](https://github.com/libvips/libvips), [blocage des opérations non fiables](https://www.libvips.org/2022/05/28/What%27s-new-in-8.13.html), [versions officielles](https://github.com/libvips/libvips/releases).
|
||||
|
||||
## Détail : `cwebp`
|
||||
|
||||
`cwebp`, livré avec la bibliothèque de référence `libwebp`, est l'encodeur le plus directement gouverné par le projet WebP. Il accepte PNG, JPEG, TIFF, WebP et Y'CbCr brut ; `-q 75` produit un WebP avec pertes ; `-metadata none` est le comportement par défaut ; `-resize` peut redimensionner et préserver le ratio si une dimension vaut zéro.
|
||||
|
||||
Il ne fournit cependant pas l'auto-orientation EXIF et son redimensionnement reçoit une largeur et une hauteur calculées. Pour respecter simultanément l'orientation, la boîte 1920 × 1080 et l'absence d'agrandissement, l'application devrait ajouter un parseur EXIF et du code de transformation, ou lancer un deuxième outil. Ce serait plus de code et deux chaînes de traitement alors qu'ImageMagick ou libvips le font seuls.
|
||||
|
||||
Sources : [documentation officielle de `cwebp`](https://developers.google.com/speed/webp/docs/cwebp), [API officielle libwebp 1.6.0](https://developers.google.com/speed/webp/docs/api), [libwebp comme implémentation de référence](https://developers.google.com/speed/webp).
|
||||
|
||||
## Absence de solution Java établie et suffisante
|
||||
|
||||
Le JDK 25 n'écrit pas WebP. TwelveMonkeys est l'extension ImageIO Java la plus établie examinée, mais son tableau officiel indique explicitement que son plugin WebP sait lire et ne sait pas écrire. Il offre un bon redimensionneur, mais il faudrait toujours un encodeur natif ou un autre projet.
|
||||
|
||||
Une grosse abstraction Java par-dessus un outil natif n'améliore donc pas la pérennité : elle ajoute un mainteneur et une API à suivre sans retirer la dépendance au codec. Appeler directement un exécutable maintenu et versionné dans Docker est plus simple et rend le cycle de correctifs natifs indépendant des dépendances Maven.
|
||||
|
||||
Sources : [formats supportés par TwelveMonkeys](https://github.com/haraldk/TwelveMonkeys#file-formats-supported), [formats garantis par ImageIO dans Java 25](https://docs.oracle.com/en/java/javase/25/docs/api/java.desktop/javax/imageio/package-summary.html).
|
||||
|
||||
## Intégration minimale recommandée
|
||||
|
||||
1. Installer ImageMagick 7 avec le codec WebP dans l'étage d'exécution Docker ; vérifier au build la version et `WEBP` avec `magick -version` et `magick identify -list format`.
|
||||
2. Ajouter un `policy.xml` en refus par défaut et n'autoriser que les codecs décidés. Ne pas recopier des seuils arbitraires : les mesurer puis les fixer.
|
||||
3. Écrire l'upload dans un fichier temporaire contrôlé par l'application.
|
||||
4. Exécuter la commande via les arguments de `ProcessBuilder`, sans shell, avec délai d'expiration et destruction forcée du processus.
|
||||
5. Exiger le code de sortie zéro, rouvrir le résultat, vérifier qu'il est WebP et dans la boîte 1920 × 1080, puis seulement publier la référence canonique et supprimer l'original.
|
||||
6. Couvrir dans l'image Docker réelle les huit orientations EXIF, portrait/paysage, absence d'agrandissement, qualité avec pertes, suppression des métadonnées, flux invalide et arrêt sur dépassement des limites.
|
||||
|
||||
Cette décision remplace la proposition Thumbnailator + usefulness/webp-imageio ; elle ne fixe ni les formats d'entrée du contrat utilisateur, ni les seuils de ressources.
|
||||
Reference in new issue
Block a user