Replace linkable associations when merging existing array elements for merge patch - #2597
ogawa-takeshi wants to merge 2 commits into
Conversation
| * @param source must not be {@literal null}. | ||
| * @param target must not be {@literal null}. | ||
| * @param mapper must not be {@literal null}. | ||
| * @throws Exception |
There was a problem hiding this comment.
This @throws Exception could be more descriptive (e.g., @throws Exception if deserialization of the source node fails).
There was a problem hiding this comment.
Done, the Javadoc now states that the exception is thrown if the node cannot be read into the target's type.
| } | ||
|
|
||
| // GH-2596 | ||
|
|
There was a problem hiding this comment.
The comment // GH-2596 above the new test classes in the test file is a bit loose — it's not attached to a specific annotation. The @Test method already has // GH-2596 in its annotation comment, so the standalone comment above the model classes is slightly redundant.
There was a problem hiding this comment.
I followed the existing convention of this file, where the model types backing a ticket's test are grouped under a standalone ticket comment (e.g. // DATAREST-1030 above Note/Tag, // DATAREST-1249 above WithNullCollection). I'd rather keep it consistent with the rest of the file, but I'm happy to drop it if the maintainers prefer.
| } | ||
|
|
||
| @JsonAutoDetect(fieldVisibility = Visibility.ANY) | ||
| static class Track { |
There was a problem hiding this comment.
Add @Id on Track test class for realism.
There was a problem hiding this comment.
I'd prefer to keep Track without an identifier on purpose. It models an element of a value collection, e.g. a JPA @Embeddable in an @ElementCollection, which has neither an identity of its own nor an association resource. That is exactly the situation where the payload is the only place to express the association, so adding @Id would turn the test into a scenario the change doesn't target.
| * @author Takeshi Ogawa | ||
| */ | ||
| @ExtendWith(MockitoExtension.class) | ||
| class DomainObjectReaderUnitTests { |
There was a problem hiding this comment.
Add edge-case tests for null association and appended elements:
- For example, There's no test verifying that sending
nullfor a linkable association in a nested array element correctly nulls out the reference. This edge case is worth covering. - Also, there's no test for the case where the array grows (new element with a linkable association).
- The PR description mentions "appended elements and elements written into an empty collection resolved them properly" — implying those paths already worked. It would be good to have a regression test confirming that appending a new element with a linkable association still works correctly after this change (since the append path goes through
rawValues.apply(current)/mapper.treeToValue, notmergeArrayElement).
There was a problem hiding this comment.
Good point, thanks. I added patchClearsReferenceOfEntityNestedInArray (sending null clears the reference) and patchResolvesReferenceOfEntityAppendedToArray (an appended element with a linkable association, as a regression guard for the append path).
While doing so I noticed that the mocked Associations only stubbed the PersistentProperty overload, so without the fix the Association overload returned false and the null case passed by accident. The mock now keeps the real delegation to the inverse property. Against the code without the fix, the replace and the clear tests fail while the append test passes.
| * Reads the given {@link ObjectNode} into a new instance of the given existing array element's type and merges it | ||
| * into the existing element applying {@literal PUT} semantics. In contrast to {@link #readPut(ObjectNode, Object, | ||
| * ObjectMapper)}, linkable associations are replaced by the ones contained in the payload as nested elements are not | ||
| * exposed as association resources and the payload is the only place to express them. |
There was a problem hiding this comment.
Javadoc for mergeArrayElement could be more precise about what it omits vs. readPut.
There was a problem hiding this comment.
Updated: the Javadoc now states that the handling of linkable associations is the only difference to readPut(…) (replaced instead of skipped, including null) and that all other properties are merged the same way.
…r merge patch. Since spring-projects#2358, an object nested in an array is applied to the existing element at the same index via readPut(…) to obtain PUT semantics as defined by RFC 7386. mergeForPut(…) however skips linkable associations, which is intended for top-level PUT requests where associations are managed through association resources. Nested elements are not exposed as association resources, so the request body is the only place to express them. As a result, non-association properties of an existing element were updated while its associations kept their previous values, whereas appended elements and elements written into an empty collection resolved them properly. We now merge existing array elements through a dedicated path that forwards linkable associations to the merging property handler, which replaces the reference with the one contained in the payload instead of merging the source state into the referenced instance. Closes spring-projects#2596. Signed-off-by: OGAWA, Takeshi <29126253+ogawa-takeshi@users.noreply.github.com>
…g array elements. Add tests for clearing a linkable association of an existing array element and for resolving the association of an appended element. Extract the reader and mapper setup shared by the tests. The mocked Associations now keeps the real delegation of the Association overload to the inverse property so that the code path without the fix behaves as in production. Clarify the Javadoc of mergeArrayElement(…) regarding the difference to readPut(…). See spring-projects#2596. Signed-off-by: OGAWA, Takeshi <29126253+ogawa-takeshi@users.noreply.github.com>
ab2a669 to
ed9e875
Compare
|
Thanks for the review, @ruthst00! I pushed an update addressing the comments (rebased onto the current |
Closes #2596.
Since #2358, an object nested in an array is applied to the existing element at the same index via
readPut(…)to obtain PUT semantics (RFC 7386).mergeForPut(…)skips linkable associations, which is intended for top-levelPUTrequests where associations are managed through association resources. Nested elements have no association resource, so the request body is the only place to express them. As a result, non-association properties of an existing element were updated while its associations kept their previous values, whereas appended elements and elements written into an empty collection resolved them properly.This change
merges existing array elements through a dedicated path (
mergeArrayElement) that forwards linkable associations toMergingPropertyHandler,makes
MergingPropertyHandlerreplace a linkable association with the reference contained in the payload instead of merging the source state into the referenced instance,leaves the top-level
PUTpath (readPut/mergeForPut) unchanged,adds
patchReplacesReferenceOfEntityNestedInArraytoDomainObjectReaderUnitTests(fails onmain, passes with this change;handlesEntityNestedInAnArrayLikePutForPatchRequestfrom Nested arrays of entities need to get PUT semantics applied for merge patch #2358 keeps passing).You have read the Spring Data contribution guidelines.
You use the code formatters provided here and have them applied to your changes. Don't submit any formatting related changes.
You submit test cases (unit or integration tests) that back your changes.
You added yourself as author in the headers of the classes you touched. Amend the date range in the Apache license header if needed. For new types, add the license header (copy from another file and set the current year only).