Worum es geht
Eigene Daten und Attributfilter decken die häufigen Fälle ab. Manchmal braucht ein Projekt eine eigene Regel, etwa „Entwürfe sieht nur die Redaktion“ oder „Archivierte Verträge nur mit einem bestimmten Attribut“. Dafür schreibst du einen eigenen Datenfilter.
Ein eigener Datenfilter ist eine Spring-Bean, die CdmsFilterInterface<T> für das DTO eines Modells umsetzt. CDMS findet sie über den Typ und fragt sie bei jeder Anfrage auf dieses Modell nach einer Bedingung.
So sieht ein eigener Filter aus
@Service
public class ContractArchiveFilter implements CdmsFilterInterface<ContractDto> {
@Override
public ListSearchFilter get() {
List<String> roles = RequestContextHolder.get().getEffectiveUserRoles();
if (roles != null && roles.contains("contract-archive"))
return null; // no restriction
return new ListSearchFilter("archived", "false", SearchLogicOperations.EQ);
}
}
Mehr brauchst du nicht: keine Annotation für CDMS, keine Registrierung, kein fester Klassenname. Entscheidend ist nur der Typparameter, hier ContractDto.
Der Filter läuft einmal pro Anfrage, nicht pro Zeile. Er muss seine Regel deshalb als Bedingung ausdrücken, die die Datenbank prüfen kann. Feldpfade über Beziehungen sind erlaubt, z. B. company.id.
Was get() zurückgeben kann
Wann: get() liefert einen ListSearchFilter
CDMS hängt die Bedingung als Pflichtfilter an die Anfrage, verbunden mit UND.
Ergebnis: Die Person sieht nur Zeilen, die die Bedingung erfüllen.
Wann: get() liefert null
CDMS hängt für diesen Filter nichts an.
Ergebnis: Keine Einschränkung durch diesen Filter. Andere Filter gelten weiter.
Wann: get() wirft eine Exception
Die Anfrage scheitert mit dem Status der Exception. Der Attributfilter macht es so bei fehlendem Attribut: 422.
Ergebnis: Nichts wird gelesen oder geschrieben.
Der Filterbaum
CDMS baut jede Anfrage auf ein Modell aus einer UND-Wurzel. Darunter hängen deine Anfrage und alle Sicherheitsfilter nebeneinander.
flowchart TB
W["UND"] --> C["Filter vom Client<br/>(aus query)"]
W --> O["eigene Daten<br/>_userId = Person"]
W --> A["Attributfilter"]
W --> E1["eigener Filter 1"]
W --> E2["eigener Filter 2"]
Gibt es mehrere eigene Filter für dasselbe Modell, gelten alle gleichzeitig. Eine Zeile muss jede Bedingung erfüllen.
Wo der Filter wirkt
| Anfrage | Wirkung |
|---|---|
POST /query | Nur passende Zeilen, totalCount zählt nur sie. |
POST /read/{id} | Nicht passende Zeile → 404. |
Listen und Referenzen in einer response | Nicht passende Einträge fehlen, eine nicht passende Einzelreferenz ist null. |
PUT, PATCH, DELETE, rollback | CDMS prüft vorher, ob die Zeile den Filter besteht; sonst 404. |
Was du nicht lesen darfst, darfst du also auch nicht ändern. Siehe Warum Unsichtbares 404 liefert.
Wenn der Filter nicht passt
Ein Filter, der nicht passt, etwa mit einem Tippfehler im Feldnamen, fällt nie still weg: Ohne ihn kämen mehr Zeilen zurück, bei einem Sicherheitsfilter jede. Wer den Fehler beheben muss, hängt davon ab, woher der Filter kommt:
- unbekanntes Feld oder unpassender Operator → Anfrage abgelehnt
- 400
unknown-search-key|…,unsupported-operator|… - der Client korrigiert seine Anfrage
- unbekanntes Feld, unbekannter Pfadschritt oder unpassender Operator → Anfrage scheitert
- 500
unresolvable-mandatory-filter|<feld>|… - lieber kein Ergebnis als zu viel
500 heißt hier: Das Projekt ist falsch eingerichtet, nicht die Anfrage. Der Client kann nichts daran ändern. Prüfe den Feldnamen in deinem Filter gegen das Modell. Einen Pflichtfilter kann der Client weder setzen noch abschalten.
Fallen
Wie es weitergeht
- Die fertige Lösung für Profilattribute: Attributfilter
- Filter aus Sicht der Suche: Filter, die immer mitlaufen
- Wo eigener Code sonst hingehört: Generierter Code und eigener Code