Skip to content

Commit 4c47e68

Browse files
authored
OData V2 Nested Field Update (#675)
Co-authored-by: Roshin Rajan Panackal <[email protected]>
1 parent 4584447 commit 4c47e68

File tree

9 files changed

+324
-31
lines changed

9 files changed

+324
-31
lines changed

datamodel/odata-client/src/main/java/com/sap/cloud/sdk/datamodel/odata/client/request/ODataRequestBatch.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,19 @@ public Changeset addUpdate( @Nonnull final ODataRequestUpdate request )
278278
{
279279
final String versionIdentifier = request.getVersionIdentifier();
280280
request.addVersionIdentifierToHeaderIfPresent(versionIdentifier);
281-
final String httpMethod = request.getUpdateStrategy() == UpdateStrategy.MODIFY_WITH_PATCH ? "PATCH" : "PUT";
281+
282+
final String httpMethod;
283+
switch( request.getUpdateStrategy() ) {
284+
case MODIFY_WITH_PATCH, MODIFY_WITH_PATCH_RECURSIVE_DELTA, MODIFY_WITH_PATCH_RECURSIVE_FULL:
285+
httpMethod = "PATCH";
286+
break;
287+
case REPLACE_WITH_PUT:
288+
httpMethod = "PUT";
289+
break;
290+
default:
291+
throw new IllegalStateException("Unexpected update strategy: " + request.getUpdateStrategy());
292+
}
293+
282294
final BatchItemSingle item =
283295
new BatchItemSingle(originalRequest, request, httpMethod, request::getSerializedEntity);
284296
queries.add(item);

datamodel/odata-client/src/main/java/com/sap/cloud/sdk/datamodel/odata/client/request/ODataRequestUpdate.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,13 @@ public ODataRequestResultGeneric execute( @Nonnull final HttpClient httpClient )
174174
final ODataHttpRequest request = ODataHttpRequest.forHttpEntity(this, httpClient, requestHttpEntity);
175175
addVersionIdentifierToHeaderIfPresent(versionIdentifier);
176176

177-
if( updateStrategy == UpdateStrategy.MODIFY_WITH_PATCH ) {
178-
return tryExecuteWithCsrfToken(httpClient, request::requestPatch).get();
179-
} else if( updateStrategy == UpdateStrategy.REPLACE_WITH_PUT ) {
180-
return tryExecuteWithCsrfToken(httpClient, request::requestPut).get();
181-
} else {
182-
throw new IllegalStateException("Unexpected update Strategy: " + updateStrategy);
177+
switch( updateStrategy ) {
178+
case MODIFY_WITH_PATCH, MODIFY_WITH_PATCH_RECURSIVE_DELTA, MODIFY_WITH_PATCH_RECURSIVE_FULL:
179+
return tryExecuteWithCsrfToken(httpClient, request::requestPatch).get();
180+
case REPLACE_WITH_PUT:
181+
return tryExecuteWithCsrfToken(httpClient, request::requestPut).get();
182+
default:
183+
throw new IllegalStateException("Unexpected update Strategy: " + updateStrategy);
183184
}
184185
}
185186

datamodel/odata-client/src/main/java/com/sap/cloud/sdk/datamodel/odata/client/request/UpdateStrategy.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
*/
44
package com.sap.cloud.sdk.datamodel.odata.client.request;
55

6+
import com.google.common.annotations.Beta;
7+
68
/**
79
* The strategy to use when updating existing entities.
810
*/
@@ -17,5 +19,28 @@ public enum UpdateStrategy
1719
/**
1820
* Request to update the entity is sent with the HTTP method PATCH and its payload contains the changed fields only.
1921
*/
20-
MODIFY_WITH_PATCH;
22+
MODIFY_WITH_PATCH,
23+
24+
/**
25+
* Request to update the entity is sent with the HTTP method PATCH and its payload contains the changed fields
26+
* including the changes in nested non-entity type fields.
27+
*
28+
* The request payload contains only the changed fields. Navigation properties are not supported.
29+
*
30+
* @since 5.16.0
31+
*/
32+
@Beta
33+
MODIFY_WITH_PATCH_RECURSIVE_DELTA,
34+
35+
/**
36+
* Request to update the entity is sent with the HTTP method PATCH and its payload contains the changed fields
37+
* including the changes in nested non-entity type fields.
38+
*
39+
* The request payload contains the full value of complex fields for changes in any nested field. Navigation
40+
* properties are not supported.
41+
*
42+
* @since 5.16.0
43+
*/
44+
@Beta
45+
MODIFY_WITH_PATCH_RECURSIVE_FULL;
2146
}

datamodel/odata/odata-api-sample/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperUpdateToRequestTest.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package com.sap.cloud.sdk.datamodel.odata.helper;
66

7+
import static com.sap.cloud.sdk.datamodel.odata.helper.ModifyPatchStrategy.*;
78
import static org.assertj.core.api.Assertions.assertThat;
89
import static org.mockito.ArgumentMatchers.any;
910
import static org.mockito.ArgumentMatchers.argThat;
@@ -19,7 +20,6 @@
1920
import org.apache.http.HttpVersion;
2021
import org.apache.http.client.HttpClient;
2122
import org.apache.http.message.BasicHttpResponse;
22-
import org.junit.jupiter.api.Disabled;
2323
import org.junit.jupiter.api.Test;
2424

2525
import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination;
@@ -211,21 +211,41 @@ void testUpdateBPatchUpdateNull()
211211
}
212212

213213
@Test
214-
@Disabled( " Test is failing as the getChangedFields() method on Complex Type is not working as expected." )
215-
void testUpdatePatchComplexProperty()
214+
void testUpdatePatchComplexPropertyDelta()
216215
{
217216
final ProductCount count1 = ProductCount.builder().productId(123).quantity(10).build();
218217
final Receipt receipt = Receipt.builder().id(1001).customerId(9001).productCount1(count1).build();
219218

220-
final String expectedSerializedEntity = "{\"ProductCount1\":{\"Quantity\":\"20\"}}";
219+
final String expectedSerializedEntity = "{\"ProductCount1\":{\"Quantity\":20}}";
221220

222221
count1.setQuantity(20);
223222

224223
final ODataRequestUpdate receiptUpdate =
225224
FluentHelperFactory
226225
.withServicePath(ODATA_ENDPOINT_URL)
227226
.update(ENTITY_COLLECTION, receipt)
228-
.modifyingEntity()
227+
.modifyingEntity(RECURSIVE_DELTA)
228+
.toRequest();
229+
230+
assertThat(receiptUpdate).isNotNull();
231+
assertThat(receiptUpdate.getSerializedEntity()).isEqualTo(expectedSerializedEntity);
232+
}
233+
234+
@Test
235+
void testUpdatePatchComplexPropertyFull()
236+
{
237+
final ProductCount count1 = ProductCount.builder().productId(123).quantity(10).build();
238+
final Receipt receipt = Receipt.builder().id(1001).customerId(9001).productCount1(count1).build();
239+
240+
final String expectedSerializedEntity = "{\"ProductCount1\":{\"ProductId\":123,\"Quantity\":20}}";
241+
242+
count1.setQuantity(20);
243+
244+
final ODataRequestUpdate receiptUpdate =
245+
FluentHelperFactory
246+
.withServicePath(ODATA_ENDPOINT_URL)
247+
.update(ENTITY_COLLECTION, receipt)
248+
.modifyingEntity(RECURSIVE_FULL)
229249
.toRequest();
230250

231251
assertThat(receiptUpdate).isNotNull();

datamodel/odata/odata-core/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperUpdate.java

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import org.apache.http.client.HttpClient;
1616

17+
import com.google.common.annotations.Beta;
1718
import com.sap.cloud.sdk.cloudplatform.connectivity.Destination;
1819
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpClientAccessor;
1920
import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol;
@@ -144,23 +145,31 @@ private String getSerializedEntity()
144145
{
145146
final EntityT entity = getEntity();
146147
try {
148+
final List<FieldReference> fieldsToExcludeUpdate =
149+
excludedFields
150+
.stream()
151+
.map(EntitySelectable::getFieldName)
152+
.map(FieldReference::of)
153+
.collect(Collectors.toList());
154+
155+
final List<FieldReference> fieldsToIncludeInUpdate =
156+
includedFields
157+
.stream()
158+
.map(EntitySelectable::getFieldName)
159+
.map(FieldReference::of)
160+
.collect(Collectors.toList());
161+
147162
switch( updateStrategy ) {
148163
case REPLACE_WITH_PUT:
149-
final List<FieldReference> fieldsToExcludeUpdate =
150-
excludedFields
151-
.stream()
152-
.map(EntitySelectable::getFieldName)
153-
.map(FieldReference::of)
154-
.collect(Collectors.toList());
155164
return ODataEntitySerializer.serializeEntityForUpdatePut(entity, fieldsToExcludeUpdate);
156165
case MODIFY_WITH_PATCH:
157-
final List<FieldReference> fieldsToIncludeInUpdate =
158-
includedFields
159-
.stream()
160-
.map(EntitySelectable::getFieldName)
161-
.map(FieldReference::of)
162-
.collect(Collectors.toList());
163-
return ODataEntitySerializer.serializeEntityForUpdatePatch(entity, fieldsToIncludeInUpdate);
166+
return ODataEntitySerializer.serializeEntityForUpdatePatchShallow(entity, fieldsToIncludeInUpdate);
167+
case MODIFY_WITH_PATCH_RECURSIVE_DELTA:
168+
return ODataEntitySerializer
169+
.serializeEntityForUpdatePatchRecursiveDelta(entity, fieldsToIncludeInUpdate);
170+
case MODIFY_WITH_PATCH_RECURSIVE_FULL:
171+
return ODataEntitySerializer
172+
.serializeEntityForUpdatePatchRecursiveFull(entity, fieldsToIncludeInUpdate);
164173
default:
165174
throw new IllegalStateException("Unexpected update strategy:" + updateStrategy);
166175
}
@@ -193,7 +202,6 @@ private String getSerializedEntity()
193202
*
194203
* @param fields
195204
* The fields to be included in the update execution.
196-
*
197205
* @return The same fluent helper which will include the specified fields in an update request.
198206
*/
199207
@Nonnull
@@ -212,7 +220,6 @@ public final FluentHelperT includingFields( @Nonnull final EntitySelectable<Enti
212220
*
213221
* @param fields
214222
* The fields to be excluded in the update execution.
215-
*
216223
* @return The same fluent helper which will exclude the specified fields in an update request.
217224
*/
218225
@Nonnull
@@ -255,4 +262,35 @@ public final FluentHelperT modifyingEntity()
255262
updateStrategy = UpdateStrategy.MODIFY_WITH_PATCH;
256263
return getThis();
257264
}
265+
266+
/**
267+
* Allows to control that the request to update the entity is sent with the HTTP method PATCH and its payload
268+
* contains the changed fields only, with different strategies for handling nested fields.
269+
*
270+
* @param strategy
271+
* The strategy to use for the PATCH update.
272+
* @return The same fluent helper which will modify the entity in the remote system.
273+
* @throws IllegalArgumentException
274+
* If an unknown ModifyPatchStrategy is provided.
275+
* @since 5.16.0
276+
*/
277+
@Beta
278+
@Nonnull
279+
public final FluentHelperT modifyingEntity( @Nonnull final ModifyPatchStrategy strategy )
280+
{
281+
switch( strategy ) {
282+
case SHALLOW:
283+
return modifyingEntity();
284+
case RECURSIVE_DELTA:
285+
updateStrategy = UpdateStrategy.MODIFY_WITH_PATCH_RECURSIVE_DELTA;
286+
break;
287+
case RECURSIVE_FULL:
288+
updateStrategy = UpdateStrategy.MODIFY_WITH_PATCH_RECURSIVE_FULL;
289+
break;
290+
default:
291+
throw new IllegalArgumentException("Unknown ModifyPatchStrategy: " + strategy);
292+
}
293+
return getThis();
294+
}
295+
258296
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.sap.cloud.sdk.datamodel.odata.helper;
2+
3+
import com.google.common.annotations.Beta;
4+
5+
/**
6+
* Strategy to determine how a patch operation should be applied to an entity.
7+
*
8+
* @since 5.16.0
9+
*/
10+
@Beta
11+
public enum ModifyPatchStrategy
12+
{
13+
/** Only the top level fields can be patched */
14+
SHALLOW,
15+
16+
/** All top level and nested fields can be patched, resulting in JSON containing only the changed fields */
17+
RECURSIVE_DELTA,
18+
19+
/**
20+
* All top level and nested fields can be patched, resulting in JSON containing the full value of complex object.
21+
*/
22+
RECURSIVE_FULL
23+
}

0 commit comments

Comments
 (0)