#832: AddressbookDO.kt

projectforge-business/src/main/kotlin/org/projectforge/business/address/AddressbookDO.kt Type: JPA Entity (Domain Object)
Purpose: Persistent domain model for address books, with user/group access rights and full-text search support
Location: projectforge-business/src/main/kotlin/org/projectforge/business/address/AddressbookDO.kt
Table: T_ADDRESSBOOK 91 lines · 52 code · 29 comments · 10 blank
Summary: AddressbookDO represents a named collection of addresses — a user- or group-owned address book. It extends BaseUserGroupRightsDO to inherit an ownership and group-permission model, implements DisplayNameCapable for consistent UI labels, and carries Hibernate Search annotations (@Indexed, @FullTextField) so that address books can be located via the application's full-text search engine. Fields are annotated with @PropertyInfo to drive automatic UI form generation.

Architecture & Position in the System

Class Hierarchy

BaseDO
 └─ DefaultBaseDO
     └─ BaseUserGroupRightsDO        ← provides owner, user-rights, group-rights fields
         └─ AddressbookDO             ← this class
             also implements DisplayNameCapable

BaseUserGroupRightsDO is the critical parent: it adds columns for owner (a PFUserDO reference), along with the rights-assignment fields that let an address book be shared with individual users or entire groups. This parent-class design means every address book automatically participates in ProjectForge's multi-tenant visibility model.

JPA Mapping

AnnotationEffect
@EntityMarks this as a Hibernate-managed persistent class.
@Table(name = "T_ADDRESSBOOK")Maps to the T_ADDRESSBOOK database table. The T_ prefix is a ProjectForge convention for all table names.
@IndexedRegisters this entity with Hibernate Search (Lucene backend). Enables full-text queries.

Fields

FieldTypeDB ColumnAnnotationsNotes
title String? (mapped) @PropertyInfo(i18nKey="addressbook.title", required=true), @FullTextField, @Column(length=LENGTH_TITLE) The required display name. Full-text indexed. Length capped at Constants.LENGTH_TITLE (likely 255).
owner PFUserDO? owner_fk @ManyToOne(fetch=EAGER), @JoinColumn(name="owner_fk"), @IndexedEmbedded(includeDepth=1), @JsonSerialize(using=IdOnlySerializer) Eagerly loaded to avoid LazyInitializationException in UI rendering. Embedded one level deep in the Hibernate Search index. Serialized to JSON as only the numeric ID to prevent circular references.
description String? (mapped) @FullTextField, @Column(length=LENGTH_TEXT) Optional free-text description. Full-text indexed. Length capped at Constants.LENGTH_TEXT (likely 4000).

Inherited Fields (from BaseUserGroupRightsDO)

These fields are defined in the parent class and inherited automatically; they are not redeclared in AddressbookDO but are present in the database table:

Hibernate Search Integration

The @Indexed marker, combined with @FullTextField on title and description, makes this entity discoverable through Hibernate Search. The HibernateSearchUsersGroupsTypeBinder (referenced in the source but not shown in the abridged content) bridges the user/group rights into the Lucene index so that search results respect the same visibility constraints as database queries — a user searching for address books will only see results they have permission to access.

DisplayNameCapable & UI Integration

The displayName property returns "$title", providing a consistent label for drop-downs, breadcrumbs, and history entries. The @PropertyInfo annotations feed into ProjectForge's automatic form-rendering system, which generates input fields, validation messages (e.g. "Title is required"), and column headers from the i18nKey lookups.

Equality Semantics

The class defines custom hashCode() and equals() methods. When the entity has been persisted (ID is non-null), equality is based on the database ID — two AddressbookDO instances with the same ID are considered equal. When the ID is null (a transient, not-yet-saved object), equality falls back to object identity (===). This is the standard Hibernate-recommended pattern that avoids problems with proxy objects and collections.

Git History

CommitDescription
868d6abb7Copyright year bump: 2025 → 2026
63081666fSource file headers: 2024 → 2025
4c04cfd65MAJOR: Migration of integer IDs to Long (changed Integer ID fields to Long throughout)
b6092df09Copyright 2023 → 2024
ab45d51faCopyright 2001-2022 → 2001-2023
b131193e7Member variables refactored using by lazy {} (deferred initialisation for collections and heavy dependencies)
5f7ef41b8Copyright 2021 → 2022
ceb63e8a1Source code header: (C) 2001-2021
7c79f1922Copyright of source header → 2020
dd5ca38acCopyRight of all java file-headers updated or created
9ebb88522Initial commit — present from the project's origin

Design Rationale

Why extend BaseUserGroupRightsDO?

Address books are shared resources — a user creates one and may grant read or write access to other users or groups. The BaseUserGroupRightsDO superclass provides a uniform rights model (owner + per-user grants + per-group grants) that is reused across many ProjectForge entity types (address books, calendars, task lists, etc.). This avoids duplicating the same access-control schema in every entity and ensures consistent behaviour in the DAO and UI layers.

Why eager-load owner?

Address books are almost always displayed with their owner's name. If owner were lazily loaded, every list page would trigger N+1 additional queries. Eager fetching is a deliberate performance trade-off: it adds a JOIN to every address-book query but eliminates the N+1 problem in the common case. The @JsonSerialize(using = IdOnlySerializer::class) annotation ensures that when the entity is serialised to JSON (e.g. for REST APIs), only the owner's numeric ID is emitted, preventing deep object-graph serialisation and circular references.

Why Hibernate Search indexing?

ProjectForge provides a global search feature that spans multiple entity types. By annotating AddressbookDO with @Indexed and @FullTextField, the Lucene index is kept in sync with the database via Hibernate Search's automatic indexing on save/update/delete. The HibernateSearchUsersGroupsTypeBinder further ensures that access-control metadata is embedded in the search index, so the same user who cannot see an address book in a list view cannot find it via full-text search either — a critical security property.

Why implement DisplayNameCapable?

This interface (providing displayName: String) is a ProjectForge convention used by history/audit logging, UI breadcrumbs, and selection drop-downs. By implementing it, AddressbookDO plugs into these subsystems without every consumer needing to know that the display name is simply the title field.

Why Constants.LENGTH_TITLE instead of hard-coded lengths?

ProjectForge uses a shared Constants class to define standard column lengths. This ensures that all entities using "title" or "description" fields have consistent database column sizes, simplifying schema management and avoiding subtle bugs where one entity's title is 255 characters but another's is 1000.