Skip to content

Commit

Permalink
OData V2 Nested Field Update (#675)
Browse files Browse the repository at this point in the history
Co-authored-by: Roshin Rajan Panackal <roshin.rajan.panackal@sap.com>
  • Loading branch information
rpanackal and Roshin Rajan Panackal authored Dec 23, 2024
1 parent 4584447 commit 4c47e68
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,19 @@ public Changeset addUpdate( @Nonnull final ODataRequestUpdate request )
{
final String versionIdentifier = request.getVersionIdentifier();
request.addVersionIdentifierToHeaderIfPresent(versionIdentifier);
final String httpMethod = request.getUpdateStrategy() == UpdateStrategy.MODIFY_WITH_PATCH ? "PATCH" : "PUT";

final String httpMethod;
switch( request.getUpdateStrategy() ) {
case MODIFY_WITH_PATCH, MODIFY_WITH_PATCH_RECURSIVE_DELTA, MODIFY_WITH_PATCH_RECURSIVE_FULL:
httpMethod = "PATCH";
break;
case REPLACE_WITH_PUT:
httpMethod = "PUT";
break;
default:
throw new IllegalStateException("Unexpected update strategy: " + request.getUpdateStrategy());
}

final BatchItemSingle item =
new BatchItemSingle(originalRequest, request, httpMethod, request::getSerializedEntity);
queries.add(item);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,13 @@ public ODataRequestResultGeneric execute( @Nonnull final HttpClient httpClient )
final ODataHttpRequest request = ODataHttpRequest.forHttpEntity(this, httpClient, requestHttpEntity);
addVersionIdentifierToHeaderIfPresent(versionIdentifier);

if( updateStrategy == UpdateStrategy.MODIFY_WITH_PATCH ) {
return tryExecuteWithCsrfToken(httpClient, request::requestPatch).get();
} else if( updateStrategy == UpdateStrategy.REPLACE_WITH_PUT ) {
return tryExecuteWithCsrfToken(httpClient, request::requestPut).get();
} else {
throw new IllegalStateException("Unexpected update Strategy: " + updateStrategy);
switch( updateStrategy ) {
case MODIFY_WITH_PATCH, MODIFY_WITH_PATCH_RECURSIVE_DELTA, MODIFY_WITH_PATCH_RECURSIVE_FULL:
return tryExecuteWithCsrfToken(httpClient, request::requestPatch).get();
case REPLACE_WITH_PUT:
return tryExecuteWithCsrfToken(httpClient, request::requestPut).get();
default:
throw new IllegalStateException("Unexpected update Strategy: " + updateStrategy);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
*/
package com.sap.cloud.sdk.datamodel.odata.client.request;

import com.google.common.annotations.Beta;

/**
* The strategy to use when updating existing entities.
*/
Expand All @@ -17,5 +19,28 @@ public enum UpdateStrategy
/**
* Request to update the entity is sent with the HTTP method PATCH and its payload contains the changed fields only.
*/
MODIFY_WITH_PATCH;
MODIFY_WITH_PATCH,

/**
* Request to update the entity is sent with the HTTP method PATCH and its payload contains the changed fields
* including the changes in nested non-entity type fields.
*
* The request payload contains only the changed fields. Navigation properties are not supported.
*
* @since 5.16.0
*/
@Beta
MODIFY_WITH_PATCH_RECURSIVE_DELTA,

/**
* Request to update the entity is sent with the HTTP method PATCH and its payload contains the changed fields
* including the changes in nested non-entity type fields.
*
* The request payload contains the full value of complex fields for changes in any nested field. Navigation
* properties are not supported.
*
* @since 5.16.0
*/
@Beta
MODIFY_WITH_PATCH_RECURSIVE_FULL;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

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

import static com.sap.cloud.sdk.datamodel.odata.helper.ModifyPatchStrategy.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
Expand All @@ -19,7 +20,6 @@
import org.apache.http.HttpVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.message.BasicHttpResponse;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination;
Expand Down Expand Up @@ -211,21 +211,41 @@ void testUpdateBPatchUpdateNull()
}

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

final String expectedSerializedEntity = "{\"ProductCount1\":{\"Quantity\":\"20\"}}";
final String expectedSerializedEntity = "{\"ProductCount1\":{\"Quantity\":20}}";

count1.setQuantity(20);

final ODataRequestUpdate receiptUpdate =
FluentHelperFactory
.withServicePath(ODATA_ENDPOINT_URL)
.update(ENTITY_COLLECTION, receipt)
.modifyingEntity()
.modifyingEntity(RECURSIVE_DELTA)
.toRequest();

assertThat(receiptUpdate).isNotNull();
assertThat(receiptUpdate.getSerializedEntity()).isEqualTo(expectedSerializedEntity);
}

@Test
void testUpdatePatchComplexPropertyFull()
{
final ProductCount count1 = ProductCount.builder().productId(123).quantity(10).build();
final Receipt receipt = Receipt.builder().id(1001).customerId(9001).productCount1(count1).build();

final String expectedSerializedEntity = "{\"ProductCount1\":{\"ProductId\":123,\"Quantity\":20}}";

count1.setQuantity(20);

final ODataRequestUpdate receiptUpdate =
FluentHelperFactory
.withServicePath(ODATA_ENDPOINT_URL)
.update(ENTITY_COLLECTION, receipt)
.modifyingEntity(RECURSIVE_FULL)
.toRequest();

assertThat(receiptUpdate).isNotNull();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import org.apache.http.client.HttpClient;

import com.google.common.annotations.Beta;
import com.sap.cloud.sdk.cloudplatform.connectivity.Destination;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpClientAccessor;
import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol;
Expand Down Expand Up @@ -144,23 +145,31 @@ private String getSerializedEntity()
{
final EntityT entity = getEntity();
try {
final List<FieldReference> fieldsToExcludeUpdate =
excludedFields
.stream()
.map(EntitySelectable::getFieldName)
.map(FieldReference::of)
.collect(Collectors.toList());

final List<FieldReference> fieldsToIncludeInUpdate =
includedFields
.stream()
.map(EntitySelectable::getFieldName)
.map(FieldReference::of)
.collect(Collectors.toList());

switch( updateStrategy ) {
case REPLACE_WITH_PUT:
final List<FieldReference> fieldsToExcludeUpdate =
excludedFields
.stream()
.map(EntitySelectable::getFieldName)
.map(FieldReference::of)
.collect(Collectors.toList());
return ODataEntitySerializer.serializeEntityForUpdatePut(entity, fieldsToExcludeUpdate);
case MODIFY_WITH_PATCH:
final List<FieldReference> fieldsToIncludeInUpdate =
includedFields
.stream()
.map(EntitySelectable::getFieldName)
.map(FieldReference::of)
.collect(Collectors.toList());
return ODataEntitySerializer.serializeEntityForUpdatePatch(entity, fieldsToIncludeInUpdate);
return ODataEntitySerializer.serializeEntityForUpdatePatchShallow(entity, fieldsToIncludeInUpdate);
case MODIFY_WITH_PATCH_RECURSIVE_DELTA:
return ODataEntitySerializer
.serializeEntityForUpdatePatchRecursiveDelta(entity, fieldsToIncludeInUpdate);
case MODIFY_WITH_PATCH_RECURSIVE_FULL:
return ODataEntitySerializer
.serializeEntityForUpdatePatchRecursiveFull(entity, fieldsToIncludeInUpdate);
default:
throw new IllegalStateException("Unexpected update strategy:" + updateStrategy);
}
Expand Down Expand Up @@ -193,7 +202,6 @@ private String getSerializedEntity()
*
* @param fields
* The fields to be included in the update execution.
*
* @return The same fluent helper which will include the specified fields in an update request.
*/
@Nonnull
Expand All @@ -212,7 +220,6 @@ public final FluentHelperT includingFields( @Nonnull final EntitySelectable<Enti
*
* @param fields
* The fields to be excluded in the update execution.
*
* @return The same fluent helper which will exclude the specified fields in an update request.
*/
@Nonnull
Expand Down Expand Up @@ -255,4 +262,35 @@ public final FluentHelperT modifyingEntity()
updateStrategy = UpdateStrategy.MODIFY_WITH_PATCH;
return getThis();
}

/**
* Allows to control that the request to update the entity is sent with the HTTP method PATCH and its payload
* contains the changed fields only, with different strategies for handling nested fields.
*
* @param strategy
* The strategy to use for the PATCH update.
* @return The same fluent helper which will modify the entity in the remote system.
* @throws IllegalArgumentException
* If an unknown ModifyPatchStrategy is provided.
* @since 5.16.0
*/
@Beta
@Nonnull
public final FluentHelperT modifyingEntity( @Nonnull final ModifyPatchStrategy strategy )
{
switch( strategy ) {
case SHALLOW:
return modifyingEntity();
case RECURSIVE_DELTA:
updateStrategy = UpdateStrategy.MODIFY_WITH_PATCH_RECURSIVE_DELTA;
break;
case RECURSIVE_FULL:
updateStrategy = UpdateStrategy.MODIFY_WITH_PATCH_RECURSIVE_FULL;
break;
default:
throw new IllegalArgumentException("Unknown ModifyPatchStrategy: " + strategy);
}
return getThis();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.sap.cloud.sdk.datamodel.odata.helper;

import com.google.common.annotations.Beta;

/**
* Strategy to determine how a patch operation should be applied to an entity.
*
* @since 5.16.0
*/
@Beta
public enum ModifyPatchStrategy
{
/** Only the top level fields can be patched */
SHALLOW,

/** All top level and nested fields can be patched, resulting in JSON containing only the changed fields */
RECURSIVE_DELTA,

/**
* All top level and nested fields can be patched, resulting in JSON containing the full value of complex object.
*/
RECURSIVE_FULL
}
Loading

0 comments on commit 4c47e68

Please sign in to comment.