Skip to content

Annotations API Reference

Référence complète de toutes les annotations SpringFlow avec exemples détaillés.

Annotations Principales

@AutoApi

Active la génération automatique d'une API REST complète pour une entité JPA.

Cible: Type (Class) Retention: Runtime Package: io.springflow.annotations

Paramètres

Paramètre Type Défaut Description
path String "" Chemin de base pour les endpoints (sans slash initial)
expose Expose ALL Opérations CRUD exposées
security @Security @Security() Configuration de sécurité
pagination boolean true Active la pagination
sorting boolean true Active le tri
supportSpecification boolean false Active le support JPA Specification (Repository & Endpoint /search)
description String "" Description pour OpenAPI
tags String[] {} Tags OpenAPI pour regroupement

Exemple Basique

@Entity
@Table(name = "products")
@AutoApi(path = "/products")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private BigDecimal price;
}

Génère automatiquement: - GET /api/products - Liste avec pagination - GET /api/products/{id} - Détails - POST /api/products - Création - PUT /api/products/{id} - Mise à jour complète - PATCH /api/products/{id} - Mise à jour partielle - DELETE /api/products/{id} - Suppression

Exemple Complet

@Entity
@AutoApi(
    path = "/products",
    expose = Expose.ALL,
    pagination = true,
    sorting = true,
    description = "Product management API",
    tags = {"Products", "Inventory"},
    security = @Security(
        level = SecurityLevel.AUTHENTICATED
    )
)
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank(message = "Name is required")
    @Size(min = 3, max = 100)
    @Filterable(types = {FilterType.LIKE, FilterType.EQUALS})
    private String name;

    @Min(0)
    @Filterable(types = FilterType.RANGE)
    private BigDecimal price;

    @Filterable(types = {FilterType.EQUALS, FilterType.IN})
    @Enumerated(EnumType.STRING)
    private ProductStatus status;

    @ReadOnly
    private LocalDateTime createdAt;

    @Hidden
    private String internalNotes;
}

Path Configuration

// Chemin simple
@AutoApi(path = "/products")
// → /api/products

// Chemin hiérarchique
@AutoApi(path = "/admin/users")
// → /api/admin/users

// Chemin avec sous-ressource
@AutoApi(path = "/orders/{orderId}/items")
// → /api/orders/{orderId}/items

@Filterable

Active le filtrage dynamique sur un champ d'entité.

Cible: Field Retention: Runtime Package: io.springflow.annotations

Paramètres

Paramètre Type Défaut Description
types FilterType[] EQUALS Types de filtres supportés
paramName String "" Nom personnalisé du paramètre (utilise le nom du champ si vide)
description String "" Description pour OpenAPI
caseSensitive boolean true Sensibilité à la casse (pour EQUALS, LIKE)

Exemples par Type

EQUALS - Correspondance exacte
@Filterable(types = FilterType.EQUALS)
private String status;

Utilisation: GET /api/products?status=ACTIVE

LIKE - Recherche textuelle
@Filterable(types = FilterType.LIKE, caseSensitive = false)
private String name;

Utilisation: GET /api/products?name_like=laptop SQL: WHERE LOWER(name) LIKE LOWER('%laptop%')

RANGE - Intervalle numérique/temporel
@Filterable(types = FilterType.RANGE)
private BigDecimal price;

@Filterable(types = FilterType.RANGE)
private LocalDateTime createdAt;

Utilisation: - GET /api/products?price_gte=100&price_lte=500 - GET /api/products?createdAt_gte=2024-01-01&createdAt_lte=2024-12-31

IN - Multiples valeurs
@Filterable(types = FilterType.IN)
private ProductStatus status;

Utilisation: GET /api/products?status_in=ACTIVE,PENDING,DRAFT

Combinaisons
@Filterable(types = {FilterType.EQUALS, FilterType.LIKE, FilterType.IS_NULL})
private String category;

Utilisation: - ?category=Electronics - Exact - ?category_like=Elect - Partiel - ?category_null=false - Non null

Exemples Avancés

Filtre avec Nom Personnalisé
@Filterable(
    types = {FilterType.EQUALS, FilterType.LIKE},
    paramName = "search",
    description = "Search by product name or SKU"
)
private String name;

Utilisation: ?search=laptop ou ?search_like=lap

Multiple Types avec Case-Insensitive
@Filterable(
    types = {FilterType.EQUALS, FilterType.LIKE},
    caseSensitive = false,
    description = "Filter by customer name (case-insensitive)"
)
private String customerName;

@Hidden

Exclut un champ des DTOs générés (input et output).

Cible: Field Retention: Runtime Package: io.springflow.annotations

Caractéristiques

  • ❌ N'apparaît jamais dans les requêtes POST/PUT
  • ❌ N'apparaît jamais dans les réponses GET
  • ❌ N'apparaît pas dans le schéma OpenAPI
  • ✅ Reste accessible dans le code Java

Cas d'Usage

@Entity
@AutoApi(path = "/users")
public class User {
    @Id
    private Long id;

    private String username;

    @Email
    private String email;

    // Champs sensibles - jamais exposés
    @Hidden
    @Column(nullable = false)
    private String passwordHash;

    @Hidden
    private String apiKey;

    // Champs internes - jamais exposés
    @Hidden
    private String internalNotes;

    @Hidden
    private Integer loginAttempts;
}

Résultat:

// GET /api/users/1
{
  "id": 1,
  "username": "john.doe",
  "email": "john@example.com"
  // passwordHash, apiKey, internalNotes, loginAttempts ne sont PAS inclus
}

Comparaison avec @ReadOnly

Annotation Input DTO Output DTO Use Case
@Hidden ❌ Exclu ❌ Exclu Données sensibles, champs internes
@ReadOnly ❌ Exclu ✅ Inclus Timestamps, IDs, champs calculés
(rien) ✅ Inclus ✅ Inclus Données normales

@ReadOnly

Marque un champ comme lecture seule dans l'API REST.

Cible: Field Retention: Runtime Package: io.springflow.annotations

Caractéristiques

  • ❌ Exclu des requêtes POST/PUT (non modifiable)
  • ✅ Inclus dans les réponses GET (visible)
  • 📄 Documenté comme "read-only" dans OpenAPI

Cas d'Usage

@Entity
@AutoApi(path = "/orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @ReadOnly  // ID auto-généré
    private Long id;

    private String customerName;

    @OneToMany(mappedBy = "order")
    private List<OrderItem> items;

    // Champs calculés
    @ReadOnly
    private BigDecimal totalAmount;  // Calculé à partir des items

    @ReadOnly
    private Integer itemCount;       // Nombre d'items

    // Timestamps
    @ReadOnly
    @Column(nullable = false, updatable = false)
    private LocalDateTime createdAt;

    @ReadOnly
    private LocalDateTime updatedAt;

    // Statut auto-généré
    @ReadOnly
    @Enumerated(EnumType.STRING)
    private OrderStatus status;

    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
        updatedAt = LocalDateTime.now();
        calculateTotals();
    }

    @PreUpdate
    protected void onUpdate() {
        updatedAt = LocalDateTime.now();
        calculateTotals();
    }

    private void calculateTotals() {
        totalAmount = items.stream()
            .map(OrderItem::getSubtotal)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        itemCount = items.size();
    }
}

Comportement:

// POST /api/orders
// Input DTO (totalAmount, itemCount, createdAt IGNORÉS)
{
  "customerName": "John Doe",
  "items": [...]
}

// Réponse (tous les champs visibles)
{
  "id": 123,
  "customerName": "John Doe",
  "totalAmount": 499.99,     // ✅ Calculé automatiquement
  "itemCount": 3,            // ✅ Calculé automatiquement
  "createdAt": "2024-12-26T10:00:00",
  "updatedAt": "2024-12-26T10:00:00",
  "status": "PENDING"
}

ID Fields

ID Fields sont toujours ReadOnly

Les champs annotés avec @Id ou @EmbeddedId sont automatiquement traités comme @ReadOnly, même sans l'annotation explicite.


FilterType Enum

Types de filtres disponibles pour @Filterable.

Liste Complète

Type Paramètre SQL Equivalent Exemple
EQUALS ?field=value WHERE field = ? ?status=ACTIVE
LIKE ?field_like=pattern WHERE field LIKE '%?%' ?name_like=laptop
GREATER_THAN ?field_gt=value WHERE field > ? ?price_gt=100
LESS_THAN ?field_lt=value WHERE field < ? ?price_lt=1000
GREATER_THAN_OR_EQUAL ?field_gte=value WHERE field >= ? ?age_gte=18
LESS_THAN_OR_EQUAL ?field_lte=value WHERE field <= ? ?age_lte=65
RANGE ?field_gte=min&field_lte=max WHERE field BETWEEN ? AND ? ?price_gte=100&price_lte=500
IN ?field_in=v1,v2,v3 WHERE field IN (?, ?, ?) ?status_in=ACTIVE,PENDING
NOT_IN ?field_not_in=v1,v2 WHERE field NOT IN (?, ?) ?status_not_in=DELETED
IS_NULL ?field_null=true/false WHERE field IS [NOT] NULL ?deletedAt_null=true
BETWEEN ?field_between=min,max WHERE field BETWEEN ? AND ? ?age_between=18,65

Exemples par Cas d'Usage

Recherche Textuelle

@Filterable(types = {FilterType.EQUALS, FilterType.LIKE})
private String productName;

Requêtes: - ?productName=Laptop - Exactement "Laptop" - ?productName_like=lap - Contient "lap"

Plages de Prix

@Filterable(types = FilterType.RANGE)
private BigDecimal price;

Requêtes: - ?price_gte=100 - Prix >= 100 - ?price_lte=500 - Prix <= 500 - ?price_gte=100&price_lte=500 - Entre 100 et 500

Filtres de Dates

@Filterable(types = {FilterType.RANGE, FilterType.IS_NULL})
private LocalDateTime publishedAt;

Requêtes: - ?publishedAt_gte=2024-01-01 - Publié après le 1er janvier - ?publishedAt_null=false - Articles publiés (non null) - ?publishedAt_null=true - Brouillons (null)

Statuts Multiples

@Filterable(types = {FilterType.EQUALS, FilterType.IN, FilterType.NOT_IN})
@Enumerated(EnumType.STRING)
private OrderStatus status;

Requêtes: - ?status=ACTIVE - Statut ACTIVE uniquement - ?status_in=ACTIVE,PENDING - ACTIVE ou PENDING - ?status_not_in=DELETED,ARCHIVED - Exclure DELETED et ARCHIVED


Annotations de Sécurité

@Security

Configure l'authentification et l'autorisation pour les endpoints générés.

Cible: Annotation (utilisée dans @AutoApi) Retention: Runtime Package: io.springflow.annotations

Paramètres

Paramètre Type Défaut Description
enabled boolean true Active/désactive la sécurité
level SecurityLevel PUBLIC Niveau de sécurité global
roles String[] {} Rôles requis (pour ROLE_BASED)
authorities String[] {} Authorities requises (pour ROLE_BASED)
readLevel SecurityLevel UNDEFINED Niveau pour GET (override level)
writeLevel SecurityLevel UNDEFINED Niveau pour POST/PUT/DELETE (override level)

Exemples

Public Access (Par Défaut)
@Entity
@AutoApi(
    path = "/products",
    security = @Security(level = SecurityLevel.PUBLIC)
)
public class Product { }

Tous les endpoints sont publics.

Authentification Requise
@Entity
@AutoApi(
    path = "/orders",
    security = @Security(level = SecurityLevel.AUTHENTICATED)
)
public class Order { }

Utilisateur doit être authentifié (JWT valide, session active).

Basé sur les Rôles
@Entity
@AutoApi(
    path = "/users",
    security = @Security(
        level = SecurityLevel.ROLE_BASED,
        roles = {"ADMIN", "USER_MANAGER"}
    )
)
public class User { }

Utilisateur doit avoir le rôle ADMIN OU USER_MANAGER.

Sécurité Granulaire (Read/Write)
@Entity
@AutoApi(
    path = "/reports",
    security = @Security(
        readLevel = SecurityLevel.AUTHENTICATED,     // GET: n'importe quel user
        writeLevel = SecurityLevel.ROLE_BASED,       // POST/PUT/DELETE: admin seulement
        roles = {"ADMIN"}
    )
)
public class Report { }
  • GET endpoints: authentification simple
  • POST/PUT/DELETE endpoints: rôle ADMIN requis
Authorities (Fine-Grained)
@Entity
@AutoApi(
    path = "/documents",
    security = @Security(
        level = SecurityLevel.ROLE_BASED,
        authorities = {"document:read", "document:write", "document:delete"}
    )
)
public class Document { }

SecurityLevel Enum

Valeur Description Use Case
PUBLIC Aucune authentification APIs publiques, données ouvertes
AUTHENTICATED Authentification simple Données utilisateur, profils
ROLE_BASED Rôles/authorities spécifiques Admin, gestion, opérations sensibles
UNDEFINED Hérite du niveau parent Utilisé pour readLevel/writeLevel

Additional Annotations

@SoftDelete

Enables logical (soft) deletion for an entity.

Status: ✅ Implemented Target: Type (Class) Retention: Runtime

Paramètres

Paramètre Type Défaut Description
deletedField String "deleted" Nom du champ boolean de suppression
deletedAtField String "deletedAt" Nom du champ timestamp de suppression

Exemple

@Entity
@AutoApi(path = "/products")
@SoftDelete
public class Product {
    @Id
    private Long id;

    private String name;

    // Champs ajoutés pour soft delete
    private Boolean deleted = false;
    private LocalDateTime deletedAt;
}

Comportement attendu: - DELETE /api/products/1 → Met deleted = true, deletedAt = now() - GET /api/products → Filtre automatiquement WHERE deleted = false - Endpoint de restauration: POST /api/products/1/restore


@Auditable

Active le tracking automatique des modifications (audit trail).

Status: ⚠️ Partial — annotation and metadata extraction are complete; automatic field population is not yet implemented Target: Type (Class) Retention: Runtime

Parameters

Parameter Type Default Description
versioned boolean false Enable optimistic locking via @Version
createdAtField String "createdAt" Field name for creation timestamp
updatedAtField String "updatedAt" Field name for last update timestamp
createdByField String "createdBy" Field name for creator
updatedByField String "updatedBy" Field name for last modifier

Exemple

@Entity
@AutoApi(path = "/documents")
@Auditable(versioned = true)
public class Document {
    @Id
    private Long id;

    private String title;
    private String content;

    // Champs ajoutés automatiquement:
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    private String createdBy;
    private String updatedBy;
    private Long version;  // Pour versioned = true
}

Comportement attendu: - Remplissage automatique de createdAt, createdBy lors de la création - Mise à jour automatique de updatedAt, updatedBy lors des modifications - Intégration avec Spring Security pour récupérer l'utilisateur courant - Optimistic locking avec version si versioned = true


Expose Enum

Contrôle quelles opérations CRUD sont exposées via l'API.

Valeurs Disponibles

ALL (Par Défaut)

Expose toutes les opérations CRUD.

@AutoApi(path = "/products", expose = Expose.ALL)

Endpoints générés: - ✅ GET /api/products - Liste - ✅ GET /api/products/{id} - Détails - ✅ POST /api/products - Création - ✅ PUT /api/products/{id} - Mise à jour complète - ✅ PATCH /api/products/{id} - Mise à jour partielle - ✅ DELETE /api/products/{id} - Suppression

READ_ONLY

Expose uniquement les opérations de lecture.

@AutoApi(path = "/reports", expose = Expose.READ_ONLY)

Endpoints générés: - ✅ GET /api/reports - Liste - ✅ GET /api/reports/{id} - Détails - ❌ POST, PUT, PATCH, DELETE

Use Cases: - Données de reporting - Vues en lecture seule - Données générées automatiquement

CREATE_UPDATE

Expose création et modification, mais pas la suppression.

@AutoApi(path = "/customers", expose = Expose.CREATE_UPDATE)

Endpoints générés: - ✅ GET /api/customers - Liste - ✅ GET /api/customers/{id} - Détails - ✅ POST /api/customers - Création - ✅ PUT /api/customers/{id} - Mise à jour complète - ✅ PATCH /api/customers/{id} - Mise à jour partielle - ❌ DELETE

Use Cases: - Entités qui ne doivent jamais être supprimées physiquement - Combiné avec @SoftDelete pour suppression logique - Données réglementaires (conservation obligatoire)

CUSTOM

Reserved for fine-grained per-operation control.

@AutoApi(path = "/advanced", expose = Expose.CUSTOM)

Status: Not yet implemented


Résumé des Annotations

Available Annotations

Annotation Level Usage Status
@AutoApi Class Enable full API generation ✅ Implemented
@Filterable Field Enable dynamic filtering ✅ Implemented
@Hidden Field Exclude from DTO (input + output) ✅ Implemented
@ReadOnly Field Exclude from input DTO only ✅ Implemented
@Security Annotation Configure endpoint security ✅ Implemented
@SoftDelete Class Logical deletion ✅ Implemented
@Auditable Class Automatic audit trail ⚠️ Partial

Enums

Enum Usage Valeurs Status
FilterType Types de filtres EQUALS, LIKE, RANGE, IN, etc. (12 types) ✅ Implémenté
Expose Opérations exposées ALL, READ_ONLY, CREATE_UPDATE, CUSTOM ✅ Implémenté
SecurityLevel Niveau de sécurité PUBLIC, AUTHENTICATED, ROLE_BASED ✅ Implémenté

Exemples Complets

E-Commerce Product

@Entity
@Table(name = "products")
@AutoApi(
    path = "/products",
    expose = Expose.ALL,
    pagination = true,
    sorting = true,
    description = "Product catalog management",
    tags = {"Products", "Inventory"},
    security = @Security(
        readLevel = SecurityLevel.PUBLIC,
        writeLevel = SecurityLevel.ROLE_BASED,
        roles = {"ADMIN", "INVENTORY_MANAGER"}
    )
)
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @ReadOnly
    private Long id;

    @NotBlank
    @Size(min = 3, max = 200)
    @Filterable(types = {FilterType.LIKE, FilterType.EQUALS})
    private String name;

    @Size(max = 2000)
    private String description;

    @Column(unique = true)
    @Filterable(types = FilterType.EQUALS)
    private String sku;

    @Min(0)
    @Filterable(types = {FilterType.RANGE, FilterType.GREATER_THAN_OR_EQUAL})
    private BigDecimal price;

    @Min(0)
    @Filterable(types = FilterType.RANGE)
    private Integer stock;

    @Filterable(types = {FilterType.EQUALS, FilterType.IN})
    @Enumerated(EnumType.STRING)
    private ProductStatus status;

    @Filterable(types = {FilterType.EQUALS, FilterType.LIKE})
    private String category;

    @ReadOnly
    private LocalDateTime createdAt;

    @ReadOnly
    private LocalDateTime updatedAt;

    @Hidden
    private String supplierNotes;

    @Hidden
    private BigDecimal costPrice;
}

User Management

@Entity
@Table(name = "users")
@AutoApi(
    path = "/users",
    expose = Expose.CREATE_UPDATE,  // Pas de DELETE physique
    description = "User management",
    tags = {"Users", "Authentication"},
    security = @Security(
        level = SecurityLevel.ROLE_BASED,
        roles = {"ADMIN"}
    )
)
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @ReadOnly
    private Long id;

    @NotBlank
    @Size(min = 3, max = 50)
    @Column(unique = true)
    @Filterable(types = {FilterType.EQUALS, FilterType.LIKE})
    private String username;

    @Email
    @NotBlank
    @Column(unique = true)
    @Filterable(types = FilterType.EQUALS)
    private String email;

    @Hidden  // Jamais exposé
    @Column(nullable = false)
    private String passwordHash;

    @Filterable(types = {FilterType.EQUALS, FilterType.IN})
    @Enumerated(EnumType.STRING)
    private UserRole role;

    @Filterable(types = FilterType.EQUALS)
    private Boolean active = true;

    @ReadOnly
    private LocalDateTime lastLogin;

    @ReadOnly
    private LocalDateTime createdAt;

    @Hidden
    private Integer failedLoginAttempts;

    @Hidden
    private String resetToken;
}

Questions Fréquentes

Puis-je combiner @Hidden et @ReadOnly?

Non, c'est redondant. @Hidden exclut déjà le champ des DTOs input ET output.

Comment filtrer sur une relation?

@ManyToOne
@JoinColumn(name = "category_id")
@Filterable(types = FilterType.EQUALS, paramName = "categoryId")
private Category category;

Utilisation: ?categoryId=5

FilterType.RANGE vs BETWEEN?

  • RANGE: Utilise ?field_gte=X&field_lte=Y (deux paramètres séparés)
  • BETWEEN: Utilise ?field_between=X,Y (un seul paramètre avec virgule)

Les deux génèrent le même SQL BETWEEN X AND Y.

Comment désactiver la pagination?

@AutoApi(path = "/config", pagination = false)

L'endpoint retournera toujours tous les résultats (attention aux performances!).


Voir Aussi