From 9f28570b06f1c33ccab4ef2b51c5391eb6cd5168 Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Sat, 7 Mar 2026 13:24:51 +0200 Subject: [PATCH 01/12] implementing the fix --- .../adapter/ODataGenericConverter.java | 20 +- .../odatav4/core/FieldSerializationTest.java | 188 ++++++++++-------- 2 files changed, 125 insertions(+), 83 deletions(-) diff --git a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java index 1e4966697..b64638691 100644 --- a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java +++ b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java @@ -53,7 +53,25 @@ final class ODataGenericConverter extends AbstractTypeConverter(Duration.class, Duration::toString, Duration::parse); private static final ODataGenericConverter BINARY = - new ODataGenericConverter<>(byte[].class, Base64.getEncoder()::encodeToString, Base64.getDecoder()::decode); + new ODataGenericConverter<>( + byte[].class, + Base64.getEncoder()::encodeToString, + ODataGenericConverter::decodeBinary); + + @Nonnull + private static byte[] decodeBinary( @Nonnull final String value ) + { + final boolean containsBase64Alphabet = value.indexOf('+') >= 0 || value.indexOf('/') >= 0; + final boolean containsBase64UrlAlphabet = value.indexOf('-') >= 0 || value.indexOf('_') >= 0; + + if( containsBase64Alphabet && containsBase64UrlAlphabet ) { + throw new IllegalArgumentException( + "Invalid binary value: mixed Base64 and Base64URL alphabets are not allowed."); + } + + final Base64.Decoder decoder = containsBase64UrlAlphabet ? Base64.getUrlDecoder() : Base64.getDecoder(); + return decoder.decode(value); + } private static final ODataGenericConverter STRING = new ODataGenericConverter<>(String.class, Function.identity(), Function.identity()); diff --git a/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java b/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java index a3c02d65f..ed17f494e 100644 --- a/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java +++ b/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java @@ -41,17 +41,15 @@ import lombok.NoArgsConstructor; import lombok.SneakyThrows; -class FieldSerializationTest -{ +class FieldSerializationTest { @Data @NoArgsConstructor @AllArgsConstructor - @EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) - @JsonAdapter( GsonVdmAdapterFactory.class ) - @JsonSerialize( using = JacksonVdmObjectSerializer.class ) - @JsonDeserialize( using = JacksonVdmObjectDeserializer.class ) - public static class ReferenceObject extends VdmEntity - { + @EqualsAndHashCode(doNotUseGetters = true, callSuper = true) + @JsonAdapter(GsonVdmAdapterFactory.class) + @JsonSerialize(using = JacksonVdmObjectSerializer.class) + @JsonDeserialize(using = JacksonVdmObjectDeserializer.class) + public static class ReferenceObject extends VdmEntity { @Getter private final String odataType = "TestEntity"; @@ -61,134 +59,160 @@ public static class ReferenceObject extends VdmEntity @Getter private final Class type = ReferenceObject.class; - @ElementName( "ByteValue" ) - @SerializedName( "ByteValue" ) - @JsonProperty( "ByteValue" ) + @ElementName("ByteValue") + @SerializedName("ByteValue") + @JsonProperty("ByteValue") short byteValue; - @ElementName( "SByteValue" ) - @SerializedName( "SByteValue" ) - @JsonProperty( "SByteValue" ) + @ElementName("SByteValue") + @SerializedName("SByteValue") + @JsonProperty("SByteValue") Byte sByteValue; - @ElementName( "Int16Value" ) - @SerializedName( "Int16Value" ) - @JsonProperty( "Int16Value" ) + @ElementName("Int16Value") + @SerializedName("Int16Value") + @JsonProperty("Int16Value") short int16Value; - @ElementName( "Int32Value" ) - @SerializedName( "Int32Value" ) - @JsonProperty( "Int32Value" ) + @ElementName("Int32Value") + @SerializedName("Int32Value") + @JsonProperty("Int32Value") int int32Value; - @ElementName( "Int64Value" ) - @SerializedName( "Int64Value" ) - @JsonProperty( "Int64Value" ) + @ElementName("Int64Value") + @SerializedName("Int64Value") + @JsonProperty("Int64Value") long int64Value; - @ElementName( "SingleValue" ) - @SerializedName( "SingleValue" ) - @JsonProperty( "SingleValue" ) + @ElementName("SingleValue") + @SerializedName("SingleValue") + @JsonProperty("SingleValue") float singleValue; - @ElementName( "DoubleValue" ) - @SerializedName( "DoubleValue" ) - @JsonProperty( "DoubleValue" ) + @ElementName("DoubleValue") + @SerializedName("DoubleValue") + @JsonProperty("DoubleValue") double doubleValue; - @ElementName( "DecimalValue" ) - @SerializedName( "DecimalValue" ) - @JsonProperty( "DecimalValue" ) + @ElementName("DecimalValue") + @SerializedName("DecimalValue") + @JsonProperty("DecimalValue") BigDecimal decimalValue; - @ElementName( "BooleanValue" ) - @SerializedName( "BooleanValue" ) - @JsonProperty( "BooleanValue" ) + @ElementName("BooleanValue") + @SerializedName("BooleanValue") + @JsonProperty("BooleanValue") boolean booleanValue; - @ElementName( "StringValue" ) - @SerializedName( "StringValue" ) - @JsonProperty( "StringValue" ) + @ElementName("StringValue") + @SerializedName("StringValue") + @JsonProperty("StringValue") String stringValue; - @ElementName( "BinaryValue" ) - @SerializedName( "BinaryValue" ) - @JsonProperty( "BinaryValue" ) + @ElementName("BinaryValue") + @SerializedName("BinaryValue") + @JsonProperty("BinaryValue") byte[] binaryValue; - @ElementName( "GuidValue" ) - @SerializedName( "GuidValue" ) - @JsonProperty( "GuidValue" ) + @ElementName("GuidValue") + @SerializedName("GuidValue") + @JsonProperty("GuidValue") UUID guidValue; - @ElementName( "TimeOfDayValue" ) - @SerializedName( "TimeOfDayValue" ) - @JsonProperty( "TimeOfDayValue" ) + @ElementName("TimeOfDayValue") + @SerializedName("TimeOfDayValue") + @JsonProperty("TimeOfDayValue") LocalTime timeOfDayValue; - @ElementName( "DateValue" ) - @SerializedName( "DateValue" ) - @JsonProperty( "DateValue" ) + @ElementName("DateValue") + @SerializedName("DateValue") + @JsonProperty("DateValue") LocalDate dateValue; - @ElementName( "DateTimeOffsetValue" ) - @SerializedName( "DateTimeOffsetValue" ) - @JsonProperty( "DateTimeOffsetValue" ) + @ElementName("DateTimeOffsetValue") + @SerializedName("DateTimeOffsetValue") + @JsonProperty("DateTimeOffsetValue") OffsetDateTime dateTimeOffsetValue; // https://docs.oasis-open.org/odata/odata-json-format/v4.01/csprd06/odata-json-format-v4.01-csprd06.html#sec_PrimitiveValue private static final String PAYLOAD_ODATA_REFERENCE = """ - {\ - "@odata.type":"#TestEntity",\ - "ByteValue":255,\ - "SByteValue":-128,\ - "Int16Value":1,\ - "Int32Value":-1234,\ - "Int64Value":1234567890,\ - "SingleValue":1234.5677,\ - "DoubleValue":1234.5678,\ - "DecimalValue":110,\ - "BooleanValue":false,\ - "StringValue":"test",\ - "BinaryValue":"AQID",\ - "GuidValue":"00000000-1111-2222-3333-444444444444",\ - "TimeOfDayValue":"12:00:00",\ - "DateValue":"1999-03-14",\ - "DateTimeOffsetValue":"1999-03-14T00:00:00Z",\ - "GeographyPoint":{"type":"Point","coordinates":[142.1,64.1]}\ - }\ - """; + {\ + "@odata.type":"#TestEntity",\ + "ByteValue":255,\ + "SByteValue":-128,\ + "Int16Value":1,\ + "Int32Value":-1234,\ + "Int64Value":1234567890,\ + "SingleValue":1234.5677,\ + "DoubleValue":1234.5678,\ + "DecimalValue":110,\ + "BooleanValue":false,\ + "StringValue":"test",\ + "BinaryValue":"AQID",\ + "GuidValue":"00000000-1111-2222-3333-444444444444",\ + "TimeOfDayValue":"12:00:00",\ + "DateValue":"1999-03-14",\ + "DateTimeOffsetValue":"1999-03-14T00:00:00Z",\ + "GeographyPoint":{"type":"Point","coordinates":[142.1,64.1]}\ + }\ + """; + + private static final String PAYLOAD_ODATA_REFERENCE_BASE64URL = + PAYLOAD_ODATA_REFERENCE.replace("\"BinaryValue\":\"AQID\"", "\"BinaryValue\":\"-__v\""); + + private static final String PAYLOAD_ODATA_REFERENCE_MIXED_BASE64_ALPHABET = + PAYLOAD_ODATA_REFERENCE.replace("\"BinaryValue\":\"AQID\"", "\"BinaryValue\":\"+__v\""); } @Test - void testBinaryFieldParsingFromResponsePayload() - { + void testBinaryFieldParsingFromResponsePayload() { final ODataRequestResultGeneric result = mockRequestResult(ReferenceObject.PAYLOAD_ODATA_REFERENCE); final ReferenceObject referenceResult = result.as(ReferenceObject.class); Objects.requireNonNull(referenceResult); - assertThat(referenceResult.getBinaryValue()).isEqualTo(new byte[] { 1, 2, 3 }); + assertThat(referenceResult.getBinaryValue()).isEqualTo(new byte[]{1, 2, 3}); final String ser = - new CreateRequestBuilder<>("/", referenceResult, "EntityCollection").toRequest().getSerializedEntity(); + new CreateRequestBuilder<>("/", referenceResult, "EntityCollection").toRequest().getSerializedEntity(); assertThat(ser).isEqualTo(ReferenceObject.PAYLOAD_ODATA_REFERENCE); } @Test - void testCustomFieldParsingFromResponsePayload() - { + void testBinaryFieldParsingFromBase64UrlResponsePayload() { + final ODataRequestResultGeneric result = mockRequestResult(ReferenceObject.PAYLOAD_ODATA_REFERENCE_BASE64URL); + final ReferenceObject referenceResult = result.as(ReferenceObject.class); + + Objects.requireNonNull(referenceResult); + assertThat(referenceResult.getBinaryValue()).isEqualTo(new byte[]{(byte) 0xfb, (byte) 0xff, (byte) 0xef}); + + final String ser = + new CreateRequestBuilder<>("/", referenceResult, "EntityCollection").toRequest().getSerializedEntity(); + assertThat(ser).contains("\"BinaryValue\":\"+//v\""); + } + + @Test + void testCustomFieldParsingFromResponsePayload() { final ODataRequestResultGeneric result = mockRequestResult(ReferenceObject.PAYLOAD_ODATA_REFERENCE); final ReferenceObject referenceResult = result.as(ReferenceObject.class); Objects.requireNonNull(referenceResult); assertThat(referenceResult.getCustomFieldNames()).containsExactly("GeographyPoint"); - assertThat(referenceResult. getCustomField("GeographyPoint")).isInstanceOf(Map.class); + assertThat(referenceResult.getCustomField("GeographyPoint")).isInstanceOf(Map.class); + } + + @Test + void testBinaryFieldParsingFromMixedBase64AlphabetFails() { + final ODataRequestResultGeneric result = + mockRequestResult(ReferenceObject.PAYLOAD_ODATA_REFERENCE_MIXED_BASE64_ALPHABET); + final ReferenceObject referenceResult = result.as(ReferenceObject.class); + + Objects.requireNonNull(referenceResult); + assertThat(referenceResult.getBinaryValue()).isNull(); } @SneakyThrows - private static ODataRequestResultGeneric mockRequestResult( final String payload ) - { + private static ODataRequestResultGeneric mockRequestResult(final String payload) { final ODataRequestGeneric request = mock(ODataRequestGeneric.class); when(request.getProtocol()).thenReturn(ODataProtocol.V4); From 531a8281059bcc3567e1f65060e6d2af7dba5474 Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Mon, 9 Mar 2026 12:04:42 +0200 Subject: [PATCH 02/12] formatting --- .../DefaultSdkGroceryStoreService.java | 159 ++++++------------ .../adapter/ODataGenericConverter.java | 61 +++---- 2 files changed, 81 insertions(+), 139 deletions(-) diff --git a/datamodel/odata-v4/odata-v4-api-sample/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/sample/services/DefaultSdkGroceryStoreService.java b/datamodel/odata-v4/odata-v4-api-sample/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/sample/services/DefaultSdkGroceryStoreService.java index caee3d90b..3c6a414ba 100644 --- a/datamodel/odata-v4/odata-v4-api-sample/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/sample/services/DefaultSdkGroceryStoreService.java +++ b/datamodel/odata-v4/odata-v4-api-sample/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/sample/services/DefaultSdkGroceryStoreService.java @@ -36,8 +36,7 @@ * * */ -public class DefaultSdkGroceryStoreService implements ServiceWithNavigableEntities, SdkGroceryStoreService -{ +public class DefaultSdkGroceryStoreService implements ServiceWithNavigableEntities, SdkGroceryStoreService { @Nonnull @Getter @@ -47,8 +46,7 @@ public class DefaultSdkGroceryStoreService implements ServiceWithNavigableEntiti * Creates a service using {@link SdkGroceryStoreService#DEFAULT_SERVICE_PATH} to send the requests. * */ - public DefaultSdkGroceryStoreService() - { + public DefaultSdkGroceryStoreService() { servicePath = SdkGroceryStoreService.DEFAULT_SERVICE_PATH; } @@ -58,43 +56,37 @@ public DefaultSdkGroceryStoreService() * Used by the fluent {@link #withServicePath(String)} method. * */ - private DefaultSdkGroceryStoreService( @Nonnull final String servicePath ) - { + private DefaultSdkGroceryStoreService(@Nonnull final String servicePath) { this.servicePath = servicePath; } @Override @Nonnull - public DefaultSdkGroceryStoreService withServicePath( @Nonnull final String servicePath ) - { + public DefaultSdkGroceryStoreService withServicePath(@Nonnull final String servicePath) { return new DefaultSdkGroceryStoreService(servicePath); } @Override @Nonnull - public BatchRequestBuilder batch() - { + public BatchRequestBuilder batch() { return new BatchRequestBuilder(servicePath); } @Override @Nonnull - public GetAllRequestBuilder getAllCustomers() - { + public GetAllRequestBuilder getAllCustomers() { return new GetAllRequestBuilder(servicePath, Customer.class, "Customers"); } @Override @Nonnull - public CountRequestBuilder countCustomers() - { + public CountRequestBuilder countCustomers() { return new CountRequestBuilder(servicePath, Customer.class, "Customers"); } @Override @Nonnull - public GetByKeyRequestBuilder getCustomersByKey( final Integer id ) - { + public GetByKeyRequestBuilder getCustomersByKey(final Integer id) { final Map key = new HashMap(); key.put("Id", id); return new GetByKeyRequestBuilder(servicePath, Customer.class, key, "Customers"); @@ -102,43 +94,37 @@ public GetByKeyRequestBuilder getCustomersByKey( final Integer id ) @Override @Nonnull - public CreateRequestBuilder createCustomers( @Nonnull final Customer customer ) - { + public CreateRequestBuilder createCustomers(@Nonnull final Customer customer) { return new CreateRequestBuilder(servicePath, customer, "Customers"); } @Override @Nonnull - public UpdateRequestBuilder updateCustomers( @Nonnull final Customer customer ) - { + public UpdateRequestBuilder updateCustomers(@Nonnull final Customer customer) { return new UpdateRequestBuilder(servicePath, customer, "Customers"); } @Override @Nonnull - public DeleteRequestBuilder deleteCustomers( @Nonnull final Customer customer ) - { + public DeleteRequestBuilder deleteCustomers(@Nonnull final Customer customer) { return new DeleteRequestBuilder(servicePath, customer, "Customers"); } @Override @Nonnull - public GetAllRequestBuilder getAllProducts() - { + public GetAllRequestBuilder getAllProducts() { return new GetAllRequestBuilder(servicePath, Product.class, "Products"); } @Override @Nonnull - public CountRequestBuilder countProducts() - { + public CountRequestBuilder countProducts() { return new CountRequestBuilder(servicePath, Product.class, "Products"); } @Override @Nonnull - public GetByKeyRequestBuilder getProductsByKey( final Integer id ) - { + public GetByKeyRequestBuilder getProductsByKey(final Integer id) { final Map key = new HashMap(); key.put("Id", id); return new GetByKeyRequestBuilder(servicePath, Product.class, key, "Products"); @@ -146,43 +132,37 @@ public GetByKeyRequestBuilder getProductsByKey( final Integer id ) @Override @Nonnull - public CreateRequestBuilder createProducts( @Nonnull final Product product ) - { + public CreateRequestBuilder createProducts(@Nonnull final Product product) { return new CreateRequestBuilder(servicePath, product, "Products"); } @Override @Nonnull - public UpdateRequestBuilder updateProducts( @Nonnull final Product product ) - { + public UpdateRequestBuilder updateProducts(@Nonnull final Product product) { return new UpdateRequestBuilder(servicePath, product, "Products"); } @Override @Nonnull - public DeleteRequestBuilder deleteProducts( @Nonnull final Product product ) - { + public DeleteRequestBuilder deleteProducts(@Nonnull final Product product) { return new DeleteRequestBuilder(servicePath, product, "Products"); } @Override @Nonnull - public GetAllRequestBuilder getAllReceipts() - { + public GetAllRequestBuilder getAllReceipts() { return new GetAllRequestBuilder(servicePath, Receipt.class, "Receipts"); } @Override @Nonnull - public CountRequestBuilder countReceipts() - { + public CountRequestBuilder countReceipts() { return new CountRequestBuilder(servicePath, Receipt.class, "Receipts"); } @Override @Nonnull - public GetByKeyRequestBuilder getReceiptsByKey( final Integer id ) - { + public GetByKeyRequestBuilder getReceiptsByKey(final Integer id) { final Map key = new HashMap(); key.put("Id", id); return new GetByKeyRequestBuilder(servicePath, Receipt.class, key, "Receipts"); @@ -190,43 +170,37 @@ public GetByKeyRequestBuilder getReceiptsByKey( final Integer id ) @Override @Nonnull - public CreateRequestBuilder createReceipts( @Nonnull final Receipt receipt ) - { + public CreateRequestBuilder createReceipts(@Nonnull final Receipt receipt) { return new CreateRequestBuilder(servicePath, receipt, "Receipts"); } @Override @Nonnull - public UpdateRequestBuilder updateReceipts( @Nonnull final Receipt receipt ) - { + public UpdateRequestBuilder updateReceipts(@Nonnull final Receipt receipt) { return new UpdateRequestBuilder(servicePath, receipt, "Receipts"); } @Override @Nonnull - public DeleteRequestBuilder deleteReceipts( @Nonnull final Receipt receipt ) - { + public DeleteRequestBuilder deleteReceipts(@Nonnull final Receipt receipt) { return new DeleteRequestBuilder(servicePath, receipt, "Receipts"); } @Override @Nonnull - public GetAllRequestBuilder
getAllAddresses() - { + public GetAllRequestBuilder
getAllAddresses() { return new GetAllRequestBuilder
(servicePath, Address.class, "Addresses"); } @Override @Nonnull - public CountRequestBuilder
countAddresses() - { + public CountRequestBuilder
countAddresses() { return new CountRequestBuilder
(servicePath, Address.class, "Addresses"); } @Override @Nonnull - public GetByKeyRequestBuilder
getAddressesByKey( final Integer id ) - { + public GetByKeyRequestBuilder
getAddressesByKey(final Integer id) { final Map key = new HashMap(); key.put("Id", id); return new GetByKeyRequestBuilder
(servicePath, Address.class, key, "Addresses"); @@ -234,43 +208,37 @@ public GetByKeyRequestBuilder
getAddressesByKey( final Integer id ) @Override @Nonnull - public CreateRequestBuilder
createAddresses( @Nonnull final Address address ) - { + public CreateRequestBuilder
createAddresses(@Nonnull final Address address) { return new CreateRequestBuilder
(servicePath, address, "Addresses"); } @Override @Nonnull - public UpdateRequestBuilder
updateAddresses( @Nonnull final Address address ) - { + public UpdateRequestBuilder
updateAddresses(@Nonnull final Address address) { return new UpdateRequestBuilder
(servicePath, address, "Addresses"); } @Override @Nonnull - public DeleteRequestBuilder
deleteAddresses( @Nonnull final Address address ) - { + public DeleteRequestBuilder
deleteAddresses(@Nonnull final Address address) { return new DeleteRequestBuilder
(servicePath, address, "Addresses"); } @Override @Nonnull - public GetAllRequestBuilder getAllShelves() - { + public GetAllRequestBuilder getAllShelves() { return new GetAllRequestBuilder(servicePath, Shelf.class, "Shelves"); } @Override @Nonnull - public CountRequestBuilder countShelves() - { + public CountRequestBuilder countShelves() { return new CountRequestBuilder(servicePath, Shelf.class, "Shelves"); } @Override @Nonnull - public GetByKeyRequestBuilder getShelvesByKey( final Integer id ) - { + public GetByKeyRequestBuilder getShelvesByKey(final Integer id) { final Map key = new HashMap(); key.put("Id", id); return new GetByKeyRequestBuilder(servicePath, Shelf.class, key, "Shelves"); @@ -278,43 +246,37 @@ public GetByKeyRequestBuilder getShelvesByKey( final Integer id ) @Override @Nonnull - public CreateRequestBuilder createShelves( @Nonnull final Shelf shelf ) - { + public CreateRequestBuilder createShelves(@Nonnull final Shelf shelf) { return new CreateRequestBuilder(servicePath, shelf, "Shelves"); } @Override @Nonnull - public UpdateRequestBuilder updateShelves( @Nonnull final Shelf shelf ) - { + public UpdateRequestBuilder updateShelves(@Nonnull final Shelf shelf) { return new UpdateRequestBuilder(servicePath, shelf, "Shelves"); } @Override @Nonnull - public DeleteRequestBuilder deleteShelves( @Nonnull final Shelf shelf ) - { + public DeleteRequestBuilder deleteShelves(@Nonnull final Shelf shelf) { return new DeleteRequestBuilder(servicePath, shelf, "Shelves"); } @Override @Nonnull - public GetAllRequestBuilder getAllShopFloorShelves() - { + public GetAllRequestBuilder getAllShopFloorShelves() { return new GetAllRequestBuilder(servicePath, Shelf.class, "ShopFloorShelves"); } @Override @Nonnull - public CountRequestBuilder countShopFloorShelves() - { + public CountRequestBuilder countShopFloorShelves() { return new CountRequestBuilder(servicePath, Shelf.class, "ShopFloorShelves"); } @Override @Nonnull - public GetByKeyRequestBuilder getShopFloorShelvesByKey( final Integer id ) - { + public GetByKeyRequestBuilder getShopFloorShelvesByKey(final Integer id) { final Map key = new HashMap(); key.put("Id", id); return new GetByKeyRequestBuilder(servicePath, Shelf.class, key, "ShopFloorShelves"); @@ -322,43 +284,37 @@ public GetByKeyRequestBuilder getShopFloorShelvesByKey( final Integer id @Override @Nonnull - public CreateRequestBuilder createShopFloorShelves( @Nonnull final Shelf shelf ) - { + public CreateRequestBuilder createShopFloorShelves(@Nonnull final Shelf shelf) { return new CreateRequestBuilder(servicePath, shelf, "ShopFloorShelves"); } @Override @Nonnull - public UpdateRequestBuilder updateShopFloorShelves( @Nonnull final Shelf shelf ) - { + public UpdateRequestBuilder updateShopFloorShelves(@Nonnull final Shelf shelf) { return new UpdateRequestBuilder(servicePath, shelf, "ShopFloorShelves"); } @Override @Nonnull - public DeleteRequestBuilder deleteShopFloorShelves( @Nonnull final Shelf shelf ) - { + public DeleteRequestBuilder deleteShopFloorShelves(@Nonnull final Shelf shelf) { return new DeleteRequestBuilder(servicePath, shelf, "ShopFloorShelves"); } @Override @Nonnull - public GetAllRequestBuilder getAllStorageShelves() - { + public GetAllRequestBuilder getAllStorageShelves() { return new GetAllRequestBuilder(servicePath, Shelf.class, "StorageShelves"); } @Override @Nonnull - public CountRequestBuilder countStorageShelves() - { + public CountRequestBuilder countStorageShelves() { return new CountRequestBuilder(servicePath, Shelf.class, "StorageShelves"); } @Override @Nonnull - public GetByKeyRequestBuilder getStorageShelvesByKey( final Integer id ) - { + public GetByKeyRequestBuilder getStorageShelvesByKey(final Integer id) { final Map key = new HashMap(); key.put("Id", id); return new GetByKeyRequestBuilder(servicePath, Shelf.class, key, "StorageShelves"); @@ -366,43 +322,37 @@ public GetByKeyRequestBuilder getStorageShelvesByKey( final Integer id ) @Override @Nonnull - public CreateRequestBuilder createStorageShelves( @Nonnull final Shelf shelf ) - { + public CreateRequestBuilder createStorageShelves(@Nonnull final Shelf shelf) { return new CreateRequestBuilder(servicePath, shelf, "StorageShelves"); } @Override @Nonnull - public UpdateRequestBuilder updateStorageShelves( @Nonnull final Shelf shelf ) - { + public UpdateRequestBuilder updateStorageShelves(@Nonnull final Shelf shelf) { return new UpdateRequestBuilder(servicePath, shelf, "StorageShelves"); } @Override @Nonnull - public DeleteRequestBuilder deleteStorageShelves( @Nonnull final Shelf shelf ) - { + public DeleteRequestBuilder deleteStorageShelves(@Nonnull final Shelf shelf) { return new DeleteRequestBuilder(servicePath, shelf, "StorageShelves"); } @Override @Nonnull - public GetAllRequestBuilder getAllOpeningHours() - { + public GetAllRequestBuilder getAllOpeningHours() { return new GetAllRequestBuilder(servicePath, OpeningHours.class, "OpeningHours"); } @Override @Nonnull - public CountRequestBuilder countOpeningHours() - { + public CountRequestBuilder countOpeningHours() { return new CountRequestBuilder(servicePath, OpeningHours.class, "OpeningHours"); } @Override @Nonnull - public GetByKeyRequestBuilder getOpeningHoursByKey( final Integer id ) - { + public GetByKeyRequestBuilder getOpeningHoursByKey(final Integer id) { final Map key = new HashMap(); key.put("Id", id); return new GetByKeyRequestBuilder(servicePath, OpeningHours.class, key, "OpeningHours"); @@ -410,22 +360,19 @@ public GetByKeyRequestBuilder getOpeningHoursByKey( final Integer @Override @Nonnull - public CreateRequestBuilder createOpeningHours( @Nonnull final OpeningHours openingHours ) - { + public CreateRequestBuilder createOpeningHours(@Nonnull final OpeningHours openingHours) { return new CreateRequestBuilder(servicePath, openingHours, "OpeningHours"); } @Override @Nonnull - public UpdateRequestBuilder updateOpeningHours( @Nonnull final OpeningHours openingHours ) - { + public UpdateRequestBuilder updateOpeningHours(@Nonnull final OpeningHours openingHours) { return new UpdateRequestBuilder(servicePath, openingHours, "OpeningHours"); } @Override @Nonnull - public DeleteRequestBuilder deleteOpeningHours( @Nonnull final OpeningHours openingHours ) - { + public DeleteRequestBuilder deleteOpeningHours(@Nonnull final OpeningHours openingHours) { return new DeleteRequestBuilder(servicePath, openingHours, "OpeningHours"); } diff --git a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java index b64638691..9a40ab769 100644 --- a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java +++ b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java @@ -22,51 +22,48 @@ /** * Generic fluent helper converters for String based OData V4 primitives. * - * @param - * The Java type to which conversion happens. + * @param The Java type to which conversion happens. */ -@RequiredArgsConstructor( access = AccessLevel.PRIVATE ) -final class ODataGenericConverter extends AbstractTypeConverter -{ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +final class ODataGenericConverter extends AbstractTypeConverter { private static final ODataGenericConverter LOCAL_DATE = - new ODataGenericConverter<>( - LocalDate.class, - o -> o.format(DateTimeFormatter.ISO_LOCAL_DATE), - s -> LocalDate.parse(s, DateTimeFormatter.ISO_LOCAL_DATE)); + new ODataGenericConverter<>( + LocalDate.class, + o -> o.format(DateTimeFormatter.ISO_LOCAL_DATE), + s -> LocalDate.parse(s, DateTimeFormatter.ISO_LOCAL_DATE)); private static final ODataGenericConverter LOCAL_TIME = - new ODataGenericConverter<>( - LocalTime.class, - o -> o.format(DateTimeFormatter.ISO_LOCAL_TIME), - s -> LocalTime.parse(s, DateTimeFormatter.ISO_LOCAL_TIME)); + new ODataGenericConverter<>( + LocalTime.class, + o -> o.format(DateTimeFormatter.ISO_LOCAL_TIME), + s -> LocalTime.parse(s, DateTimeFormatter.ISO_LOCAL_TIME)); private static final ODataGenericConverter OFFSET_DATE_TIME = - new ODataGenericConverter<>( - OffsetDateTime.class, - o -> o.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), - s -> OffsetDateTime.parse(s, DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + new ODataGenericConverter<>( + OffsetDateTime.class, + o -> o.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), + s -> OffsetDateTime.parse(s, DateTimeFormatter.ISO_OFFSET_DATE_TIME)); private static final ODataGenericConverter GUID = - new ODataGenericConverter<>(UUID.class, UUID::toString, UUID::fromString); + new ODataGenericConverter<>(UUID.class, UUID::toString, UUID::fromString); private static final ODataGenericConverter DURATION = - new ODataGenericConverter<>(Duration.class, Duration::toString, Duration::parse); + new ODataGenericConverter<>(Duration.class, Duration::toString, Duration::parse); private static final ODataGenericConverter BINARY = - new ODataGenericConverter<>( - byte[].class, - Base64.getEncoder()::encodeToString, - ODataGenericConverter::decodeBinary); + new ODataGenericConverter<>( + byte[].class, + Base64.getEncoder()::encodeToString, + ODataGenericConverter::decodeBinary); @Nonnull - private static byte[] decodeBinary( @Nonnull final String value ) - { + private static byte[] decodeBinary(@Nonnull final String value) { final boolean containsBase64Alphabet = value.indexOf('+') >= 0 || value.indexOf('/') >= 0; final boolean containsBase64UrlAlphabet = value.indexOf('-') >= 0 || value.indexOf('_') >= 0; - if( containsBase64Alphabet && containsBase64UrlAlphabet ) { + if (containsBase64Alphabet && containsBase64UrlAlphabet) { throw new IllegalArgumentException( - "Invalid binary value: mixed Base64 and Base64URL alphabets are not allowed."); + "Invalid binary value: mixed Base64 and Base64URL alphabets are not allowed."); } final Base64.Decoder decoder = containsBase64UrlAlphabet ? Base64.getUrlDecoder() : Base64.getDecoder(); @@ -74,13 +71,13 @@ private static byte[] decodeBinary( @Nonnull final String value ) } private static final ODataGenericConverter STRING = - new ODataGenericConverter<>(String.class, Function.identity(), Function.identity()); + new ODataGenericConverter<>(String.class, Function.identity(), Function.identity()); /** * Array of OData value converters for primitive types. */ public static final ODataGenericConverter[] DEFAULT_CONVERTERS = - { LOCAL_DATE, LOCAL_TIME, OFFSET_DATE_TIME, GUID, DURATION, BINARY, STRING }; + {LOCAL_DATE, LOCAL_TIME, OFFSET_DATE_TIME, GUID, DURATION, BINARY, STRING}; @Getter private final Class type; @@ -93,15 +90,13 @@ private static byte[] decodeBinary( @Nonnull final String value ) @Nonnull @Override - public ConvertedObject toDomainNonNull( @Nonnull final JavaT object ) - { + public ConvertedObject toDomainNonNull(@Nonnull final JavaT object) { return ConvertedObject.of(serializer.apply(object)); } @Nonnull @Override - public ConvertedObject fromDomainNonNull( @Nonnull final String domainObject ) - { + public ConvertedObject fromDomainNonNull(@Nonnull final String domainObject) { final Try maybe = Try.of(() -> deserializer.apply(domainObject)); return maybe.map(ConvertedObject::of).getOrElse(ConvertedObject::ofNotConvertible); } From fd1ed08f2a1e42aa8759ea0165cf9916a76387fe Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Mon, 9 Mar 2026 12:17:01 +0200 Subject: [PATCH 03/12] formatting again --- .../adapter/ODataGenericConverter.java | 61 +++--- .../odatav4/core/FieldSerializationTest.java | 175 +++++++++--------- 2 files changed, 124 insertions(+), 112 deletions(-) diff --git a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java index 9a40ab769..b64638691 100644 --- a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java +++ b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java @@ -22,48 +22,51 @@ /** * Generic fluent helper converters for String based OData V4 primitives. * - * @param The Java type to which conversion happens. + * @param + * The Java type to which conversion happens. */ -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -final class ODataGenericConverter extends AbstractTypeConverter { +@RequiredArgsConstructor( access = AccessLevel.PRIVATE ) +final class ODataGenericConverter extends AbstractTypeConverter +{ private static final ODataGenericConverter LOCAL_DATE = - new ODataGenericConverter<>( - LocalDate.class, - o -> o.format(DateTimeFormatter.ISO_LOCAL_DATE), - s -> LocalDate.parse(s, DateTimeFormatter.ISO_LOCAL_DATE)); + new ODataGenericConverter<>( + LocalDate.class, + o -> o.format(DateTimeFormatter.ISO_LOCAL_DATE), + s -> LocalDate.parse(s, DateTimeFormatter.ISO_LOCAL_DATE)); private static final ODataGenericConverter LOCAL_TIME = - new ODataGenericConverter<>( - LocalTime.class, - o -> o.format(DateTimeFormatter.ISO_LOCAL_TIME), - s -> LocalTime.parse(s, DateTimeFormatter.ISO_LOCAL_TIME)); + new ODataGenericConverter<>( + LocalTime.class, + o -> o.format(DateTimeFormatter.ISO_LOCAL_TIME), + s -> LocalTime.parse(s, DateTimeFormatter.ISO_LOCAL_TIME)); private static final ODataGenericConverter OFFSET_DATE_TIME = - new ODataGenericConverter<>( - OffsetDateTime.class, - o -> o.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), - s -> OffsetDateTime.parse(s, DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + new ODataGenericConverter<>( + OffsetDateTime.class, + o -> o.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), + s -> OffsetDateTime.parse(s, DateTimeFormatter.ISO_OFFSET_DATE_TIME)); private static final ODataGenericConverter GUID = - new ODataGenericConverter<>(UUID.class, UUID::toString, UUID::fromString); + new ODataGenericConverter<>(UUID.class, UUID::toString, UUID::fromString); private static final ODataGenericConverter DURATION = - new ODataGenericConverter<>(Duration.class, Duration::toString, Duration::parse); + new ODataGenericConverter<>(Duration.class, Duration::toString, Duration::parse); private static final ODataGenericConverter BINARY = - new ODataGenericConverter<>( - byte[].class, - Base64.getEncoder()::encodeToString, - ODataGenericConverter::decodeBinary); + new ODataGenericConverter<>( + byte[].class, + Base64.getEncoder()::encodeToString, + ODataGenericConverter::decodeBinary); @Nonnull - private static byte[] decodeBinary(@Nonnull final String value) { + private static byte[] decodeBinary( @Nonnull final String value ) + { final boolean containsBase64Alphabet = value.indexOf('+') >= 0 || value.indexOf('/') >= 0; final boolean containsBase64UrlAlphabet = value.indexOf('-') >= 0 || value.indexOf('_') >= 0; - if (containsBase64Alphabet && containsBase64UrlAlphabet) { + if( containsBase64Alphabet && containsBase64UrlAlphabet ) { throw new IllegalArgumentException( - "Invalid binary value: mixed Base64 and Base64URL alphabets are not allowed."); + "Invalid binary value: mixed Base64 and Base64URL alphabets are not allowed."); } final Base64.Decoder decoder = containsBase64UrlAlphabet ? Base64.getUrlDecoder() : Base64.getDecoder(); @@ -71,13 +74,13 @@ private static byte[] decodeBinary(@Nonnull final String value) { } private static final ODataGenericConverter STRING = - new ODataGenericConverter<>(String.class, Function.identity(), Function.identity()); + new ODataGenericConverter<>(String.class, Function.identity(), Function.identity()); /** * Array of OData value converters for primitive types. */ public static final ODataGenericConverter[] DEFAULT_CONVERTERS = - {LOCAL_DATE, LOCAL_TIME, OFFSET_DATE_TIME, GUID, DURATION, BINARY, STRING}; + { LOCAL_DATE, LOCAL_TIME, OFFSET_DATE_TIME, GUID, DURATION, BINARY, STRING }; @Getter private final Class type; @@ -90,13 +93,15 @@ private static byte[] decodeBinary(@Nonnull final String value) { @Nonnull @Override - public ConvertedObject toDomainNonNull(@Nonnull final JavaT object) { + public ConvertedObject toDomainNonNull( @Nonnull final JavaT object ) + { return ConvertedObject.of(serializer.apply(object)); } @Nonnull @Override - public ConvertedObject fromDomainNonNull(@Nonnull final String domainObject) { + public ConvertedObject fromDomainNonNull( @Nonnull final String domainObject ) + { final Try maybe = Try.of(() -> deserializer.apply(domainObject)); return maybe.map(ConvertedObject::of).getOrElse(ConvertedObject::ofNotConvertible); } diff --git a/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java b/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java index ed17f494e..00dbcfb2b 100644 --- a/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java +++ b/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java @@ -41,15 +41,17 @@ import lombok.NoArgsConstructor; import lombok.SneakyThrows; -class FieldSerializationTest { +class FieldSerializationTest +{ @Data @NoArgsConstructor @AllArgsConstructor - @EqualsAndHashCode(doNotUseGetters = true, callSuper = true) - @JsonAdapter(GsonVdmAdapterFactory.class) - @JsonSerialize(using = JacksonVdmObjectSerializer.class) - @JsonDeserialize(using = JacksonVdmObjectDeserializer.class) - public static class ReferenceObject extends VdmEntity { + @EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) + @JsonAdapter( GsonVdmAdapterFactory.class ) + @JsonSerialize( using = JacksonVdmObjectSerializer.class ) + @JsonDeserialize( using = JacksonVdmObjectDeserializer.class ) + public static class ReferenceObject extends VdmEntity + { @Getter private final String odataType = "TestEntity"; @@ -59,152 +61,156 @@ public static class ReferenceObject extends VdmEntity { @Getter private final Class type = ReferenceObject.class; - @ElementName("ByteValue") - @SerializedName("ByteValue") - @JsonProperty("ByteValue") + @ElementName( "ByteValue" ) + @SerializedName( "ByteValue" ) + @JsonProperty( "ByteValue" ) short byteValue; - @ElementName("SByteValue") - @SerializedName("SByteValue") - @JsonProperty("SByteValue") + @ElementName( "SByteValue" ) + @SerializedName( "SByteValue" ) + @JsonProperty( "SByteValue" ) Byte sByteValue; - @ElementName("Int16Value") - @SerializedName("Int16Value") - @JsonProperty("Int16Value") + @ElementName( "Int16Value" ) + @SerializedName( "Int16Value" ) + @JsonProperty( "Int16Value" ) short int16Value; - @ElementName("Int32Value") - @SerializedName("Int32Value") - @JsonProperty("Int32Value") + @ElementName( "Int32Value" ) + @SerializedName( "Int32Value" ) + @JsonProperty( "Int32Value" ) int int32Value; - @ElementName("Int64Value") - @SerializedName("Int64Value") - @JsonProperty("Int64Value") + @ElementName( "Int64Value" ) + @SerializedName( "Int64Value" ) + @JsonProperty( "Int64Value" ) long int64Value; - @ElementName("SingleValue") - @SerializedName("SingleValue") - @JsonProperty("SingleValue") + @ElementName( "SingleValue" ) + @SerializedName( "SingleValue" ) + @JsonProperty( "SingleValue" ) float singleValue; - @ElementName("DoubleValue") - @SerializedName("DoubleValue") - @JsonProperty("DoubleValue") + @ElementName( "DoubleValue" ) + @SerializedName( "DoubleValue" ) + @JsonProperty( "DoubleValue" ) double doubleValue; - @ElementName("DecimalValue") - @SerializedName("DecimalValue") - @JsonProperty("DecimalValue") + @ElementName( "DecimalValue" ) + @SerializedName( "DecimalValue" ) + @JsonProperty( "DecimalValue" ) BigDecimal decimalValue; - @ElementName("BooleanValue") - @SerializedName("BooleanValue") - @JsonProperty("BooleanValue") + @ElementName( "BooleanValue" ) + @SerializedName( "BooleanValue" ) + @JsonProperty( "BooleanValue" ) boolean booleanValue; - @ElementName("StringValue") - @SerializedName("StringValue") - @JsonProperty("StringValue") + @ElementName( "StringValue" ) + @SerializedName( "StringValue" ) + @JsonProperty( "StringValue" ) String stringValue; - @ElementName("BinaryValue") - @SerializedName("BinaryValue") - @JsonProperty("BinaryValue") + @ElementName( "BinaryValue" ) + @SerializedName( "BinaryValue" ) + @JsonProperty( "BinaryValue" ) byte[] binaryValue; - @ElementName("GuidValue") - @SerializedName("GuidValue") - @JsonProperty("GuidValue") + @ElementName( "GuidValue" ) + @SerializedName( "GuidValue" ) + @JsonProperty( "GuidValue" ) UUID guidValue; - @ElementName("TimeOfDayValue") - @SerializedName("TimeOfDayValue") - @JsonProperty("TimeOfDayValue") + @ElementName( "TimeOfDayValue" ) + @SerializedName( "TimeOfDayValue" ) + @JsonProperty( "TimeOfDayValue" ) LocalTime timeOfDayValue; - @ElementName("DateValue") - @SerializedName("DateValue") - @JsonProperty("DateValue") + @ElementName( "DateValue" ) + @SerializedName( "DateValue" ) + @JsonProperty( "DateValue" ) LocalDate dateValue; - @ElementName("DateTimeOffsetValue") - @SerializedName("DateTimeOffsetValue") - @JsonProperty("DateTimeOffsetValue") + @ElementName( "DateTimeOffsetValue" ) + @SerializedName( "DateTimeOffsetValue" ) + @JsonProperty( "DateTimeOffsetValue" ) OffsetDateTime dateTimeOffsetValue; // https://docs.oasis-open.org/odata/odata-json-format/v4.01/csprd06/odata-json-format-v4.01-csprd06.html#sec_PrimitiveValue private static final String PAYLOAD_ODATA_REFERENCE = """ - {\ - "@odata.type":"#TestEntity",\ - "ByteValue":255,\ - "SByteValue":-128,\ - "Int16Value":1,\ - "Int32Value":-1234,\ - "Int64Value":1234567890,\ - "SingleValue":1234.5677,\ - "DoubleValue":1234.5678,\ - "DecimalValue":110,\ - "BooleanValue":false,\ - "StringValue":"test",\ - "BinaryValue":"AQID",\ - "GuidValue":"00000000-1111-2222-3333-444444444444",\ - "TimeOfDayValue":"12:00:00",\ - "DateValue":"1999-03-14",\ - "DateTimeOffsetValue":"1999-03-14T00:00:00Z",\ - "GeographyPoint":{"type":"Point","coordinates":[142.1,64.1]}\ - }\ - """; + {\ + "@odata.type":"#TestEntity",\ + "ByteValue":255,\ + "SByteValue":-128,\ + "Int16Value":1,\ + "Int32Value":-1234,\ + "Int64Value":1234567890,\ + "SingleValue":1234.5677,\ + "DoubleValue":1234.5678,\ + "DecimalValue":110,\ + "BooleanValue":false,\ + "StringValue":"test",\ + "BinaryValue":"AQID",\ + "GuidValue":"00000000-1111-2222-3333-444444444444",\ + "TimeOfDayValue":"12:00:00",\ + "DateValue":"1999-03-14",\ + "DateTimeOffsetValue":"1999-03-14T00:00:00Z",\ + "GeographyPoint":{"type":"Point","coordinates":[142.1,64.1]}\ + }\ + """; private static final String PAYLOAD_ODATA_REFERENCE_BASE64URL = - PAYLOAD_ODATA_REFERENCE.replace("\"BinaryValue\":\"AQID\"", "\"BinaryValue\":\"-__v\""); + PAYLOAD_ODATA_REFERENCE.replace("\"BinaryValue\":\"AQID\"", "\"BinaryValue\":\"-__v\""); private static final String PAYLOAD_ODATA_REFERENCE_MIXED_BASE64_ALPHABET = - PAYLOAD_ODATA_REFERENCE.replace("\"BinaryValue\":\"AQID\"", "\"BinaryValue\":\"+__v\""); + PAYLOAD_ODATA_REFERENCE.replace("\"BinaryValue\":\"AQID\"", "\"BinaryValue\":\"+__v\""); } @Test - void testBinaryFieldParsingFromResponsePayload() { + void testBinaryFieldParsingFromResponsePayload() + { final ODataRequestResultGeneric result = mockRequestResult(ReferenceObject.PAYLOAD_ODATA_REFERENCE); final ReferenceObject referenceResult = result.as(ReferenceObject.class); Objects.requireNonNull(referenceResult); - assertThat(referenceResult.getBinaryValue()).isEqualTo(new byte[]{1, 2, 3}); + assertThat(referenceResult.getBinaryValue()).isEqualTo(new byte[] { 1, 2, 3 }); final String ser = - new CreateRequestBuilder<>("/", referenceResult, "EntityCollection").toRequest().getSerializedEntity(); + new CreateRequestBuilder<>("/", referenceResult, "EntityCollection").toRequest().getSerializedEntity(); assertThat(ser).isEqualTo(ReferenceObject.PAYLOAD_ODATA_REFERENCE); } @Test - void testBinaryFieldParsingFromBase64UrlResponsePayload() { + void testBinaryFieldParsingFromBase64UrlResponsePayload() + { final ODataRequestResultGeneric result = mockRequestResult(ReferenceObject.PAYLOAD_ODATA_REFERENCE_BASE64URL); final ReferenceObject referenceResult = result.as(ReferenceObject.class); Objects.requireNonNull(referenceResult); - assertThat(referenceResult.getBinaryValue()).isEqualTo(new byte[]{(byte) 0xfb, (byte) 0xff, (byte) 0xef}); + assertThat(referenceResult.getBinaryValue()).isEqualTo(new byte[] { (byte) 0xfb, (byte) 0xff, (byte) 0xef }); final String ser = - new CreateRequestBuilder<>("/", referenceResult, "EntityCollection").toRequest().getSerializedEntity(); + new CreateRequestBuilder<>("/", referenceResult, "EntityCollection").toRequest().getSerializedEntity(); assertThat(ser).contains("\"BinaryValue\":\"+//v\""); } @Test - void testCustomFieldParsingFromResponsePayload() { + void testCustomFieldParsingFromResponsePayload() + { final ODataRequestResultGeneric result = mockRequestResult(ReferenceObject.PAYLOAD_ODATA_REFERENCE); final ReferenceObject referenceResult = result.as(ReferenceObject.class); Objects.requireNonNull(referenceResult); assertThat(referenceResult.getCustomFieldNames()).containsExactly("GeographyPoint"); - assertThat(referenceResult.getCustomField("GeographyPoint")).isInstanceOf(Map.class); + assertThat(referenceResult. getCustomField("GeographyPoint")).isInstanceOf(Map.class); } @Test - void testBinaryFieldParsingFromMixedBase64AlphabetFails() { + void testBinaryFieldParsingFromMixedBase64AlphabetFails() + { final ODataRequestResultGeneric result = - mockRequestResult(ReferenceObject.PAYLOAD_ODATA_REFERENCE_MIXED_BASE64_ALPHABET); + mockRequestResult(ReferenceObject.PAYLOAD_ODATA_REFERENCE_MIXED_BASE64_ALPHABET); final ReferenceObject referenceResult = result.as(ReferenceObject.class); Objects.requireNonNull(referenceResult); @@ -212,7 +218,8 @@ void testBinaryFieldParsingFromMixedBase64AlphabetFails() { } @SneakyThrows - private static ODataRequestResultGeneric mockRequestResult(final String payload) { + private static ODataRequestResultGeneric mockRequestResult( final String payload ) + { final ODataRequestGeneric request = mock(ODataRequestGeneric.class); when(request.getProtocol()).thenReturn(ODataProtocol.V4); From 81629ca0652aa53c704f87567118977e32fa1160 Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Mon, 9 Mar 2026 13:10:17 +0200 Subject: [PATCH 04/12] more formatting --- .../DefaultSdkGroceryStoreService.java | 159 ++++++++++++------ 1 file changed, 106 insertions(+), 53 deletions(-) diff --git a/datamodel/odata-v4/odata-v4-api-sample/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/sample/services/DefaultSdkGroceryStoreService.java b/datamodel/odata-v4/odata-v4-api-sample/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/sample/services/DefaultSdkGroceryStoreService.java index 3c6a414ba..caee3d90b 100644 --- a/datamodel/odata-v4/odata-v4-api-sample/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/sample/services/DefaultSdkGroceryStoreService.java +++ b/datamodel/odata-v4/odata-v4-api-sample/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/sample/services/DefaultSdkGroceryStoreService.java @@ -36,7 +36,8 @@ * * */ -public class DefaultSdkGroceryStoreService implements ServiceWithNavigableEntities, SdkGroceryStoreService { +public class DefaultSdkGroceryStoreService implements ServiceWithNavigableEntities, SdkGroceryStoreService +{ @Nonnull @Getter @@ -46,7 +47,8 @@ public class DefaultSdkGroceryStoreService implements ServiceWithNavigableEntiti * Creates a service using {@link SdkGroceryStoreService#DEFAULT_SERVICE_PATH} to send the requests. * */ - public DefaultSdkGroceryStoreService() { + public DefaultSdkGroceryStoreService() + { servicePath = SdkGroceryStoreService.DEFAULT_SERVICE_PATH; } @@ -56,37 +58,43 @@ public DefaultSdkGroceryStoreService() { * Used by the fluent {@link #withServicePath(String)} method. * */ - private DefaultSdkGroceryStoreService(@Nonnull final String servicePath) { + private DefaultSdkGroceryStoreService( @Nonnull final String servicePath ) + { this.servicePath = servicePath; } @Override @Nonnull - public DefaultSdkGroceryStoreService withServicePath(@Nonnull final String servicePath) { + public DefaultSdkGroceryStoreService withServicePath( @Nonnull final String servicePath ) + { return new DefaultSdkGroceryStoreService(servicePath); } @Override @Nonnull - public BatchRequestBuilder batch() { + public BatchRequestBuilder batch() + { return new BatchRequestBuilder(servicePath); } @Override @Nonnull - public GetAllRequestBuilder getAllCustomers() { + public GetAllRequestBuilder getAllCustomers() + { return new GetAllRequestBuilder(servicePath, Customer.class, "Customers"); } @Override @Nonnull - public CountRequestBuilder countCustomers() { + public CountRequestBuilder countCustomers() + { return new CountRequestBuilder(servicePath, Customer.class, "Customers"); } @Override @Nonnull - public GetByKeyRequestBuilder getCustomersByKey(final Integer id) { + public GetByKeyRequestBuilder getCustomersByKey( final Integer id ) + { final Map key = new HashMap(); key.put("Id", id); return new GetByKeyRequestBuilder(servicePath, Customer.class, key, "Customers"); @@ -94,37 +102,43 @@ public GetByKeyRequestBuilder getCustomersByKey(final Integer id) { @Override @Nonnull - public CreateRequestBuilder createCustomers(@Nonnull final Customer customer) { + public CreateRequestBuilder createCustomers( @Nonnull final Customer customer ) + { return new CreateRequestBuilder(servicePath, customer, "Customers"); } @Override @Nonnull - public UpdateRequestBuilder updateCustomers(@Nonnull final Customer customer) { + public UpdateRequestBuilder updateCustomers( @Nonnull final Customer customer ) + { return new UpdateRequestBuilder(servicePath, customer, "Customers"); } @Override @Nonnull - public DeleteRequestBuilder deleteCustomers(@Nonnull final Customer customer) { + public DeleteRequestBuilder deleteCustomers( @Nonnull final Customer customer ) + { return new DeleteRequestBuilder(servicePath, customer, "Customers"); } @Override @Nonnull - public GetAllRequestBuilder getAllProducts() { + public GetAllRequestBuilder getAllProducts() + { return new GetAllRequestBuilder(servicePath, Product.class, "Products"); } @Override @Nonnull - public CountRequestBuilder countProducts() { + public CountRequestBuilder countProducts() + { return new CountRequestBuilder(servicePath, Product.class, "Products"); } @Override @Nonnull - public GetByKeyRequestBuilder getProductsByKey(final Integer id) { + public GetByKeyRequestBuilder getProductsByKey( final Integer id ) + { final Map key = new HashMap(); key.put("Id", id); return new GetByKeyRequestBuilder(servicePath, Product.class, key, "Products"); @@ -132,37 +146,43 @@ public GetByKeyRequestBuilder getProductsByKey(final Integer id) { @Override @Nonnull - public CreateRequestBuilder createProducts(@Nonnull final Product product) { + public CreateRequestBuilder createProducts( @Nonnull final Product product ) + { return new CreateRequestBuilder(servicePath, product, "Products"); } @Override @Nonnull - public UpdateRequestBuilder updateProducts(@Nonnull final Product product) { + public UpdateRequestBuilder updateProducts( @Nonnull final Product product ) + { return new UpdateRequestBuilder(servicePath, product, "Products"); } @Override @Nonnull - public DeleteRequestBuilder deleteProducts(@Nonnull final Product product) { + public DeleteRequestBuilder deleteProducts( @Nonnull final Product product ) + { return new DeleteRequestBuilder(servicePath, product, "Products"); } @Override @Nonnull - public GetAllRequestBuilder getAllReceipts() { + public GetAllRequestBuilder getAllReceipts() + { return new GetAllRequestBuilder(servicePath, Receipt.class, "Receipts"); } @Override @Nonnull - public CountRequestBuilder countReceipts() { + public CountRequestBuilder countReceipts() + { return new CountRequestBuilder(servicePath, Receipt.class, "Receipts"); } @Override @Nonnull - public GetByKeyRequestBuilder getReceiptsByKey(final Integer id) { + public GetByKeyRequestBuilder getReceiptsByKey( final Integer id ) + { final Map key = new HashMap(); key.put("Id", id); return new GetByKeyRequestBuilder(servicePath, Receipt.class, key, "Receipts"); @@ -170,37 +190,43 @@ public GetByKeyRequestBuilder getReceiptsByKey(final Integer id) { @Override @Nonnull - public CreateRequestBuilder createReceipts(@Nonnull final Receipt receipt) { + public CreateRequestBuilder createReceipts( @Nonnull final Receipt receipt ) + { return new CreateRequestBuilder(servicePath, receipt, "Receipts"); } @Override @Nonnull - public UpdateRequestBuilder updateReceipts(@Nonnull final Receipt receipt) { + public UpdateRequestBuilder updateReceipts( @Nonnull final Receipt receipt ) + { return new UpdateRequestBuilder(servicePath, receipt, "Receipts"); } @Override @Nonnull - public DeleteRequestBuilder deleteReceipts(@Nonnull final Receipt receipt) { + public DeleteRequestBuilder deleteReceipts( @Nonnull final Receipt receipt ) + { return new DeleteRequestBuilder(servicePath, receipt, "Receipts"); } @Override @Nonnull - public GetAllRequestBuilder
getAllAddresses() { + public GetAllRequestBuilder
getAllAddresses() + { return new GetAllRequestBuilder
(servicePath, Address.class, "Addresses"); } @Override @Nonnull - public CountRequestBuilder
countAddresses() { + public CountRequestBuilder
countAddresses() + { return new CountRequestBuilder
(servicePath, Address.class, "Addresses"); } @Override @Nonnull - public GetByKeyRequestBuilder
getAddressesByKey(final Integer id) { + public GetByKeyRequestBuilder
getAddressesByKey( final Integer id ) + { final Map key = new HashMap(); key.put("Id", id); return new GetByKeyRequestBuilder
(servicePath, Address.class, key, "Addresses"); @@ -208,37 +234,43 @@ public GetByKeyRequestBuilder
getAddressesByKey(final Integer id) { @Override @Nonnull - public CreateRequestBuilder
createAddresses(@Nonnull final Address address) { + public CreateRequestBuilder
createAddresses( @Nonnull final Address address ) + { return new CreateRequestBuilder
(servicePath, address, "Addresses"); } @Override @Nonnull - public UpdateRequestBuilder
updateAddresses(@Nonnull final Address address) { + public UpdateRequestBuilder
updateAddresses( @Nonnull final Address address ) + { return new UpdateRequestBuilder
(servicePath, address, "Addresses"); } @Override @Nonnull - public DeleteRequestBuilder
deleteAddresses(@Nonnull final Address address) { + public DeleteRequestBuilder
deleteAddresses( @Nonnull final Address address ) + { return new DeleteRequestBuilder
(servicePath, address, "Addresses"); } @Override @Nonnull - public GetAllRequestBuilder getAllShelves() { + public GetAllRequestBuilder getAllShelves() + { return new GetAllRequestBuilder(servicePath, Shelf.class, "Shelves"); } @Override @Nonnull - public CountRequestBuilder countShelves() { + public CountRequestBuilder countShelves() + { return new CountRequestBuilder(servicePath, Shelf.class, "Shelves"); } @Override @Nonnull - public GetByKeyRequestBuilder getShelvesByKey(final Integer id) { + public GetByKeyRequestBuilder getShelvesByKey( final Integer id ) + { final Map key = new HashMap(); key.put("Id", id); return new GetByKeyRequestBuilder(servicePath, Shelf.class, key, "Shelves"); @@ -246,37 +278,43 @@ public GetByKeyRequestBuilder getShelvesByKey(final Integer id) { @Override @Nonnull - public CreateRequestBuilder createShelves(@Nonnull final Shelf shelf) { + public CreateRequestBuilder createShelves( @Nonnull final Shelf shelf ) + { return new CreateRequestBuilder(servicePath, shelf, "Shelves"); } @Override @Nonnull - public UpdateRequestBuilder updateShelves(@Nonnull final Shelf shelf) { + public UpdateRequestBuilder updateShelves( @Nonnull final Shelf shelf ) + { return new UpdateRequestBuilder(servicePath, shelf, "Shelves"); } @Override @Nonnull - public DeleteRequestBuilder deleteShelves(@Nonnull final Shelf shelf) { + public DeleteRequestBuilder deleteShelves( @Nonnull final Shelf shelf ) + { return new DeleteRequestBuilder(servicePath, shelf, "Shelves"); } @Override @Nonnull - public GetAllRequestBuilder getAllShopFloorShelves() { + public GetAllRequestBuilder getAllShopFloorShelves() + { return new GetAllRequestBuilder(servicePath, Shelf.class, "ShopFloorShelves"); } @Override @Nonnull - public CountRequestBuilder countShopFloorShelves() { + public CountRequestBuilder countShopFloorShelves() + { return new CountRequestBuilder(servicePath, Shelf.class, "ShopFloorShelves"); } @Override @Nonnull - public GetByKeyRequestBuilder getShopFloorShelvesByKey(final Integer id) { + public GetByKeyRequestBuilder getShopFloorShelvesByKey( final Integer id ) + { final Map key = new HashMap(); key.put("Id", id); return new GetByKeyRequestBuilder(servicePath, Shelf.class, key, "ShopFloorShelves"); @@ -284,37 +322,43 @@ public GetByKeyRequestBuilder getShopFloorShelvesByKey(final Integer id) @Override @Nonnull - public CreateRequestBuilder createShopFloorShelves(@Nonnull final Shelf shelf) { + public CreateRequestBuilder createShopFloorShelves( @Nonnull final Shelf shelf ) + { return new CreateRequestBuilder(servicePath, shelf, "ShopFloorShelves"); } @Override @Nonnull - public UpdateRequestBuilder updateShopFloorShelves(@Nonnull final Shelf shelf) { + public UpdateRequestBuilder updateShopFloorShelves( @Nonnull final Shelf shelf ) + { return new UpdateRequestBuilder(servicePath, shelf, "ShopFloorShelves"); } @Override @Nonnull - public DeleteRequestBuilder deleteShopFloorShelves(@Nonnull final Shelf shelf) { + public DeleteRequestBuilder deleteShopFloorShelves( @Nonnull final Shelf shelf ) + { return new DeleteRequestBuilder(servicePath, shelf, "ShopFloorShelves"); } @Override @Nonnull - public GetAllRequestBuilder getAllStorageShelves() { + public GetAllRequestBuilder getAllStorageShelves() + { return new GetAllRequestBuilder(servicePath, Shelf.class, "StorageShelves"); } @Override @Nonnull - public CountRequestBuilder countStorageShelves() { + public CountRequestBuilder countStorageShelves() + { return new CountRequestBuilder(servicePath, Shelf.class, "StorageShelves"); } @Override @Nonnull - public GetByKeyRequestBuilder getStorageShelvesByKey(final Integer id) { + public GetByKeyRequestBuilder getStorageShelvesByKey( final Integer id ) + { final Map key = new HashMap(); key.put("Id", id); return new GetByKeyRequestBuilder(servicePath, Shelf.class, key, "StorageShelves"); @@ -322,37 +366,43 @@ public GetByKeyRequestBuilder getStorageShelvesByKey(final Integer id) { @Override @Nonnull - public CreateRequestBuilder createStorageShelves(@Nonnull final Shelf shelf) { + public CreateRequestBuilder createStorageShelves( @Nonnull final Shelf shelf ) + { return new CreateRequestBuilder(servicePath, shelf, "StorageShelves"); } @Override @Nonnull - public UpdateRequestBuilder updateStorageShelves(@Nonnull final Shelf shelf) { + public UpdateRequestBuilder updateStorageShelves( @Nonnull final Shelf shelf ) + { return new UpdateRequestBuilder(servicePath, shelf, "StorageShelves"); } @Override @Nonnull - public DeleteRequestBuilder deleteStorageShelves(@Nonnull final Shelf shelf) { + public DeleteRequestBuilder deleteStorageShelves( @Nonnull final Shelf shelf ) + { return new DeleteRequestBuilder(servicePath, shelf, "StorageShelves"); } @Override @Nonnull - public GetAllRequestBuilder getAllOpeningHours() { + public GetAllRequestBuilder getAllOpeningHours() + { return new GetAllRequestBuilder(servicePath, OpeningHours.class, "OpeningHours"); } @Override @Nonnull - public CountRequestBuilder countOpeningHours() { + public CountRequestBuilder countOpeningHours() + { return new CountRequestBuilder(servicePath, OpeningHours.class, "OpeningHours"); } @Override @Nonnull - public GetByKeyRequestBuilder getOpeningHoursByKey(final Integer id) { + public GetByKeyRequestBuilder getOpeningHoursByKey( final Integer id ) + { final Map key = new HashMap(); key.put("Id", id); return new GetByKeyRequestBuilder(servicePath, OpeningHours.class, key, "OpeningHours"); @@ -360,19 +410,22 @@ public GetByKeyRequestBuilder getOpeningHoursByKey(final Integer i @Override @Nonnull - public CreateRequestBuilder createOpeningHours(@Nonnull final OpeningHours openingHours) { + public CreateRequestBuilder createOpeningHours( @Nonnull final OpeningHours openingHours ) + { return new CreateRequestBuilder(servicePath, openingHours, "OpeningHours"); } @Override @Nonnull - public UpdateRequestBuilder updateOpeningHours(@Nonnull final OpeningHours openingHours) { + public UpdateRequestBuilder updateOpeningHours( @Nonnull final OpeningHours openingHours ) + { return new UpdateRequestBuilder(servicePath, openingHours, "OpeningHours"); } @Override @Nonnull - public DeleteRequestBuilder deleteOpeningHours(@Nonnull final OpeningHours openingHours) { + public DeleteRequestBuilder deleteOpeningHours( @Nonnull final OpeningHours openingHours ) + { return new DeleteRequestBuilder(servicePath, openingHours, "OpeningHours"); } From 7611ba07a29de754a66d5c629da41f7ecbb1e667 Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Mon, 9 Mar 2026 13:20:16 +0200 Subject: [PATCH 05/12] style issue --- .../adapter/ODataGenericConverter.java | 85 +++++++++---------- 1 file changed, 40 insertions(+), 45 deletions(-) diff --git a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java index b64638691..9e84fff55 100644 --- a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java +++ b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java @@ -22,65 +22,48 @@ /** * Generic fluent helper converters for String based OData V4 primitives. * - * @param - * The Java type to which conversion happens. + * @param The Java type to which conversion happens. */ -@RequiredArgsConstructor( access = AccessLevel.PRIVATE ) -final class ODataGenericConverter extends AbstractTypeConverter -{ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +final class ODataGenericConverter extends AbstractTypeConverter { private static final ODataGenericConverter LOCAL_DATE = - new ODataGenericConverter<>( - LocalDate.class, - o -> o.format(DateTimeFormatter.ISO_LOCAL_DATE), - s -> LocalDate.parse(s, DateTimeFormatter.ISO_LOCAL_DATE)); + new ODataGenericConverter<>( + LocalDate.class, + o -> o.format(DateTimeFormatter.ISO_LOCAL_DATE), + s -> LocalDate.parse(s, DateTimeFormatter.ISO_LOCAL_DATE)); private static final ODataGenericConverter LOCAL_TIME = - new ODataGenericConverter<>( - LocalTime.class, - o -> o.format(DateTimeFormatter.ISO_LOCAL_TIME), - s -> LocalTime.parse(s, DateTimeFormatter.ISO_LOCAL_TIME)); + new ODataGenericConverter<>( + LocalTime.class, + o -> o.format(DateTimeFormatter.ISO_LOCAL_TIME), + s -> LocalTime.parse(s, DateTimeFormatter.ISO_LOCAL_TIME)); private static final ODataGenericConverter OFFSET_DATE_TIME = - new ODataGenericConverter<>( - OffsetDateTime.class, - o -> o.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), - s -> OffsetDateTime.parse(s, DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + new ODataGenericConverter<>( + OffsetDateTime.class, + o -> o.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), + s -> OffsetDateTime.parse(s, DateTimeFormatter.ISO_OFFSET_DATE_TIME)); private static final ODataGenericConverter GUID = - new ODataGenericConverter<>(UUID.class, UUID::toString, UUID::fromString); + new ODataGenericConverter<>(UUID.class, UUID::toString, UUID::fromString); private static final ODataGenericConverter DURATION = - new ODataGenericConverter<>(Duration.class, Duration::toString, Duration::parse); + new ODataGenericConverter<>(Duration.class, Duration::toString, Duration::parse); private static final ODataGenericConverter BINARY = - new ODataGenericConverter<>( - byte[].class, - Base64.getEncoder()::encodeToString, - ODataGenericConverter::decodeBinary); - - @Nonnull - private static byte[] decodeBinary( @Nonnull final String value ) - { - final boolean containsBase64Alphabet = value.indexOf('+') >= 0 || value.indexOf('/') >= 0; - final boolean containsBase64UrlAlphabet = value.indexOf('-') >= 0 || value.indexOf('_') >= 0; - - if( containsBase64Alphabet && containsBase64UrlAlphabet ) { - throw new IllegalArgumentException( - "Invalid binary value: mixed Base64 and Base64URL alphabets are not allowed."); - } - - final Base64.Decoder decoder = containsBase64UrlAlphabet ? Base64.getUrlDecoder() : Base64.getDecoder(); - return decoder.decode(value); - } + new ODataGenericConverter<>( + byte[].class, + Base64.getEncoder()::encodeToString, + ODataGenericConverter::decodeBinary); private static final ODataGenericConverter STRING = - new ODataGenericConverter<>(String.class, Function.identity(), Function.identity()); + new ODataGenericConverter<>(String.class, Function.identity(), Function.identity()); /** * Array of OData value converters for primitive types. */ public static final ODataGenericConverter[] DEFAULT_CONVERTERS = - { LOCAL_DATE, LOCAL_TIME, OFFSET_DATE_TIME, GUID, DURATION, BINARY, STRING }; + {LOCAL_DATE, LOCAL_TIME, OFFSET_DATE_TIME, GUID, DURATION, BINARY, STRING}; @Getter private final Class type; @@ -91,18 +74,30 @@ private static byte[] decodeBinary( @Nonnull final String value ) private final Function serializer; private final Function deserializer; + @Nonnull + private static byte[] decodeBinary(@Nonnull final String value) { + final boolean containsBase64Alphabet = value.indexOf('+') >= 0 || value.indexOf('/') >= 0; + final boolean containsBase64UrlAlphabet = value.indexOf('-') >= 0 || value.indexOf('_') >= 0; + + if (containsBase64Alphabet && containsBase64UrlAlphabet) { + throw new IllegalArgumentException( + "Invalid binary value: mixed Base64 and Base64URL alphabets are not allowed."); + } + + final Base64.Decoder decoder = containsBase64UrlAlphabet ? Base64.getUrlDecoder() : Base64.getDecoder(); + return decoder.decode(value); + } + @Nonnull @Override - public ConvertedObject toDomainNonNull( @Nonnull final JavaT object ) - { + public ConvertedObject toDomainNonNull(@Nonnull final JavaT object) { return ConvertedObject.of(serializer.apply(object)); } @Nonnull @Override - public ConvertedObject fromDomainNonNull( @Nonnull final String domainObject ) - { + public ConvertedObject fromDomainNonNull(@Nonnull final String domainObject) { final Try maybe = Try.of(() -> deserializer.apply(domainObject)); return maybe.map(ConvertedObject::of).getOrElse(ConvertedObject::ofNotConvertible); } -} +} \ No newline at end of file From e043b09fda6026cd30faaab2071cb8d21e7a15bb Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Mon, 9 Mar 2026 13:33:44 +0200 Subject: [PATCH 06/12] again formatting --- .../adapter/ODataGenericConverter.java | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java index 9e84fff55..c3d2949ba 100644 --- a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java +++ b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java @@ -22,48 +22,50 @@ /** * Generic fluent helper converters for String based OData V4 primitives. * - * @param The Java type to which conversion happens. + * @param + * The Java type to which conversion happens. */ -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -final class ODataGenericConverter extends AbstractTypeConverter { +@RequiredArgsConstructor( access = AccessLevel.PRIVATE ) +final class ODataGenericConverter extends AbstractTypeConverter +{ private static final ODataGenericConverter LOCAL_DATE = - new ODataGenericConverter<>( - LocalDate.class, - o -> o.format(DateTimeFormatter.ISO_LOCAL_DATE), - s -> LocalDate.parse(s, DateTimeFormatter.ISO_LOCAL_DATE)); + new ODataGenericConverter<>( + LocalDate.class, + o -> o.format(DateTimeFormatter.ISO_LOCAL_DATE), + s -> LocalDate.parse(s, DateTimeFormatter.ISO_LOCAL_DATE)); private static final ODataGenericConverter LOCAL_TIME = - new ODataGenericConverter<>( - LocalTime.class, - o -> o.format(DateTimeFormatter.ISO_LOCAL_TIME), - s -> LocalTime.parse(s, DateTimeFormatter.ISO_LOCAL_TIME)); + new ODataGenericConverter<>( + LocalTime.class, + o -> o.format(DateTimeFormatter.ISO_LOCAL_TIME), + s -> LocalTime.parse(s, DateTimeFormatter.ISO_LOCAL_TIME)); private static final ODataGenericConverter OFFSET_DATE_TIME = - new ODataGenericConverter<>( - OffsetDateTime.class, - o -> o.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), - s -> OffsetDateTime.parse(s, DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + new ODataGenericConverter<>( + OffsetDateTime.class, + o -> o.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), + s -> OffsetDateTime.parse(s, DateTimeFormatter.ISO_OFFSET_DATE_TIME)); private static final ODataGenericConverter GUID = - new ODataGenericConverter<>(UUID.class, UUID::toString, UUID::fromString); + new ODataGenericConverter<>(UUID.class, UUID::toString, UUID::fromString); private static final ODataGenericConverter DURATION = - new ODataGenericConverter<>(Duration.class, Duration::toString, Duration::parse); + new ODataGenericConverter<>(Duration.class, Duration::toString, Duration::parse); private static final ODataGenericConverter BINARY = - new ODataGenericConverter<>( - byte[].class, - Base64.getEncoder()::encodeToString, - ODataGenericConverter::decodeBinary); + new ODataGenericConverter<>( + byte[].class, + Base64.getEncoder()::encodeToString, + ODataGenericConverter::decodeBinary); private static final ODataGenericConverter STRING = - new ODataGenericConverter<>(String.class, Function.identity(), Function.identity()); + new ODataGenericConverter<>(String.class, Function.identity(), Function.identity()); /** * Array of OData value converters for primitive types. */ public static final ODataGenericConverter[] DEFAULT_CONVERTERS = - {LOCAL_DATE, LOCAL_TIME, OFFSET_DATE_TIME, GUID, DURATION, BINARY, STRING}; + { LOCAL_DATE, LOCAL_TIME, OFFSET_DATE_TIME, GUID, DURATION, BINARY, STRING }; @Getter private final Class type; @@ -75,13 +77,14 @@ final class ODataGenericConverter extends AbstractTypeConverter deserializer; @Nonnull - private static byte[] decodeBinary(@Nonnull final String value) { + private static byte[] decodeBinary( @Nonnull final String value ) + { final boolean containsBase64Alphabet = value.indexOf('+') >= 0 || value.indexOf('/') >= 0; final boolean containsBase64UrlAlphabet = value.indexOf('-') >= 0 || value.indexOf('_') >= 0; - if (containsBase64Alphabet && containsBase64UrlAlphabet) { + if( containsBase64Alphabet && containsBase64UrlAlphabet ) { throw new IllegalArgumentException( - "Invalid binary value: mixed Base64 and Base64URL alphabets are not allowed."); + "Invalid binary value: mixed Base64 and Base64URL alphabets are not allowed."); } final Base64.Decoder decoder = containsBase64UrlAlphabet ? Base64.getUrlDecoder() : Base64.getDecoder(); @@ -90,14 +93,16 @@ private static byte[] decodeBinary(@Nonnull final String value) { @Nonnull @Override - public ConvertedObject toDomainNonNull(@Nonnull final JavaT object) { + public ConvertedObject toDomainNonNull( @Nonnull final JavaT object ) + { return ConvertedObject.of(serializer.apply(object)); } @Nonnull @Override - public ConvertedObject fromDomainNonNull(@Nonnull final String domainObject) { + public ConvertedObject fromDomainNonNull( @Nonnull final String domainObject ) + { final Try maybe = Try.of(() -> deserializer.apply(domainObject)); return maybe.map(ConvertedObject::of).getOrElse(ConvertedObject::ofNotConvertible); } -} \ No newline at end of file +} From f630f8d042f7641b6919228acc316d386dc31ad6 Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Tue, 10 Mar 2026 13:29:09 +0200 Subject: [PATCH 07/12] normalizing done --- .../odatav4/adapter/ODataGenericConverter.java | 16 +++++----------- .../odatav4/core/FieldSerializationTest.java | 8 ++++++-- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java index c3d2949ba..549701b5d 100644 --- a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java +++ b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java @@ -56,7 +56,7 @@ final class ODataGenericConverter extends AbstractTypeConverter( byte[].class, Base64.getEncoder()::encodeToString, - ODataGenericConverter::decodeBinary); + ODataGenericConverter::normalizeBinary); private static final ODataGenericConverter STRING = new ODataGenericConverter<>(String.class, Function.identity(), Function.identity()); @@ -77,18 +77,12 @@ final class ODataGenericConverter extends AbstractTypeConverter deserializer; @Nonnull - private static byte[] decodeBinary( @Nonnull final String value ) + private static byte[] normalizeBinary( @Nonnull final String value ) { - final boolean containsBase64Alphabet = value.indexOf('+') >= 0 || value.indexOf('/') >= 0; - final boolean containsBase64UrlAlphabet = value.indexOf('-') >= 0 || value.indexOf('_') >= 0; + // Normalize URL-safe characters to standard Base64 + String normalized = value.replace('-', '+').replace('_', '/'); - if( containsBase64Alphabet && containsBase64UrlAlphabet ) { - throw new IllegalArgumentException( - "Invalid binary value: mixed Base64 and Base64URL alphabets are not allowed."); - } - - final Base64.Decoder decoder = containsBase64UrlAlphabet ? Base64.getUrlDecoder() : Base64.getDecoder(); - return decoder.decode(value); + return Base64.getDecoder().decode(normalized); } @Nonnull diff --git a/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java b/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java index 00dbcfb2b..23abf7811 100644 --- a/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java +++ b/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java @@ -207,14 +207,18 @@ void testCustomFieldParsingFromResponsePayload() } @Test - void testBinaryFieldParsingFromMixedBase64AlphabetFails() + void testBinaryFieldParsingFromMixedBase64AlphabetNormalized() { final ODataRequestResultGeneric result = mockRequestResult(ReferenceObject.PAYLOAD_ODATA_REFERENCE_MIXED_BASE64_ALPHABET); final ReferenceObject referenceResult = result.as(ReferenceObject.class); Objects.requireNonNull(referenceResult); - assertThat(referenceResult.getBinaryValue()).isNull(); + assertThat(referenceResult.getBinaryValue()).isEqualTo(new byte[] { (byte) 0xfb, (byte) 0xff, (byte) 0xef }); + + final String ser = + new CreateRequestBuilder<>("/", referenceResult, "EntityCollection").toRequest().getSerializedEntity(); + assertThat(ser).contains("\"BinaryValue\":\"+//v\""); } @SneakyThrows From ba9745cdd5b4e4e46aed7f5e79c266d0c723307f Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Tue, 10 Mar 2026 13:39:06 +0200 Subject: [PATCH 08/12] fix --- .../sdk/datamodel/odatav4/adapter/ODataGenericConverter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java index 549701b5d..ab7dac232 100644 --- a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java +++ b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java @@ -80,7 +80,7 @@ final class ODataGenericConverter extends AbstractTypeConverter Date: Tue, 10 Mar 2026 15:04:27 +0200 Subject: [PATCH 09/12] reverted name + updated tests --- .../datamodel/odatav4/adapter/ODataGenericConverter.java | 4 ++-- .../sdk/datamodel/odatav4/core/FieldSerializationTest.java | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java index ab7dac232..fb0639c1a 100644 --- a/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java +++ b/datamodel/odata-v4/odata-v4-core/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java @@ -56,7 +56,7 @@ final class ODataGenericConverter extends AbstractTypeConverter( byte[].class, Base64.getEncoder()::encodeToString, - ODataGenericConverter::normalizeBinary); + ODataGenericConverter::decodeBinary); private static final ODataGenericConverter STRING = new ODataGenericConverter<>(String.class, Function.identity(), Function.identity()); @@ -77,7 +77,7 @@ final class ODataGenericConverter extends AbstractTypeConverter deserializer; @Nonnull - private static byte[] normalizeBinary( @Nonnull final String value ) + private static byte[] decodeBinary( @Nonnull final String value ) { // Normalize URL-safe characters to standard Base64 final String normalized = value.replace('-', '+').replace('_', '/'); diff --git a/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java b/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java index 23abf7811..17ac5d5ad 100644 --- a/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java +++ b/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java @@ -8,6 +8,7 @@ import java.time.LocalDate; import java.time.LocalTime; import java.time.OffsetDateTime; +import java.util.Base64; import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -187,7 +188,7 @@ void testBinaryFieldParsingFromBase64UrlResponsePayload() final ReferenceObject referenceResult = result.as(ReferenceObject.class); Objects.requireNonNull(referenceResult); - assertThat(referenceResult.getBinaryValue()).isEqualTo(new byte[] { (byte) 0xfb, (byte) 0xff, (byte) 0xef }); + assertThat(referenceResult.getBinaryValue()).isEqualTo(Base64.getUrlDecoder().decode("-__v")); final String ser = new CreateRequestBuilder<>("/", referenceResult, "EntityCollection").toRequest().getSerializedEntity(); @@ -214,7 +215,7 @@ void testBinaryFieldParsingFromMixedBase64AlphabetNormalized() final ReferenceObject referenceResult = result.as(ReferenceObject.class); Objects.requireNonNull(referenceResult); - assertThat(referenceResult.getBinaryValue()).isEqualTo(new byte[] { (byte) 0xfb, (byte) 0xff, (byte) 0xef }); + assertThat(referenceResult.getBinaryValue()).isEqualTo(Base64.getDecoder().decode("+//v")); final String ser = new CreateRequestBuilder<>("/", referenceResult, "EntityCollection").toRequest().getSerializedEntity(); @@ -233,4 +234,4 @@ private static ODataRequestResultGeneric mockRequestResult( final String payload return new ODataRequestResultGeneric(request, response); } -} +} \ No newline at end of file From a266f592d61c499bf361bc5855b18da5bef0ae8f Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Tue, 10 Mar 2026 15:16:17 +0200 Subject: [PATCH 10/12] Format FieldSerializationTest --- .../sdk/datamodel/odatav4/core/FieldSerializationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java b/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java index 17ac5d5ad..23f54c897 100644 --- a/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java +++ b/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java @@ -234,4 +234,4 @@ private static ODataRequestResultGeneric mockRequestResult( final String payload return new ODataRequestResultGeneric(request, response); } -} \ No newline at end of file +} From fdc2eb3751016da4c9d0a00bddbe8ed0838415a2 Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Tue, 10 Mar 2026 15:40:07 +0200 Subject: [PATCH 11/12] minor --- .../odatav4/core/FieldSerializationTest.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java b/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java index 23f54c897..642b4656e 100644 --- a/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java +++ b/datamodel/odata-v4/odata-v4-core/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java @@ -165,6 +165,9 @@ public static class ReferenceObject extends VdmEntity private static final String PAYLOAD_ODATA_REFERENCE_MIXED_BASE64_ALPHABET = PAYLOAD_ODATA_REFERENCE.replace("\"BinaryValue\":\"AQID\"", "\"BinaryValue\":\"+__v\""); + + static final String Base_64 = "+//v"; + static final String Base_64_Url = "-__v"; } @Test @@ -188,11 +191,12 @@ void testBinaryFieldParsingFromBase64UrlResponsePayload() final ReferenceObject referenceResult = result.as(ReferenceObject.class); Objects.requireNonNull(referenceResult); - assertThat(referenceResult.getBinaryValue()).isEqualTo(Base64.getUrlDecoder().decode("-__v")); + assertThat(referenceResult.getBinaryValue()) + .isEqualTo(Base64.getUrlDecoder().decode(ReferenceObject.Base_64_Url)); final String ser = new CreateRequestBuilder<>("/", referenceResult, "EntityCollection").toRequest().getSerializedEntity(); - assertThat(ser).contains("\"BinaryValue\":\"+//v\""); + assertThat(ser).contains("\"BinaryValue\":\"" + ReferenceObject.Base_64 + "\""); } @Test @@ -215,11 +219,11 @@ void testBinaryFieldParsingFromMixedBase64AlphabetNormalized() final ReferenceObject referenceResult = result.as(ReferenceObject.class); Objects.requireNonNull(referenceResult); - assertThat(referenceResult.getBinaryValue()).isEqualTo(Base64.getDecoder().decode("+//v")); + assertThat(referenceResult.getBinaryValue()).isEqualTo(Base64.getDecoder().decode(ReferenceObject.Base_64)); final String ser = new CreateRequestBuilder<>("/", referenceResult, "EntityCollection").toRequest().getSerializedEntity(); - assertThat(ser).contains("\"BinaryValue\":\"+//v\""); + assertThat(ser).contains("\"BinaryValue\":\"" + ReferenceObject.Base_64 + "\""); } @SneakyThrows From d63fef4bfca29d48a9da0f77c66f79c251a52383 Mon Sep 17 00:00:00 2001 From: I538344 Date: Wed, 11 Mar 2026 09:15:54 +0100 Subject: [PATCH 12/12] Added release notes --- release_notes.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/release_notes.md b/release_notes.md index bc140eba2..9ef4c9e9d 100644 --- a/release_notes.md +++ b/release_notes.md @@ -14,6 +14,7 @@ ### ✨ New Functionality - [OpenAPI] Cloud SDK OpenAPI Generator now supports `apache-httpclient` library besides Spring RestTemplate through the newly introduced module `openapi-core-apache`. +- [IAS] Add `IasOptions.withTokenFormat()` to allow specifying token format ### 📈 Improvements @@ -21,4 +22,4 @@ ### 🐛 Fixed Issues -- +- [OData v4] Binary deserialization can now handle both `Base64URL` and `Base64`.