fix: detect @PropertyTransformer on read-only derived properties [DHIS2-21872]#24520
Merged
jason-p-pickering merged 2 commits intoJul 22, 2026
Merged
Conversation
…S2-21872] PropertyPropertyIntrospector.updatePropertyTypes() only read the @PropertyTransformer annotation inside the isWritable() branch. A property whose getter is annotated but has no matching setter (e.g. UserRole.getUsers(), computed from the separately-named `members` field) is read-only, so its annotation was silently dropped and Property.hasPropertyTransformer() returned false at runtime even though the annotation is present in source. Move the annotation read outside the writability gate, since whether a property is serialized via a custom transformer is orthogonal to whether it can be written to. Found while live-validating DHIS2-21856/PR #24519: with that fix alone, GET /api/userRoles/{id}?fields=users[*] against an 83,385-member role still took 139.7s / ~583k queries because hasPropertyTransformer() never saw UserRole.users' annotation. With this fix applied, the same request drops to 5.76s.
jbee
approved these changes
Jul 22, 2026
…21872] Per review feedback from @jbee: initFromPropertyAnnotation() was also gated behind isWritable(), but most of @Property's attributes (type override, required/persisted/owner flags, persistedAs aliasing) describe the property itself and apply equally to read-only properties or aliases, not just writable ones. Snapshot writability before calling initFromPropertyAnnotation(), since that call can itself flip it via access = READ_ONLY/WRITE_ONLY, and range/min-max defaults should still be judged against the pre-annotation writability (matches existing behavior for e.g. Attribute.getObjectTypes(), which has a real setter overridden to read-only via the annotation). Added regression coverage: a read-only property now picks up persistedAs() (previously silently ignored), and a writable property whose annotation overrides it to read-only still gets range/min-max defaults applied (unchanged behavior).
|
netroms
approved these changes
Jul 22, 2026
jason-p-pickering
deleted the
fix/DHIS2-21872-propertytransformer-iswritable-gate
branch
July 22, 2026 11:02
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
PropertyPropertyIntrospector.updatePropertyTypes()only calledinitFromPropertyTransformerAnnotation(property)inside theif (property.isWritable())branch. A property whose getter carries@PropertyTransformerbut has no matching setter — e.g.UserRole.getUsers(), which is a computed getter derived from the separately-namedmembersfield (its own setter issetMembers(), there is nosetUsers()) — is read-only, so the annotation was silently dropped from the schema.Property.hasPropertyTransformer()incorrectly returnedfalseforUserRole.usersat runtime, even though the annotation is right there in source.Whether a property is serialized via a custom transformer is orthogonal to whether it can be written to, so this moves the annotation read outside the writability gate.
Other transformer-annotated properties happen to be unaffected because their getter has a matching setter (e.g.
OrganisationUnit.users/setUsers(),UserGroup.members/setMembers()), so this specifically hitUserRole.users.How this was found
While live-validating #24519 (DHIS2-21856, PropertyTransformer subtree pruning) against a local dev instance with Glowroot,
GET /api/userRoles/{id}?fields=users[*]against a role with 83,385 members still took 139.7s / ~583k queries — unchanged from the pre-#24519 baseline. #24519 and the still-open #24514 both short-circuit onProperty.hasPropertyTransformer(), but neither could work forUserRole.usersspecifically because this introspection bug meant the annotation was never seen in the first place.With this fix applied (schema rebuilt, server restarted), the same request against the same role dropped to 5.76s.
Test plan
PropertyPropertyIntrospectorTest.detectsPropertyTransformerOnReadOnlyDerivedGetter, which runs the realJacksonPropertyIntrospector→PropertyPropertyIntrospectorpipeline against the actualUserRoleclass (not a hand-builtProperty), so it exercises the exact reflection path that had the bug.hasPropertyTransformer()false) and passes after the fix.dhis-service-schemamodule test suite green (47/47).🤖 Generated with Claude Code