#837: FavoritesResultFilter.kt

projectforge-business/src/main/kotlin/org/projectforge/business/address/FavoritesResultFilter.kt Kotlin result filter — user-specific favorites filter for the address list. Implements CustomResultFilter<AddressDO> to show only contacts marked as favorites by the current user. Location: projectforge-business/src/main/kotlin/org/projectforge/business/address/FavoritesResultFilter.kt 34 lines · 8 code · 22 comments · 4 blank
Purpose: Each user can mark contacts as "favorites" — these are stored in the PersonalAddressDO table (a per-user extension of AddressDO). The favorites filter enables the address list UI to toggle between "all contacts" and "favorites only" with a single click, producing a result set containing only the user's starred contacts.

Architecture

The filter is stateless after construction — it captures a snapshot of the user's favorite address ID list at construction time and uses it for all match() calls. This snapshot is a List<Long?> obtained from PersonalAddressDao.favoriteAddressIdList, which is a pre-validated list of address IDs the current user has favorited.

The match() implementation is trivial: favoriteAddressIds.contains(element.id) — O(n) per element where n is the number of favorites. For a typical user with 10–100 favorites and a result set of 1000+ addresses, this is acceptably fast since List.contains() is linear but the list size is small.
AspectFavoritesResultFilterDatabase-level WHERE IN
ImplementationIn-memory on fetched result setSQL WHERE address.id IN (SELECT favorites...)
FlexibilityComposable with other result filters (AND logic)Mixed with other query clauses
N+1 riskNone — one DB query for addresses, one for favoritesNone — single query
PerformanceO(|result| × |favorites|) in memoryOptimized by DB index
The result filter approach was chosen because the address list page uses a filter pipeline: search filter → favorites filter → doublets filter → images filter. Composing post-filters is simpler than building dynamic SQL with multiple optional WHERE clauses. For the favorites case specifically, the performance difference is negligible since the favorites list is small.

Git History

868d6abb7 2025 → 2026
63081666f Source headers: 2024→2025
4c04cfd65 MAJOR: int→Long ID migration
067a4cbb1 Migration continuation
0d183e5df Migration continuation
b7b459e73 Migration continuation
923f5c64b Migration continuation
b6092df09 Copyright 2023 → 2024
f99817436 Images of addresses now in separate entity (performance improvement via lazy loading)
bbed76a3b Addresses may now be searched for addresses with images
bc0a52bc7 Vacation stuff
b2657ec3a Vacation list, MagicFilterProcessor supports I18nEnum values
7c79f1922 Copyright 2020
2d86a8cd6 PROJECTFORGE-2306 Refactorings for access check at update time
9ebb88522 Initial commit
The filter has existed since the project's initial git commit, predating the migration to Long IDs and Kotlin. It was originally a Java class and was converted to Kotlin during the address module's Kotlin migration.

Comparison: Favorites vs. Doublets vs. Images Filters

FilterClassStateComplexity
Favorites (#830)FavoritesResultFilterStateless (captured at construction)O(1) per element — simple contains check
Doublets (#829)DoubletsResultFilterAccumulating (sets grow with each element)O(n²) retroactive flush — most complex
Images (#838)ImagesResultFilterStatelessSimple boolean check per element
All three implement the same CustomResultFilter interface but serve different UX purposes: Favorites shows a user's personal picks, Doublets helps administrators clean duplicates, and Images highlights contacts with photos. They form a filter pipeline that can be toggled independently from the address list toolbar.