CodamAIDocs
Topicdone

Custom data filters

How a project adds its own visibility rules and why a mandatory filter that cannot be resolved aborts the request instead of silently disappearing.

Variants
filter returns a conditionfilter returns null → no restrictionfilter throws an error → request failsseveral filters on one modelunresolvable → 500

What this is about

Own data and attribute filters cover the common cases. Sometimes a project needs a rule of its own, such as “only the editorial team sees drafts” or “archived contracts only with a certain attribute”. For that you write a custom data filter.

A custom data filter is a Spring bean that implements CdmsFilterInterface<T> for the DTO of a model. CDMS finds it by its type and asks it for a condition on every request on that model.

What a custom filter looks like

@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);
  }
}

You do not need more: no annotation for CDMS, no registration, no fixed class name. Only the type parameter matters, here ContractDto.

The filter runs once per request, not per row. So it has to express its rule as a condition the database can check. Field paths through relations are allowed, e.g. company.id.

What get() can return

The three answers of a filter

When: get() returns a ListSearchFilter

CDMS adds the condition to the request as a mandatory filter, combined with AND.

Result: The person sees only rows that meet the condition.

When: get() returns null

CDMS adds nothing for this filter.

Result: No restriction by this filter. Other filters still apply.

When: get() throws an exception

The request fails with the status of the exception. The attribute filter does exactly this for a missing attribute: 422.

Result: Nothing is read or written.

The filter tree

CDMS builds every request on a model from an AND root. Your request and all security filters hang below it, side by side.

flowchart TB
    W["AND"] --> C["client filters<br/>(from query)"]
    W --> O["own data<br/>_userId = person"]
    W --> A["attribute filter"]
    W --> E1["custom filter 1"]
    W --> E2["custom filter 2"]

If there are several custom filters for the same model, all of them apply at the same time. A row has to meet every condition.

Where the filter applies

RequestEffect
POST /queryOnly matching rows; totalCount counts only them.
POST /read/{id}Non-matching row → 404.
Lists and references in a responseNon-matching entries are missing; a non-matching single reference is null.
PUT, PATCH, DELETE, rollbackCDMS first checks whether the row passes the filter; otherwise 404.

So what you may not read, you may not change either. See Why invisible objects return 404.

When the filter does not fit

A filter that does not fit, for example with a typo in the field name, never silently drops out: without it more rows would come back, for a security filter every row. Who has to fix the error depends on where the filter comes from:

Client filter and mandatory filter
Client filter
from query
  • unknown field or unsuitable operator → request rejected
  • 400 unknown-search-key|…, unsupported-operator|…
  • the client corrects its request
Mandatory filter
own data, attribute filters, custom filters
  • unknown field, unknown path step or unsuitable operator → request fails
  • 500 unresolvable-mandatory-filter|<field>|…
  • better no result than too much

500 here means: the project is set up wrongly, not the request. The client cannot do anything about it. Check the field name in your filter against the model. The client can neither set nor switch off a mandatory filter.

Pitfalls

Where to go next

Sources in the code and the knowledge base
  • CDMS/cdms-commons – CdmsFilterInterface, ListSearchFilter (mandatory)
  • CDMS/cdms-system-layer – AbstractLayer (getCdmsFilter, addSecurityFilters, buildSearchRoot, assertVisibleForWrite, fetchAndSetModel, fetchAndSetList)
  • CDMS/cdms-persistence-database – DatabaseConditionBuilder (unresolvable-mandatory-filter)
  • CDMS/cdms-commons – SystemConfigurationException
  • CDMS/cdms-integrationtest – AbstractFilterTest, OrderFilter, VaultFilter
  • documentation/60-erweiterung/03-eigene-filter.md
Search